I'm developing a multi-module android project using gradle build.
I'm using maven-publish plugin to publish artifact to sonartype nexus.
The current setup is inside every module there is a publication job
plugins{
id(Plugins.kotlinAndroidApplication)
id(Plugins.kotlinAndroid)
id(Plugins.kotlinAndroidExtensions)
id(Plugins.mavenPublish)
id(Plugins.dokkaAndroid)
}
publishing {
repositories {
maven {
credentials {
username = project.properties["mavenUser"] as? String
password = project.properties["mavenPassword"] as? String
}
url = https://mynexus
}
}
publications {
create<MavenPublication>("mavenAar") {
groupId = rootProject.extra.get("groupId") as String
artifactId = rootProject.extra.get("artifactId") as String
version = (rootProject.extra.get("versionName") as String) + (if (project.hasProperty("release")) "" else "-SNAPSHOT")
from(components["android"])
//artifact(tasks["javadocJar"])
artifact(tasks["dokkaJar"])
}
}
}
When my Jenkin CI build the project with deploy = true, every module will be published. The problem is some module depend on other, so the dependency module will be published twice. Re-deploy module with same version is not allowed on release nexus maven repo.
Anyway to only publish certain module only or to ensure every module is only deploy once?
Related
I'm building a multiplatform library for Android and iOS. My gradle file looks like this:
plugins {
id 'org.jetbrains.kotlin.multiplatform' version '1.4.0'
}
repositories {
mavenCentral()
}
group 'com.example'
version '0.0.1'
apply plugin: 'maven-publish'
kotlin {
jvm()
// This is for iPhone simulator
// Switch here to iosArm64 (or iosArm32) to build library for iPhone device
ios {
binaries {
framework()
}
}
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib-common')
implementation("com.ionspin.kotlin:bignum:0.2.2")
}
}
commonTest {
dependencies {
implementation kotlin('test-common')
implementation kotlin('test-annotations-common')
}
}
jvmMain {
dependencies {
implementation("com.ionspin.kotlin:bignum:0.2.2")
}
}
jvmTest {
dependencies {
implementation kotlin('test')
implementation kotlin('test-junit')
}
}
iosMain {
}
iosTest {
}
}
}
configurations {
compileClasspath
}
Im using a third party library and I'm using it like this:
fun test(value: String): Int {
return BigDecimal.parseString(value).toBigInteger().intValue()
}
The problem is when I build the .jar the bignum library isn't included, and when I use the lib in an Android project I get an exception ClassNotFoundException: Didn't find class "com.ionspin.kotlin.bignum.decimal.BigDecimal".
Is there a way to include third party libs in the .jar for Android and .framework for iOS?
JVM
So, the only way I've found to generate a Fat JAR that works like you expect is by adding two custom gradle tasks in project:build.gradle.kts of your KMP library after appling the java plugin.
plugins {
[...]
id("java")
}
[...]
kotlin {
jvm {
[...]
compilations {
val main = getByName("main")
tasks {
register<Copy>("unzip") {
group = "library"
val targetDir = File(buildDir, "3rd-libs")
project.delete(files(targetDir))
main.compileDependencyFiles.forEach {
println(it)
if (it.path.contains("com.")) {
from(zipTree(it))
into(targetDir)
}
}
}
register<Jar>("fatJar") {
group = "library"
manifest {
attributes["Implementation-Title"] = "Fat Jar"
attributes["Implementation-Version"] = archiveVersion
}
archiveBaseName.set("${project.name}-fat")
val thirdLibsDir = File(buildDir, "3rd-libs")
from(main.output.classesDirs, thirdLibsDir)
with(jar.get() as CopySpec)
}
}
tasks.getByName("fatJar").dependsOn("unzip")
}
}
[...]
}
You then must launch the fatJar gradle task that generate a .jar file with the 3rd libraries classes extracted from they corresponding jar archives.
You can customize the two custom gradle scripts even more in order to better fit your needs (here I only included com. package name starting deps).
Then in your Android app app:build.gradle file you can use it as you did or simply
implementation files('libs/KMLibraryTest001-fat-1.0-SNAPSHOT.jar')
iOS
As you ask also for the iOS part in your title (even if it's a second citizen in the main topic of your question) you need only to use api instead of implementation for your 3rd party library along with the export option of the framework.
ios() {
binaries {
framework() {
transitiveExport = true // all libraries
//export(project(":LibA")) // this library project in a trainsitive way
//export("your 3rd party lib") // this 3rd party lib in a transitive way
}
}
}
And you can find a full reference here.
If you see the Krypto library, it has
androidMain
jsMain
jvmMain
mingwX64Main
nativPosixMain
Which means 5 kind of binaries are generated to support 5 platforms
Convincingly, this explains that each platform expects its own binary
for example,
windows -- DLL file
linux -- so file
java -- JAR file
mac -- dylib file
A JAR gets loaded into JVM, but IOS does not use JVM
Separate your Utility functions which has a common logic and write gradle to target multiple platforms
If you want to start with pure multiplatform, you can try this Official Example
Or create a sub gradle module and create a library project which is common to IOS as well as Android
The possible targets are properly documented here
I have created a application which publishes the binary to local repository and re-uses in the MainActivity -- you can get the code here
modify the local.properties for android SDK location and use
gradlew assemble
to build the APK and test it yourself
open the mylib\build.gradle.kts folder and you can see the targets jvm and iosX64 , jvm is used for android
If I'm correct using api instead of implementation should fix your problem, though I didn't try it out yet on the Native part
See Api and implementation separation
I'm trying to follow https://developer.android.com/studio/build/maven-publish-plugin to publish library (aar) module and application (apk) module to Maven repository (maven-publish plugin). I'm using Kotlin Gradle kts instead of Grovy.
Sample publish in Grovy (from link above)
publishing {
publications {
paidRelease(MavenPublication) {
// The following applies a component to this publication
// which results in publishing an app bundle.
from components.paidRelease_aab
groupId = 'com.example.MyApp'
artifactId = 'paid-release-aab'
version = '1.0'
}
paidDebug(MavenPublication) {
// The following applies a component to this publication
// which results in publishing APKs in a ZIP file.
from components.paidDebug_apk
groupId = 'com.example.MyApp'
artifactId = 'paid-debug-apks'
version = '1.0'
}
}
}
My kotlin dls publishing code for app module
publications {
create<MavenPublication>("mastercardMavenApk") {
groupId = ProjectInfo.groupId
artifactId = ProjectInfo.artifactId
version = ProjectInfo.nexusVersion + if (useReleaseRepo) "" else "-SNAPSHOT"
from(components["mastercardDebug_apk"])
}
}
where mastercardDebug is one of my Active Build Variant
I always have below error:
SoftwareComponentInternal with name 'mastercardDebug_apk' not found
What should be the correct way to use maven-publish plugin for Android project (support both aar and apk module)?
just use print(components.names) before from(components["mastercardDebug_apk"])
to verify that the component exsists. maybe its just a typo
I'm trying to upload a android library module from android studio,
followed by this blog: https://inthecheesefactory.com/blog/how-to-upload-library-to-jcenter-maven-central-as-dependency/en
(1)
./gradlew install
Result:- BUILD SUCCESSFUL
(2)
./gradlew build bintrayUpload
Result:- Getting below error-
FAILURE: Build failed with an exception.
What went wrong:
Execution failed for task ':acr:bintrayUpload'.
Could not create version '1.0.0': HTTP/1.1 401 Unauthorized [message:This resource requires authentication]
I checked many times and sure my username and apikey is correct.
(In username i'm using organization name instead of bintray username because my repository is created under organization).
If anyone has an idea, I would appreciate the help :)
In Bintray your username must be the username of your user account and not the Organisation.
If you are the owner of the repo then the permission mechanism will allow the action.
In username i'm using organization name
Some documentation links:
https://github.com/bintray/gradle-bintray-plugin#readme
https://bintray.com/docs/usermanual/formats/formats_mavenrepositories.html#_working_with_gradle
EDIT:
Make sure you are using the userOrg parameter, since your repo is under the organization subject and not under the user.
check step 4 here:
https://github.com/bintray/gradle-bintray-plugin#step-4-add-your-bintray-package-information-to-the-bintray-closure
Here is a working build.gradle:
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7'
}
}
plugins {
id "com.jfrog.bintray" version "1.7"
}
apply plugin: 'com.jfrog.bintray'
apply plugin: 'java'
bintray {
user = 'myuserusername'
key = '**********'
pkg {
repo = 'gradlerepo'
name = 'gradlepackage'
userOrg = 'myorgname'
version {
name = '1.0-Final'
}
}
}
I would like to add more to #gba's answer here
Instead of directly including your bintray username and apikey, you should include them in local.properties file at root of your project. The local.properties file is by default added to .gitignore and hence not uploaded to githup with other files. It helps in keeping your username and apikey safe.
bintray.user=<your bintray username>
bintray.apikey=<your bintray apikey>
Then in your module gradle file add:
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
bintray {
user = properties.getProperty("bintray.user")
key = properties.getProperty("bintray.apikey")
configurations = ['archives']
pkg {
repo = "maven"
name = "<Your library name>"
websiteUrl = <your siteUrl>
vcsUrl = gitUrl
licenses = ["Apache-2.0"]
publish = true
}
}
Reference: https://www.virag.si/2015/01/publishing-gradle-android-library-to-jcenter/
For my Android Library I'm using the "UploadArchives" task to deploy the AAR :
uploadArchives {
repositories.mavenDeployer {
configuration = configurations.deployLibrary
pom.groupId = groupId
pom.artifactId = artifactId
pom.version = version
uniqueVersion = false
repository(url: "${RemoteReleaseDest}")
snapshotRepository(url: 'file://' + "${localReleaseDest}") { }
}
}
Where "RemoteReleaseDest" is a remote Repository and "localReleaseDest" point to my Local maven repository (C:/User/me/.m2.repository).
this work pretty well for release version ex: myaar:2.0.0 ( I can as well set the "RemoteReleaseDest" to my local .m2 repository to force to deploy the release on it.
Then when I configure the dependency in my android project like this :
compile 'mygroupId:myartefact:2.0.0'
the dependency is correctly resolved (even from my local repository).
but if I try with snaptshot :
compile 'mygroupId:myartefact:2.0.0-SNAPSHOT'
gradle cannot resolve the dependency. while in the gradle log I can see that it is seaching in my .m2 local repository.
But if I Upload the snapshot archives with the parameter :
uniqueVersion = true
the gradle can resolve the dependency !
The problem in this case is that every time I launch the task "uploadArchives" I will create new AAR, xml, md5 etc... files under the local SNAPSHOT directory.
So why gradle cannot resolve the snapshot dependency when the snapshot is deployed with the param uniqueVersion = falseand how can I manage this issue ?
I've got this config to upload artifact to local maven repository.
uploadArchives {
repositories {
mavenDeployer {
pom {
groupId = 'group'
artifactId = 'android'
version = android.defaultConfig.versionName
}
repository(url: 'file://' + new File(System.getProperty('user.home'), '.m2/repository').absolutePath)
}
}
}
I want it to run all the unit tests before uploading the artifact. I'm tired of running the unit tests task manually every time, so I thought uploadArchives task should depend on testDebugUnitTest. The test tasks comes from Android library plugin.
uploadArchives {
dependsOn testDebugUnitTest
}
Unfortunately this configuration doesn't work. Is it possible to configure it the way I want?
I do this to automate my tests. The basic syntax here is <your task> dependsOn '<this task>' in order for your entire build process to finish successfully.
You want something like this:
uploadArchives.dependsOn 'testDebugUnitTest'