• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2017-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 
5 package kotlinx.atomicfu.plugin.gradle
6 
7 import kotlinx.atomicfu.transformer.*
8 import org.gradle.api.*
9 import org.gradle.api.file.*
10 import org.gradle.api.internal.*
11 import org.gradle.api.plugins.*
12 import org.gradle.api.tasks.*
13 import org.gradle.api.tasks.compile.*
14 import org.gradle.api.tasks.testing.*
15 import org.gradle.jvm.tasks.*
16 import org.jetbrains.kotlin.gradle.dsl.*
17 import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
18 import org.jetbrains.kotlin.gradle.plugin.*
19 import java.io.*
20 import java.util.*
21 import java.util.concurrent.*
22 import org.jetbrains.kotlin.gradle.targets.js.*
23 import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrTarget
24 import org.jetbrains.kotlin.gradle.tasks.*
25 import org.jetbrains.kotlinx.atomicfu.gradle.*
26 
27 private const val EXTENSION_NAME = "atomicfu"
28 private const val ORIGINAL_DIR_NAME = "originalClassesDir"
29 private const val COMPILE_ONLY_CONFIGURATION = "compileOnly"
30 private const val IMPLEMENTATION_CONFIGURATION = "implementation"
31 private const val TEST_IMPLEMENTATION_CONFIGURATION = "testImplementation"
32 // If the project uses KGP <= 1.6.20, only JS IR compiler plugin is available, and it is turned on via setting this property.
33 // The property is supported for backwards compatibility.
34 private const val ENABLE_JS_IR_TRANSFORMATION_LEGACY = "kotlinx.atomicfu.enableIrTransformation"
35 private const val ENABLE_JS_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJsIrTransformation"
36 private const val ENABLE_JVM_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJvmIrTransformation"
37 
38 open class AtomicFUGradlePlugin : Plugin<Project> {
39     override fun apply(project: Project) = project.run {
40         val pluginVersion = rootProject.buildscript.configurations.findByName("classpath")
41             ?.allDependencies?.find { it.name == "atomicfu-gradle-plugin" }?.version
42         extensions.add(EXTENSION_NAME, AtomicFUPluginExtension(pluginVersion))
43         applyAtomicfuCompilerPlugin()
44         configureDependencies()
45         configureTasks()
46     }
47 }
48 
Projectnull49 private fun Project.configureDependencies() {
50     withPluginWhenEvaluatedDependencies("kotlin") { version ->
51         dependencies.add(
52             if (config.transformJvm) COMPILE_ONLY_CONFIGURATION else IMPLEMENTATION_CONFIGURATION,
53             getAtomicfuDependencyNotation(Platform.JVM, version)
54         )
55         dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JVM, version))
56     }
57     withPluginWhenEvaluatedDependencies("org.jetbrains.kotlin.js") { version ->
58         dependencies.add(
59             if (config.transformJs) COMPILE_ONLY_CONFIGURATION else IMPLEMENTATION_CONFIGURATION,
60             getAtomicfuDependencyNotation(Platform.JS, version)
61         )
62         dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JS, version))
63         addCompilerPluginDependency()
64     }
65     withPluginWhenEvaluatedDependencies("kotlin-multiplatform") { version ->
66         configureMultiplatformPluginDependencies(version)
67     }
68 }
69 
configureTasksnull70 private fun Project.configureTasks() {
71     val config = config
72     withPluginWhenEvaluated("kotlin") {
73         if (config.transformJvm) {
74             // skip transformation task if ir transformation is enabled
75             if (rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)) return@withPluginWhenEvaluated
76             configureJvmTransformation("compileTestKotlin") { sourceSet, transformedDir, originalDir ->
77                 createJvmTransformTask(sourceSet).configureJvmTask(
78                     sourceSet.compileClasspath,
79                     sourceSet.classesTaskName,
80                     transformedDir,
81                     originalDir,
82                     config
83                 )
84             }
85         }
86     }
87     withPluginWhenEvaluated("org.jetbrains.kotlin.js") {
88         if (config.transformJs) configureJsTransformation()
89     }
90     withPluginWhenEvaluated("kotlin-multiplatform") {
91         configureMultiplatformTransformation()
92     }
93 }
94 
95 private data class KotlinVersion(val major: Int, val minor: Int, val patch: Int)
96 
Projectnull97 private fun Project.getKotlinVersion(): KotlinVersion {
98     val kotlinVersion = getKotlinPluginVersion()
99     val (major, minor) = kotlinVersion
100         .split('.')
101         .take(2)
102         .map { it.toInt() }
103     val patch = kotlinVersion.substringAfterLast('.').substringBefore('-').toInt()
104     return KotlinVersion(major, minor, patch)
105 }
106 
KotlinVersionnull107 private fun KotlinVersion.atLeast(major: Int, minor: Int, patch: Int) =
108     this.major == major && (this.minor == minor && this.patch >= patch || this.minor > minor) || this.major > major
109 
110 // kotlinx-atomicfu compiler plugin is available for KGP >= 1.6.20
111 private fun Project.isCompilerPluginAvailable() = getKotlinVersion().atLeast(1, 6, 20)
112 
113 private fun Project.applyAtomicfuCompilerPlugin() {
114     val kotlinVersion = getKotlinVersion()
115     // for KGP >= 1.7.20:
116     // compiler plugin for JS IR is applied via the property `kotlinx.atomicfu.enableJsIrTransformation`
117     // compiler plugin for JVM IR is applied via the property `kotlinx.atomicfu.enableJvmIrTransformation`
118     if (kotlinVersion.atLeast(1, 7, 20)) {
119         plugins.apply(AtomicfuKotlinGradleSubplugin::class.java)
120         extensions.getByType(AtomicfuKotlinGradleSubplugin.AtomicfuKotlinGradleExtension::class.java).apply {
121             isJsIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION)
122             isJvmIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)
123         }
124     } else {
125         // for KGP >= 1.6.20 && KGP <= 1.7.20:
126         // compiler plugin for JS IR is applied via the property `kotlinx.atomicfu.enableIrTransformation`
127         // compiler plugin for JVM IR is not supported yet
128         if (kotlinVersion.atLeast(1, 6, 20)) {
129             if (rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY)) {
130                 plugins.apply(AtomicfuKotlinGradleSubplugin::class.java)
131             }
132         }
133     }
134 }
135 
Projectnull136 private fun Project.getBooleanProperty(name: String) =
137     rootProject.findProperty(name)?.toString()?.toBooleanStrict() ?: false
138 
139 private fun String.toBooleanStrict(): Boolean = when (this) {
140     "true" -> true
141     "false" -> false
142     else -> throw IllegalArgumentException("The string doesn't represent a boolean value: $this")
143 }
144 
Projectnull145 private fun Project.needsJsIrTransformation(target: KotlinTarget): Boolean =
146     (rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION) || rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY))
147             && target.isJsIrTarget()
148 
149 private fun KotlinTarget.isJsIrTarget() = (this is KotlinJsTarget && this.irTarget != null) || this is KotlinJsIrTarget
150 
151 private fun Project.addCompilerPluginDependency() {
152     if (isCompilerPluginAvailable()) {
153         withKotlinTargets { target ->
154             if (needsJsIrTransformation(target)) {
155                 target.compilations.forEach { kotlinCompilation ->
156                     kotlinCompilation.dependencies {
157                         if (getKotlinVersion().atLeast(1, 7, 10)) {
158                             // since Kotlin 1.7.10 we can add `atomicfu-runtime` dependency directly
159                             implementation("org.jetbrains.kotlin:kotlinx-atomicfu-runtime:${getKotlinPluginVersion()}")
160                         } else {
161                             // add atomicfu compiler plugin dependency
162                             // to provide the `atomicfu-runtime` library used during compiler plugin transformation
163                             implementation("org.jetbrains.kotlin:atomicfu:${getKotlinPluginVersion()}")
164                         }
165                     }
166                 }
167             }
168         }
169     }
170 }
171 
172 private enum class Platform(val suffix: String) {
173     JVM("-jvm"),
174     JS("-js"),
175     NATIVE(""),
176     MULTIPLATFORM("")
177 }
178 
179 private enum class CompilationType { MAIN, TEST }
180 
compilationNameToTypenull181 private fun String.compilationNameToType(): CompilationType? = when (this) {
182     KotlinCompilation.MAIN_COMPILATION_NAME -> CompilationType.MAIN
183     KotlinCompilation.TEST_COMPILATION_NAME -> CompilationType.TEST
184     else -> null
185 }
186 
sourceSetNameToTypenull187 private fun String.sourceSetNameToType(): CompilationType? = when (this) {
188     SourceSet.MAIN_SOURCE_SET_NAME -> CompilationType.MAIN
189     SourceSet.TEST_SOURCE_SET_NAME -> CompilationType.TEST
190     else -> null
191 }
192 
193 private val Project.config: AtomicFUPluginExtension
194     get() = extensions.findByName(EXTENSION_NAME) as? AtomicFUPluginExtension ?: AtomicFUPluginExtension(null)
195 
getAtomicfuDependencyNotationnull196 private fun getAtomicfuDependencyNotation(platform: Platform, version: String): String =
197     "org.jetbrains.kotlinx:atomicfu${platform.suffix}:$version"
198 
199 // Note "afterEvaluate" does nothing when the project is already in executed state, so we need
200 // a special check for this case
201 fun <T> Project.whenEvaluated(fn: Project.() -> T) {
202     if (state.executed) {
203         fn()
204     } else {
205         afterEvaluate { fn() }
206     }
207 }
208 
Projectnull209 fun Project.withPluginWhenEvaluated(plugin: String, fn: Project.() -> Unit) {
210     pluginManager.withPlugin(plugin) { whenEvaluated(fn) }
211 }
212 
Projectnull213 fun Project.withPluginWhenEvaluatedDependencies(plugin: String, fn: Project.(version: String) -> Unit) {
214     withPluginWhenEvaluated(plugin) {
215         config.dependenciesVersion?.let { fn(it) }
216     }
217 }
218 
Projectnull219 fun Project.withKotlinTargets(fn: (KotlinTarget) -> Unit) {
220     extensions.findByType(KotlinTargetsContainer::class.java)?.let { kotlinExtension ->
221         // find all compilations given sourceSet belongs to
222         kotlinExtension.targets
223             .all { target -> fn(target) }
224     }
225 }
226 
setFriendPathsnull227 private fun KotlinCompile<*>.setFriendPaths(friendPathsFileCollection: FileCollection) {
228     val (majorVersion, minorVersion) = project.getKotlinPluginVersion()
229         .split('.')
230         .take(2)
231         .map { it.toInt() }
232     if (majorVersion == 1 && minorVersion < 7) {
233         (this as? AbstractKotlinCompile<*>)?.friendPaths?.from(friendPathsFileCollection)
234     } else {
235         // See KT-KT-54167 (works only for KGP 1.7.0+)
236         (this as BaseKotlinCompile).friendPaths.from(friendPathsFileCollection)
237     }
238 }
239 
Projectnull240 fun Project.configureJsTransformation() =
241     configureTransformationForTarget((kotlinExtension as KotlinJsProjectExtension).js())
242 
243 fun Project.configureMultiplatformTransformation() =
244     withKotlinTargets { target ->
245         if (target.platformType == KotlinPlatformType.common || target.platformType == KotlinPlatformType.native) {
246             return@withKotlinTargets // skip the common & native targets -- no transformation for them
247         }
248         configureTransformationForTarget(target)
249     }
250 
Projectnull251 private fun Project.configureTransformationForTarget(target: KotlinTarget) {
252     val originalDirsByCompilation = hashMapOf<KotlinCompilation<*>, FileCollection>()
253     val config = config
254     target.compilations.all compilations@{ compilation ->
255         val compilationType = compilation.name.compilationNameToType()
256             ?: return@compilations // skip unknown compilations
257         val classesDirs = compilation.output.classesDirs
258         // make copy of original classes directory
259         val originalClassesDirs: FileCollection =
260             project.files(classesDirs.from.toTypedArray()).filter { it.exists() }
261         originalDirsByCompilation[compilation] = originalClassesDirs
262         val transformedClassesDir =
263             project.buildDir.resolve("classes/atomicfu/${target.name}/${compilation.name}")
264         val transformTask = when (target.platformType) {
265             KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> {
266                 // skip transformation task if transformation is turned off or ir transformation is enabled
267                 if (!config.transformJvm || rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)) return@compilations
268                 project.createJvmTransformTask(compilation).configureJvmTask(
269                     compilation.compileDependencyFiles,
270                     compilation.compileAllTaskName,
271                     transformedClassesDir,
272                     originalClassesDirs,
273                     config
274                 )
275             }
276             KotlinPlatformType.js -> {
277                 // skip when js transformation is not needed or when IR is transformed
278                 if (!config.transformJs || (needsJsIrTransformation(target))) {
279                     return@compilations
280                 }
281                 project.createJsTransformTask(compilation).configureJsTask(
282                     compilation.compileAllTaskName,
283                     transformedClassesDir,
284                     originalClassesDirs,
285                     config
286                 )
287             }
288             else -> error("Unsupported transformation platform '${target.platformType}'")
289         }
290         //now transformTask is responsible for compiling this source set into the classes directory
291         classesDirs.setFrom(transformedClassesDir)
292         classesDirs.builtBy(transformTask)
293         (tasks.findByName(target.artifactsTaskName) as? Jar)?.apply {
294             setupJarManifest(multiRelease = config.jvmVariant.toJvmVariant() == JvmVariant.BOTH)
295         }
296         // test should compile and run against original production binaries
297         if (compilationType == CompilationType.TEST) {
298             val mainCompilation =
299                 compilation.target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
300             val originalMainClassesDirs = project.files(
301                 // use Callable because there is no guarantee that main is configured before test
302                 Callable { originalDirsByCompilation[mainCompilation]!! }
303             )
304 
305             // KGP >= 1.7.0 has breaking changes in task hierarchy:
306             // https://youtrack.jetbrains.com/issue/KT-32805#focus=Comments-27-5915479.0-0
307             val (majorVersion, minorVersion) = getKotlinPluginVersion()
308                 .split('.')
309                 .take(2)
310                 .map { it.toInt() }
311             if (majorVersion == 1 && minorVersion < 7) {
312                 (tasks.findByName(compilation.compileKotlinTaskName) as? AbstractCompile)?.classpath =
313                     originalMainClassesDirs + compilation.compileDependencyFiles - mainCompilation.output.classesDirs
314             } else {
315                 (tasks.findByName(compilation.compileKotlinTaskName) as? AbstractKotlinCompileTool<*>)
316                     ?.libraries
317                     ?.setFrom(
318                         originalMainClassesDirs + compilation.compileDependencyFiles - mainCompilation.output.classesDirs
319                     )
320             }
321 
322             (tasks.findByName("${target.name}${compilation.name.capitalize()}") as? Test)?.classpath =
323                 originalMainClassesDirs + (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyFiles - mainCompilation.output.classesDirs
324 
325             compilation.compileKotlinTask.setFriendPaths(originalMainClassesDirs)
326         }
327     }
328 }
329 
Projectnull330 fun Project.sourceSetsByCompilation(): Map<KotlinSourceSet, List<KotlinCompilation<*>>> {
331     val sourceSetsByCompilation = hashMapOf<KotlinSourceSet, MutableList<KotlinCompilation<*>>>()
332     withKotlinTargets { target ->
333         target.compilations.forEach { compilation ->
334             compilation.allKotlinSourceSets.forEach { sourceSet ->
335                 sourceSetsByCompilation.getOrPut(sourceSet) { mutableListOf() }.add(compilation)
336             }
337         }
338     }
339     return sourceSetsByCompilation
340 }
341 
Projectnull342 fun Project.configureMultiplatformPluginDependencies(version: String) {
343     if (rootProject.getBooleanProperty("kotlin.mpp.enableGranularSourceSetsMetadata")) {
344         addCompilerPluginDependency()
345         val mainConfigurationName = project.extensions.getByType(KotlinMultiplatformExtension::class.java).sourceSets
346             .getByName(KotlinSourceSet.COMMON_MAIN_SOURCE_SET_NAME)
347             .compileOnlyConfigurationName
348         dependencies.add(mainConfigurationName, getAtomicfuDependencyNotation(Platform.MULTIPLATFORM, version))
349 
350         val testConfigurationName = project.extensions.getByType(KotlinMultiplatformExtension::class.java).sourceSets
351             .getByName(KotlinSourceSet.COMMON_TEST_SOURCE_SET_NAME)
352             .implementationConfigurationName
353         dependencies.add(testConfigurationName, getAtomicfuDependencyNotation(Platform.MULTIPLATFORM, version))
354 
355         // For each source set that is only used in Native compilations, add an implementation dependency so that it
356         // gets published and is properly consumed as a transitive dependency:
357         sourceSetsByCompilation().forEach { (sourceSet, compilations) ->
358             val isSharedNativeSourceSet = compilations.all {
359                 it.platformType == KotlinPlatformType.common || it.platformType == KotlinPlatformType.native
360             }
361             if (isSharedNativeSourceSet) {
362                 val configuration = sourceSet.implementationConfigurationName
363                 dependencies.add(configuration, getAtomicfuDependencyNotation(Platform.MULTIPLATFORM, version))
364             }
365         }
366     } else {
367         sourceSetsByCompilation().forEach { (sourceSet, compilations) ->
368             addCompilerPluginDependency()
369             val platformTypes = compilations.map { it.platformType }.toSet()
370             val compilationNames = compilations.map { it.compilationName }.toSet()
371             if (compilationNames.size != 1)
372                 error("Source set '${sourceSet.name}' of project '$name' is part of several compilations $compilationNames")
373             val compilationType = compilationNames.single().compilationNameToType()
374                 ?: return@forEach // skip unknown compilations
375             val platform =
376                 if (platformTypes.size > 1) Platform.MULTIPLATFORM else // mix of platform types -> "common"
377                     when (platformTypes.single()) {
378                         KotlinPlatformType.common -> Platform.MULTIPLATFORM
379                         KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> Platform.JVM
380                         KotlinPlatformType.js -> Platform.JS
381                         KotlinPlatformType.native, KotlinPlatformType.wasm -> Platform.NATIVE
382                     }
383             val configurationName = when {
384                 // impl dependency for native (there is no transformation)
385                 platform == Platform.NATIVE -> sourceSet.implementationConfigurationName
386                 // compileOnly dependency for main compilation (commonMain, jvmMain, jsMain)
387                 compilationType == CompilationType.MAIN -> sourceSet.compileOnlyConfigurationName
388                 // impl dependency for tests
389                 else -> sourceSet.implementationConfigurationName
390             }
391             dependencies.add(configurationName, getAtomicfuDependencyNotation(platform, version))
392         }
393     }
394 }
395 
Projectnull396 fun Project.configureJvmTransformation(
397     testTaskName: String,
398     createTransformTask: (sourceSet: SourceSet, transformedDir: File, originalDir: FileCollection) -> Task
399 ) {
400     val config = config
401     sourceSets.all { sourceSet ->
402         val compilationType = sourceSet.name.sourceSetNameToType()
403             ?: return@all // skip unknown types
404         val classesDirs = (sourceSet.output.classesDirs as ConfigurableFileCollection).from as Collection<Any>
405         // make copy of original classes directory
406         val originalClassesDirs: FileCollection = project.files(classesDirs.toTypedArray()).filter { it.exists() }
407         (sourceSet as ExtensionAware).extensions.add(ORIGINAL_DIR_NAME, originalClassesDirs)
408         val transformedClassesDir =
409             project.buildDir.resolve("classes/atomicfu/${sourceSet.name}")
410         // make transformedClassesDir the source path for output.classesDirs
411         (sourceSet.output.classesDirs as ConfigurableFileCollection).setFrom(transformedClassesDir)
412         val transformTask = createTransformTask(sourceSet, transformedClassesDir, originalClassesDirs)
413         //now transformTask is responsible for compiling this source set into the classes directory
414         sourceSet.compiledBy(transformTask)
415         (tasks.findByName(sourceSet.jarTaskName) as? Jar)?.apply {
416             setupJarManifest(multiRelease = config.jvmVariant.toJvmVariant() == JvmVariant.BOTH)
417         }
418         // test should compile and run against original production binaries
419         if (compilationType == CompilationType.TEST) {
420             val mainSourceSet = sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME)
421             val originalMainClassesDirs = project.files(
422                 // use Callable because there is no guarantee that main is configured before test
423                 Callable { (mainSourceSet as ExtensionAware).extensions.getByName(ORIGINAL_DIR_NAME) as FileCollection }
424             )
425 
426             (tasks.findByName(testTaskName) as? AbstractCompile)?.run {
427                 classpath =
428                     originalMainClassesDirs + sourceSet.compileClasspath - mainSourceSet.output.classesDirs
429 
430                 (this as? KotlinCompile<*>)?.setFriendPaths(originalMainClassesDirs)
431             }
432 
433             // todo: fix test runtime classpath for JS?
434             (tasks.findByName(JavaPlugin.TEST_TASK_NAME) as? Test)?.classpath =
435                 originalMainClassesDirs + sourceSet.runtimeClasspath - mainSourceSet.output.classesDirs
436         }
437     }
438 }
439 
Stringnull440 fun String.toJvmVariant(): JvmVariant = enumValueOf(toUpperCase(Locale.US))
441 
442 fun Project.createJvmTransformTask(compilation: KotlinCompilation<*>): AtomicFUTransformTask =
443     tasks.create(
444         "transform${compilation.target.name.capitalize()}${compilation.name.capitalize()}Atomicfu",
445         AtomicFUTransformTask::class.java
446     )
447 
448 fun Project.createJsTransformTask(compilation: KotlinCompilation<*>): AtomicFUTransformJsTask =
449     tasks.create(
450         "transform${compilation.target.name.capitalize()}${compilation.name.capitalize()}Atomicfu",
451         AtomicFUTransformJsTask::class.java
452     )
453 
454 fun Project.createJvmTransformTask(sourceSet: SourceSet): AtomicFUTransformTask =
455     tasks.create(sourceSet.getTaskName("transform", "atomicfuClasses"), AtomicFUTransformTask::class.java)
456 
457 fun AtomicFUTransformTask.configureJvmTask(
458     classpath: FileCollection,
459     classesTaskName: String,
460     transformedClassesDir: File,
461     originalClassesDir: FileCollection,
462     config: AtomicFUPluginExtension
463 ): ConventionTask =
464     apply {
465         dependsOn(classesTaskName)
466         classPath = classpath
467         inputFiles = originalClassesDir
468         outputDir = transformedClassesDir
469         jvmVariant = config.jvmVariant
470         verbose = config.verbose
471     }
472 
AtomicFUTransformJsTasknull473 fun AtomicFUTransformJsTask.configureJsTask(
474     classesTaskName: String,
475     transformedClassesDir: File,
476     originalClassesDir: FileCollection,
477     config: AtomicFUPluginExtension
478 ): ConventionTask =
479     apply {
480         dependsOn(classesTaskName)
481         inputFiles = originalClassesDir
482         outputDir = transformedClassesDir
483         verbose = config.verbose
484     }
485 
setupJarManifestnull486 fun Jar.setupJarManifest(multiRelease: Boolean) {
487     if (multiRelease) {
488         manifest.attributes.apply {
489             put("Multi-Release", "true")
490         }
491     }
492 }
493 
494 val Project.sourceSets: SourceSetContainer
495     get() = convention.getPlugin(JavaPluginConvention::class.java).sourceSets
496 
497 class AtomicFUPluginExtension(pluginVersion: String?) {
498     var dependenciesVersion = pluginVersion
499     var transformJvm = true
500     var transformJs = true
501     var jvmVariant: String = "FU"
502     var verbose: Boolean = false
503 }
504 
505 @CacheableTask
506 open class AtomicFUTransformTask : ConventionTask() {
507     @PathSensitive(PathSensitivity.RELATIVE)
508     @InputFiles
509     lateinit var inputFiles: FileCollection
510 
511     @OutputDirectory
512     lateinit var outputDir: File
513 
514     @Classpath
515     @InputFiles
516     lateinit var classPath: FileCollection
517 
518     @Input
519     var jvmVariant = "FU"
520 
521     @Input
522     var verbose = false
523 
524     @TaskAction
transformnull525     fun transform() {
526         val cp = classPath.files.map { it.absolutePath }
527         inputFiles.files.forEach { inputDir ->
528             AtomicFUTransformer(cp, inputDir, outputDir).let { t ->
529                 t.jvmVariant = jvmVariant.toJvmVariant()
530                 t.verbose = verbose
531                 t.transform()
532             }
533         }
534     }
535 }
536 
537 @CacheableTask
538 open class AtomicFUTransformJsTask : ConventionTask() {
539     @PathSensitive(PathSensitivity.RELATIVE)
540     @InputFiles
541     lateinit var inputFiles: FileCollection
542 
543     @OutputDirectory
544     lateinit var outputDir: File
545 
546     @Input
547     var verbose = false
548 
549     @TaskAction
transformnull550     fun transform() {
551         inputFiles.files.forEach { inputDir ->
552             AtomicFUTransformerJS(inputDir, outputDir).let { t ->
553                 t.verbose = verbose
554                 t.transform()
555             }
556         }
557     }
558 }
559