Publishing SNAPSHOT versions of Android libraries using Android Gradle Plugin 7+ - android-gradle-plugin

I have an Android library (gradle multi module project) where I publish to two different Nexus Maven2 repositories with strict versioning, a release repository and a snapshot repository.
This has worked well using Android Gradle Plugin 4.0.1.
command:
gradlew -PmavenRepoUrl=https://myserver.com/nexus/repository/project-snapshots -PmavenRepoCredentialType=PasswordType -PmavenUser=$NEXUS_USER -PmavenPassword=$NEXUS_PASSWORD publishDebugPublicationToMavenRepository
build.gradle.kts:
plugins {
id("com.android.library")
id("maven-publish")
...
}
val mavenRepoUrl: String by project
val mavenRepoCredentialType: String by project
val mavenPublicAccessKey: String by project
val mavenPublicSecretKey: String by project
val mavenUser: String by project
val mavenPassword: String by project
afterEvaluate {
publishing {
repositories {
maven(url = mavenRepoUrl) {
when (mavenRepoCredentialType) {
"AwsCredentialType" -> {
credentials(AwsCredentials::class) {
accessKey = mavenPublicAccessKey
secretKey = mavenPublicSecretKey
}
}
"PasswordType" -> {
credentials(PasswordCredentials::class) {
username = mavenUser
password = mavenPassword
}
}
else -> {
logger.log(ERROR, "Unsupported credential type: $mavenRepoCredentialType")
}
}
}
}
publications {
create<MavenPublication>("Release") {
from(components.getByName("release"))
groupId = project.group as String
artifactId = "my-android-project-${project.name}"
version = project.version as String
}
create<MavenPublication>("Debug") {
from(components.getByName("debug"))
groupId = project.group as String
artifactId = "my-android-project-${project.name}"
version = project.version as String
}
}
}
}
But after updating to Android Gradle Plugin 7.0+ I'm getting this error:
Execution failed for task ':core:publishDebugPublicationToMavenRepository'.
> Failed to publish publication 'Debug' to repository 'maven'
> Could not PUT 'https://myserver.com/nexus/repository/project-snapshots/com/myproject/1.1.0-M25/my-project-1.1.0-M25.aar'. Received status code 400 from server: Repository version policy: SNAPSHOT does not allow version: 1.1.0-M25
(When upgrading Android Gradle Plugin I had to remove the "isDebuggable" attribute from the build variants. Maybe that's a clue?).
To troubleshoot I tried explicitly setting the version to "-SNAPSHOT" for debug builds, build.gradle.kts:
publications {
create<MavenPublication>("Debug") {
...
version = (project.version as String) + "-SNAPSHOT"
...
}
}
but then I get this error:
* What went wrong:
Could not determine the dependencies of task ':my-module-2:publishDebugPublicationToMavenRepository'.
> Publishing is not able to resolve a dependency on a project with multiple publications that have different coordinates.
Found the following publications in project ':my-modulue':
- Maven publication 'Release' with coordinates com.myproject:my-module:1.1.0-M25
- Maven publication 'Debug' with coordinates com.myproject:my-module:1.1.0-M25-SNAPSHOT
(related: https://github.com/gradle/gradle/issues/12324)
My setup:
Multi module project with Android library modules
Android Gradle Plugin 7.0.1
Gradle 7.0.2, 7.1.1, 7.2 (doesn't work with any of them)
Kotlin Gradle DSL
Is this a regression caused by the Android Gradle Plugin or is there a way to solve my problem?

Related

How do you set the SoftwareComponent when publishing a Kotlin Multiplatform Library with the maven publish plugin?

I'm struggling to understand how the Maven publish plugin gets the right SoftwareComponent to publish when publishing a kotlin multiplatform project.
In a simple java project, configuring the plugin is as follows:
publishing {
publications {
create<MavenPublication>("maven") {
groupId = "org.gradle.sample"
artifactId = "library"
version = "1.1"
from(components["java"])
}
}
}
The SoftwareComponent is selected with the from(components["java"]) declaration.
The tutorial for publishing a kotlin multiplatform library skips this. Worse yet, when I leave it blank and publish to maven local, I get an empty pom.xml with no jar.
publishing {
publications {
register("lib", MavenPublication::class) {
...
}
}
repositories {
...
}
}
Even more confounding is if I change the above to this, I do get a sources jar, but I don't get publications for all the target platforms:
publishing {
publications {
withType<MavenPublication> {
...
}
}
repositories {
...
}
}
The only difference in the above is using withType<MavenPublication> versus register("lib", MavenPublication::class). I'm not sure why using either of them yields completely different results.
How does one properly set the SoftwareComponent when publishing a Kotlin Multiplatform Library with the maven publish plugin?
I turns out to properly configure the maven-publish plugin for publishing, care must be taken not to overwrite any properties in the maven publication block. That is
publishing {
publications {
withType<MavenPublication> {
// Don't do this. Set the group and version on the project/subproject level.
// The multiplatform plugin will read it appropriately.
groupId = "org.gradle.sample"
artifactId = "library"
version = "1.1"
}
}
}
Following the comment above fixed things for me.

Kotlin/Native compileKotlinIosX64 task fails when building iOS app

I have a Kotlin Multiplatform project. I recently updated to Kotlin 1.4-M2 (I need it to solve some issues with Ktor).
After updating all the required libraries, resolving all gradle issues and having my Android project compile successfully, I now encounter the following error when building the iOS app:
Task :shared:compileKotlinIosX64
e: Compilation failed: Could not find declaration for unbound symbol org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionPublicSymbolImpl#56f11f08
* Source files: [all shared folder kt files]
* Compiler version info: Konan: 1.4-M2 / Kotlin: 1.4.0
* Output kind: LIBRARY
e: java.lang.IllegalStateException: Could not find declaration for unbound symbol org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionPublicSymbolImpl#56f11f08
at org.jetbrains.kotlin.ir.util.ExternalDependenciesGeneratorKt.getDeclaration(ExternalDependenciesGenerator.kt:76)
The curious thing is that in Source files it shows all the files in the shared code folder. I checked and absolutely all kt files appear in there. So my guess is that it is some issue when building the shared code, but does not seem specific of any library.
This is a slightly reduced version of how my build.gradle.kts looks like:
plugins {
kotlin("multiplatform")
kotlin("native.cocoapods")
id("kotlinx-serialization")
id("com.android.library")
id("io.fabric")
}
// CocoaPods requires the podspec to have a version.
version = "1.0"
tasks {
withType<KotlinCompile> {
kotlinOptions {
jvmTarget = "1.8"
}
}
}
kotlin {
ios()
android()
cocoapods {
// Configure fields required by CocoaPods.
summary = "Some description for a Kotlin/Native module"
homepage = "Link to a Kotlin/Native module homepage"
}
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$serializationVersion")
api("org.kodein.di:kodein-di:7.1.0-kotlin-1.4-M3-84")
implementation("io.mockk:mockk:1.9.2")
api("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion")
api("com.russhwolf:multiplatform-settings:$multiplatformSettingsVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-json:$ktorVersion")
implementation("io.ktor:ktor-client-logging:$ktorVersion")
implementation("io.ktor:ktor-client-serialization:$ktorVersion")
}
}
}
}
And the library versions are as follows:
val ktorVersion = "1.3.2-1.4-M2"
val kotlinVersion = "1.4-M2"
val coroutinesVersion = "1.3.7-native-mt-1.4-M2"
val serializationVersion = "0.20.0-1.4-M2"
val multiplatformSettingsVersion = "0.6-1.4-M2"
It's worth mentioning this was building correctly in iOS when using 1.3.72.
As #KevinGalligan suggested, I updated Kotlin and all related libs to 1.4.0-rc and the problem was solved.
The root issue with 1.4-M2 remains unknown.

