Skip to content

Commit

Permalink
Integration tests (#345)
Browse files Browse the repository at this point in the history
Introduced better integration tests for `atomicfu-gradle-plugin`, that build and locally publish the current version of the plugin and actually check correctness of it's application to sample projects. 
    * Final artefacts are checked for atomicfu references: there should not be any if plugin transformations are applied.
    * Dependencies added by the `atomicfu-gradle-plugin` to every target are also verified: in case the plugin is applied the dependency to the library should be compileOnly, otherwise it should also be included in runtime.
    * For an MPP project there are tests that enable IR transformations separately for JVM, JS and Native targets.
---------

Co-authored-by: Sergey.Shanshin <sergey.shanshin@jetbrains.com>
Co-authored-by: Filipp Zhinkin <filipp.zhinkin@gmail.com>
  • Loading branch information
3 people authored Nov 13, 2023
1 parent 90d52fd commit 9d2a3e4
Show file tree
Hide file tree
Showing 32 changed files with 932 additions and 569 deletions.
19 changes: 0 additions & 19 deletions atomicfu-gradle-plugin/api/atomicfu-gradle-plugin.api
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,6 @@ public class kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePlugin : org/gradle/ap
public fun apply (Lorg/gradle/api/Project;)V
}

public final class kotlinx/atomicfu/plugin/gradle/AtomicFUGradlePluginKt {
public static final fun configureJsTask (Lorg/gradle/api/tasks/TaskProvider;Ljava/lang/String;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/file/FileCollection;Lkotlinx/atomicfu/plugin/gradle/AtomicFUPluginExtension;)Lorg/gradle/api/tasks/TaskProvider;
public static final fun configureJsTransformation (Lorg/gradle/api/Project;)V
public static final fun configureJvmTask (Lorg/gradle/api/tasks/TaskProvider;Lorg/gradle/api/file/FileCollection;Ljava/lang/String;Lorg/gradle/api/provider/Provider;Lorg/gradle/api/file/FileCollection;Lkotlinx/atomicfu/plugin/gradle/AtomicFUPluginExtension;)Lorg/gradle/api/tasks/TaskProvider;
public static final fun configureJvmTransformation (Lorg/gradle/api/Project;)V
public static final fun configureMultiplatformPluginDependencies (Lorg/gradle/api/Project;Ljava/lang/String;)V
public static final fun configureMultiplatformTransformation (Lorg/gradle/api/Project;)V
public static final fun getSourceSets (Lorg/gradle/api/Project;)Lorg/gradle/api/tasks/SourceSetContainer;
public static final fun registerJsTransformTask (Lorg/gradle/api/Project;Lorg/jetbrains/kotlin/gradle/plugin/KotlinCompilation;)Lorg/gradle/api/tasks/TaskProvider;
public static final fun registerJvmTransformTask (Lorg/gradle/api/Project;Lorg/jetbrains/kotlin/gradle/plugin/KotlinCompilation;)Lorg/gradle/api/tasks/TaskProvider;
public static final fun setupJarManifest (Lorg/gradle/jvm/tasks/Jar;Z)V
public static final fun sourceSetsByCompilation (Lorg/gradle/api/Project;)Ljava/util/Map;
public static final fun toJvmVariant (Ljava/lang/String;)Lkotlinx/atomicfu/transformer/JvmVariant;
public static final fun whenEvaluated (Lorg/gradle/api/Project;Lkotlin/jvm/functions/Function1;)V
public static final fun withKotlinTargets (Lorg/gradle/api/Project;Lkotlin/jvm/functions/Function1;)V
public static final fun withPluginWhenEvaluated (Lorg/gradle/api/Project;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
public static final fun withPluginWhenEvaluatedDependencies (Lorg/gradle/api/Project;Ljava/lang/String;Lkotlin/jvm/functions/Function2;)V
}

public final class kotlinx/atomicfu/plugin/gradle/AtomicFUPluginExtension {
public fun <init> (Ljava/lang/String;)V
public final fun getDependenciesVersion ()Ljava/lang/String;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,9 @@ import kotlinx.atomicfu.transformer.*
import org.gradle.api.*
import org.gradle.api.file.*
import org.gradle.api.internal.*
import org.gradle.api.plugins.*
import org.gradle.api.provider.Provider
import org.gradle.api.provider.ProviderFactory
import org.gradle.api.tasks.*
import org.gradle.api.tasks.compile.*
import org.gradle.api.tasks.testing.*
import org.gradle.jvm.tasks.*
import org.gradle.util.*
Expand All @@ -21,7 +19,6 @@ 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.*
Expand Down Expand Up @@ -71,6 +68,29 @@ private fun Project.checkCompatibility() {
}
}

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)
}
}
}
}

