Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative solution how to add compiler plugin dependency to the project #386

Merged
merged 12 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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() {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added some check that there is org.jetbrains.kotlin:atomicfu:1.9.20 dependency in the project classpath.

In the buildEnvironment we can also find the resolution string:
org.jetbrains.kotlin:atomicfu:1.6.20 -> 1.9.20

but probably just checking for the kotlin version is enough

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks to covering such a scenario!

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
Original file line number Diff line number Diff line change
Expand Up @@ -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(":")
}
Original file line number Diff line number Diff line change
Expand Up @@ -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("..")
Expand Down