Skip to content

Commit

Permalink
[Gradle] Check when Android library is published from kotlin managed
Browse files Browse the repository at this point in the history
components.

This can lead into errors and mistakes. Android requires users to
explicitly configure publication on AGP side.
For example BuildTypeAttribute may leak into publication.

So in this diagnostic we will warn users that doing publications
incorrectly.

^KT-70380 Verification Pending
  • Loading branch information
antohaby authored and qodana-bot committed Oct 23, 2024
1 parent 9b7fefd commit 8c02836
Show file tree
Hide file tree
Showing 14 changed files with 348 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ dependencies {
testCompileOnly(project(":kotlin-gradle-plugin-test-utils-embeddable"))
testRuntimeOnly(project(":kotlin-gradle-plugin-test-utils-embeddable")) { isTransitive = false }

// AGP classes for buildScriptInjection's
testCompileOnly(libs.android.gradle.plugin.gradle.api) { isTransitive = false }
testCompileOnly(libs.android.gradle.plugin.gradle) { isTransitive = false }


testImplementation(project(path = ":examples:annotation-processor-example"))
testImplementation(kotlinStdlib("jdk8"))
testImplementation(project(":kotlin-parcelize-compiler"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

package org.jetbrains.kotlin.gradle.android

import org.gradle.api.publish.maven.MavenPublication
import org.gradle.testkit.runner.BuildResult
import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
Expand Down Expand Up @@ -1068,4 +1069,38 @@ class KotlinAndroidMppIT : KGPBaseTest() {
}
}
}

@DisplayName("KT-70380: KMM App failed to consume android binary lib when published incorrectly")
@GradleAndroidTest
@AndroidTestVersions(additionalVersions = [TestVersions.AGP.AGP_81])
@GradleTestVersions(additionalVersions = [TestVersions.Gradle.G_8_1, TestVersions.Gradle.G_8_2, TestVersions.Gradle.G_8_3])
fun kotlinAndroidHasBuildTypeAttribute(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
) {
kotlinAndroidLibraryProject(gradleVersion, agpVersion, jdkVersion).apply {
buildScriptInjection {
applyMavenPublishPlugin()
publishing.publications.create("default", MavenPublication::class.java) { publication ->
publication.groupId = "com.example"
publication.artifactId = "lib"
publication.version = "1.0"

project.afterEvaluate {
publication.from(project.components.getByName("release"))
}
}
}

build("publish") {
if (agpVersion == TestVersions.AGP.AGP_73) {
// AGP 7.3 configures Publication automatically, so no diagnostic should be reported
assertNoDiagnostic(KotlinToolingDiagnostics.AndroidPublicationNotConfigured)
} else {
assertHasDiagnostic(KotlinToolingDiagnostics.AndroidPublicationNotConfigured)
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

package org.jetbrains.kotlin.gradle.testbase

import com.android.build.api.dsl.LibraryExtension
import org.gradle.api.Project
import org.gradle.api.plugins.JavaPluginExtension
import org.gradle.api.publish.PublishingExtension
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
import kotlin.io.path.appendText
import kotlin.io.path.exists
import kotlin.io.path.readText
Expand Down Expand Up @@ -76,7 +80,10 @@ annotation class BuildGradleKtsInjectionScope
class GradleBuildScriptInjectionContext(
val project: Project
) {
val kotlinMultiplatform get() = project.extensions.getByName("kotlin") as org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
val java get() = project.extensions.getByName("java") as JavaPluginExtension
val kotlinMultiplatform get() = project.extensions.getByName("kotlin") as KotlinMultiplatformExtension
val androidLibrary get() = project.extensions.getByName("android") as LibraryExtension
val publishing get() = project.extensions.getByName("publishing") as PublishingExtension
val dependencies get() = project.dependencies
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/

package org.jetbrains.kotlin.gradle.testbase

import org.gradle.api.publish.PublishingExtension
import org.gradle.jvm.toolchain.JavaLanguageVersion
import org.gradle.util.GradleVersion

fun KGPBaseTest.kotlinAndroidLibraryProject(
gradleVersion: GradleVersion,
agpVersion: String,
jdkVersion: JdkVersions.ProvidedJdk,
): TestProject {
return project(
"base-kotlin-android-library",
gradleVersion,
buildOptions = defaultBuildOptions.copy(androidVersion = agpVersion),
buildJdk = jdkVersion.location,
) {
buildScriptInjection { applyDefaultAndroidLibraryConfiguration() }
}
}

fun GradleBuildScriptInjectionContext.applyMavenPublishPlugin(): PublishingExtension {
project.plugins.apply("maven-publish")
publishing.repositories.apply {
maven { maven ->
maven.setUrl(project.layout.projectDirectory.dir("repo"))
}
}
return publishing
}

fun GradleBuildScriptInjectionContext.applyDefaultAndroidLibraryConfiguration() {
androidLibrary.apply {
compileSdk = 31
defaultConfig {
minSdk = 31
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
namespace = "org.jetbrains.kotlin.sample"
}

java.apply {
toolchain.languageVersion.set(JavaLanguageVersion.of(8))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
plugins {
id("com.android.library")
kotlin("android")
}

// To be filled by test code using buildScriptInjection API
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity android:name=".KotlinActivity"/>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/

package com.example

class AppDummy
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/

package com.example

import android.app.Activity

open class KotlinActivity : Activity()
Original file line number Diff line number Diff line change
Expand Up @@ -1166,6 +1166,11 @@ public final class org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingD
public final fun invoke (Lorg/jetbrains/kotlin/gradle/plugin/KotlinSourceSet;)Lorg/jetbrains/kotlin/gradle/plugin/diagnostics/ToolingDiagnostic;
}

public final class org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics$AndroidPublicationNotConfigured : org/jetbrains/kotlin/gradle/plugin/diagnostics/ToolingDiagnosticFactory {
public static final field INSTANCE Lorg/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics$AndroidPublicationNotConfigured;
public final fun invoke (Ljava/lang/String;Ljava/lang/String;)Lorg/jetbrains/kotlin/gradle/plugin/diagnostics/ToolingDiagnostic;
}

public final class org/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics$AndroidSourceSetLayoutV1Deprecation : org/jetbrains/kotlin/gradle/plugin/diagnostics/ToolingDiagnosticFactory {
public static final field INSTANCE Lorg/jetbrains/kotlin/gradle/plugin/diagnostics/KotlinToolingDiagnostics$AndroidSourceSetLayoutV1Deprecation;
public final fun invoke ()Lorg/jetbrains/kotlin/gradle/plugin/diagnostics/ToolingDiagnostic;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import org.gradle.util.GradleVersion
import org.jetbrains.kotlin.gradle.InternalKotlinGradlePluginApi
import org.jetbrains.kotlin.gradle.PRESETS_DEPRECATION_MESSAGE_SUFFIX
import org.jetbrains.kotlin.gradle.dsl.KotlinSourceSetConvention.isAccessedByKotlinSourceSetConventionAt
import org.jetbrains.kotlin.gradle.dsl.NativeTargetShortcutTrace
import org.jetbrains.kotlin.gradle.internal.KOTLIN_BUILD_TOOLS_API_IMPL
import org.jetbrains.kotlin.gradle.internal.KOTLIN_MODULE_GROUP
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
Expand Down Expand Up @@ -1027,6 +1026,29 @@ object KotlinToolingDiagnostics {
""".trimIndent()
)
}

object AndroidPublicationNotConfigured : ToolingDiagnosticFactory(WARNING) {
operator fun invoke(
componentName: String,
publicationName: String,
): ToolingDiagnostic = build(
"""
Android Publication '$publicationName' for variant '$componentName' was not configured properly:
To avoid this warning, please create and configure Android publication variant with name '$componentName'.
Example:
android {
publishing {
singleVariant("$componentName") {}
}
}
See https://kotl.in/oe70nr for more details
""".trimIndent(),
throwable = Throwable()
)
}
}

private fun String.indentLines(nSpaces: Int = 4, skipFirstLine: Boolean = true): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2024 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/

package org.jetbrains.kotlin.gradle.plugin.diagnostics.checkers

import org.gradle.api.publish.PublishingExtension
import org.gradle.api.publish.maven.MavenPublication
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
import org.jetbrains.kotlin.gradle.dsl.kotlinExtension
import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.Stage
import org.jetbrains.kotlin.gradle.plugin.await
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinGradleProjectChecker
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinGradleProjectCheckerContext
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnostics
import org.jetbrains.kotlin.gradle.plugin.diagnostics.KotlinToolingDiagnosticsCollector
import org.jetbrains.kotlin.gradle.plugin.internal.getComponentOrNull
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinTargetSoftwareComponent
import org.jetbrains.kotlin.gradle.utils.isPluginApplied

internal object AndroidPublicationNotConfiguredChecker : KotlinGradleProjectChecker {
override suspend fun KotlinGradleProjectCheckerContext.runChecks(collector: KotlinToolingDiagnosticsCollector) {
// kotlin("android") is applied
if (project.kotlinExtension !is KotlinAndroidProjectExtension) return
project.isPluginApplied("com.android.library") || return
project.isPluginApplied("maven-publish") || return

// After this stage, all publications are created and configured
Stage.AfterFinaliseDsl.await()

val publishing = project.extensions.getByName("publishing") as PublishingExtension
publishing.publications.withType(MavenPublication::class.java).configureEach { publication ->
val component = publication.getComponentOrNull(project) ?: return@configureEach

// This is heuristic, but still reliable check.
// When all three plugins are applied AND user configured AGP's publications correctly,
// then neither of the components should be `KotlinTargetComponent`
if (component !is KotlinTargetSoftwareComponent) return@configureEach

collector.report(
project,
KotlinToolingDiagnostics.AndroidPublicationNotConfigured(
// cast is needed to avoid ambiguity on getName()
componentName = component.name,
publicationName = publication.name,
)
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ abstract class AbstractKotlinTarget(

/**
* Returns, potentially not configured (e.g. without some usages), Gradle SoftwareComponent's for this target
* For final version of components use [awaitComponents]
*/
override val components: Set<KotlinTargetSoftwareComponent> by lazy {
kotlinComponents.map { kotlinComponent -> KotlinTargetSoftwareComponent(this, kotlinComponent) }.toSet()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ internal fun Project.registerKotlinPluginExtensions() {
register(project, OverriddenKotlinNativeHomeChecker)
register(project, ComposePluginSuggestApplyChecker)
register(project, NativeVersionChecker)
register(project, AndroidPublicationNotConfiguredChecker)

if (isMultiplatform) {
register(project, MultipleSourceSetRootsInCompilationChecker)
Expand Down
Loading

0 comments on commit 8c02836

Please sign in to comment.