Unify approach in Kotlin/JS tasks configuration.

Move tasks configuration into JS tasks configurators.
Introduce enhancedFreeCompilerArgs workaround when freeCompilerArgs
are modified based on existing values.
Introduce workaround for 'destinationDirectory' input when it
content are depenends on multiple other task inputs.

^KT-27301 In Progress
This commit is contained in:
Yahor Berdnikau
2022-09-14 21:16:52 +02:00
parent 4881e32e44
commit 9de0fe48f3
18 changed files with 328 additions and 258 deletions
@@ -530,7 +530,7 @@ abstract class AbstractKotlin2JsGradlePluginIT(protected val irBackend: Boolean)
":compileTestKotlin2Js"
)
if (irBackend) {
assertFileInProjectExists("build/kotlin2js/main/default/manifest")
assertFileInProjectExists("build/kotlin2js/main/module.js/default/manifest")
} else {
assertFileInProjectExists("build/kotlin2js/main/module.js")
}
@@ -37,7 +37,7 @@ if (isIrBackend) {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-js:$kotlin_version"
integrationTestImplementation files(file(compileKotlin2Js.kotlinOptions.outputFile).parent)
integrationTestImplementation files(compileKotlin2Js.destinationDirectory)
integrationTestImplementation "org.jetbrains.kotlin:kotlin-test-js:$kotlin_version"
}
@@ -156,7 +156,11 @@ internal abstract class KotlinSourceSetProcessor<T : AbstractKotlinCompile<*>>(
protected fun applyStandardTaskConfiguration(taskConfiguration: AbstractKotlinCompileConfig<*>) {
taskConfiguration.configureTask {
it.description = taskDescription
it.destinationDirectory.convention(defaultKotlinDestinationDir)
if (it is Kotlin2JsCompile) {
it.defaultDestinationDirectory.convention(defaultKotlinDestinationDir)
} else {
it.destinationDirectory.convention(defaultKotlinDestinationDir)
}
it.libraries.from({ kotlinCompilation.compileDependencyFiles })
}
}
@@ -252,46 +256,11 @@ internal class Kotlin2JsSourceSetProcessor(
kotlinCompilation.javaSourceSet.clearJavaSrcDirs()
}
// outputFile can be set later during the configuration phase, get it only after the phase:
project.whenEvaluated {
kotlinTask.configure { kotlinTaskInstance ->
val kotlinOptions = kotlinTaskInstance.kotlinOptions
val outputFile = kotlinTaskInstance.outputFileProperty.get()
val outputDir: File = outputFile.parentFile
kotlinOptions.outputFile = if (!kotlinOptions.isProduceUnzippedKlib()) {
outputFile.absolutePath
} else {
outputFile.parentFile.absolutePath
}
if (outputDir.isParentOf(project.rootDir))
throw InvalidUserDataException(
"The output directory '$outputDir' (defined by outputFile of $kotlinTaskInstance) contains or " +
"matches the project root directory '${project.rootDir}'.\n" +
"Gradle will not be able to build the project because of the root directory lock.\n" +
"To fix this, consider using the default outputFile location instead of providing it explicitly."
)
kotlinTaskInstance.destinationDirectory.set(outputDir)
if (
kotlinOptions.freeCompilerArgs.contains(PRODUCE_JS) ||
kotlinOptions.freeCompilerArgs.contains(PRODUCE_UNZIPPED_KLIB) ||
kotlinOptions.freeCompilerArgs.contains(PRODUCE_ZIPPED_KLIB)
) {
// Configure FQ module name to avoid cyclic dependencies in klib manifests (see KT-36721).
val baseName = if (kotlinCompilation.isMainCompilationData()) {
project.name
} else {
"${project.name}_${kotlinCompilation.compilationPurpose}"
}
kotlinTaskInstance.kotlinOptions.freeCompilerArgs += "$MODULE_NAME=${project.klibModuleName(baseName)}"
}
}
project.whenEvaluated {
val subpluginEnvironment: SubpluginEnvironment = SubpluginEnvironment.loadSubplugins(project)
if (kotlinCompilation is KotlinCompilation<*>) { // FIXME support compiler plugins with PM20
subpluginEnvironment.addSubpluginOptions(project, kotlinCompilation)
}
val subpluginEnvironment: SubpluginEnvironment = SubpluginEnvironment.loadSubplugins(project)
if (kotlinCompilation is KotlinCompilation<*>) { // FIXME support compiler plugins with PM20
subpluginEnvironment.addSubpluginOptions(project, kotlinCompilation)
}
}
}
@@ -8,7 +8,6 @@ package org.jetbrains.kotlin.gradle.targets.js.binaryen
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.tasks.*
import org.gradle.work.NormalizeLineEndings
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.tasks.registerTask
import org.jetbrains.kotlin.gradle.utils.newFileProperty
@@ -75,7 +74,7 @@ constructor() : AbstractExecTask<BinaryenExec>(BinaryenExec::class.java) {
it.binaryen = binaryen
it.executable = binaryen.requireConfigured().executablePath.absolutePath
it.dependsOn(binaryen.setupTaskProvider)
it.dependsOn(compilation.compileKotlinTaskProvider)
it.dependsOn(compilation.compileTaskProvider)
it.configuration()
}
}
@@ -5,8 +5,6 @@
package org.jetbrains.kotlin.gradle.targets.js.ir
import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptions
/**
* @see [compiler/testData/cli/js/jsExtraHelp.out]
*/
@@ -30,6 +28,3 @@ internal const val PER_MODULE = "-Xir-per-module"
internal const val PER_MODULE_OUTPUT_NAME = "-Xir-per-module-output-name"
internal const val WASM_BACKEND = "-Xwasm"
fun KotlinJsOptions.isProduceUnzippedKlib() = PRODUCE_UNZIPPED_KLIB in freeCompilerArgs
fun KotlinJsOptions.isProduceZippedKlib() = PRODUCE_ZIPPED_KLIB in freeCompilerArgs
@@ -6,6 +6,7 @@
package org.jetbrains.kotlin.gradle.targets.js.ir
import org.gradle.api.Task
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.Provider
import org.gradle.api.tasks.Copy
import org.gradle.language.base.plugins.LifecycleBasePlugin
@@ -117,9 +118,12 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
),
listOf(compilation)
) { task ->
task.dependsOn(binary.linkSyncTask)
val entryFileProvider = binary.linkSyncTask.flatMap { syncTask ->
binary.linkTask.map {
syncTask.destinationDir.resolve(it.outputFileProperty.get().name)
binary.linkTask.flatMap { linkTask ->
linkTask.outputFileProperty.map {
syncTask.destinationDir.resolve(it.name)
}
}
}
@@ -203,9 +207,13 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
),
listOf(compilation)
) { task ->
val entryFileProvider = binary.linkSyncTask.map {
it.destinationDir
.resolve(binary.linkTask.get().outputFileProperty.get().name)
task.dependsOn(binary.linkSyncTask)
val entryFileProvider = binary.linkSyncTask.flatMap { linkSyncTask ->
binary.linkTask.flatMap { linkTask ->
linkTask.outputFileProperty.map {
linkSyncTask.destinationDir.resolve(it.name)
}
}
}
task.description = "build webpack ${mode.name.toLowerCase()} bundle"
@@ -266,9 +274,7 @@ abstract class KotlinBrowserJsIr @Inject constructor(target: KotlinJsIrTarget) :
configureOptimization(mode)
entryProperty.set(
project.layout.file(entryFileProvider)
)
entryProperty.fileProvider(entryFileProvider)
configurationActions.forEach { configure ->
configure()
@@ -33,13 +33,15 @@ abstract class KotlinD8Ir @Inject constructor(target: KotlinJsIrTarget) :
val runTaskHolder = D8Exec.create(binary.compilation, name) {
group = taskGroupName
inputFileProperty.set(
project.layout.file(
binary.linkSyncTask.map {
it.destinationDir
.resolve(binary.linkTask.get().outputFileProperty.get().name)
dependsOn(binary.linkSyncTask)
inputFileProperty.fileProvider(
binary.linkSyncTask.flatMap { linkSyncTask ->
binary.linkTask.flatMap { linkTask ->
linkTask.outputFileProperty.map {
linkSyncTask.destinationDir.resolve(it.name)
}
}
)
}
)
}
target.runTask.dependsOn(runTaskHolder)
@@ -23,7 +23,6 @@ import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode.PRODUCTION
import org.jetbrains.kotlin.gradle.targets.js.subtargets.DefaultDistribution
import org.jetbrains.kotlin.gradle.targets.js.subtargets.KotlinJsSubTarget
import org.jetbrains.kotlin.gradle.utils.lowerCamelCaseName
import java.io.File
import javax.inject.Inject
open class KotlinJsBinaryContainer
@@ -43,10 +42,7 @@ constructor(
private fun configureBinaryen(binary: JsIrBinary, binaryenDsl: BinaryenExec.() -> Unit) {
val linkTask = binary.linkTask
val compiledMjsFile = linkTask.map { link ->
link.kotlinOptions.outputFile?.let(::File)
?: link.destinationDirectory.locationOnly.get().asFile.resolve("${link.compilation.ownModuleName}.mjs")
}
val compiledMjsFile = linkTask.flatMap { it.outputFileProperty }
val compiledWasmFile = compiledMjsFile.map {
it.parentFile.resolve("${it.nameWithoutExtension}.wasm")
@@ -54,12 +50,15 @@ constructor(
//TODO This is temporary solution that overrides compiled files that triggers recompile and reoptimize wasm every time (when binaryen is enabled)
val binaryenTask = BinaryenExec.create(binary.compilation, "${linkTask.name}Optimize") {
dependsOn(linkTask)
inputFileProperty.fileProvider(compiledWasmFile)
outputFileProperty.fileProvider(compiledWasmFile)
binaryenDsl()
}
binary.compilation.compileKotlinTask.finalizedBy(binaryenTask)
binary.compilation.compileTaskProvider.configure {
it.finalizedBy(binaryenTask)
}
binary.linkSyncTask.configure {
it.dependsOn(binaryenTask)
}
@@ -7,7 +7,6 @@ package org.jetbrains.kotlin.gradle.targets.js.ir
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.FileCollection
import org.gradle.api.file.ProjectLayout
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.Property
import org.gradle.api.tasks.*
@@ -17,15 +16,12 @@ import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.compilerRunner.ArgumentUtils
import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptionsDefault
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
import org.jetbrains.kotlin.gradle.plugin.statistics.KotlinBuildStatsService
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode.DEVELOPMENT
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode.PRODUCTION
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.utils.getValue
import org.jetbrains.kotlin.gradle.utils.toHexString
import org.jetbrains.kotlin.statistics.metrics.BooleanMetrics
import org.jetbrains.kotlin.statistics.metrics.StringMetrics
@@ -38,7 +34,6 @@ import javax.inject.Inject
abstract class KotlinJsIrLink @Inject constructor(
objectFactory: ObjectFactory,
workerExecutor: WorkerExecutor,
private val projectLayout: ProjectLayout
) : Kotlin2JsCompile(
objectFactory.newInstance(CompilerJsOptionsDefault::class.java),
objectFactory,
@@ -61,10 +56,6 @@ abstract class KotlinJsIrLink @Inject constructor(
@get:Internal
internal lateinit var compilation: KotlinCompilationData<*>
private val platformType by project.provider {
compilation.platformType
}
@Transient
@get:Internal
internal val propertiesProvider = PropertiesProvider(project)
@@ -95,23 +86,12 @@ abstract class KotlinJsIrLink @Inject constructor(
@get:PathSensitive(PathSensitivity.RELATIVE)
internal abstract val entryModule: DirectoryProperty
@Deprecated(
message = "Replace with destinationDirectory",
replaceWith = ReplaceWith("destinationDirectory")
)
@get:Internal
override val destinationDirectory: DirectoryProperty = objectFactory.directoryProperty()
@get:OutputDirectory
val normalizedDestinationDirectory: DirectoryProperty = objectFactory
.directoryProperty()
.apply {
set(
destinationDirectory.map { dir ->
if (compilerOptions.outputFile.orNull != null) {
projectLayout.dir(outputFileProperty.map { it.parentFile }).get()
} else {
dir
}
}
)
}
val normalizedDestinationDirectory: DirectoryProperty get() = destinationDirectory
@get:Internal
val rootCacheDirectory by lazy {
@@ -132,6 +112,9 @@ abstract class KotlinJsIrLink @Inject constructor(
KotlinJsIrOutputGranularity.WHOLE_PROGRAM.name.toLowerCase()
)
}
args.includes = entryModule.get().asFile.canonicalPath
if (incrementalJsIr && mode == DEVELOPMENT) {
val digest = MessageDigest.getInstance("SHA-256")
args.cacheDirectories = args.libraries?.splitByPathSeparator()
@@ -161,45 +144,4 @@ abstract class KotlinJsIrLink @Inject constructor(
.toTypedArray()
.filterNot { it.isEmpty() }
}
override fun setupCompilerArgs(
args: K2JSCompilerArguments,
defaultsOnly: Boolean,
ignoreClasspathResolutionErrors: Boolean
) {
when (mode) {
PRODUCTION -> {
args.configureOptions(ENABLE_DCE, GENERATE_D_TS, MINIMIZED_MEMBER_NAMES)
}
DEVELOPMENT -> {
args.configureOptions(GENERATE_D_TS)
}
}
val alreadyDefinedOutputMode = compilerOptions.freeCompilerArgs.get()
.any { it.startsWith(PER_MODULE) }
if (!alreadyDefinedOutputMode) {
args.freeArgs += outputGranularity.toCompilerArgument()
}
super.setupCompilerArgs(args, defaultsOnly, ignoreClasspathResolutionErrors)
}
private fun K2JSCompilerArguments.configureOptions(vararg additionalCompilerArgs: String) {
freeArgs = freeArgs + (
additionalCompilerArgs
.mapNotNull { arg ->
if (compilerOptions.freeCompilerArgs.get().any { it.startsWith(arg) }) {
null
} else {
arg
}
} +
PRODUCE_JS +
"$ENTRY_IR_MODULE=${entryModule.get().asFile.canonicalPath}"
)
if (platformType == KotlinPlatformType.wasm) {
freeArgs = freeArgs + WASM_BACKEND
}
}
}
@@ -121,13 +121,15 @@ abstract class KotlinJsIrSubTarget(
KotlinJsBinaryMode.DEVELOPMENT
).single()
testJs.inputFileProperty.set(
project.layout.file(
binary.linkSyncTask.map {
it.destinationDir
.resolve(binary.linkTask.get().outputFileProperty.get().name)
testJs.dependsOn(binary.linkSyncTask)
testJs.inputFileProperty.fileProvider(
binary.linkSyncTask.flatMap { linkSyncTask ->
binary.linkTask.flatMap { linkTask ->
linkTask.outputFileProperty.map {
linkSyncTask.destinationDir.resolve(it.name)
}
}
)
}
)
configureTestDependencies(testJs)
@@ -5,22 +5,18 @@
package org.jetbrains.kotlin.gradle.targets.js.ir
import org.gradle.api.InvalidUserDataException
import org.gradle.api.attributes.Usage
import org.gradle.api.tasks.TaskProvider
import org.gradle.api.tasks.bundling.Zip
import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptions
import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptions
import org.jetbrains.kotlin.gradle.dsl.JsModuleKind
import org.jetbrains.kotlin.gradle.dsl.JsSourceMapEmbedMode
import org.jetbrains.kotlin.gradle.plugin.*
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinUsages
import org.jetbrains.kotlin.gradle.plugin.mpp.isMain
import org.jetbrains.kotlin.gradle.targets.js.KotlinJsReportAggregatingTestRun
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.tasks.KotlinTasksProvider
import org.jetbrains.kotlin.gradle.testing.internal.kotlinTestRegistry
import org.jetbrains.kotlin.gradle.testing.testTaskName
import org.jetbrains.kotlin.gradle.utils.isParentOf
import org.jetbrains.kotlin.gradle.utils.klibModuleName
import java.io.File
open class KotlinJsIrTargetConfigurator() :
KotlinOnlyTargetConfigurator<KotlinJsIrCompilation, KotlinJsIrTarget>(true),
@@ -73,70 +69,30 @@ open class KotlinJsIrTargetConfigurator() :
super.configureCompilations(target)
target.compilations.all { compilation ->
compilation.kotlinOptions {
compilation.compilerOptions.configure {
configureOptions()
if (target.platformType == KotlinPlatformType.wasm) {
freeCompilerArgs = freeCompilerArgs + WASM_BACKEND
freeCompilerArgs.add(WASM_BACKEND)
}
var produceUnzippedKlib = isProduceUnzippedKlib()
val produceZippedKlib = isProduceZippedKlib()
freeCompilerArgs = freeCompilerArgs + DISABLE_PRE_IR
val isMainCompilation = compilation.isMain()
if (!produceUnzippedKlib && !produceZippedKlib) {
freeCompilerArgs = freeCompilerArgs + PRODUCE_UNZIPPED_KLIB
produceUnzippedKlib = true
}
// Configure FQ module name to avoid cyclic dependencies in klib manifests (see KT-36721).
val baseName = if (isMainCompilation) {
target.project.name
} else {
"${target.project.name}_${compilation.name}"
}
compilation.compileKotlinTaskProvider.configure { task ->
val outputFilePath = outputFile ?: if (produceUnzippedKlib) {
task.destinationDirectory.get().asFile.absoluteFile.normalize().absolutePath
} else {
File(task.destinationDirectory.get().asFile, "$baseName.$KLIB_TYPE").absoluteFile.normalize().absolutePath
}
outputFile = outputFilePath
val taskOutputDir = if (produceUnzippedKlib) File(outputFilePath) else File(outputFilePath).parentFile
if (taskOutputDir.isParentOf(task.project.rootDir))
throw InvalidUserDataException(
"The output directory '$taskOutputDir' (defined by outputFile of $task) contains or " +
"matches the project root directory '${task.project.rootDir}'.\n" +
"Gradle will not be able to build the project because of the root directory lock.\n" +
"To fix this, consider using the default outputFile location instead of providing it explicitly."
)
task.destinationDirectory.set(taskOutputDir)
}
val klibModuleName = target.project.klibModuleName(baseName)
freeCompilerArgs = freeCompilerArgs + "$MODULE_NAME=$klibModuleName"
freeCompilerArgs.add(DISABLE_PRE_IR)
}
compilation.binaries
.withType(JsIrBinary::class.java)
.all { binary ->
binary.linkTask.configure { linkTask ->
linkTask.kotlinOptions.configureOptions()
linkTask.compilerOptions.configureOptions()
}
}
}
}
private fun KotlinJsOptions.configureOptions() {
moduleKind = "umd"
sourceMap = true
sourceMapEmbedSources = "never"
private fun CompilerJsOptions.configureOptions() {
moduleKind.set(JsModuleKind.MODULE_UMD)
sourceMap.set(true)
sourceMapEmbedSources.set(JsSourceMapEmbedMode.SOURCE_MAP_SOURCE_CONTENT_NEVER)
}
override fun defineConfigurationsForTarget(target: KotlinJsIrTarget) {
@@ -34,13 +34,15 @@ abstract class KotlinNodeJsIr @Inject constructor(target: KotlinJsIrTarget) :
val runTaskHolder = NodeJsExec.create(binary.compilation, name) {
group = taskGroupName
inputFileProperty.set(
project.layout.file(
binary.linkSyncTask.map {
it.destinationDir
.resolve(binary.linkTask.get().outputFileProperty.get().name)
dependsOn(binary.linkSyncTask)
inputFileProperty.fileProvider(
binary.linkSyncTask.flatMap { linkSyncTask ->
binary.linkTask.flatMap { linkTask ->
linkTask.outputFileProperty.map {
linkSyncTask.destinationDir.resolve(it.name)
}
}
)
}
)
}
target.runTask.dependsOn(runTaskHolder)
@@ -289,14 +289,16 @@ abstract class KotlinBrowserJs @Inject constructor(target: KotlinJsTarget) :
KotlinJsBinaryMode.DEVELOPMENT -> devDceTaskProvider
}
dependsOn(actualDceTaskProvider)
entryProperty.set(
project.layout.file(
actualDceTaskProvider
.map {
it.destinationDirectory.file(compilation.compileKotlinTask.outputFileProperty.get().name)
}
.flatMap { it.map { it.asFile } }
)
actualDceTaskProvider.flatMap { dceTask ->
compilation.compileTaskProvider.flatMap { compileTask ->
dceTask.destinationDirectory.file(
compileTask.outputFileProperty.map { it.name }
)
}
}
)
resolveFromModulesFirst = true
@@ -98,7 +98,7 @@ abstract class KotlinJsSubTarget(
testRun.subtargetTestTaskName(),
listOf(compilation)
) { testJs ->
val compileTask = compilation.compileKotlinTaskProvider
val compileTask = compilation.compileTaskProvider
testJs.group = LifecycleBasePlugin.VERIFICATION_GROUP
testJs.description = testTaskDescription
@@ -102,7 +102,11 @@ constructor(
@get:PathSensitive(PathSensitivity.ABSOLUTE)
@get:InputFile
@get:NormalizeLineEndings
val entryProperty: RegularFileProperty = objects.fileProperty().fileProvider(compilation.compileKotlinTask.outputFileProperty)
val entryProperty: RegularFileProperty = objects
.fileProperty()
.fileProvider(
compilation.compileTaskProvider.flatMap { it.outputFileProperty }
)
init {
onlyIf {
@@ -6,14 +6,12 @@
package org.jetbrains.kotlin.gradle.tasks
import groovy.lang.Closure
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.*
import org.gradle.api.file.*
import org.gradle.api.invocation.Gradle
import org.gradle.api.logging.Logger
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.Provider
import org.gradle.api.provider.SetProperty
@@ -57,6 +55,11 @@ import org.jetbrains.kotlin.gradle.report.BuildReportMode
import org.jetbrains.kotlin.gradle.report.BuildReportsService
import org.jetbrains.kotlin.gradle.report.ReportingSettings
import org.jetbrains.kotlin.gradle.tasks.internal.KotlinJvmOptionsCompat
import org.jetbrains.kotlin.gradle.targets.js.ir.*
import org.jetbrains.kotlin.gradle.targets.js.ir.DISABLE_PRE_IR
import org.jetbrains.kotlin.gradle.targets.js.ir.PRODUCE_JS
import org.jetbrains.kotlin.gradle.targets.js.ir.PRODUCE_UNZIPPED_KLIB
import org.jetbrains.kotlin.gradle.targets.js.ir.PRODUCE_ZIPPED_KLIB
import org.jetbrains.kotlin.gradle.utils.*
import org.jetbrains.kotlin.incremental.*
import org.jetbrains.kotlin.incremental.ClasspathChanges.ClasspathSnapshotDisabled
@@ -940,25 +943,32 @@ abstract class Kotlin2JsCompile @Inject constructor(
@get:Input
internal var incrementalJsKlib: Boolean = true
override fun isIncrementalCompilationEnabled(): Boolean =
when {
"-Xir-produce-js" in kotlinOptions.freeCompilerArgs -> {
false
}
"-Xir-produce-klib-dir" in kotlinOptions.freeCompilerArgs -> {
override fun isIncrementalCompilationEnabled(): Boolean {
val freeArgs = enhancedFreeCompilerArgs.get()
return when {
PRODUCE_JS in freeArgs -> false
PRODUCE_UNZIPPED_KLIB in freeArgs -> {
KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_KLIB_INCREMENTAL, incrementalJsKlib)
}
incrementalJsKlib
}
"-Xir-produce-klib-file" in kotlinOptions.freeCompilerArgs -> {
PRODUCE_ZIPPED_KLIB in freeArgs -> {
KotlinBuildStatsService.applyIfInitialised {
it.report(BooleanMetrics.JS_KLIB_INCREMENTAL, incrementalJsKlib)
}
incrementalJsKlib
}
else -> incremental
}
}
// Workaround to be able to use default value and change it later based on external input
@get:Internal
internal abstract val defaultDestinationDirectory: DirectoryProperty
// This can be file or directory
@get:Internal
@@ -973,6 +983,14 @@ abstract class Kotlin2JsCompile @Inject constructor(
@get:Optional
abstract val optionalOutputFile: RegularFileProperty
// Workaround to add additional compiler args based on the exising one
// Currently there is a logic to add additional compiler arguments based on already existing one.
// And it is not possible to update compilerOptions.freeCompilerArgs using some kind of .map
// or .flatMap call - this will cause StackOverlowException as upstream source will be updated
// and .map will be called again.
@get:Input
internal abstract val enhancedFreeCompilerArgs: ListProperty<String>
override fun createCompilerArgs(): K2JSCompilerArguments =
K2JSCompilerArguments()
@@ -992,6 +1010,14 @@ abstract class Kotlin2JsCompile @Inject constructor(
if (defaultsOnly) return
(compilerOptions as CompilerJsOptionsDefault).fillCompilerArguments(args)
if (!args.sourceMapPrefix.isNullOrEmpty()) {
args.sourceMapBaseDirs = sourceMapBaseDir.get().asFile.absolutePath
}
// Rewriting default outputFile property back to outputFilePropertyValue
args.outputFile = outputFileProperty.get().absoluteFile.normalize().absolutePath
// Overriding freeArgs from compilerOptions with enhanced one
// containing additional arguments based on the js compilation configuration
args.freeArgs = enhancedFreeCompilerArgs.get()
}
@get:InputFiles
@@ -1018,26 +1044,32 @@ abstract class Kotlin2JsCompile @Inject constructor(
private fun isHybridKotlinJsLibrary(file: File): Boolean =
JsLibraryUtils.isKotlinJavascriptLibrary(file) && isKotlinLibrary(file)
private fun KotlinJsOptions.isPreIrBackendDisabled(): Boolean =
listOf(
"-Xir-only",
"-Xir-produce-js",
"-Xir-produce-klib-file"
).any(freeCompilerArgs::contains)
private val preIrBackendCompilerFlags = listOf(
DISABLE_PRE_IR,
PRODUCE_JS,
PRODUCE_ZIPPED_KLIB
)
private fun isPreIrBackendDisabled(): Boolean = enhancedFreeCompilerArgs
.get()
.any { preIrBackendCompilerFlags.contains(it) }
// see also isIncrementalCompilationEnabled
private fun KotlinJsOptions.isIrBackendEnabled(): Boolean =
listOf(
"-Xir-produce-klib-dir",
"-Xir-produce-js",
"-Xir-produce-klib-file"
).any(freeCompilerArgs::contains)
private val irBackendCompilerFlags = listOf(
PRODUCE_UNZIPPED_KLIB,
PRODUCE_JS,
PRODUCE_ZIPPED_KLIB
)
private fun isIrBackendEnabled(): Boolean = enhancedFreeCompilerArgs
.get()
.any { irBackendCompilerFlags.contains(it) }
private val File.asLibraryFilterCacheKey: LibraryFilterCachingService.LibraryFilterCacheKey
get() = LibraryFilterCachingService.LibraryFilterCacheKey(
this,
irEnabled = kotlinOptions.isIrBackendEnabled(),
preIrDisabled = kotlinOptions.isPreIrBackendDisabled()
irEnabled = isIrBackendEnabled(),
preIrDisabled = isPreIrBackendDisabled()
)
// Kotlin/JS can operate in 3 modes:
@@ -1045,8 +1077,8 @@ abstract class Kotlin2JsCompile @Inject constructor(
// 2) purely IR backend
// 3) hybrid pre-IR and IR backend. Can only accept libraries with both JS and IR parts.
private val libraryFilterBody: (File) -> Boolean
get() = if (kotlinOptions.isIrBackendEnabled()) {
if (kotlinOptions.isPreIrBackendDisabled()) {
get() = if (isIrBackendEnabled()) {
if (isPreIrBackendDisabled()) {
//::isKotlinLibrary
// Workaround for KT-47797
{ isKotlinLibrary(it) }
@@ -1088,7 +1120,9 @@ abstract class Kotlin2JsCompile @Inject constructor(
) {
logger.debug("Calling compiler")
if (kotlinOptions.isIrBackendEnabled()) {
validateOutputDirectory()
if (isIrBackendEnabled()) {
logger.info(USING_JS_IR_BACKEND_MESSAGE)
}
@@ -1103,11 +1137,6 @@ abstract class Kotlin2JsCompile @Inject constructor(
}
args.friendModules = friendDependencies.files.joinToString(File.pathSeparator) { it.absolutePath }
if (!args.sourceMapPrefix.isNullOrEmpty()) {
args.sourceMapBaseDirs = sourceMapBaseDir.get().asFile.absolutePath
}
args.legacyDeprecatedNoWarn = jsLegacyNoWarn.get()
logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")
@@ -1141,6 +1170,22 @@ abstract class Kotlin2JsCompile @Inject constructor(
taskOutputsBackup
)
}
private val projectRootDir = project.rootDir
private fun validateOutputDirectory() {
val outputFile = outputFileProperty.get()
val outputDir = outputFile.parentFile
if (outputDir.isParentOf(projectRootDir)) {
throw InvalidUserDataException(
"The output directory '$outputDir' (defined by outputFile of ':$name') contains or " +
"matches the project root directory '${projectRootDir}'.\n" +
"Gradle will not be able to build the project because of the root directory lock.\n" +
"To fix this, consider using the default outputFile location instead of providing it explicitly."
)
}
}
}
data class KotlinCompilerPluginData(
@@ -7,8 +7,13 @@ package org.jetbrains.kotlin.gradle.tasks.configuration
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
import org.jetbrains.kotlin.gradle.targets.js.ir.isProduceUnzippedKlib
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.isMainCompilationData
import org.jetbrains.kotlin.gradle.targets.js.ir.*
import org.jetbrains.kotlin.gradle.targets.js.ir.PRODUCE_JS
import org.jetbrains.kotlin.gradle.targets.js.ir.PRODUCE_UNZIPPED_KLIB
import org.jetbrains.kotlin.gradle.targets.js.ir.PRODUCE_ZIPPED_KLIB
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import org.jetbrains.kotlin.gradle.utils.klibModuleName
import java.io.File
internal typealias Kotlin2JsCompileConfig = BaseKotlin2JsCompileConfig<Kotlin2JsCompile>
@@ -27,18 +32,96 @@ internal open class BaseKotlin2JsCompileConfig<TASK : Kotlin2JsCompile>(
task.incremental = propertiesProvider.incrementalJs ?: true
task.incrementalJsKlib = propertiesProvider.incrementalJsKlib ?: true
task.outputFileProperty.value(task.project.provider {
val extensionName = if (compilation.platformType == KotlinPlatformType.wasm) ".mjs" else ".js"
task.compilerOptions.outputFile.orNull?.let(::File)
?: task.destinationDirectory.locationOnly.get().asFile.resolve("${compilation.ownModuleName}$extensionName")
}).disallowChanges()
configureAdditionalFreeCompilerArguments(task, compilation)
task.optionalOutputFile.fileProvider(task.outputFileProperty.flatMap { outputFile ->
task.project.provider {
outputFile.takeUnless { task.kotlinOptions.isProduceUnzippedKlib() }
@Suppress("DEPRECATION")
task.compilerOptions.outputFile.convention(
task.defaultDestinationDirectory.zip(task.enhancedFreeCompilerArgs) { destDir, freeArgs ->
val baseName = if (compilation.isMainCompilationData()) {
project.name
} else {
"${project.name}_${compilation.compilationPurpose}"
}
if (freeArgs.contains(PRODUCE_UNZIPPED_KLIB)) {
destDir.asFile.absoluteFile.normalize().absolutePath
} else {
if (compilation is KotlinJsIrCompilation) {
destDir.asFile.resolve("$baseName.$KLIB_TYPE").absoluteFile.normalize().absolutePath
} else {
val extensionName = if (compilation.platformType == KotlinPlatformType.wasm) ".mjs" else ".js"
destDir.asFile.resolve("${compilation.ownModuleName}$extensionName").absolutePath
}
}
}
}).disallowChanges()
)
@Suppress("DEPRECATION")
task.outputFileProperty.value(
task.compilerOptions.outputFile.map { File(it) }
)
task.destinationDirectory
.fileProvider(
task.outputFileProperty.zip(task.enhancedFreeCompilerArgs) { outputFile, freeArgs ->
if (freeArgs.contains(PRODUCE_UNZIPPED_KLIB)) {
outputFile
} else {
outputFile.parentFile
}
}
)
.disallowChanges()
task.optionalOutputFile.fileProvider(
task.outputFileProperty.flatMap { outputFile ->
task.enhancedFreeCompilerArgs.flatMap { freeArgs ->
task.project.providers.provider {
outputFile.takeUnless {
freeArgs.contains(PRODUCE_UNZIPPED_KLIB)
}
}
}
}
).disallowChanges()
task.libraryCache.set(libraryCacheService).also { task.libraryCache.disallowChanges() }
}
}
protected open fun configureAdditionalFreeCompilerArguments(
task: TASK,
compilation: KotlinCompilationData<*>
) {
task.enhancedFreeCompilerArgs.value(
task.compilerOptions.freeCompilerArgs.map { freeArgs ->
freeArgs.toMutableList().apply {
commonJsAdditionalCompilerFlags(compilation)
}
}
).disallowChanges()
}
protected fun MutableList<String>.commonJsAdditionalCompilerFlags(
compilation: KotlinCompilationData<*>
) {
if (contains(DISABLE_PRE_IR) &&
!contains(PRODUCE_UNZIPPED_KLIB) &&
!contains(PRODUCE_ZIPPED_KLIB)
) {
add(PRODUCE_UNZIPPED_KLIB)
}
if (contains(PRODUCE_JS) ||
contains(PRODUCE_UNZIPPED_KLIB) ||
contains(PRODUCE_ZIPPED_KLIB)
) {
// Configure FQ module name to avoid cyclic dependencies in klib manifests (see KT-36721).
val baseName = if (compilation.isMainCompilationData()) {
project.name
} else {
"${project.name}_${compilation.compilationPurpose}"
}
add("$MODULE_NAME=${project.klibModuleName(baseName)}")
}
}
}
@@ -5,8 +5,11 @@
package org.jetbrains.kotlin.gradle.tasks.configuration
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrCompilation
import org.jetbrains.kotlin.gradle.targets.js.ir.KotlinJsIrLink
import org.gradle.api.InvalidUserDataException
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData
import org.jetbrains.kotlin.gradle.targets.js.dsl.KotlinJsBinaryMode
import org.jetbrains.kotlin.gradle.targets.js.ir.*
internal open class KotlinJsIrLinkConfig(
compilation: KotlinJsIrCompilation
@@ -17,9 +20,70 @@ internal open class KotlinJsIrLinkConfig(
// Link tasks are not affected by compiler plugin, so set to empty
task.pluginClasspath.setFrom(objectFactory.fileCollection())
task.entryModule.fileProvider(compilation.output.classesDirs.elements.map { it.single().asFile }).disallowChanges()
task.dependsOn(compilation.compileTaskProvider)
task.dependsOn(compilation.output.classesDirs)
task.entryModule.fileProvider(
compilation.output.classesDirs.elements.flatMap {
task.project.providers.provider {
it.single().asFile
}
}
).disallowChanges()
task.compilation = compilation
task.destinationDirectory.fileProvider(task.outputFileProperty.map { it.parentFile })
}
}
override fun configureAdditionalFreeCompilerArguments(
task: KotlinJsIrLink,
compilation: KotlinCompilationData<*>
) {
task.enhancedFreeCompilerArgs.value(
task.compilerOptions.freeCompilerArgs.zip(task.modeProperty) { freeArgs, mode ->
freeArgs.toMutableList().apply {
commonJsAdditionalCompilerFlags(compilation)
when (mode) {
KotlinJsBinaryMode.PRODUCTION -> {
configureOptions(
compilation,
ENABLE_DCE,
GENERATE_D_TS,
MINIMIZED_MEMBER_NAMES
)
}
KotlinJsBinaryMode.DEVELOPMENT -> {
configureOptions(
compilation,
GENERATE_D_TS
)
}
else -> throw InvalidUserDataException(
"Unknown KotlinJsBinaryMode to configure the build: $mode"
)
}
val alreadyDefinedOutputMode = any { it.startsWith(PER_MODULE) }
if (!alreadyDefinedOutputMode) {
add(task.outputGranularity.toCompilerArgument())
}
}
}
).disallowChanges()
}
private fun MutableList<String>.configureOptions(
compilation: KotlinCompilationData<*>,
vararg additionalCompilerArgs: String
) {
additionalCompilerArgs.forEach { arg ->
if (none { it.startsWith(arg) }) add(arg)
}
add(PRODUCE_JS)
if (compilation.platformType == KotlinPlatformType.wasm) {
add(WASM_BACKEND)
}
}
}