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.provider.Provider
12 import org.gradle.api.provider.ProviderFactory
13 import org.gradle.api.tasks.*
14 import org.gradle.api.tasks.testing.*
15 import org.gradle.jvm.tasks.*
16 import org.gradle.util.*
17 import org.jetbrains.kotlin.gradle.dsl.*
18 import org.jetbrains.kotlin.gradle.dsl.KotlinCompile
19 import org.jetbrains.kotlin.gradle.plugin.*
20 import java.io.*
21 import java.util.*
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 import javax.inject.Inject
27 
28 private const val EXTENSION_NAME = "atomicfu"
29 private const val ORIGINAL_DIR_NAME = "originalClassesDir"
30 private const val COMPILE_ONLY_CONFIGURATION = "compileOnly"
31 private const val IMPLEMENTATION_CONFIGURATION = "implementation"
32 private const val TEST_IMPLEMENTATION_CONFIGURATION = "testImplementation"
33 // If the project uses KGP <= 1.6.20, only JS IR compiler plugin is available, and it is turned on via setting this property.
34 // The property is supported for backwards compatibility.
35 private const val ENABLE_JS_IR_TRANSFORMATION_LEGACY = "kotlinx.atomicfu.enableIrTransformation"
36 private const val ENABLE_JS_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJsIrTransformation"
37 private const val ENABLE_JVM_IR_TRANSFORMATION = "kotlinx.atomicfu.enableJvmIrTransformation"
38 private const val ENABLE_NATIVE_IR_TRANSFORMATION = "kotlinx.atomicfu.enableNativeIrTransformation"
39 private const val MIN_SUPPORTED_GRADLE_VERSION = "7.0"
40 private const val MIN_SUPPORTED_KGP_VERSION = "1.7.0"
41 
42 open class AtomicFUGradlePlugin : Plugin<Project> {
43     override fun apply(project: Project) = project.run {
44         checkCompatibility()
45         val pluginVersion = rootProject.buildscript.configurations.findByName("classpath")
46             ?.allDependencies?.find { it.name == "atomicfu-gradle-plugin" }?.version
47         extensions.add(EXTENSION_NAME, AtomicFUPluginExtension(pluginVersion))
48         applyAtomicfuCompilerPlugin()
49         configureDependencies()
50         configureTasks()
51     }
52 }
53 
Projectnull54 private fun Project.checkCompatibility() {
55     val currentGradleVersion = GradleVersion.current()
56     val kotlinVersion = getKotlinVersion()
57     val minSupportedVersion = GradleVersion.version(MIN_SUPPORTED_GRADLE_VERSION)
58     if (currentGradleVersion < minSupportedVersion) {
59         throw GradleException(
60             "The current Gradle version is not compatible with Atomicfu gradle plugin. " +
61                     "Please use Gradle $MIN_SUPPORTED_GRADLE_VERSION or newer, or the previous version of Atomicfu gradle plugin."
62         )
63     }
64     if (!kotlinVersion.atLeast(1, 7, 0)) {
65         throw GradleException(
66             "The current Kotlin gradle plugin version is not compatible with Atomicfu gradle plugin. " +
67                     "Please use Kotlin $MIN_SUPPORTED_KGP_VERSION or newer, or the previous version of Atomicfu gradle plugin."
68         )
69     }
70 }
71 
Projectnull72 private fun Project.applyAtomicfuCompilerPlugin() {
73     val kotlinVersion = getKotlinVersion()
74     // for KGP >= 1.7.20:
75     // compiler plugin for JS IR is applied via the property `kotlinx.atomicfu.enableJsIrTransformation`
76     // compiler plugin for JVM IR is applied via the property `kotlinx.atomicfu.enableJvmIrTransformation`
77     if (kotlinVersion.atLeast(1, 7, 20)) {
78         plugins.apply(AtomicfuKotlinGradleSubplugin::class.java)
79         extensions.getByType(AtomicfuKotlinGradleSubplugin.AtomicfuKotlinGradleExtension::class.java).apply {
80             isJsIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION)
81             isJvmIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION)
82             if (kotlinVersion.atLeast(1, 9, 20)) {
83                 // Native IR transformation is available since Kotlin 1.9.20
84                 isNativeIrTransformationEnabled = rootProject.getBooleanProperty(ENABLE_NATIVE_IR_TRANSFORMATION)
85             }
86         }
87     } else {
88         // for KGP >= 1.6.20 && KGP <= 1.7.20:
89         // compiler plugin for JS IR is applied via the property `kotlinx.atomicfu.enableIrTransformation`
90         // compiler plugin for JVM IR is not supported yet
91         if (kotlinVersion.atLeast(1, 6, 20)) {
92             if (rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY)) {
93                 plugins.apply(AtomicfuKotlinGradleSubplugin::class.java)
94             }
95         }
96     }
97 }
98 
Projectnull99 private fun Project.configureDependencies() {
100     withPluginWhenEvaluatedDependencies("kotlin") { version ->
101         dependencies.add(
102             if (config.transformJvm) COMPILE_ONLY_CONFIGURATION else IMPLEMENTATION_CONFIGURATION,
103             getAtomicfuDependencyNotation(Platform.JVM, version)
104         )
105         dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JVM, version))
106     }
107     withPluginWhenEvaluatedDependencies("org.jetbrains.kotlin.js") { version ->
108         dependencies.add(
109             if (config.transformJs) COMPILE_ONLY_CONFIGURATION else IMPLEMENTATION_CONFIGURATION,
110             getAtomicfuDependencyNotation(Platform.JS, version)
111         )
112         dependencies.add(TEST_IMPLEMENTATION_CONFIGURATION, getAtomicfuDependencyNotation(Platform.JS, version))
113         addJsCompilerPluginRuntimeDependency()
114     }
115     withPluginWhenEvaluatedDependencies("kotlin-multiplatform") { version ->
116         addJsCompilerPluginRuntimeDependency()
117         configureMultiplatformPluginDependencies(version)
118     }
119 }
120 
Projectnull121 private fun Project.configureMultiplatformPluginDependencies(version: String) {
122     val multiplatformExtension = kotlinExtension as? KotlinMultiplatformExtension ?: error("Expected kotlin multiplatform extension")
123     val atomicfuDependency = "org.jetbrains.kotlinx:atomicfu:$version"
124     multiplatformExtension.sourceSets.getByName("commonMain").dependencies {
125         compileOnly(atomicfuDependency)
126     }
127     multiplatformExtension.sourceSets.getByName("commonTest").dependencies {
128         implementation(atomicfuDependency)
129     }
130     // Include atomicfu as a dependency for publication when transformation for the target is disabled
131     multiplatformExtension.targets.all { target ->
132         if (isTransformationDisabled(target)) {
133             target.compilations.all { compilation ->
134                 compilation
135                     .defaultSourceSet
136                     .dependencies {
137                         implementation(atomicfuDependency)
138                     }
139             }
140         }
141     }
142 }
143 
144 private data class KotlinVersion(val major: Int, val minor: Int, val patch: Int)
145 
Projectnull146 private fun Project.getKotlinVersion(): KotlinVersion {
147     val kotlinVersion = getKotlinPluginVersion()
148     val (major, minor) = kotlinVersion
149         .split('.')
150         .take(2)
151         .map { it.toInt() }
152     val patch = kotlinVersion.substringAfterLast('.').substringBefore('-').toInt()
153     return KotlinVersion(major, minor, patch)
154 }
155 
KotlinVersionnull156 private fun KotlinVersion.atLeast(major: Int, minor: Int, patch: Int) =
157     this.major == major && (this.minor == minor && this.patch >= patch || this.minor > minor) || this.major > major
158 
159 // kotlinx-atomicfu compiler plugin is available for KGP >= 1.6.20
160 private fun Project.isCompilerPluginAvailable() = getKotlinVersion().atLeast(1, 6, 20)
161 
162 private fun Project.getBooleanProperty(name: String) =
163     rootProject.findProperty(name)?.toString()?.toBooleanStrict() ?: false
164 
165 private fun String.toBooleanStrict(): Boolean = when (this) {
166     "true" -> true
167     "false" -> false
168     else -> throw IllegalArgumentException("The string doesn't represent a boolean value: $this")
169 }
170 
Projectnull171 private fun Project.needsJsIrTransformation(target: KotlinTarget): Boolean =
172     (rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION) || rootProject.getBooleanProperty(ENABLE_JS_IR_TRANSFORMATION_LEGACY))
173             && target.isJsIrTarget()
174 
175 private fun Project.needsJvmIrTransformation(target: KotlinTarget): Boolean =
176     rootProject.getBooleanProperty(ENABLE_JVM_IR_TRANSFORMATION) &&
177             (target.platformType == KotlinPlatformType.jvm || target.platformType == KotlinPlatformType.androidJvm)
178 
179 private fun Project.needsNativeIrTransformation(target: KotlinTarget): Boolean =
180     rootProject.getBooleanProperty(ENABLE_NATIVE_IR_TRANSFORMATION) &&
181             (target.platformType == KotlinPlatformType.native)
182 
183 
184 private fun KotlinTarget.isJsIrTarget() =
185     (this is KotlinJsTarget && this.irTarget != null) ||
186             (this is KotlinJsIrTarget && this.platformType != KotlinPlatformType.wasm)
187 
188 private fun Project.isTransformationDisabled(target: KotlinTarget): Boolean {
189     val platformType = target.platformType
190     return !config.transformJvm && (platformType == KotlinPlatformType.jvm || platformType == KotlinPlatformType.androidJvm) ||
191             !config.transformJs && platformType == KotlinPlatformType.js ||
192             platformType == KotlinPlatformType.wasm ||
193             !needsNativeIrTransformation(target) && platformType == KotlinPlatformType.native
194 }
195 
196 // Adds kotlinx-atomicfu-runtime as an implementation dependency to the JS IR target:
197 // it provides inline methods that replace atomic methods from the library and is needed at runtime.
Projectnull198 private fun Project.addJsCompilerPluginRuntimeDependency() {
199     if (isCompilerPluginAvailable()) {
200         withKotlinTargets { target ->
201             if (target.isJsIrTarget()) {
202                 target.compilations.forEach { kotlinCompilation ->
203                     kotlinCompilation.dependencies {
204                         if (getKotlinVersion().atLeast(1, 7, 10)) {
205                             // since Kotlin 1.7.10 `kotlinx-atomicfu-runtime` is published and should be added directly
206                             implementation("org.jetbrains.kotlin:kotlinx-atomicfu-runtime:${getKotlinPluginVersion()}")
207                         } else {
208                             implementation("org.jetbrains.kotlin:atomicfu:${getKotlinPluginVersion()}")
209                         }
210                     }
211                 }
212             }
213         }
214     }
215 }
216 
217 private enum class Platform(val suffix: String) {
218     JVM("-jvm"),
219     JS("-js"),
220     NATIVE(""),
221     MULTIPLATFORM("")
222 }
223 
224 private enum class CompilationType { MAIN, TEST }
225 
compilationNameToTypenull226 private fun String.compilationNameToType(): CompilationType? = when (this) {
227     KotlinCompilation.MAIN_COMPILATION_NAME -> CompilationType.MAIN
228     KotlinCompilation.TEST_COMPILATION_NAME -> CompilationType.TEST
229     else -> null
230 }
231 
232 private val Project.config: AtomicFUPluginExtension
233     get() = extensions.findByName(EXTENSION_NAME) as? AtomicFUPluginExtension ?: AtomicFUPluginExtension(null)
234 
getAtomicfuDependencyNotationnull235 private fun getAtomicfuDependencyNotation(platform: Platform, version: String): String =
236     "org.jetbrains.kotlinx:atomicfu${platform.suffix}:$version"
237 
238 // Note "afterEvaluate" does nothing when the project is already in executed state, so we need
239 // a special check for this case
240 private fun <T> Project.whenEvaluated(fn: Project.() -> T) {
241     if (state.executed) {
242         fn()
243     } else {
244         afterEvaluate { fn() }
245     }
246 }
247 
Projectnull248 private fun Project.withPluginWhenEvaluated(plugin: String, fn: Project.() -> Unit) {
249     pluginManager.withPlugin(plugin) { whenEvaluated(fn) }
250 }
251 
Projectnull252 private fun Project.withPluginWhenEvaluatedDependencies(plugin: String, fn: Project.(version: String) -> Unit) {
253     withPluginWhenEvaluated(plugin) {
254         config.dependenciesVersion?.let { fn(it) }
255     }
256 }
257 
Projectnull258 private fun Project.withKotlinTargets(fn: (KotlinTarget) -> Unit) {
259     extensions.findByType(KotlinTargetsContainer::class.java)?.let { kotlinExtension ->
260         // find all compilations given sourceSet belongs to
261         kotlinExtension.targets
262             .all { target -> fn(target) }
263     }
264 }
265 
setFriendPathsnull266 private fun KotlinCompile<*>.setFriendPaths(friendPathsFileCollection: FileCollection) {
267     val (majorVersion, minorVersion) = project.getKotlinPluginVersion()
268         .split('.')
269         .take(2)
270         .map { it.toInt() }
271     if (majorVersion == 1 && minorVersion < 7) {
272         (this as? AbstractKotlinCompile<*>)?.friendPaths?.from(friendPathsFileCollection)
273     } else {
274         // See KT-KT-54167 (works only for KGP 1.7.0+)
275         (this as BaseKotlinCompile).friendPaths.from(friendPathsFileCollection)
276     }
277 }
278 
configureTasksnull279 private fun Project.configureTasks() {
280     val config = config
281     withPluginWhenEvaluated("kotlin") {
282         if (config.transformJvm) configureJvmTransformation()
283     }
284     withPluginWhenEvaluated("org.jetbrains.kotlin.js") {
285         if (config.transformJs) configureJsTransformation()
286     }
287     withPluginWhenEvaluated("kotlin-multiplatform") {
288         configureMultiplatformTransformation()
289     }
290 }
291 
Projectnull292 private fun Project.configureJvmTransformation() {
293     if (kotlinExtension is KotlinJvmProjectExtension || kotlinExtension is KotlinAndroidProjectExtension) {
294         val target = (kotlinExtension as KotlinSingleTargetExtension<*>).target
295         if (!needsJvmIrTransformation(target)) {
296             configureTransformationForTarget(target)
297         }
298     }
299 }
300 
Projectnull301 private fun Project.configureJsTransformation() {
302     val target = (kotlinExtension as KotlinJsProjectExtension).js()
303     if (!needsJsIrTransformation(target)) {
304         configureTransformationForTarget(target)
305     }
306 }
307 
Projectnull308 private fun Project.configureMultiplatformTransformation() =
309     withKotlinTargets { target ->
310         // Skip transformation for common, native and wasm targets or in case IR transformation by the compiler plugin is enabled (for JVM or JS targets)
311         if (target.platformType == KotlinPlatformType.common ||
312             target.platformType == KotlinPlatformType.native ||
313             target.platformType == KotlinPlatformType.wasm ||
314             needsJvmIrTransformation(target) || needsJsIrTransformation(target)
315            ) {
316             return@withKotlinTargets
317         }
318         configureTransformationForTarget(target)
319     }
320 
Projectnull321 private fun Project.configureTransformationForTarget(target: KotlinTarget) {
322     val originalDirsByCompilation = hashMapOf<KotlinCompilation<*>, FileCollection>()
323     val config = config
324     target.compilations.all compilations@{ compilation ->
325         val compilationType = compilation.name.compilationNameToType()
326             ?: return@compilations // skip unknown compilations
327         val classesDirs = compilation.output.classesDirs
328         // make copy of original classes directory
329         @Suppress("UNCHECKED_CAST")
330         val compilationTask = compilation.compileTaskProvider as TaskProvider<KotlinCompileTool>
331         val originalDestinationDirectory = project.layout.buildDirectory
332             .dir("classes/atomicfu-orig/${target.name}/${compilation.name}")
333         compilationTask.configure {
334             if (it is Kotlin2JsCompile) {
335                 @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE")
336                 it.defaultDestinationDirectory.value(originalDestinationDirectory)
337             } else {
338                 it.destinationDirectory.value(originalDestinationDirectory)
339             }
340         }
341         val originalClassesDirs: FileCollection = project.objects.fileCollection().from(
342             compilationTask.flatMap { it.destinationDirectory }
343         )
344         originalDirsByCompilation[compilation] = originalClassesDirs
345         val transformedClassesDir = project.layout.buildDirectory
346             .dir("classes/atomicfu/${target.name}/${compilation.name}")
347         val transformTask = when (target.platformType) {
348             KotlinPlatformType.jvm, KotlinPlatformType.androidJvm -> {
349                 // create transformation task only if transformation is required and JVM IR compiler transformation is not enabled
350                 if (config.transformJvm) {
351                     project.registerJvmTransformTask(compilation)
352                         .configureJvmTask(
353                             compilation.compileDependencyFiles,
354                             compilation.compileAllTaskName,
355                             transformedClassesDir,
356                             originalClassesDirs,
357                             config
358                         )
359                         .also {
360                             compilation.defaultSourceSet.kotlin.compiledBy(it, AtomicFUTransformTask::destinationDirectory)
361                         }
362                 } else null
363             }
364             KotlinPlatformType.js -> {
365                 // create transformation task only if transformation is required and JS IR compiler transformation is not enabled
366                 if (config.transformJs && !needsJsIrTransformation(target)) {
367                     project.registerJsTransformTask(compilation)
368                         .configureJsTask(
369                             compilation.compileAllTaskName,
370                             transformedClassesDir,
371                             originalClassesDirs,
372                             config
373                         )
374                         .also {
375                             compilation.defaultSourceSet.kotlin.compiledBy(it, AtomicFUTransformJsTask::destinationDirectory)
376                         }
377                 } else null
378             }
379             else -> error("Unsupported transformation platform '${target.platformType}'")
380         }
381         if (transformTask != null) {
382             //now transformTask is responsible for compiling this source set into the classes directory
383             compilation.defaultSourceSet.kotlin.destinationDirectory.value(transformedClassesDir)
384             classesDirs.setFrom(transformedClassesDir)
385             classesDirs.setBuiltBy(listOf(transformTask))
386             tasks.withType(Jar::class.java).configureEach {
387                 if (name == target.artifactsTaskName) {
388                     it.setupJarManifest(multiRelease = config.jvmVariant.toJvmVariant() == JvmVariant.BOTH)
389                 }
390             }
391         }
392         // test should compile and run against original production binaries
393         if (compilationType == CompilationType.TEST) {
394             val mainCompilation =
395                 compilation.target.compilations.getByName(KotlinCompilation.MAIN_COMPILATION_NAME)
396             val originalMainClassesDirs = project.objects.fileCollection().from(
397                 mainCompilation.compileTaskProvider.flatMap { (it as KotlinCompileTool).destinationDirectory }
398             )
399             // compilationTask.destinationDirectory was changed from build/classes/kotlin/main to build/classes/atomicfu-orig/main,
400             // so we need to update libraries
401             (tasks.findByName(compilation.compileKotlinTaskName) as? AbstractKotlinCompileTool<*>)
402                 ?.libraries
403                 ?.setFrom(
404                     originalMainClassesDirs + compilation.compileDependencyFiles
405                 )
406             if (transformTask != null) {
407                 // if transform task was not created, then originalMainClassesDirs == mainCompilation.output.classesDirs
408                 (tasks.findByName("${target.name}${compilation.name.capitalize()}") as? Test)?.classpath =
409                     originalMainClassesDirs + (compilation as KotlinCompilationToRunnableFiles).runtimeDependencyFiles - mainCompilation.output.classesDirs
410             }
411             compilation.compileKotlinTask.setFriendPaths(originalMainClassesDirs)
412         }
413     }
414 }
415 
Stringnull416 private fun String.toJvmVariant(): JvmVariant = enumValueOf(toUpperCase(Locale.US))
417 
418 private fun Project.registerJvmTransformTask(compilation: KotlinCompilation<*>): TaskProvider<AtomicFUTransformTask> =
419     tasks.register(
420         "transform${compilation.target.name.capitalize()}${compilation.name.capitalize()}Atomicfu",
421         AtomicFUTransformTask::class.java
422     )
423 
424 private fun Project.registerJsTransformTask(compilation: KotlinCompilation<*>): TaskProvider<AtomicFUTransformJsTask> =
425     tasks.register(
426         "transform${compilation.target.name.capitalize()}${compilation.name.capitalize()}Atomicfu",
427         AtomicFUTransformJsTask::class.java
428     )
429 
430 private fun TaskProvider<AtomicFUTransformTask>.configureJvmTask(
431     classpath: FileCollection,
432     classesTaskName: String,
433     transformedClassesDir: Provider<Directory>,
434     originalClassesDir: FileCollection,
435     config: AtomicFUPluginExtension
436 ): TaskProvider<AtomicFUTransformTask> =
437     apply {
438         configure {
439             it.dependsOn(classesTaskName)
440             it.classPath = classpath
441             it.inputFiles = originalClassesDir
442             it.destinationDirectory.value(transformedClassesDir)
443             it.jvmVariant = config.jvmVariant
444             it.verbose = config.verbose
445         }
446     }
447 
configureJsTasknull448 private fun TaskProvider<AtomicFUTransformJsTask>.configureJsTask(
449     classesTaskName: String,
450     transformedClassesDir: Provider<Directory>,
451     originalClassesDir: FileCollection,
452     config: AtomicFUPluginExtension
453 ): TaskProvider<AtomicFUTransformJsTask> =
454     apply {
455         configure {
456             it.dependsOn(classesTaskName)
457             it.inputFiles = originalClassesDir
458             it.destinationDirectory.value(transformedClassesDir)
459             it.verbose = config.verbose
460         }
461     }
462 
setupJarManifestnull463 private fun Jar.setupJarManifest(multiRelease: Boolean) {
464     if (multiRelease) {
465         manifest.attributes.apply {
466             put("Multi-Release", "true")
467         }
468     }
469 }
470 
471 class AtomicFUPluginExtension(pluginVersion: String?) {
472     var dependenciesVersion = pluginVersion
473     var transformJvm = true
474     var transformJs = true
475     var jvmVariant: String = "FU"
476     var verbose: Boolean = false
477 }
478 
479 @CacheableTask
480 abstract class AtomicFUTransformTask : ConventionTask() {
481     @get:Inject
482     internal abstract val providerFactory: ProviderFactory
483 
484     @get:Inject
485     internal abstract val projectLayout: ProjectLayout
486 
487     @PathSensitive(PathSensitivity.RELATIVE)
488     @InputFiles
489     lateinit var inputFiles: FileCollection
490 
491     @Suppress("unused")
492     @Deprecated(
493         message = "Replaced with 'destinationDirectory'",
494         replaceWith = ReplaceWith("destinationDirectory")
495     )
496     @get:Internal
497     var outputDir: File
498         get() = destinationDirectory.get().asFile
<lambda>null499         set(value) { destinationDirectory.value(projectLayout.dir(providerFactory.provider { value })) }
500 
501     @get:OutputDirectory
502     abstract val destinationDirectory: DirectoryProperty
503 
504     @Classpath
505     @InputFiles
506     lateinit var classPath: FileCollection
507 
508     @Input
509     var jvmVariant = "FU"
510 
511     @Input
512     var verbose = false
513 
514     @TaskAction
transformnull515     fun transform() {
516         val cp = classPath.files.map { it.absolutePath }
517         inputFiles.files.forEach { inputDir ->
518             AtomicFUTransformer(cp, inputDir, destinationDirectory.get().asFile).let { t ->
519                 t.jvmVariant = jvmVariant.toJvmVariant()
520                 t.verbose = verbose
521                 t.transform()
522             }
523         }
524     }
525 }
526 
527 @CacheableTask
528 abstract class AtomicFUTransformJsTask : ConventionTask() {
529 
530     @get:Inject
531     internal abstract val providerFactory: ProviderFactory
532 
533     @get:Inject
534     internal abstract val projectLayout: ProjectLayout
535 
536     @PathSensitive(PathSensitivity.RELATIVE)
537     @InputFiles
538     lateinit var inputFiles: FileCollection
539 
540     @Suppress("unused")
541     @Deprecated(
542         message = "Replaced with 'destinationDirectory'",
543         replaceWith = ReplaceWith("destinationDirectory")
544     )
545     @get:Internal
546     var outputDir: File
547         get() = destinationDirectory.get().asFile
<lambda>null548         set(value) { destinationDirectory.value(projectLayout.dir(providerFactory.provider { value })) }
549 
550     @get:OutputDirectory
551     abstract val destinationDirectory: DirectoryProperty
552 
553     @Input
554     var verbose = false
555 
556     @TaskAction
transformnull557     fun transform() {
558         inputFiles.files.forEach { inputDir ->
559             AtomicFUTransformerJS(inputDir, destinationDirectory.get().asFile).let { t ->
560                 t.verbose = verbose
561                 t.transform()
562             }
563         }
564     }
565 }
566