Skip to content

Commit fea9b5d

Browse files
Tapchicomamvicsokolova
authored andcommitted
Fix duplicated class files in jar for JVM-only projects (#303)
Jar task for JVM-only projects packed both original and transformed classes with Kotlin 1.8.20 and Gradle 7.3. This commit contains fix for the issue, plus some refactoring in AtomicfuGradlePlugin using KGP API introduced in 1.7.0. * Fixed configuration of outputs classesDir: remove outputs of Kotlin compilation task and replace with atomicfu transformation task output * Refactoring: - Renamed the directory with original classes to `classes/atomicfu-orig/` - Introduced 'destinationDirectory' for AtomicFUTransformTask - Lazy registration of AtomicFU transform tasks * Added minimal supported version of KGP and Gradle (KGP >= 1.7.0, Gradle >= 7.0)
1 parent fb6555d commit fea9b5d

File tree

5 files changed

+160
-78
lines changed

5 files changed

+160
-78
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
**Atomicfu** is a multiplatform library that provides the idiomatic and effective way of using atomic operations in Kotlin.
1313

1414
## Table of contents
15+
- [Requirements](#requirements)
1516
- [Features](#features)
1617
- [Example](#example)
1718
- [Quickstart](#quickstart)
@@ -31,6 +32,13 @@
3132
- [Tracing operations](#tracing-operations)
3233
- [Kotlin/Native support](#kotlin-native-support)
3334

35+
## Requirements
36+
37+
Starting from version `0.21.0` of the library your project is required to use:
38+
39+
* Gradle `7.0` or newer
40+
41+
* Kotlin `1.7.0` or newer
3442

3543
## Features
3644

atomicfu-gradle-plugin/src/main/kotlin/kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin.kt

Lines changed: 141 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,13 @@ import org.gradle.api.*
99
import org.gradle.api.file.*
1010
import org.gradle.api.internal.*
1111
import org.gradle.api.plugins.*
12+
import org.gradle.api.provider.Provider
13+
import org.gradle.api.provider.ProviderFactory
1214
import org.gradle.api.tasks.*
1315
import org.gradle.api.tasks.compile.*
1416
import org.gradle.api.tasks.testing.*
1517
import org.gradle.jvm.tasks.*
18+
import org.gradle.util.*
1619
import org.jetbrains.kotlin.gradle.dsl.*
1720
import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
1821
import org.jetbrains.kotlin.gradle.plugin.*
@@ -23,6 +26,7 @@ import org.jetbrains.kotlin.gradle.targets.js.*
2326
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
2427
import org.jetbrains.kotlin.gradle.tasks.*
2528
import org.jetbrains.kotlinx.atomicfu.gradle.*
29+
import javax.inject.Inject
2630

2731
private const val EXTENSION_NAME = "atomicfu"
2832
private const val ORIGINAL_DIR_NAME = "originalClassesDir"
@@ -34,9 +38,12 @@ private const val TEST_IMPLEMENTATION_CONFIGURATION = "testImplementation"
3438
private const val ENABLE_JS_IR_TRANSFORMATION_LEGACY = "kotlinx.atomicfu.enableIrTransformation"
3539
private const val ENABLE_JS_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJsIrTransformation"
3640
private const val ENABLE_JVM_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJvmIrTransformation"
41+
private const val MIN_SUPPORTED_GRADLE_VERSION = "7.0"
42+
private const val MIN_SUPPORTED_KGP_VERSION = "1.7.0"
3743

3844
open class AtomicFUGradlePlugin : Plugin<Project> {
3945
override fun apply(project: Project) = project.run {
46+
checkCompatibility()
4047
val pluginVersion = rootProject.buildscript.configurations.findByName("classpath")
4148
?.allDependencies?.find { it.name == "atomicfu-gradle-plugin" }?.version
4249
extensions.add(EXTENSION_NAME, AtomicFUPluginExtension(pluginVersion))
@@ -46,6 +53,24 @@ open class AtomicFUGradlePlugin : Plugin<Project> {
4653
}
4754
}
4855

56+
private fun Project.checkCompatibility() {
57+
val currentGradleVersion = GradleVersion.current()
58+
val kotlinVersion = getKotlinVersion()
59+
val minSupportedVersion = GradleVersion.version(MIN_SUPPORTED_GRADLE_VERSION)
60+
if (currentGradleVersion < minSupportedVersion) {
61+
throw GradleException(
62+
"The current Gradle version is not compatible with Atomicfu gradle plugin. " +
63+
"Please use Gradle $MIN_SUPPORTED_GRADLE_VERSION or newer, or the previous version of Atomicfu gradle plugin."
64+
)
65+
}
66+
if (!kotlinVersion.atLeast(1, 7, 0)) {
67+
throw GradleException(
68+
"The current Kotlin gradle plugin version is not compatible with Atomicfu gradle plugin. " +
69+
"Please use Kotlin $MIN_SUPPORTED_KGP_VERSION or newer, or the previous version of Atomicfu gradle plugin."
70+
)
71+
}
72+
}
73+
4974
private fun Project.configureDependencies() {
5075
withPluginWhenEvaluatedDependencies("kotlin") { version ->
5176
dependencies.add(
@@ -242,68 +267,79 @@ private fun Project.configureTransformationForTarget(target: KotlinTarget) {
242267
?: return@compilations // skip unknown compilations
243268
val classesDirs = compilation.output.classesDirs
244269
// make copy of original classes directory
245-
val originalClassesDirs: FileCollection =
246-
project.files(classesDirs.from.toTypedArray()).filter { it.exists() }
270+
@Suppress("UNCHECKED_CAST")
271+
val compilationTask = compilation.compileTaskProvider as TaskProvider<KotlinCompileTool>
272+
val originalDestinationDirectory = project.layout.buildDirectory
273+
.dir("classes/atomicfu-orig/${target.name}/${compilation.name}")
274+
compilationTask.configure {
275+
if (it is Kotlin2JsCompile) {
276+
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE")
277+
it.defaultDestinationDirectory.value(originalDestinationDirectory)
278+
} else {
279+
it.destinationDirectory.value(originalDestinationDirectory)
280+
}
281+
}
282+
val originalClassesDirs: FileCollection = project.objects.fileCollection().from(
283+
compilationTask.flatMap { it.destinationDirectory }
284+
)
247285
originalDirsByCompilation[compilation] = originalClassesDirs
248-
val transformedClassesDir =
249-
project.buildDir.resolve("classes/atomicfu/${target.name}/${compilation.name}")
286+
val transformedClassesDir = project.layout.buildDirectory
287+
.dir("classes/atomicfu/${target.name}/${compilation.name}")
250288
val transformTask = when (target.platformType) {
251289
KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> {
252290
// skip transformation task if transformation is turned off or ir transformation is enabled
253291
if (!config.transformJvm || rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)) return@compilations
254-
project.createJvmTransformTask(compilation).configureJvmTask(
255-
compilation.compileDependencyFiles,
256-
compilation.compileAllTaskName,
257-
transformedClassesDir,
258-
originalClassesDirs,
259-
config
260-
)
292+
project.registerJvmTransformTask(compilation)
293+
.configureJvmTask(
294+
compilation.compileDependencyFiles,
295+
compilation.compileAllTaskName,
296+
transformedClassesDir,
297+
originalClassesDirs,
298+
config
299+
)
300+
.also {
301+
compilation.defaultSourceSet.kotlin.compiledBy(it, AtomicFUTransformTask::destinationDirectory)
302+
}
261303
}
262304
KotlinPlatformType.js -> {
263305
// skip when js transformation is not needed or when IR is transformed
264306
if (!config.transformJs || (needsJsIrTransformation(target))) {
265307
return@compilations
266308
}
267-
project.createJsTransformTask(compilation).configureJsTask(
268-
compilation.compileAllTaskName,
269-
transformedClassesDir,
270-
originalClassesDirs,
271-
config
272-
)
309+
project.registerJsTransformTask(compilation)
310+
.configureJsTask(
311+
compilation.compileAllTaskName,
312+
transformedClassesDir,
313+
originalClassesDirs,
314+
config
315+
)
316+
.also {
317+
compilation.defaultSourceSet.kotlin.compiledBy(it, AtomicFUTransformJsTask::destinationDirectory)
318+
}
273319
}
274320
else -> error("Unsupported transformation platform '${target.platformType}'")
275321
}
276322
//now transformTask is responsible for compiling this source set into the classes directory
323+
compilation.defaultSourceSet.kotlin.destinationDirectory.value(transformedClassesDir)
277324
classesDirs.setFrom(transformedClassesDir)
278-
classesDirs.builtBy(transformTask)
279-
(tasks.findByName(target.artifactsTaskName) as? Jar)?.apply {
280-
setupJarManifest(multiRelease = config.jvmVariant.toJvmVariant() == JvmVariant.BOTH)
325+
classesDirs.setBuiltBy(listOf(transformTask))
326+
tasks.withType(Jar::class.java).configureEach {
327+
if (name == target.artifactsTaskName) {
328+
it.setupJarManifest(multiRelease = config.jvmVariant.toJvmVariant() == JvmVariant.BOTH)
329+
}
281330
}
282331
// test should compile and run against original production binaries
283332
if (compilationType == CompilationType.TEST) {
284333
val mainCompilation =
285334
compilation.target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
286-
val originalMainClassesDirs = project.files(
287-
// use Callable because there is no guarantee that main is configured before test
288-
Callable { originalDirsByCompilation[mainCompilation]!! }
335+
val originalMainClassesDirs = project.objects.fileCollection().from(
336+
mainCompilation.compileTaskProvider.flatMap { (it as KotlinCompileTool).destinationDirectory }
289337
)
290-
291-
// KGP >= 1.7.0 has breaking changes in task hierarchy:
292-
// https://youtrack.jetbrains.com/issue/KT-32805#focus=Comments-27-5915479.0-0
293-
val (majorVersion, minorVersion) = getKotlinPluginVersion()
294-
.split('.')
295-
.take(2)
296-
.map { it.toInt() }
297-
if (majorVersion == 1 && minorVersion < 7) {
298-
(tasks.findByName(compilation.compileKotlinTaskName) as? AbstractCompile)?.classpath =
299-
originalMainClassesDirs + compilation.compileDependencyFiles - mainCompilation.output.classesDirs
300-
} else {
301-
(tasks.findByName(compilation.compileKotlinTaskName) as? AbstractKotlinCompileTool<*>)
302-
?.libraries
303-
?.setFrom(
304-
originalMainClassesDirs + compilation.compileDependencyFiles - mainCompilation.output.classesDirs
305-
)
306-
}
338+
(tasks.findByName(compilation.compileKotlinTaskName) as? AbstractKotlinCompileTool<*>)
339+
?.libraries
340+
?.setFrom(
341+
originalMainClassesDirs + compilation.compileDependencyFiles
342+
)
307343

308344
(tasks.findByName("${target.name}${compilation.name.capitalize()}") as? Test)?.classpath =
309345
originalMainClassesDirs + (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyFiles - mainCompilation.output.classesDirs
@@ -381,48 +417,49 @@ fun Project.configureMultiplatformPluginDependencies(version: String) {
381417

382418
fun String.toJvmVariant(): JvmVariant = enumValueOf(toUpperCase(Locale.US))
383419

384-
fun Project.createJvmTransformTask(compilation: KotlinCompilation<*>): AtomicFUTransformTask =
385-
tasks.create(
420+
fun Project.registerJvmTransformTask(compilation: KotlinCompilation<*>): TaskProvider<AtomicFUTransformTask> =
421+
tasks.register(
386422
"transform${compilation.target.name.capitalize()}${compilation.name.capitalize()}Atomicfu",
387423
AtomicFUTransformTask::class.java
388424
)
389425

390-
fun Project.createJsTransformTask(compilation: KotlinCompilation<*>): AtomicFUTransformJsTask =
391-
tasks.create(
426+
fun Project.registerJsTransformTask(compilation: KotlinCompilation<*>): TaskProvider<AtomicFUTransformJsTask> =
427+
tasks.register(
392428
"transform${compilation.target.name.capitalize()}${compilation.name.capitalize()}Atomicfu",
393429
AtomicFUTransformJsTask::class.java
394430
)
395431

396-
fun Project.createJvmTransformTask(sourceSet: SourceSet): AtomicFUTransformTask =
397-
tasks.create(sourceSet.getTaskName("transform", "atomicfuClasses"), AtomicFUTransformTask::class.java)
398-
399-
fun AtomicFUTransformTask.configureJvmTask(
432+
fun TaskProvider<AtomicFUTransformTask>.configureJvmTask(
400433
classpath: FileCollection,
401434
classesTaskName: String,
402-
transformedClassesDir: File,
435+
transformedClassesDir: Provider<Directory>,
403436
originalClassesDir: FileCollection,
404437
config: AtomicFUPluginExtension
405-
): ConventionTask =
438+
): TaskProvider<AtomicFUTransformTask> =
406439
apply {
407-
dependsOn(classesTaskName)
408-
classPath = classpath
409-
inputFiles = originalClassesDir
410-
outputDir = transformedClassesDir
411-
jvmVariant = config.jvmVariant
412-
verbose = config.verbose
440+
configure {
441+
it.dependsOn(classesTaskName)
442+
it.classPath = classpath
443+
it.inputFiles = originalClassesDir
444+
it.destinationDirectory.value(transformedClassesDir)
445+
it.jvmVariant = config.jvmVariant
446+
it.verbose = config.verbose
447+
}
413448
}
414449

415-
fun AtomicFUTransformJsTask.configureJsTask(
450+
fun TaskProvider<AtomicFUTransformJsTask>.configureJsTask(
416451
classesTaskName: String,
417-
transformedClassesDir: File,
452+
transformedClassesDir: Provider<Directory>,
418453
originalClassesDir: FileCollection,
419454
config: AtomicFUPluginExtension
420-
): ConventionTask =
455+
): TaskProvider<AtomicFUTransformJsTask> =
421456
apply {
422-
dependsOn(classesTaskName)
423-
inputFiles = originalClassesDir
424-
outputDir = transformedClassesDir
425-
verbose = config.verbose
457+
configure {
458+
it.dependsOn(classesTaskName)
459+
it.inputFiles = originalClassesDir
460+
it.destinationDirectory.value(transformedClassesDir)
461+
it.verbose = config.verbose
462+
}
426463
}
427464

428465
fun Jar.setupJarManifest(multiRelease: Boolean) {
@@ -445,13 +482,29 @@ class AtomicFUPluginExtension(pluginVersion: String?) {
445482
}
446483

447484
@CacheableTask
448-
open class AtomicFUTransformTask : ConventionTask() {
485+
abstract class AtomicFUTransformTask : ConventionTask() {
486+
@get:Inject
487+
internal abstract val providerFactory: ProviderFactory
488+
489+
@get:Inject
490+
internal abstract val projectLayout: ProjectLayout
491+
449492
@PathSensitive(PathSensitivity.RELATIVE)
450493
@InputFiles
451494
lateinit var inputFiles: FileCollection
452495

453-
@OutputDirectory
454-
lateinit var outputDir: File
496+
@Suppress("unused")
497+
@Deprecated(
498+
message = "Replaced with 'destinationDirectory'",
499+
replaceWith = ReplaceWith("destinationDirectory")
500+
)
501+
@get:Internal
502+
var outputDir: File
503+
get() = destinationDirectory.get().asFile
504+
set(value) { destinationDirectory.value(projectLayout.dir(providerFactory.provider { value })) }
505+
506+
@get:OutputDirectory
507+
abstract val destinationDirectory: DirectoryProperty
455508

456509
@Classpath
457510
@InputFiles
@@ -467,7 +520,7 @@ open class AtomicFUTransformTask : ConventionTask() {
467520
fun transform() {
468521
val cp = classPath.files.map { it.absolutePath }
469522
inputFiles.files.forEach { inputDir ->
470-
AtomicFUTransformer(cp, inputDir, outputDir).let { t ->
523+
AtomicFUTransformer(cp, inputDir, destinationDirectory.get().asFile).let { t ->
471524
t.jvmVariant = jvmVariant.toJvmVariant()
472525
t.verbose = verbose
473526
t.transform()
@@ -477,21 +530,38 @@ open class AtomicFUTransformTask : ConventionTask() {
477530
}
478531

479532
@CacheableTask
480-
open class AtomicFUTransformJsTask : ConventionTask() {
533+
abstract class AtomicFUTransformJsTask : ConventionTask() {
534+
535+
@get:Inject
536+
internal abstract val providerFactory: ProviderFactory
537+
538+
@get:Inject
539+
internal abstract val projectLayout: ProjectLayout
540+
481541
@PathSensitive(PathSensitivity.RELATIVE)
482542
@InputFiles
483543
lateinit var inputFiles: FileCollection
484544

485-
@OutputDirectory
486-
lateinit var outputDir: File
545+
@Suppress("unused")
546+
@Deprecated(
547+
message = "Replaced with 'destinationDirectory'",
548+
replaceWith = ReplaceWith("destinationDirectory")
549+
)
550+
@get:Internal
551+
var outputDir: File
552+
get() = destinationDirectory.get().asFile
553+
set(value) { destinationDirectory.value(projectLayout.dir(providerFactory.provider { value })) }
554+
555+
@get:OutputDirectory
556+
abstract val destinationDirectory: DirectoryProperty
487557

488558
@Input
489559
var verbose = false
490560

491561
@TaskAction
492562
fun transform() {
493563
inputFiles.files.forEach { inputDir ->
494-
AtomicFUTransformerJS(inputDir, outputDir).let { t ->
564+
AtomicFUTransformerJS(inputDir, destinationDirectory.get().asFile).let { t ->
495565
t.verbose = verbose
496566
t.transform()
497567
}

atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/internal/Assert.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,9 @@ internal fun BuildResult.assertTaskUpToDate(task: String) {
2828
}
2929

3030
private fun BuildResult.assertTaskOutcome(taskOutcome: TaskOutcome, taskName: String) {
31-
assertEquals(taskOutcome, task(taskName)?.outcome)
31+
assertEquals(
32+
taskOutcome,
33+
task(taskName)?.outcome,
34+
"Task $taskName does not have ${taskOutcome.name} outcome"
35+
)
3236
}

atomicfu-gradle-plugin/src/test/kotlin/kotlinx/atomicfu/plugin/gradle/test/JvmProjectTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class JvmLegacyTransformationTest : BaseKotlinGradleTest("jvm-simple") {
3535
checkTaskOutcomes(
3636
executedTasks = listOf(
3737
":compileKotlin",
38-
":transformAtomicfu",
38+
":transformMainAtomicfu",
3939
":compileTestKotlin",
4040
":transformTestAtomicfu"
4141
),
@@ -46,7 +46,7 @@ class JvmLegacyTransformationTest : BaseKotlinGradleTest("jvm-simple") {
4646
fun testClasspath() {
4747
runner.build()
4848
checkJvmCompilationClasspath(
49-
originalClassFile = "build/classes/kotlin/main/IntArithmetic.class",
49+
originalClassFile = "build/classes/atomicfu-orig/main/IntArithmetic.class",
5050
transformedClassFile = "build/classes/atomicfu/main/IntArithmetic.class"
5151
)
5252
}
@@ -103,6 +103,6 @@ class JvmIrTransformationTest : BaseKotlinGradleTest("jvm-simple") {
103103
@Test
104104
fun testAtomicfuReferences() {
105105
runner.build()
106-
checkBytecode("build/classes/kotlin/main/IntArithmetic.class")
106+
checkBytecode("build/classes/atomicfu-orig/main/IntArithmetic.class")
107107
}
108108
}

0 commit comments

Comments
 (0)