diff --git a/README.md b/README.md index 4feccca7..5d9ac161 100644 --- a/README.md +++ b/README.md @@ -107,12 +107,7 @@ operations. They can be also atomically modified via `+=` and `-=` operators. Gradle configuration is supported for all platforms, minimal version is Gradle 6.8. -To apply kotlinx-atomicfu plugin, you need to add 2 dependencies to the project classpath: -1. `atomicfu-gradle-plugin`: it provides the necessary library dependencies and manages transformation modes. -2. `atomicfu` compiler plugin: the compiler plugin is used to perform IR transformations (see [Atomicfu compiler plugin](#atomicfu-compiler-plugin) section). - -> Note: kotlinx-atomicfu gradle plugin does not have an id and is not currently published to the Gradle Plugin Portal. -> Therefore, the only available method for application is through the buildscript specification. +In top-level build file:
Kotlin DSL @@ -125,9 +120,6 @@ buildscript { dependencies { classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.23.1") - // Please note that the Kotlin version specified for the atomicfu compiler plugin - // should match the Kotlin version used in your project. - classpath("org.jetbrains.kotlin:atomicfu:$kotlin_version") } } @@ -145,9 +137,6 @@ buildscript { } dependencies { classpath 'org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.23.1' - // Please note that the Kotlin version specified for the atomicfu compiler plugin - // should match the Kotlin version used in your project. - classpath 'org.jetbrains.kotlin:atomicfu:$kotlin_version' } } diff --git a/atomicfu-gradle-plugin/build.gradle b/atomicfu-gradle-plugin/build.gradle index 1313f499..fca7953c 100644 --- a/atomicfu-gradle-plugin/build.gradle +++ b/atomicfu-gradle-plugin/build.gradle @@ -24,6 +24,10 @@ dependencies { // Atomicfu compiler plugin dependency will be loaded to kotlinCompilerPluginClasspath // Atomicfu plugin will only be applied if the flag is set kotlinx.atomicfu.enableJsIrTransformation=true compileOnly "org.jetbrains.kotlin:atomicfu:$kotlin_version" + // This runtimeOnly dependency is added as a temporary WA for the problem #384. + // The version 1.6.21 was chosen as the lowest valid version of this artifact, + // and it's expected to be resolved to the highest existing version from the user's classpath. + runtimeOnly "org.jetbrains.kotlin:atomicfu:1.6.21" testImplementation gradleTestKit() testImplementation 'org.jetbrains.kotlin:kotlin-test' @@ -31,69 +35,4 @@ dependencies { testImplementation 'junit:junit:4.12' } -configurations { - testPluginClasspath { - attributes { - attribute( - Usage.USAGE_ATTRIBUTE, - project.objects.named(Usage.class, Usage.JAVA_RUNTIME) - ) - attribute( - Category.CATEGORY_ATTRIBUTE, - project.objects.named(Category.class, Category.LIBRARY) - ) - } - } -} - -dependencies { - testPluginClasspath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" -} - evaluationDependsOn(':atomicfu') -def atomicfu = project(':atomicfu') -def atomicfuJvmJarTask = atomicfu.tasks.getByName(atomicfu.kotlin.targets.jvm.artifactsTaskName) - -def jsLegacy = atomicfu.kotlin.targets.hasProperty("jsLegacy") - ? atomicfu.kotlin.targets.jsLegacy - : atomicfu.kotlin.targets.js -def atomicfuJsJarTask = atomicfu.tasks.getByName(jsLegacy.artifactsTaskName) - -def atomicfuMetadataOutput = atomicfu.kotlin.targets.metadata.compilations["main"].output.allOutputs - -// Write the plugin's classpath to a file to share with the tests -task createClasspathManifest { - dependsOn(atomicfuJvmJarTask) - dependsOn(atomicfuJsJarTask) - dependsOn(atomicfuMetadataOutput) - - def outputDir = file("$buildDir/$name") - outputs.dir outputDir - - doLast { - outputDir.mkdirs() - file("$outputDir/plugin-classpath.txt").text = (sourceSets.main.runtimeClasspath + configurations.testPluginClasspath).join("\n") - file("$outputDir/atomicfu-jvm.txt").text = atomicfuJvmJarTask.archivePath - file("$outputDir/atomicfu-js.txt").text = atomicfuJsJarTask.archivePath - file("$outputDir/atomicfu-metadata.txt").text = atomicfuMetadataOutput.join("\n") - } -} - -task createKotlinRepoUrlResource { - def customKotlinRepoUrl = KotlinConfiguration.getCustomKotlinRepositoryURL(project) - if (customKotlinRepoUrl == null) return - - def outputDir = file("$buildDir/$name") - outputs.dir outputDir - - doLast { - outputDir.mkdirs() - file("$outputDir/kotlin-repo-url.txt").text = customKotlinRepoUrl - } -} - -// Add the classpath file to the test runtime classpath -dependencies { - testRuntimeOnly files(createClasspathManifest) - testRuntimeOnly files(createKotlinRepoUrlResource) -} diff --git a/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt b/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt index 393e09bf..42c2bc92 100644 --- a/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt +++ b/atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt @@ -45,42 +45,12 @@ open class AtomicFUGradlePlugin : Plugin { val pluginVersion = rootProject.buildscript.configurations.findByName("classpath") ?.allDependencies?.find { it.name == "atomicfu-gradle-plugin" }?.version extensions.add(EXTENSION_NAME, AtomicFUPluginExtension(pluginVersion)) - checkClasspathForAtomicfuCompilerPlugin(pluginVersion) applyAtomicfuCompilerPlugin() configureDependencies() configureTasks() } } -private fun Project.checkClasspathForAtomicfuCompilerPlugin(pluginVersion: String?) { - val kotlinVersion = getKotlinPluginVersion() - rootProject.buildscript.configurations.findByName("classpath") - ?.allDependencies?.find { it.group == "org.jetbrains.kotlin" && it.name == "atomicfu" }?.let { - require(it.version == kotlinVersion) { - "Please ensure that the Kotlin version specified for the atomicfu compiler plugin matches the Kotlin version used in your project. \n" + - "You should use this dependency in your classpath: classpath(\"org.jetbrains.kotlin:atomicfu:$kotlinVersion\")\n\n" + - "For details about plugin application, please refer to the README section at: https://github.com/Kotlin/kotlinx-atomicfu/blob/master/README.md#apply-plugin" - } - } - ?: error("Please add a dependency to the atomicfu compiler plugin in the buildscript classpath configuration, " + - "in addition to the atomicfu-gradle-plugin dependency:\n" + - "```\n" + - "buildscript {\n" + - " repositories {\n" + - " mavenCentral() \n" + - " }\n" + - "\n" + - " dependencies {\n" + - " classpath(\"org.jetbrains.kotlinx:atomicfu-gradle-plugin:$pluginVersion\")\n" + - " classpath(\"org.jetbrains.kotlin:atomicfu:$kotlinVersion\")\n" + - " }\n" + - "}\n\n" + - "apply(plugin = \"kotlinx-atomicfu\")\n" + - "```\n\n" + - "For details about plugin application, please refer to the README section at: https://github.com/Kotlin/kotlinx-atomicfu/blob/master/README.md#apply-plugin" - ) -} - private fun Project.checkCompatibility() { val currentGradleVersion = GradleVersion.current() val kotlinVersion = getKotlinVersion() diff --git a/atomicfu/build.gradle b/atomicfu/build.gradle index ee40e0d1..1ddd5858 100644 --- a/atomicfu/build.gradle +++ b/atomicfu/build.gradle @@ -50,7 +50,11 @@ kotlin { sourceSets { commonMain { dependencies { - compileOnly 'org.jetbrains.kotlin:kotlin-stdlib' + implementation('org.jetbrains.kotlin:kotlin-stdlib') { + version { + prefer "$kotlin_version" + } + } } } commonTest { diff --git a/integration-testing/build.gradle.kts b/integration-testing/build.gradle.kts index efb26b15..bfcc00da 100644 --- a/integration-testing/build.gradle.kts +++ b/integration-testing/build.gradle.kts @@ -64,6 +64,7 @@ val functionalTest by tasks.registering(Test::class) { testClassesDirs = sourceSets["functionalTest"].output.classesDirs classpath = sourceSets["functionalTest"].runtimeClasspath + systemProperties["kotlinVersion"] = kotlin_version systemProperties["atomicfuVersion"] = atomicfu_snapshot_version dependsOn(":atomicfu-gradle-plugin:publishToMavenLocal") diff --git a/integration-testing/examples/jvm-sample/build.gradle.kts b/integration-testing/examples/jvm-sample/build.gradle.kts index 937560bd..2b3f2d5d 100644 --- a/integration-testing/examples/jvm-sample/build.gradle.kts +++ b/integration-testing/examples/jvm-sample/build.gradle.kts @@ -6,9 +6,7 @@ buildscript { dependencies { val atomicfuVersion = libs.versions.atomicfuVersion.get() - val kotlinVersion = libs.versions.kotlinVersion.get() classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfuVersion") - classpath("org.jetbrains.kotlin:atomicfu:$kotlinVersion") } } diff --git a/integration-testing/examples/mpp-sample/build.gradle.kts b/integration-testing/examples/mpp-sample/build.gradle.kts index 9e5d864b..5871b3ac 100644 --- a/integration-testing/examples/mpp-sample/build.gradle.kts +++ b/integration-testing/examples/mpp-sample/build.gradle.kts @@ -12,9 +12,7 @@ buildscript { dependencies { val atomicfuVersion = libs.versions.atomicfuVersion.get() - val kotlinVersion = libs.versions.kotlinVersion.get() classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfuVersion") - classpath("org.jetbrains.kotlin:atomicfu:$kotlinVersion") } } diff --git a/integration-testing/examples/plugin-order-bug/build.gradle b/integration-testing/examples/plugin-order-bug/build.gradle new file mode 100644 index 00000000..958d5c79 --- /dev/null +++ b/integration-testing/examples/plugin-order-bug/build.gradle @@ -0,0 +1,45 @@ +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + + dependencies { + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${libs.versions.kotlinVersion.get()}") + classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${libs.versions.atomicfuVersion.get()}") + } +} +// Apply KGP via buildscript to check this issue: #384 +apply plugin: 'org.jetbrains.kotlin.multiplatform' +apply plugin: 'kotlinx-atomicfu' + +repositories { + mavenCentral() + maven{ url = "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" } + mavenLocal() +} + +kotlin { + jvm() + + js() + + wasmJs {} + wasmWasi {} + + macosArm64() + macosX64() + linuxArm64() + linuxX64() + mingwX64() + + sourceSets { + commonMain { + dependencies { + implementation(kotlin("stdlib")) + implementation(kotlin("test-junit")) + } + } + commonTest {} + } +} diff --git a/integration-testing/examples/plugin-order-bug/gradle.properties b/integration-testing/examples/plugin-order-bug/gradle.properties new file mode 100644 index 00000000..b75bc9ee --- /dev/null +++ b/integration-testing/examples/plugin-order-bug/gradle.properties @@ -0,0 +1,5 @@ +## +## Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. +## +kotlin_version=1.9.20 +atomicfu_version=0.23.1-SNAPSHOT diff --git a/integration-testing/examples/plugin-order-bug/settings.gradle.kts b/integration-testing/examples/plugin-order-bug/settings.gradle.kts new file mode 100644 index 00000000..55de7958 --- /dev/null +++ b/integration-testing/examples/plugin-order-bug/settings.gradle.kts @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + mavenLocal() + mavenCentral() + maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") + gradlePluginPortal() + } +} + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + version("atomicfuVersion", providers.gradleProperty("atomicfu_version").orNull) + version("kotlinVersion", providers.gradleProperty("kotlin_version").orNull) + } + } +} + +rootProject.name = "plugin-order-bug" diff --git a/integration-testing/examples/plugin-order-bug/src/commonMain/kotlin/AtomicSampleClass.kt b/integration-testing/examples/plugin-order-bug/src/commonMain/kotlin/AtomicSampleClass.kt new file mode 100644 index 00000000..d690d57b --- /dev/null +++ b/integration-testing/examples/plugin-order-bug/src/commonMain/kotlin/AtomicSampleClass.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package examples.mpp_sample + +import kotlinx.atomicfu.* +import kotlinx.atomicfu.locks.* +import kotlin.test.* + +public class AtomicSampleClass { + private val _x = atomic(0) + val x get() = _x.value + + public fun doWork(finalValue: Int) { + assertEquals(0, x) + assertEquals(0, _x.getAndSet(3)) + assertEquals(3, x) + assertTrue(_x.compareAndSet(3, finalValue)) + } + + private val lock = reentrantLock() + + public fun synchronizedFoo(value: Int): Int { + return lock.withLock { value } + } +} diff --git a/integration-testing/examples/plugin-order-bug/src/commonTest/kotlin/AtomicSampleTest.kt b/integration-testing/examples/plugin-order-bug/src/commonTest/kotlin/AtomicSampleTest.kt new file mode 100644 index 00000000..f7649ea4 --- /dev/null +++ b/integration-testing/examples/plugin-order-bug/src/commonTest/kotlin/AtomicSampleTest.kt @@ -0,0 +1,17 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +import kotlin.test.* +import examples.mpp_sample.* + +class AtomicSampleTest { + + @Test + fun testInt() { + val a = AtomicSampleClass() + a.doWork(1234) + assertEquals(1234, a.x) + assertEquals(42, a.synchronizedFoo(42)) + } +} diff --git a/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/cases/PluginOrderBugTest.kt b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/cases/PluginOrderBugTest.kt new file mode 100644 index 00000000..1ec0fd7f --- /dev/null +++ b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/cases/PluginOrderBugTest.kt @@ -0,0 +1,46 @@ +package kotlinx.atomicfu.gradle.plugin.test.cases + +import kotlinx.atomicfu.gradle.plugin.test.framework.checker.getProjectClasspath +import kotlinx.atomicfu.gradle.plugin.test.framework.runner.* +import kotlinx.atomicfu.gradle.plugin.test.framework.runner.GradleBuild +import kotlinx.atomicfu.gradle.plugin.test.framework.runner.cleanAndBuild +import kotlinx.atomicfu.gradle.plugin.test.framework.runner.createGradleBuildFromSources +import kotlin.test.* + +/** + * This test reproduces and tracks the issue #384. + */ +class PluginOrderBugTest { + private val pluginOrderBugProject: GradleBuild = createGradleBuildFromSources("plugin-order-bug") + + @Test + fun testUserProjectBuild() { + val buildResult = pluginOrderBugProject.cleanAndBuild() + assertTrue(buildResult.isSuccessful, buildResult.output) + } + + /** + * Ensures that the version of atomicfu compiler plugin in the project's classpath equals the version of KGP used in the project. + */ + @Test + fun testResolvedCompilerPluginDependency() { + val classpath = pluginOrderBugProject.getProjectClasspath() + assertTrue(classpath.contains("org.jetbrains.kotlin:atomicfu:${pluginOrderBugProject.getKotlinVersion()}")) + } + + /** + * kotlin-stdlib is an implementation dependency of :atomicfu module, + * because compileOnly dependencies are not applicable for Native targets (#376). + * + * This test ensures that kotlin-stdlib of the Kotlin version used to build kotlinx-atomicfu library is not "required" in the user's project. + * The user project should use kotlin-stdlib version that is present in it's classpath. + */ + @Test + fun testTransitiveKotlinStdlibDependency() { + val dependencies = pluginOrderBugProject.dependencies() + assertFalse(dependencies.output.contains("org.jetbrains.kotlin:kotlin-stdlib:{strictly $libraryKotlinVersion}"), + "Strict requirement for 'org.jetbrains.kotlin:kotlin-stdlib:{strictly $libraryKotlinVersion}' was found in the project ${pluginOrderBugProject.projectName} dependencies, " + + "while Kotlin version used in the project is ${pluginOrderBugProject.getKotlinVersion()}" + ) + } +} diff --git a/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/checker/DependenciesChecker.kt b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/checker/DependenciesChecker.kt index 55eb9e30..e459b2e7 100644 --- a/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/checker/DependenciesChecker.kt +++ b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/checker/DependenciesChecker.kt @@ -92,3 +92,6 @@ internal fun GradleBuild.checkConsumableDependencies() { index++ } } + +internal fun GradleBuild.getProjectClasspath(): List = + buildEnvironment().getDependenciesForConfig("classpath") diff --git a/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/BuildRunner.kt b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/BuildRunner.kt index 42cb46fc..809831d8 100644 --- a/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/BuildRunner.kt +++ b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/BuildRunner.kt @@ -27,10 +27,7 @@ internal class GradleBuild(val projectName: String, val targetDir: File) { private var runCount = 0 - fun runGradle(commands: List): BuildResult = - buildGradleByShell(runCount++, commands, properties).also { - require(it.isSuccessful) { "Running $commands on project $projectName FAILED with error:\n" + it.output } - } + fun runGradle(commands: List): BuildResult = buildGradleByShell(runCount++, commands, properties) } internal class BuildResult(exitCode: Int, private val logFile: File) { diff --git a/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/Commands.kt b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/Commands.kt index 1ea20237..a5455ce9 100644 --- a/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/Commands.kt +++ b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/Commands.kt @@ -4,9 +4,28 @@ package kotlinx.atomicfu.gradle.plugin.test.framework.runner +import kotlinx.atomicfu.gradle.plugin.test.framework.checker.getProjectClasspath + internal fun GradleBuild.cleanAndBuild(): BuildResult = runGradle(listOf("clean", "build")) -internal fun GradleBuild.dependencies(): BuildResult = runGradle(listOf("dependencies")) +internal fun GradleBuild.dependencies(): BuildResult = + runGradle(listOf("dependencies")).also { + require(it.isSuccessful) { "${this.projectName}:dependencies task FAILED: ${it.output} " } + } + +internal fun GradleBuild.buildEnvironment(): BuildResult = + runGradle(listOf("buildEnvironment")).also { + require(it.isSuccessful) { "${this.projectName}:buildEnvironment task FAILED: ${it.output} " } + } internal fun GradleBuild.publishToLocalRepository(): BuildResult = - runGradle(listOf("clean", "publish")) + runGradle(listOf("clean", "publish")).also { + require(it.isSuccessful) { "${this.projectName}:publish task FAILED: ${it.output} " } + } + +internal fun GradleBuild.getKotlinVersion(): String { + val classpath = getProjectClasspath() + val kpg = classpath.firstOrNull { it.startsWith("org.jetbrains.kotlin:kotlin-gradle-plugin") } + ?: error("kotlin-gradle-plugin is not found in the classpath of the project $projectName") + return kpg.substringAfterLast(":") +} diff --git a/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/Environment.kt b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/Environment.kt index c8c24e23..de117a30 100644 --- a/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/Environment.kt +++ b/integration-testing/src/functionalTest/kotlin/kotlinx.atomicfu.gradle.plugin.test/framework/runner/Environment.kt @@ -13,6 +13,7 @@ internal const val LOCAL_REPOSITORY_URL_PROPERTY = "localRepositoryUrl" internal const val DUMMY_VERSION = "DUMMY_VERSION" internal const val LOCAL_REPO_DIR_PREFIX = "build/.m2/" +internal val libraryKotlinVersion = System.getProperty("kotlinVersion") internal val atomicfuVersion = System.getProperty("atomicfuVersion") internal val gradleWrapperDir = File("..")