private fun Project.configureDependencies() {
withPluginWhenEvaluatedDependencies("kotlin") { version ->
dependencies.add(
Expand All @@ -85,23 +105,34 @@ private fun Project.configureDependencies() {
getAtomicfuDependencyNotation(Platform.JS, version)
)
dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JS, version))
addCompilerPluginDependency()
addJsCompilerPluginRuntimeDependency()
}
withPluginWhenEvaluatedDependencies("kotlin-multiplatform") { version ->
addJsCompilerPluginRuntimeDependency()
configureMultiplatformPluginDependencies(version)
}
}

private fun Project.configureTasks() {
val config = config
withPluginWhenEvaluated("kotlin") {
if (config.transformJvm) configureJvmTransformation()
private fun Project.configureMultiplatformPluginDependencies(version: String) {
val multiplatformExtension = kotlinExtension as? KotlinMultiplatformExtension ?: error("Expected kotlin multiplatform extension")
val atomicfuDependency = "org.jetbrains.kotlinx:atomicfu:$version"
multiplatformExtension.sourceSets.getByName("commonMain").dependencies {
compileOnly(atomicfuDependency)
}
withPluginWhenEvaluated("org.jetbrains.kotlin.js") {
if (config.transformJs) configureJsTransformation()
multiplatformExtension.sourceSets.getByName("commonTest").dependencies {
implementation(atomicfuDependency)
}
withPluginWhenEvaluated("kotlin-multiplatform") {
configureMultiplatformTransformation()
// Include atomicfu as a dependency for publication when transformation for the target is disabled
multiplatformExtension.targets.all { target ->
if (isTransformationDisabled(target)) {
target.compilations.all { compilation ->
compilation
.defaultSourceSet
.dependencies {
implementation(atomicfuDependency)
}
}
}
}
}

Expand All @@ -123,29 +154,6 @@ private fun KotlinVersion.atLeast(major: Int, minor: Int, patch: Int) =
// kotlinx-atomicfu compiler plugin is available for KGP >= 1.6.20
private fun Project.isCompilerPluginAvailable() = getKotlinVersion().atLeast(1, 6, 20)

private fun Project.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)
}
}
}
}

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

Expand All @@ -165,7 +173,15 @@ private fun Project.needsJvmIrTransformation(target: KotlinTarget): Boolean =

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

private fun Project.addCompilerPluginDependency() {
private fun Project.isTransformationDisabled(target: KotlinTarget): Boolean {
val platformType = target.platformType
return !config.transformJvm && (platformType == KotlinPlatformType.jvm || platformType == KotlinPlatformType.androidJvm) ||
!config.transformJs && platformType == KotlinPlatformType.js
}

// Adds kotlinx-atomicfu-runtime as an implementation dependency to the JS IR target:
// it provides inline methods that replace atomic methods from the library and is needed at runtime.
private fun Project.addJsCompilerPluginRuntimeDependency() {
if (isCompilerPluginAvailable()) {
withKotlinTargets { target ->
if (target.isJsIrTarget()) {
Expand Down Expand Up @@ -207,25 +223,25 @@ private fun getAtomicfuDependencyNotation(platform: Platform, version: String):

// Note "afterEvaluate" does nothing when the project is already in executed state, so we need
// a special check for this case
fun <T> Project.whenEvaluated(fn: Project.() -> T) {
private fun <T> Project.whenEvaluated(fn: Project.() -> T) {
if (state.executed) {
fn()
} else {
afterEvaluate { fn() }
}
}

fun Project.withPluginWhenEvaluated(plugin: String, fn: Project.() -> Unit) {
private fun Project.withPluginWhenEvaluated(plugin: String, fn: Project.() -> Unit) {
pluginManager.withPlugin(plugin) { whenEvaluated(fn) }
}

fun Project.withPluginWhenEvaluatedDependencies(plugin: String, fn: Project.(version: String) -> Unit) {
private fun Project.withPluginWhenEvaluatedDependencies(plugin: String, fn: Project.(version: String) -> Unit) {
withPluginWhenEvaluated(plugin) {
config.dependenciesVersion?.let { fn(it) }
}
}

fun Project.withKotlinTargets(fn: (KotlinTarget) -> Unit) {
private fun Project.withKotlinTargets(fn: (KotlinTarget) -> Unit) {
extensions.findByType(KotlinTargetsContainer::class.java)?.let { kotlinExtension ->
// find all compilations given sourceSet belongs to
kotlinExtension.targets
Expand All @@ -246,16 +262,29 @@ private fun KotlinCompile<*>.setFriendPaths(friendPathsFileCollection: FileColle
}
}

fun Project.configureJvmTransformation() {
private fun Project.configureTasks() {
val config = config
withPluginWhenEvaluated("kotlin") {
if (config.transformJvm) configureJvmTransformation()
}
withPluginWhenEvaluated("org.jetbrains.kotlin.js") {
if (config.transformJs) configureJsTransformation()
}
withPluginWhenEvaluated("kotlin-multiplatform") {
configureMultiplatformTransformation()
}
}

private fun Project.configureJvmTransformation() {
if (kotlinExtension is KotlinJvmProjectExtension || kotlinExtension is KotlinAndroidProjectExtension) {
configureTransformationForTarget((kotlinExtension as KotlinSingleTargetExtension<*>).target)
}
}

fun Project.configureJsTransformation() =
private fun Project.configureJsTransformation() =
configureTransformationForTarget((kotlinExtension as KotlinJsProjectExtension).js())

fun Project.configureMultiplatformTransformation() =
private 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
Expand Down Expand Up @@ -360,87 +389,21 @@ private fun Project.configureTransformationForTarget(target: KotlinTarget) {
}
}

fun Project.sourceSetsByCompilation(): Map<KotlinSourceSet, List<KotlinCompilation<*>>> {
val sourceSetsByCompilation = hashMapOf<KotlinSourceSet, MutableList<KotlinCompilation<*>>>()
withKotlinTargets { target ->
target.compilations.forEach { compilation ->
compilation.allKotlinSourceSets.forEach { sourceSet ->
sourceSetsByCompilation.getOrPut(sourceSet) { mutableListOf() }.add(compilation)
}
}
}
return sourceSetsByCompilation
}

fun Project.configureMultiplatformPluginDependencies(version: String) {
if (rootProject.getBooleanProperty("kotlin.mpp.enableGranularSourceSetsMetadata")) {
addCompilerPluginDependency()
val mainConfigurationName = project.extensions.getByType(KotlinMultiplatformExtension::class.java).sourceSets
.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
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
// gets published and is properly consumed as a transitive dependency:
sourceSetsByCompilation().forEach { (sourceSet, compilations) ->
val isSharedNativeSourceSet = compilations.all {
it.platformType == KotlinPlatformType.common || it.platformType == KotlinPlatformType.native
}
if (isSharedNativeSourceSet) {
val configuration = sourceSet.implementationConfigurationName
dependencies.add(configuration, getAtomicfuDependencyNotation(Platform.MULTIPLATFORM, version))
}
}
} 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
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, KotlinPlatformType.wasm -> Platform.NATIVE
}
val configurationName = when {
// impl dependency for native (there is no transformation)
platform == Platform.NATIVE -> sourceSet.implementationConfigurationName
// compileOnly dependency for main compilation (commonMain, jvmMain, jsMain)
compilationType == CompilationType.MAIN -> sourceSet.compileOnlyConfigurationName
// impl dependency for tests
else -> sourceSet.implementationConfigurationName
}
dependencies.add(configurationName, getAtomicfuDependencyNotation(platform, version))
}
}
}
private fun String.toJvmVariant(): JvmVariant = enumValueOf(toUpperCase(Locale.US))

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

fun Project.registerJvmTransformTask(compilation: KotlinCompilation<*>): TaskProvider<AtomicFUTransformTask> =
private fun Project.registerJvmTransformTask(compilation: KotlinCompilation<*>): TaskProvider<AtomicFUTransformTask> =
tasks.register(
"transform${compilation.target.name.capitalize()}${compilation.name.capitalize()}Atomicfu",
AtomicFUTransformTask::class.java
)

fun Project.registerJsTransformTask(compilation: KotlinCompilation<*>): TaskProvider<AtomicFUTransformJsTask> =
private fun Project.registerJsTransformTask(compilation: KotlinCompilation<*>): TaskProvider<AtomicFUTransformJsTask> =
tasks.register(
"transform${compilation.target.name.capitalize()}${compilation.name.capitalize()}Atomicfu",
AtomicFUTransformJsTask::class.java
)

fun TaskProvider<AtomicFUTransformTask>.configureJvmTask(
private fun TaskProvider<AtomicFUTransformTask>.configureJvmTask(
classpath: FileCollection,
classesTaskName: String,
transformedClassesDir: Provider<Directory>,
Expand All @@ -458,7 +421,7 @@ fun TaskProvider<AtomicFUTransformTask>.configureJvmTask(
}
}

fun TaskProvider<AtomicFUTransformJsTask>.configureJsTask(
private fun TaskProvider<AtomicFUTransformJsTask>.configureJsTask(
classesTaskName: String,
transformedClassesDir: Provider<Directory>,
originalClassesDir: FileCollection,
Expand All @@ -473,17 +436,14 @@ fun TaskProvider<AtomicFUTransformJsTask>.configureJsTask(
}
}

fun Jar.setupJarManifest(multiRelease: Boolean) {
private fun Jar.setupJarManifest(multiRelease: Boolean) {
if (multiRelease) {
manifest.attributes.apply {
put("Multi-Release", "true")
}
}
}

val Project.sourceSets: SourceSetContainer
get() = convention.getPlugin(JavaPluginConvention::class.java).sourceSets

class AtomicFUPluginExtension(pluginVersion: String?) {
var dependenciesVersion = pluginVersion
var transformJvm = true
Expand Down
Empty file.
Loading

0 comments on commit 9d2a3e4

Please sign in to comment.