Skip to content

Commit

Permalink
Move the logic from koltin repo atomicfu gradle plugin to the gradle …
Browse files Browse the repository at this point in the history
…plugin in the library. (#406)

The logic of the gradle plugin from the Kotlin repo `org.jetbrains.kotlinx.atomicfu.gradle.AtomicfuKotlinGradleSubplugin` that applies atomicfu compiler plugin transformations was moved to the gradle plugin in the library. 

Starting from version 0.24.0, the library will only be compatible with Kotlin 1.9.0 or newer. 

Fixes #370 

---------

Co-authored-by: Filipp Zhinkin <[email protected]>
  • Loading branch information
mvicsokolova and fzhinkin authored Mar 20, 2024
1 parent 16b9b93 commit 387c3db
Show file tree
Hide file tree
Showing 15 changed files with 272 additions and 91 deletions.
35 changes: 23 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,18 @@

## Requirements

Starting from version `0.23.2` of the library your project is required to use:
Starting from version `0.24.0` of the library your project is required to use:

* Gradle `7.0` or newer

* Kotlin `1.7.0` or newer
* Kotlin `1.9.0` or newer

> **Note on Kotlin version:** Currently, the `kotlinx-atomicfu` Gradle plugin only relies on the version of Kotlin Gradle Plugin (KGP) present in the user's project.
> It's important to note this constraint if your project configures the custom Kotlin compiler version or modifies the Kotlin Native compiler version using `kotlin.native.version` property.
>**In case, you cannot upgrade the Kotlin version to 1.9.0 or newer** in your project,
you can downgrade `kotlinx-atomicfu` plugin version to `0.22.0`.
> Please note, though, that using the latest version of the plugin and upgrading the Kotlin version is the more recommended approach.
## Features

Expand Down Expand Up @@ -105,8 +112,6 @@ operations. They can be also atomically modified via `+=` and `-=` operators.
### Apply plugin
#### Gradle configuration

Gradle configuration is supported for all platforms, minimal version is Gradle 6.8.

In top-level build file:

<details open>
Expand Down Expand Up @@ -256,36 +261,42 @@ IR for all the target backends:
* **Native**: atomics are implemented via atomic intrinsics on Kotlin/Native.
* **JS**: atomics are unboxed and represented as plain values.

To turn on IR transformation set these properties in your `gradle.properties` file:
To turn on IR transformations set the following properties in your `gradle.properties` file:

<details open>
<summary>For Kotlin >= 1.7.20</summary>
> Please note, that starting from version `0.24.0` of the library your project is required to use `Kotlin version >= 1.9.0`.
> See the [requirements section](#Requirements).
```groovy
kotlinx.atomicfu.enableJvmIrTransformation=true // for JVM IR transformation
kotlinx.atomicfu.enableNativeIrTransformation=true // for Native IR transformation
kotlinx.atomicfu.enableJsIrTransformation=true // for JS IR transformation
```

</details>
<details open>
<summary> Here are the configuration properties in case you use older versions of the library lower than 0.24.0. </summary>

<details>
For `Kotlin >= 1.7.20`

```groovy
kotlinx.atomicfu.enableJvmIrTransformation=true // for JVM IR transformation
kotlinx.atomicfu.enableNativeIrTransformation=true // for Native IR transformation
kotlinx.atomicfu.enableJsIrTransformation=true // for JS IR transformation
```

<summary> For Kotlin >= 1.6.20 and Kotlin < 1.7.20</summary>
For `Kotlin >= 1.6.20` and `Kotlin < 1.7.20`

```groovy
kotlinx.atomicfu.enableIrTransformation=true // only JS IR transformation is supported
```

</details>

Also for JS backend make sure that `ir` or `both` compiler mode is set:

```groovy
kotlin.js.compiler=ir // or both
```

</details>


## Options for post-compilation transformation

Expand Down
4 changes: 0 additions & 4 deletions atomicfu-gradle-plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ 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'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.atomicfu.plugin.gradle
Expand All @@ -20,33 +20,33 @@ import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.targets.js.*
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlinx.atomicfu.gradle.*
import java.io.*
import java.util.*
import javax.inject.Inject

private const val EXTENSION_NAME = "atomicfu"
private const val ORIGINAL_DIR_NAME = "originalClassesDir"
private const val COMPILE_ONLY_CONFIGURATION = "compileOnly"
private const val IMPLEMENTATION_CONFIGURATION = "implementation"
private const val TEST_IMPLEMENTATION_CONFIGURATION = "testImplementation"

// If the project uses KGP <= 1.6.20, only JS IR compiler plugin is available, and it is turned on via setting this property.
// The property is supported for backwards compatibility.
private const val ENABLE_JS_IR_TRANSFORMATION_LEGACY = "kotlinx.atomicfu.enableIrTransformation"
private const val ENABLE_JS_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJsIrTransformation"
private const val ENABLE_JVM_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJvmIrTransformation"
private const val ENABLE_NATIVE_IR_TRANSFORMATION = "kotlinx.atomicfu.enableNativeIrTransformation"
internal const val ENABLE_JS_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJsIrTransformation"
internal const val ENABLE_JVM_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJvmIrTransformation"
internal const val ENABLE_NATIVE_IR_TRANSFORMATION = "kotlinx.atomicfu.enableNativeIrTransformation"
private const val MIN_SUPPORTED_GRADLE_VERSION = "7.0"
private const val MIN_SUPPORTED_KGP_VERSION = "1.7.0"

open class AtomicFUGradlePlugin : Plugin<Project> {
override fun apply(project: Project) = project.run {
checkCompatibility()
// atomicfu version is stored at build time in atomicfu.properties file
// Atomicfu version is stored at build time in atomicfu.properties file
// located in atomicfu-gradle-plugin resources
val pluginVersion = loadPropertyFromResources("atomicfu.properties", "atomicfu.version")
extensions.add(EXTENSION_NAME, AtomicFUPluginExtension(pluginVersion))
applyAtomicfuCompilerPlugin()
val afuPluginVersion = loadPropertyFromResources("atomicfu.properties", "atomicfu.version")
checkCompatibility(afuPluginVersion)
extensions.add(EXTENSION_NAME, AtomicFUPluginExtension(afuPluginVersion))
// Apply Atomicfu compiler plugin
plugins.apply(AtomicfuKotlinCompilerPluginInternal::class.java)
configureDependencies()
configureTasks()
}
Expand All @@ -55,59 +55,40 @@ open class AtomicFUGradlePlugin : Plugin<Project> {
private fun loadPropertyFromResources(propFileName: String, property: String): String {
val props = Properties()
val inputStream = AtomicFUGradlePlugin::class.java.classLoader!!.getResourceAsStream(propFileName)
?: throw FileNotFoundException("You are applying `kotlinx-atomicfu` plugin of version 0.23.3 or newer, yet we were unable to determine the specific version of the plugin.\". \n" +
"Starting from version 0.23.3 of `kotlinx-atomicfu`, the plugin version is extracted from the `atomicfu.properties` file, which resides within the atomicfu-gradle-plugin-{version}.jar. \n" +
"However, this file couldn't be found. Please ensure that there are no atomicfu-gradle-plugin-{version}.jar with version older than 0.23.3 present on the classpath.\n" +
"If the problem is not resolved, please submit the issue: https://github.com/Kotlin/kotlinx-atomicfu/" )
?: throw FileNotFoundException(
"You are applying `kotlinx-atomicfu` plugin of version 0.24.0 or newer, yet we were unable to determine the specific version of the plugin.\". \n" +
"Starting from version 0.24.0 of `kotlinx-atomicfu`, the plugin version is extracted from the `atomicfu.properties` file, which resides within the atomicfu-gradle-plugin-{version}.jar. \n" +
"However, this file couldn't be found. Please ensure that there are no atomicfu-gradle-plugin-{version}.jar with version older than 0.24.0 present on the classpath.\n" +
"If the problem is not resolved, please submit the issue: https://github.com/Kotlin/kotlinx-atomicfu/issues"
)
inputStream.use { props.load(it) }
return props[property] as String
}

private fun Project.checkCompatibility() {
private fun Project.checkCompatibility(afuPluginVersion: String) {
val currentGradleVersion = GradleVersion.current()
val kotlinVersion = getKotlinVersion()
val minSupportedVersion = GradleVersion.version(MIN_SUPPORTED_GRADLE_VERSION)
if (currentGradleVersion < minSupportedVersion) {
throw GradleException(
"The current Gradle version is not compatible with Atomicfu gradle plugin. " +
"Please use Gradle $MIN_SUPPORTED_GRADLE_VERSION or newer, or the previous version of Atomicfu gradle plugin."
"Please use Gradle $MIN_SUPPORTED_GRADLE_VERSION or newer, or the previous version of Atomicfu gradle plugin."
)
}
if (!kotlinVersion.atLeast(1, 7, 0)) {
throw GradleException(
"The current Kotlin gradle plugin version is not compatible with Atomicfu gradle plugin. " +
"Please use Kotlin $MIN_SUPPORTED_KGP_VERSION or newer, or the previous version of Atomicfu gradle plugin."
if (!kotlinVersion.atLeast(1, 9, 0)) {
// Since Kotlin 1.9.0 the logic of the Gradle plugin from the Kotlin repo (AtomicfuKotlinGradleSubplugin)
// may be moved to the Gradle plugin in the library. The sources of the compiler plugin
// are published as `kotlin-atomicfu-compiler-plugin-embeddable` since Kotlin 1.9.0 and may be accessed out of the Kotlin repo.
error(
"You are applying `kotlinx-atomicfu` plugin of version $afuPluginVersion. " +
"However, this version of the plugin is only compatible with Kotlin versions newer than 1.9.0.\n" +
"If you wish to use this version of the plugin, please upgrade your Kotlin version to 1.9.0 or newer.\n" +
"In case you can not upgrade the Kotlin version, please read further instructions in the README: https://github.com/Kotlin/kotlinx-atomicfu/blob/master/README.md#requirements \n" +
"If you encounter any problems, please submit the issue: https://github.com/Kotlin/kotlinx-atomicfu/issues"
)
}
}

private fun Project.applyAtomicfuCompilerPlugin() {
val kotlinVersion = getKotlinVersion()
// for KGP >= 1.7.20:
// compiler plugin for JS IR is applied via the property `kotlinx.atomicfu.enableJsIrTransformation`
// compiler plugin for JVM IR is applied via the property `kotlinx.atomicfu.enableJvmIrTransformation`
if (kotlinVersion.atLeast(1, 7, 20)) {
plugins.apply(AtomicfuKotlinGradleSubplugin::class.java)
extensions.getByType(AtomicfuKotlinGradleSubplugin.AtomicfuKotlinGradleExtension::class.java).apply {
isJsIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION)
isJvmIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)
if (kotlinVersion.atLeast(1, 9, 20)) {
// Native IR transformation is available since Kotlin 1.9.20
isNativeIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_NATIVE_IR_TRANSFORMATION)
}
}
} else {
// for KGP >= 1.6.20 && KGP <= 1.7.20:
// compiler plugin for JS IR is applied via the property `kotlinx.atomicfu.enableIrTransformation`
// compiler plugin for JVM IR is not supported yet
if (kotlinVersion.atLeast(1, 6, 20)) {
if (rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY)) {
plugins.apply(AtomicfuKotlinGradleSubplugin::class.java)
}
}
}
}

private fun Project.configureDependencies() {
withPluginWhenEvaluatedDependencies("kotlin") { version ->
dependencies.add(
Expand All @@ -131,7 +112,8 @@ private fun Project.configureDependencies() {
}

private fun Project.configureMultiplatformPluginDependencies(version: String) {
val multiplatformExtension = kotlinExtension as? KotlinMultiplatformExtension ?: error("Expected kotlin multiplatform extension")
val multiplatformExtension =
kotlinExtension as? KotlinMultiplatformExtension ?: error("Expected kotlin multiplatform extension")
val atomicfuDependency = "org.jetbrains.kotlinx:atomicfu:$version"
multiplatformExtension.sourceSets.getByName("commonMain").dependencies {
compileOnly(atomicfuDependency)
Expand All @@ -152,7 +134,6 @@ private fun Project.configureMultiplatformPluginDependencies(version: String) {
}
}
}

// atomicfu should also appear in apiElements config for native targets,
// otherwise the warning is triggered, see: KT-64109
multiplatformExtension.targets
Expand Down Expand Up @@ -186,7 +167,7 @@ private fun KotlinVersion.atLeast(major: Int, minor: Int, patch: Int) =
// kotlinx-atomicfu compiler plugin is available for KGP >= 1.6.20
private fun Project.isCompilerPluginAvailable() = getKotlinVersion().atLeast(1, 6, 20)

private fun Project.getBooleanProperty(name: String) =
internal fun Project.getBooleanProperty(name: String) =
rootProject.findProperty(name)?.toString()?.toBooleanStrict() ?: false

private fun String.toBooleanStrict(): Boolean = when (this) {
Expand All @@ -195,30 +176,29 @@ private fun String.toBooleanStrict(): Boolean = when (this) {
else -> throw IllegalArgumentException("The string doesn't represent a boolean value: $this")
}

private fun Project.needsJsIrTransformation(target: KotlinTarget): Boolean =
internal fun Project.needsJsIrTransformation(target: KotlinTarget): Boolean =
(rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION) || rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY))
&& target.isJsIrTarget()

private fun Project.needsJvmIrTransformation(target: KotlinTarget): Boolean =
internal fun Project.needsJvmIrTransformation(target: KotlinTarget): Boolean =
rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION) &&
(target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm)
(target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm)

private fun Project.needsNativeIrTransformation(target: KotlinTarget): Boolean =
internal fun Project.needsNativeIrTransformation(target: KotlinTarget): Boolean =
rootProject.getBooleanProperty(ENABLE_NATIVE_IR_TRANSFORMATION) &&
(target.platformType == KotlinPlatformType.native)

(target.platformType == KotlinPlatformType.native)

private fun KotlinTarget.isJsIrTarget() =
(this is KotlinJsTarget && this.irTarget != null) ||
(this is KotlinJsIrTarget && this.platformType != KotlinPlatformType.wasm)
(this is KotlinJsIrTarget && this.platformType != KotlinPlatformType.wasm)

private fun Project.isTransitiveAtomicfuDependencyRequired(target: KotlinTarget): Boolean {
val platformType = target.platformType
return !config.transformJvm && (platformType == KotlinPlatformType.jvm || platformType == KotlinPlatformType.androidJvm) ||
!config.transformJs && platformType == KotlinPlatformType.js ||
platformType == KotlinPlatformType.wasm ||
// Always add the transitive atomicfu dependency for native targets, see #379
platformType == KotlinPlatformType.native
(!config.transformJs && platformType == KotlinPlatformType.js) ||
platformType == KotlinPlatformType.wasm ||
// Always add the transitive atomicfu dependency for native targets, see #379
platformType == KotlinPlatformType.native
}

// Adds kotlinx-atomicfu-runtime as an implementation dependency to the JS IR target:
Expand Down Expand Up @@ -319,7 +299,7 @@ private fun Project.configureJvmTransformation() {
if (kotlinExtension is KotlinJvmProjectExtension || kotlinExtension is KotlinAndroidProjectExtension) {
val target = (kotlinExtension as KotlinSingleTargetExtension<*>).target
if (!needsJvmIrTransformation(target)) {
configureTransformationForTarget(target)
configureTransformationForTarget(target)
}
}
}
Expand All @@ -334,11 +314,11 @@ private fun Project.configureJsTransformation() {
private fun Project.configureMultiplatformTransformation() =
withKotlinTargets { target ->
// Skip transformation for common, native and wasm targets or in case IR transformation by the compiler plugin is enabled (for JVM or JS targets)
if (target.platformType == KotlinPlatformType.common ||
if (target.platformType == KotlinPlatformType.common ||
target.platformType == KotlinPlatformType.native ||
target.platformType == KotlinPlatformType.wasm ||
needsJvmIrTransformation(target) || needsJsIrTransformation(target)
) {
) {
return@withKotlinTargets
}
configureTransformationForTarget(target)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright 2017-2024 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package kotlinx.atomicfu.plugin.gradle

import org.gradle.api.provider.Provider
import org.jetbrains.kotlin.gradle.plugin.*

/**
* This Gradle plugin applies compiler transformations to the project, it was copied from the kotlin repo (org.jetbrains.kotlinx.atomicfu.gradle.AtomicfuKotlinGradleSubplugin).
*
* As the sources of the compiler plugin are published as `org.jetbrains.kotlin.kotlin-atomicfu-compiler-plugin-embeddable` starting from Kotlin 1.9.0,
* the Gradle plugin can access this artifact from the library and apply the transformations.
*
* NOTE: The version of KGP may differ from the version of Kotlin compiler, and kotlin.native.version may override the version of Kotlin native compiler.
* So, the right behavior for the Gradle plugin would be to obtain compiler versions and apply compiler transformations separately to JVM/JS and Native targets.
* This was postponed as a separate task (#408).
*/
internal class AtomicfuKotlinCompilerPluginInternal : KotlinCompilerPluginSupportPlugin {

companion object {
const val ATOMICFU_ARTIFACT_NAME = "kotlin-atomicfu-compiler-plugin-embeddable"
}

override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean {
val target = kotlinCompilation.target
val project = target.project
return project.needsJvmIrTransformation(target) || project.needsJsIrTransformation(target) || project.needsNativeIrTransformation(target)
}

override fun applyToCompilation(
kotlinCompilation: KotlinCompilation<*>
): Provider<List<SubpluginOption>> = kotlinCompilation.target.project.provider { emptyList() }

override fun getCompilerPluginId() = "org.jetbrains.kotlinx.atomicfu"

// Gets "org.jetbrains.kotlin:kotlin-atomicfu-compiler-plugin-embeddable:{KGP version}"
override fun getPluginArtifact(): SubpluginArtifact {
return JetBrainsSubpluginArtifact(ATOMICFU_ARTIFACT_NAME)
}
}
13 changes: 13 additions & 0 deletions integration-testing/examples/mpp-version-catalog/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile

plugins {
// this is necessary to avoid the plugins to be loaded multiple times
// in each subproject's classloader
alias(libs.plugins.kotlinMultiplatform) apply false
}

tasks.withType<KotlinCompile<*>>().configureEach {
kotlinOptions {
freeCompilerArgs += listOf("-Xskip-prerelease-check")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
org.gradle.jvmargs=-Xmx2g -XX:MaxMetaspaceSize=2g
Loading

0 comments on commit 387c3db

Please sign in to comment.