How to share Java code between Android and JVM targets (with Kotlin Multiplatform)?

I am trying to share Java code between Android and JVM targets using Kotlin Multiplatform feature (sample project: https://github.com/dmitrykolesnikovich/accessJavaCode-issue)
Simply saying, ":library1" and ":library2" both are Kotlin multiplatform libraries targeting JVM and Android. ":library2" depends on ":library1". They both uses Kotlin and Java. ":library2" is intended to be dependency of 1) Android application and 2) desktop (JavaFX) application. That's why 1) AAR artifact and 2) JAR artifact both are needed (?) - so I use 1) Android target and 2) JVM target for both ":library1" and ":library2".
The problem is that, when I have Java code in ":library1"
public class JavaCode {} // JavaCode.java
And Kotlin code in ":library2" that depends on ":library1"
class AccessJavaCode : JavaCode() // AccessJavaCode.kt
Android target is OK with recognizing Java but JVM target is not:
> Task :library2:compileKotlinJvm FAILED
e: AccessJavaCode.kt: (3, 38): Unresolved reference: JavaCode
In gradle config I define two plugins: kotlin-multiplatform and com.android.library:
apply plugin: "kotlin-multiplatform"
apply plugin: "com.android.library"
kotlin {
targets {
jvm()
android()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
}
}
androidMain {
dependsOn jvmMain
}
}
}
android {
compileSdkVersion 28
sourceSets {
main {
java.srcDirs += "src/jvmMain/kotlin" // Android target recognizes Java with this
manifest.srcFile "src/androidMain/AndroidManifest.xml"
}
}
}
I am pretty sure it’s something simple with my gradle file. Many thanks for your help guys.
~~~~ EDIT ~~~~
Another workaround the issue without losing the ability to generate an android archive .aar for the library1 would be to make a new version of this same library depending on precompiled artefacts of a splitted version of the original library1.
So you would end up with a multi-module gradle project, something like this:
library1-jvm with java plugin enabled
library1-android with android plugin enabled
library1 which will depends on prebuilt library1-jvm.jar and library1-android.aar
You could use whatever you prefer to publish those artefacts, but a local maven repo should work just fine!
That would mean replacing:
kotlin {
targets {
jvm()
android()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
}
}
androidMain {
dependsOn jvmMain
}
}
}
with:
kotlin {
targets {
jvm()
android()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
api "com.company:library-jvm:1.0.0"
}
}
androidMain {
dependsOn jvmMain
dependencies {
api "com.company:library-android:1.0.0"
}
}
}
}
That way, you don't need the java plugin at all in the final library1, because all the java code will already be built in a separate step.
Hence library1 could keep both the JVM and Android targets
~~~~~~~~~~~~~
In order to fix your issue, you need to:
Split your build.gradle configuration in order to have one config per library, this will be needed because you cannot enable the java plugin and the android at the same time for the same Gradle project, or you will end up with the following Error: The 'java' plugin has been applied, but it is not compatible with the Android plugins
Enable the java plugin in your library1 project if you want your JVM target to recognize your Java source files.
The Java source files need to be placed in the sibling directories java of the kotlin source roots.
More infos: Kotlin docs for java-support-in-jvm-targets
I also created a pull request solving your issue.
The downside of this approach is that you will not be able to generate an android archive .aar for the library1, but I guess using the java archive .jar in your android project should not be a problem at all.
library1/build.gradle:
apply plugin: "kotlin-multiplatform"
kotlin {
jvm {
withJava()
}
sourceSets {
jvmMain {
dependencies {
api kotlin("stdlib-common")
api kotlin("stdlib-jdk8")
}
}
}
}
library2/build.gradle:
apply plugin: "kotlin-multiplatform"
apply plugin: "com.android.library"
kotlin{
jvm()
android()
sourceSets {
jvmMain.dependencies {
api project(":library1")
}
androidMain {
dependsOn jvmMain
}
}
}
android {
compileSdkVersion 28
sourceSets {
main {
java.srcDirs += "src/jvmMain/kotlin"
manifest.srcFile "src/androidMain/AndroidManifest.xml"
}
}
}

