aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSadaf Ebrahimi <sadafebrahimi@google.com>2022-11-01 20:37:16 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2022-11-01 20:37:16 +0000
commit2fe11a0356c22378a2b1e690bc9ee456a44ba71a (patch)
tree1e310cb8cd77f2e9ec4f1e3728eaffb60a8a4666
parent8f4851d32831c91ff4f83e8c31242d5671e70305 (diff)
parent2282b1ddcdfb4f5c24fbbe0ce5f831b9ceaf683f (diff)
downloadkotlinx.atomicfu-2fe11a0356c22378a2b1e690bc9ee456a44ba71a.tar.gz
Upgrade kotlinx.atomicfu to 0.18.5 am: b943793454 am: 2282b1ddcdandroid-u-beta-1-gpl
Original change: https://android-review.googlesource.com/c/platform/external/kotlinx.atomicfu/+/2281219 Change-Id: I313dfddc8f437b3e1f4070bb15ea94d1a52578d8 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--CHANGES.md70
-rw-r--r--METADATA10
-rw-r--r--README.md424
-rw-r--r--RELEASE.md2
-rw-r--r--atomicfu-gradle-plugin/build.gradle35
-rw-r--r--atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt311
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/BaseKotlinGradleTest.kt29
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/EmptyProjectTest.kt14
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JsProjectTest.kt43
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JvmProjectTest.kt63
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/MppProjectTest.kt55
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/Project.kt99
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/Assert.kt32
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/TestDsl.kt139
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/utils.kt13
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/BaseKotlinGradleTest.kt108
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JsProjectTest.kt49
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JvmProjectTest.kt108
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/MppProjectTest.kt219
-rw-r--r--atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/utils.kt36
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/empty/build.gradle10
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/js-simple/build.gradle18
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/js-simple/js-simple.gradle.kts38
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/js-simple/settings.gradle.kts1
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/build.gradle30
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/gradle.properties1
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/jvm-simple.gradle.kts41
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/settings.gradle.kts1
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/build.gradle73
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_both3
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_js2
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_jvm1
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/mpp-simple.gradle.kts90
-rw-r--r--atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/settings.gradle.kts1
-rw-r--r--atomicfu-maven-plugin/build.gradle26
-rw-r--r--atomicfu-maven-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/TransformMojo.kt8
-rw-r--r--atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt13
-rw-r--r--atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt200
-rw-r--r--atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt104
-rw-r--r--atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/MetadataTransformer.kt3
-rw-r--r--atomicfu/build.gradle125
-rw-r--r--atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt112
-rw-r--r--atomicfu/src/jvmMain/java9/module-info.java6
-rw-r--r--atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/AtomicFU.kt122
-rw-r--r--atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/Interceptor.kt46
-rw-r--r--atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt482
-rw-r--r--atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt33
-rw-r--r--atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/TraceLFTest.kt76
-rw-r--r--atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/AtomicFU.kt16
-rw-r--r--atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/locks/Synchronized.kt2
-rw-r--r--build.gradle18
-rw-r--r--buildSrc/src/main/kotlin/Publishing.kt14
-rw-r--r--gradle.properties8
-rw-r--r--gradle/compile-options.gradle2
-rw-r--r--gradle/interop-as-source-set-klib.gradle2
-rw-r--r--gradle/publish-npm-js.gradle2
-rw-r--r--gradle/publishing.gradle12
-rw-r--r--gradle/wrapper/gradle-wrapper.properties2
58 files changed, 1866 insertions, 1737 deletions
diff --git a/CHANGES.md b/CHANGES.md
index 1ad8836..1303cb4 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,75 @@
# Change log for kotlinx.atomicfu
+# Version 0.18.5
+
+* Support JVM IR compiler plugin (#246).
+* Update Kotlin to 1.7.20.
+* Added more tests for atomicfu-gradle-plugin (#255).
+
+# Version 0.18.4
+
+* Fix KGP compatibility bug with freeCompilerArgs modification (#247).
+* Update kotlinx.metadata to 0.5.0 (#245).
+* Update gradle version to 6.8.3 (#244)
+
+# Version 0.18.3
+
+* Fix for atomicfu-gradle-plugin application to the MPP project (for Kotlin 1.7.20).
+
+# Version 0.18.2
+
+* In Kotlin 1.7.10 the name of `atomicfu-runtime` module was reverted back to `kotlinx-atomicfu-runtime`,
+ as the renaming was an incompatible change.
+ Fixed `atomicfu-gradle-plugin` to add `kotlinx-atomicfu-runtime` dependency directly.
+
+# Version 0.18.1
+
+* Fix for the compatibility issue: add `atomicfu-runtime` dependency directly since Kotlin 1.7.10.
+
+# Version 0.18.0
+
+* Update Kotlin to 1.7.0.
+* Fix kotlin 1.7 compatibility (#222).
+* Update JVM target to 1.8 (see KT-45165).
+* Fix for parsing Kotlin version in AtomicfuGradlePlugin.
+
+# Version 0.17.3
+
+* Adding compiler plugin dependency only for projects with KGP >= 1.6.20 (#226).
+* Compiler plugin runtime dependency fixes (#230).
+* Update README badges (#228).
+
+# Version 0.17.2
+
+* Update Kotlin to 1.6.20.
+* IR transformation for Kotlin/JS. (#215).
+* Update ASM to 9.3 for Java 18 support (#223)
+* Update kotlinx.metadata to 0.4.2.
+
+# Version 0.17.1
+
+* Support of `org.jetbrains.kotlin.js` plugin (#218).
+* Fixed configuration cache bug. (#216).
+* Bug fixes for delegated fields support (#179).
+
+# Version 0.17.0
+
+* Update Kotlin to 1.6.0.
+* Update ASM minimal api version to ASM7 (#203).
+* Add explicit module-info for JPMS compatibility (#201).
+
+# Version 0.16.3
+
+* Kotlin is updated to 1.5.30.
+* All references to Bintray are removed from artefacts POMs.
+* Added new Apple Silicon targets for K/N.
+
+# Version 0.16.2
+
+* Update Kotlin to 1.5.20.
+* ASM 9.1 for Java 15+ support (#190).
+* Removing extra atomicfu references from LVT.
+
# Version 0.16.0
* Update Kotlin to 1.5.0.
diff --git a/METADATA b/METADATA
index c25731d..ef605b9 100644
--- a/METADATA
+++ b/METADATA
@@ -1,3 +1,7 @@
+# This project was upgraded with external_updater.
+# Usage: tools/external_updater/updater.sh update kotlinx.atomicfu
+# For more info, check https://cs.android.com/android/platform/superproject/+/master:tools/external_updater/README.md
+
name: "kotlinx.atomicfu"
description: "The idiomatic way to use atomic operations in Kotlin."
third_party {
@@ -9,11 +13,11 @@ third_party {
type: GIT
value: "https://github.com/Kotlin/kotlinx.atomicfu"
}
- version: "0.14.4"
+ version: "0.18.5"
license_type: NOTICE
last_upgrade_date {
- year: 2020
+ year: 2022
month: 11
- day: 25
+ day: 1
}
}
diff --git a/README.md b/README.md
index 3f86a27..e3dd5b3 100644
--- a/README.md
+++ b/README.md
@@ -1,30 +1,49 @@
# AtomicFU
-[![JetBrains incubator project](https://jb.gg/badges/incubator.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
+[![Kotlin Beta](https://kotl.in/badges/beta.svg)](https://kotlinlang.org/docs/components-stability.html)
+[![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)
-[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.atomicfu/images/download.svg) ](https://bintray.com/kotlin/kotlinx/kotlinx.atomicfu/_latestVersion)
-
-The idiomatic way to use atomic operations in Kotlin.
-
-* Code it like `AtomicReference/Int/Long`, but run it in production efficiently as `AtomicXxxFieldUpdater` on Kotlin/JVM
- and as plain unboxed values on Kotlin/JS.
-* Use Kotlin-specific extensions (e.g. inline `updateAndGet` and `getAndUpdate` functions).
-* Compile-time dependency only (no runtime dependencies).
- * Post-compilation bytecode transformer that declares all the relevant field updaters for you on [Kotlin/JVM](#jvm).
- * Post-compilation JavaScript files transformer on [Kotlin/JS](#js).
-* Multiplatform:
- * [Kotlin/Native](#native) is supported.
- * However, Kotlin/Native works as library dependency at the moment (unlike Kotlin/JVM and Kotlin/JS).
- * This enables writing [common](#common) Kotlin code with atomics that compiles for JVM, JS, and Native.
-* [Gradle](#gradle-build-setup) for all platforms and [Maven](#maven-build-setup) for JVM are supported.
-* [Additional features](#additional-features) include:
- * [JDK9 VarHandle](#varhandles-with-java-9).
- * [Arrays of atomic values](#arrays-of-atomic-values).
- * [User-defined extensions on atomics](#user-defined-extensions-on-atomics)
- * [Locks](#locks)
- * [Testing of lock-free data structures](#testing-lock-free-data-structures-on-jvm).
- * [Tracing operations](#tracing-operations)
-
+[![Maven Central](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/atomicfu)](https://search.maven.org/artifact/org.jetbrains.kotlinx/atomicfu/0.18.5/pom)
+
+>Note on Beta status: the plugin is in its active development phase and changes from release to release.
+>We do provide a compatibility of atomicfu-transformed artifacts between releases, but we do not provide
+>strict compatibility guarantees on plugin API and its general stability between Kotlin versions.
+
+**Atomicfu** is a multiplatform library that provides the idiomatic and effective way of using atomic operations in Kotlin.
+
+## Table of contents
+- [Features](#features)
+- [Example](#example)
+- [Quickstart](#quickstart)
+ - [Apply plugin to a project](#apply-plugin)
+ - [Gradle configuration](#gradle-configuration)
+ - [Maven configuration](#maven-configuration)
+- [Usage constraints](#usage-constraints)
+- [Transformation modes](#transformation-modes)
+ - [Atomicfu compiler plugin](#atomicfu-compiler-plugin)
+- [Options for post-compilation transformation](#options-for-post-compilation-transformation)
+ - [JVM options](#jvm-options)
+ - [JS options](#js-options)
+- [More features](#more-features)
+ - [Arrays of atomic values](#arrays-of-atomic-values)
+ - [User-defined extensions on atomics](#user-defined-extensions-on-atomics)
+ - [Locks](#locks)
+ - [Tracing operations](#tracing-operations)
+- [Kotlin/Native support](#kotlin-native-support)
+
+
+## Features
+
+* Code it like a boxed value `atomic(0)`, but run it in production efficiently:
+ * as `java.util.concurrent.atomic.AtomicXxxFieldUpdater` on Kotlin/JVM
+ * as a plain unboxed value on Kotlin/JS
+* Multiplatform: write common Kotlin code with atomics that compiles for Kotlin JVM, JS, and Native backends:
+ * Compile-only dependency for JVM and JS (no runtime dependencies)
+ * Compile and runtime dependency for Kotlin/Native
+* Use Kotlin-specific extensions (e.g. inline `loop`, `update`, `updateAndGet` functions).
+* Use atomic arrays, user-defined extensions on atomics and locks (see [more features](#more-features)).
+* [Tracing operations](#tracing-operations) for debugging.
+
## Example
Let us declare a `top` variable for a lock-free stack implementation:
@@ -74,107 +93,67 @@ val myLong = atomic(0L) // note: long initial value
Integer and long atomics provide all the usual `getAndIncrement`, `incrementAndGet`, `getAndAdd`, `addAndGet`, and etc
operations. They can be also atomically modified via `+=` and `-=` operators.
-## Dos and Don'ts
-
-* Declare atomic variables as `private val` or `internal val`. You can use just (public) `val`,
- but make sure they are not directly accessed outside of your Kotlin module (outside of the source set).
- Access to the atomic variable itself shall be encapsulated.
-* Only simple operations on atomic variables _directly_ are supported.
- * Do not read references on atomic variables into local variables,
- e.g. `top.compareAndSet(...)` is Ok, while `val tmp = top; tmp...` is not.
- * Do not leak references on atomic variables in other way (return, pass as params, etc).
-* Do not introduce complex data flow in parameters to atomic variable operations,
- i.e. `top.value = complex_expression` and `top.compareAndSet(cur, complex_expression)` are not supported
- (more specifically, `complex_expression` should not have branches in its compiled representation).
- Extract `complex_expression` into a variable when needed.
-* Use the following convention if you need to expose the value of atomic property to the public:
-
-```kotlin
-private val _foo = atomic<T>(initial) // private atomic, convention is to name it with leading underscore
-public var foo: T by _foo // public delegated property (val/var)
-```
-
-## Gradle build setup
+## Quickstart
+### Apply plugin
+#### Gradle configuration
-Building with Gradle is supported for all platforms.
+Gradle configuration is supported for all platforms, minimal version is Gradle 6.8.
-### JVM
+In top-level build file:
-You will need Gradle 4.10 or later.
-Add and apply AtomicFU plugin. It adds all the corresponding dependencies
-and transformations automatically.
-See [additional configuration](#additional-configuration) if that needs tweaking.
+<details open>
+<summary>Kotlin</summary>
-```groovy
+```kotlin
buildscript {
- ext.atomicfu_version = '0.16.0'
+ repositories {
+ mavenCentral()
+ }
dependencies {
- classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
+ classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5")
}
}
-apply plugin: 'kotlinx-atomicfu'
+apply(plugin = "kotlinx-atomicfu")
```
+</details>
-### JS
-
-Configure add apply plugin just like for [JVM](#jvm).
-
-### Native
-
-This library is available for Kotlin/Native (`atomicfu-native`).
-Kotlin/Native uses Gradle metadata and needs Gradle version 5.3 or later.
-See [Gradle Metadata 1.0 announcement](https://blog.gradle.org/gradle-metadata-1.0) for more details.
-Apply the corresponding plugin just like for [JVM](#jvm).
-
-Atomic references for Kotlin/Native are based on
-[FreezableAtomicReference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-freezable-atomic-reference/-init-.html)
-and every reference that is stored to the previously
-[frozen](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/freeze.html)
-(shared with another thread) atomic is automatically frozen, too.
-
-Since Kotlin/Native does not generally provide binary compatibility between versions,
-you should use the same version of Kotlin compiler as was used to build AtomicFU.
-See [gradle.properties](gradle.properties) in AtomicFU project for its `kotlin_version`.
-
-### Common
-
-If you write a common code that should get compiled or different platforms, add `org.jetbrains.kotlinx:atomicfu-common`
-to your common code dependencies or apply `kotlinx-atomicfu` plugin that adds this dependency automatically:
+<details>
+<summary>Groovy</summary>
```groovy
-dependencies {
- compile "org.jetbrains.kotlinx:atomicfu-common:$atomicfu_version"
+buildscript {
+ repositories {
+ mavenCentral()
+ }
+ dependencies {
+ classpath 'org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.18.5'
+ }
}
+
+apply plugin: 'kotlinx-atomicfu'
```
+</details>
-### Additional configuration
-
-There are the following additional parameters (with their defaults):
+#### Maven configuration
-```groovy
-atomicfu {
- dependenciesVersion = '0.16.0' // set to null to turn-off auto dependencies
- transformJvm = true // set to false to turn off JVM transformation
- transformJs = true // set to false to turn off JS transformation
- variant = "FU" // JVM transformation variant: FU,VH, or BOTH
- verbose = false // set to true to be more verbose
-}
-```
+Maven configuration is supported for JVM projects.
-## Maven build setup
-Declare AtomicFU version:
+<details open>
+<summary>Declare atomicfu version</summary>
```xml
<properties>
- <atomicfu.version>0.16.0</atomicfu.version>
+ <atomicfu.version>0.18.5</atomicfu.version>
</properties>
```
-Declare _provided_ dependency on the AtomicFU library
-(the users of the resulting artifact will not have a dependency on AtomicFU library):
+</details>
+
+<details>
+<summary>Declare provided dependency on the AtomicFU library</summary>
```xml
<dependencies>
@@ -187,71 +166,161 @@ Declare _provided_ dependency on the AtomicFU library
</dependencies>
```
+</details>
+
Configure build steps so that Kotlin compiler puts classes into a different `classes-pre-atomicfu` directory,
which is then transformed to a regular `classes` directory to be used later by tests and delivery.
+<details>
+<summary>Build steps</summary>
+
```xml
<build>
- <plugins>
- <!-- compile Kotlin files to staging directory -->
- <plugin>
- <groupId>org.jetbrains.kotlin</groupId>
- <artifactId>kotlin-maven-plugin</artifactId>
- <version>${kotlin.version}</version>
- <executions>
- <execution>
- <id>compile</id>
- <phase>compile</phase>
- <goals>
- <goal>compile</goal>
- </goals>
- <configuration>
- <output>${project.build.directory}/classes-pre-atomicfu</output>
- <!-- "VH" to use Java 9 VarHandle, "BOTH" to produce multi-version code -->
- <variant>FU</variant>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <!-- transform classes with AtomicFU plugin -->
- <plugin>
- <groupId>org.jetbrains.kotlinx</groupId>
- <artifactId>atomicfu-maven-plugin</artifactId>
- <version>${atomicfu.version}</version>
- <executions>
- <execution>
- <goals>
- <goal>transform</goal>
- </goals>
- <configuration>
- <input>${project.build.directory}/classes-pre-atomicfu</input>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
+ <plugins>
+ <!-- compile Kotlin files to staging directory -->
+ <plugin>
+ <groupId>org.jetbrains.kotlin</groupId>
+ <artifactId>kotlin-maven-plugin</artifactId>
+ <version>${kotlin.version}</version>
+ <executions>
+ <execution>
+ <id>compile</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ <configuration>
+ <output>${project.build.directory}/classes-pre-atomicfu</output>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <!-- transform classes with AtomicFU plugin -->
+ <plugin>
+ <groupId>org.jetbrains.kotlinx</groupId>
+ <artifactId>atomicfu-maven-plugin</artifactId>
+ <version>${atomicfu.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>transform</goal>
+ </goals>
+ <configuration>
+ <input>${project.build.directory}/classes-pre-atomicfu</input>
+ <!-- "VH" to use Java 9 VarHandle, "BOTH" to produce multi-version code -->
+ <variant>FU</variant>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
</build>
```
-## Additional features
+</details>
-AtomicFU provides some additional features that you can optionally use.
+## Usage constraints
-### VarHandles with Java 9
+* Declare atomic variables as `private val` or `internal val`. You can use just (public) `val`,
+ but make sure they are not directly accessed outside of your Kotlin module (outside of the source set).
+ Access to the atomic variable itself shall be encapsulated.
+* Only simple operations on atomic variables _directly_ are supported.
+ * Do not read references on atomic variables into local variables,
+ e.g. `top.compareAndSet(...)` is ok, while `val tmp = top; tmp...` is not.
+ * Do not leak references on atomic variables in other way (return, pass as params, etc).
+* Do not introduce complex data flow in parameters to atomic variable operations,
+ i.e. `top.value = complex_expression` and `top.compareAndSet(cur, complex_expression)` are not supported
+ (more specifically, `complex_expression` should not have branches in its compiled representation).
+ Extract `complex_expression` into a variable when needed.
+* Use the following convention if you need to expose the value of atomic property to the public:
+
+```kotlin
+private val _foo = atomic<T>(initial) // private atomic, convention is to name it with leading underscore
+public var foo: T by _foo // public delegated property (val/var)
+```
+
+## Transformation modes
+
+Basically, Atomicfu library provides an effective usage of atomic values by performing the transformations of the compiled code.
+For JVM and JS there 2 transformation modes available:
+* **Post-compilation transformation** that modifies the compiled bytecode or `*.js` files.
+* **IR transformation** that is performed by the atomicfu compiler plugin.
+
+### Atomicfu compiler plugin
+
+Compiler plugin transformation is less fragile than transformation of the compiled sources
+as it depends on the compiler IR tree.
+
+To turn on IR transformation set these properties in your `gradle.properties` file:
+
+<details open>
+<summary>For Kotlin >= 1.7.20</summary>
+
+```groovy
+kotlinx.atomicfu.enableJvmIrTransformation=true // for JVM IR transformation
+kotlinx.atomicfu.enableJsIrTransformation=true // for JS IR transformation
+```
+
+</details>
+
+<details>
+
+
+<summary> For Kotlin >= 1.6.20 and Kotlin < 1.7.20</summary>
+
+```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:
-AtomicFU can produce code that uses Java 9
-[VarHandle](https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html)
-instead of `AtomicXxxFieldUpdater`. Configure transformation `variant` in Gradle build file:
-
+```groovy
+kotlin.js.compiler=ir // or both
+```
+
+
+## Options for post-compilation transformation
+
+Some configuration options are available for _post-compilation transform tasks_ on JVM and JS.
+
+To set configuration options you should create `atomicfu` section in a `build.gradle` file,
+like this:
```groovy
atomicfu {
- variant = "VH"
+ dependenciesVersion = '0.18.5'
}
-```
-
-It can also create [JEP 238](https://openjdk.java.net/jeps/238) multi-release jar file with both
-`AtomicXxxFieldUpdater` for JDK<=8 and `VarHandle` for for JDK9+ if you
-set `variant` to `"BOTH"`.
+```
+
+### JVM options
+
+To turn off transformation for Kotlin/JVM set option `transformJvm` to `false`.
+
+Configuration option `jvmVariant` defines the Java class that replaces atomics during bytecode transformation.
+Here are the valid options:
+- `FU` – atomics are replaced with [AtomicXxxFieldUpdater](https://docs.oracle.com/javase/10/docs/api/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.html).
+- `VH` – atomics are replaced with [VarHandle](https://docs.oracle.com/javase/9/docs/api/java/lang/invoke/VarHandle.html),
+ this option is supported for JDK 9+.
+- `BOTH` – [multi-release jar file](https://openjdk.java.net/jeps/238) will be created with both `AtomicXxxFieldUpdater` for JDK <= 8 and `VarHandle` for JDK 9+.
+
+### JS options
+
+To turn off transformation for Kotlin/JS set option `transformJs` to `false`.
+
+Here are all available configuration options (with their defaults):
+```groovy
+atomicfu {
+ dependenciesVersion = '0.18.5' // set to null to turn-off auto dependencies
+ transformJvm = true // set to false to turn off JVM transformation
+ jvmVariant = "FU" // JVM transformation variant: FU,VH, or BOTH
+ transformJs = true // set to false to turn off JVM transformation
+}
+```
+
+## More features
+
+AtomicFU provides some additional features that you can use.
### Arrays of atomic values
@@ -297,46 +366,9 @@ use `lock`/`tryLock`/`unlock` functions or `lock.withLock { ... }` extension fun
[jucl.ReentrantLock](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/ReentrantLock.html)
is used on JVM. On JVM it is a typealias to the later class, erased on JS.
-Condition variables (`notify`/`wait` and `signal`/`await`) are not supported.
-
-### Testing lock-free data structures on JVM
-
-You can optionally test lock-freedomness of lock-free data structures using
-[`LockFreedomTestEnvironment`](atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt) class.
-See example in [`LockFreeQueueLFTest`](atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt).
-Testing is performed by pausing one (random) thread before or after a random state-update operation and
-making sure that all other threads can still make progress.
-
-In order to make those test to actually perform lock-freedomness testing you need to configure an additional
-execution of tests with the original (non-transformed) classes for Maven:
-
-```xml
-<build>
- <plugins>
- <!-- additional test execution with surefire on non-transformed files -->
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <executions>
- <execution>
- <id>lockfree-test</id>
- <phase>test</phase>
- <goals>
- <goal>test</goal>
- </goals>
- <configuration>
- <classesDirectory>${project.build.directory}/classes-pre-atomicfu</classesDirectory>
- <includes>
- <include>**/*LFTest.*</include>
- </includes>
- </configuration>
- </execution>
- </executions>
- </plugin>
- </plugins>
-</build>
-```
-
-For Gradle there is nothing else to add. Tests are always run using original (non-transformed) classes.
+> Note that package `kotlinx.atomicfu.locks` is experimental explicitly even while atomicfu is experimental itself,
+> meaning that no ABI guarantees are provided whatsoever. API from this package is not recommended to use in libraries
+> that other projects depend on.
### Tracing operations
@@ -369,3 +401,15 @@ private val trace = Trace(size = 64) {
`trace` is only seen before transformation and completely erased after on Kotlin/JVM and Kotlin/JS.
+## Kotlin Native support
+
+Atomic references for Kotlin/Native are based on
+[FreezableAtomicReference](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/-freezable-atomic-reference/-init-.html)
+and every reference that is stored to the previously
+[frozen](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.native.concurrent/freeze.html)
+(shared with another thread) atomic is automatically frozen, too.
+
+Since Kotlin/Native does not generally provide binary compatibility between versions,
+you should use the same version of Kotlin compiler as was used to build AtomicFU.
+See [gradle.properties](gradle.properties) in AtomicFU project for its `kotlin_version`.
+
diff --git a/RELEASE.md b/RELEASE.md
index 6f5e71d..d603b5d 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -51,7 +51,7 @@ To release new `<version>` of `kotlinx-atomicfu`:
* Create a release named `<version>`.
* Cut & paste lines from [`CHANGES.md`](CHANGES.md) into description.
-13. In [Sonatype](oss.sonatype.org/#stagingRepositories) admin interface:
+13. In [Nexus](https://oss.sonatype.org/#stagingRepositories) admin interface:
* Close the repository and wait for it to verify.
* Release it.
diff --git a/atomicfu-gradle-plugin/build.gradle b/atomicfu-gradle-plugin/build.gradle
index 28091b6..5312551 100644
--- a/atomicfu-gradle-plugin/build.gradle
+++ b/atomicfu-gradle-plugin/build.gradle
@@ -14,26 +14,41 @@ if (rootProject.ext.jvm_ir_enabled) {
// Gradle plugin must be compiled targeting the same Kotlin version as used by Gradle
kotlin.sourceSets.all {
languageSettings {
- apiVersion = "1.3"
- languageVersion = "1.3"
+ apiVersion = "1.4"
+ languageVersion = "1.4"
}
}
dependencies {
- compile gradleApi()
- compile project(":atomicfu-transformer")
- compile 'org.jetbrains.kotlin:kotlin-stdlib'
+ implementation(project(":atomicfu-transformer")) {
+ exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib'
+ }
+ compileOnly gradleApi()
+ compileOnly 'org.jetbrains.kotlin:kotlin-stdlib'
compileOnly "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ // atomicfu compiler plugin dependency will be loaded to kotlinCompilerPluginClasspath
+ implementation "org.jetbrains.kotlin:atomicfu:$kotlin_version"
- testCompile gradleTestKit()
- testCompile 'org.jetbrains.kotlin:kotlin-test'
- testCompile 'org.jetbrains.kotlin:kotlin-test-junit'
- testCompile 'junit:junit:4.12'
+ testImplementation gradleTestKit()
+ testImplementation 'org.jetbrains.kotlin:kotlin-test'
+ testImplementation 'org.jetbrains.kotlin:kotlin-test-junit'
+ testImplementation 'junit:junit:4.12'
}
configurations {
- testPluginClasspath
+ 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 {
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 bb619a5..b77e95b 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
@@ -14,22 +14,33 @@ import org.gradle.api.tasks.compile.*
import org.gradle.api.tasks.testing.*
import org.gradle.jvm.tasks.*
import org.jetbrains.kotlin.gradle.dsl.*
+import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
import org.jetbrains.kotlin.gradle.plugin.*
import java.io.*
import java.util.*
import java.util.concurrent.*
+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.*
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"
open class AtomicFUGradlePlugin : Plugin<Project> {
override fun apply(project: Project) = project.run {
val pluginVersion = rootProject.buildscript.configurations.findByName("classpath")
?.allDependencies?.find { it.name == "atomicfu-gradle-plugin" }?.version
extensions.add(EXTENSION_NAME, AtomicFUPluginExtension(pluginVersion))
+ applyAtomicfuCompilerPlugin()
configureDependencies()
configureTasks()
}
@@ -43,12 +54,13 @@ private fun Project.configureDependencies() {
)
dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JVM, version))
}
- withPluginWhenEvaluatedDependencies("kotlin2js") { version ->
+ withPluginWhenEvaluatedDependencies("org.jetbrains.kotlin.js") { version ->
dependencies.add(
if (config.transformJs) COMPILE_ONLY_CONFIGURATION else IMPLEMENTATION_CONFIGURATION,
getAtomicfuDependencyNotation(Platform.JS, version)
)
dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JS, version))
+ addCompilerPluginDependency()
}
withPluginWhenEvaluatedDependencies("kotlin-multiplatform") { version ->
configureMultiplatformPluginDependencies(version)
@@ -59,7 +71,9 @@ private fun Project.configureTasks() {
val config = config
withPluginWhenEvaluated("kotlin") {
if (config.transformJvm) {
- configureTransformTasks("compileTestKotlin") { sourceSet, transformedDir, originalDir ->
+ // skip transformation task if ir transformation is enabled
+ if (rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)) return@withPluginWhenEvaluated
+ configureJvmTransformation("compileTestKotlin") { sourceSet, transformedDir, originalDir ->
createJvmTransformTask(sourceSet).configureJvmTask(
sourceSet.compileClasspath,
sourceSet.classesTaskName,
@@ -70,20 +84,88 @@ private fun Project.configureTasks() {
}
}
}
- withPluginWhenEvaluated("kotlin2js") {
- if (config.transformJs) {
- configureTransformTasks("compileTestKotlin2Js") { sourceSet, transformedDir, originalDir ->
- createJsTransformTask(sourceSet).configureJsTask(
- sourceSet.classesTaskName,
- transformedDir,
- originalDir,
- config
- )
+ withPluginWhenEvaluated("org.jetbrains.kotlin.js") {
+ if (config.transformJs) configureJsTransformation()
+ }
+ withPluginWhenEvaluated("kotlin-multiplatform") {
+ configureMultiplatformTransformation()
+ }
+}
+
+private data class KotlinVersion(val major: Int, val minor: Int, val patch: Int)
+
+private fun Project.getKotlinVersion(): KotlinVersion {
+ val kotlinVersion = getKotlinPluginVersion()
+ val (major, minor) = kotlinVersion
+ .split('.')
+ .take(2)
+ .map { it.toInt() }
+ val patch = kotlinVersion.substringAfterLast('.').substringBefore('-').toInt()
+ return KotlinVersion(major, minor, patch)
+}
+
+private fun KotlinVersion.atLeast(major: Int, minor: Int, patch: Int) =
+ this.major == major && (this.minor == minor && this.patch >= patch || this.minor > minor) || this.major > major
+
+// kotlinx-atomicfu compiler plugin is available for KGP >= 1.6.20
+private fun Project.isCompilerPluginAvailable() = getKotlinVersion().atLeast(1, 6, 20)
+
+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)
+ }
+ } 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)
}
}
}
- withPluginWhenEvaluated("kotlin-multiplatform") {
- configureMultiplatformPluginTasks()
+}
+
+private fun Project.getBooleanProperty(name: String) =
+ rootProject.findProperty(name)?.toString()?.toBooleanStrict() ?: false
+
+private fun String.toBooleanStrict(): Boolean = when (this) {
+ "true" -> true
+ "false" -> false
+ else -> throw IllegalArgumentException("The string doesn't represent a boolean value: $this")
+}
+
+private fun Project.needsJsIrTransformation(target: KotlinTarget): Boolean =
+ (rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION) || rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY))
+ && target.isJsIrTarget()
+
+private fun KotlinTarget.isJsIrTarget() = (this is KotlinJsTarget && this.irTarget != null) || this is KotlinJsIrTarget
+
+private fun Project.addCompilerPluginDependency() {
+ if (isCompilerPluginAvailable()) {
+ withKotlinTargets { target ->
+ if (needsJsIrTransformation(target)) {
+ target.compilations.forEach { kotlinCompilation ->
+ kotlinCompilation.dependencies {
+ if (getKotlinVersion().atLeast(1, 7, 10)) {
+ // since Kotlin 1.7.10 we can add `atomicfu-runtime` dependency directly
+ implementation("org.jetbrains.kotlin:kotlinx-atomicfu-runtime:${getKotlinPluginVersion()}")
+ } else {
+ // add atomicfu compiler plugin dependency
+ // to provide the `atomicfu-runtime` library used during compiler plugin transformation
+ implementation("org.jetbrains.kotlin:atomicfu:${getKotlinPluginVersion()}")
+ }
+ }
+ }
+ }
+ }
}
}
@@ -135,88 +217,112 @@ fun Project.withPluginWhenEvaluatedDependencies(plugin: String, fn: Project.(ver
}
fun Project.withKotlinTargets(fn: (KotlinTarget) -> Unit) {
- extensions.findByType(KotlinProjectExtension::class.java)?.let { kotlinExtension ->
- val targetsExtension = (kotlinExtension as? ExtensionAware)?.extensions?.findByName("targets")
- @Suppress("UNCHECKED_CAST")
- val targets = targetsExtension as NamedDomainObjectContainer<KotlinTarget>
+ extensions.findByType(KotlinTargetsContainer::class.java)?.let { kotlinExtension ->
// find all compilations given sourceSet belongs to
- targets.all { target -> fn(target) }
+ kotlinExtension.targets
+ .all { target -> fn(target) }
}
}
-private fun KotlinCommonOptions.addFriendPaths(friendPathsFileCollection: FileCollection) {
- val argName = when (this) {
- is KotlinJvmOptions -> "-Xfriend-paths"
- is KotlinJsOptions -> "-Xfriend-modules"
- else -> return
+private fun KotlinCompile<*>.setFriendPaths(friendPathsFileCollection: FileCollection) {
+ val (majorVersion, minorVersion) = project.getKotlinPluginVersion()
+ .split('.')
+ .take(2)
+ .map { it.toInt() }
+ if (majorVersion == 1 && minorVersion < 7) {
+ (this as? AbstractKotlinCompile<*>)?.friendPaths?.from(friendPathsFileCollection)
+ } else {
+ // See KT-KT-54167 (works only for KGP 1.7.0+)
+ (this as BaseKotlinCompile).friendPaths.from(friendPathsFileCollection)
}
- freeCompilerArgs = freeCompilerArgs + "$argName=${friendPathsFileCollection.joinToString(",")}"
}
-fun Project.configureMultiplatformPluginTasks() {
- val originalDirsByCompilation = hashMapOf<KotlinCompilation<*>, FileCollection>()
- val config = config
+fun Project.configureJsTransformation() =
+ configureTransformationForTarget((kotlinExtension as KotlinJsProjectExtension).js())
+
+fun Project.configureMultiplatformTransformation() =
withKotlinTargets { target ->
if (target.platformType == KotlinPlatformType.common || target.platformType == KotlinPlatformType.native) {
return@withKotlinTargets // skip the common & native targets -- no transformation for them
}
- target.compilations.all compilations@{ compilation ->
- val compilationType = compilation.name.compilationNameToType()
- ?: return@compilations // skip unknown compilations
- val classesDirs = compilation.output.classesDirs
- // make copy of original classes directory
- val originalClassesDirs: FileCollection =
- project.files(classesDirs.from.toTypedArray()).filter { it.exists() }
- originalDirsByCompilation[compilation] = originalClassesDirs
- val transformedClassesDir =
- project.buildDir.resolve("classes/atomicfu/${target.name}/${compilation.name}")
- val transformTask = when (target.platformType) {
- KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> {
- if (!config.transformJvm) return@compilations // skip when transformation is turned off
- project.createJvmTransformTask(compilation).configureJvmTask(
- compilation.compileDependencyFiles,
- compilation.compileAllTaskName,
- transformedClassesDir,
- originalClassesDirs,
- config
- )
- }
- KotlinPlatformType.js -> {
- if (!config.transformJs) return@compilations // skip when transformation is turned off
- project.createJsTransformTask(compilation).configureJsTask(
- compilation.compileAllTaskName,
- transformedClassesDir,
- originalClassesDirs,
- config
- )
- }
- else -> error("Unsupported transformation platform '${target.platformType}'")
- }
- //now transformTask is responsible for compiling this source set into the classes directory
- classesDirs.setFrom(transformedClassesDir)
- classesDirs.builtBy(transformTask)
- (tasks.findByName(target.artifactsTaskName) as? Jar)?.apply {
- setupJarManifest(multiRelease = config.variant.toVariant() == Variant.BOTH)
+ configureTransformationForTarget(target)
+ }
+
+private fun Project.configureTransformationForTarget(target: KotlinTarget) {
+ val originalDirsByCompilation = hashMapOf<KotlinCompilation<*>, FileCollection>()
+ val config = config
+ target.compilations.all compilations@{ compilation ->
+ val compilationType = compilation.name.compilationNameToType()
+ ?: return@compilations // skip unknown compilations
+ val classesDirs = compilation.output.classesDirs
+ // make copy of original classes directory
+ val originalClassesDirs: FileCollection =
+ project.files(classesDirs.from.toTypedArray()).filter { it.exists() }
+ originalDirsByCompilation[compilation] = originalClassesDirs
+ val transformedClassesDir =
+ project.buildDir.resolve("classes/atomicfu/${target.name}/${compilation.name}")
+ val transformTask = when (target.platformType) {
+ KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> {
+ // skip transformation task if transformation is turned off or ir transformation is enabled
+ if (!config.transformJvm || rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)) return@compilations
+ project.createJvmTransformTask(compilation).configureJvmTask(
+ compilation.compileDependencyFiles,
+ compilation.compileAllTaskName,
+ transformedClassesDir,
+ originalClassesDirs,
+ config
+ )
}
- // test should compile and run against original production binaries
- if (compilationType == CompilationType.TEST) {
- val mainCompilation =
- compilation.target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
- val originalMainClassesDirs = project.files(
- // use Callable because there is no guarantee that main is configured before test
- Callable { originalDirsByCompilation[mainCompilation]!! }
+ KotlinPlatformType.js -> {
+ // skip when js transformation is not needed or when IR is transformed
+ if (!config.transformJs || (needsJsIrTransformation(target))) {
+ return@compilations
+ }
+ project.createJsTransformTask(compilation).configureJsTask(
+ compilation.compileAllTaskName,
+ transformedClassesDir,
+ originalClassesDirs,
+ config
)
+ }
+ else -> error("Unsupported transformation platform '${target.platformType}'")
+ }
+ //now transformTask is responsible for compiling this source set into the classes directory
+ classesDirs.setFrom(transformedClassesDir)
+ classesDirs.builtBy(transformTask)
+ (tasks.findByName(target.artifactsTaskName) as? Jar)?.apply {
+ setupJarManifest(multiRelease = config.jvmVariant.toJvmVariant() == JvmVariant.BOTH)
+ }
+ // test should compile and run against original production binaries
+ if (compilationType == CompilationType.TEST) {
+ val mainCompilation =
+ compilation.target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
+ val originalMainClassesDirs = project.files(
+ // use Callable because there is no guarantee that main is configured before test
+ Callable { originalDirsByCompilation[mainCompilation]!! }
+ )
+ // KGP >= 1.7.0 has breaking changes in task hierarchy:
+ // https://youtrack.jetbrains.com/issue/KT-32805#focus=Comments-27-5915479.0-0
+ val (majorVersion, minorVersion) = getKotlinPluginVersion()
+ .split('.')
+ .take(2)
+ .map { it.toInt() }
+ if (majorVersion == 1 && minorVersion < 7) {
(tasks.findByName(compilation.compileKotlinTaskName) as? AbstractCompile)?.classpath =
originalMainClassesDirs + compilation.compileDependencyFiles - mainCompilation.output.classesDirs
+ } else {
+ (tasks.findByName(compilation.compileKotlinTaskName) as? AbstractKotlinCompileTool<*>)
+ ?.libraries
+ ?.setFrom(
+ originalMainClassesDirs + compilation.compileDependencyFiles - mainCompilation.output.classesDirs
+ )
+ }
- (tasks.findByName("${target.name}${compilation.name.capitalize()}") as? Test)?.classpath =
- originalMainClassesDirs + (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyFiles - mainCompilation.output.classesDirs
+ (tasks.findByName("${target.name}${compilation.name.capitalize()}") as? Test)?.classpath =
+ originalMainClassesDirs + (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyFiles - mainCompilation.output.classesDirs
- compilation.compileKotlinTask.doFirst {
- compilation.kotlinOptions.addFriendPaths(originalMainClassesDirs)
- }
- }
+ compilation.compileKotlinTask.setFriendPaths(originalMainClassesDirs)
}
}
}
@@ -234,15 +340,16 @@ fun Project.sourceSetsByCompilation(): Map<KotlinSourceSet, List<KotlinCompilati
}
fun Project.configureMultiplatformPluginDependencies(version: String) {
- if (rootProject.findProperty("kotlin.mpp.enableGranularSourceSetsMetadata").toString().toBoolean()) {
+ if (rootProject.getBooleanProperty("kotlin.mpp.enableGranularSourceSetsMetadata")) {
+ addCompilerPluginDependency()
val mainConfigurationName = project.extensions.getByType(KotlinMultiplatformExtension::class.java).sourceSets
- .getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)
- .compileOnlyConfigurationName
+ .getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)
+ .compileOnlyConfigurationName
dependencies.add(mainConfigurationName, getAtomicfuDependencyNotation(Platform.MULTIPLATFORM, version))
val testConfigurationName = project.extensions.getByType(KotlinMultiplatformExtension::class.java).sourceSets
- .getByName(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME)
- .implementationConfigurationName
+ .getByName(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME)
+ .implementationConfigurationName
dependencies.add(testConfigurationName, getAtomicfuDependencyNotation(Platform.MULTIPLATFORM, version))
// For each source set that is only used in Native compilations, add an implementation dependency so that it
@@ -258,20 +365,21 @@ fun Project.configureMultiplatformPluginDependencies(version: String) {
}
} else {
sourceSetsByCompilation().forEach { (sourceSet, compilations) ->
+ addCompilerPluginDependency()
val platformTypes = compilations.map { it.platformType }.toSet()
val compilationNames = compilations.map { it.compilationName }.toSet()
if (compilationNames.size != 1)
error("Source set '${sourceSet.name}' of project '$name' is part of several compilations $compilationNames")
val compilationType = compilationNames.single().compilationNameToType()
- ?: return@forEach // skip unknown compilations
+ ?: return@forEach // skip unknown compilations
val platform =
- if (platformTypes.size > 1) Platform.MULTIPLATFORM else // mix of platform types -> "common"
- when (platformTypes.single()) {
- KotlinPlatformType.common -> Platform.MULTIPLATFORM
- KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> Platform.JVM
- KotlinPlatformType.js -> Platform.JS
- KotlinPlatformType.native -> Platform.NATIVE
- }
+ if (platformTypes.size > 1) Platform.MULTIPLATFORM else // mix of platform types -> "common"
+ when (platformTypes.single()) {
+ KotlinPlatformType.common -> Platform.MULTIPLATFORM
+ KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> Platform.JVM
+ KotlinPlatformType.js -> Platform.JS
+ KotlinPlatformType.native, KotlinPlatformType.wasm -> Platform.NATIVE
+ }
val configurationName = when {
// impl dependency for native (there is no transformation)
platform == Platform.NATIVE -> sourceSet.implementationConfigurationName
@@ -285,7 +393,7 @@ fun Project.configureMultiplatformPluginDependencies(version: String) {
}
}
-fun Project.configureTransformTasks(
+fun Project.configureJvmTransformation(
testTaskName: String,
createTransformTask: (sourceSet: SourceSet, transformedDir: File, originalDir: FileCollection) -> Task
) {
@@ -305,7 +413,7 @@ fun Project.configureTransformTasks(
//now transformTask is responsible for compiling this source set into the classes directory
sourceSet.compiledBy(transformTask)
(tasks.findByName(sourceSet.jarTaskName) as? Jar)?.apply {
- setupJarManifest(multiRelease = config.variant.toVariant() == Variant.BOTH)
+ setupJarManifest(multiRelease = config.jvmVariant.toJvmVariant() == JvmVariant.BOTH)
}
// test should compile and run against original production binaries
if (compilationType == CompilationType.TEST) {
@@ -319,9 +427,7 @@ fun Project.configureTransformTasks(
classpath =
originalMainClassesDirs + sourceSet.compileClasspath - mainSourceSet.output.classesDirs
- (this as? KotlinCompile<*>)?.doFirst {
- kotlinOptions.addFriendPaths(originalMainClassesDirs)
- }
+ (this as? KotlinCompile<*>)?.setFriendPaths(originalMainClassesDirs)
}
// todo: fix test runtime classpath for JS?
@@ -331,7 +437,7 @@ fun Project.configureTransformTasks(
}
}
-fun String.toVariant(): Variant = enumValueOf(toUpperCase(Locale.US))
+fun String.toJvmVariant(): JvmVariant = enumValueOf(toUpperCase(Locale.US))
fun Project.createJvmTransformTask(compilation: KotlinCompilation<*>): AtomicFUTransformTask =
tasks.create(
@@ -348,9 +454,6 @@ fun Project.createJsTransformTask(compilation: KotlinCompilation<*>): AtomicFUTr
fun Project.createJvmTransformTask(sourceSet: SourceSet): AtomicFUTransformTask =
tasks.create(sourceSet.getTaskName("transform", "atomicfuClasses"), AtomicFUTransformTask::class.java)
-fun Project.createJsTransformTask(sourceSet: SourceSet): AtomicFUTransformJsTask =
- tasks.create(sourceSet.getTaskName("transform", "atomicfuJsFiles"), AtomicFUTransformJsTask::class.java)
-
fun AtomicFUTransformTask.configureJvmTask(
classpath: FileCollection,
classesTaskName: String,
@@ -363,7 +466,7 @@ fun AtomicFUTransformTask.configureJvmTask(
classPath = classpath
inputFiles = originalClassesDir
outputDir = transformedClassesDir
- variant = config.variant
+ jvmVariant = config.jvmVariant
verbose = config.verbose
}
@@ -395,7 +498,7 @@ class AtomicFUPluginExtension(pluginVersion: String?) {
var dependenciesVersion = pluginVersion
var transformJvm = true
var transformJs = true
- var variant: String = "FU"
+ var jvmVariant: String = "FU"
var verbose: Boolean = false
}
@@ -413,7 +516,8 @@ open class AtomicFUTransformTask : ConventionTask() {
lateinit var classPath: FileCollection
@Input
- var variant = "FU"
+ var jvmVariant = "FU"
+
@Input
var verbose = false
@@ -422,7 +526,7 @@ open class AtomicFUTransformTask : ConventionTask() {
val cp = classPath.files.map { it.absolutePath }
inputFiles.files.forEach { inputDir ->
AtomicFUTransformer(cp, inputDir, outputDir).let { t ->
- t.variant = variant.toVariant()
+ t.jvmVariant = jvmVariant.toJvmVariant()
t.verbose = verbose
t.transform()
}
@@ -438,6 +542,7 @@ open class AtomicFUTransformJsTask : ConventionTask() {
@OutputDirectory
lateinit var outputDir: File
+
@Input
var verbose = false
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/BaseKotlinGradleTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/BaseKotlinGradleTest.kt
deleted file mode 100644
index b81a0c9..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/BaseKotlinGradleTest.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 2017-2018 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.internal.impldep.com.google.common.io.Files
-import org.junit.After
-import org.junit.Before
-import java.io.File
-
-abstract class BaseKotlinGradleTest {
- private lateinit var workingDir: File
-
- fun project(name: String, suffix: String = "", fn: Project.() -> Unit) {
- workingDir = File("build${File.separator}test-$name$suffix").absoluteFile
- workingDir.deleteRecursively()
- workingDir.mkdirs()
- val testResources = File("src/test/resources")
- val originalProjectDir = testResources.resolve("projects/$name").apply { checkExists() }
- val projectDir = workingDir.resolve(name).apply { mkdirs() }
- originalProjectDir.listFiles().forEach { it.copyRecursively(projectDir.resolve(it.name)) }
-
- // Add an empty setting.gradle
- projectDir.resolve("settings.gradle").writeText("// this file is intentionally left empty")
-
- Project(projectDir = projectDir).fn()
- }
-} \ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/EmptyProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/EmptyProjectTest.kt
deleted file mode 100644
index b542f2b..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/EmptyProjectTest.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.plugin.gradle
-
-import org.junit.Test
-
-class EmptyProjectTest : BaseKotlinGradleTest() {
- @Test
- fun testEmpty() = project("empty") {
- build("build") {}
- }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JsProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JsProjectTest.kt
deleted file mode 100644
index 2201199..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JsProjectTest.kt
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright 2017-2018 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.testkit.runner.TaskOutcome
-import org.junit.Test
-import java.io.File
-
-class JsProjectTest : BaseKotlinGradleTest() {
- @Test
- fun testKotlin2JsPlugin() = project("js-simple") {
- val tasksToCheck = arrayOf(
- ":compileKotlin2Js",
- ":compileTestKotlin2Js",
- ":transformAtomicfuJsFiles",
- ":transformTestAtomicfuJsFiles"
- )
-
- build("build") {
- checkOutcomes(TaskOutcome.SUCCESS, *tasksToCheck)
-
- val testCompileClasspathFiles = projectDir.resolve("build/test_compile_classpath.txt")
- .readLines().asSequence().flatMap { File(it).walk().filter(File::isFile) }.toHashSet()
-
- projectDir.resolve("build/classes/kotlin/main/js-simple.js").let {
- it.checkExists()
- check(it in testCompileClasspathFiles) { "Original '$it' is missing from test compile classpath" }
- // todo: check test runtime classpath when js test tasks are supported in plugin
- }
-
- projectDir.resolve("build/classes/atomicfu/main/js-simple.js").let {
- it.checkExists()
- check(it !in testCompileClasspathFiles) { "Transformed '$it' is present in test compile classpath" }
- }
- }
-
- build("build") {
- checkOutcomes(TaskOutcome.UP_TO_DATE, *tasksToCheck)
- }
- }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JvmProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JvmProjectTest.kt
deleted file mode 100644
index e017f05..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/JvmProjectTest.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Copyright 2017-2018 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.testkit.runner.TaskOutcome
-import org.junit.Test
-import java.io.File
-
-class JvmProjectTest : BaseKotlinGradleTest() {
- @Test
- fun testKotlinPlugin() =
- project("jvm-simple") {
- doSimpleTest()
- }
-
- @Test
- fun testKotlinPlatformJvmPlugin() =
- project("jvm-simple", "-platform") {
- projectDir.resolve("build.gradle").modify {
- it.checkedReplace("apply plugin: 'kotlin'", "apply plugin: 'kotlin-platform-jvm'")
- }
- doSimpleTest()
- }
-
- private fun Project.doSimpleTest() {
- val tasksToCheck = arrayOf(
- ":compileKotlin",
- ":compileTestKotlin",
- ":transformAtomicfuClasses",
- ":transformTestAtomicfuClasses"
- )
-
- build("build") {
- checkOutcomes(TaskOutcome.SUCCESS, *tasksToCheck)
-
- val testCompileClasspathFiles = filesFrom("build/test_compile_classpath.txt")
- val testRuntimeClasspathFiles = filesFrom("build/test_runtime_classpath.txt")
-
- projectDir.resolve("build/classes/kotlin/main/IntArithmetic.class").let {
- it.checkExists()
- check(it in testCompileClasspathFiles) { "Original '$it' is missing from test compile classpath" }
- check(it in testRuntimeClasspathFiles) { "Original '$it' is missing from test runtime classpath" }
- }
-
- projectDir.resolve("build/classes/atomicfu/main/IntArithmetic.class").let {
- it.checkExists()
- check(it !in testCompileClasspathFiles) { "Transformed '$it' is present in test compile classpath" }
- check(it !in testRuntimeClasspathFiles) { "Transformed '$it' is present in test runtime classpath" }
- }
- }
-
- build("build") {
- checkOutcomes(TaskOutcome.UP_TO_DATE, *tasksToCheck)
- }
- }
-
- private fun Project.filesFrom(name: String) = projectDir.resolve(name)
- .readLines().asSequence().flatMap { listFiles(it) }.toHashSet()
-
- private fun listFiles(dir: String): Sequence<File> = File(dir).walk().filter { it.isFile }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/MppProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/MppProjectTest.kt
deleted file mode 100644
index 6dd7fa1..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/MppProjectTest.kt
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright 2017-2018 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.testkit.runner.TaskOutcome
-import org.junit.Test
-import java.io.File
-
-class MppProjectTest : BaseKotlinGradleTest() {
- @Test
- fun testKotlinMultiplatformPlugin() = project("mpp-simple") {
- val tasksToCheck = arrayOf(
- ":compileKotlinJvm",
- ":compileTestKotlinJvm",
- ":transformJvmMainAtomicfu",
- ":transformJvmTestAtomicfu",
- ":compileKotlinJs",
- ":transformJsMainAtomicfu"
- )
-
- build("build") {
- checkOutcomes(TaskOutcome.SUCCESS, *tasksToCheck)
-
- fun checkPlatform(platform: String, fileInMainName: String) {
- val isJs = platform == "js"
- val testCompileClasspathFiles = projectDir.resolve("build/classpath/$platform/test_compile.txt")
- .readLines().asSequence().flatMapTo(HashSet()) { File(it).walk().filter(File::isFile) }
- val testRuntimeClasspathFiles = if (isJs) emptySet<File>() else projectDir.resolve("build/classpath/$platform/test_runtime.txt")
- .readLines().asSequence().flatMapTo(HashSet()) { File(it).walk().filter(File::isFile) }
-
- projectDir.resolve("build/classes/kotlin/$platform/main/$fileInMainName").let {
- it.checkExists()
- check(it in testCompileClasspathFiles) { "Original '$it' is missing from $platform test compile classpath" }
- if (!isJs) check(it in testRuntimeClasspathFiles) { "Original '$it' is missing from $platform test runtime classpath" }
- }
-
- projectDir.resolve("build/classes/atomicfu/jvm/main/IntArithmetic.class").let {
- it.checkExists()
- check(it !in testCompileClasspathFiles) { "Transformed '$it' is present in $platform test compile classpath" }
- if (!isJs) check(it !in testRuntimeClasspathFiles) { "Transformed '$it' is present in $platform test runtime classpath" }
- }
-
- }
-
- checkPlatform("jvm", "IntArithmetic.class")
- checkPlatform("js", "mpp-simple.js")
- }
-
- build("build") {
- checkOutcomes(TaskOutcome.UP_TO_DATE, *tasksToCheck)
- }
- }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/Project.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/Project.kt
deleted file mode 100644
index f3f49e1..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/Project.kt
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright 2017-2018 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.testkit.runner.BuildResult
-import org.gradle.testkit.runner.GradleRunner
-import java.io.File
-
-class Project(val projectDir: File) {
- init {
- projectDir.resolve("build.gradle").modify {
- buildScript + "\n\n" + it
- }
- }
-
- private var isDebug = false
- private var printStdout = false
-
- @Deprecated("Should be used for debug only!")
- @Suppress("unused")
- fun debug() {
- isDebug = true
- }
-
- /**
- * Redirects Gradle runner output to stdout. Useful for debugging.
- */
- @Deprecated("Should be used for debug only!")
- @Suppress("unused")
- fun printStdout() {
- printStdout = true
- }
-
- fun gradle(vararg tasks: String): GradleRunner =
- GradleRunner.create()
- .withDebug(isDebug)
- .withProjectDir(projectDir)
- .withArguments(*(defaultArguments() + tasks))
- .run {
- if (printStdout) {
- forwardStdOutput(System.out.bufferedWriter())
- } else {
- this
- }
- }
-
- fun build(vararg tasks: String, fn: BuildResult.() -> Unit = {}) {
- val gradle = gradle(*tasks)
- val buildResult = gradle.build()
- buildResult.fn()
- }
-
- @Suppress("unused")
- fun buildAndFail(vararg tasks: String, fn: BuildResult.() -> Unit = {}) {
- val gradle = gradle(*tasks)
- val buildResult = gradle.buildAndFail()
- buildResult.fn()
- }
-
- private fun defaultArguments(): Array<String> =
- arrayOf("--stacktrace")
-
- companion object {
- private fun readFileList(fileName: String): String {
- val resource = Project::class.java.classLoader.getResource(fileName)
- ?: throw IllegalStateException("Could not find resource '$fileName'")
- val files = File(resource.toURI())
- .readLines()
- .map { File(it).absolutePath.replace("\\", "\\\\") } // escape backslashes in Windows paths
- return files.joinToString(", ") { "'$it'" }
- }
-
- private val buildScript = run {
- """
- buildscript {
- dependencies {
- classpath files(${readFileList("plugin-classpath.txt")})
- }
- }
-
- repositories {
- jcenter()
- mavenCentral()
- maven { url 'https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev' }
- maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
- maven { url 'https://dl.bintray.com/kotlin/kotlin-dev' }
- maven { url 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev' }
- maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
- }
-
- def atomicfuJvm = files(${readFileList("atomicfu-jvm.txt")})
- def atomicfuJs = files(${readFileList("atomicfu-js.txt")})
- def atomicfuMetadata = files(${readFileList("atomicfu-metadata.txt")})
- """.trimIndent()
- }
- }
-}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/Assert.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/Assert.kt
new file mode 100644
index 0000000..f55e38a
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/Assert.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o.
+ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
+ */
+
+package kotlinx.atomicfu.plugin.gradle.internal
+
+import org.gradle.testkit.runner.BuildResult
+import org.gradle.testkit.runner.TaskOutcome
+import kotlin.test.assertEquals
+
+/**
+ * Helper `fun` for asserting a [TaskOutcome] to be equal to [TaskOutcome.SUCCESS]
+ */
+internal fun BuildResult.assertTaskSuccess(task: String) {
+ assertTaskOutcome(TaskOutcome.SUCCESS, task)
+}
+
+/**
+ * Helper `fun` for asserting a [TaskOutcome] to be equal to [TaskOutcome.FAILED]
+ */
+internal fun BuildResult.assertTaskFailure(task: String) {
+ assertTaskOutcome(TaskOutcome.FAILED, task)
+}
+
+internal fun BuildResult.assertTaskUpToDate(task: String) {
+ assertTaskOutcome(TaskOutcome.UP_TO_DATE, task)
+}
+
+private fun BuildResult.assertTaskOutcome(taskOutcome: TaskOutcome, taskName: String) {
+ assertEquals(taskOutcome, task(taskName)?.outcome)
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/TestDsl.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/TestDsl.kt
new file mode 100644
index 0000000..2541b41
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/TestDsl.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o.
+ * Use of this source code is governed by the Apache 2.0 License that can be found in the LICENSE.txt file.
+ */
+
+package kotlinx.atomicfu.plugin.gradle.internal
+
+import kotlinx.atomicfu.plugin.gradle.test.*
+import org.gradle.testkit.runner.*
+import java.io.*
+
+internal fun BaseKotlinGradleTest.test(fn: BaseKotlinScope.() -> Unit): GradleRunner {
+ val baseKotlinScope = BaseKotlinScope()
+ fn(baseKotlinScope)
+
+ baseKotlinScope.files.forEach { scope ->
+ val fileWriteTo = rootProjectDir.resolve(scope.filePath)
+ .apply {
+ parentFile.mkdirs()
+ createNewFile()
+ }
+
+ scope.files.forEach {
+ val fileContent = readFileList(it)
+ fileWriteTo.appendText(fileContent)
+ }
+ }
+
+ return GradleRunner.create()
+ .withProjectDir(rootProjectDir)
+ .withArguments(baseKotlinScope.runner.arguments)
+ .withPluginClasspath()
+ .addPluginTestRuntimeClasspath()
+}
+
+/**
+ * same as [file][FileContainer.file], but prepends "src/${sourceSet}/kotlin" before given `classFileName`
+ */
+internal fun FileContainer.kotlin(classFileName: String, sourceSet: String = "main", fn: AppendableScope.() -> Unit) {
+ require(classFileName.endsWith(".kt")) {
+ "ClassFileName must end with '.kt'"
+ }
+
+ val fileName = "src/${sourceSet}/kotlin/$classFileName"
+ file(fileName, fn)
+}
+
+/**
+ * Shortcut for creating a `build.gradle.kts` by using [file][FileContainer.file]
+ */
+internal fun FileContainer.buildGradleKts(fn: AppendableScope.() -> Unit) {
+ val fileName = "build.gradle.kts"
+ file(fileName, fn)
+}
+
+/**
+ * Shortcut for creating a `settings.gradle.kts` by using [file][FileContainer.file]
+ */
+internal fun FileContainer.settingsGradleKts(fn: AppendableScope.() -> Unit) {
+ val fileName = "settings.gradle.kts"
+ file(fileName, fn)
+}
+
+/**
+ * Shortcut for creating a `gradle.properties` by using [file][FileContainer.file]
+ */
+internal fun FileContainer.gradleProperties(fn: AppendableScope.() -> Unit) {
+ val fileName = "gradle.properties"
+ file(fileName, fn)
+}
+
+/**
+ * Declares a directory with the given [dirName] inside the current container.
+ * All calls creating files within this scope will create the files nested in this directory.
+ *
+ * Note that it is valid to call this method multiple times at the same level with the same [dirName].
+ * Files declared within 2 independent calls to [dir] will be added to the same directory.
+ */
+internal fun FileContainer.dir(dirName: String, fn: DirectoryScope.() -> Unit) {
+ DirectoryScope(dirName, this).fn()
+}
+
+internal fun BaseKotlinScope.runner(fn: Runner.() -> Unit) {
+ val runner = Runner()
+ fn(runner)
+
+ this.runner = runner
+}
+
+internal fun AppendableScope.resolve(fileName: String) {
+ this.files.add(fileName)
+}
+
+internal interface FileContainer {
+ fun file(fileName: String, fn: AppendableScope.() -> Unit)
+}
+
+internal class BaseKotlinScope : FileContainer {
+ var files: MutableList<AppendableScope> = mutableListOf()
+ var runner: Runner = Runner()
+
+ override fun file(fileName: String, fn: AppendableScope.() -> Unit) {
+ val appendableScope = AppendableScope(fileName)
+ fn(appendableScope)
+ files.add(appendableScope)
+ }
+}
+
+internal class DirectoryScope(
+ val dirPath: String,
+ val parent: FileContainer
+): FileContainer {
+
+ override fun file(fileName: String, fn: AppendableScope.() -> Unit) {
+ parent.file("$dirPath/$fileName", fn)
+ }
+}
+
+internal class AppendableScope(val filePath: String) {
+ val files: MutableList<String> = mutableListOf()
+}
+
+internal class Runner {
+ val arguments: MutableList<String> = mutableListOf()
+}
+
+internal fun readFileList(fileName: String): String =
+ getFile(fileName).readText()
+
+internal fun getFile(fileName: String): File {
+ val resource = BaseKotlinGradleTest::class.java.classLoader.getResource(fileName)
+ ?: throw IllegalStateException("Could not find resource '$fileName'")
+ return File(resource.toURI())
+}
+
+internal fun GradleRunner.addPluginTestRuntimeClasspath() = apply {
+ val pluginClasspath = getFile("plugin-classpath.txt").readLines().map { File(it) }
+ withPluginClasspath(pluginClasspath)
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/utils.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/utils.kt
new file mode 100644
index 0000000..49856f2
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/utils.kt
@@ -0,0 +1,13 @@
+package kotlinx.atomicfu.plugin.gradle.internal
+
+import java.io.*
+import kotlin.test.*
+
+fun File.checkExists() {
+ assertTrue(exists(), "File does not exist: $canonicalPath")
+}
+
+fun File.filesFrom(relative: String) = resolve(relative)
+ .readLines().asSequence().flatMap { listFiles(it) }.toHashSet()
+
+fun listFiles(dir: String): Sequence<File> = File(dir).walk().filter { it.isFile } \ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/BaseKotlinGradleTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/BaseKotlinGradleTest.kt
new file mode 100644
index 0000000..e9ff7bb
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/BaseKotlinGradleTest.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.atomicfu.plugin.gradle.test
+
+import kotlinx.atomicfu.plugin.gradle.internal.*
+import org.objectweb.asm.*
+import java.io.File
+import kotlin.test.*
+
+abstract class BaseKotlinGradleTest(private val projectName: String) {
+ internal val rootProjectDir: File
+ private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
+ private val KOTLIN_METADATA_DESC = "Lkotlin/Metadata;"
+
+ init {
+ rootProjectDir = File("build${File.separator}test-$projectName").absoluteFile
+ rootProjectDir.deleteRecursively()
+ rootProjectDir.mkdirs()
+ }
+
+ internal abstract fun BaseKotlinScope.createProject()
+
+ val runner = test {
+ createProject()
+ runner {
+ arguments.add(":build")
+ }
+ }
+
+ fun checkTaskOutcomes(executedTasks: List<String>, excludedTasks: List<String>) {
+ runner.build().apply {
+ val tasks = tasks.map { it.path }
+ excludedTasks.forEach {
+ check(it !in tasks) { "Post-compilation transformation task $it was added in the compiler plugin mode" }
+ }
+ executedTasks.forEach {
+ assertTaskSuccess(it)
+ }
+ }
+ // check that task outcomes are cached for the second build
+ runner.build().apply {
+ executedTasks.forEach {
+ assertTaskUpToDate(it)
+ }
+ }
+ }
+
+ fun checkJvmCompilationClasspath(originalClassFile: String, transformedClassFile: String) {
+ // check that test compile and runtime classpath does not contain original non-transformed classes
+ val testCompileClasspathFiles = rootProjectDir.filesFrom("build/test_compile_jvm_classpath.txt")
+ val testRuntimeClasspathFiles = rootProjectDir.filesFrom("build/test_runtime_jvm_classpath.txt")
+
+ rootProjectDir.resolve(transformedClassFile).let {
+ it.checkExists()
+ check(it in testCompileClasspathFiles) { "Transformed '$it' is missing from test compile classpath" }
+ check(it in testRuntimeClasspathFiles) { "Transformed '$it' is missing from test runtime classpath" }
+ }
+
+ rootProjectDir.resolve(originalClassFile).let {
+ it.checkExists()
+ check(it !in testCompileClasspathFiles) { "Original '$it' is present in test compile classpath" }
+ check(it !in testRuntimeClasspathFiles) { "Original '$it' is present in test runtime classpath" }
+ }
+ }
+
+ fun checkJsCompilationClasspath() {
+ // check that test compilation depends on transformed main sources
+ val testCompileClasspathFiles = rootProjectDir.filesFrom("build/test_compile_js_classpath.txt")
+
+ rootProjectDir.resolve("build/classes/atomicfu/js/main/$projectName.js").let {
+ it.checkExists()
+ check(it in testCompileClasspathFiles) { "Transformed '$it' is missing from test compile classpath" }
+ }
+ }
+
+ fun checkBytecode(classFilePath: String) {
+ rootProjectDir.resolve(classFilePath).let {
+ it.checkExists()
+ assertFalse(it.readBytes().findAtomicfuRef(), "Found 'Lkotlinx/atomicfu/' reference in $it" )
+ }
+ }
+
+ private fun ByteArray.findAtomicfuRef(): Boolean {
+ val bytes = this.eraseMetadata()
+ loop@for (i in 0 until bytes.size - ATOMIC_FU_REF.size) {
+ for (j in 0 until ATOMIC_FU_REF.size) {
+ if (bytes[i + j] != ATOMIC_FU_REF[j]) continue@loop
+ }
+ return true
+ }
+ return false
+ }
+
+ // The atomicfu compiler plugin does not remove atomic properties from metadata,
+ // so for now we check that there are no ATOMIC_FU_REF left in the class bytecode excluding metadata.
+ // This may be reverted after the fix in the compiler plugin transformer (See #254).
+ private fun ByteArray.eraseMetadata(): ByteArray {
+ val cw = ClassWriter(ClassWriter.COMPUTE_MAXS or ClassWriter.COMPUTE_FRAMES)
+ ClassReader(this).accept(object : ClassVisitor(Opcodes.ASM9, cw) {
+ override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
+ return if (descriptor == KOTLIN_METADATA_DESC) null else super.visitAnnotation(descriptor, visible)
+ }
+ }, ClassReader.SKIP_FRAMES)
+ return cw.toByteArray()
+ }
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JsProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JsProjectTest.kt
new file mode 100644
index 0000000..0b34955
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JsProjectTest.kt
@@ -0,0 +1,49 @@
+package kotlinx.atomicfu.plugin.gradle.test
+
+import kotlinx.atomicfu.plugin.gradle.internal.*
+import kotlinx.atomicfu.plugin.gradle.internal.BaseKotlinScope
+import org.junit.Test
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the JS project:
+ * - post-compilation js transformation tasks are created
+ * (legacy transformation is tested here, compiler plugin is not applied).
+ * - original non-transformed classes are not left in compile/runtime classpath.
+ */
+class JsLegacyTransformationTest : BaseKotlinGradleTest("js-simple") {
+
+ override fun BaseKotlinScope.createProject() {
+ buildGradleKts {
+ resolve("projects/js-simple/js-simple.gradle.kts")
+ }
+ settingsGradleKts {
+ resolve("projects/js-simple/settings.gradle.kts")
+ }
+ dir("src/main/kotlin") {}
+ kotlin("IntArithmetic.kt", "main") {
+ resolve("projects/js-simple/src/main/kotlin/IntArithmetic.kt")
+ }
+ dir("src/test/kotlin") {}
+ kotlin("ArithmeticTest.kt", "test") {
+ resolve("projects/js-simple/src/test/kotlin/ArithmeticTest.kt")
+ }
+ }
+
+ @Test
+ fun testPluginApplication() =
+ checkTaskOutcomes(
+ executedTasks = listOf(
+ ":compileKotlinJs",
+ ":transformJsMainAtomicfu",
+ ":compileTestKotlinJs",
+ ":transformJsTestAtomicfu"
+ ),
+ excludedTasks = emptyList()
+ )
+
+ @Test
+ fun testClasspath() {
+ runner.build()
+ checkJsCompilationClasspath()
+ }
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JvmProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JvmProjectTest.kt
new file mode 100644
index 0000000..2545e0e
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JvmProjectTest.kt
@@ -0,0 +1,108 @@
+package kotlinx.atomicfu.plugin.gradle.test
+
+import kotlinx.atomicfu.plugin.gradle.internal.*
+import kotlinx.atomicfu.plugin.gradle.internal.BaseKotlinScope
+import org.junit.Test
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the JVM project:
+ * - post-compilation bytecode transformation tasks are created
+ * (legacy transformation is tested here, compiler plugin is not applied).
+ * - original non-transformed classes are not left in compile/runtime classpath.
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class JvmLegacyTransformationTest : BaseKotlinGradleTest("jvm-simple") {
+
+ override fun BaseKotlinScope.createProject() {
+ buildGradleKts {
+ resolve("projects/jvm-simple/jvm-simple.gradle.kts")
+ }
+ settingsGradleKts {
+ resolve("projects/jvm-simple/settings.gradle.kts")
+ }
+ dir("src/main/kotlin") {}
+ kotlin("IntArithmetic.kt", "main") {
+ resolve("projects/jvm-simple/src/main/kotlin/IntArithmetic.kt")
+ }
+ dir("src/test/kotlin") {}
+ kotlin("ArithmeticTest.kt", "test") {
+ resolve("projects/jvm-simple/src/test/kotlin/ArithmeticTest.kt")
+ }
+ }
+
+ @Test
+ fun testPluginApplication() =
+ checkTaskOutcomes(
+ executedTasks = listOf(
+ ":compileKotlin",
+ ":transformAtomicfuClasses",
+ ":compileTestKotlin",
+ ":transformTestAtomicfuClasses"
+ ),
+ excludedTasks = emptyList()
+ )
+
+ @Test
+ fun testClasspath() {
+ runner.build()
+ checkJvmCompilationClasspath(
+ originalClassFile = "build/classes/kotlin/main/IntArithmetic.class",
+ transformedClassFile = "build/classes/atomicfu/main/IntArithmetic.class"
+ )
+ }
+
+ @Test
+ fun testAtomicfuReferences() {
+ runner.build()
+ checkBytecode("build/classes/atomicfu/main/IntArithmetic.class")
+ }
+}
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the JVM project,
+ * - JVM IR compiler plugin transformation (kotlinx.atomicfu.enableJvmIrTransformation=true)
+ * - no post-compilation bytecode transforming tasks created
+ * - no `kotlinx/atomicfu` references are left in the resulting bytecode after IR transformation.
+ */
+class JvmIrTransformationTest : BaseKotlinGradleTest("jvm-simple") {
+
+ override fun BaseKotlinScope.createProject() {
+ buildGradleKts {
+ resolve("projects/jvm-simple/jvm-simple.gradle.kts")
+ }
+ settingsGradleKts {
+ resolve("projects/jvm-simple/settings.gradle.kts")
+ }
+ // set kotlinx.atomicfu.enableJvmIrTransformation=true to apply compiler plugin
+ gradleProperties {
+ resolve("projects/jvm-simple/gradle.properties")
+ }
+ dir("src/main/kotlin") {}
+ kotlin("IntArithmetic.kt", "main") {
+ resolve("projects/jvm-simple/src/main/kotlin/IntArithmetic.kt")
+ }
+ dir("src/test/kotlin") {}
+ kotlin("ArithmeticTest.kt", "test") {
+ resolve("projects/jvm-simple/src/test/kotlin/ArithmeticTest.kt")
+ }
+ }
+
+ @Test
+ fun testPluginApplication() =
+ checkTaskOutcomes(
+ executedTasks = listOf(
+ ":compileKotlin",
+ ":compileTestKotlin"
+ ),
+ excludedTasks = listOf(
+ ":transformAtomicfuClasses",
+ ":transformTestAtomicfuClasses"
+ )
+ )
+
+ @Test
+ fun testAtomicfuReferences() {
+ runner.build()
+ checkBytecode("build/classes/kotlin/main/IntArithmetic.class")
+ }
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/MppProjectTest.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/MppProjectTest.kt
new file mode 100644
index 0000000..e95b091
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/MppProjectTest.kt
@@ -0,0 +1,219 @@
+package kotlinx.atomicfu.plugin.gradle.test
+
+import kotlinx.atomicfu.plugin.gradle.internal.*
+import org.junit.*
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the MPP project:
+ * - post-compilation bytecode transformation tasks are created
+ * (legacy transformation is tested here, compiler plugin is not applied).
+ * - original non-transformed classes are not left in compile/runtime classpath.
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class MppLegacyTransformationTest : BaseKotlinGradleTest("mpp-simple") {
+
+ override fun BaseKotlinScope.createProject() {
+ buildGradleKts {
+ resolve("projects/mpp-simple/mpp-simple.gradle.kts")
+ }
+ settingsGradleKts {
+ resolve("projects/mpp-simple/settings.gradle.kts")
+ }
+ dir("src/commonMain/kotlin") {}
+ kotlin("IntArithmetic.kt", "commonMain") {
+ resolve("projects/mpp-simple/src/commonMain/kotlin/IntArithmetic.kt")
+ }
+ dir("src/commonTest/kotlin") {}
+ kotlin("ArithmeticTest.kt", "commonTest") {
+ resolve("projects/mpp-simple/src/commonTest/kotlin/ArithmeticTest.kt")
+ }
+ }
+
+ @Test
+ fun testPluginApplication() =
+ checkTaskOutcomes(
+ executedTasks = listOf(
+ ":compileKotlinJvm",
+ ":compileTestKotlinJvm",
+ ":transformJvmMainAtomicfu",
+ ":transformJvmTestAtomicfu",
+ ":compileKotlinJs",
+ ":transformJsMainAtomicfu"
+ ),
+ excludedTasks = emptyList()
+ )
+
+ @Test
+ fun testClasspath() {
+ runner.build()
+ checkJvmCompilationClasspath(
+ originalClassFile = "build/classes/kotlin/jvm/main/IntArithmetic.class",
+ transformedClassFile = "build/classes/atomicfu/jvm/main/IntArithmetic.class"
+ )
+ checkJsCompilationClasspath()
+ }
+
+ @Test
+ fun testAtomicfuReferences() {
+ runner.build()
+ checkBytecode("build/classes/atomicfu/jvm/main/IntArithmetic.class")
+ }
+}
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the MPP project,
+ * - JVM IR compiler plugin transformation (kotlinx.atomicfu.enableJvmIrTransformation=true)
+ * - no post-compilation bytecode transformation tasks are created
+ * - post-compilation js file transformation task created (as only JVM IR transformation applied, js is transformed in legacy mode)
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class MppJvmIrTransformationTest : BaseKotlinGradleTest("mpp-simple") {
+
+ override fun BaseKotlinScope.createProject() {
+ buildGradleKts {
+ resolve("projects/mpp-simple/mpp-simple.gradle.kts")
+ }
+ settingsGradleKts {
+ resolve("projects/mpp-simple/settings.gradle.kts")
+ }
+ gradleProperties {
+ resolve("projects/mpp-simple/gradle.properties_jvm")
+ }
+ dir("src/commonMain/kotlin") {}
+ kotlin("IntArithmetic.kt", "commonMain") {
+ resolve("projects/mpp-simple/src/commonMain/kotlin/IntArithmetic.kt")
+ }
+ dir("src/commonTest/kotlin") {}
+ kotlin("ArithmeticTest.kt", "commonTest") {
+ resolve("projects/mpp-simple/src/commonTest/kotlin/ArithmeticTest.kt")
+ }
+ }
+
+ @Test
+ fun testPluginApplication() =
+ checkTaskOutcomes(
+ executedTasks = listOf(
+ ":compileKotlinJvm",
+ ":compileTestKotlinJvm",
+ ":compileKotlinJs",
+ // legacy JS transformation
+ ":transformJsMainAtomicfu",
+ ":transformJsTestAtomicfu"
+ ),
+ excludedTasks = listOf(
+ ":transformJvmMainAtomicfu",
+ ":transformJvmTestAtomicfu"
+ )
+ )
+
+ @Test
+ fun testAtomicfuReferences() {
+ runner.build()
+ checkBytecode("build/classes/kotlin/jvm/main/IntArithmetic.class")
+ }
+}
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the MPP project,
+ * - JS IR compiler plugin transformation (kotlinx.atomicfu.enableJsIrTransformation=true)
+ * - post-compilation bytecode transformation tasks are created (only JS IR transformation is applied, jvm is transformed in legacy mode)
+ * - no post-compilation js file transformation tasks are created
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class MppJsIrTransformationTest : BaseKotlinGradleTest("mpp-simple") {
+
+ override fun BaseKotlinScope.createProject() {
+ buildGradleKts {
+ resolve("projects/mpp-simple/mpp-simple.gradle.kts")
+ }
+ settingsGradleKts {
+ resolve("projects/mpp-simple/settings.gradle.kts")
+ }
+ gradleProperties {
+ resolve("projects/mpp-simple/gradle.properties_js")
+ }
+ dir("src/commonMain/kotlin") {}
+ kotlin("IntArithmetic.kt", "commonMain") {
+ resolve("projects/mpp-simple/src/commonMain/kotlin/IntArithmetic.kt")
+ }
+ dir("src/commonTest/kotlin") {}
+ kotlin("ArithmeticTest.kt", "commonTest") {
+ resolve("projects/mpp-simple/src/commonTest/kotlin/ArithmeticTest.kt")
+ }
+ }
+
+ @Test
+ fun testPluginApplication() =
+ checkTaskOutcomes(
+ executedTasks = listOf(
+ ":compileKotlinJvm",
+ ":compileTestKotlinJvm",
+ ":compileKotlinJs",
+ // legacy JVM transformation
+ ":transformJvmMainAtomicfu",
+ ":transformJvmTestAtomicfu"
+ ),
+ excludedTasks = listOf(
+ ":transformJsMainAtomicfu",
+ ":transformJsTestAtomicfu"
+ )
+ )
+
+ @Test
+ fun testAtomicfuReferences() {
+ runner.build()
+ checkBytecode("build/classes/atomicfu/jvm/main/IntArithmetic.class")
+ }
+}
+
+/**
+ * Test that ensures correctness of `atomicfu-gradle-plugin` application to the MPP project,
+ * - JS IR and JVM IR compiler plugin transformation
+ * - no post-compilation bytecode transformation tasks are created (only JS IR transformation is applied, jvm is transformed in legacy mode)
+ * - no post-compilation js file transformation tasks are created
+ * - no `kotlinx/atomicfu` references are left in the transformed bytecode.
+ */
+class MppBothIrTransformationTest : BaseKotlinGradleTest("mpp-simple") {
+
+ override fun BaseKotlinScope.createProject() {
+ buildGradleKts {
+ resolve("projects/mpp-simple/mpp-simple.gradle.kts")
+ }
+ settingsGradleKts {
+ resolve("projects/mpp-simple/settings.gradle.kts")
+ }
+ gradleProperties {
+ resolve("projects/mpp-simple/gradle.properties_both")
+ }
+ dir("src/commonMain/kotlin") {}
+ kotlin("IntArithmetic.kt", "commonMain") {
+ resolve("projects/mpp-simple/src/commonMain/kotlin/IntArithmetic.kt")
+ }
+ dir("src/commonTest/kotlin") {}
+ kotlin("ArithmeticTest.kt", "commonTest") {
+ resolve("projects/mpp-simple/src/commonTest/kotlin/ArithmeticTest.kt")
+ }
+ }
+
+ @Test
+ fun testPluginApplication() =
+ checkTaskOutcomes(
+ executedTasks = listOf(
+ ":compileKotlinJvm",
+ ":compileTestKotlinJvm",
+ ":compileKotlinJs"
+ ),
+ excludedTasks = listOf(
+ ":transformJvmMainAtomicfu",
+ ":transformJvmTestAtomicfu",
+ ":transformJsMainAtomicfu",
+ ":transformJsTestAtomicfu"
+ )
+ )
+
+ @Test
+ fun testAtomicfuReferences() {
+ runner.build()
+ checkBytecode("build/classes/kotlin/jvm/main/IntArithmetic.class")
+ }
+}
diff --git a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/utils.kt b/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/utils.kt
deleted file mode 100644
index 2a8d0f7..0000000
--- a/atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/utils.kt
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2017-2018 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.testkit.runner.BuildResult
-import org.gradle.testkit.runner.TaskOutcome
-import java.io.File
-import kotlin.test.assertTrue
-
-fun BuildResult.checkOutcomes(expected: TaskOutcome, vararg tasks: String) {
- val unexpectedOutcomes = tasks
- .map { it to task(it)?.outcome }
- .filter { (_, outcome) -> outcome != expected }
- if (unexpectedOutcomes.isNotEmpty()) {
- throw AssertionError("Unexpected outcomes for tasks." +
- "\nExpected: $expected." +
- "\nGot:" +
- "\n${unexpectedOutcomes.joinToString("\n") { (task, outcome) -> "* $task -> $outcome" }}")
-
- }
-}
-
-fun File.checkExists() {
- assertTrue(exists(), "File does not exist: $canonicalPath")
-}
-
-fun File.modify(fn: (String) -> String) {
- writeText(fn(readText()))
-}
-
-fun String.checkedReplace(oldValue: String, newValue: String, ignoreCase: Boolean = false): String {
- check(contains(oldValue, ignoreCase)) { "String must contain '$oldValue'" }
- return replace(oldValue, newValue, ignoreCase)
-} \ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/empty/build.gradle b/atomicfu-gradle-plugin/src/test/resources/projects/empty/build.gradle
deleted file mode 100644
index f4add41..0000000
--- a/atomicfu-gradle-plugin/src/test/resources/projects/empty/build.gradle
+++ /dev/null
@@ -1,10 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-apply plugin: 'kotlinx-atomicfu'
-apply plugin: 'base'
-
-repositories {
- maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
-} \ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/build.gradle b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/build.gradle
deleted file mode 100644
index 31dbec6..0000000
--- a/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/build.gradle
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-apply plugin: 'kotlinx-atomicfu'
-apply plugin: 'kotlin2js'
-
-dependencies {
- compileOnly atomicfuJs
- testRuntime atomicfuJs
-
- compile 'org.jetbrains.kotlin:kotlin-stdlib-js'
- testCompile 'org.jetbrains.kotlin:kotlin-test-js'
-}
-
-compileTestKotlin2Js.doLast {
- file("$buildDir/test_compile_classpath.txt").text = classpath.join("\n")
-} \ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/js-simple.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/js-simple.gradle.kts
new file mode 100644
index 0000000..37a41e5
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/js-simple.gradle.kts
@@ -0,0 +1,38 @@
+import kotlinx.atomicfu.plugin.gradle.*
+
+buildscript {
+ dependencies {
+ classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.17.0")
+ }
+}
+
+plugins {
+ kotlin("js")
+}
+
+apply(plugin = "kotlinx-atomicfu")
+
+repositories {
+ mavenLocal()
+ mavenCentral()
+}
+
+dependencies {
+ implementation(kotlin("stdlib-js"))
+ implementation(kotlin("test-junit"))
+ implementation("org.jetbrains.kotlin:kotlin-test-js")
+}
+
+kotlin {
+ js {
+ nodejs()
+ }
+
+ tasks.named("compileTestKotlinJs") {
+ doLast {
+ file("$buildDir/test_compile_js_classpath.txt").writeText(
+ target.compilations["test"].compileDependencyFiles.joinToString("\n")
+ )
+ }
+ }
+}
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/settings.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/settings.gradle.kts
new file mode 100644
index 0000000..bd39e74
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/js-simple/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "js-simple" \ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/build.gradle b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/build.gradle
deleted file mode 100644
index 7e21215..0000000
--- a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/build.gradle
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-apply plugin: 'kotlinx-atomicfu'
-apply plugin: 'kotlin'
-
-// This flag is enabled to be able using JVM IR compiled dependencies (when build is ran with -Penable_jvm_ir)
-kotlin.target.compilations.all {
- kotlinOptions.freeCompilerArgs += '-Xallow-jvm-ir-dependencies'
-}
-
-dependencies {
- compileOnly atomicfuJvm
- testRuntime atomicfuJvm
-
- compile 'org.jetbrains.kotlin:kotlin-stdlib'
-
- testCompile 'org.jetbrains.kotlin:kotlin-test'
- testCompile 'org.jetbrains.kotlin:kotlin-test-junit'
- testCompile 'junit:junit:4.12'
-}
-
-compileTestKotlin.doLast {
- file("$buildDir/test_compile_classpath.txt").text = classpath.join("\n")
-}
-
-test.doLast {
- file("$buildDir/test_runtime_classpath.txt").text = classpath.join("\n")
-} \ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/gradle.properties b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/gradle.properties
new file mode 100644
index 0000000..fa37a2c
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/gradle.properties
@@ -0,0 +1 @@
+kotlinx.atomicfu.enableJvmIrTransformation=true
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/jvm-simple.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/jvm-simple.gradle.kts
new file mode 100644
index 0000000..db644ef
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/jvm-simple.gradle.kts
@@ -0,0 +1,41 @@
+import org.gradle.api.tasks.compile.*
+import org.jetbrains.kotlin.gradle.plugin.*
+
+buildscript {
+ dependencies {
+ classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.17.0")
+ }
+}
+
+plugins {
+ kotlin("jvm")
+}
+
+apply(plugin = "kotlinx-atomicfu")
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation(kotlin("stdlib"))
+ implementation(kotlin("test-junit"))
+}
+
+kotlin {
+ tasks.compileTestKotlin {
+ doLast {
+ file("$buildDir/test_compile_jvm_classpath.txt").writeText(
+ target.compilations["test"].compileDependencyFiles.joinToString("\n")
+ )
+ }
+ }
+
+ tasks.test {
+ doLast {
+ file("$buildDir/test_runtime_jvm_classpath.txt").writeText(
+ (target.compilations["test"] as KotlinCompilationToRunnableFiles<*>).runtimeDependencyFiles.joinToString("\n")
+ )
+ }
+ }
+}
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/settings.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/settings.gradle.kts
new file mode 100644
index 0000000..2f5327f
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/jvm-simple/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "jvm-simple" \ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/build.gradle b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/build.gradle
deleted file mode 100644
index fc95366..0000000
--- a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/build.gradle
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-apply plugin: 'kotlinx-atomicfu'
-apply plugin: 'kotlin-multiplatform'
-
-kotlin {
- // This flag is enabled to be able using JVM IR compiled dependencies (when build is ran with -Penable_jvm_ir)
- jvm() {
- compilations.all {
- kotlinOptions.freeCompilerArgs += '-Xallow-jvm-ir-dependencies'
- }
- }
- js()
-
- sourceSets {
- commonMain.dependencies {
- implementation 'org.jetbrains.kotlin:kotlin-stdlib-common'
- compileOnly atomicfuMetadata
-
- }
- commonTest.dependencies {
- implementation 'org.jetbrains.kotlin:kotlin-test-common'
- implementation 'org.jetbrains.kotlin:kotlin-test-annotations-common'
- runtimeOnly atomicfuMetadata
-
- }
- jsMain.dependencies {
- implementation 'org.jetbrains.kotlin:kotlin-stdlib-js'
- compileOnly atomicfuJs
-
- }
- jsTest.dependencies {
- implementation 'org.jetbrains.kotlin:kotlin-test-js'
- runtimeOnly atomicfuJs
- }
- jvmMain.dependencies {
- implementation 'org.jetbrains.kotlin:kotlin-stdlib'
- compileOnly atomicfuJvm
- }
- jvmTest.dependencies {
- implementation 'org.jetbrains.kotlin:kotlin-test'
- implementation 'org.jetbrains.kotlin:kotlin-test-junit'
- implementation "junit:junit:4.12"
- runtimeOnly atomicfuJvm
- }
- }
-}
-
-def File classpathFile(String platform, String fileName) {
- def dir = file("$buildDir/classpath/$platform")
- dir.mkdirs()
- return file("$dir/$fileName")
-}
-
-
-compileTestKotlinJvm.doLast {
- classpathFile("jvm", "test_compile.txt").text = classpath.files.join("\n")
-}
-
-jvmTest.doLast {
- classpathFile("jvm", "test_runtime.txt").text = classpath.files.join("\n")
-}
-
-
-compileTestKotlinJs.doLast {
- classpathFile("js", "test_compile.txt").text = classpath.files.join("\n")
-}
-
-jsTest.dependsOn(":compileTestKotlinJs")
-jsTest.dependsOn(":transformJsTestAtomicfu")
-check.dependsOn(":jsTest") \ No newline at end of file
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_both b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_both
new file mode 100644
index 0000000..5d28ccd
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_both
@@ -0,0 +1,3 @@
+kotlin.js.compiler=ir
+kotlinx.atomicfu.enableJvmIrTransformation=true
+kotlinx.atomicfu.enableJsIrTransformation=true
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_js b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_js
new file mode 100644
index 0000000..b57875b
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_js
@@ -0,0 +1,2 @@
+kotlin.js.compiler=ir
+kotlinx.atomicfu.enableJsIrTransformation=true
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_jvm b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_jvm
new file mode 100644
index 0000000..fa37a2c
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/gradle.properties_jvm
@@ -0,0 +1 @@
+kotlinx.atomicfu.enableJvmIrTransformation=true
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/mpp-simple.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/mpp-simple.gradle.kts
new file mode 100644
index 0000000..ed15d3d
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/mpp-simple.gradle.kts
@@ -0,0 +1,90 @@
+import org.jetbrains.kotlin.gradle.plugin.*
+
+buildscript {
+ dependencies {
+ classpath("org.jetbrains.kotlinx:atomicfu-gradle-plugin:0.17.0")
+ }
+}
+
+plugins {
+ kotlin("multiplatform")
+}
+
+apply(plugin = "kotlinx-atomicfu")
+
+repositories {
+ mavenCentral()
+}
+
+kotlin {
+ targets {
+ jvm {
+ compilations.all {
+ kotlinOptions.jvmTarget = "1.8"
+ }
+ testRuns["test"].executionTask.configure {
+ useJUnit()
+ }
+ }
+ js {
+ nodejs()
+ }
+ }
+ sourceSets {
+ val commonMain by getting {
+ dependencies {
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-common")
+ }
+ }
+ val commonTest by getting {
+ dependencies {
+ implementation(kotlin("test-common"))
+ implementation(kotlin("test-annotations-common"))
+ }
+ }
+ val jvmMain by getting {
+ dependencies {
+ implementation(kotlin("stdlib"))
+ }
+ }
+ val jvmTest by getting {
+ dependencies {
+ implementation(kotlin("test-junit"))
+ }
+ }
+ val jsMain by getting {
+ dependencies {
+ implementation("org.jetbrains.kotlin:kotlin-stdlib-js")
+ }
+ }
+ val jsTest by getting {
+ dependencies {
+ implementation("org.jetbrains.kotlin:kotlin-test-js")
+ }
+ }
+ }
+
+ tasks.named("compileTestKotlinJvm") {
+ doLast {
+ file("$buildDir/test_compile_jvm_classpath.txt").writeText(
+ targets["jvm"].compilations["test"].compileDependencyFiles.joinToString("\n")
+ )
+ }
+ }
+
+ tasks.named("jvmTest") {
+ doLast {
+ file("$buildDir/test_runtime_jvm_classpath.txt").writeText(
+ (targets["jvm"].compilations["test"] as KotlinCompilationToRunnableFiles).runtimeDependencyFiles.joinToString("\n")
+ )
+ }
+ }
+
+ tasks.named("compileTestKotlinJs") {
+ doLast {
+ file("$buildDir/test_compile_js_classpath.txt").writeText(
+ targets["js"].compilations["test"].compileDependencyFiles.joinToString("\n")
+ )
+ }
+ }
+}
diff --git a/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/settings.gradle.kts b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/settings.gradle.kts
new file mode 100644
index 0000000..5a4e5ab
--- /dev/null
+++ b/atomicfu-gradle-plugin/src/test/resources/projects/mpp-simple/settings.gradle.kts
@@ -0,0 +1 @@
+rootProject.name = "mpp-simple" \ No newline at end of file
diff --git a/atomicfu-maven-plugin/build.gradle b/atomicfu-maven-plugin/build.gradle
index 8929be9..a165769 100644
--- a/atomicfu-maven-plugin/build.gradle
+++ b/atomicfu-maven-plugin/build.gradle
@@ -18,7 +18,7 @@ dependencies {
}
def pomFile = file("$buildDir/pom.xml")
-def outputDir = compileKotlin.destinationDir
+def outputDir = compileKotlin.destinationDirectory
def buildSnapshots = rootProject.properties['build_snapshot_train'] != null
evaluationDependsOn(':atomicfu-transformer')
@@ -37,32 +37,17 @@ task generatePomFile(dependsOn: [compileKotlin, ':atomicfu-transformer:publishTo
asNode().with {
appendNode('build').with {
appendNode('directory', buildDir)
- appendNode('outputDirectory', outputDir)
+ appendNode('outputDirectory', outputDir.get().getAsFile())
}
appendNode('properties').with {
appendNode('project.build.sourceEncoding', 'UTF-8')
}
appendNode('repositories').with {
appendNode('repository').with {
- appendNode('id', 'kotlin-eap')
- appendNode('url', 'https://kotlin.bintray.com/kotlin-eap')
- }
-
- appendNode('repository').with {
- appendNode('id', 'kotlin-dev')
- appendNode('url', 'https://kotlin.bintray.com/kotlin-dev')
- }
-
- appendNode('repository').with {
appendNode('id', 'dev')
appendNode('url', 'https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev')
}
- appendNode('repository').with {
- appendNode('id', 'kotlinx')
- appendNode('url', 'https://kotlin.bintray.com/kotlinx')
- }
-
if (buildSnapshots) {
appendNode('repository').with {
appendNode('id', 'kotlin-snapshots')
@@ -84,7 +69,7 @@ String mavenRepoLocal = System.getProperty("maven.repo.local")
// runs the plugin description generator
task generatePluginDescriptor(type: Exec, dependsOn: generatePomFile) {
- def pluginDescriptorFile = new File(outputDir, 'META-INF/maven/plugin.xml')
+ def pluginDescriptorFile = outputDir.file('META-INF/maven/plugin.xml')
workingDir projectDir
boolean isWindows = System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0
@@ -100,8 +85,9 @@ task generatePluginDescriptor(type: Exec, dependsOn: generatePomFile) {
])
commandLine args
doLast {
- assert pluginDescriptorFile.file, "$pluginDescriptorFile: was not generated"
- logger.info("Plugin descriptor is generated in $pluginDescriptorFile")
+ def descriptorFile = pluginDescriptorFile.get().getAsFile()
+ assert descriptorFile, "$descriptorFile: was not generated"
+ logger.info("Plugin descriptor is generated in $descriptorFile")
}
}
diff --git a/atomicfu-maven-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/TransformMojo.kt b/atomicfu-maven-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/TransformMojo.kt
index 28580d8..5f45f3e 100644
--- a/atomicfu-maven-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/TransformMojo.kt
+++ b/atomicfu-maven-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/TransformMojo.kt
@@ -17,7 +17,7 @@
package kotlinx.atomicfu.plugin
import kotlinx.atomicfu.transformer.AtomicFUTransformer
-import kotlinx.atomicfu.transformer.Variant
+import kotlinx.atomicfu.transformer.JvmVariant
import org.apache.maven.plugin.AbstractMojo
import org.apache.maven.plugins.annotations.LifecyclePhase
import org.apache.maven.plugins.annotations.Mojo
@@ -50,8 +50,8 @@ class TransformMojo : AbstractMojo() {
/**
* Transformation variant: "FU", "VH", or "BOTH".
*/
- @Parameter(defaultValue = "FU", property = "variant", required = true)
- lateinit var variant: Variant
+ @Parameter(defaultValue = "FU", property = "jvmVariant", required = true)
+ lateinit var jvmVariant: JvmVariant
/**
* Verbose debug info.
@@ -60,7 +60,7 @@ class TransformMojo : AbstractMojo() {
var verbose: Boolean = false
override fun execute() {
- val t = AtomicFUTransformer(classpath, input, output, variant)
+ val t = AtomicFUTransformer(classpath, input, output, jvmVariant)
t.verbose = verbose
t.transform()
}
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
index ba08fbc..0687f38 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AsmUtil.kt
@@ -4,7 +4,9 @@
package kotlinx.atomicfu.transformer
+import org.objectweb.asm.*
import org.objectweb.asm.Opcodes.*
+import org.objectweb.asm.Type.*
import org.objectweb.asm.tree.*
import org.objectweb.asm.util.*
@@ -69,12 +71,23 @@ fun AbstractInsnNode.isGetField(owner: String) =
fun AbstractInsnNode.isGetStatic(owner: String) =
this is FieldInsnNode && this.opcode == GETSTATIC && this.owner == owner
+fun AbstractInsnNode.isGetFieldOrGetStatic() =
+ this is FieldInsnNode && (this.opcode == GETFIELD || this.opcode == GETSTATIC)
+
fun AbstractInsnNode.isAreturn() =
this.opcode == ARETURN
fun AbstractInsnNode.isReturn() =
this.opcode == RETURN
+fun AbstractInsnNode.isTypeReturn(type: Type) =
+ opcode == when (type) {
+ INT_TYPE -> IRETURN
+ LONG_TYPE -> LRETURN
+ BOOLEAN_TYPE -> IRETURN
+ else -> ARETURN
+ }
+
fun AbstractInsnNode.isInvokeVirtual() =
this.opcode == INVOKEVIRTUAL
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
index c7c262c..a138422 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformer.kt
@@ -91,7 +91,6 @@ private val TRACE_APPEND_3 = MethodId(TRACE_BASE_CLS, "append", getMethodDescrip
private val TRACE_APPEND_4 = MethodId(TRACE_BASE_CLS, "append", getMethodDescriptor(VOID_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE, OBJECT_TYPE), INVOKEVIRTUAL)
private val TRACE_DEFAULT_ARGS = "I${OBJECT_TYPE.descriptor}"
private const val DEFAULT = "\$default"
-private const val DELEGATE = "\$delegate"
private val TRACE_FACTORY = MethodId(TRACE_KT, TRACE, "(IL$AFU_PKG/$TRACE_FORMAT;)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
private val TRACE_PARTIAL_ARGS_FACTORY = MethodId(TRACE_KT, "$TRACE$DEFAULT", "(IL$AFU_PKG/$TRACE_FORMAT;$TRACE_DEFAULT_ARGS)L$AFU_PKG/$TRACE_BASE;", INVOKESTATIC)
@@ -126,7 +125,7 @@ private inline fun code(mv: MethodVisitor, block: InstructionAdapter.() -> Unit)
}
private inline fun insns(block: InstructionAdapter.() -> Unit): InsnList {
- val node = MethodNode(ASM5)
+ val node = MethodNode(ASM9)
block(InstructionAdapter(node))
return node.instructions
}
@@ -169,13 +168,13 @@ class FieldInfo(
override fun toString(): String = "${owner.prettyStr()}::$name"
}
-enum class Variant { FU, VH, BOTH }
+enum class JvmVariant { FU, VH, BOTH }
class AtomicFUTransformer(
classpath: List<String>,
inputDir: File,
outputDir: File = inputDir,
- var variant: Variant = Variant.FU
+ var jvmVariant: JvmVariant = JvmVariant.FU
) : AtomicFUTransformerBase(inputDir, outputDir) {
private val classPathLoader = URLClassLoader(
@@ -188,6 +187,7 @@ class AtomicFUTransformer(
private val traceFields = mutableSetOf<FieldId>()
private val traceAccessors = mutableSetOf<MethodId>()
private val fieldDelegates = mutableMapOf<FieldId, FieldInfo>()
+ private val delegatedPropertiesAccessors = mutableMapOf<FieldId, MethodId>()
private val removeMethods = mutableSetOf<MethodId>()
override fun transform() {
@@ -195,7 +195,7 @@ class AtomicFUTransformer(
val files = inputDir.walk().filter { it.isFile }.toList()
val needTransform = analyzeFilesForFields(files)
if (needTransform || outputDir == inputDir) {
- val vh = variant == Variant.VH
+ val vh = jvmVariant == JvmVariant.VH
// visit method bodies for external references to fields, runs all logic, fails if anything is wrong
val needsTransform = analyzeFilesForRefs(files, vh)
// perform transformation
@@ -205,7 +205,7 @@ class AtomicFUTransformer(
val outBytes = if (file.isClassFile() && file in needsTransform) transformFile(file, bytes, vh) else bytes
val outFile = file.toOutputFile()
outFile.mkdirsAndWrite(outBytes)
- if (variant == Variant.BOTH && outBytes !== bytes) {
+ if (jvmVariant == JvmVariant.BOTH && outBytes !== bytes) {
val vhBytes = transformFile(file, bytes, true)
val vhFile = outputDir / "META-INF" / "versions" / "9" / file.relativeTo(inputDir).toString()
vhFile.mkdirsAndWrite(vhBytes)
@@ -280,7 +280,7 @@ class AtomicFUTransformer(
return cw.toByteArray() // write transformed bytes
}
- private abstract inner class CV(cv: ClassVisitor?) : ClassVisitor(ASM5, cv) {
+ private abstract inner class CV(cv: ClassVisitor?) : ClassVisitor(ASM9, cv) {
lateinit var className: String
override fun visit(
@@ -341,6 +341,10 @@ class AtomicFUTransformer(
// check for copying atomic values into delegate fields and register potential delegate fields
return DelegateFieldsCollectorMV(access, name, desc, signature, exceptions)
}
+ // collect accessors of potential delegated properties
+ if (methodType.argumentTypes.isEmpty()) {
+ return DelegatedFieldAccessorCollectorMV(className, methodType.returnType, access, name, desc, signature, exceptions)
+ }
return null
}
}
@@ -348,7 +352,7 @@ class AtomicFUTransformer(
private inner class AccessorCollectorMV(
private val className: String,
access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
- ) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
+ ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
override fun visitEnd() {
val insns = instructions.listUseful(4)
if (insns.size == 3 &&
@@ -396,10 +400,47 @@ class AtomicFUTransformer(
}
}
- private inner class DelegateFieldsCollectorMV(
+ private inner class DelegatedFieldAccessorCollectorMV(
+ private val className: String, private val returnType: Type,
access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
override fun visitEnd() {
+ // check for pattern of a delegated property getter
+ // getfield/getstatic a$delegate: Atomic*
+ // astore_i ...
+ // aload_i
+ // invokevirtual Atomic*.getValue()
+ // ireturn
+ var cur = instructions.first
+ while (cur != null && !(cur.isGetFieldOrGetStatic() && getType((cur as FieldInsnNode).desc) in AFU_TYPES)) {
+ cur = cur.next
+ }
+ if (cur != null && cur.next.opcode == ASTORE) {
+ val fi = cur as FieldInsnNode
+ val fieldDelegate = FieldId(className, fi.name, fi.desc)
+ val atomicType = getType(fi.desc)
+ val v = (cur.next as VarInsnNode).`var`
+ while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
+ cur = cur.next
+ }
+ val invokeVirtual = cur.next
+ if (invokeVirtual.opcode == INVOKEVIRTUAL && (invokeVirtual as MethodInsnNode).name == GET_VALUE && invokeVirtual.owner == atomicType.internalName) {
+ // followed by RETURN operation
+ val next = invokeVirtual.nextUseful
+ val ret = if (next?.opcode == CHECKCAST) next.nextUseful else next
+ if (ret != null && ret.isTypeReturn(returnType)) {
+ // register delegated property accessor
+ delegatedPropertiesAccessors[fieldDelegate] = MethodId(className, name, desc, accessToInvokeOpcode(access))
+ }
+ }
+ }
+ }
+ }
+
+ private inner class DelegateFieldsCollectorMV(
+ access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?
+ ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
+ override fun visitEnd() {
// register delegate field and the corresponding original atomic field
// getfield a: *Atomic
// putfield a$delegate: *Atomic
@@ -408,7 +449,7 @@ class AtomicFUTransformer(
insn.checkGetFieldOrGetStatic()?.let { getfieldId ->
val next = insn.next
(next as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
- if (delegateFieldId.name.endsWith(DELEGATE)) {
+ if (getfieldId in fields && delegateFieldId in fields) {
// original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
val originalField = fields[getfieldId]!!
fieldDelegates[delegateFieldId] = originalField
@@ -420,11 +461,12 @@ class AtomicFUTransformer(
val methodId = MethodId(insn.owner, insn.name, insn.desc, insn.opcode)
if (methodId in FACTORIES) {
(insn.nextUseful as? FieldInsnNode)?.checkPutFieldOrPutStatic()?.let { delegateFieldId ->
- if (delegateFieldId.name.endsWith(DELEGATE)) {
+ val fieldType = getType(insn.desc).returnType
+ if (fieldType in AFU_TYPES) {
+ val isStatic = insn.nextUseful!!.opcode == PUTSTATIC
// delegate field is initialized by a factory invocation
- val fieldType = getType(insn.desc).returnType
// for volatile delegated properties store FieldInfo of the delegate field itself
- fieldDelegates[delegateFieldId] = FieldInfo(delegateFieldId, fieldType)
+ fieldDelegates[delegateFieldId] = FieldInfo(delegateFieldId, fieldType, isStatic)
}
}
}
@@ -447,6 +489,8 @@ class AtomicFUTransformer(
return if (fieldId in fields) fieldId else null
}
+ private fun FieldId.isFieldDelegate() = this in fieldDelegates && delegatedPropertiesAccessors.contains(this)
+
private inner class TransformerCV(
cv: ClassVisitor?,
private val vh: Boolean,
@@ -460,7 +504,7 @@ class AtomicFUTransformer(
private var originalClinit: MethodNode? = null
private var newClinit: MethodNode? = null
- private fun newClinit() = MethodNode(ASM5, ACC_STATIC, "<clinit>", "()V", null, null)
+ private fun newClinit() = MethodNode(ASM9, ACC_STATIC, "<clinit>", "()V", null, null)
fun getOrCreateNewClinit(): MethodNode = newClinit ?: newClinit().also { newClinit = it }
override fun visitSource(source: String?, debug: String?) {
@@ -478,8 +522,8 @@ class AtomicFUTransformer(
val fieldType = getType(desc)
if (fieldType.sort == OBJECT && fieldType.internalName in AFU_CLASSES) {
val fieldId = FieldId(className, name, desc)
- // skip delegate field
- if (fieldId in fieldDelegates && (fieldId != fieldDelegates[fieldId]!!.fieldId)) {
+ // skip field delegates except volatile delegated properties (e.g. val a: Int by atomic(0))
+ if (fieldId.isFieldDelegate() && (fieldId != fieldDelegates[fieldId]!!.fieldId)) {
transformed = true
return null
}
@@ -607,7 +651,7 @@ class AtomicFUTransformer(
val superMV = if (name == "<clinit>" && desc == "()V") {
if (access and ACC_STATIC == 0) abort("<clinit> method not marked as static")
// defer writing class initialization method
- val node = MethodNode(ASM5, access, name, desc, signature, exceptions)
+ val node = MethodNode(ASM9, access, name, desc, signature, exceptions)
if (originalClinit != null) abort("Multiple <clinit> methods found")
originalClinit = node
node
@@ -673,7 +717,7 @@ class AtomicFUTransformer(
private val packageName: String,
private val vh: Boolean,
private val analyzePhase2: Boolean // true in Phase 2 when we are analyzing file for refs (not transforming yet)
- ) : MethodNode(ASM5, access, name, desc, signature, exceptions) {
+ ) : MethodNode(ASM9, access, name, desc, signature, exceptions) {
init {
this.mv = mv
}
@@ -715,14 +759,19 @@ class AtomicFUTransformer(
i = i.next
hasErrors = true
}
+ // make sure all kotlinx/atomicfu references removed
+ removeAtomicReferencesFromLVT()
// save transformed method if not in analysis phase
if (!hasErrors && !analyzePhase2)
accept(mv)
}
+ private fun removeAtomicReferencesFromLVT() =
+ localVariables?.removeIf { getType(it.desc) in AFU_TYPES }
+
private fun FieldInsnNode.checkCopyToDelegate(): AbstractInsnNode? {
val fieldId = FieldId(owner, name, desc)
- if (fieldId in fieldDelegates) {
+ if (fieldId.isFieldDelegate()) {
// original atomic value is copied to the synthetic delegate atomic field <delegated field name>$delegate
val originalField = fieldDelegates[fieldId]!!
val getField = previous as FieldInsnNode
@@ -748,51 +797,29 @@ class AtomicFUTransformer(
if (iv.name == GET_VALUE || iv.name == SET_VALUE) {
check(!f.isArray || onArrayElement) { "getValue/setValue can only be called on elements of arrays" }
val setInsn = iv.name == SET_VALUE
- if (!onArrayElement) {
- val primitiveType = f.getPrimitiveType(vh)
- val owner = if (!vh && f.isStatic) f.refVolatileClassName else f.owner
- if (!vh && f.isStatic) {
- val getOwnerClass = FieldInsnNode(
- GETSTATIC,
- f.owner,
- f.staticRefVolatileField,
- getObjectType(owner).descriptor
- )
- instructions.insert(ld, getOwnerClass)
- }
- instructions.remove(ld) // drop getstatic (we don't need field updater)
- val j = FieldInsnNode(
- when {
- iv.name == GET_VALUE -> if (f.isStatic && vh) GETSTATIC else GETFIELD
- else -> if (f.isStatic && vh) PUTSTATIC else PUTFIELD
- }, owner, f.name, primitiveType.descriptor
- )
- instructions.set(iv, j) // replace invokevirtual with get/setfield
- return j.next
+ if (!onArrayElement) return getPureTypeField(ld, f, iv)
+ var methodType = getMethodType(iv.desc)
+ if (f.typeInfo.originalType != f.typeInfo.transformedType && !vh) {
+ val ret = f.typeInfo.transformedType.elementType
+ iv.desc = if (setInsn) getMethodDescriptor(methodType.returnType, ret) else getMethodDescriptor(ret, *methodType.argumentTypes)
+ methodType = getMethodType(iv.desc)
+ }
+ iv.name = iv.name.substring(0, 3)
+ if (!vh) {
+ // map to j.u.c.a.Atomic*Array get or set
+ iv.owner = descToName(f.fuType.descriptor)
+ iv.desc = getMethodDescriptor(methodType.returnType, INT_TYPE, *methodType.argumentTypes)
} else {
- var methodType = getMethodType(iv.desc)
- if (f.typeInfo.originalType != f.typeInfo.transformedType && !vh) {
- val ret = f.typeInfo.transformedType.elementType
- iv.desc = if (setInsn) getMethodDescriptor(methodType.returnType, ret) else getMethodDescriptor(ret, *methodType.argumentTypes)
- methodType = getMethodType(iv.desc)
- }
- iv.name = iv.name.substring(0, 3)
- if (!vh) {
- // map to j.u.c.a.Atomic*Array get or set
- iv.owner = descToName(f.fuType.descriptor)
- iv.desc = getMethodDescriptor(methodType.returnType, INT_TYPE, *methodType.argumentTypes)
- } else {
- // map to VarHandle get or set
- iv.owner = descToName(VH_TYPE.descriptor)
- iv.desc = getMethodDescriptor(
- methodType.returnType,
- f.getPrimitiveType(vh),
- INT_TYPE,
- *methodType.argumentTypes
- )
- }
- return iv
+ // map to VarHandle get or set
+ iv.owner = descToName(VH_TYPE.descriptor)
+ iv.desc = getMethodDescriptor(
+ methodType.returnType,
+ f.getPrimitiveType(vh),
+ INT_TYPE,
+ *methodType.argumentTypes
+ )
}
+ return iv
}
if (f.isArray && iv.name == GET_SIZE) {
if (!vh) {
@@ -855,6 +882,29 @@ class AtomicFUTransformer(
return iv.next
}
+ private fun getPureTypeField(ld: FieldInsnNode, f: FieldInfo, iv: MethodInsnNode): AbstractInsnNode? {
+ val primitiveType = f.getPrimitiveType(vh)
+ val owner = if (!vh && f.isStatic) f.refVolatileClassName else f.owner
+ if (!vh && f.isStatic) {
+ val getOwnerClass = FieldInsnNode(
+ GETSTATIC,
+ f.owner,
+ f.staticRefVolatileField,
+ getObjectType(owner).descriptor
+ )
+ instructions.insert(ld, getOwnerClass)
+ }
+ instructions.remove(ld) // drop getfield/getstatic of the atomic field
+ val j = FieldInsnNode(
+ when {
+ iv.name == GET_VALUE -> if (f.isStatic && vh) GETSTATIC else GETFIELD
+ else -> if (f.isStatic && vh) PUTSTATIC else PUTFIELD
+ }, owner, f.name, primitiveType.descriptor
+ )
+ instructions.set(iv, j) // replace invokevirtual with get/setfield
+ return j.next
+ }
+
private fun vhOperation(iv: MethodInsnNode, typeInfo: TypeInfo, f: FieldInfo) {
val methodType = getMethodType(iv.desc)
val args = methodType.argumentTypes
@@ -1305,7 +1355,7 @@ class AtomicFUTransformer(
is FieldInsnNode -> {
val fieldId = FieldId(i.owner, i.name, i.desc)
if ((i.opcode == GETFIELD || i.opcode == GETSTATIC) && fieldId in fields) {
- if (fieldId in fieldDelegates && i.next.opcode == ASTORE) {
+ if (fieldId.isFieldDelegate() && i.next.opcode == ASTORE) {
return transformDelegatedFieldAccessor(i, fieldId)
}
(i.next as? FieldInsnNode)?.checkCopyToDelegate()?.let { return it } // atomic field is copied to delegate field
@@ -1338,29 +1388,25 @@ class AtomicFUTransformer(
return i.next
}
- private fun transformDelegatedFieldAccessor(i: FieldInsnNode, fieldId: FieldId): AbstractInsnNode {
+ private fun transformDelegatedFieldAccessor(i: FieldInsnNode, fieldId: FieldId): AbstractInsnNode? {
val f = fieldDelegates[fieldId]!!
- val astore = (i.next as? VarInsnNode) ?: abort("Method $name does not match the pattern of a delegated field accessor")
- val v = astore.`var`
- var cur: AbstractInsnNode = i
+ val v = (i.next as VarInsnNode).`var`
+ // remove instructions [astore_v .. aload_v]
+ var cur: AbstractInsnNode = i.next
while (!(cur is VarInsnNode && cur.opcode == ALOAD && cur.`var` == v)) {
val next = cur.next
instructions.remove(cur)
cur = next
}
- val invokeVirtual = FlowAnalyzer(cur.next).execute()
- instructions.remove(cur)
- check(invokeVirtual.isAtomicGetValueOrSetValue()) { "Aload of the field delegate $f should be followed with Atomic*.getValue()/setValue() invocation" }
- val accessorName = (invokeVirtual as MethodInsnNode).name.substring(0, 3)
- val isGetter = accessorName == "get"
- val primitiveType = f.getPrimitiveType(vh)
- val j = FieldInsnNode(if (isGetter) GETFIELD else PUTFIELD, f.owner, f.name, primitiveType.descriptor)
- instructions.set(invokeVirtual, j)
+ val iv = FlowAnalyzer(cur.next).execute()
+ check(iv.isAtomicGetValueOrSetValue()) { "Aload of the field delegate $f should be followed with Atomic*.getValue()/setValue() invocation" }
+ val isGetter = (iv as MethodInsnNode).name == GET_VALUE
+ instructions.remove(cur) // remove aload_v
localVariables.removeIf {
!(getType(it.desc).internalName == f.owner ||
(!isGetter && getType(it.desc) == getType(desc).argumentTypes.first() && it.name == "<set-?>"))
}
- return j.next
+ return getPureTypeField(i, f, iv)
}
private fun AbstractInsnNode.isAtomicGetFieldOrGetStatic() =
@@ -1466,7 +1512,7 @@ fun main(args: Array<String>) {
}
val t = AtomicFUTransformer(emptyList(), File(args[0]))
if (args.size > 1) t.outputDir = File(args[1])
- if (args.size > 2) t.variant = enumValueOf(args[2].toUpperCase(Locale.US))
+ if (args.size > 2) t.jvmVariant = enumValueOf(args[2].toUpperCase(Locale.US))
t.verbose = true
t.transform()
}
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt
index 91f3037..de8f94a 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/AtomicFUTransformerJS.kt
@@ -52,8 +52,8 @@ class AtomicFUTransformerJS(
outputDir: File
) : AtomicFUTransformerBase(inputDir, outputDir) {
private val atomicConstructors = mutableSetOf<String>()
- private val fieldDelegates = mutableMapOf<String, String>()
- private val delegatedProperties = mutableMapOf<String, String>()
+ private val delegateToOriginalAtomicField = mutableMapOf<String, Name>()
+ private val topLevelDelegatedFieldAccessorToOriginalField = mutableMapOf<String, Name>()
private val atomicArrayConstructors = mutableMapOf<String, String?>()
private val traceConstructors = mutableSetOf<String>()
private val traceFormatObjects = mutableSetOf<String>()
@@ -81,6 +81,7 @@ class AtomicFUTransformerJS(
root.visit(AtomicConstructorDetector())
root.visit(FieldDelegatesVisitor())
root.visit(DelegatedPropertyAccessorsVisitor())
+ root.visit(TopLevelDelegatedFieldsAccessorVisitor())
root.visit(TransformVisitor())
root.visit(AtomicOperationsInliner())
return root.eraseGetValue().toByteArray()
@@ -283,12 +284,17 @@ class AtomicFUTransformerJS(
if (stmt is ExpressionStatement) {
if (stmt.expression is Assignment) {
val delegateAssignment = stmt.expression as Assignment
- if (delegateAssignment.right is PropertyGet) {
- val initializer = delegateAssignment.right as PropertyGet
- if (initializer.toSource() == atomicField.toSource()) {
- // register field delegate and the original atomic field
- fieldDelegates[(delegateAssignment.left as PropertyGet).property.toSource()] =
- (atomicField as PropertyGet).property.toSource()
+ val initializer = delegateAssignment.right
+ if (initializer.toSource() == atomicField.toSource()) {
+ if (delegateAssignment.right is PropertyGet) { // initialization of a class field
+ // delegate${owner_class} to original atomic field
+ val delegateFieldName = (delegateAssignment.left as PropertyGet).property.toSource()
+ val ownerClassName = constructorBlock.enclosingFunction.functionName.identifier
+ delegateToOriginalAtomicField["$delegateFieldName\$$ownerClassName"] =
+ (atomicField as PropertyGet).property
+ } else { // top-level delegated fields
+ val delegateFieldName = delegateAssignment.left.toSource()
+ delegateToOriginalAtomicField[delegateFieldName] = atomicField as Name
}
}
}
@@ -303,22 +309,74 @@ class AtomicFUTransformerJS(
inner class DelegatedPropertyAccessorsVisitor : NodeVisitor {
override fun visit(node: AstNode?): Boolean {
- if (node is PropertyGet) {
- if (node.target is PropertyGet) {
- if ((node.target as PropertyGet).property.toSource() in fieldDelegates && node.property.toSource() == MANGLED_VALUE_PROP) {
- if (node.parent is ReturnStatement) {
- val getter = ((((node.parent.parent as? Block)?.parent as? FunctionNode)?.parent as? ObjectProperty)?.parent as? ObjectLiteral)
- ?: abort("Incorrect tree structure of the accessor for the property delegated " +
- "to the atomic field ${fieldDelegates[node.target.toSource()]}")
- val definePropertyCall = getter.parent as FunctionCall
- val stringLiteral = definePropertyCall.arguments[1] as? StringLiteral
- ?: abort ("Object.defineProperty invocation should take a property name as the second argument")
- val delegatedProperty = stringLiteral.value.toString()
- delegatedProperties[delegatedProperty] = (node.target as PropertyGet).property.toSource()
+ // find ObjectLiteral with accessors of the delegated field (get: FunctionNode, set: FunctionNode)
+ // redirect getter/setter from generated delegate field to the original atomic field
+ if (node is ObjectLiteral && node.parent is FunctionCall &&
+ ((node.elements.size == 2 && node.elements[1].left.toSource() == "get") ||
+ (node.elements.size == 3 && node.elements[1].left.toSource() == "get" && node.elements[2].left.toSource() == "set"))) {
+ // check that these are accessors of the atomic delegate field (check only getter)
+ if (node.elements[1].right is FunctionNode) {
+ val getter = node.elements[1].right as FunctionNode
+ if (getter.body.hasChildren() && getter.body.firstChild is ReturnStatement) {
+ val returnStmt = getter.body.firstChild as ReturnStatement
+ if (returnStmt.returnValue is PropertyGet && (returnStmt.returnValue as PropertyGet).property.toSource() == MANGLED_VALUE_PROP) {
+ val delegateField = ((returnStmt.returnValue as PropertyGet).target as PropertyGet).property.toSource()
+ val ownerClassName = ((node.parent as FunctionCall).arguments[0] as PropertyGet).target.toSource()
+ val key = "$delegateField\$$ownerClassName"
+ delegateToOriginalAtomicField[key]?.let { atomicField ->
+ // get() = a$delegate.value -> _a.value
+ getter.replaceAccessedField(true, atomicField)
+ if (node.elements.size == 3) {
+ // set(v: T) { a$delegate.value = v } -> { _a.value = v }
+ val setter = node.elements[2].right as FunctionNode
+ setter.replaceAccessedField(false, atomicField)
+ }
+ }
+ }
+ }
+ }
+ }
+ if (node is ObjectLiteral && node.parent is FunctionCall && ((node.elements.size == 1 && node.elements[0].left.toSource() == "get") ||
+ node.elements.size == 2 && node.elements[0].left.toSource() == "get" && node.elements[1].left.toSource() == "set")) {
+ val parent = node.parent as FunctionCall
+ if (parent.arguments.size == 3 && parent.arguments[1] is StringLiteral) {
+ val topLevelDelegatedFieldName = (parent.arguments[1] as StringLiteral).value
+ if (topLevelDelegatedFieldName in delegateToOriginalAtomicField) {
+ val originalAtomicFieldName = delegateToOriginalAtomicField[topLevelDelegatedFieldName]!!
+ val getterName = node.elements[0].right.toSource()
+ topLevelDelegatedFieldAccessorToOriginalField[getterName] = originalAtomicFieldName
+ if (node.elements.size == 2) {
+ val setterName = node.elements[1].right.toSource()
+ topLevelDelegatedFieldAccessorToOriginalField[setterName] = originalAtomicFieldName
}
}
}
+ }
+ return true
+ }
+ }
+ private fun FunctionNode.replaceAccessedField(isGetter: Boolean, newField: Name) {
+ val propertyGet = if (isGetter) {
+ (body.firstChild as ReturnStatement).returnValue as PropertyGet
+ } else {
+ ((body.firstChild as ExpressionStatement).expression as Assignment).left as PropertyGet
+ }
+ if (propertyGet.target is PropertyGet) { // class member
+ (propertyGet.target as PropertyGet).property = newField
+ } else { // top-level field
+ propertyGet.target = newField
+ }
+ }
+
+ inner class TopLevelDelegatedFieldsAccessorVisitor : NodeVisitor {
+ override fun visit(node: AstNode?): Boolean {
+ if (node is FunctionNode && node.name.toString() in topLevelDelegatedFieldAccessorToOriginalField) {
+ val accessorName = node.name.toString()
+ val atomicField = topLevelDelegatedFieldAccessorToOriginalField[accessorName]!!
+ // function get_topLevelDelegatedField() = a.value -> _a.value
+ // function set_topLevelDelegatedField(v: T) { a.value = v } -> { _a.value = v }
+ node.replaceAccessedField(accessorName.startsWith("get"), atomicField)
}
return true
}
@@ -388,12 +446,6 @@ class AtomicFUTransformerJS(
rr.receiver?.let { node.target = it }
}
}
- if (node.property.toSource() in delegatedProperties) {
- // replace delegated property name with the name of the original atomic field
- val fieldDelegate = delegatedProperties[node.property.toSource()]
- val originalField = fieldDelegates[fieldDelegate]!!
- node.property = Name().apply { identifier = originalField }
- }
// replace Atomic*Array.size call with `length` property on the pure type js array
if (node.property.toSource() == ARRAY_SIZE) {
node.property = Name().also { it.identifier = LENGTH }
diff --git a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/MetadataTransformer.kt b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/MetadataTransformer.kt
index f8bcaf1..13c5366 100644
--- a/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/MetadataTransformer.kt
+++ b/atomicfu-transformer/src/main/kotlin/kotlinx/atomicfu/transformer/MetadataTransformer.kt
@@ -26,7 +26,6 @@ class MetadataTransformer(
val hdr = KotlinClassHeader(
kind = map["k"] as Int?,
metadataVersion = (map["mv"] as? List<Int>)?.toIntArray(),
- bytecodeVersion = (map["bv"] as? List<Int>)?.toIntArray(),
data1 = (map["d1"] as? List<String>)?.toTypedArray(),
data2 = (map["d2"] as? List<String>)?.toTypedArray(),
extraString = map["xs"] as String?,
@@ -229,4 +228,4 @@ private fun AnnotationNode.setKey(key: String, value: Any?) {
}
}
error("Annotation key '$key' is not found")
-} \ No newline at end of file
+}
diff --git a/atomicfu/build.gradle b/atomicfu/build.gradle
index 66cfcb8..063c9a3 100644
--- a/atomicfu/build.gradle
+++ b/atomicfu/build.gradle
@@ -18,12 +18,15 @@ ext {
}
kotlin {
- targets.metaClass.addTarget = { preset ->
- addNative(delegate.fromPreset(preset, preset.name))
+ targets {
+ delegate.metaClass.addTarget = { preset ->
+ addNative(delegate.fromPreset(preset, preset.name))
+ }
}
// JS -- always
js {
+ moduleName = "kotlinx-atomicfu"
// TODO: Commented out because browser tests do not work on TeamCity
// browser()
nodejs()
@@ -89,6 +92,11 @@ if (rootProject.ext.native_targets_enabled) {
addTarget(presets.watchosArm64)
addTarget(presets.watchosX86)
addTarget(presets.watchosX64)
+
+ addTarget(presets.iosSimulatorArm64)
+ addTarget(presets.watchosSimulatorArm64)
+ addTarget(presets.tvosSimulatorArm64)
+ addTarget(presets.macosArm64)
}
}
@@ -159,14 +167,6 @@ tasks.withType(compileJsLegacy.getClass()) {
}
}
-compileJsLegacy.configure {
- kotlinOptions {
- // NOTE: Module base-name must be equal to the package name declared in package.json
- def baseName = "kotlinx-atomicfu"
- outputFile = new File(outputFile.parent, baseName + ".js")
- }
-}
-
apply from: file("$rootProject.projectDir/gradle/node-js.gradle")
apply from: file("$rootProject.projectDir/gradle/publish-npm-js.gradle")
@@ -188,7 +188,7 @@ compileTestJsLegacy.configure {
kotlinOptions {
// NOTE: Module base-name must be equal to the package name declared in package.json
def baseName = "kotlinx-atomicfu"
- outputFile = new File(outputFile.parent, baseName + ".js")
+ outputFile = new File(new File(outputFile).parent, baseName + ".js")
}
}
def originalJsFile = compileTestJsLegacy.kotlinOptions.outputFile
@@ -203,8 +203,14 @@ task transformJS(type: JavaExec, dependsOn: [compileTestJsLegacy]) {
if (project.tasks.findByName('jsLegacyNodeTest')) {
jsLegacyNodeTest.dependsOn transformJS
+ jsLegacyNodeTest.configure {
+ inputFileProperty.set(new File(transformedJsFile))
+ }
} else {
jsNodeTest.dependsOn transformJS
+ jsNodeTest.configure {
+ inputFileProperty.set(new File(transformedJsFile))
+ }
}
// ==== CONFIGURE JVM =====
@@ -216,7 +222,7 @@ def classesPostTransformBOTH = file("$buildDir/classes/kotlin/jvm/postTransforme
tasks.withType(compileTestKotlinJvm.getClass()) {
kotlinOptions {
- jvmTarget = "1.6"
+ jvmTarget = "1.8"
}
}
@@ -244,24 +250,6 @@ task transformVH(type: JavaExec, dependsOn: compileTestKotlinJvm) {
outputs.dir(classesPostTransformVH)
}
-task checkJdk16() {
- doLast {
- if (!System.env.JDK_16) {
- throw new GradleException("JDK_16 environment variable is not defined. " +
- "Can't build against JDK 1.6 runtime and run JDK 1.6 compatibility tests. " +
- "Please ensure JDK 1.6 is installed and that JDK_16 points to it.")
- }
- }
-}
-
-task transformedTestFU_6(type: Test, dependsOn: [checkJdk16, transformFU]) {
- executable = "$System.env.JDK_16/bin/java"
- classpath = configurations.jvmTestRuntimeClasspath + project.files(classesPostTransformFU)
- testClassesDirs = project.files(classesPostTransformFU)
- exclude '**/*LFTest.*', '**/TraceToStringTest.*', '**/AtomicfuReferenceJsTest.*'
- filter { setFailOnNoMatchingTests(false) } // to run excluded tests in Idea
-}
-
task transformedTestFU_current(type: Test, dependsOn: transformFU) {
classpath = files(configurations.jvmTestRuntimeClasspath, classesPostTransformFU)
testClassesDirs = project.files(classesPostTransformFU)
@@ -269,14 +257,6 @@ task transformedTestFU_current(type: Test, dependsOn: transformFU) {
filter { setFailOnNoMatchingTests(false) }
}
-task transformedTestBOTH_6(type: Test, dependsOn: [checkJdk16, transformBOTH]) {
- executable = "$System.env.JDK_16/bin/java"
- classpath = files(configurations.jvmTestRuntimeClasspath, classesPostTransformBOTH)
- testClassesDirs = project.files(classesPostTransformBOTH)
- exclude '**/*LFTest.*', '**/TraceToStringTest.*', '**/TopLevelGeneratedDeclarationsReflectionTest.*', '**/SyntheticFUFieldsTest.*', '**/AtomicfuReferenceJsTest.*'
- filter { setFailOnNoMatchingTests(false) }
-}
-
task transformedTestBOTH_current(type: Test, dependsOn: transformBOTH) {
classpath = files(configurations.jvmTestRuntimeClasspath, classesPostTransformBOTH)
testClassesDirs = project.files(classesPostTransformBOTH)
@@ -305,9 +285,7 @@ task testAtomicfuReferenceJs(type: Test, dependsOn: [compileTestKotlinJvm, trans
}
task jvmTestAll(dependsOn: [
- transformedTestFU_6,
transformedTestFU_current,
- transformedTestBOTH_6,
transformedTestBOTH_current,
transformedTestVH,
testAtomicfuReferenceJs])
@@ -319,6 +297,72 @@ tasks.withType(Test) {
}
}
+task compileJavaModuleInfo(type: JavaCompile) {
+ def moduleName = "kotlinx.atomicfu" // this module's name
+ def compileKotlinJvm = kotlin.targets["jvm"].compilations["main"].compileKotlinTask
+ def sourceDir = file("src/jvmMain/java9/")
+ def targetDir = compileKotlinJvm.destinationDirectory.map { it.dir("../java9/") }
+
+ // Use a Java 11 compiler for the module info.
+ javaCompiler.set(project.javaToolchains.compilerFor { languageVersion.set(JavaLanguageVersion.of(11)) })
+
+ // Always compile kotlin classes before the module descriptor.
+ dependsOn(compileKotlinJvm)
+
+ // Add the module-info source file.
+ source(sourceDir)
+
+ // Also add the module-info.java source file to the Kotlin compile task.
+ // The Kotlin compiler will parse and check module dependencies,
+ // but it currently won't compile to a module-info.class file.
+ // Note that module checking only works on JDK 9+,
+ // because the JDK built-in base modules are not available in earlier versions.
+ def javaVersion = compileKotlinJvm.kotlinJavaToolchain.javaVersion.getOrNull()
+ if (javaVersion?.isJava9Compatible() == true) {
+ logger.info("Module-info checking is enabled; $compileKotlinJvm is compiled using Java $javaVersion")
+ compileKotlinJvm.source(sourceDir)
+ } else {
+ logger.info("Module-info checking is disabled")
+ }
+
+ // Set the task outputs and destination dir
+ outputs.dir(targetDir)
+ destinationDirectory.set(targetDir)
+
+ // Configure JVM compatibility
+ sourceCompatibility = JavaVersion.VERSION_1_9.toString()
+ targetCompatibility = JavaVersion.VERSION_1_9.toString()
+
+ // Set the Java release version.
+ options.release.set(9)
+
+ // Ignore warnings about using 'requires transitive' on automatic modules.
+ // not needed when compiling with recent JDKs, e.g. 17
+ options.compilerArgs.add("-Xlint:-requires-transitive-automatic")
+
+ // Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly.
+ options.compilerArgs.addAll(["--patch-module", "$moduleName=${compileKotlinJvm.destinationDirectory.get().getAsFile()}"])
+
+ // Use the classpath of the compileKotlinJvm task.
+ // Also ensure that the module path is used instead of classpath.
+ classpath = compileKotlinJvm.classpath
+ modularity.inferModulePath.set(true)
+
+ doFirst {
+ logger.warn("Task destination directory: ${destinationDirectory.get().getAsFile()}")
+ }
+}
+
+// Configure the JAR task so that it will include the compiled module-info class file.
+tasks.named("jvmJar") {
+ manifest {
+ attributes(["Multi-Release": true])
+ }
+ from(compileJavaModuleInfo) {
+ into("META-INF/versions/9/")
+ }
+}
+
jvmTest {
exclude "**/AtomicfuBytecodeTest*", "**/AtomicfuReferenceJsTest*", '**/TopLevelGeneratedDeclarationsReflectionTest.*', '**/SyntheticFUFieldsTest.*' // run them only for transformed code
}
@@ -337,3 +381,4 @@ afterEvaluate {
tasks.matching { it.name == "generatePomFileForKotlinMultiplatformPublication" }.configureEach {
dependsOn(tasks["generatePomFileForJvmPublication"])
}
+
diff --git a/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt b/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt
index 1b24e6d..4521c09 100644
--- a/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt
+++ b/atomicfu/src/commonTest/kotlin/kotlinx/atomicfu/test/DelegatedPropertiesTest.kt
@@ -5,8 +5,27 @@ package kotlinx.atomicfu.test
import kotlinx.atomicfu.atomic
import kotlin.test.*
-class DelegatedProperties {
+private val topLevelIntOriginalAtomic = atomic(77)
+var topLevelIntDelegatedProperty: Int by topLevelIntOriginalAtomic
+
+private val _topLevelLong = atomic(55555555555)
+var topLevelDelegatedPropertyLong: Long by _topLevelLong
+
+private val _topLevelBoolean = atomic(false)
+var topLevelDelegatedPropertyBoolean: Boolean by _topLevelBoolean
+
+private val _topLevelRef = atomic(listOf("a", "b"))
+var topLevelDelegatedPropertyRef: List<String> by _topLevelRef
+
+var vTopLevelInt by atomic(77)
+
+var vTopLevelLong by atomic(777777777)
+var vTopLevelBoolean by atomic(false)
+
+var vTopLevelRef by atomic(listOf("a", "b"))
+
+class DelegatedProperties {
private val _a = atomic(42)
var a: Int by _a
@@ -99,6 +118,76 @@ class DelegatedProperties {
assertEquals(99, vRef.b.n)
}
+ @Test
+ fun testTopLevelDelegatedPropertiesInt() {
+ assertEquals(77, topLevelIntDelegatedProperty)
+ topLevelIntOriginalAtomic.compareAndSet(77, 56)
+ assertEquals(56, topLevelIntDelegatedProperty)
+ topLevelIntDelegatedProperty = 88
+ topLevelIntOriginalAtomic.compareAndSet(88, 66)
+ assertEquals(66, topLevelIntOriginalAtomic.value)
+ assertEquals(66, topLevelIntDelegatedProperty)
+ }
+
+ @Test
+ fun testTopLevelDelegatedPropertiesLong() {
+ assertEquals(55555555555, topLevelDelegatedPropertyLong)
+ _topLevelLong.getAndIncrement()
+ assertEquals(55555555556, topLevelDelegatedPropertyLong)
+ topLevelDelegatedPropertyLong = 7777777777777
+ assertTrue(_topLevelLong.compareAndSet(7777777777777, 66666666666))
+ assertEquals(66666666666, _topLevelLong.value)
+ assertEquals(66666666666, topLevelDelegatedPropertyLong)
+ }
+
+ @Test
+ fun testTopLevelDelegatedPropertiesBoolean() {
+ assertEquals(false, topLevelDelegatedPropertyBoolean)
+ _topLevelBoolean.lazySet(true)
+ assertEquals(true, topLevelDelegatedPropertyBoolean)
+ topLevelDelegatedPropertyBoolean = false
+ assertTrue(_topLevelBoolean.compareAndSet(false, true))
+ assertEquals(true, _topLevelBoolean.value)
+ assertEquals(true, topLevelDelegatedPropertyBoolean)
+ }
+
+ @Test
+ fun testTopLevelDelegatedPropertiesRef() {
+ assertEquals("b", topLevelDelegatedPropertyRef[1])
+ _topLevelRef.lazySet(listOf("c"))
+ assertEquals("c", topLevelDelegatedPropertyRef[0])
+ topLevelDelegatedPropertyRef = listOf("d", "e")
+ assertEquals("e", _topLevelRef.value[1])
+ }
+
+ @Test
+ fun testVolatileTopLevelInt() {
+ assertEquals(77, vTopLevelInt)
+ vTopLevelInt = 55
+ assertEquals(110, vTopLevelInt * 2)
+ }
+
+ @Test
+ fun testVolatileTopLevelLong() {
+ assertEquals(777777777, vTopLevelLong)
+ vTopLevelLong = 55
+ assertEquals(55, vTopLevelLong)
+ }
+
+ @Test
+ fun testVolatileTopLevelBoolean() {
+ assertEquals(false, vTopLevelBoolean)
+ vTopLevelBoolean = true
+ assertEquals(true, vTopLevelBoolean)
+ }
+
+ @Test
+ fun testVolatileTopLevelRef() {
+ assertEquals("a", vTopLevelRef[0])
+ vTopLevelRef = listOf("c")
+ assertEquals("c", vTopLevelRef[0])
+ }
+
class A (val b: B)
class B (val n: Int)
}
@@ -144,4 +233,25 @@ class ExposedDelegatedPropertiesAccessorsTest {
cl.vInt = 99
assertEquals(99, cl.vInt)
}
+}
+
+class ClashedNamesTest {
+ private class A1 {
+ val _a = atomic(0)
+ val a: Int by _a
+ }
+
+ private class A2 {
+ val _a = atomic(0)
+ val a: Int by _a
+ }
+
+ @Test
+ fun testClashedDelegatedPropertiesNames() {
+ val a1Class = A1()
+ val a2Class = A2()
+ a1Class._a.compareAndSet(0, 77)
+ assertEquals(77, a1Class.a)
+ assertEquals(0, a2Class.a)
+ }
} \ No newline at end of file
diff --git a/atomicfu/src/jvmMain/java9/module-info.java b/atomicfu/src/jvmMain/java9/module-info.java
new file mode 100644
index 0000000..e68d750
--- /dev/null
+++ b/atomicfu/src/jvmMain/java9/module-info.java
@@ -0,0 +1,6 @@
+module kotlinx.atomicfu {
+ requires transitive kotlin.stdlib;
+
+ exports kotlinx.atomicfu;
+ exports kotlinx.atomicfu.locks;
+}
diff --git a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/AtomicFU.kt b/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
index 20dc5f2..ddaf1dc 100644
--- a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
+++ b/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
@@ -80,10 +80,8 @@ public actual class AtomicRef<T> internal constructor(value: T, val trace: Trace
@Volatile
public actual var value: T = value
set(value) {
- interceptor.beforeUpdate(this)
field = value
- if (trace !== TraceBase.None) trace { "set($value)" }
- interceptor.afterSet(this, value)
+ if (trace !== None) trace { "set($value)" }
}
public actual inline operator fun getValue(thisRef: Any?, property: KProperty<*>): T = value
@@ -94,22 +92,16 @@ public actual class AtomicRef<T> internal constructor(value: T, val trace: Trace
* Maps to [AtomicReferenceFieldUpdater.lazySet].
*/
public actual fun lazySet(value: T) {
- interceptor.beforeUpdate(this)
FU.lazySet(this, value)
- if (trace !== TraceBase.None) trace { "lazySet($value)" }
- interceptor.afterSet(this, value)
+ if (trace !== None) trace { "lazySet($value)" }
}
/**
* Maps to [AtomicReferenceFieldUpdater.compareAndSet].
*/
public actual fun compareAndSet(expect: T, update: T): Boolean {
- interceptor.beforeUpdate(this)
val result = FU.compareAndSet(this, expect, update)
- if (result) {
- if (trace !== TraceBase.None) trace { "CAS($expect, $update)" }
- interceptor.afterRMW(this, expect, update)
- }
+ if (result && trace !== None) trace { "CAS($expect, $update)" }
return result
}
@@ -117,10 +109,8 @@ public actual class AtomicRef<T> internal constructor(value: T, val trace: Trace
* Maps to [AtomicReferenceFieldUpdater.getAndSet].
*/
public actual fun getAndSet(value: T): T {
- interceptor.beforeUpdate(this)
val oldValue = FU.getAndSet(this, value) as T
- if (trace !== TraceBase.None) trace { "getAndSet($value):$oldValue" }
- interceptor.afterRMW(this, oldValue, value)
+ if (trace !== None) trace { "getAndSet($value):$oldValue" }
return oldValue
}
@@ -155,35 +145,27 @@ public actual class AtomicBoolean internal constructor(v: Boolean, val trace: Tr
public actual var value: Boolean
get() = _value != 0
set(value) {
- interceptor.beforeUpdate(this)
_value = if (value) 1 else 0
- if (trace !== TraceBase.None) trace { "set($value)" }
- interceptor.afterSet(this, value)
+ if (trace !== None) trace { "set($value)" }
}
/**
* Maps to [AtomicIntegerFieldUpdater.lazySet].
*/
public actual fun lazySet(value: Boolean) {
- interceptor.beforeUpdate(this)
val v = if (value) 1 else 0
FU.lazySet(this, v)
- if (trace !== TraceBase.None) trace { "lazySet($value)" }
- interceptor.afterSet(this, value)
+ if (trace !== None) trace { "lazySet($value)" }
}
/**
* Maps to [AtomicIntegerFieldUpdater.compareAndSet].
*/
public actual fun compareAndSet(expect: Boolean, update: Boolean): Boolean {
- interceptor.beforeUpdate(this)
val e = if (expect) 1 else 0
val u = if (update) 1 else 0
val result = FU.compareAndSet(this, e, u)
- if (result) {
- if (trace !== TraceBase.None) trace { "CAS($expect, $update)" }
- interceptor.afterRMW(this, expect, update)
- }
+ if (result && trace !== None) trace { "CAS($expect, $update)" }
return result
}
@@ -191,11 +173,9 @@ public actual class AtomicBoolean internal constructor(v: Boolean, val trace: Tr
* Maps to [AtomicIntegerFieldUpdater.getAndSet].
*/
public actual fun getAndSet(value: Boolean): Boolean {
- interceptor.beforeUpdate(this)
val v = if (value) 1 else 0
val oldValue = FU.getAndSet(this, v)
- if (trace !== TraceBase.None) trace { "getAndSet($value):$oldValue" }
- interceptor.afterRMW(this, (oldValue == 1), value)
+ if (trace !== None) trace { "getAndSet($value):$oldValue" }
return oldValue == 1
}
@@ -220,10 +200,8 @@ public actual class AtomicInt internal constructor(value: Int, val trace: TraceB
@Volatile
public actual var value: Int = value
set(value) {
- interceptor.beforeUpdate(this)
field = value
- if (trace !== TraceBase.None) trace { "set($value)" }
- interceptor.afterSet(this, value)
+ if (trace !== None) trace { "set($value)" }
}
public actual inline operator fun getValue(thisRef: Any?, property: KProperty<*>): Int = value
@@ -234,22 +212,16 @@ public actual class AtomicInt internal constructor(value: Int, val trace: TraceB
* Maps to [AtomicIntegerFieldUpdater.lazySet].
*/
public actual fun lazySet(value: Int) {
- interceptor.beforeUpdate(this)
FU.lazySet(this, value)
- if (trace !== TraceBase.None) trace { "lazySet($value)" }
- interceptor.afterSet(this, value)
+ if (trace !== None) trace { "lazySet($value)" }
}
/**
* Maps to [AtomicIntegerFieldUpdater.compareAndSet].
*/
public actual fun compareAndSet(expect: Int, update: Int): Boolean {
- interceptor.beforeUpdate(this)
val result = FU.compareAndSet(this, expect, update)
- if (result) {
- if (trace !== TraceBase.None) trace { "CAS($expect, $update)" }
- interceptor.afterRMW(this, expect, update)
- }
+ if (result && trace !== None) trace { "CAS($expect, $update)" }
return result
}
@@ -257,10 +229,8 @@ public actual class AtomicInt internal constructor(value: Int, val trace: TraceB
* Maps to [AtomicIntegerFieldUpdater.getAndSet].
*/
public actual fun getAndSet(value: Int): Int {
- interceptor.beforeUpdate(this)
val oldValue = FU.getAndSet(this, value)
- if (trace !== TraceBase.None) trace { "getAndSet($value):$oldValue" }
- interceptor.afterRMW(this, oldValue, value)
+ if (trace !== None) trace { "getAndSet($value):$oldValue" }
return oldValue
}
@@ -268,10 +238,8 @@ public actual class AtomicInt internal constructor(value: Int, val trace: TraceB
* Maps to [AtomicIntegerFieldUpdater.getAndIncrement].
*/
public actual fun getAndIncrement(): Int {
- interceptor.beforeUpdate(this)
val oldValue = FU.getAndIncrement(this)
- if (trace !== TraceBase.None) trace { "getAndInc():$oldValue" }
- interceptor.afterRMW(this, oldValue, oldValue + 1)
+ if (trace !== None) trace { "getAndInc():$oldValue" }
return oldValue
}
@@ -279,10 +247,8 @@ public actual class AtomicInt internal constructor(value: Int, val trace: TraceB
* Maps to [AtomicIntegerFieldUpdater.getAndDecrement].
*/
public actual fun getAndDecrement(): Int {
- interceptor.beforeUpdate(this)
val oldValue = FU.getAndDecrement(this)
- if (trace !== TraceBase.None) trace { "getAndDec():$oldValue" }
- interceptor.afterRMW(this, oldValue, oldValue - 1)
+ if (trace !== None) trace { "getAndDec():$oldValue" }
return oldValue
}
@@ -290,10 +256,8 @@ public actual class AtomicInt internal constructor(value: Int, val trace: TraceB
* Maps to [AtomicIntegerFieldUpdater.getAndAdd].
*/
public actual fun getAndAdd(delta: Int): Int {
- interceptor.beforeUpdate(this)
val oldValue = FU.getAndAdd(this, delta)
- if (trace !== TraceBase.None) trace { "getAndAdd($delta):$oldValue" }
- interceptor.afterRMW(this, oldValue, oldValue + delta)
+ if (trace !== None) trace { "getAndAdd($delta):$oldValue" }
return oldValue
}
@@ -301,10 +265,8 @@ public actual class AtomicInt internal constructor(value: Int, val trace: TraceB
* Maps to [AtomicIntegerFieldUpdater.addAndGet].
*/
public actual fun addAndGet(delta: Int): Int {
- interceptor.beforeUpdate(this)
val newValue = FU.addAndGet(this, delta)
- if (trace !== TraceBase.None) trace { "addAndGet($delta):$newValue" }
- interceptor.afterRMW(this, newValue - delta, newValue)
+ if (trace !== None) trace { "addAndGet($delta):$newValue" }
return newValue
}
@@ -312,10 +274,8 @@ public actual class AtomicInt internal constructor(value: Int, val trace: TraceB
* Maps to [AtomicIntegerFieldUpdater.incrementAndGet].
*/
public actual fun incrementAndGet(): Int {
- interceptor.beforeUpdate(this)
val newValue = FU.incrementAndGet(this)
- if (trace !== TraceBase.None) trace { "incAndGet():$newValue" }
- interceptor.afterRMW(this, newValue - 1, newValue)
+ if (trace !== None) trace { "incAndGet():$newValue" }
return newValue
}
@@ -323,10 +283,8 @@ public actual class AtomicInt internal constructor(value: Int, val trace: TraceB
* Maps to [AtomicIntegerFieldUpdater.decrementAndGet].
*/
public actual fun decrementAndGet(): Int {
- interceptor.beforeUpdate(this)
val newValue = FU.decrementAndGet(this)
- if (trace !== TraceBase.None) trace { "decAndGet():$newValue" }
- interceptor.afterRMW(this, newValue + 1, newValue)
+ if (trace !== None) trace { "decAndGet():$newValue" }
return newValue
}
@@ -365,10 +323,8 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
@Volatile
public actual var value: Long = value
set(value) {
- interceptor.beforeUpdate(this)
field = value
- if (trace !== TraceBase.None) trace { "set($value)" }
- interceptor.afterSet(this, value)
+ if (trace !== None) trace { "set($value)" }
}
public actual inline operator fun getValue(thisRef: Any?, property: KProperty<*>): Long = value
@@ -379,22 +335,16 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
* Maps to [AtomicLongFieldUpdater.lazySet].
*/
public actual fun lazySet(value: Long) {
- interceptor.beforeUpdate(this)
FU.lazySet(this, value)
- if (trace !== TraceBase.None) trace { "lazySet($value)" }
- interceptor.afterSet(this, value)
+ if (trace !== None) trace { "lazySet($value)" }
}
/**
* Maps to [AtomicLongFieldUpdater.compareAndSet].
*/
public actual fun compareAndSet(expect: Long, update: Long): Boolean {
- interceptor.beforeUpdate(this)
val result = FU.compareAndSet(this, expect, update)
- if (result) {
- if (trace !== TraceBase.None) trace { "CAS($expect, $update)" }
- interceptor.afterRMW(this, expect, update)
- }
+ if (result && trace !== None) trace { "CAS($expect, $update)" }
return result
}
@@ -402,10 +352,8 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
* Maps to [AtomicLongFieldUpdater.getAndSet].
*/
public actual fun getAndSet(value: Long): Long {
- interceptor.beforeUpdate(this)
val oldValue = FU.getAndSet(this, value)
- if (trace !== TraceBase.None) trace { "getAndSet($value):$oldValue" }
- interceptor.afterRMW(this, oldValue, value)
+ if (trace !== None) trace { "getAndSet($value):$oldValue" }
return oldValue
}
@@ -413,10 +361,8 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
* Maps to [AtomicLongFieldUpdater.getAndIncrement].
*/
public actual fun getAndIncrement(): Long {
- interceptor.beforeUpdate(this)
val oldValue = FU.getAndIncrement(this)
- if (trace !== TraceBase.None) trace { "getAndInc():$oldValue" }
- interceptor.afterRMW(this, oldValue, oldValue + 1)
+ if (trace !== None) trace { "getAndInc():$oldValue" }
return oldValue
}
@@ -424,10 +370,8 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
* Maps to [AtomicLongFieldUpdater.getAndDecrement].
*/
public actual fun getAndDecrement(): Long {
- interceptor.beforeUpdate(this)
val oldValue = FU.getAndDecrement(this)
- if (trace !== TraceBase.None) trace { "getAndDec():$oldValue" }
- interceptor.afterRMW(this, oldValue, oldValue - 1)
+ if (trace !== None) trace { "getAndDec():$oldValue" }
return oldValue
}
@@ -435,10 +379,8 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
* Maps to [AtomicLongFieldUpdater.getAndAdd].
*/
public actual fun getAndAdd(delta: Long): Long {
- interceptor.beforeUpdate(this)
val oldValue = FU.getAndAdd(this, delta)
- if (trace !== TraceBase.None) trace { "getAndAdd($delta):$oldValue" }
- interceptor.afterRMW(this, oldValue, oldValue + delta)
+ if (trace !== None) trace { "getAndAdd($delta):$oldValue" }
return oldValue
}
@@ -446,10 +388,8 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
* Maps to [AtomicLongFieldUpdater.addAndGet].
*/
public actual fun addAndGet(delta: Long): Long {
- interceptor.beforeUpdate(this)
val newValue = FU.addAndGet(this, delta)
- if (trace !== TraceBase.None) trace { "addAndGet($delta):$newValue" }
- interceptor.afterRMW(this, newValue - delta, newValue)
+ if (trace !== None) trace { "addAndGet($delta):$newValue" }
return newValue
}
@@ -457,10 +397,8 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
* Maps to [AtomicLongFieldUpdater.incrementAndGet].
*/
public actual fun incrementAndGet(): Long {
- interceptor.beforeUpdate(this)
val newValue = FU.incrementAndGet(this)
- if (trace !== TraceBase.None) trace { "incAndGet():$newValue" }
- interceptor.afterRMW(this, newValue - 1, newValue)
+ if (trace !== None) trace { "incAndGet():$newValue" }
return newValue
}
@@ -468,10 +406,8 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
* Maps to [AtomicLongFieldUpdater.decrementAndGet].
*/
public actual fun decrementAndGet(): Long {
- interceptor.beforeUpdate(this)
val newValue = FU.decrementAndGet(this)
- if (trace !== TraceBase.None) trace { "decAndGet():$newValue" }
- interceptor.afterRMW(this, newValue + 1, newValue)
+ if (trace !== None) trace { "decAndGet():$newValue" }
return newValue
}
@@ -494,4 +430,4 @@ public actual class AtomicLong internal constructor(value: Long, val trace: Trac
private companion object {
private val FU = AtomicLongFieldUpdater.newUpdater(AtomicLong::class.java, "value")
}
-} \ No newline at end of file
+}
diff --git a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/Interceptor.kt b/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/Interceptor.kt
deleted file mode 100644
index 28d989b..0000000
--- a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/Interceptor.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu
-
-import java.util.concurrent.locks.ReentrantLock
-
-internal var interceptor: AtomicOperationInterceptor = DefaultInterceptor
- private set
-private val interceptorLock = ReentrantLock()
-
-internal fun lockAndSetInterceptor(impl: AtomicOperationInterceptor) {
- if (!interceptorLock.tryLock() || interceptor !== DefaultInterceptor) {
- error("Interceptor is locked by another test: $interceptor")
- }
- interceptor = impl
-}
-
-internal fun unlockAndResetInterceptor(impl: AtomicOperationInterceptor) {
- check(interceptor === impl) { "Unexpected interceptor found: $interceptor" }
- interceptor = DefaultInterceptor
- interceptorLock.unlock()
-}
-
-/**
- * Interceptor for modifications of atomic variables.
- */
-internal open class AtomicOperationInterceptor {
- open fun <T> beforeUpdate(ref: AtomicRef<T>) {}
- open fun beforeUpdate(ref: AtomicInt) {}
- open fun beforeUpdate(ref: AtomicLong) {}
- open fun beforeUpdate(ref: AtomicBoolean){}
- open fun <T> afterSet(ref: AtomicRef<T>, newValue: T) {}
- open fun afterSet(ref: AtomicInt, newValue: Int) {}
- open fun afterSet(ref: AtomicLong, newValue: Long) {}
- open fun afterSet(ref: AtomicBoolean, newValue: Boolean) {}
- open fun <T> afterRMW(ref: AtomicRef<T>, oldValue: T, newValue: T) {}
- open fun afterRMW(ref: AtomicInt, oldValue: Int, newValue: Int) {}
- open fun afterRMW(ref: AtomicLong, oldValue: Long, newValue: Long) {}
- open fun afterRMW(ref: AtomicBoolean, oldValue: Boolean, newValue: Boolean) {}
-}
-
-private object DefaultInterceptor : AtomicOperationInterceptor() {
- override fun toString(): String = "DefaultInterceptor"
-}
diff --git a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt b/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt
deleted file mode 100644
index 0208feb..0000000
--- a/atomicfu/src/jvmMain/kotlin/kotlinx/atomicfu/LockFreedomTestEnvironment.kt
+++ /dev/null
@@ -1,482 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-@file:Suppress("RedundantVisibilityModifier")
-
-package kotlinx.atomicfu
-
-import java.util.*
-import java.util.concurrent.atomic.*
-import java.util.concurrent.locks.*
-import kotlin.coroutines.*
-import kotlin.coroutines.intrinsics.*
-
-private const val PAUSE_EVERY_N_STEPS = 1000
-private const val STALL_LIMIT_MS = 15_000L // 15s
-private const val SHUTDOWN_CHECK_MS = 10L // 10ms
-
-private const val STATUS_DONE = Int.MAX_VALUE
-
-private const val MAX_PARK_NANOS = 1_000_000L // part for at most 1ms just in case of loosing unpark signal
-
-/**
- * Environment for performing lock-freedom tests for lock-free data structures
- * that are written with [atomic] variables.
- */
-public open class LockFreedomTestEnvironment(
- private val name: String,
- private val allowSuspendedThreads: Int = 0
-) {
- private val interceptor = Interceptor()
- private val threads = mutableListOf<TestThread>()
- private val performedOps = LongAdder()
- private val uncaughtException = AtomicReference<Throwable?>()
- private var started = false
- private var performedResumes = 0
-
- @Volatile
- private var completed = false
- private val onCompletion = mutableListOf<() -> Unit>()
-
- private val ueh = Thread.UncaughtExceptionHandler { t, e ->
- synchronized(System.out) {
- println("Uncaught exception in thread $t")
- e.printStackTrace(System.out)
- uncaughtException.compareAndSet(null, e)
- }
- }
-
- // status < 0 - inv paused thread id
- // status >= 0 - no. of performed resumes so far (==last epoch)
- // status == STATUS_DONE - done working
- private val status = AtomicInteger()
- private val globalPauseProgress = AtomicInteger()
- private val suspendedThreads = ArrayList<TestThread>()
-
- @Volatile
- private var isActive = true
-
- // ---------- API ----------
-
- /**
- * Starts lock-freedom test for a given duration in seconds,
- * invoking [progress] every second (it will be invoked `seconds + 1` times).
- */
- public fun performTest(seconds: Int, progress: () -> Unit = {}) {
- check(isActive) { "Can perform test at most once on this instance" }
- println("=== $name")
- val minThreads = 2 + allowSuspendedThreads
- check(threads.size >= minThreads) { "Must define at least $minThreads test threads" }
- lockAndSetInterceptor(interceptor)
- started = true
- var nextTime = System.currentTimeMillis()
- threads.forEach { thread ->
- thread.setUncaughtExceptionHandler(ueh)
- thread.lastOpTime = nextTime
- thread.start()
- }
- try {
- var second = 0
- while (uncaughtException.get() == null) {
- waitUntil(nextTime)
- println("--- $second: Performed ${performedOps.sum()} operations${resumeStr()}")
- progress()
- checkStalled()
- if (++second > seconds) break
- nextTime += 1000L
- }
- } finally {
- complete()
- }
- println("------ Done with ${performedOps.sum()} operations${resumeStr()}")
- progress()
- }
-
- private fun complete() {
- val activeNonPausedThreads: MutableMap<TestThread, Array<StackTraceElement>> = mutableMapOf()
- val shutdownDeadline = System.currentTimeMillis() + STALL_LIMIT_MS
- try {
- completed = true
- // perform custom completion blocks. For testing of things like channels, these custom completion
- // blocks close all the channels, so that all suspended coroutines shall get resumed.
- onCompletion.forEach { it() }
- // signal shutdown to all threads (non-paused threads will terminate)
- isActive = false
- // wait for threads to terminate
- while (System.currentTimeMillis() < shutdownDeadline) {
- // Check all threads while shutting down:
- // All terminated threads are considered to make progress for the purpose of resuming stalled ones
- activeNonPausedThreads.clear()
- for (t in threads) {
- when {
- !t.isAlive -> t.makeProgress(getPausedEpoch()) // not alive - makes progress
- t.index.inv() == status.get() -> {} // active, paused -- skip
- else -> {
- val stackTrace = t.stackTrace
- if (t.isAlive) activeNonPausedThreads[t] = stackTrace
- }
- }
- }
- if (activeNonPausedThreads.isEmpty()) break
- checkStalled()
- Thread.sleep(SHUTDOWN_CHECK_MS)
- }
- activeNonPausedThreads.forEach { (t, stackTrack) ->
- println("=== $t had failed to shutdown in time")
- stackTrack.forEach { println("\tat $it") }
- }
- } finally {
- shutdown(shutdownDeadline)
- }
- // if no other exception was throws & we had threads that did not shut down -- still fails
- if (activeNonPausedThreads.isNotEmpty()) error("Some threads had failed to shutdown in time")
- }
-
- private fun shutdown(shutdownDeadline: Long) {
- // forcefully unpause paused threads to shut them down (if any left)
- val curStatus = status.getAndSet(STATUS_DONE)
- if (curStatus < 0) LockSupport.unpark(threads[curStatus.inv()])
- threads.forEach {
- val remaining = shutdownDeadline - System.currentTimeMillis()
- if (remaining > 0) it.join(remaining)
- }
- // abort waiting threads (if still any left)
- threads.forEach { it.abortWait() }
- // cleanup & be done
- unlockAndResetInterceptor(interceptor)
- uncaughtException.get()?.let { throw it }
- threads.find { it.isAlive }?.let { dumpThreadsError("A thread is still alive: $it")}
- }
-
- private fun checkStalled() {
- val stallLimit = System.currentTimeMillis() - STALL_LIMIT_MS
- val stalled = threads.filter { it.lastOpTime < stallLimit }
- if (stalled.isNotEmpty()) dumpThreadsError("Progress stalled in threads ${stalled.map { it.name }}")
- }
-
- private fun resumeStr(): String {
- val resumes = performedResumes
- return if (resumes == 0) "" else " (pause/resumes $resumes)"
- }
-
- private fun waitUntil(nextTime: Long) {
- while (true) {
- val curTime = System.currentTimeMillis()
- if (curTime >= nextTime) break
- Thread.sleep(nextTime - curTime)
- }
- }
-
- private fun dumpThreadsError(message: String) : Nothing {
- val traces = threads.associate { it to it.stackTrace }
- println("!!! $message")
- println("=== Dumping live thread stack traces")
- for ((thread, trace) in traces) {
- if (trace.isEmpty()) continue
- println("Thread \"${thread.name}\" ${thread.state}")
- for (t in trace) println("\tat ${t.className}.${t.methodName}(${t.fileName}:${t.lineNumber})")
- println()
- }
- println("===")
- error(message)
- }
-
- /**
- * Returns true when test was completed.
- * Sets to true before calling [onCompletion] blocks.
- */
- public val isCompleted: Boolean get() = completed
-
- /**
- * Performs a given block of code on test's completion
- */
- public fun onCompletion(block: () -> Unit) {
- onCompletion += block
- }
-
- /**
- * Creates a new test thread in this environment that is executes a given lock-free [operation]
- * in a loop while this environment [isActive].
- */
- public fun testThread(name: String? = null, operation: suspend TestThread.() -> Unit): TestThread =
- TestThread(name, operation)
-
- /**
- * Test thread.
- */
- @Suppress("LeakingThis")
- public inner class TestThread internal constructor(
- name: String?,
- private val operation: suspend TestThread.() -> Unit
- ) : Thread(composeThreadName(name)) {
- internal val index: Int
-
- internal @Volatile var lastOpTime = 0L
- internal @Volatile var pausedEpoch = -1
-
- private val random = Random()
-
- // thread-local stuff
- private var operationEpoch = -1
- private var progressEpoch = -1
- private var sink = 0
-
- init {
- check(!started)
- index = threads.size
- threads += this
- }
-
- public override fun run() {
- while (isActive) {
- callOperation()
- }
- }
-
- /**
- * Use it to insert an arbitrary intermission between lock-free operations.
- */
- public inline fun <T> intermission(block: () -> T): T {
- afterLockFreeOperation()
- return try { block() }
- finally { beforeLockFreeOperation() }
- }
-
- @PublishedApi
- internal fun beforeLockFreeOperation() {
- operationEpoch = getPausedEpoch()
- }
-
- @PublishedApi
- internal fun afterLockFreeOperation() {
- makeProgress(operationEpoch)
- lastOpTime = System.currentTimeMillis()
- performedOps.add(1)
- }
-
- internal fun makeProgress(epoch: Int) {
- if (epoch <= progressEpoch) return
- progressEpoch = epoch
- val total = globalPauseProgress.incrementAndGet()
- if (total >= threads.size - 1) {
- check(total == threads.size - 1)
- check(globalPauseProgress.compareAndSet(threads.size - 1, 0))
- resumeImpl()
- }
- }
-
- /**
- * Inserts random spin wait between multiple lock-free operations in [operation].
- */
- public fun randomSpinWaitIntermission() {
- intermission {
- if (random.nextInt(100) < 95) return // be quick, no wait 95% of time
- do {
- val x = random.nextInt(100)
- repeat(x) { sink += it }
- } while (x >= 90)
- }
- }
-
- internal fun stepImpl() {
- if (random.nextInt(PAUSE_EVERY_N_STEPS) == 0) pauseImpl()
- }
-
- internal fun pauseImpl() {
- while (true) {
- val curStatus = status.get()
- if (curStatus < 0 || curStatus == STATUS_DONE) return // some other thread paused or done
- pausedEpoch = curStatus + 1
- val newStatus = index.inv()
- if (status.compareAndSet(curStatus, newStatus)) {
- while (status.get() == newStatus) LockSupport.parkNanos(MAX_PARK_NANOS) // wait
- return
- }
- }
- }
-
- // ----- Lightweight support for suspending operations -----
-
- private fun callOperation() {
- beforeLockFreeOperation()
- beginRunningOperation()
- val result = operation.startCoroutineUninterceptedOrReturn(this, completion)
- when {
- result === Unit -> afterLockFreeOperation() // operation completed w/o suspension -- done
- result === COROUTINE_SUSPENDED -> waitUntilCompletion() // operation had suspended
- else -> error("Unexpected result of operation: $result")
- }
- try {
- doneRunningOperation()
- } catch(e: IllegalStateException) {
- throw IllegalStateException("${e.message}; original start result=$result", e)
- }
- }
-
- private var runningOperation = false
- private var result: Result<Any?>? = null
- private var continuation: Continuation<Any?>? = null
-
- private fun waitUntilCompletion() {
- try {
- while (true) {
- afterLockFreeOperation()
- val result: Result<Any?> = waitForResult()
- val continuation = takeContinuation()
- if (continuation == null) { // done
- check(result.getOrThrow() === Unit)
- return
- }
- removeSuspended(this)
- beforeLockFreeOperation()
- continuation.resumeWith(result)
- }
- } finally {
- removeSuspended(this)
- }
- }
-
- private fun beginRunningOperation() {
- runningOperation = true
- result = null
- continuation = null
- }
-
- @Synchronized
- private fun doneRunningOperation() {
- check(runningOperation) { "Should be running operation" }
- check(result == null && continuation == null) {
- "Callback invoked with result=$result, continuation=$continuation"
- }
- runningOperation = false
- }
-
- @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
- @Synchronized
- private fun resumeWith(result: Result<Any?>, continuation: Continuation<Any?>?) {
- check(runningOperation) { "Should be running operation" }
- check(this.result == null && this.continuation == null) {
- "Resumed again with result=$result, continuation=$continuation, when this: result=${this.result}, continuation=${this.continuation}"
- }
- this.result = result
- this.continuation = continuation
- (this as Object).notifyAll()
- }
-
- @Suppress("RESULT_CLASS_IN_RETURN_TYPE", "PLATFORM_CLASS_MAPPED_TO_KOTLIN")
- @Synchronized
- private fun waitForResult(): Result<Any?> {
- while (true) {
- val result = this.result
- if (result != null) return result
- val index = addSuspended(this)
- if (index < allowSuspendedThreads) {
- // This suspension was permitted, so assume progress is happening while it is suspended
- makeProgress(getPausedEpoch())
- }
- (this as Object).wait(10) // at most 10 ms
- }
- }
-
- @Synchronized
- private fun takeContinuation(): Continuation<Any?>? =
- continuation.also {
- this.result = null
- this.continuation = null
- }
-
- @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
- @Synchronized
- fun abortWait() {
- this.result = Result.failure(IllegalStateException("Aborted at the end of test"))
- (this as Object).notifyAll()
- }
-
- private val interceptor: CoroutineContext = object : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
- override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
- Continuation<T>(this) {
- @Suppress("UNCHECKED_CAST")
- resumeWith(it, continuation as Continuation<Any?>)
- }
- }
-
- private val completion = Continuation<Unit>(interceptor) {
- resumeWith(it, null)
- }
- }
-
- // ---------- Implementation ----------
-
- @Synchronized
- private fun addSuspended(thread: TestThread): Int {
- val index = suspendedThreads.indexOf(thread)
- if (index >= 0) return index
- suspendedThreads.add(thread)
- return suspendedThreads.size - 1
- }
-
- @Synchronized
- private fun removeSuspended(thread: TestThread) {
- suspendedThreads.remove(thread)
- }
-
- private fun getPausedEpoch(): Int {
- while (true) {
- val curStatus = status.get()
- if (curStatus >= 0) return -1 // not paused
- val thread = threads[curStatus.inv()]
- val pausedEpoch = thread.pausedEpoch
- if (curStatus == status.get()) return pausedEpoch
- }
- }
-
- internal fun step() {
- val thread = Thread.currentThread() as? TestThread ?: return
- thread.stepImpl()
- }
-
- private fun resumeImpl() {
- while (true) {
- val curStatus = status.get()
- if (curStatus == STATUS_DONE) return // done
- check(curStatus < 0)
- val thread = threads[curStatus.inv()]
- performedResumes = thread.pausedEpoch
- if (status.compareAndSet(curStatus, thread.pausedEpoch)) {
- LockSupport.unpark(thread)
- return
- }
- }
- }
-
- private fun composeThreadName(threadName: String?): String {
- if (threadName != null) return "$name-$threadName"
- return name + "-${threads.size + 1}"
- }
-
- private inner class Interceptor : AtomicOperationInterceptor() {
- override fun <T> beforeUpdate(ref: AtomicRef<T>) = step()
- override fun beforeUpdate(ref: AtomicInt) = step()
- override fun beforeUpdate(ref: AtomicLong) = step()
- override fun <T> afterSet(ref: AtomicRef<T>, newValue: T) = step()
- override fun afterSet(ref: AtomicInt, newValue: Int) = step()
- override fun afterSet(ref: AtomicLong, newValue: Long) = step()
- override fun <T> afterRMW(ref: AtomicRef<T>, oldValue: T, newValue: T) = step()
- override fun afterRMW(ref: AtomicInt, oldValue: Int, newValue: Int) = step()
- override fun afterRMW(ref: AtomicLong, oldValue: Long, newValue: Long) = step()
- override fun toString(): String = "LockFreedomTestEnvironment($name)"
- }
-}
-
-/**
- * Manual pause for on-going lock-free operation in a specified piece of code.
- * Use it for targeted debugging of specific places in code. It does nothing
- * when invoked outside of test thread.
- *
- * **Don't use it in production code.**
- */
-public fun pauseLockFreeOp() {
- val thread = Thread.currentThread() as? LockFreedomTestEnvironment.TestThread ?: return
- thread.pauseImpl()
-} \ No newline at end of file
diff --git a/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt b/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt
deleted file mode 100644
index 5179638..0000000
--- a/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/LockFreeQueueLFTest.kt
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.test
-
-import kotlinx.atomicfu.LockFreedomTestEnvironment
-import org.junit.Test
-import java.util.*
-
-class LockFreeQueueLFTest : LockFreedomTestEnvironment("LockFreeQueueLFTest") {
- val nEnqueuers = 2
- val nDequeuers = 2
- val nSeconds = 5
-
- val queue = LockFreeQueue()
-
- @Test
- fun testLockFreedom() {
- repeat(nEnqueuers) { id ->
- val rnd = Random()
- testThread("Enqueue-$id") {
- queue.enqueue(rnd.nextInt(1000))
- }
- }
- repeat(nDequeuers) { id ->
- testThread("Dequeue-$id") {
- queue.dequeue()
- }
- }
- performTest(nSeconds)
- }
-} \ No newline at end of file
diff --git a/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/TraceLFTest.kt b/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/TraceLFTest.kt
deleted file mode 100644
index 32ea8d8..0000000
--- a/atomicfu/src/jvmTest/kotlin/kotlinx/atomicfu/test/TraceLFTest.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.atomicfu.test
-
-import kotlinx.atomicfu.*
-import kotlin.test.Test
-
-class Counter {
- private val t = Trace(64, TraceFormat { index, text ->
- "$index: [${Thread.currentThread().name}] $text"
- })
- private val a = atomic(0, t)
-
- fun inc(): Int {
- t { "inc() invoked" }
- val x = a.incrementAndGet()
- t { "inc() = $x" }
- return x
- }
-
- internal fun get() = a.value
-}
-
-class CounterDefaultAtomic {
- private val a = atomic(0)
- private val trace = Trace(64)
-
- fun inc(): Int {
- trace { "inc() invoked" }
- val x = a.incrementAndGet()
- trace { "inc() = $x" }
- return x
- }
-
- internal fun get() = a.value
-}
-
-class CounterLFTest : LockFreedomTestEnvironment("CounterLFTest") {
- private val c = Counter()
- private val c1 = CounterDefaultAtomic()
-
- @Test
- fun testCounterDefault() {
- repeat(10) { id ->
- testThread ("Inc-$id") {
- c1.inc()
- }
- }
- repeat(2) { id ->
- testThread("Get-$id") {
- c1.get()
- }
- }
- performTest(10)
- println(c1.get())
- }
-
- @Test
- fun testLockFreedom() {
- repeat(10) { id ->
- testThread("Inc-$id") {
- c.inc()
- }
- }
- repeat(2) { id ->
- testThread("Get-$id") {
- c.get()
- }
- }
- performTest(10)
- println(c.get())
- }
-}
-
diff --git a/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/AtomicFU.kt b/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
index 55ed452..b369540 100644
--- a/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
+++ b/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/AtomicFU.kt
@@ -25,8 +25,8 @@ public actual fun atomic(initial: Boolean): AtomicBoolean = atomic(initial, None
// ==================================== AtomicRef ====================================
-@Suppress("ACTUAL_WITHOUT_EXPECT", "EXPERIMENTAL_FEATURE_WARNING", "NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
-public actual inline class AtomicRef<T> internal constructor(@PublishedApi internal val a: KAtomicRef<T>) {
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual value class AtomicRef<T> internal constructor(@PublishedApi internal val a: KAtomicRef<T>) {
public actual inline var value: T
get() = a.value
set(value) {
@@ -62,8 +62,8 @@ public actual inline class AtomicRef<T> internal constructor(@PublishedApi inter
// ==================================== AtomicBoolean ====================================
-@Suppress("ACTUAL_WITHOUT_EXPECT", "EXPERIMENTAL_FEATURE_WARNING", "NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
-public actual inline class AtomicBoolean internal constructor(@PublishedApi internal val a: KAtomicInt) {
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual value class AtomicBoolean internal constructor(@PublishedApi internal val a: KAtomicInt) {
public actual inline var value: Boolean
get() = a.value != 0
set(value) { a.value = if (value) 1 else 0 }
@@ -94,8 +94,8 @@ public actual inline class AtomicBoolean internal constructor(@PublishedApi inte
// ==================================== AtomicInt ====================================
-@Suppress("ACTUAL_WITHOUT_EXPECT", "EXPERIMENTAL_FEATURE_WARNING", "NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
-public actual inline class AtomicInt internal constructor(@PublishedApi internal val a: KAtomicInt) {
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual value class AtomicInt internal constructor(@PublishedApi internal val a: KAtomicInt) {
public actual inline var value: Int
get() = a.value
set(value) { a.value = value }
@@ -132,8 +132,8 @@ public actual inline class AtomicInt internal constructor(@PublishedApi internal
// ==================================== AtomicLong ====================================
-@Suppress("ACTUAL_WITHOUT_EXPECT", "EXPERIMENTAL_FEATURE_WARNING", "NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
-public actual inline class AtomicLong internal constructor(@PublishedApi internal val a: KAtomicLong) {
+@Suppress("ACTUAL_WITHOUT_EXPECT")
+public actual value class AtomicLong internal constructor(@PublishedApi internal val a: KAtomicLong) {
public actual inline var value: Long
get() = a.value
set(value) { a.value = value }
diff --git a/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/locks/Synchronized.kt b/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/locks/Synchronized.kt
index ff81a5a..76e5d7a 100644
--- a/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/locks/Synchronized.kt
+++ b/atomicfu/src/nativeMain/kotlin/kotlinx/atomicfu/locks/Synchronized.kt
@@ -190,7 +190,7 @@ class MutexPool(capacity: Int) {
init {
for (i in 0 until capacity) {
- release(interpretCPointer<mutex_node_t>(mutexes.rawValue.plus(i * mutex_node_t.size))!!)
+ release(interpretCPointer<mutex_node_t>(mutexes.rawValue.plus(i * sizeOf<mutex_node_t>()))!!)
}
}
diff --git a/build.gradle b/build.gradle
index 12f531d..09a62fe 100644
--- a/build.gradle
+++ b/build.gradle
@@ -34,15 +34,6 @@ buildscript {
maven { url "https://plugins.gradle.org/m2/" }
// Future replacement for kotlin-dev, with cache redirector
maven { url "https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
- maven {
- url "https://kotlin.bintray.com/kotlin-dev"
- credentials {
- username = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') ?: ""
- password = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') ?: ""
- }
- }
- maven { url "https://kotlin.bintray.com/kotlin-eap" }
- maven { url "https://jetbrains.bintray.com/kotlin-native-dependencies" }
maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
}
@@ -66,17 +57,8 @@ allprojects {
println "Using Kotlin $kotlin_version for project $it"
repositories {
jcenter()
- maven { url "https://kotlin.bintray.com/kotlin-eap" }
// Future replacement for kotlin-dev, with cache redirector
maven { url "https://cache-redirector.jetbrains.com/maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
- maven {
- url "https://kotlin.bintray.com/kotlin-dev"
- credentials {
- username = project.hasProperty('bintrayUser') ? project.property('bintrayUser') : System.getenv('BINTRAY_USER') ?: ""
- password = project.hasProperty('bintrayApiKey') ? project.property('bintrayApiKey') : System.getenv('BINTRAY_API_KEY') ?: ""
- }
- }
- maven { url "https://kotlin.bintray.com/kotlinx" }
maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
}
diff --git a/buildSrc/src/main/kotlin/Publishing.kt b/buildSrc/src/main/kotlin/Publishing.kt
index 6844ad7..3db911f 100644
--- a/buildSrc/src/main/kotlin/Publishing.kt
+++ b/buildSrc/src/main/kotlin/Publishing.kt
@@ -44,20 +44,6 @@ fun MavenPom.configureMavenCentralMetadata(project: Project) {
}
}
-fun configureBintrayPublication(rh: RepositoryHandler, project: Project) {
- rh.maven {
- val user = "kotlin"
- val repo = "kotlinx"
- val name = "kotlinx.atomicfu"
- url = URI("https://api.bintray.com/maven/$user/$repo/$name/;publish=0;override=0")
-
- credentials {
- username = project.findProperty("bintrayUser") as? String ?: System.getenv("BINTRAY_USER")
- password = project.findProperty("bintrayApiKey") as? String ?: System.getenv("BINTRAY_API_KEY")
- }
- }
-}
-
fun mavenRepositoryUri(): URI {
// TODO -SNAPSHOT detection can be made here as well
val repositoryId: String? = System.getenv("libs.repository.id")
diff --git a/gradle.properties b/gradle.properties
index ce4f84c..903d60d 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -2,14 +2,14 @@
# Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
#
-version=0.16.0-SNAPSHOT
+version=0.18.5-SNAPSHOT
group=org.jetbrains.kotlinx
-kotlin_version=1.5.0
-asm_version=7.2
+kotlin_version=1.7.20
+asm_version=9.3
slf4j_version=1.8.0-alpha2
junit_version=4.12
-kotlinx_metadata_version=0.2.0
+kotlinx_metadata_version=0.5.0
maven_version=3.5.3
diff --git a/gradle/compile-options.gradle b/gradle/compile-options.gradle
index e6b4432..b6ce532 100644
--- a/gradle/compile-options.gradle
+++ b/gradle/compile-options.gradle
@@ -21,8 +21,6 @@ ext.configureKotlin = { isMultiplatform ->
languageSettings {
apiVersion = "1.4"
languageVersion = "1.4"
- useExperimentalAnnotation("kotlin.Experimental")
- useExperimentalAnnotation("kotlin.ExperimentalStdlibApi")
}
}
}
diff --git a/gradle/interop-as-source-set-klib.gradle b/gradle/interop-as-source-set-klib.gradle
index 62f2b77..25cb0c2 100644
--- a/gradle/interop-as-source-set-klib.gradle
+++ b/gradle/interop-as-source-set-klib.gradle
@@ -7,7 +7,7 @@ project.ext.registerInteropAsSourceSetOutput = { interop, sourceSet ->
def cinteropTask = tasks.named(interop.interopProcessingTaskName)
def cinteropKlib = cinteropTask.map { it.outputFile }
def fakeCinteropCompilation = kotlin.targets["metadata"].compilations[sourceSet.name]
- def destination = fakeCinteropCompilation.compileKotlinTask.destinationDir
+ def destination = fakeCinteropCompilation.compileKotlinTask.destinationDirectory
def tempDir = "$buildDir/tmp/${sourceSet.name}UnpackedInteropKlib"
diff --git a/gradle/publish-npm-js.gradle b/gradle/publish-npm-js.gradle
index e6760a7..b7e7264 100644
--- a/gradle/publish-npm-js.gradle
+++ b/gradle/publish-npm-js.gradle
@@ -29,7 +29,7 @@ task preparePublishNpm(type: Copy, dependsOn: [compileJsLegacy]) {
from(npmTemplateDir) {
expand (project.properties + [kotlinDependency: "\"kotlin\": \"$kotlin_version\""])
}
- from(compileJsLegacy.destinationDir)
+ from(compileJsLegacy.destinationDirectory)
into npmDeployDir
}
diff --git a/gradle/publishing.gradle b/gradle/publishing.gradle
index 0ca4788..e9e4372 100644
--- a/gradle/publishing.gradle
+++ b/gradle/publishing.gradle
@@ -27,15 +27,9 @@ task javadocJar(type: Jar) {
archiveClassifier = 'javadoc'
}
-def bintrayUpload = System.getenv("libs.bintray.upload") != null
-
publishing {
repositories { // this: closure
- if (bintrayUpload) {
- PublishingKt.configureBintrayPublication(delegate, project)
- } else {
- PublishingKt.configureMavenPublication(delegate, project)
- }
+ PublishingKt.configureMavenPublication(delegate, project)
}
if (!isMultiplatform) {
@@ -61,9 +55,7 @@ publishing {
publications.all {
PublishingKt.configureMavenCentralMetadata(pom, project)
- if (!bintrayUpload) {
- PublishingKt.signPublicationIfKeyPresent(project, it)
- }
+ PublishingKt.signPublicationIfKeyPresent(project, it)
// add empty javadocs
if (it.name != "kotlinMultiplatform") { // The root module gets the JVM's javadoc JAR
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 4c83d2a..0904b9b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip