Skip to content

Commit

Permalink
Alternative solution how to add compiler plugin dependency to the pro…
Browse files Browse the repository at this point in the history
…ject (#386)

* Do not require to add atomicfu compiler plugin dependency manually to the buildscript of the project. This change reverts #377. To prevent resolution conflicts of the compiler plugin version, atomicfu-gradle-plugin adds dependency to the earliest plugin version 1.6.20 and gradle should resolve it to the newest version present in the classpath of the user project. 

Fixes #384

* Old atomicfu-gradle-plugin tests: remove tasks that used to log classpath of the plugin for atomicfu-gradle-plugin tests, as those tests are already removed.

* Resolved the warning for compileOnly dependency to kotlin-stdlib present in Native sourceSets. kotlin-stdlib dependency is added as an implementation dependency now, though it's version is marked as prefered to avoid resolution in the user project.
  • Loading branch information
mvicsokolova authored Jan 15, 2024
1 parent d96a9e6 commit c5685d4
Show file tree
Hide file tree
Showing 17 changed files with 196 additions and 118 deletions.
13 changes: 1 addition & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:

<details open>
<summary>Kotlin DSL</summary>
Expand All @@ -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")
}
}

Expand All @@ -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'
}
}
Expand Down
69 changes: 4 additions & 65 deletions atomicfu-gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,76 +24,15 @@ 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'
testImplementation 'org.jetbrains.kotlin:kotlin-test-junit'
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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,42 +45,12 @@ open class AtomicFUGradlePlugin : Plugin<Project> {
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()
Expand Down
6 changes: 5 additions & 1 deletion atomicfu/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions integration-testing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
2 changes: 0 additions & 2 deletions integration-testing/examples/jvm-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Expand Down
2 changes: 0 additions & 2 deletions integration-testing/examples/mpp-sample/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
}

Expand Down
45 changes: 45 additions & 0 deletions integration-testing/examples/plugin-order-bug/build.gradle
Original file line number Diff line number Diff line change
@@ -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 {}
}
}
Original file line number Diff line number Diff line change
@@ -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
19 changes: 19 additions & 0 deletions integration-testing/examples/plugin-order-bug/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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 }
}
}
Original file line number Diff line number Diff line change
@@ -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))
}
}
Original file line number Diff line number Diff line change
@@ -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()}"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,6 @@ internal fun GradleBuild.checkConsumableDependencies() {
index++
}
}

internal fun GradleBuild.getProjectClasspath(): List<String> =
buildEnvironment().getDependenciesForConfig("classpath")
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,7 @@ internal class GradleBuild(val projectName: String, val targetDir: File) {

private var runCount = 0

fun runGradle(commands: List<String>): BuildResult =
buildGradleByShell(runCount++, commands, properties).also {
require(it.isSuccessful) { "Running $commands on project $projectName FAILED with error:\n" + it.output }
}
fun runGradle(commands: List<String>): BuildResult = buildGradleByShell(runCount++, commands, properties)
}

internal class BuildResult(exitCode: Int, private val logFile: File) {
Expand Down
Loading

0 comments on commit c5685d4

Please sign in to comment.