Create fat jar from kotlin multiplatform project

I recently switched from old 1.2 multiplatform into 1.3. Difference is, there's one one build.gradle file per multiplatform module (I got 5 of them) so a lot less configuration.
However I can't seem to be able to configure creating runnable fat jar with all dependencies from jvm platform.
I used to use standard "application" plugin in my jvm project and jar task, but that does not work anymore. I found there's "jvmJar" task and I modified it (set Main-class), but created jar doesn't contain dependencies and crashes on ClassNotFoundException. How do I do it?
This is what I have now:
jvm() {
jvmJar {
manifest {
attributes 'Main-Class': 'eu.xx.Runner'
}
from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
I did hit that bump and used this work around.
1. Restructure your project
Lets call your project Project.
create another submodule say subA, which will have the gradle notation Project:subA
now, subA has your multiplatform code in it (It is the gradle project with apply :kotlin-multiplafrom) in its build.gradle
2. Add Another submodule
create another submodule which targets only jvm say subB, which will have the gradle notation Project:subB
So, subB will have plugins: 'application' and 'org.jetbrains.kotlin.jvm'
3. Add your module as a gradle dependency (see my build.gradle)
plugins {
id 'org.jetbrains.kotlin.jvm' version '1.3.31'
id "application"
}
apply plugin: "kotlinx-serialization"
group 'tz.or.self'
version '0.0.0'
mainClassName = "com.example.MainKt"
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
dependencies {
implementation project(':subA')
}
you can proceed and build subB as you would a regular java project or even use the existing plugins, it will work
Got it working with the multiplatform plugin in kotlin 1.3.61:
The following works for a main file in src/jvmMain/kotlin/com/example/Hello.kt
Hello.kt must also specify its package as package com.example
I configured my jvm target in this way:
kotlin {
targets {
jvm()
configure([jvm]) {
withJava()
jvmJar {
manifest {
attributes 'Main-Class': 'com.example.HelloKt'
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
}
}
Got it to work with a slightly modified version of what luca992 did:
kotlin {
jvm() {
withJava()
jvmJar {
manifest {
attributes 'Main-Class': 'sample.MainKt'
}
from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
}
}
...
}
The only way to get gradle/multiplatform working appears to be endless trial and error; It's a nightmare, it's not being built as a "build" system so much as a "build system"; to put it another way, these two tools (together or in isolation) are a means of implementing only a single software development life cycle that the plugin maker intended, however, if you've engineered a desired software lifecycle and CI/CD system and now your trying to implement that engineering, it will be MUCH harder to do it with these tools than it would be to do it with scripts, code or maven. There are a number of reasons for this:
Massive changing in coding convention due to the plugin makers only exposing bar minimum configurability, probably only giving access to the things they need for their own personal project.
Very poor documentation updates; Kotlin, gradle and plugins are changing so rapidly I have begun to seriously question the usefulness of these tools.
Thus, at the time of writing this seems to be the correct syntax to use when using kotlin 1.3.72, multiplatform 1.3.72, ktor 1.3.2 and gradle 6.2.2 (using the kts format).
Note the fatJar seems to assemble correctly but won't run, it can't find the class, so I included the second runLocally task I've been using in the mean time.
This isn't a complete solution so I hate posting it on here, but from what I can tell... it is the most complete and up to date solution I can find documented anywhere.
//Import variables from gradle.properties
val environment: String by project
val kotlinVersion: String by project
val ktorVersion: String by project
val kotlinExposedVersion: String by project
val mySqlConnectorVersion: String by project
val logbackVersion: String by project
val romeToolsVersion: String by project
val klaxonVersion: String by project
val kotlinLoggingVersion: String by project
val skrapeItVersion: String by project
val jsoupVersion: String by project
val devWebApiServer: String by project
val devWebApiServerVersion: String by project
//Build File Configuration
plugins {
java
kotlin("multiplatform") version "1.3.72"
}
group = "com.app"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
jcenter()
jcenter {
url = uri("https://kotlin.bintray.com/kotlin-js-wrappers")
}
maven {
url = uri("https://jitpack.io")
}
}
//Multiplatform Configuration
kotlin {
jvm {
compilations {
val main = getByName("main")
tasks {
register<Jar>("buildFatJar") {
group = "application"
manifest {
attributes["Implementation-Title"] = "Gradle Jar File Example"
attributes["Implementation-Version"] = archiveVersion
attributes["Main-Class"] = "com.app.BackendAppKt"
}
archiveBaseName.set("${project.name}-fat")
from(main.output.classesDirs, main.compileDependencyFiles)
with(jar.get() as CopySpec)
}
register<JavaExec>("runLocally") {
group = "application"
setMain("com.app.BackendAppKt")
classpath = main.output.classesDirs
classpath += main.compileDependencyFiles
}
}
}
}
js {
browser { EXCLUDED FOR LENGTH }
}
sourceSets { EXCLUDED FOR LENGTH }
}

Upgrading to Android Gradle Plugin 1.3 fails build due to check failure in Espresso tests

Before upgrading to Android Gradle Plugin 1.3, the custom lint scope was only Android source files.
After I upgraded to 1.3.1, my tests files started getting checked and as a scenario fails my custom lint rule, the build fails.
There is no documentation about this. I read some warnings have become fatal but nothing around test files getting scanned.
Anyone facing such an issue?
EDIT:
top level build.gradle
buildscript {
repositories {
mavenCentral()
maven { url 'http://download.crashlytics.com/maven' }
maven { url "https://plugins.gradle.org/m2/" }
}
dependencies {
classpath 'com.android.tools.build:gradle:1.3.1'
classpath 'org.sonarqube.gradle:gradle-sonarqube-plugin:1.0'
}
}
allprojects {
repositories {
flatDir {
dirs './prebuilt-libs'
}
mavenCentral()
}
}
task wrapper(type: Wrapper) {
gradleVersion = '2.4'
}
Error:
HardCoding: Detects HardCoded "abc" string
../../src/androidTest/java/com/xyz/mobile/trips/test.java:108: HardCoding Found. (This is my custom lint rule)
hasExtra(CODE, "abc")