From b8cab8b1d7b8cf4e1ad295c5a40af2e6f459e9d2 Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Fri, 2 Sep 2022 23:26:46 -0700 Subject: [PATCH 1/8] Support running KSP via KotlinCompilerRunner which is based on KGP's GradleCompilerRunnerWithWorker. Also refactored the code to allow selection between the old and new ways to create KSP tasks and call the compiler. --- gradle-plugin/build.gradle.kts | 1 + .../ksp/gradle/AndroidPluginIntegration.kt | 3 +- .../ksp/gradle/KotlinCompilerRunner.kt | 113 +++ .../ksp/gradle/KotlinCompilerRunnerImpls.kt | 233 +++++ .../devtools/ksp/gradle/KspSubplugin.kt | 738 +-------------- .../ksp/gradle/tasks/KspTaskFactory.kt | 70 ++ .../gradle/tasks/inherited/InheritedTasks.kt | 776 ++++++++++++++++ .../tasks/standalone/StandaloneTasks.kt | 870 ++++++++++++++++++ 8 files changed, 2103 insertions(+), 701 deletions(-) create mode 100644 gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt create mode 100644 gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt create mode 100644 gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/KspTaskFactory.kt create mode 100644 gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/inherited/InheritedTasks.kt create mode 100644 gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index aa67a18f00..0bc88ca7b6 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -24,6 +24,7 @@ plugins { dependencies { implementation("org.jetbrains.kotlin:kotlin-gradle-plugin-api:$kotlinBaseVersion") implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinBaseVersion") + implementation("org.jetbrains.kotlin:kotlin-compiler-runner:$kotlinBaseVersion") compileOnly("org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlinBaseVersion") // replace AGP dependency w/ gradle-api when we have source registering API available. compileOnly("com.android.tools.build:gradle:$agpBaseVersion") diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/AndroidPluginIntegration.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/AndroidPluginIntegration.kt index a78a610ee6..862dc1f364 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/AndroidPluginIntegration.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/AndroidPluginIntegration.kt @@ -19,6 +19,7 @@ package com.google.devtools.ksp.gradle import com.android.build.api.dsl.CommonExtension import com.android.build.gradle.BaseExtension import org.gradle.api.Project +import org.gradle.api.Task import org.gradle.api.file.FileCollection import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation @@ -65,7 +66,7 @@ object AndroidPluginIntegration { fun registerGeneratedJavaSources( project: Project, kotlinCompilation: KotlinJvmAndroidCompilation, - kspTaskProvider: TaskProvider, + kspTaskProvider: TaskProvider, javaOutputDir: File, classOutputDir: File, resourcesOutputDir: FileCollection, diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt new file mode 100644 index 0000000000..f1c8c912ed --- /dev/null +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt @@ -0,0 +1,113 @@ +/* + * Copyright 2022 Google LLC + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package com.google.devtools.ksp.gradle + +import org.gradle.api.Task +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Internal +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy +import org.jetbrains.kotlin.gradle.utils.newInstance +import java.io.File + +interface KotlinCompilerRunner { + @get:Internal + val compilerExecutionStrategy: Property + + @get:Internal + val useDaemonFallbackStrategy: Property + + @get:Internal + val kotlinDaemonJvmArguments: ListProperty + + @get:Classpath + val compilerClasspath: ConfigurableFileCollection +} + +interface KotlinJvmCompilerRunner : KotlinCompilerRunner { + fun runJvmCompilerAsync( + args: K2JVMCompilerArguments, + sources: List, + commonSources: List, + outputs: List + ) +} + +interface KotlinJsCompilerRunner : KotlinCompilerRunner { + fun runJsCompilerAsync( + args: K2JSCompilerArguments, + sources: List, + commonSources: List, + outputs: List + ) +} + +interface KotlinMetadataCompilerRunner : KotlinCompilerRunner { + fun runMetadataCompilerAsync( + args: K2MetadataCompilerArguments, + sources: List, + commonSources: List, + outputs: List + ) +} + +interface KotlinNativeCompilerRunner : KotlinCompilerRunner { + fun runNativeCompilerAsync( + args: K2NativeCompilerArguments, + sources: List, + commonSources: List, + outputs: List, + ) +} + +fun createKotlinJvmCompilerRunner( + task: Task, + objectFactory: ObjectFactory, +): KotlinJvmCompilerRunner { + return objectFactory.newInstance(task) +} + +fun createKotlinJsCompilerRunner( + task: Task, + objectFactory: ObjectFactory, +): KotlinJsCompilerRunner { + return objectFactory.newInstance(task) +} + +fun createKotlinMetadataCompilerRunner( + task: Task, + objectFactory: ObjectFactory, +): KotlinMetadataCompilerRunner { + return objectFactory.newInstance(task) +} + +fun createKotlinNativeCompilerRunner( + task: Task, + objectFactory: ObjectFactory, +): KotlinNativeCompilerRunner { + return objectFactory.newInstance(task) +} diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt new file mode 100644 index 0000000000..e8202e7175 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt @@ -0,0 +1,233 @@ +/* + * Copyright 2022 Google LLC + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package com.google.devtools.ksp.gradle + +import org.gradle.api.Task +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Internal +import org.gradle.process.ExecOperations +import org.gradle.workers.WorkerExecutor +import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter +import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporterImpl +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments +import org.jetbrains.kotlin.compilerRunner.CompilerExecutionSettings +import org.jetbrains.kotlin.compilerRunner.GradleCompilerEnvironment +import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers +import org.jetbrains.kotlin.compilerRunner.KotlinToolRunner +import org.jetbrains.kotlin.compilerRunner.OutputItemsCollectorImpl +import org.jetbrains.kotlin.gradle.logging.GradleKotlinLogger +import org.jetbrains.kotlin.gradle.logging.GradlePrintingMessageCollector +import org.jetbrains.kotlin.gradle.report.ReportingSettings +import org.jetbrains.kotlin.gradle.tasks.DefaultKotlinJavaToolchain +import org.jetbrains.kotlin.gradle.tasks.GradleCompileTaskProvider +import org.jetbrains.kotlin.gradle.utils.newInstance +import org.jetbrains.kotlin.gradle.utils.propertyWithNewInstance +import org.jetbrains.kotlin.konan.target.KonanTarget +import java.io.File +import javax.inject.Inject + +internal inline fun ObjectFactory.property() = property(T::class.java) +internal inline fun ObjectFactory.property(initialValue: T) = property().value(initialValue) + +abstract class KotlinCompilerRunnerImpl @Inject constructor( + task: Task, + objectFactory: ObjectFactory, + @get:Internal val workerExecutor: WorkerExecutor +) : KotlinCompilerRunner { + @get:Internal + internal val taskProvider: Provider = objectFactory.property( + objectFactory.newInstance(task.project.gradle, task, task.project) + ) + + @get:Internal + internal val defaultKotlinJavaToolchain: Provider = + objectFactory.propertyWithNewInstance({ null }) + + @get:Internal + internal val metrics: Property = objectFactory.property(BuildMetricsReporterImpl()) + + @get:Internal + val normalizedKotlinDaemonJvmArguments: Provider> + get() = kotlinDaemonJvmArguments.map { + it.map { arg -> arg.trim().removePrefix("-") } + } + + @get:Internal + internal val logger = task.logger + + @Internal + internal fun prepareEnvironment(allWarningsAsErrors: Boolean, outputs: List): GradleCompilerEnvironment { + val messageCollector = GradlePrintingMessageCollector(GradleKotlinLogger(logger), allWarningsAsErrors) + val outputItemCollector = OutputItemsCollectorImpl() + return GradleCompilerEnvironment( + compilerClasspath.files.toList(), messageCollector, outputItemCollector, + reportingSettings = ReportingSettings(), + outputFiles = outputs + ) + } + + @Internal + internal fun prepareCompilerRunner(): GradleCompilerRunnerWithWorkers { + return GradleCompilerRunnerWithWorkers( + taskProvider.get(), + defaultKotlinJavaToolchain.get().currentJvmJdkToolsJar.orNull, + CompilerExecutionSettings( + normalizedKotlinDaemonJvmArguments.get(), + compilerExecutionStrategy.get(), + useDaemonFallbackStrategy.get() + ), + metrics.get(), + workerExecutor + ) + } +} + +abstract class KotlinJvmCompilerRunnerImpl @Inject constructor( + task: Task, + objectFactory: ObjectFactory, + workerExecutor: WorkerExecutor +) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinJvmCompilerRunner { + + override fun runJvmCompilerAsync( + args: K2JVMCompilerArguments, + sources: List, + commonSources: List, + outputs: List + ) { + val environment = prepareEnvironment(args.allWarningsAsErrors, outputs) + val compilerRunner = prepareCompilerRunner() + + compilerRunner.runJvmCompilerAsync( + sourcesToCompile = sources, + commonSources = commonSources, + javaPackagePrefix = null, + args = args, + environment = environment, + jdkHome = defaultKotlinJavaToolchain.get().providedJvm.get().javaHome, + null + ) + } +} + +abstract class KotlinJsCompilerRunnerImpl @Inject constructor( + task: Task, + objectFactory: ObjectFactory, + workerExecutor: WorkerExecutor +) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinJsCompilerRunner { + + override fun runJsCompilerAsync( + args: K2JSCompilerArguments, + sources: List, + commonSources: List, + outputs: List + ) { + val environment = prepareEnvironment(args.allWarningsAsErrors, outputs) + val compilerRunner = prepareCompilerRunner() + + compilerRunner.runJsCompilerAsync(sources, commonSources, args, environment, null) + } +} + +abstract class KotlinMetadataCompilerRunnerImpl @Inject constructor( + task: Task, + objectFactory: ObjectFactory, + workerExecutor: WorkerExecutor +) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinMetadataCompilerRunner { + + override fun runMetadataCompilerAsync( + args: K2MetadataCompilerArguments, + sources: List, + commonSources: List, + outputs: List + ) { + val environment = prepareEnvironment(args.allWarningsAsErrors, outputs) + val compilerRunner = prepareCompilerRunner() + + compilerRunner.runMetadataCompilerAsync(sources, commonSources, args, environment) + } +} + +abstract class KotlinNativeCompilerRunnerImpl @Inject constructor( + task: Task, + private val objectFactory: ObjectFactory, + workerExecutor: WorkerExecutor, + private val execOperations: ExecOperations +) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinNativeCompilerRunner { + + private val runnerSettings = org.jetbrains.kotlin.compilerRunner + .KotlinNativeCompilerRunner.Settings.fromProject(task.project) + + override fun runNativeCompilerAsync( + args: K2NativeCompilerArguments, + sources: List, + commonSources: List, + outputs: List, + ) { + val output = File(outputs.first(), "dummy.out") + + val target = KonanTarget.predefinedTargets.get(args.target!!)!! + val buildArgs: MutableList = mutableListOf( + "-o", output.path, + "-target", target.name, + "-p", "library", + "-Xmulti-platform" + ) + args.libraries?.flatMap { listOf("-l", it) }?.let { buildArgs.addAll(it) } + args.friendModules?.let { + buildArgs.add("-friend-modules") + buildArgs.add(it) + } + + if (args.verbose) + buildArgs.add("-verbose") + if (args.allWarningsAsErrors) + buildArgs.add("-Werror") + + args.pluginClasspaths?.map { "-Xplugin=$it" }?.let { buildArgs.addAll(it) } + args.pluginOptions?.flatMap { listOf("-P", it) }?.let { buildArgs.addAll(it) } + + args.languageVersion?.let { + buildArgs.add("-language-version") + buildArgs.add(it) + } + args.apiVersion?.let { + buildArgs.add("-api-version") + buildArgs.add(it) + } + + buildArgs.addAll(sources.map { it.absolutePath }) + buildArgs.addAll(args.freeArgs) + buildArgs.addAll(commonSources.map { it.absolutePath }) + + org.jetbrains.kotlin.compilerRunner.KotlinNativeCompilerRunner( + settings = runnerSettings, + executionContext = KotlinToolRunner.GradleExecutionContext.fromTaskContext( + objectFactory, + execOperations, + logger + ) + ).run(buildArgs) + } +} diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt index 08caa23ac8..79e663f616 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt @@ -15,11 +15,10 @@ * limitations under the License. */ -@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") - package com.google.devtools.ksp.gradle import com.google.devtools.ksp.gradle.model.builder.KspModelBuilder +import com.google.devtools.ksp.gradle.tasks.KspTaskFactory import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.UnknownTaskException @@ -27,76 +26,36 @@ import org.gradle.api.artifacts.Configuration import org.gradle.api.attributes.Attribute import org.gradle.api.file.ConfigurableFileCollection import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.FileCollection -import org.gradle.api.model.ObjectFactory import org.gradle.api.provider.ListProperty import org.gradle.api.provider.MapProperty -import org.gradle.api.provider.Property import org.gradle.api.provider.Provider -import org.gradle.api.provider.ProviderFactory -import org.gradle.api.tasks.* +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.LocalState +import org.gradle.api.tasks.Nested import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskProvider import org.gradle.api.tasks.compile.JavaCompile import org.gradle.language.jvm.tasks.ProcessResources import org.gradle.process.CommandLineArgumentProvider -import org.gradle.process.ExecOperations import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry -import org.gradle.util.GradleVersion -import org.gradle.work.Incremental -import org.gradle.work.InputChanges -import org.gradle.workers.WorkerExecutor -import org.jetbrains.kotlin.cli.common.arguments.* import org.jetbrains.kotlin.config.ApiVersion -import org.jetbrains.kotlin.gradle.dsl.* -import org.jetbrains.kotlin.gradle.internal.CompilerArgumentsContributor -import org.jetbrains.kotlin.gradle.internal.compilerArgumentsConfigurationFlags -import org.jetbrains.kotlin.gradle.internal.kapt.incremental.* -import org.jetbrains.kotlin.gradle.plugin.* -import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinNativeCompilation -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation +import org.jetbrains.kotlin.gradle.plugin.FilesSubpluginOption +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilationWithResources +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin +import org.jetbrains.kotlin.gradle.plugin.SubpluginArtifact +import org.jetbrains.kotlin.gradle.plugin.SubpluginOption +import org.jetbrains.kotlin.gradle.plugin.getKotlinPluginVersion import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmCompilation import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinWithJavaCompilation -import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost -import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData -import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinNativeCompilationData -import org.jetbrains.kotlin.gradle.tasks.* +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import org.jetbrains.kotlin.gradle.tasks.configuration.AbstractKotlinCompileConfig -import org.jetbrains.kotlin.incremental.ChangedFiles -import org.jetbrains.kotlin.incremental.destinationAsFile -import org.jetbrains.kotlin.incremental.isJavaFile -import org.jetbrains.kotlin.incremental.isKotlinFile -import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty -import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.io.File -import java.nio.file.Paths -import java.util.concurrent.Callable import javax.inject.Inject -import kotlin.reflect.KProperty1 - -@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") -internal class Configurator : AbstractKotlinCompileConfig> { - constructor(compilation: KotlinCompilationData<*>, kotlinCompile: AbstractKotlinCompile<*>) : super(compilation) { - configureTask { task -> - if (task is KspTaskJvm) { - // Assign ownModuleName different from kotlin compilation to - // work around https://github.com/google/ksp/issues/647 - // This will not be necessary once https://youtrack.jetbrains.com/issue/KT-45777 lands - task.ownModuleName.value(kotlinCompile.ownModuleName.map { "$it-ksp" }) - } - if (task is KspTaskJS) { - val libraryCacheService = project.rootProject.gradle.sharedServices.registerIfAbsent( - Kotlin2JsCompile.LibraryFilterCachingService::class.java.canonicalName + - "_${Kotlin2JsCompile.LibraryFilterCachingService::class.java.classLoader.hashCode()}", - Kotlin2JsCompile.LibraryFilterCachingService::class.java - ) {} - task.libraryCache.set(libraryCacheService).also { task.libraryCache.disallowChanges() } - task.pluginClasspath.setFrom(objectFactory.fileCollection()) - } - } - } -} class KspGradleSubplugin @Inject internal constructor(private val registry: ToolingModelBuilderRegistry) : KotlinCompilerPluginSupportPlugin { @@ -110,6 +69,8 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool const val KSP_GROUP_ID = "com.google.devtools.ksp" const val KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME = "kspPluginClasspath" + val apiArtifact = "com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION" + @JvmStatic fun getKspOutputDir(project: Project, sourceSetName: String, target: String) = File(project.project.buildDir, "generated/ksp/$target/$sourceSetName") @@ -135,7 +96,7 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool File(project.project.buildDir, "kspCaches/$target/$sourceSetName") @JvmStatic - private fun getSubpluginOptions( + fun getSubpluginOptions( project: Project, kspExtension: KspExtension, classpath: Configuration, @@ -245,7 +206,6 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool ) project.dependencies.add( KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME, - "$KSP_GROUP_ID:$KSP_COMPILER_PLUGIN_ID:$KSP_VERSION" ) @@ -261,129 +221,28 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool val kotlinCompileTask = kotlinCompileProvider.get() - fun configureAsKspTask(kspTask: KspTask, isIncremental: Boolean) { - // depends on the processor; if the processor changes, it needs to be reprocessed. - val processorClasspath = project.configurations.maybeCreate("${kspTaskName}ProcessorClasspath") - .extendsFrom(*nonEmptyKspConfigurations.toTypedArray()) - kspTask.processorClasspath.from(processorClasspath) - kspTask.dependsOn(processorClasspath.buildDependencies) - - kspTask.options.addAll( - kspTask.project.provider { - getSubpluginOptions( - project, - kspExtension, - processorClasspath, - sourceSetName, - target, - isIncremental, - kspExtension.allWarningsAsErrors - ) - } - ) - kspTask.commandLineArgumentProviders.addAll(kspExtension.commandLineArgumentProviders) - kspTask.destination = kspOutputDir - kspTask.blockOtherCompilerPlugins = kspExtension.blockOtherCompilerPlugins - kspTask.apOptions.value(kspExtension.arguments).disallowChanges() - kspTask.kspCacheDir.fileValue(getKspCachesDir(project, sourceSetName, target)).disallowChanges() - - if (kspExtension.blockOtherCompilerPlugins) { - kspTask.overridePluginClasspath.from(kspClasspathCfg) - } - kspTask.isKspIncremental = isIncremental - } - - fun configureAsAbstractKotlinCompileTool(kspTask: AbstractKotlinCompileTool<*>) { - kspTask.destinationDirectory.set(kspOutputDir) - kspTask.outputs.dirs( - kotlinOutputDir, - javaOutputDir, - classOutputDir, - resourceOutputDir - ) - - if (kspExtension.allowSourcesFromOtherPlugins) { - val deps = kotlinCompileTask.dependsOn.filterNot { - it.safeAs>()?.name == kspTaskName || - it.safeAs()?.name == kspTaskName - } - kspTask.dependsOn(deps) - kspTask.setSource(kotlinCompileTask.sources) - if (kotlinCompileTask is KotlinCompile) { - kspTask.setSource(kotlinCompileTask.javaSources) - } - } else { - kotlinCompilation.allKotlinSourceSets.forEach { sourceSet -> - kspTask.setSource(sourceSet.kotlin) - } - if (kotlinCompilation is KotlinCommonCompilation) { - kspTask.setSource(kotlinCompilation.defaultSourceSet.kotlin) - } - } - - // Don't support binary generation for non-JVM platforms yet. - // FIXME: figure out how to add user generated libraries. - if (kspTask is KspTaskJvm) { - kotlinCompilation.output.classesDirs.from(classOutputDir) - } - } - - val kspTaskProvider = when (kotlinCompileTask) { - is AbstractKotlinCompile<*> -> { - val kspTaskClass = when (kotlinCompileTask) { - is KotlinCompile -> KspTaskJvm::class.java - is Kotlin2JsCompile -> KspTaskJS::class.java - is KotlinCompileCommon -> KspTaskMetadata::class.java - else -> return project.provider { emptyList() } - } - val isIncremental = project.findProperty("ksp.incremental")?.toString()?.toBoolean() ?: true - project.tasks.register(kspTaskName, kspTaskClass) { kspTask -> - configureAsKspTask(kspTask, isIncremental) - configureAsAbstractKotlinCompileTool(kspTask) - - kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) - kspTask.configureCompilation( - kotlinCompilation as KotlinCompilationData<*>, - kotlinCompileTask, - ) - } - } - is KotlinNativeCompile -> { - val kspTaskClass = KspTaskNative::class.java - val pluginConfigurationName = - (kotlinCompileTask.compilation as AbstractKotlinNativeCompilation).pluginConfigurationName - project.configurations.getByName(pluginConfigurationName).dependencies.add( - project.dependencies.create(apiArtifact) - ) - project.tasks.register(kspTaskName, kspTaskClass, kotlinCompileTask.compilation).apply { - configure { kspTask -> - kspTask.onlyIf { - kotlinCompileTask.compilation.konanTarget.enabledOnCurrentHost - } - configureAsKspTask(kspTask, false) - configureAsAbstractKotlinCompileTool(kspTask) - - // KotlinNativeCompile computes -Xplugin=... from compilerPluginClasspath. - kspTask.compilerPluginClasspath = project.configurations.getByName(pluginConfigurationName) - kspTask.commonSources.from(kotlinCompileTask.commonSources) - kspTask.compilerPluginOptions.addPluginArgument(kotlinCompileTask.compilerPluginOptions) - } - } - } - else -> return project.provider { emptyList() } - } - - kotlinCompileTask.safeAs>()?.let { - Configurator(kotlinCompilation as KotlinCompilationData<*>, kotlinCompileTask as AbstractKotlinCompile<*>) - .execute(kspTaskProvider as TaskProvider>) - } + val kspTaskProvider = KspTaskFactory.createKspTask( + project, + kotlinCompilation, + kotlinCompileTask, + kspExtension, + nonEmptyKspConfigurations, + kspTaskName, + target, + sourceSetName, + classOutputDir, + javaOutputDir, + kotlinOutputDir, + resourceOutputDir, + kspOutputDir + ) kotlinCompileProvider.configure { kotlinCompile -> kotlinCompile.dependsOn(kspTaskProvider) kotlinCompile.setSource(kotlinOutputDir, javaOutputDir) when (kotlinCompile) { - is AbstractKotlinCompile<*> -> kotlinCompile.libraries.from(project.files(classOutputDir)) - // is KotlinNativeCompile -> TODO: support binary generation? + is KotlinCompile -> kotlinCompile.libraries.from(project.files(classOutputDir)) + // TODO: support binary generation on other platforms? } } @@ -399,7 +258,7 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool AndroidPluginIntegration.registerGeneratedJavaSources( project = project, kotlinCompilation = kotlinCompilation, - kspTaskProvider = kspTaskProvider as TaskProvider, + kspTaskProvider = kspTaskProvider, javaOutputDir = javaOutputDir, classOutputDir = classOutputDir, resourcesOutputDir = project.files(resourceOutputDir) @@ -417,14 +276,12 @@ class KspGradleSubplugin @Inject internal constructor(private val registry: Tool version = KSP_VERSION ) - override fun getPluginArtifactForNative(): SubpluginArtifact? = + override fun getPluginArtifactForNative(): SubpluginArtifact = SubpluginArtifact( groupId = "com.google.devtools.ksp", artifactId = KSP_ARTIFACT_NAME_NATIVE, version = KSP_VERSION ) - - val apiArtifact = "com.google.devtools.ksp:symbol-processing-api:$KSP_VERSION" } private val artifactType = Attribute.of("artifactType", String::class.java) @@ -477,523 +334,4 @@ interface KspTask : Task { @get:Input var isKspIncremental: Boolean - - fun configureCompilation( - kotlinCompilation: KotlinCompilationData<*>, - kotlinCompile: AbstractKotlinCompile<*>, - ) -} - -@CacheableTask -abstract class KspTaskJvm @Inject constructor( - workerExecutor: WorkerExecutor, - objectFactory: ObjectFactory -) : KotlinCompile(KotlinJvmOptionsImpl(), workerExecutor, objectFactory), KspTask { - @get:PathSensitive(PathSensitivity.NONE) - @get:Optional - @get:InputFiles - @get:Incremental - abstract val classpathStructure: ConfigurableFileCollection - - @get:Input - var isIntermoduleIncremental: Boolean = false - - override fun configureCompilation( - kotlinCompilation: KotlinCompilationData<*>, - kotlinCompile: AbstractKotlinCompile<*>, - ) { - kotlinCompile as KotlinCompile - val providerFactory = kotlinCompile.project.providers - compileKotlinArgumentsContributor.set( - providerFactory.provider { - kotlinCompile.compilerArgumentsContributor - } - ) - - isIntermoduleIncremental = - (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) && - isKspIncremental - if (isIntermoduleIncremental) { - val classStructureIfIncremental = project.configurations.detachedConfiguration( - project.dependencies.create(project.files(project.provider { kotlinCompile.libraries })) - ) - maybeRegisterTransform(project) - - classpathStructure.from( - classStructureIfIncremental.incoming.artifactView { viewConfig -> - viewConfig.attributes.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) - }.files - ).disallowChanges() - classpathSnapshotProperties.useClasspathSnapshot.value(true).disallowChanges() - } else { - classpathSnapshotProperties.useClasspathSnapshot.value(false).disallowChanges() - } - - // Used only in incremental compilation and is not applicable to KSP. - useKotlinAbiSnapshot.value(false) - } - - private fun maybeRegisterTransform(project: Project) { - // Use the same flag with KAPT, so as to share the same transformation in case KAPT and KSP are both enabled. - if (!project.extensions.extraProperties.has("KaptStructureTransformAdded")) { - val transformActionClass = - if (GradleVersion.current() >= GradleVersion.version("5.4")) - StructureTransformAction::class.java - else - - StructureTransformLegacyAction::class.java - project.dependencies.registerTransform(transformActionClass) { transformSpec -> - transformSpec.from.attribute(artifactType, "jar") - transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) - } - - project.dependencies.registerTransform(transformActionClass) { transformSpec -> - transformSpec.from.attribute(artifactType, "directory") - transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) - } - - project.extensions.extraProperties["KaptStructureTransformAdded"] = true - } - } - - // Reuse Kapt's infrastructure to compute affected names in classpath. - // This is adapted from KaptTask.findClasspathChanges. - private fun findClasspathChanges( - changes: ChangedFiles, - ): KaptClasspathChanges { - val cacheDir = kspCacheDir.asFile.get() - cacheDir.mkdirs() - - val allDataFiles = classpathStructure.files - val changedFiles = (changes as? ChangedFiles.Known)?.let { it.modified + it.removed }?.toSet() ?: allDataFiles - - val loadedPrevious = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(cacheDir) - val previousAndCurrentDataFiles = lazy { loadedPrevious.getAllDataFiles() + allDataFiles } - val allChangesRecognized = changedFiles.all { - val extension = it.extension - if (extension.isEmpty() || extension == "kt" || extension == "java" || extension == "jar" || - extension == "class" - ) { - return@all true - } - // if not a directory, Java source file, jar, or class, it has to be a structure file, in order to understand changes - it in previousAndCurrentDataFiles.value - } - val previousSnapshot = if (allChangesRecognized) { - loadedPrevious - } else { - ClasspathSnapshot.ClasspathSnapshotFactory.getEmptySnapshot() - } - - val currentSnapshot = - ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent( - cacheDir, - libraries.files.toList(), - processorClasspath.files.toList(), - allDataFiles - ) - - val classpathChanges = currentSnapshot.diff(previousSnapshot, changedFiles) - if (classpathChanges is KaptClasspathChanges.Unknown || changes is ChangedFiles.Unknown) { - clearIncCache() - cacheDir.mkdirs() - } - currentSnapshot.writeToCache() - - return classpathChanges - } - - @get:Internal - internal abstract val compileKotlinArgumentsContributor: - Property> - - init { - // kotlinc's incremental compilation isn't compatible with symbol processing in a few ways: - // * It doesn't consider private / internal changes when computing dirty sets. - // * It compiles iteratively; Sources can be compiled in different rounds. - incremental = false - - // Mute a warning from ScriptingGradleSubplugin, which tries to get `sourceSetName` before this task is - // configured. - sourceSetName.set("main") - } - - override fun setupCompilerArgs( - args: K2JVMCompilerArguments, - defaultsOnly: Boolean, - ignoreClasspathResolutionErrors: Boolean, - ) { - // Start with / copy from kotlinCompile. - compileKotlinArgumentsContributor.get().contributeArguments( - args, - compilerArgumentsConfigurationFlags( - defaultsOnly, - ignoreClasspathResolutionErrors - ) - ) - if (blockOtherCompilerPlugins) { - args.blockOtherPlugins(overridePluginClasspath) - } - args.addPluginOptions(options.get()) - args.destinationAsFile = destination - args.allowNoSourceFiles = true - args.useK2 = false - } - - // Overrding an internal function is hacky. - // TODO: Ask upstream to open it. - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") - fun `callCompilerAsync$kotlin_gradle_plugin_common`( - args: K2JVMCompilerArguments, - kotlinSources: Set, - inputChanges: InputChanges, - taskOutputsBackup: TaskOutputsBackup? - ) { - val changedFiles = getChangedFiles(inputChanges, incrementalProps) - if (isKspIncremental) { - if (isIntermoduleIncremental) { - // findClasspathChanges may clear caches, if there are - // 1. unknown changes, or - // 2. changes in annotation processors. - val classpathChanges = findClasspathChanges(changedFiles) - args.addChangedClasses(classpathChanges) - } else { - if (changedFiles.hasNonSourceChange()) { - clearIncCache() - } - } - } else { - clearIncCache() - } - args.addChangedFiles(changedFiles) - super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) - } - - override fun skipCondition(): Boolean = sources.isEmpty && javaSources.isEmpty - - override val incrementalProps: List - get() = listOf( - sources, - javaSources, - commonSourceSet, - classpathSnapshotProperties.classpath, - classpathSnapshotProperties.classpathSnapshot - ) - - @get:InputFiles - @get:SkipWhenEmpty - @get:IgnoreEmptyDirectories - @get:PathSensitive(PathSensitivity.RELATIVE) - override val sources: FileCollection = super.sources.filter { - !destination.isParentOf(it) - } - - @get:InputFiles - @get:SkipWhenEmpty - @get:IgnoreEmptyDirectories - @get:PathSensitive(PathSensitivity.RELATIVE) - override val javaSources: FileCollection = super.javaSources.filter { - !destination.isParentOf(it) - } -} - -@CacheableTask -abstract class KspTaskJS @Inject constructor( - objectFactory: ObjectFactory, - workerExecutor: WorkerExecutor -) : Kotlin2JsCompile(KotlinJsOptionsImpl(), objectFactory, workerExecutor), KspTask { - private val backendSelectionArgs = listOf( - "-Xir-only", - "-Xir-produce-js", - "-Xir-produce-klib-dir", - "-Xir-produce-klib-file" - ) - - override fun configureCompilation( - kotlinCompilation: KotlinCompilationData<*>, - kotlinCompile: AbstractKotlinCompile<*>, - ) { - kotlinCompile as Kotlin2JsCompile - kotlinOptions.freeCompilerArgs = kotlinCompile.kotlinOptions.freeCompilerArgs.filter { - it in backendSelectionArgs - } - val providerFactory = kotlinCompile.project.providers - compileKotlinArgumentsContributor.set( - providerFactory.provider { - kotlinCompile.abstractKotlinCompileArgumentsContributor - } - ) - } - - @get:Internal - internal abstract val compileKotlinArgumentsContributor: - Property> - - init { - // kotlinc's incremental compilation isn't compatible with symbol processing in a few ways: - // * It doesn't consider private / internal changes when computing dirty sets. - // * It compiles iteratively; Sources can be compiled in different rounds. - incremental = false - } - - override fun setupCompilerArgs( - args: K2JSCompilerArguments, - defaultsOnly: Boolean, - ignoreClasspathResolutionErrors: Boolean, - ) { - // Start with / copy from kotlinCompile. - args.fillDefaultValues() - compileKotlinArgumentsContributor.get().contributeArguments( - args, - compilerArgumentsConfigurationFlags( - defaultsOnly, - ignoreClasspathResolutionErrors - ) - ) - if (blockOtherCompilerPlugins) { - args.blockOtherPlugins(overridePluginClasspath) - } - args.addPluginOptions(options.get()) - args.outputFile = File(destination, "dummyOutput.js").canonicalPath - kotlinOptions.copyFreeCompilerArgsToArgs(args) - args.useK2 = false - } - - // Overrding an internal function is hacky. - // TODO: Ask upstream to open it. - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") - fun `callCompilerAsync$kotlin_gradle_plugin_common`( - args: K2JSCompilerArguments, - kotlinSources: Set, - inputChanges: InputChanges, - taskOutputsBackup: TaskOutputsBackup? - ) { - val changedFiles = getChangedFiles(inputChanges, incrementalProps) - if (!isKspIncremental || changedFiles.hasNonSourceChange()) { - clearIncCache() - } else { - args.addChangedFiles(changedFiles) - } - super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) - } - - // Overrding an internal function is hacky. - // TODO: Ask upstream to open it. - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") - fun `isIncrementalCompilationEnabled$kotlin_gradle_plugin_common`(): Boolean = false - - @get:InputFiles - @get:SkipWhenEmpty - @get:IgnoreEmptyDirectories - @get:PathSensitive(PathSensitivity.RELATIVE) - override val sources: FileCollection = super.sources.filter { - !destination.isParentOf(it) - } -} - -@CacheableTask -abstract class KspTaskMetadata @Inject constructor( - workerExecutor: WorkerExecutor, - objectFactory: ObjectFactory -) : KotlinCompileCommon(KotlinMultiplatformCommonOptionsImpl(), workerExecutor, objectFactory), KspTask { - override fun configureCompilation( - kotlinCompilation: KotlinCompilationData<*>, - kotlinCompile: AbstractKotlinCompile<*>, - ) { - kotlinCompile as KotlinCompileCommon - val providerFactory = kotlinCompile.project.providers - compileKotlinArgumentsContributor.set( - providerFactory.provider { - kotlinCompile.abstractKotlinCompileArgumentsContributor - } - ) - } - - @get:Internal - internal abstract val compileKotlinArgumentsContributor: - Property> - - init { - // kotlinc's incremental compilation isn't compatible with symbol processing in a few ways: - // * It doesn't consider private / internal changes when computing dirty sets. - // * It compiles iteratively; Sources can be compiled in different rounds. - incremental = false - } - - override fun setupCompilerArgs( - args: K2MetadataCompilerArguments, - defaultsOnly: Boolean, - ignoreClasspathResolutionErrors: Boolean, - ) { - // Start with / copy from kotlinCompile. - args.apply { fillDefaultValues() } - compileKotlinArgumentsContributor.get().contributeArguments( - args, - compilerArgumentsConfigurationFlags( - defaultsOnly, - ignoreClasspathResolutionErrors - ) - ) - if (blockOtherCompilerPlugins) { - args.blockOtherPlugins(overridePluginClasspath) - } - args.addPluginOptions(options.get()) - args.destination = destination.canonicalPath - val classpathList = libraries.files.filter { it.exists() }.toMutableList() - args.classpath = classpathList.joinToString(File.pathSeparator) - args.friendPaths = friendPaths.files.map { it.absolutePath }.toTypedArray() - args.refinesPaths = refinesMetadataPaths.map { it.absolutePath }.toTypedArray() - args.expectActualLinker = true - args.useK2 = false - } - - // Overrding an internal function is hacky. - // TODO: Ask upstream to open it. - @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") - fun `callCompilerAsync$kotlin_gradle_plugin_common`( - args: K2MetadataCompilerArguments, - kotlinSources: Set, - inputChanges: InputChanges, - taskOutputsBackup: TaskOutputsBackup? - ) { - val changedFiles = getChangedFiles(inputChanges, incrementalProps) - if (!isKspIncremental || changedFiles.hasNonSourceChange()) { - clearIncCache() - } else { - args.addChangedFiles(changedFiles) - } - super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) - } - - @get:InputFiles - @get:SkipWhenEmpty - @get:IgnoreEmptyDirectories - @get:PathSensitive(PathSensitivity.RELATIVE) - override val sources: FileCollection = super.sources.filter { - !destination.isParentOf(it) - } -} - -@CacheableTask -abstract class KspTaskNative @Inject constructor( - compilation: KotlinNativeCompilationData<*>, - objectFactory: ObjectFactory, - providerFactory: ProviderFactory, - execOperations: ExecOperations -) : KotlinNativeCompile(compilation, objectFactory, providerFactory, execOperations), KspTask { - override val additionalCompilerOptions: Provider> - get() { - return project.provider { - val kspOptions = options.get().flatMap { listOf("-P", it.toArg()) } - super.additionalCompilerOptions.get() + kspOptions - } - } - - override var compilerPluginClasspath: FileCollection? = null - get() { - if (blockOtherCompilerPlugins) { - field = overridePluginClasspath - } - return field - } - - override fun configureCompilation( - kotlinCompilation: KotlinCompilationData<*>, - kotlinCompile: AbstractKotlinCompile<*>, - ) = Unit - - // KotlinNativeCompile doesn't support Gradle incremental compilation. Therefore, there is no information about - // new / changed / removed files. - // Long term solution: contribute to upstream to support incremental compilation. - // Short term workaround: declare a @TaskAction function and call super.compile(). - // Use a name that gets sorted in the front just in case. `_$` is lower, but it might be too hacky. - @TaskAction - fun _0() { - options.get().single { it.key == "kspOutputDir" }.value.let { - File(it).deleteRecursively() - } - super.compile() - } - - @get:InputFiles - @get:SkipWhenEmpty - @get:IgnoreEmptyDirectories - @get:PathSensitive(PathSensitivity.RELATIVE) - override val sources: FileCollection = super.sources.filter { - !destination.isParentOf(it) - } -} - -// This forces rebuild. -private fun KspTask.clearIncCache() { - kspCacheDir.get().asFile.deleteRecursively() -} - -private fun ChangedFiles.hasNonSourceChange(): Boolean { - if (this !is ChangedFiles.Known) - return true - - return !(this.modified + this.removed).all { - it.isKotlinFile(listOf("kt")) || it.isJavaFile() - } -} - -fun CommonCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { - if (changed is KaptClasspathChanges.Known) { - changed.names.map { it.replace('/', '.').replace('$', '.') }.ifNotEmpty { - addPluginOptions(listOf(SubpluginOption("changedClasses", joinToString(":")))) - } - } -} - -fun SubpluginOption.toArg() = "plugin:${KspGradleSubplugin.KSP_PLUGIN_ID}:$key=$value" - -fun CommonCompilerArguments.addPluginOptions(options: List) { - pluginOptions = (options.map { it.toArg() } + pluginOptions!!).toTypedArray() -} - -fun CommonCompilerArguments.addChangedFiles(changedFiles: ChangedFiles) { - if (changedFiles is ChangedFiles.Known) { - val options = mutableListOf() - changedFiles.modified.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { - options += SubpluginOption("knownModified", map { it.path }.joinToString(File.pathSeparator)) - } - changedFiles.removed.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { - options += SubpluginOption("knownRemoved", map { it.path }.joinToString(File.pathSeparator)) - } - options.ifNotEmpty { addPluginOptions(this) } - } -} - -private fun CommonCompilerArguments.blockOtherPlugins(kspPluginClasspath: FileCollection) { - pluginClasspaths = kspPluginClasspath.map { it.canonicalPath }.toTypedArray() - pluginOptions = arrayOf() -} - -// TODO: Move into dumpArgs after the compiler supports local function in inline functions. -private inline fun T.toPair(property: KProperty1): Pair { - @Suppress("UNCHECKED_CAST") - val value = (property as KProperty1).get(this) - return property.name to if (value is Array<*>) - value.asList().toString() - else - value.toString() -} - -@Suppress("unused") -internal inline fun dumpArgs(args: T): Map { - @Suppress("UNCHECKED_CAST") - val argumentProperties = - args::class.members.mapNotNull { member -> - (member as? KProperty1)?.takeIf { it.annotations.any { ann -> ann is Argument } } - } - - return argumentProperties.associate(args::toPair).toSortedMap() -} - -internal fun File.isParentOf(childCandidate: File): Boolean { - val parentPath = Paths.get(this.absolutePath).normalize() - val childCandidatePath = Paths.get(childCandidate.absolutePath).normalize() - - return childCandidatePath.startsWith(parentPath) } diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/KspTaskFactory.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/KspTaskFactory.kt new file mode 100644 index 0000000000..ef60e13bf6 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/KspTaskFactory.kt @@ -0,0 +1,70 @@ +package com.google.devtools.ksp.gradle.tasks + +import com.google.devtools.ksp.gradle.KspExtension +import com.google.devtools.ksp.gradle.tasks.inherited.InheritedTasks +import com.google.devtools.ksp.gradle.tasks.standalone.StandaloneTasks +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration +import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool +import java.io.File + +object KspTaskFactory { + private fun getKspTaskCreator(project: Project): KspTaskCreator { + return if (project.findProperty("ksp.compiler.runner")?.toString() == "standalone") { + StandaloneTasks + } else { + InheritedTasks + } + } + + fun createKspTask( + project: Project, + kotlinCompilation: KotlinCompilation<*>, + kotlinCompileTask: AbstractKotlinCompileTool<*>, + kspExtension: KspExtension, + kspConfigurations: List, + kspTaskName: String, + target: String, + sourceSetName: String, + classOutputDir: File, + javaOutputDir: File, + kotlinOutputDir: File, + resourceOutputDir: File, + kspOutputDir: File, + ): TaskProvider = getKspTaskCreator(project).createKspTask( + project, + kotlinCompilation, + kotlinCompileTask, + kspExtension, + kspConfigurations, + kspTaskName, + target, + sourceSetName, + classOutputDir, + javaOutputDir, + kotlinOutputDir, + resourceOutputDir, + kspOutputDir + ) +} + +interface KspTaskCreator { + fun createKspTask( + project: Project, + kotlinCompilation: KotlinCompilation<*>, + kotlinCompileTask: AbstractKotlinCompileTool<*>, + kspExtension: KspExtension, + kspConfigurations: List, + kspTaskName: String, + target: String, + sourceSetName: String, + classOutputDir: File, + javaOutputDir: File, + kotlinOutputDir: File, + resourceOutputDir: File, + kspOutputDir: File, + ): TaskProvider +} diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/inherited/InheritedTasks.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/inherited/InheritedTasks.kt new file mode 100644 index 0000000000..5bc8f35914 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/inherited/InheritedTasks.kt @@ -0,0 +1,776 @@ +/* + * Copyright 2022 Google LLC + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package com.google.devtools.ksp.gradle.tasks.inherited + +import com.google.devtools.ksp.gradle.KspExtension +import com.google.devtools.ksp.gradle.KspGradleSubplugin +import com.google.devtools.ksp.gradle.KspGradleSubplugin.Companion.getKspCachesDir +import com.google.devtools.ksp.gradle.KspTask +import com.google.devtools.ksp.gradle.tasks.KspTaskCreator +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration +import org.gradle.api.attributes.Attribute +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.FileCollection +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.IgnoreEmptyDirectories +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.SkipWhenEmpty +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskProvider +import org.gradle.process.ExecOperations +import org.gradle.util.GradleVersion +import org.gradle.work.Incremental +import org.gradle.work.InputChanges +import org.gradle.workers.WorkerExecutor +import org.jetbrains.kotlin.cli.common.arguments.Argument +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments +import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptionsImpl +import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformCommonOptionsImpl +import org.jetbrains.kotlin.gradle.dsl.copyFreeCompilerArgsToArgs +import org.jetbrains.kotlin.gradle.dsl.fillDefaultValues +import org.jetbrains.kotlin.gradle.internal.CompilerArgumentsContributor +import org.jetbrains.kotlin.gradle.internal.compilerArgumentsConfigurationFlags +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.CLASS_STRUCTURE_ARTIFACT_TYPE +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.ClasspathSnapshot +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptClasspathChanges +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.StructureTransformAction +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.StructureTransformLegacyAction +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.SubpluginOption +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinCompilationData +import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinNativeCompilationData +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile +import org.jetbrains.kotlin.gradle.tasks.TaskOutputsBackup +import org.jetbrains.kotlin.gradle.tasks.configuration.AbstractKotlinCompileConfig +import org.jetbrains.kotlin.incremental.ChangedFiles +import org.jetbrains.kotlin.incremental.destinationAsFile +import org.jetbrains.kotlin.incremental.isJavaFile +import org.jetbrains.kotlin.incremental.isKotlinFile +import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import java.io.File +import java.nio.file.Paths +import java.util.concurrent.Callable +import javax.inject.Inject +import kotlin.reflect.KProperty1 + +@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") +internal class Configurator : AbstractKotlinCompileConfig> { + constructor(compilation: KotlinCompilationData<*>, kotlinCompile: AbstractKotlinCompile<*>) : super(compilation) { + configureTask { task -> + if (task is KspTaskJvm) { + // Assign ownModuleName different from kotlin compilation to + // work around https://github.com/google/ksp/issues/647 + // This will not be necessary once https://youtrack.jetbrains.com/issue/KT-45777 lands + task.ownModuleName.value(kotlinCompile.ownModuleName.map { "$it-ksp" }) + } + if (task is KspTaskJS) { + val libraryCacheService = project.rootProject.gradle.sharedServices.registerIfAbsent( + Kotlin2JsCompile.LibraryFilterCachingService::class.java.canonicalName + + "_${Kotlin2JsCompile.LibraryFilterCachingService::class.java.classLoader.hashCode()}", + Kotlin2JsCompile.LibraryFilterCachingService::class.java + ) {} + task.libraryCache.set(libraryCacheService).also { task.libraryCache.disallowChanges() } + task.pluginClasspath.setFrom(objectFactory.fileCollection()) + } + } + } +} + +object InheritedTasks : KspTaskCreator { + override fun createKspTask( + project: Project, + kotlinCompilation: KotlinCompilation<*>, + kotlinCompileTask: AbstractKotlinCompileTool<*>, + kspExtension: KspExtension, + kspConfigurations: List, + kspTaskName: String, + target: String, + sourceSetName: String, + classOutputDir: File, + javaOutputDir: File, + kotlinOutputDir: File, + resourceOutputDir: File, + kspOutputDir: File, + ): TaskProvider { + + val kspClasspathConfiguration = project.configurations.getByName( + KspGradleSubplugin.KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME + ) + + fun configureAsKspTask(kspTask: KspTaskInherited, isIncremental: Boolean) { + // depends on the processor; if the processor changes, it needs to be reprocessed. + val processorClasspath = project.configurations.maybeCreate("${kspTaskName}ProcessorClasspath") + .extendsFrom(*kspConfigurations.toTypedArray()) + kspTask.processorClasspath.from(processorClasspath) + kspTask.dependsOn(processorClasspath.buildDependencies) + + kspTask.options.addAll( + kspTask.project.provider { + KspGradleSubplugin.getSubpluginOptions( + project, + kspExtension, + processorClasspath, + sourceSetName, + target, + isIncremental, + kspExtension.allWarningsAsErrors + ) + } + ) + kspTask.commandLineArgumentProviders.addAll(kspExtension.commandLineArgumentProviders) + kspTask.destination = kspOutputDir + kspTask.blockOtherCompilerPlugins = kspExtension.blockOtherCompilerPlugins + kspTask.apOptions.value(kspExtension.arguments).disallowChanges() + kspTask.kspCacheDir.fileValue(getKspCachesDir(project, sourceSetName, target)).disallowChanges() + + if (kspExtension.blockOtherCompilerPlugins) { + kspTask.overridePluginClasspath.from(kspClasspathConfiguration) + } + kspTask.isKspIncremental = isIncremental + } + + fun configureAsAbstractKotlinCompileTool(kspTask: AbstractKotlinCompileTool<*>) { + kspTask.destinationDirectory.set(kspOutputDir) + kspTask.outputs.dirs( + kotlinOutputDir, + javaOutputDir, + classOutputDir, + resourceOutputDir + ) + + if (kspExtension.allowSourcesFromOtherPlugins) { + val deps = kotlinCompileTask.dependsOn.filterNot { + it.safeAs>()?.name == kspTaskName || + it.safeAs()?.name == kspTaskName + } + kspTask.dependsOn(deps) + kspTask.setSource(kotlinCompileTask.sources) + if (kotlinCompileTask is KotlinCompile) { + kspTask.setSource(kotlinCompileTask.javaSources) + } + } else { + kotlinCompilation.allKotlinSourceSets.forEach { sourceSet -> + kspTask.setSource(sourceSet.kotlin) + } + if (kotlinCompilation is KotlinCommonCompilation) { + kspTask.setSource(kotlinCompilation.defaultSourceSet.kotlin) + } + } + + // Don't support binary generation for non-JVM platforms yet. + // FIXME: figure out how to add user generated libraries. + if (kspTask is KspTaskJvm) { + kotlinCompilation.output.classesDirs.from(classOutputDir) + } + } + val kspTaskProvider = when (kotlinCompileTask) { + is AbstractKotlinCompile<*> -> { + val kspTaskClass = when (kotlinCompileTask) { + is KotlinCompile -> KspTaskJvm::class.java + is Kotlin2JsCompile -> KspTaskJS::class.java + is KotlinCompileCommon -> KspTaskMetadata::class.java + else -> throw IllegalArgumentException("Unknown Kotlin compilation task $kotlinCompileTask") + } + val isIncremental = project.findProperty("ksp.incremental")?.toString()?.toBoolean() ?: true + project.tasks.register(kspTaskName, kspTaskClass) { kspTask -> + configureAsKspTask(kspTask, isIncremental) + configureAsAbstractKotlinCompileTool(kspTask) + + kspTask.libraries.setFrom(kotlinCompileTask.project.files(Callable { kotlinCompileTask.libraries })) + kspTask.configureCompilation( + kotlinCompilation as KotlinCompilationData<*>, + kotlinCompileTask, + ) + } + } + is KotlinNativeCompile -> { + val kspTaskClass = KspTaskNative::class.java + project.tasks.register(kspTaskName, kspTaskClass, kotlinCompileTask.compilation).apply { + configure { kspTask -> + kspTask.onlyIf { + kotlinCompileTask.compilation.konanTarget.enabledOnCurrentHost + } + configureAsKspTask(kspTask, false) + configureAsAbstractKotlinCompileTool(kspTask) + + // KotlinNativeCompile computes -Xplugin=... from compilerPluginClasspath. + kspTask.compilerPluginClasspath = kspClasspathConfiguration + kspTask.commonSources.from(kotlinCompileTask.commonSources) + kspTask.compilerPluginOptions.addPluginArgument(kotlinCompileTask.compilerPluginOptions) + } + } + } + else -> throw IllegalArgumentException("Unknown Kotlin compilation task $kotlinCompileTask") + } + + kotlinCompileTask.safeAs>()?.let { + Configurator(kotlinCompilation as KotlinCompilationData<*>, kotlinCompileTask as AbstractKotlinCompile<*>) + .execute(kspTaskProvider as TaskProvider>) + } + + return kspTaskProvider + } +} + +private val artifactType = Attribute.of("artifactType", String::class.java) + +interface KspTaskInherited : KspTask { + fun configureCompilation( + kotlinCompilation: KotlinCompilationData<*>, + kotlinCompile: AbstractKotlinCompile<*>, + ) +} + +@CacheableTask +abstract class KspTaskJvm @Inject constructor( + workerExecutor: WorkerExecutor, + objectFactory: ObjectFactory +) : KotlinCompile(KotlinJvmOptionsImpl(), workerExecutor, objectFactory), KspTaskInherited { + @get:PathSensitive(PathSensitivity.NONE) + @get:Optional + @get:InputFiles + @get:Incremental + abstract val classpathStructure: ConfigurableFileCollection + + @get:Input + var isIntermoduleIncremental: Boolean = false + + override fun configureCompilation( + kotlinCompilation: KotlinCompilationData<*>, + kotlinCompile: AbstractKotlinCompile<*>, + ) { + kotlinCompile as KotlinCompile + val providerFactory = kotlinCompile.project.providers + compileKotlinArgumentsContributor.set( + providerFactory.provider { + kotlinCompile.compilerArgumentsContributor + } + ) + + isIntermoduleIncremental = + (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) && + isKspIncremental + if (isIntermoduleIncremental) { + val classStructureIfIncremental = project.configurations.detachedConfiguration( + project.dependencies.create(project.files(project.provider { kotlinCompile.libraries })) + ) + maybeRegisterTransform(project) + + classpathStructure.from( + classStructureIfIncremental.incoming.artifactView { viewConfig -> + viewConfig.attributes.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + }.files + ).disallowChanges() + classpathSnapshotProperties.useClasspathSnapshot.value(true).disallowChanges() + } else { + classpathSnapshotProperties.useClasspathSnapshot.value(false).disallowChanges() + } + + // Used only in incremental compilation and is not applicable to KSP. + useKotlinAbiSnapshot.value(false) + } + + private fun maybeRegisterTransform(project: Project) { + // Use the same flag with KAPT, so as to share the same transformation in case KAPT and KSP are both enabled. + if (!project.extensions.extraProperties.has("KaptStructureTransformAdded")) { + val transformActionClass = + if (GradleVersion.current() >= GradleVersion.version("5.4")) + StructureTransformAction::class.java + else + + StructureTransformLegacyAction::class.java + project.dependencies.registerTransform(transformActionClass) { transformSpec -> + transformSpec.from.attribute(artifactType, "jar") + transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + } + + project.dependencies.registerTransform(transformActionClass) { transformSpec -> + transformSpec.from.attribute(artifactType, "directory") + transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + } + + project.extensions.extraProperties["KaptStructureTransformAdded"] = true + } + } + + // Reuse Kapt's infrastructure to compute affected names in classpath. + // This is adapted from KaptTask.findClasspathChanges. + private fun findClasspathChanges( + changes: ChangedFiles, + ): KaptClasspathChanges { + val cacheDir = kspCacheDir.asFile.get() + cacheDir.mkdirs() + + val allDataFiles = classpathStructure.files + val changedFiles = (changes as? ChangedFiles.Known)?.let { it.modified + it.removed }?.toSet() ?: allDataFiles + + val loadedPrevious = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(cacheDir) + val previousAndCurrentDataFiles = lazy { loadedPrevious.getAllDataFiles() + allDataFiles } + val allChangesRecognized = changedFiles.all { + val extension = it.extension + if (extension.isEmpty() || extension == "kt" || extension == "java" || extension == "jar" || + extension == "class" + ) { + return@all true + } + // if not a directory, Java source file, jar, or class, it has to be a structure file, in order to understand changes + it in previousAndCurrentDataFiles.value + } + val previousSnapshot = if (allChangesRecognized) { + loadedPrevious + } else { + ClasspathSnapshot.ClasspathSnapshotFactory.getEmptySnapshot() + } + + val currentSnapshot = + ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent( + cacheDir, + libraries.files.toList(), + processorClasspath.files.toList(), + allDataFiles + ) + + val classpathChanges = currentSnapshot.diff(previousSnapshot, changedFiles) + if (classpathChanges is KaptClasspathChanges.Unknown || changes is ChangedFiles.Unknown) { + clearIncCache() + cacheDir.mkdirs() + } + currentSnapshot.writeToCache() + + return classpathChanges + } + + @get:Internal + internal abstract val compileKotlinArgumentsContributor: + Property> + + init { + // kotlinc's incremental compilation isn't compatible with symbol processing in a few ways: + // * It doesn't consider private / internal changes when computing dirty sets. + // * It compiles iteratively; Sources can be compiled in different rounds. + incremental = false + + // Mute a warning from ScriptingGradleSubplugin, which tries to get `sourceSetName` before this task is + // configured. + sourceSetName.set("main") + } + + override fun setupCompilerArgs( + args: K2JVMCompilerArguments, + defaultsOnly: Boolean, + ignoreClasspathResolutionErrors: Boolean, + ) { + // Start with / copy from kotlinCompile. + compileKotlinArgumentsContributor.get().contributeArguments( + args, + compilerArgumentsConfigurationFlags( + defaultsOnly, + ignoreClasspathResolutionErrors + ) + ) + if (blockOtherCompilerPlugins) { + args.blockOtherPlugins(overridePluginClasspath) + } + args.addPluginOptions(options.get()) + args.destinationAsFile = destination + args.allowNoSourceFiles = true + args.useK2 = false + } + + // Overrding an internal function is hacky. + // TODO: Ask upstream to open it. + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") + fun `callCompilerAsync$kotlin_gradle_plugin_common`( + args: K2JVMCompilerArguments, + kotlinSources: Set, + inputChanges: InputChanges, + taskOutputsBackup: TaskOutputsBackup? + ) { + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (isKspIncremental) { + if (isIntermoduleIncremental) { + // findClasspathChanges may clear caches, if there are + // 1. unknown changes, or + // 2. changes in annotation processors. + val classpathChanges = findClasspathChanges(changedFiles) + args.addChangedClasses(classpathChanges) + } else { + if (changedFiles.hasNonSourceChange()) { + clearIncCache() + } + } + } else { + clearIncCache() + } + args.addChangedFiles(changedFiles) + super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) + } + + override fun skipCondition(): Boolean = sources.isEmpty && javaSources.isEmpty + + override val incrementalProps: List + get() = listOf( + sources, + javaSources, + commonSourceSet, + classpathSnapshotProperties.classpath, + classpathSnapshotProperties.classpathSnapshot + ) + + @get:InputFiles + @get:SkipWhenEmpty + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + override val sources: FileCollection = super.sources.filter { + !destination.isParentOf(it) + } + + @get:InputFiles + @get:SkipWhenEmpty + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + override val javaSources: FileCollection = super.javaSources.filter { + !destination.isParentOf(it) + } +} + +@CacheableTask +abstract class KspTaskJS @Inject constructor( + @get:Internal val objectFactory: ObjectFactory, + workerExecutor: WorkerExecutor +) : Kotlin2JsCompile(KotlinJsOptionsImpl(), objectFactory, workerExecutor), KspTaskInherited { + private val backendSelectionArgs = listOf( + "-Xir-only", + "-Xir-produce-js", + "-Xir-produce-klib-dir", + "-Xir-produce-klib-file" + ) + + override fun configureCompilation( + kotlinCompilation: KotlinCompilationData<*>, + kotlinCompile: AbstractKotlinCompile<*>, + ) { + kotlinCompile as Kotlin2JsCompile + kotlinOptions.freeCompilerArgs = kotlinCompile.kotlinOptions.freeCompilerArgs.filter { + it in backendSelectionArgs + } + val providerFactory = kotlinCompile.project.providers + compileKotlinArgumentsContributor.set( + providerFactory.provider { + kotlinCompile.abstractKotlinCompileArgumentsContributor + } + ) + } + + @get:Internal + internal abstract val compileKotlinArgumentsContributor: + Property> + + init { + // kotlinc's incremental compilation isn't compatible with symbol processing in a few ways: + // * It doesn't consider private / internal changes when computing dirty sets. + // * It compiles iteratively; Sources can be compiled in different rounds. + incremental = false + } + + override fun setupCompilerArgs( + args: K2JSCompilerArguments, + defaultsOnly: Boolean, + ignoreClasspathResolutionErrors: Boolean, + ) { + // Start with / copy from kotlinCompile. + args.fillDefaultValues() + compileKotlinArgumentsContributor.get().contributeArguments( + args, + compilerArgumentsConfigurationFlags( + defaultsOnly, + ignoreClasspathResolutionErrors + ) + ) + if (blockOtherCompilerPlugins) { + args.blockOtherPlugins(overridePluginClasspath) + } + args.addPluginOptions(options.get()) + args.outputFile = File(destination, "dummyOutput.js").canonicalPath + kotlinOptions.copyFreeCompilerArgsToArgs(args) + args.useK2 = false + } + + // Overrding an internal function is hacky. + // TODO: Ask upstream to open it. + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") + fun `callCompilerAsync$kotlin_gradle_plugin_common`( + args: K2JSCompilerArguments, + kotlinSources: Set, + inputChanges: InputChanges, + taskOutputsBackup: TaskOutputsBackup? + ) { + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (!isKspIncremental || changedFiles.hasNonSourceChange()) { + clearIncCache() + } else { + args.addChangedFiles(changedFiles) + } + super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) + } + + // Overrding an internal function is hacky. + // TODO: Ask upstream to open it. + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") + fun `isIncrementalCompilationEnabled$kotlin_gradle_plugin_common`(): Boolean = false + + @get:InputFiles + @get:SkipWhenEmpty + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + override val sources: FileCollection = super.sources.filter { + !destination.isParentOf(it) + } +} + +@CacheableTask +abstract class KspTaskMetadata @Inject constructor( + workerExecutor: WorkerExecutor, + objectFactory: ObjectFactory +) : KotlinCompileCommon(KotlinMultiplatformCommonOptionsImpl(), workerExecutor, objectFactory), KspTaskInherited { + override fun configureCompilation( + kotlinCompilation: KotlinCompilationData<*>, + kotlinCompile: AbstractKotlinCompile<*>, + ) { + kotlinCompile as KotlinCompileCommon + val providerFactory = kotlinCompile.project.providers + compileKotlinArgumentsContributor.set( + providerFactory.provider { + kotlinCompile.abstractKotlinCompileArgumentsContributor + } + ) + } + + @get:Internal + internal abstract val compileKotlinArgumentsContributor: + Property> + + init { + // kotlinc's incremental compilation isn't compatible with symbol processing in a few ways: + // * It doesn't consider private / internal changes when computing dirty sets. + // * It compiles iteratively; Sources can be compiled in different rounds. + incremental = false + } + + override fun setupCompilerArgs( + args: K2MetadataCompilerArguments, + defaultsOnly: Boolean, + ignoreClasspathResolutionErrors: Boolean, + ) { + // Start with / copy from kotlinCompile. + args.apply { fillDefaultValues() } + compileKotlinArgumentsContributor.get().contributeArguments( + args, + compilerArgumentsConfigurationFlags( + defaultsOnly, + ignoreClasspathResolutionErrors + ) + ) + if (blockOtherCompilerPlugins) { + args.blockOtherPlugins(overridePluginClasspath) + } + args.addPluginOptions(options.get()) + args.destination = destination.canonicalPath + val classpathList = libraries.files.filter { it.exists() }.toMutableList() + args.classpath = classpathList.joinToString(File.pathSeparator) + args.friendPaths = friendPaths.files.map { it.absolutePath }.toTypedArray() + args.refinesPaths = refinesMetadataPaths.map { it.absolutePath }.toTypedArray() + args.expectActualLinker = true + args.useK2 = false + } + + // Overrding an internal function is hacky. + // TODO: Ask upstream to open it. + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") + fun `callCompilerAsync$kotlin_gradle_plugin_common`( + args: K2MetadataCompilerArguments, + kotlinSources: Set, + inputChanges: InputChanges, + taskOutputsBackup: TaskOutputsBackup? + ) { + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (!isKspIncremental || changedFiles.hasNonSourceChange()) { + clearIncCache() + } else { + args.addChangedFiles(changedFiles) + } + super.callCompilerAsync(args, kotlinSources, inputChanges, taskOutputsBackup) + } + + @get:InputFiles + @get:SkipWhenEmpty + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + override val sources: FileCollection = super.sources.filter { + !destination.isParentOf(it) + } +} + +@CacheableTask +abstract class KspTaskNative @Inject constructor( + compilation: KotlinNativeCompilationData<*>, + objectFactory: ObjectFactory, + providerFactory: ProviderFactory, + execOperations: ExecOperations +) : KotlinNativeCompile(compilation, objectFactory, providerFactory, execOperations), KspTaskInherited { + override val additionalCompilerOptions: Provider> + get() { + return project.provider { + val kspOptions = options.get().flatMap { listOf("-P", it.toArg()) } + super.additionalCompilerOptions.get() + kspOptions + } + } + + override var compilerPluginClasspath: FileCollection? = null + get() { + if (blockOtherCompilerPlugins) { + field = overridePluginClasspath + } + return field + } + + override fun configureCompilation( + kotlinCompilation: KotlinCompilationData<*>, + kotlinCompile: AbstractKotlinCompile<*>, + ) = Unit + + // KotlinNativeCompile doesn't support Gradle incremental compilation. Therefore, there is no information about + // new / changed / removed files. + // Long term solution: contribute to upstream to support incremental compilation. + // Short term workaround: declare a @TaskAction function and call super.compile(). + // Use a name that gets sorted in the front just in case. `_$` is lower, but it might be too hacky. + @TaskAction + fun _0() { + options.get().single { it.key == "kspOutputDir" }.value.let { + File(it).deleteRecursively() + } + super.compile() + } + + @get:InputFiles + @get:SkipWhenEmpty + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + override val sources: FileCollection = super.sources.filter { + !destination.isParentOf(it) + } +} + +// This forces rebuild. +private fun KspTaskInherited.clearIncCache() { + kspCacheDir.get().asFile.deleteRecursively() +} + +private fun ChangedFiles.hasNonSourceChange(): Boolean { + if (this !is ChangedFiles.Known) + return true + + return !(this.modified + this.removed).all { + it.isKotlinFile(listOf("kt")) || it.isJavaFile() + } +} + +fun CommonCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { + if (changed is KaptClasspathChanges.Known) { + changed.names.map { it.replace('/', '.').replace('$', '.') }.ifNotEmpty { + addPluginOptions(listOf(SubpluginOption("changedClasses", joinToString(":")))) + } + } +} + +fun SubpluginOption.toArg() = "plugin:${KspGradleSubplugin.KSP_PLUGIN_ID}:$key=$value" + +fun CommonCompilerArguments.addPluginOptions(options: List) { + pluginOptions = (options.map { it.toArg() } + pluginOptions!!).toTypedArray() +} + +fun CommonCompilerArguments.addChangedFiles(changedFiles: ChangedFiles) { + if (changedFiles is ChangedFiles.Known) { + val options = mutableListOf() + changedFiles.modified.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { + options += SubpluginOption("knownModified", map { it.path }.joinToString(File.pathSeparator)) + } + changedFiles.removed.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { + options += SubpluginOption("knownRemoved", map { it.path }.joinToString(File.pathSeparator)) + } + options.ifNotEmpty { addPluginOptions(this) } + } +} + +private fun CommonCompilerArguments.blockOtherPlugins(kspPluginClasspath: FileCollection) { + pluginClasspaths = kspPluginClasspath.map { it.canonicalPath }.toTypedArray() + pluginOptions = arrayOf() +} + +// TODO: Move into dumpArgs after the compiler supports local function in inline functions. +private inline fun T.toPair(property: KProperty1): Pair { + @Suppress("UNCHECKED_CAST") + val value = (property as KProperty1).get(this) + return property.name to if (value is Array<*>) + value.asList().toString() + else + value.toString() +} + +@Suppress("unused") +internal inline fun dumpArgs(args: T): Map { + @Suppress("UNCHECKED_CAST") + val argumentProperties = + args::class.members.mapNotNull { member -> + (member as? KProperty1)?.takeIf { it.annotations.any { ann -> ann is Argument } } + } + + return argumentProperties.associate(args::toPair).toSortedMap() +} + +internal fun File.isParentOf(childCandidate: File): Boolean { + val parentPath = Paths.get(this.absolutePath).normalize() + val childCandidatePath = Paths.get(childCandidate.absolutePath).normalize() + + return childCandidatePath.startsWith(parentPath) +} diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt new file mode 100644 index 0000000000..84762327c6 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt @@ -0,0 +1,870 @@ +/* + * Copyright 2020 Google LLC + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package com.google.devtools.ksp.gradle.tasks.standalone + +import com.google.devtools.ksp.gradle.KspExtension +import com.google.devtools.ksp.gradle.KspGradleSubplugin +import com.google.devtools.ksp.gradle.KspGradleSubplugin.Companion.getKspCachesDir +import com.google.devtools.ksp.gradle.KspGradleSubplugin.Companion.getSubpluginOptions +import com.google.devtools.ksp.gradle.KspTask +import com.google.devtools.ksp.gradle.createKotlinJsCompilerRunner +import com.google.devtools.ksp.gradle.createKotlinJvmCompilerRunner +import com.google.devtools.ksp.gradle.createKotlinMetadataCompilerRunner +import com.google.devtools.ksp.gradle.createKotlinNativeCompilerRunner +import com.google.devtools.ksp.gradle.tasks.KspTaskCreator +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration +import org.gradle.api.attributes.Attribute +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Classpath +import org.gradle.api.tasks.IgnoreEmptyDirectories +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.Nested +import org.gradle.api.tasks.Optional +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.PathSensitive +import org.gradle.api.tasks.PathSensitivity +import org.gradle.api.tasks.SkipWhenEmpty +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.TaskProvider +import org.gradle.util.GradleVersion +import org.gradle.work.ChangeType +import org.gradle.work.Incremental +import org.gradle.work.InputChanges +import org.jetbrains.kotlin.cli.common.arguments.Argument +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments +import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions +import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptions +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.CLASS_STRUCTURE_ARTIFACT_TYPE +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.ClasspathSnapshot +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptClasspathChanges +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.StructureTransformAction +import org.jetbrains.kotlin.gradle.internal.kapt.incremental.StructureTransformLegacyAction +import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation +import org.jetbrains.kotlin.gradle.plugin.SubpluginOption +import org.jetbrains.kotlin.gradle.plugin.mpp.AbstractKotlinNativeCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinCommonCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJvmAndroidCompilation +import org.jetbrains.kotlin.gradle.plugin.mpp.enabledOnCurrentHost +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile +import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompileTool +import org.jetbrains.kotlin.gradle.tasks.CompileUsingKotlinDaemon +import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon +import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool +import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile +import org.jetbrains.kotlin.incremental.ChangedFiles +import org.jetbrains.kotlin.incremental.classpathAsList +import org.jetbrains.kotlin.incremental.destinationAsFile +import org.jetbrains.kotlin.incremental.isJavaFile +import org.jetbrains.kotlin.incremental.isKotlinFile +import org.jetbrains.kotlin.library.impl.isKotlinLibrary +import org.jetbrains.kotlin.utils.JsLibraryUtils +import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty +import org.jetbrains.kotlin.utils.addToStdlib.safeAs +import java.io.File +import java.nio.file.Paths +import javax.inject.Inject +import kotlin.reflect.KProperty1 + +object StandaloneTasks : KspTaskCreator { + override fun createKspTask( + project: Project, + kotlinCompilation: KotlinCompilation<*>, + kotlinCompileTask: AbstractKotlinCompileTool<*>, + kspExtension: KspExtension, + kspConfigurations: List, + kspTaskName: String, + target: String, + sourceSetName: String, + classOutputDir: File, + javaOutputDir: File, + kotlinOutputDir: File, + resourceOutputDir: File, + kspOutputDir: File, + ): TaskProvider { + + val kspClasspathConfiguration = project.configurations.getByName( + KspGradleSubplugin.KSP_PLUGIN_CLASSPATH_CONFIGURATION_NAME + ) + + fun configureKspTask(kspTask: KspTaskStandalone, kotlinCompile: KotlinCompileTool, isIncremental: Boolean) { + // depends on the processor; if the processor changes, it needs to be reprocessed. + val processorClasspath = project.configurations.maybeCreate("${kspTaskName}ProcessorClasspath") + .extendsFrom(*kspConfigurations.toTypedArray()) + kspTask.processorClasspath.from(processorClasspath) + kspTask.dependsOn(processorClasspath.buildDependencies) + + kspTask.options.addAll( + kspTask.project.provider { + getSubpluginOptions( + project, + kspExtension, + processorClasspath, + sourceSetName, + target, + isIncremental, + kspExtension.allWarningsAsErrors + ) + } + ) + kspTask.commandLineArgumentProviders.addAll(kspExtension.commandLineArgumentProviders) + kspTask.destination = kspOutputDir + kspTask.apOptions.value(kspExtension.arguments).disallowChanges() + kspTask.kspCacheDir.fileValue(getKspCachesDir(project, sourceSetName, target)).disallowChanges() + + kspTask.isKspIncremental = isIncremental + + val providers = project.providers + kspTask.outputs.dirs( + kotlinOutputDir, + javaOutputDir, + classOutputDir, + resourceOutputDir + ) + + fun FileCollection.excludeKSP(): FileCollection = filter { + !kspOutputDir.isParentOf(it) + } + + if (kspExtension.allowSourcesFromOtherPlugins) { + val deps = kotlinCompile.dependsOn.filterNot { + it.safeAs>()?.name == kspTaskName || + it.safeAs()?.name == kspTaskName + } + kspTask.dependsOn(deps) + kspTask.allSources.from(kotlinCompile.sources.excludeKSP()) + if (kotlinCompile is KotlinCompile) { + kspTask.allSources.from(kotlinCompile.javaSources.excludeKSP()) + } + } else { + kotlinCompilation.allKotlinSourceSets.forEach { sourceSet -> + kspTask.allSources.from(sourceSet.kotlin.excludeKSP()) + } + if (kotlinCompilation is KotlinCommonCompilation) { + kspTask.allSources.from(kotlinCompilation.defaultSourceSet.kotlin.excludeKSP()) + } + } + + if (kotlinCompile is KotlinNativeCompile) { + kspTask.commonSources.from(providers.provider { kotlinCompile.commonSources }) + kspTask.friendPaths.from(kotlinCompile.compilation.friendPaths) + } else if (kotlinCompile is AbstractKotlinCompile<*>) { + kspTask.commonSources.from(providers.provider { kotlinCompile.commonSourceSet }) + kspTask.friendPaths.from(kotlinCompile.friendPaths) + } else { + throw IllegalArgumentException("Unknown compilation task type") + } + kspTask.pluginClasspath.from(kspClasspathConfiguration) + kspTask.libraries.from(providers.provider { kotlinCompile.libraries.excludeKSP() }) + kspTask.kotlinApiVersion.value(kotlinCompilation.kotlinOptions.apiVersion) + kspTask.kotlinLanguageVersion.value(kotlinCompilation.kotlinOptions.languageVersion) + kspTask.verbose.value(kotlinCompilation.kotlinOptions.verbose || project.logger.isDebugEnabled) + + kspTask.configureCompilation(kotlinCompilation as KotlinCompilation, kotlinCompile) + + // Don't support binary generation for non-JVM platforms yet. + // FIXME: figure out how to add user generated libraries. + if (kspTask is KspTaskJvm) { + kotlinCompilation.output.classesDirs.from(classOutputDir) + } + } + + val kspTaskProvider = when (kotlinCompileTask) { + is KotlinCompileTool -> { + val kspTaskClass = when (kotlinCompileTask) { + is KotlinCompile -> KspTaskJvm::class.java + is Kotlin2JsCompile -> KspTaskJs::class.java + is KotlinCompileCommon -> KspTaskMetadata::class.java + is KotlinNativeCompile -> KspTaskNative::class.java + else -> throw IllegalArgumentException("Unknown Kotlin compilation task $kotlinCompileTask") + } + val isNative = kspTaskClass == KspTaskNative::class.java + val isIncremental = project.findProperty("ksp.incremental")?.toString()?.toBoolean() ?: !isNative + project.tasks.register(kspTaskName, kspTaskClass) { kspTask -> + if (isNative) { + // KotlinNativeCompile computes -Xplugin=... from compilerPluginClasspath. + kspTask.pluginClasspath.from(kspClasspathConfiguration) + // The lambda to Task.onlyIf is evaluated at evaluation time. + // It will try to serialize the entire closure and break configuration cache. + val isEnabled = + (kotlinCompilation as AbstractKotlinNativeCompilation).konanTarget.enabledOnCurrentHost + kspTask.onlyIf { isEnabled } + } + configureKspTask(kspTask, kotlinCompileTask, isIncremental) + } + } + else -> throw IllegalArgumentException("Unknown Kotlin compilation task $kotlinCompileTask") + } + + return kspTaskProvider + } +} + +private val artifactType = Attribute.of("artifactType", String::class.java) + +interface KspTaskStandalone : KspTask { + @get:Classpath + @get:Incremental + val libraries: ConfigurableFileCollection + + @get:Classpath + val pluginClasspath: ConfigurableFileCollection + + @get:InputFiles + @get:IgnoreEmptyDirectories + @get:Incremental + @get:PathSensitive(PathSensitivity.RELATIVE) + val commonSources: ConfigurableFileCollection + + @get:InputFiles + @get:SkipWhenEmpty + @get:IgnoreEmptyDirectories + @get:PathSensitive(PathSensitivity.RELATIVE) + @get:Incremental + val allSources: ConfigurableFileCollection + + @get:Input + @get:Optional + val kotlinApiVersion: Property + + @get:Input + @get:Optional + val kotlinLanguageVersion: Property + + @get:Input + val verbose: Property + + @get:Internal + val friendPaths: ConfigurableFileCollection + + fun configureCompilation( + kotlinCompilation: KotlinCompilation<*>, + kotlinCompile: KotlinCompileTool, + ) +} + +// This forces rebuild. +private fun KspTaskStandalone.clearIncCache() { + kspCacheDir.get().asFile.deleteRecursively() +} + +private fun ChangedFiles.hasNonSourceChange(): Boolean { + if (this !is ChangedFiles.Known) + return true + + return !(this.modified + this.removed).all { + it.isKotlinFile(listOf("kt")) || it.isJavaFile() + } +} + +fun CommonCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { + if (changed is KaptClasspathChanges.Known) { + changed.names.map { it.replace('/', '.').replace('$', '.') }.ifNotEmpty { + addPluginOptions(listOf(SubpluginOption("changedClasses", joinToString(":")))) + } + } +} + +fun SubpluginOption.toArg() = "plugin:${KspGradleSubplugin.KSP_PLUGIN_ID}:$key=$value" + +fun CommonCompilerArguments.addPluginOptions(options: List) { + pluginOptions = ((pluginOptions?.toList() ?: emptyList()) + options.map { it.toArg() }).toTypedArray() +} + +fun CommonCompilerArguments.addChangedFiles(changedFiles: ChangedFiles) { + if (changedFiles is ChangedFiles.Known) { + val options = mutableListOf() + changedFiles.modified.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { + options += SubpluginOption("knownModified", map { it.path }.joinToString(File.pathSeparator)) + } + changedFiles.removed.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { + options += SubpluginOption("knownRemoved", map { it.path }.joinToString(File.pathSeparator)) + } + options.ifNotEmpty { addPluginOptions(this) } + } +} + +private fun CommonCompilerArguments.blockOtherPlugins(kspPluginClasspath: FileCollection) { + pluginClasspaths = kspPluginClasspath.map { it.canonicalPath }.toTypedArray() + pluginOptions = arrayOf() +} + +// TODO: Move into dumpArgs after the compiler supports local function in inline functions. +private inline fun T.toPair(property: KProperty1): Pair { + @Suppress("UNCHECKED_CAST") + val value = property.get(this) + return property.name to if (value is Array<*>) + value.asList().toString() + else + value.toString() +} + +@Suppress("unused") +internal inline fun dumpArgs(args: T): Map { + @Suppress("UNCHECKED_CAST") + val argumentProperties = + args::class.members.mapNotNull { member -> + (member as? KProperty1)?.takeIf { it.annotations.any { ann -> ann is Argument } } + } + + return argumentProperties.associate(args::toPair).toSortedMap() +} + +internal fun File.isParentOf(childCandidate: File): Boolean { + val parentPath = Paths.get(this.absolutePath).normalize() + val childCandidatePath = Paths.get(childCandidate.absolutePath).normalize() + + return childCandidatePath.startsWith(parentPath) +} + +@CacheableTask +abstract class KspTaskJvm @Inject constructor( + objectFactory: ObjectFactory, +) : KspTaskStandalone, CompileUsingKotlinDaemon, DefaultTask() { + @get:Nested + val compilerRunner = createKotlinJvmCompilerRunner(this, objectFactory) + + @get:Input + abstract val moduleName: Property + + @get:Input + var isIntermoduleIncremental: Boolean = false + + @get:PathSensitive(PathSensitivity.NONE) + @get:Optional + @get:InputFiles + @get:Incremental + abstract val classpathStructure: ConfigurableFileCollection + + @get:Nested + abstract val classpathSnapshotProperties: ClasspathSnapshotProperties + + @get:Internal + val incrementalProps: List + get() = listOf( + allSources, + commonSources, + classpathSnapshotProperties.classpath, + classpathSnapshotProperties.classpathSnapshot + ) + + @get:Internal + abstract val noJdk: Property + + @TaskAction + fun execute(inputChanges: InputChanges) { + val args = K2JVMCompilerArguments().apply { + // Common + useK2 = false + incrementalCompilation = false + pluginClasspaths = pluginClasspath.files.map { it.absolutePath }.toTypedArray() + apiVersion = this@KspTaskJvm.kotlinApiVersion.orNull + languageVersion = this@KspTaskJvm.kotlinLanguageVersion.orNull + verbose = this@KspTaskJvm.verbose.get() + noJdk = this@KspTaskJvm.noJdk.get() + + addPluginOptions(options.get()) + + // JVM + allowNoSourceFiles = true + destinationAsFile = this@KspTaskJvm.destination + noReflect = true + noStdlib = true + classpathAsList = libraries.files.toList() + friendPaths = this@KspTaskJvm.friendPaths.map { it.absolutePath }.toTypedArray() + moduleName = this@KspTaskJvm.moduleName.get() + } + + // Clean outputs. Backups will be copied back if incremental. + // TODO: leave outputs untouched and only restore outputs if build fails. + outputs.files.forEach { + it.deleteRecursively() + } + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (isKspIncremental) { + if (isIntermoduleIncremental) { + // findClasspathChanges may clear caches, if there are + // 1. unknown changes, or + // 2. changes in annotation processors. + val classpathChanges = findClasspathChanges(changedFiles) + args.addChangedClasses(classpathChanges) + } else { + if (changedFiles.hasNonSourceChange()) { + clearIncCache() + } + } + } else { + clearIncCache() + } + args.addChangedFiles(changedFiles) + + compilerRunner.runJvmCompilerAsync( + args, + allSources.files.filter { !destination.isParentOf(it) }, + commonSources.files.filter { !destination.isParentOf(it) }, + outputs.files.toList() + ) + } + + override fun configureCompilation( + kotlinCompilation: KotlinCompilation<*>, + kotlinCompile: KotlinCompileTool, + ) { + val providers = project.providers + + kotlinCompile as AbstractKotlinCompile<*> + compilerRunner.compilerClasspath.from(providers.provider { kotlinCompile.defaultCompilerClasspath }) + compilerRunner.compilerExecutionStrategy.set(kotlinCompile.compilerExecutionStrategy) + compilerRunner.useDaemonFallbackStrategy.set(kotlinCompile.useDaemonFallbackStrategy) + compilerRunner.kotlinDaemonJvmArguments.set(kotlinCompile.kotlinDaemonJvmArguments) + + // Android compilation doesn't include JDK libraries. + // Copying the settings from KotlinCompilation is complicated and implementation dependent. So let's check + // for Android explicitly. + noJdk.value(kotlinCompilation is KotlinJvmAndroidCompilation) + + moduleName.value(kotlinCompilation.moduleName) + + isIntermoduleIncremental = + (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) && isKspIncremental + + if (isIntermoduleIncremental) { + val classStructureIfIncremental = project.configurations.detachedConfiguration( + project.dependencies.create(project.files(project.provider { kotlinCompile.libraries })) + ) + maybeRegisterTransform(project) + + classpathStructure.from( + classStructureIfIncremental.incoming.artifactView { viewConfig -> + viewConfig.attributes.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + }.files + ).disallowChanges() + classpathSnapshotProperties.useClasspathSnapshot.value(true).disallowChanges() + } else { + classpathSnapshotProperties.useClasspathSnapshot.value(false).disallowChanges() + } + } + + private fun maybeRegisterTransform(project: Project) { + // Use the same flag with KAPT, so as to share the same transformation in case KAPT and KSP are both enabled. + if (!project.extensions.extraProperties.has("KaptStructureTransformAdded")) { + val transformActionClass = + if (GradleVersion.current() >= GradleVersion.version("5.4")) + StructureTransformAction::class.java + else + + StructureTransformLegacyAction::class.java + project.dependencies.registerTransform(transformActionClass) { transformSpec -> + transformSpec.from.attribute(artifactType, "jar") + transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + } + + project.dependencies.registerTransform(transformActionClass) { transformSpec -> + transformSpec.from.attribute(artifactType, "directory") + transformSpec.to.attribute(artifactType, CLASS_STRUCTURE_ARTIFACT_TYPE) + } + + project.extensions.extraProperties["KaptStructureTransformAdded"] = true + } + } + + // Reuse Kapt's infrastructure to compute affected names in classpath. + // This is adapted from KaptTask.findClasspathChanges. + private fun findClasspathChanges( + changes: ChangedFiles, + ): KaptClasspathChanges { + val cacheDir = kspCacheDir.asFile.get() + cacheDir.mkdirs() + + val allDataFiles = classpathStructure.files + val changedFiles = (changes as? ChangedFiles.Known)?.let { it.modified + it.removed }?.toSet() ?: allDataFiles + + val loadedPrevious = ClasspathSnapshot.ClasspathSnapshotFactory.loadFrom(cacheDir) + val previousAndCurrentDataFiles = lazy { loadedPrevious.getAllDataFiles() + allDataFiles } + val allChangesRecognized = changedFiles.all { + val extension = it.extension + if (extension.isEmpty() || extension == "kt" || extension == "java" || extension == "jar" || + extension == "class" + ) { + return@all true + } + // if not a directory, Java source file, jar, or class, it has to be a structure file, in order to understand changes + it in previousAndCurrentDataFiles.value + } + val previousSnapshot = if (allChangesRecognized) { + loadedPrevious + } else { + ClasspathSnapshot.ClasspathSnapshotFactory.getEmptySnapshot() + } + + val currentSnapshot = + ClasspathSnapshot.ClasspathSnapshotFactory.createCurrent( + cacheDir, + libraries.files.toList(), + processorClasspath.files.toList(), + allDataFiles + ) + + val classpathChanges = currentSnapshot.diff(previousSnapshot, changedFiles) + if (classpathChanges is KaptClasspathChanges.Unknown || changes is ChangedFiles.Unknown) { + clearIncCache() + cacheDir.mkdirs() + } + currentSnapshot.writeToCache() + + return classpathChanges + } +} + +@CacheableTask +abstract class KspTaskJs @Inject constructor( + objectFactory: ObjectFactory, +) : KspTaskStandalone, CompileUsingKotlinDaemon, DefaultTask() { + // TODO: setup by checking target info, instead of copying from compile task + private val backendSelectionArgs = listOf( + "-Xir-only", + "-Xir-produce-js", + "-Xir-produce-klib-dir", + "-Xir-produce-klib-file", + "-Xwasm" + ) + + @get:Nested + val compilerRunner = createKotlinJsCompilerRunner(this, objectFactory) + + @get:Internal + abstract val freeArgs: ListProperty + + @get:Internal + val incrementalProps: List + get() = listOf( + allSources, + commonSources, + ) + + private fun isJsLib(file: File): Boolean = + file.exists() && when (backend.get()) { + JS_BACKEND.JS_IR -> isKotlinLibrary(file) + JS_BACKEND.JS_LEGACY -> JsLibraryUtils.isKotlinJavascriptLibrary(file) + JS_BACKEND.JS_BOTH -> JsLibraryUtils.isKotlinJavascriptLibrary(file) && isKotlinLibrary(file) + } + + @TaskAction + fun execute(inputChanges: InputChanges) { + val args = K2JSCompilerArguments().apply { + // Common + useK2 = false + incrementalCompilation = false + pluginClasspaths = pluginClasspath.files.map { it.absolutePath }.toTypedArray() + apiVersion = this@KspTaskJs.kotlinApiVersion.orNull + languageVersion = this@KspTaskJs.kotlinLanguageVersion.orNull + verbose = this@KspTaskJs.verbose.get() + + addPluginOptions(options.get()) + + // JS + destination = this@KspTaskJs.destination + noStdlib = true + libraries = this@KspTaskJs.libraries.filter { isJsLib(it) }.asPath + freeArgs = this@KspTaskJs.freeArgs.get() + outputFile = File(destination, "dummyOutput.js").canonicalPath + friendModules = this@KspTaskJs.friendPaths.filter { isJsLib(it) }.asPath + } + + // Clean outputs. Backups will be copied back if incremental. + // TODO: leave outputs untouched and only restore outputs if build fails. + outputs.files.forEach { + it.deleteRecursively() + } + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (isKspIncremental) { + if (changedFiles.hasNonSourceChange()) { + clearIncCache() + } + } else { + clearIncCache() + } + args.addChangedFiles(changedFiles) + + compilerRunner.runJsCompilerAsync( + args, + allSources.files.toList(), + commonSources.files.toList(), + outputs.files.toList() + ) + } + + @get:Internal + abstract val backend: Property + + override fun configureCompilation( + kotlinCompilation: KotlinCompilation<*>, + kotlinCompile: KotlinCompileTool, + ) { + val providers = project.providers + + kotlinCompile as AbstractKotlinCompile<*> + compilerRunner.compilerClasspath.from(providers.provider { kotlinCompile.defaultCompilerClasspath }) + compilerRunner.compilerExecutionStrategy.set(kotlinCompile.compilerExecutionStrategy) + compilerRunner.useDaemonFallbackStrategy.set(kotlinCompile.useDaemonFallbackStrategy) + compilerRunner.kotlinDaemonJvmArguments.set(kotlinCompile.kotlinDaemonJvmArguments) + + val kotlinJsOptions = kotlinCompilation.kotlinOptions as KotlinJsOptions + backend.value( + when { + kotlinJsOptions.isIrBackendEnabled() && kotlinJsOptions.isPreIrBackendDisabled() -> + JS_BACKEND.JS_IR + !kotlinJsOptions.isIrBackendEnabled() && !kotlinJsOptions.isPreIrBackendDisabled() -> + JS_BACKEND.JS_LEGACY + kotlinJsOptions.isIrBackendEnabled() && !kotlinJsOptions.isPreIrBackendDisabled() -> + JS_BACKEND.JS_BOTH + else -> throw IllegalArgumentException("Unknown JS backend") + } + ) + freeArgs.value( + (kotlinCompile as Kotlin2JsCompile).kotlinOptions.freeCompilerArgs.filter { + it in backendSelectionArgs + } + ) + } + + enum class JS_BACKEND { + JS_IR, + JS_LEGACY, + JS_BOTH + } + + private fun KotlinJsOptions.isPreIrBackendDisabled(): Boolean = + listOf( + "-Xir-only", + "-Xir-produce-js", + "-Xir-produce-klib-file" + ).any(freeCompilerArgs::contains) + + // see also isIncrementalCompilationEnabled + private fun KotlinJsOptions.isIrBackendEnabled(): Boolean = + listOf( + "-Xir-produce-klib-dir", + "-Xir-produce-js", + "-Xir-produce-klib-file" + ).any(freeCompilerArgs::contains) +} + +@CacheableTask +abstract class KspTaskMetadata @Inject constructor( + objectFactory: ObjectFactory, +) : KspTaskStandalone, CompileUsingKotlinDaemon, DefaultTask() { + @get:Nested + val compilerRunner = createKotlinMetadataCompilerRunner(this, objectFactory) + + @get:Internal + val incrementalProps: List + get() = listOf( + allSources, + commonSources, + ) + + @TaskAction + fun execute(inputChanges: InputChanges) { + val args = K2MetadataCompilerArguments().apply { + // Common + useK2 = false + incrementalCompilation = false + pluginClasspaths = pluginClasspath.files.map { it.absolutePath }.toTypedArray() + apiVersion = this@KspTaskMetadata.kotlinApiVersion.orNull + languageVersion = this@KspTaskMetadata.kotlinLanguageVersion.orNull + verbose = this@KspTaskMetadata.verbose.get() + + addPluginOptions(options.get()) + + // Metadata + destination = this@KspTaskMetadata.destination.absolutePath + classpath = this@KspTaskMetadata.libraries.joinToString(":") + multiPlatform = true + expectActualLinker = true + friendPaths = this@KspTaskMetadata.friendPaths.map { it.absolutePath }.toTypedArray() + } + + // Clean outputs. Backups will be copied back if incremental. + // TODO: leave outputs untouched and only restore outputs if build fails. + outputs.files.forEach { + it.deleteRecursively() + } + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (isKspIncremental) { + if (changedFiles.hasNonSourceChange()) { + clearIncCache() + } + } else { + clearIncCache() + } + args.addChangedFiles(changedFiles) + + compilerRunner.runMetadataCompilerAsync( + args, + allSources.files.toList(), + commonSources.files.toList(), + outputs.files.toList() + ) + } + + override fun configureCompilation( + kotlinCompilation: KotlinCompilation, + kotlinCompile: KotlinCompileTool, + ) { + val providers = project.providers + + kotlinCompile as AbstractKotlinCompile<*> + compilerRunner.compilerClasspath.from(providers.provider { kotlinCompile.defaultCompilerClasspath }) + compilerRunner.compilerExecutionStrategy.set(kotlinCompile.compilerExecutionStrategy) + compilerRunner.useDaemonFallbackStrategy.set(kotlinCompile.useDaemonFallbackStrategy) + compilerRunner.kotlinDaemonJvmArguments.set(kotlinCompile.kotlinDaemonJvmArguments) + } +} + +@CacheableTask +abstract class KspTaskNative @Inject constructor( + objectFactory: ObjectFactory, +) : KspTaskStandalone, DefaultTask() { + @get:Nested + val compilerRunner = createKotlinNativeCompilerRunner(this, objectFactory) + + @get:Internal + val incrementalProps: List + get() = listOf( + allSources, + commonSources, + ) + + @get:Input + abstract val target: Property + + @TaskAction + fun execute(inputChanges: InputChanges) { + val args = K2NativeCompilerArguments().apply { + // Common + useK2 = false + incrementalCompilation = false + pluginClasspaths = pluginClasspath.files.map { it.absolutePath }.toTypedArray() + apiVersion = this@KspTaskNative.kotlinApiVersion.orNull + languageVersion = this@KspTaskNative.kotlinLanguageVersion.orNull + verbose = this@KspTaskNative.verbose.get() + + addPluginOptions(options.get()) + + // Native + destination = this@KspTaskNative.destination + libraries = this@KspTaskNative.libraries.map { it.absolutePath }.toTypedArray() + multiPlatform = true + expectActualLinker = true + friendModules = this@KspTaskNative.friendPaths.asPath + target = this@KspTaskNative.target.get() + } + + // Clean outputs. Backups will be copied back if incremental. + // TODO: leave outputs untouched and only restore outputs if build fails. + outputs.files.forEach { + it.deleteRecursively() + } + val changedFiles = getChangedFiles(inputChanges, incrementalProps) + if (isKspIncremental) { + if (changedFiles.hasNonSourceChange()) { + clearIncCache() + } + } else { + clearIncCache() + } + args.addChangedFiles(changedFiles) + + compilerRunner.runNativeCompilerAsync( + args, + allSources.toList(), + commonSources.toList(), + outputs.files.toList(), + ) + } + + override fun configureCompilation( + kotlinCompilation: KotlinCompilation, + kotlinCompile: KotlinCompileTool, + ) { + target.value((kotlinCompile as KotlinNativeCompile).target) + } +} + +internal fun getChangedFiles( + inputChanges: InputChanges, + incrementalProps: List, +) = if (!inputChanges.isIncremental) { + ChangedFiles.Unknown() +} else { + incrementalProps + .fold(mutableListOf() to mutableListOf()) { (modified, removed), prop -> + inputChanges.getFileChanges(prop).forEach { + when (it.changeType) { + ChangeType.ADDED, ChangeType.MODIFIED -> modified.add(it.file) + ChangeType.REMOVED -> removed.add(it.file) + else -> Unit + } + } + modified to removed + } + .run { + ChangedFiles.Known(first, second) + } +} + +/** Properties related to the `kotlin.incremental.useClasspathSnapshot` feature. */ +abstract class ClasspathSnapshotProperties { + @get:Input + abstract val useClasspathSnapshot: Property + + @get:Classpath + @get:Incremental + @get:Optional // Set if useClasspathSnapshot == true + abstract val classpathSnapshot: ConfigurableFileCollection + + // Optional: Set if useClasspathSnapshot == false + // (to restore the existing classpath annotations when the feature is disabled) + @get:Classpath + @get:Incremental + @get:Optional + abstract val classpath: ConfigurableFileCollection + + @get:OutputDirectory + @get:Optional // Set if useClasspathSnapshot == true + abstract val classpathSnapshotDir: DirectoryProperty +} From 50c93b29df3fc3ee4483f70897b8d3ab36062e83 Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Fri, 23 Sep 2022 11:06:40 -0700 Subject: [PATCH 2/8] Fix test for build cache. --- .../test/kotlin/com/google/devtools/ksp/test/PlaygroundIT.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PlaygroundIT.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PlaygroundIT.kt index 912ae97fe5..0fc3df5cc5 100644 --- a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PlaygroundIT.kt +++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/PlaygroundIT.kt @@ -97,9 +97,7 @@ class PlaygroundIT { @Test fun testBuildCache() { val gradleRunner = GradleRunner.create().withProjectDir(project.root) - gradleRunner.buildAndCheck("--build-cache", ":workload:clean", "build") { - Assert.assertEquals(TaskOutcome.SUCCESS, it.task(":workload:kspKotlin")?.outcome) - } + gradleRunner.buildAndCheck("--build-cache", ":workload:clean", "build") gradleRunner.buildAndCheck("--build-cache", ":workload:clean", "build") { Assert.assertEquals(TaskOutcome.FROM_CACHE, it.task(":workload:kspKotlin")?.outcome) } From dd523243eb0d38684e12ce1e2af5256efb41b78b Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Fri, 23 Sep 2022 23:43:07 -0700 Subject: [PATCH 3/8] Support ksp.compiler.runner in integration tests --- gradle-plugin/build.gradle.kts | 1 + .../ksp/gradle/testing/KspIntegrationTestRule.kt | 8 ++++++++ .../com/google/devtools/ksp/gradle/testing/TestConfig.kt | 9 +++++++-- integration-tests/build.gradle.kts | 1 + .../com/google/devtools/ksp/test/TemporaryTestProject.kt | 2 ++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/gradle-plugin/build.gradle.kts b/gradle-plugin/build.gradle.kts index 0bc88ca7b6..6322082c61 100644 --- a/gradle-plugin/build.gradle.kts +++ b/gradle-plugin/build.gradle.kts @@ -106,6 +106,7 @@ val writeTestPropsTask = tasks.register("prepareTestConfigurati property("mavenRepoDir", File(rootProject.buildDir, "repos/test").absolutePath) property("kspProjectRootDir", rootProject.projectDir.absolutePath) property("processorClasspath", project.tasks["compileTestKotlin"].outputs.files.asPath) + property("kspCompilerRunner", project.properties.getOrDefault("ksp.compiler.runner", "inherited") as String) } java { diff --git a/gradle-plugin/src/test/kotlin/com/google/devtools/ksp/gradle/testing/KspIntegrationTestRule.kt b/gradle-plugin/src/test/kotlin/com/google/devtools/ksp/gradle/testing/KspIntegrationTestRule.kt index 088fa89b36..b4e3d39612 100644 --- a/gradle-plugin/src/test/kotlin/com/google/devtools/ksp/gradle/testing/KspIntegrationTestRule.kt +++ b/gradle-plugin/src/test/kotlin/com/google/devtools/ksp/gradle/testing/KspIntegrationTestRule.kt @@ -88,6 +88,7 @@ class KspIntegrationTestRule( PluginDeclaration.id("com.google.devtools.ksp", testConfig.kspVersion) ) ) + addKspCompilerRunner() } /** @@ -103,6 +104,7 @@ class KspIntegrationTestRule( ) ) addAndroidBoilerplate() + addKspCompilerRunner() } /** @@ -118,6 +120,12 @@ class KspIntegrationTestRule( ) testProject.appModule.buildFileAdditions.add(targets) addAndroidBoilerplate() + addKspCompilerRunner() + } + + private fun addKspCompilerRunner() { + val contents = "\nksp.compiler.runner=${testConfig.kspCompilerRunner}\n" + testProject.rootDir.resolve("gradle.properties").appendText(contents) } private fun addAndroidBoilerplate() { diff --git a/gradle-plugin/src/test/kotlin/com/google/devtools/ksp/gradle/testing/TestConfig.kt b/gradle-plugin/src/test/kotlin/com/google/devtools/ksp/gradle/testing/TestConfig.kt index 1c048da64d..69faceb28b 100644 --- a/gradle-plugin/src/test/kotlin/com/google/devtools/ksp/gradle/testing/TestConfig.kt +++ b/gradle-plugin/src/test/kotlin/com/google/devtools/ksp/gradle/testing/TestConfig.kt @@ -41,7 +41,11 @@ data class TestConfig( /** * The version of KSP. */ - val kspVersion: String + val kspVersion: String, + /** + * The compiler runner; Can be standalone or inherited + */ + val kspCompilerRunner: String ) { private val kspProjectProperties by lazy { Properties().also { props -> @@ -73,7 +77,8 @@ data class TestConfig( kspProjectDir = File(props.get("kspProjectRootDir") as String), processorClasspath = props.get("processorClasspath") as String, mavenRepoDir = File(props.get("mavenRepoDir") as String), - kspVersion = props.get("kspVersion") as String + kspVersion = props.get("kspVersion") as String, + kspCompilerRunner = props.get("kspCompilerRunner") as String, ) } } diff --git a/integration-tests/build.gradle.kts b/integration-tests/build.gradle.kts index 305592c4d5..2fb58572a8 100644 --- a/integration-tests/build.gradle.kts +++ b/integration-tests/build.gradle.kts @@ -17,6 +17,7 @@ tasks.named("test") { systemProperty("kspVersion", version) systemProperty("agpVersion", agpBaseVersion) systemProperty("testRepo", File(rootProject.buildDir, "repos/test").absolutePath) + systemProperty("kspCompilerRunner", project.properties.getOrDefault("ksp.compiler.runner", "inherited") as String) dependsOn(":api:publishAllPublicationsToTestRepository") dependsOn(":gradle-plugin:publishAllPublicationsToTestRepository") dependsOn(":symbol-processing:publishAllPublicationsToTestRepository") diff --git a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/TemporaryTestProject.kt b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/TemporaryTestProject.kt index df9cc5b57b..cd8702fc5a 100644 --- a/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/TemporaryTestProject.kt +++ b/integration-tests/src/test/kotlin/com/google/devtools/ksp/test/TemporaryTestProject.kt @@ -17,12 +17,14 @@ class TemporaryTestProject(projectName: String, baseProject: String? = null) : T val kspVersion = System.getProperty("kspVersion") val agpVersion = System.getProperty("agpVersion") val testRepo = System.getProperty("testRepo").replace(File.separator, "/") + val kspCompilerRunner = System.getProperty("kspCompilerRunner") val gradleProperties = File(root, "gradle.properties") gradleProperties.appendText("\nkotlinVersion=$kotlinVersion") gradleProperties.appendText("\nkspVersion=$kspVersion") gradleProperties.appendText("\nagpVersion=$agpVersion") gradleProperties.appendText("\ntestRepo=$testRepo") gradleProperties.appendText("\norg.gradle.unsafe.configuration-cache=true") + gradleProperties.appendText("\nksp.compiler.runner=$kspCompilerRunner") // Uncomment this to debug compiler and compiler plugin. // gradleProperties.appendText("\nsystemProp.kotlin.compiler.execution.strategy=in-process") } From c99bcb59da7e9c7fb1168dc51ce7fd87c73ddb01 Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Sat, 24 Sep 2022 00:29:43 -0700 Subject: [PATCH 4/8] CI: Test the standalone runner nightly --- .github/workflows/standalone.yml | 74 ++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 .github/workflows/standalone.yml diff --git a/.github/workflows/standalone.yml b/.github/workflows/standalone.yml new file mode 100644 index 0000000000..fe968c1651 --- /dev/null +++ b/.github/workflows/standalone.yml @@ -0,0 +1,74 @@ +# Workflow to run tests + +name: standalone compiler runner + +on: + # runs on 2:00 AM PST or 3:00 AM PDT + schedule: + - cron: '0 10 * * *' + +jobs: + build-and-test: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + branch: [ main, 1.0.7-release ] + + # The type of runner that the job will run on + runs-on: ${{ matrix.os }} + + steps: + - name: Setup Java 9 + uses: actions/setup-java@v1.4.3 + with: + java-version: '9' + java-package: jdk + architecture: x64 + - name: set JDK_9 environment variable for kotlin compiler + shell: bash + env: + ACTIONS_ALLOW_UNSECURE_COMMANDS: true + run: echo ::set-env name=JDK_9::$(echo $JAVA_HOME) + - name: Setup Java 11 + uses: actions/setup-java@v1.4.3 + with: + java-version: '11' + java-package: jdk + architecture: x64 + + # Checkout + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + ref: ${{ matrix.branch }} + + # Build cache + - name: Cache Gradle Cache + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}-${{ hashFiles('**/gradle.properties') }} + # An ordered list of keys to use for restoring the cache if no cache hit occurred for key + restore-keys: | + ${{ runner.os }}-gradle- + - name: Cache gradle wrapper + uses: actions/cache@v2 + with: + path: ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }} + + # Run tests + - name: test + shell: bash + run: ./gradlew -Pksp.compiler.runner=standalone --stacktrace --info :integration-tests:test :gradle-plugin:test + - name: Upload test results + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-reports-standalone-${{ matrix.os }}-${{ matrix.branch }} + path: | + compiler-plugin/build/reports + integration-tests/build/reports + gradle-plugin/build/reports + common-util/build/reports From 6566ca14993ed6f24c374e762d450ad8d5847489 Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Wed, 28 Sep 2022 22:14:14 -0700 Subject: [PATCH 5/8] Add TODOs for upstreaming KotlinCompilerRunner --- .../google/devtools/ksp/gradle/KotlinCompilerRunner.kt | 9 +++++++++ .../devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt | 1 + 2 files changed, 10 insertions(+) diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt index f1c8c912ed..d3534e0d31 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt @@ -35,6 +35,9 @@ import org.jetbrains.kotlin.gradle.utils.newInstance import java.io.File interface KotlinCompilerRunner { + // TODO: Remove those properties when getting into KGP. + // They should be configured by KGP. For now, they allow KSP to copy the settings from compilation task. + @get:Internal val compilerExecutionStrategy: Property @@ -84,6 +87,9 @@ interface KotlinNativeCompilerRunner : KotlinCompilerRunner { ) } +// TODO: Maybe move those functions into proper KGP class when getting into KGP. + +// TODO: Remove objectFactory when getting into KGP. fun createKotlinJvmCompilerRunner( task: Task, objectFactory: ObjectFactory, @@ -91,6 +97,7 @@ fun createKotlinJvmCompilerRunner( return objectFactory.newInstance(task) } +// TODO: Remove objectFactory when getting into KGP. fun createKotlinJsCompilerRunner( task: Task, objectFactory: ObjectFactory, @@ -98,6 +105,7 @@ fun createKotlinJsCompilerRunner( return objectFactory.newInstance(task) } +// TODO: Remove objectFactory when getting into KGP. fun createKotlinMetadataCompilerRunner( task: Task, objectFactory: ObjectFactory, @@ -105,6 +113,7 @@ fun createKotlinMetadataCompilerRunner( return objectFactory.newInstance(task) } +// TODO: Remove objectFactory when getting into KGP. fun createKotlinNativeCompilerRunner( task: Task, objectFactory: ObjectFactory, diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt index e8202e7175..14f2b21df4 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt @@ -51,6 +51,7 @@ import javax.inject.Inject internal inline fun ObjectFactory.property() = property(T::class.java) internal inline fun ObjectFactory.property(initialValue: T) = property().value(initialValue) +// TODO: All the properties should be configured by KGP abstract class KotlinCompilerRunnerImpl @Inject constructor( task: Task, objectFactory: ObjectFactory, From a2a7d15e7e6e9ddcdd65cd1f175904ea6b2af165 Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Thu, 29 Sep 2022 18:34:46 -0700 Subject: [PATCH 6/8] Introduce KotlinCompilerArguments and target specific subclasses. This is a simplified version of the CommonCompilerArguments family. --- .../ksp/gradle/KotlinCompilerArguments.kt | 93 ++++++++ .../ksp/gradle/KotlinCompilerRunner.kt | 12 +- .../ksp/gradle/KotlinCompilerRunnerImpls.kt | 104 +++++++-- .../tasks/standalone/StandaloneTasks.kt | 213 ++++++------------ 4 files changed, 251 insertions(+), 171 deletions(-) create mode 100644 gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerArguments.kt diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerArguments.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerArguments.kt new file mode 100644 index 0000000000..3dd49fb102 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerArguments.kt @@ -0,0 +1,93 @@ +/* + * Copyright 2022 Google LLC + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.devtools.ksp.gradle + +import java.io.File + +/** + * A simplified version of CommonComilerArguments. + */ +open class KotlinCompilerArguments { + open var freeArgs: List = emptyList() + open var verbose: Boolean = false + open var allWarningsAsErrors: Boolean = false + open var languageVersion: String? = null + open var apiVersion: String? = null + open var useK2: Boolean = false + open var incrementalCompilation: Boolean = false + open var pluginOptions: List = emptyList() + open var pluginClasspaths: List = emptyList() + open var multiPlatform: Boolean = false + open var expectActualLinker: Boolean = false + + // A.K.A. friend modules + open var friendPaths: List = emptyList() + + // A.K.A. classpath + open var libraries: List = emptyList() + + // A.K.A. output, output file + open var destination: File? = null +} + +/** + * A simplified version of K2JVMComilerArguments. + */ +class KotlinJvmCompilerArguments : KotlinCompilerArguments() { + var noJdk: Boolean = false + var noStdlib: Boolean = false + var noReflect: Boolean = false + var moduleName: String? = null + var jvmTarget: String? = null + var jdkRelease: String? = null + var allowNoSourceFiles: Boolean = false + + var javaSourceRoots: List = emptyList() +} + +/** + * A simplified version of K2JSComilerArguments. + */ +class KotlinJsCompilerArguments : KotlinCompilerArguments() { + var noStdlib: Boolean = false + var irOnly: Boolean = false + var irProduceJs: Boolean = false + var irProduceKlibDir: Boolean = false + var irProduceKlibFile: Boolean = false + var irBuildCache: Boolean = false + var wasm: Boolean = false + var target: String = "v5" + + override var multiPlatform = true +} + +/** + * A simplified version of K2MetadataComilerArguments. + */ +class KotlinMetadataCompilerArguments : KotlinCompilerArguments() { + override var multiPlatform = true + override var expectActualLinker = true +} + +/** + * A simplified version of K2NativeComilerArguments. + */ +class KotlinNativeCompilerArguments : KotlinCompilerArguments() { + var target: String? = null + override var multiPlatform = true +} diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt index d3534e0d31..4c91df7e5f 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt @@ -26,10 +26,6 @@ import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Internal -import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy import org.jetbrains.kotlin.gradle.utils.newInstance import java.io.File @@ -53,7 +49,7 @@ interface KotlinCompilerRunner { interface KotlinJvmCompilerRunner : KotlinCompilerRunner { fun runJvmCompilerAsync( - args: K2JVMCompilerArguments, + args: KotlinJvmCompilerArguments, sources: List, commonSources: List, outputs: List @@ -62,7 +58,7 @@ interface KotlinJvmCompilerRunner : KotlinCompilerRunner { interface KotlinJsCompilerRunner : KotlinCompilerRunner { fun runJsCompilerAsync( - args: K2JSCompilerArguments, + args: KotlinJsCompilerArguments, sources: List, commonSources: List, outputs: List @@ -71,7 +67,7 @@ interface KotlinJsCompilerRunner : KotlinCompilerRunner { interface KotlinMetadataCompilerRunner : KotlinCompilerRunner { fun runMetadataCompilerAsync( - args: K2MetadataCompilerArguments, + args: KotlinMetadataCompilerArguments, sources: List, commonSources: List, outputs: List @@ -80,7 +76,7 @@ interface KotlinMetadataCompilerRunner : KotlinCompilerRunner { interface KotlinNativeCompilerRunner : KotlinCompilerRunner { fun runNativeCompilerAsync( - args: K2NativeCompilerArguments, + args: KotlinNativeCompilerArguments, sources: List, commonSources: List, outputs: List, diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt index 14f2b21df4..587b662275 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt @@ -28,10 +28,12 @@ import org.gradle.process.ExecOperations import org.gradle.workers.WorkerExecutor import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporterImpl +import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments +import org.jetbrains.kotlin.cli.common.arguments.isIrBackendEnabled +import org.jetbrains.kotlin.cli.common.arguments.isPreIrBackendDisabled import org.jetbrains.kotlin.compilerRunner.CompilerExecutionSettings import org.jetbrains.kotlin.compilerRunner.GradleCompilerEnvironment import org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers @@ -45,6 +47,9 @@ import org.jetbrains.kotlin.gradle.tasks.GradleCompileTaskProvider import org.jetbrains.kotlin.gradle.utils.newInstance import org.jetbrains.kotlin.gradle.utils.propertyWithNewInstance import org.jetbrains.kotlin.konan.target.KonanTarget +import org.jetbrains.kotlin.library.impl.isKotlinLibrary +import org.jetbrains.kotlin.utils.JsLibraryUtils +import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import java.io.File import javax.inject.Inject @@ -105,6 +110,20 @@ abstract class KotlinCompilerRunnerImpl @Inject constructor( } } +internal fun CommonCompilerArguments.copyFrom(args: KotlinCompilerArguments) { + freeArgs = args.freeArgs + verbose = args.verbose + allWarningsAsErrors = args.allWarningsAsErrors + languageVersion = args.languageVersion + apiVersion = args.apiVersion + useK2 = args.useK2 + incrementalCompilation = args.incrementalCompilation + pluginOptions = args.pluginOptions.toTypedArray() + pluginClasspaths = args.pluginClasspaths.map { it.absolutePath }.toTypedArray() + expectActualLinker = args.expectActualLinker + multiPlatform = args.multiPlatform +} + abstract class KotlinJvmCompilerRunnerImpl @Inject constructor( task: Task, objectFactory: ObjectFactory, @@ -112,19 +131,35 @@ abstract class KotlinJvmCompilerRunnerImpl @Inject constructor( ) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinJvmCompilerRunner { override fun runJvmCompilerAsync( - args: K2JVMCompilerArguments, + args: KotlinJvmCompilerArguments, sources: List, commonSources: List, outputs: List ) { val environment = prepareEnvironment(args.allWarningsAsErrors, outputs) val compilerRunner = prepareCompilerRunner() + val compilerArgs = K2JVMCompilerArguments().apply { + copyFrom(args) + + friendPaths = args.friendPaths.map { it.absolutePath }.toTypedArray() + classpath = args.libraries.map { it.absolutePath }.joinToString(File.pathSeparator) + destination = args.destination?.absolutePath + + noJdk = args.noJdk + noStdlib = args.noStdlib + noReflect = args.noReflect + moduleName = args.moduleName + jvmTarget = args.jvmTarget + jdkRelease = args.jdkRelease + allowNoSourceFiles = args.allowNoSourceFiles + javaSourceRoots = args.javaSourceRoots.map { it.absolutePath }.toTypedArray() + } compilerRunner.runJvmCompilerAsync( sourcesToCompile = sources, commonSources = commonSources, javaPackagePrefix = null, - args = args, + args = compilerArgs, environment = environment, jdkHome = defaultKotlinJavaToolchain.get().providedJvm.get().javaHome, null @@ -138,16 +173,52 @@ abstract class KotlinJsCompilerRunnerImpl @Inject constructor( workerExecutor: WorkerExecutor ) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinJsCompilerRunner { + private fun libFilter(args: K2JSCompilerArguments, file: File): Boolean = + file.exists() && when { + // JS_IR + args.isIrBackendEnabled() && args.isPreIrBackendDisabled() -> + isKotlinLibrary(file) + + // JS_LEGACY + !args.isIrBackendEnabled() && !args.isPreIrBackendDisabled() -> + JsLibraryUtils.isKotlinJavascriptLibrary(file) + + // JS_BOTH + args.isIrBackendEnabled() && !args.isPreIrBackendDisabled() -> + isKotlinLibrary(file) && JsLibraryUtils.isKotlinJavascriptLibrary(file) + + else -> throw IllegalArgumentException("Cannot determine JS backend.") + } + override fun runJsCompilerAsync( - args: K2JSCompilerArguments, + args: KotlinJsCompilerArguments, sources: List, commonSources: List, outputs: List ) { val environment = prepareEnvironment(args.allWarningsAsErrors, outputs) val compilerRunner = prepareCompilerRunner() + val compilerArgs = K2JSCompilerArguments().apply { + copyFrom(args) + + outputFile = args.destination?.absolutePath + + noStdlib = args.noStdlib + irOnly = args.irOnly + irProduceJs = args.irProduceJs + irProduceKlibDir = args.irProduceKlibDir + irProduceKlibFile = args.irProduceKlibFile + irBuildCache = args.irBuildCache + wasm = args.wasm + target = args.target + + friendModules = args.friendPaths.filter { libFilter(this, it) } + .map { it.absolutePath }.joinToString(File.pathSeparator) + libraries = args.libraries.filter { libFilter(this, it) } + .map { it.absolutePath }.joinToString(File.pathSeparator) + } - compilerRunner.runJsCompilerAsync(sources, commonSources, args, environment, null) + compilerRunner.runJsCompilerAsync(sources, commonSources, compilerArgs, environment, null) } } @@ -158,15 +229,22 @@ abstract class KotlinMetadataCompilerRunnerImpl @Inject constructor( ) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinMetadataCompilerRunner { override fun runMetadataCompilerAsync( - args: K2MetadataCompilerArguments, + args: KotlinMetadataCompilerArguments, sources: List, commonSources: List, outputs: List ) { val environment = prepareEnvironment(args.allWarningsAsErrors, outputs) val compilerRunner = prepareCompilerRunner() + val compilerArgs = K2MetadataCompilerArguments().apply { + copyFrom(args) + + friendPaths = args.friendPaths.map { it.absolutePath }.toTypedArray() + classpath = args.libraries.map { it.absolutePath }.joinToString(File.pathSeparator) + destination = args.destination?.absolutePath + } - compilerRunner.runMetadataCompilerAsync(sources, commonSources, args, environment) + compilerRunner.runMetadataCompilerAsync(sources, commonSources, compilerArgs, environment) } } @@ -181,7 +259,7 @@ abstract class KotlinNativeCompilerRunnerImpl @Inject constructor( .KotlinNativeCompilerRunner.Settings.fromProject(task.project) override fun runNativeCompilerAsync( - args: K2NativeCompilerArguments, + args: KotlinNativeCompilerArguments, sources: List, commonSources: List, outputs: List, @@ -195,10 +273,10 @@ abstract class KotlinNativeCompilerRunnerImpl @Inject constructor( "-p", "library", "-Xmulti-platform" ) - args.libraries?.flatMap { listOf("-l", it) }?.let { buildArgs.addAll(it) } - args.friendModules?.let { + args.libraries.flatMap { listOf("-l", it.absolutePath) }.let { buildArgs.addAll(it) } + args.friendPaths.ifNotEmpty { buildArgs.add("-friend-modules") - buildArgs.add(it) + buildArgs.add(joinToString(File.pathSeparator)) } if (args.verbose) @@ -206,8 +284,8 @@ abstract class KotlinNativeCompilerRunnerImpl @Inject constructor( if (args.allWarningsAsErrors) buildArgs.add("-Werror") - args.pluginClasspaths?.map { "-Xplugin=$it" }?.let { buildArgs.addAll(it) } - args.pluginOptions?.flatMap { listOf("-P", it) }?.let { buildArgs.addAll(it) } + args.pluginClasspaths.map { "-Xplugin=${it.absolutePath}" }.let { buildArgs.addAll(it) } + args.pluginOptions.flatMap { listOf("-P", it) }.let { buildArgs.addAll(it) } args.languageVersion?.let { buildArgs.add("-language-version") diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt index 84762327c6..aade62b2d5 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt @@ -19,6 +19,11 @@ package com.google.devtools.ksp.gradle.tasks.standalone +import com.google.devtools.ksp.gradle.KotlinCompilerArguments +import com.google.devtools.ksp.gradle.KotlinJsCompilerArguments +import com.google.devtools.ksp.gradle.KotlinJvmCompilerArguments +import com.google.devtools.ksp.gradle.KotlinMetadataCompilerArguments +import com.google.devtools.ksp.gradle.KotlinNativeCompilerArguments import com.google.devtools.ksp.gradle.KspExtension import com.google.devtools.ksp.gradle.KspGradleSubplugin import com.google.devtools.ksp.gradle.KspGradleSubplugin.Companion.getKspCachesDir @@ -58,14 +63,7 @@ import org.gradle.util.GradleVersion import org.gradle.work.ChangeType import org.gradle.work.Incremental import org.gradle.work.InputChanges -import org.jetbrains.kotlin.cli.common.arguments.Argument -import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments -import org.jetbrains.kotlin.cli.common.arguments.K2NativeCompilerArguments import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions -import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptions import org.jetbrains.kotlin.gradle.internal.kapt.incremental.CLASS_STRUCTURE_ARTIFACT_TYPE import org.jetbrains.kotlin.gradle.internal.kapt.incremental.ClasspathSnapshot import org.jetbrains.kotlin.gradle.internal.kapt.incremental.KaptClasspathChanges @@ -86,18 +84,13 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompileCommon import org.jetbrains.kotlin.gradle.tasks.KotlinCompileTool import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile import org.jetbrains.kotlin.incremental.ChangedFiles -import org.jetbrains.kotlin.incremental.classpathAsList -import org.jetbrains.kotlin.incremental.destinationAsFile import org.jetbrains.kotlin.incremental.isJavaFile import org.jetbrains.kotlin.incremental.isKotlinFile -import org.jetbrains.kotlin.library.impl.isKotlinLibrary -import org.jetbrains.kotlin.utils.JsLibraryUtils import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.io.File import java.nio.file.Paths import javax.inject.Inject -import kotlin.reflect.KProperty1 object StandaloneTasks : KspTaskCreator { override fun createKspTask( @@ -144,6 +137,7 @@ object StandaloneTasks : KspTaskCreator { kspTask.destination = kspOutputDir kspTask.apOptions.value(kspExtension.arguments).disallowChanges() kspTask.kspCacheDir.fileValue(getKspCachesDir(project, sourceSetName, target)).disallowChanges() + kspTask.allWarningAsErrors.value(kspExtension.allWarningsAsErrors) kspTask.isKspIncremental = isIncremental @@ -181,9 +175,11 @@ object StandaloneTasks : KspTaskCreator { if (kotlinCompile is KotlinNativeCompile) { kspTask.commonSources.from(providers.provider { kotlinCompile.commonSources }) kspTask.friendPaths.from(kotlinCompile.compilation.friendPaths) + kspTask.multiplatform.value(true) } else if (kotlinCompile is AbstractKotlinCompile<*>) { kspTask.commonSources.from(providers.provider { kotlinCompile.commonSourceSet }) kspTask.friendPaths.from(kotlinCompile.friendPaths) + kspTask.multiplatform.set(kotlinCompile.multiPlatformEnabled) } else { throw IllegalArgumentException("Unknown compilation task type") } @@ -270,6 +266,12 @@ interface KspTaskStandalone : KspTask { @get:Internal val friendPaths: ConfigurableFileCollection + @get:Input + val multiplatform: Property + + @get:Input + val allWarningAsErrors: Property + fun configureCompilation( kotlinCompilation: KotlinCompilation<*>, kotlinCompile: KotlinCompileTool, @@ -290,7 +292,7 @@ private fun ChangedFiles.hasNonSourceChange(): Boolean { } } -fun CommonCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { +fun KotlinCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { if (changed is KaptClasspathChanges.Known) { changed.names.map { it.replace('/', '.').replace('$', '.') }.ifNotEmpty { addPluginOptions(listOf(SubpluginOption("changedClasses", joinToString(":")))) @@ -300,11 +302,11 @@ fun CommonCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { fun SubpluginOption.toArg() = "plugin:${KspGradleSubplugin.KSP_PLUGIN_ID}:$key=$value" -fun CommonCompilerArguments.addPluginOptions(options: List) { - pluginOptions = ((pluginOptions?.toList() ?: emptyList()) + options.map { it.toArg() }).toTypedArray() +fun KotlinCompilerArguments.addPluginOptions(options: List) { + pluginOptions += options.map { it.toArg() } } -fun CommonCompilerArguments.addChangedFiles(changedFiles: ChangedFiles) { +fun KotlinCompilerArguments.addChangedFiles(changedFiles: ChangedFiles) { if (changedFiles is ChangedFiles.Known) { val options = mutableListOf() changedFiles.modified.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { @@ -317,32 +319,6 @@ fun CommonCompilerArguments.addChangedFiles(changedFiles: ChangedFiles) { } } -private fun CommonCompilerArguments.blockOtherPlugins(kspPluginClasspath: FileCollection) { - pluginClasspaths = kspPluginClasspath.map { it.canonicalPath }.toTypedArray() - pluginOptions = arrayOf() -} - -// TODO: Move into dumpArgs after the compiler supports local function in inline functions. -private inline fun T.toPair(property: KProperty1): Pair { - @Suppress("UNCHECKED_CAST") - val value = property.get(this) - return property.name to if (value is Array<*>) - value.asList().toString() - else - value.toString() -} - -@Suppress("unused") -internal inline fun dumpArgs(args: T): Map { - @Suppress("UNCHECKED_CAST") - val argumentProperties = - args::class.members.mapNotNull { member -> - (member as? KProperty1)?.takeIf { it.annotations.any { ann -> ann is Argument } } - } - - return argumentProperties.associate(args::toPair).toSortedMap() -} - internal fun File.isParentOf(childCandidate: File): Boolean { val parentPath = Paths.get(this.absolutePath).normalize() val childCandidatePath = Paths.get(childCandidate.absolutePath).normalize() @@ -350,6 +326,19 @@ internal fun File.isParentOf(childCandidate: File): Boolean { return childCandidatePath.startsWith(parentPath) } +private fun KotlinCompilerArguments.fromTask(task: KspTaskStandalone) { + useK2 = false + incrementalCompilation = false + pluginClasspaths = task.pluginClasspath.files.toList() + apiVersion = task.kotlinApiVersion.orNull + languageVersion = task.kotlinLanguageVersion.orNull + verbose = task.verbose.get() + multiPlatform = task.multiplatform.get() + allWarningsAsErrors = task.allWarningAsErrors.get() + + addPluginOptions(task.options.get()) +} + @CacheableTask abstract class KspTaskJvm @Inject constructor( objectFactory: ObjectFactory, @@ -386,25 +375,18 @@ abstract class KspTaskJvm @Inject constructor( @TaskAction fun execute(inputChanges: InputChanges) { - val args = K2JVMCompilerArguments().apply { + val args = KotlinJvmCompilerArguments().apply { // Common - useK2 = false - incrementalCompilation = false - pluginClasspaths = pluginClasspath.files.map { it.absolutePath }.toTypedArray() - apiVersion = this@KspTaskJvm.kotlinApiVersion.orNull - languageVersion = this@KspTaskJvm.kotlinLanguageVersion.orNull - verbose = this@KspTaskJvm.verbose.get() - noJdk = this@KspTaskJvm.noJdk.get() - - addPluginOptions(options.get()) + fromTask(this@KspTaskJvm) // JVM + noJdk = this@KspTaskJvm.noJdk.get() allowNoSourceFiles = true - destinationAsFile = this@KspTaskJvm.destination + destination = this@KspTaskJvm.destination noReflect = true noStdlib = true - classpathAsList = libraries.files.toList() - friendPaths = this@KspTaskJvm.friendPaths.map { it.absolutePath }.toTypedArray() + libraries = this@KspTaskJvm.libraries.files.toList() + friendPaths = this@KspTaskJvm.friendPaths.files.toList() moduleName = this@KspTaskJvm.moduleName.get() } @@ -553,15 +535,6 @@ abstract class KspTaskJvm @Inject constructor( abstract class KspTaskJs @Inject constructor( objectFactory: ObjectFactory, ) : KspTaskStandalone, CompileUsingKotlinDaemon, DefaultTask() { - // TODO: setup by checking target info, instead of copying from compile task - private val backendSelectionArgs = listOf( - "-Xir-only", - "-Xir-produce-js", - "-Xir-produce-klib-dir", - "-Xir-produce-klib-file", - "-Xwasm" - ) - @get:Nested val compilerRunner = createKotlinJsCompilerRunner(this, objectFactory) @@ -575,33 +548,29 @@ abstract class KspTaskJs @Inject constructor( commonSources, ) - private fun isJsLib(file: File): Boolean = - file.exists() && when (backend.get()) { - JS_BACKEND.JS_IR -> isKotlinLibrary(file) - JS_BACKEND.JS_LEGACY -> JsLibraryUtils.isKotlinJavascriptLibrary(file) - JS_BACKEND.JS_BOTH -> JsLibraryUtils.isKotlinJavascriptLibrary(file) && isKotlinLibrary(file) - } - @TaskAction fun execute(inputChanges: InputChanges) { - val args = K2JSCompilerArguments().apply { + val args = KotlinJsCompilerArguments().apply { // Common - useK2 = false - incrementalCompilation = false - pluginClasspaths = pluginClasspath.files.map { it.absolutePath }.toTypedArray() - apiVersion = this@KspTaskJs.kotlinApiVersion.orNull - languageVersion = this@KspTaskJs.kotlinLanguageVersion.orNull - verbose = this@KspTaskJs.verbose.get() - - addPluginOptions(options.get()) + fromTask(this@KspTaskJs) // JS destination = this@KspTaskJs.destination noStdlib = true - libraries = this@KspTaskJs.libraries.filter { isJsLib(it) }.asPath - freeArgs = this@KspTaskJs.freeArgs.get() - outputFile = File(destination, "dummyOutput.js").canonicalPath - friendModules = this@KspTaskJs.friendPaths.filter { isJsLib(it) }.asPath + libraries = this@KspTaskJs.libraries.files.toList() + friendPaths = this@KspTaskJs.friendPaths.files.toList() + destination = File(destination, "dummyOutput.js") + + this@KspTaskJs.freeArgs.get().forEach { + when (it) { + "-Xir-only" -> irOnly = true + "-Xir-produce-js" -> irProduceJs = true + "-Xir-produce-klib-file" -> irProduceKlibFile = true + "-Xir-produce-klib-dir" -> irProduceKlibDir = true + "-Xir-build-cache" -> irBuildCache = true + "-Xwasm" -> wasm = true + } + } } // Clean outputs. Backups will be copied back if incremental. @@ -627,9 +596,6 @@ abstract class KspTaskJs @Inject constructor( ) } - @get:Internal - abstract val backend: Property - override fun configureCompilation( kotlinCompilation: KotlinCompilation<*>, kotlinCompile: KotlinCompileTool, @@ -642,45 +608,8 @@ abstract class KspTaskJs @Inject constructor( compilerRunner.useDaemonFallbackStrategy.set(kotlinCompile.useDaemonFallbackStrategy) compilerRunner.kotlinDaemonJvmArguments.set(kotlinCompile.kotlinDaemonJvmArguments) - val kotlinJsOptions = kotlinCompilation.kotlinOptions as KotlinJsOptions - backend.value( - when { - kotlinJsOptions.isIrBackendEnabled() && kotlinJsOptions.isPreIrBackendDisabled() -> - JS_BACKEND.JS_IR - !kotlinJsOptions.isIrBackendEnabled() && !kotlinJsOptions.isPreIrBackendDisabled() -> - JS_BACKEND.JS_LEGACY - kotlinJsOptions.isIrBackendEnabled() && !kotlinJsOptions.isPreIrBackendDisabled() -> - JS_BACKEND.JS_BOTH - else -> throw IllegalArgumentException("Unknown JS backend") - } - ) - freeArgs.value( - (kotlinCompile as Kotlin2JsCompile).kotlinOptions.freeCompilerArgs.filter { - it in backendSelectionArgs - } - ) - } - - enum class JS_BACKEND { - JS_IR, - JS_LEGACY, - JS_BOTH + freeArgs.value(kotlinCompilation.kotlinOptions.freeCompilerArgs) } - - private fun KotlinJsOptions.isPreIrBackendDisabled(): Boolean = - listOf( - "-Xir-only", - "-Xir-produce-js", - "-Xir-produce-klib-file" - ).any(freeCompilerArgs::contains) - - // see also isIncrementalCompilationEnabled - private fun KotlinJsOptions.isIrBackendEnabled(): Boolean = - listOf( - "-Xir-produce-klib-dir", - "-Xir-produce-js", - "-Xir-produce-klib-file" - ).any(freeCompilerArgs::contains) } @CacheableTask @@ -699,23 +628,15 @@ abstract class KspTaskMetadata @Inject constructor( @TaskAction fun execute(inputChanges: InputChanges) { - val args = K2MetadataCompilerArguments().apply { + val args = KotlinMetadataCompilerArguments().apply { // Common - useK2 = false - incrementalCompilation = false - pluginClasspaths = pluginClasspath.files.map { it.absolutePath }.toTypedArray() - apiVersion = this@KspTaskMetadata.kotlinApiVersion.orNull - languageVersion = this@KspTaskMetadata.kotlinLanguageVersion.orNull - verbose = this@KspTaskMetadata.verbose.get() - - addPluginOptions(options.get()) + fromTask(this@KspTaskMetadata) // Metadata - destination = this@KspTaskMetadata.destination.absolutePath - classpath = this@KspTaskMetadata.libraries.joinToString(":") - multiPlatform = true + destination = this@KspTaskMetadata.destination + libraries = this@KspTaskMetadata.libraries.files.toList() + friendPaths = this@KspTaskMetadata.friendPaths.files.toList() expectActualLinker = true - friendPaths = this@KspTaskMetadata.friendPaths.map { it.absolutePath }.toTypedArray() } // Clean outputs. Backups will be copied back if incremental. @@ -774,23 +695,15 @@ abstract class KspTaskNative @Inject constructor( @TaskAction fun execute(inputChanges: InputChanges) { - val args = K2NativeCompilerArguments().apply { + val args = KotlinNativeCompilerArguments().apply { // Common - useK2 = false - incrementalCompilation = false - pluginClasspaths = pluginClasspath.files.map { it.absolutePath }.toTypedArray() - apiVersion = this@KspTaskNative.kotlinApiVersion.orNull - languageVersion = this@KspTaskNative.kotlinLanguageVersion.orNull - verbose = this@KspTaskNative.verbose.get() - - addPluginOptions(options.get()) + fromTask(this@KspTaskNative) // Native destination = this@KspTaskNative.destination - libraries = this@KspTaskNative.libraries.map { it.absolutePath }.toTypedArray() - multiPlatform = true + libraries = this@KspTaskNative.libraries.files.toList() expectActualLinker = true - friendModules = this@KspTaskNative.friendPaths.asPath + friendPaths = this@KspTaskNative.friendPaths.files.toList() target = this@KspTaskNative.target.get() } From e73051e90cfd71ee038830bfb26d9c539017d0c5 Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Mon, 10 Oct 2022 22:57:21 -0700 Subject: [PATCH 7/8] UPDATE_KOTLIN_VERSION: 1.8.20-dev-649 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 667d4679a2..a1dd09220c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # Copied from kotlinc org.gradle.jvmargs=-Duser.country=US -Dkotlin.daemon.jvm.options=-Xmx2200m -Dfile.encoding=UTF-8 -kotlinBaseVersion=1.8.0-dev-2843 +kotlinBaseVersion=1.8.20-dev-649 agpBaseVersion=7.0.0 intellijVersion=203.8084.24 junitVersion=4.12 From 45354a730a168b9f861badd8e5b748298aa0ee67 Mon Sep 17 00:00:00 2001 From: Ting-Yuan Huang Date: Mon, 10 Oct 2022 22:56:30 -0700 Subject: [PATCH 8/8] Replace KotlinCompilerArguments with CompilerCommonOptions --- .../ksp/gradle/CompilerOptionsFactory.kt | 43 +++ .../ksp/gradle/KotlinCompilerArguments.kt | 93 ------- .../ksp/gradle/KotlinCompilerRunner.kt | 32 ++- .../ksp/gradle/KotlinCompilerRunnerImpls.kt | 150 +++++------ .../devtools/ksp/gradle/KspSubplugin.kt | 2 +- .../gradle/tasks/inherited/InheritedTasks.kt | 23 +- .../tasks/standalone/StandaloneTasks.kt | 253 +++++++++++------- 7 files changed, 305 insertions(+), 291 deletions(-) create mode 100644 gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/CompilerOptionsFactory.kt delete mode 100644 gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerArguments.kt diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/CompilerOptionsFactory.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/CompilerOptionsFactory.kt new file mode 100644 index 0000000000..7e4dd330d8 --- /dev/null +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/CompilerOptionsFactory.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2022 Google LLC + * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + +package com.google.devtools.ksp.gradle + +import org.gradle.api.model.ObjectFactory +import org.jetbrains.kotlin.gradle.dsl.CompilerCommonOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerCommonOptionsDefault +import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptionsDefault +import org.jetbrains.kotlin.gradle.dsl.CompilerJvmOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerJvmOptionsDefault +import org.jetbrains.kotlin.gradle.utils.newInstance + +// TODO: to be replaced by KotlinJvmFactory, etc. +class CompilerOptionsFactory { + companion object { + fun createCompilerJvmOptions(objectFactory: ObjectFactory): CompilerJvmOptions = + objectFactory.newInstance() + + fun createCompilerJsOptions(objectFactory: ObjectFactory): CompilerJsOptions = + objectFactory.newInstance() + + fun createCompilerCommonOptions(objectFactory: ObjectFactory): CompilerCommonOptions = + objectFactory.newInstance() + } +} diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerArguments.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerArguments.kt deleted file mode 100644 index 3dd49fb102..0000000000 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerArguments.kt +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright 2022 Google LLC - * Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.devtools.ksp.gradle - -import java.io.File - -/** - * A simplified version of CommonComilerArguments. - */ -open class KotlinCompilerArguments { - open var freeArgs: List = emptyList() - open var verbose: Boolean = false - open var allWarningsAsErrors: Boolean = false - open var languageVersion: String? = null - open var apiVersion: String? = null - open var useK2: Boolean = false - open var incrementalCompilation: Boolean = false - open var pluginOptions: List = emptyList() - open var pluginClasspaths: List = emptyList() - open var multiPlatform: Boolean = false - open var expectActualLinker: Boolean = false - - // A.K.A. friend modules - open var friendPaths: List = emptyList() - - // A.K.A. classpath - open var libraries: List = emptyList() - - // A.K.A. output, output file - open var destination: File? = null -} - -/** - * A simplified version of K2JVMComilerArguments. - */ -class KotlinJvmCompilerArguments : KotlinCompilerArguments() { - var noJdk: Boolean = false - var noStdlib: Boolean = false - var noReflect: Boolean = false - var moduleName: String? = null - var jvmTarget: String? = null - var jdkRelease: String? = null - var allowNoSourceFiles: Boolean = false - - var javaSourceRoots: List = emptyList() -} - -/** - * A simplified version of K2JSComilerArguments. - */ -class KotlinJsCompilerArguments : KotlinCompilerArguments() { - var noStdlib: Boolean = false - var irOnly: Boolean = false - var irProduceJs: Boolean = false - var irProduceKlibDir: Boolean = false - var irProduceKlibFile: Boolean = false - var irBuildCache: Boolean = false - var wasm: Boolean = false - var target: String = "v5" - - override var multiPlatform = true -} - -/** - * A simplified version of K2MetadataComilerArguments. - */ -class KotlinMetadataCompilerArguments : KotlinCompilerArguments() { - override var multiPlatform = true - override var expectActualLinker = true -} - -/** - * A simplified version of K2NativeComilerArguments. - */ -class KotlinNativeCompilerArguments : KotlinCompilerArguments() { - var target: String? = null - override var multiPlatform = true -} diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt index 4c91df7e5f..9c6e0ee7dc 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunner.kt @@ -26,6 +26,9 @@ import org.gradle.api.provider.ListProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.Classpath import org.gradle.api.tasks.Internal +import org.jetbrains.kotlin.gradle.dsl.CompilerCommonOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerJvmOptions import org.jetbrains.kotlin.gradle.tasks.KotlinCompilerExecutionStrategy import org.jetbrains.kotlin.gradle.utils.newInstance import java.io.File @@ -49,37 +52,50 @@ interface KotlinCompilerRunner { interface KotlinJvmCompilerRunner : KotlinCompilerRunner { fun runJvmCompilerAsync( - args: KotlinJvmCompilerArguments, + options: CompilerJvmOptions, + freeArgs: List, sources: List, commonSources: List, - outputs: List + friendPaths: List, + libraries: List, + destination: File ) } interface KotlinJsCompilerRunner : KotlinCompilerRunner { fun runJsCompilerAsync( - args: KotlinJsCompilerArguments, + options: CompilerJsOptions, + freeArgs: List, sources: List, commonSources: List, - outputs: List + friendPaths: List, + libraries: List, + destination: File ) } interface KotlinMetadataCompilerRunner : KotlinCompilerRunner { fun runMetadataCompilerAsync( - args: KotlinMetadataCompilerArguments, + options: CompilerCommonOptions, + freeArgs: List, sources: List, commonSources: List, - outputs: List + friendPaths: List, + libraries: List, + destination: File ) } interface KotlinNativeCompilerRunner : KotlinCompilerRunner { fun runNativeCompilerAsync( - args: KotlinNativeCompilerArguments, + options: CompilerCommonOptions, + freeArgs: List, sources: List, commonSources: List, - outputs: List, + friendPaths: List, + libraries: List, + destination: File, + target: String ) } diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt index 587b662275..a8f7bdd5c9 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KotlinCompilerRunnerImpls.kt @@ -28,7 +28,6 @@ import org.gradle.process.ExecOperations import org.gradle.workers.WorkerExecutor import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporter import org.jetbrains.kotlin.build.report.metrics.BuildMetricsReporterImpl -import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments @@ -52,6 +51,13 @@ import org.jetbrains.kotlin.utils.JsLibraryUtils import org.jetbrains.kotlin.utils.addToStdlib.ifNotEmpty import java.io.File import javax.inject.Inject +import org.jetbrains.kotlin.gradle.dsl.CompilerCommonOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerCommonOptionsDefault +import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptionsDefault +import org.jetbrains.kotlin.gradle.dsl.CompilerJvmOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerJvmOptionsDefault +import org.jetbrains.kotlin.gradle.logging.GradleErrorMessageCollector internal inline fun ObjectFactory.property() = property(T::class.java) internal inline fun ObjectFactory.property(initialValue: T) = property().value(initialValue) @@ -86,9 +92,10 @@ abstract class KotlinCompilerRunnerImpl @Inject constructor( @Internal internal fun prepareEnvironment(allWarningsAsErrors: Boolean, outputs: List): GradleCompilerEnvironment { val messageCollector = GradlePrintingMessageCollector(GradleKotlinLogger(logger), allWarningsAsErrors) + val errorMessageCollector = GradleErrorMessageCollector(messageCollector) val outputItemCollector = OutputItemsCollectorImpl() return GradleCompilerEnvironment( - compilerClasspath.files.toList(), messageCollector, outputItemCollector, + compilerClasspath.files.toList(), errorMessageCollector, outputItemCollector, reportingSettings = ReportingSettings(), outputFiles = outputs ) @@ -110,49 +117,31 @@ abstract class KotlinCompilerRunnerImpl @Inject constructor( } } -internal fun CommonCompilerArguments.copyFrom(args: KotlinCompilerArguments) { - freeArgs = args.freeArgs - verbose = args.verbose - allWarningsAsErrors = args.allWarningsAsErrors - languageVersion = args.languageVersion - apiVersion = args.apiVersion - useK2 = args.useK2 - incrementalCompilation = args.incrementalCompilation - pluginOptions = args.pluginOptions.toTypedArray() - pluginClasspaths = args.pluginClasspaths.map { it.absolutePath }.toTypedArray() - expectActualLinker = args.expectActualLinker - multiPlatform = args.multiPlatform -} - abstract class KotlinJvmCompilerRunnerImpl @Inject constructor( - task: Task, + private val task: Task, objectFactory: ObjectFactory, workerExecutor: WorkerExecutor ) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinJvmCompilerRunner { override fun runJvmCompilerAsync( - args: KotlinJvmCompilerArguments, + options: CompilerJvmOptions, + freeArgs: List, sources: List, commonSources: List, - outputs: List + friendPaths: List, + libraries: List, + destination: File ) { - val environment = prepareEnvironment(args.allWarningsAsErrors, outputs) + val environment = prepareEnvironment(options.allWarningsAsErrors.get(), task.outputs.files.toList()) val compilerRunner = prepareCompilerRunner() val compilerArgs = K2JVMCompilerArguments().apply { - copyFrom(args) - - friendPaths = args.friendPaths.map { it.absolutePath }.toTypedArray() - classpath = args.libraries.map { it.absolutePath }.joinToString(File.pathSeparator) - destination = args.destination?.absolutePath - - noJdk = args.noJdk - noStdlib = args.noStdlib - noReflect = args.noReflect - moduleName = args.moduleName - jvmTarget = args.jvmTarget - jdkRelease = args.jdkRelease - allowNoSourceFiles = args.allowNoSourceFiles - javaSourceRoots = args.javaSourceRoots.map { it.absolutePath }.toTypedArray() + options as CompilerJvmOptionsDefault + options.fillCompilerArguments(this) + + this@apply.friendPaths = friendPaths.map { it.absolutePath }.toTypedArray() + this@apply.classpath = libraries.map { it.absolutePath }.joinToString(File.pathSeparator) + this@apply.freeArgs = options.freeCompilerArgs.get() + freeArgs + this@apply.destination = destination.absolutePath } compilerRunner.runJvmCompilerAsync( @@ -168,7 +157,7 @@ abstract class KotlinJvmCompilerRunnerImpl @Inject constructor( } abstract class KotlinJsCompilerRunnerImpl @Inject constructor( - task: Task, + private val task: Task, objectFactory: ObjectFactory, workerExecutor: WorkerExecutor ) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinJsCompilerRunner { @@ -191,30 +180,33 @@ abstract class KotlinJsCompilerRunnerImpl @Inject constructor( } override fun runJsCompilerAsync( - args: KotlinJsCompilerArguments, + options: CompilerJsOptions, + freeArgs: List, sources: List, commonSources: List, - outputs: List + friendPaths: List, + libraries: List, + destination: File ) { - val environment = prepareEnvironment(args.allWarningsAsErrors, outputs) + val environment = prepareEnvironment(options.allWarningsAsErrors.get(), task.outputs.files.toList()) val compilerRunner = prepareCompilerRunner() val compilerArgs = K2JSCompilerArguments().apply { - copyFrom(args) + options as CompilerJsOptionsDefault + options.fillCompilerArguments(this) - outputFile = args.destination?.absolutePath + this@apply.freeArgs = options.freeCompilerArgs.get() + freeArgs + this@apply.outputFile = File(destination, "dummy.js").absolutePath - noStdlib = args.noStdlib - irOnly = args.irOnly - irProduceJs = args.irProduceJs - irProduceKlibDir = args.irProduceKlibDir - irProduceKlibFile = args.irProduceKlibFile - irBuildCache = args.irBuildCache - wasm = args.wasm - target = args.target + irOnly = this@apply.freeArgs.contains("-Xir-only") + irProduceJs = this@apply.freeArgs.contains("-Xir-produce-js") + irProduceKlibDir = this@apply.freeArgs.contains("-Xir-produce-klib-dir") + irProduceKlibFile = this@apply.freeArgs.contains("-Xir-produce-klib-file") + irBuildCache = this@apply.freeArgs.contains("-Xir-build-cache") + wasm = this@apply.freeArgs.contains("-Xwasm") - friendModules = args.friendPaths.filter { libFilter(this, it) } + this@apply.friendModules = friendPaths.filter { libFilter(this, it) } .map { it.absolutePath }.joinToString(File.pathSeparator) - libraries = args.libraries.filter { libFilter(this, it) } + this@apply.libraries = libraries.filter { libFilter(this, it) } .map { it.absolutePath }.joinToString(File.pathSeparator) } @@ -223,25 +215,30 @@ abstract class KotlinJsCompilerRunnerImpl @Inject constructor( } abstract class KotlinMetadataCompilerRunnerImpl @Inject constructor( - task: Task, + private val task: Task, objectFactory: ObjectFactory, workerExecutor: WorkerExecutor ) : KotlinCompilerRunnerImpl(task, objectFactory, workerExecutor), KotlinMetadataCompilerRunner { override fun runMetadataCompilerAsync( - args: KotlinMetadataCompilerArguments, + options: CompilerCommonOptions, + freeArgs: List, sources: List, commonSources: List, - outputs: List + friendPaths: List, + libraries: List, + destination: File ) { - val environment = prepareEnvironment(args.allWarningsAsErrors, outputs) + val environment = prepareEnvironment(options.allWarningsAsErrors.get(), task.outputs.files.toList()) val compilerRunner = prepareCompilerRunner() val compilerArgs = K2MetadataCompilerArguments().apply { - copyFrom(args) + options as CompilerCommonOptionsDefault + options.fillCompilerArguments(this) - friendPaths = args.friendPaths.map { it.absolutePath }.toTypedArray() - classpath = args.libraries.map { it.absolutePath }.joinToString(File.pathSeparator) - destination = args.destination?.absolutePath + this@apply.friendPaths = friendPaths.map { it.absolutePath }.toTypedArray() + this@apply.classpath = libraries.map { it.absolutePath }.joinToString(File.pathSeparator) + this@apply.freeArgs = options.freeCompilerArgs.get() + freeArgs + this@apply.destination = destination.absolutePath } compilerRunner.runMetadataCompilerAsync(sources, commonSources, compilerArgs, environment) @@ -249,7 +246,7 @@ abstract class KotlinMetadataCompilerRunnerImpl @Inject constructor( } abstract class KotlinNativeCompilerRunnerImpl @Inject constructor( - task: Task, + private val task: Task, private val objectFactory: ObjectFactory, workerExecutor: WorkerExecutor, private val execOperations: ExecOperations @@ -259,45 +256,44 @@ abstract class KotlinNativeCompilerRunnerImpl @Inject constructor( .KotlinNativeCompilerRunner.Settings.fromProject(task.project) override fun runNativeCompilerAsync( - args: KotlinNativeCompilerArguments, + options: CompilerCommonOptions, + freeArgs: List, sources: List, commonSources: List, - outputs: List, + friendPaths: List, + libraries: List, + destination: File, + target: String ) { - val output = File(outputs.first(), "dummy.out") - - val target = KonanTarget.predefinedTargets.get(args.target!!)!! + val target = KonanTarget.predefinedTargets.get(target)!! val buildArgs: MutableList = mutableListOf( - "-o", output.path, + "-o", destination.path, "-target", target.name, "-p", "library", "-Xmulti-platform" ) - args.libraries.flatMap { listOf("-l", it.absolutePath) }.let { buildArgs.addAll(it) } - args.friendPaths.ifNotEmpty { + libraries.flatMap { listOf("-l", it.absolutePath) }.let { buildArgs.addAll(it) } + friendPaths.ifNotEmpty { buildArgs.add("-friend-modules") buildArgs.add(joinToString(File.pathSeparator)) } - if (args.verbose) + if (options.verbose.get()) buildArgs.add("-verbose") - if (args.allWarningsAsErrors) + if (options.allWarningsAsErrors.get()) buildArgs.add("-Werror") - args.pluginClasspaths.map { "-Xplugin=${it.absolutePath}" }.let { buildArgs.addAll(it) } - args.pluginOptions.flatMap { listOf("-P", it) }.let { buildArgs.addAll(it) } - - args.languageVersion?.let { + options.languageVersion.getOrNull()?.let { buildArgs.add("-language-version") - buildArgs.add(it) + buildArgs.add(it.version) } - args.apiVersion?.let { + options.apiVersion.getOrNull()?.let { buildArgs.add("-api-version") - buildArgs.add(it) + buildArgs.add(it.version) } buildArgs.addAll(sources.map { it.absolutePath }) - buildArgs.addAll(args.freeArgs) + buildArgs.addAll(freeArgs) buildArgs.addAll(commonSources.map { it.absolutePath }) org.jetbrains.kotlin.compilerRunner.KotlinNativeCompilerRunner( diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt index 79e663f616..ed13258170 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/KspSubplugin.kt @@ -298,7 +298,7 @@ internal inline fun Project.locateTask(name: String): TaskPro internal fun findJavaTaskForKotlinCompilation(compilation: KotlinCompilation<*>): TaskProvider? = when (compilation) { is KotlinJvmAndroidCompilation -> compilation.compileJavaTaskProvider - is KotlinWithJavaCompilation -> compilation.compileJavaTaskProvider + is KotlinWithJavaCompilation<*, *> -> compilation.compileJavaTaskProvider is KotlinJvmCompilation -> compilation.compileJavaTaskProvider // may be null for Kotlin-only JVM target in MPP else -> null } diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/inherited/InheritedTasks.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/inherited/InheritedTasks.kt index 5bc8f35914..7a0586e823 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/inherited/InheritedTasks.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/inherited/InheritedTasks.kt @@ -55,11 +55,10 @@ import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments -import org.jetbrains.kotlin.gradle.dsl.KotlinJsOptionsImpl -import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptionsImpl -import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformCommonOptionsImpl +import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptionsDefault +import org.jetbrains.kotlin.gradle.dsl.CompilerJvmOptionsDefault +import org.jetbrains.kotlin.gradle.dsl.CompilerMultiplatformCommonOptionsDefault import org.jetbrains.kotlin.gradle.dsl.copyFreeCompilerArgsToArgs -import org.jetbrains.kotlin.gradle.dsl.fillDefaultValues import org.jetbrains.kotlin.gradle.internal.CompilerArgumentsContributor import org.jetbrains.kotlin.gradle.internal.compilerArgumentsConfigurationFlags import org.jetbrains.kotlin.gradle.internal.kapt.incremental.CLASS_STRUCTURE_ARTIFACT_TYPE @@ -92,6 +91,9 @@ import java.nio.file.Paths import java.util.concurrent.Callable import javax.inject.Inject import kotlin.reflect.KProperty1 +import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerJvmOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerMultiplatformCommonOptions @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "EXPOSED_PARAMETER_TYPE") internal class Configurator : AbstractKotlinCompileConfig> { @@ -263,9 +265,10 @@ interface KspTaskInherited : KspTask { @CacheableTask abstract class KspTaskJvm @Inject constructor( + compilerOptions: CompilerJvmOptions, workerExecutor: WorkerExecutor, objectFactory: ObjectFactory -) : KotlinCompile(KotlinJvmOptionsImpl(), workerExecutor, objectFactory), KspTaskInherited { +) : KotlinCompile(compilerOptions, workerExecutor, objectFactory), KspTaskInherited { @get:PathSensitive(PathSensitivity.NONE) @get:Optional @get:InputFiles @@ -476,9 +479,10 @@ abstract class KspTaskJvm @Inject constructor( @CacheableTask abstract class KspTaskJS @Inject constructor( + compilerOptions: CompilerJsOptions, @get:Internal val objectFactory: ObjectFactory, workerExecutor: WorkerExecutor -) : Kotlin2JsCompile(KotlinJsOptionsImpl(), objectFactory, workerExecutor), KspTaskInherited { +) : Kotlin2JsCompile(compilerOptions, objectFactory, workerExecutor), KspTaskInherited { private val backendSelectionArgs = listOf( "-Xir-only", "-Xir-produce-js", @@ -519,7 +523,7 @@ abstract class KspTaskJS @Inject constructor( ignoreClasspathResolutionErrors: Boolean, ) { // Start with / copy from kotlinCompile. - args.fillDefaultValues() + (compilerOptions as CompilerJsOptionsDefault).fillDefaultValues(args) compileKotlinArgumentsContributor.get().contributeArguments( args, compilerArgumentsConfigurationFlags( @@ -570,9 +574,10 @@ abstract class KspTaskJS @Inject constructor( @CacheableTask abstract class KspTaskMetadata @Inject constructor( + compilerOptions: CompilerMultiplatformCommonOptions, workerExecutor: WorkerExecutor, objectFactory: ObjectFactory -) : KotlinCompileCommon(KotlinMultiplatformCommonOptionsImpl(), workerExecutor, objectFactory), KspTaskInherited { +) : KotlinCompileCommon(compilerOptions, workerExecutor, objectFactory), KspTaskInherited { override fun configureCompilation( kotlinCompilation: KotlinCompilationData<*>, kotlinCompile: AbstractKotlinCompile<*>, @@ -603,7 +608,7 @@ abstract class KspTaskMetadata @Inject constructor( ignoreClasspathResolutionErrors: Boolean, ) { // Start with / copy from kotlinCompile. - args.apply { fillDefaultValues() } + (compilerOptions as CompilerJsOptionsDefault).fillDefaultValues(args) compileKotlinArgumentsContributor.get().contributeArguments( args, compilerArgumentsConfigurationFlags( diff --git a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt index aade62b2d5..d0a66ea4b4 100644 --- a/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt +++ b/gradle-plugin/src/main/kotlin/com/google/devtools/ksp/gradle/tasks/standalone/StandaloneTasks.kt @@ -19,11 +19,7 @@ package com.google.devtools.ksp.gradle.tasks.standalone -import com.google.devtools.ksp.gradle.KotlinCompilerArguments -import com.google.devtools.ksp.gradle.KotlinJsCompilerArguments -import com.google.devtools.ksp.gradle.KotlinJvmCompilerArguments -import com.google.devtools.ksp.gradle.KotlinMetadataCompilerArguments -import com.google.devtools.ksp.gradle.KotlinNativeCompilerArguments +import com.google.devtools.ksp.gradle.CompilerOptionsFactory import com.google.devtools.ksp.gradle.KspExtension import com.google.devtools.ksp.gradle.KspGradleSubplugin import com.google.devtools.ksp.gradle.KspGradleSubplugin.Companion.getKspCachesDir @@ -91,6 +87,11 @@ import org.jetbrains.kotlin.utils.addToStdlib.safeAs import java.io.File import java.nio.file.Paths import javax.inject.Inject +import org.jetbrains.kotlin.gradle.dsl.CompilerCommonOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerCommonToolOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerJsOptions +import org.jetbrains.kotlin.gradle.dsl.CompilerJvmOptions +import org.jetbrains.kotlin.gradle.tasks.KotlinCompilationTask object StandaloneTasks : KspTaskCreator { override fun createKspTask( @@ -292,7 +293,7 @@ private fun ChangedFiles.hasNonSourceChange(): Boolean { } } -fun KotlinCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { +fun MutableList.addChangedClasses(changed: KaptClasspathChanges) { if (changed is KaptClasspathChanges.Known) { changed.names.map { it.replace('/', '.').replace('$', '.') }.ifNotEmpty { addPluginOptions(listOf(SubpluginOption("changedClasses", joinToString(":")))) @@ -302,11 +303,11 @@ fun KotlinCompilerArguments.addChangedClasses(changed: KaptClasspathChanges) { fun SubpluginOption.toArg() = "plugin:${KspGradleSubplugin.KSP_PLUGIN_ID}:$key=$value" -fun KotlinCompilerArguments.addPluginOptions(options: List) { - pluginOptions += options.map { it.toArg() } +fun MutableList.addPluginOptions(options: List) { + this.addAll(options.flatMap { listOf("-P", it.toArg()) }) } -fun KotlinCompilerArguments.addChangedFiles(changedFiles: ChangedFiles) { +fun MutableList.addChangedFiles(changedFiles: ChangedFiles) { if (changedFiles is ChangedFiles.Known) { val options = mutableListOf() changedFiles.modified.filter { it.isKotlinFile(listOf("kt")) || it.isJavaFile() }.ifNotEmpty { @@ -326,17 +327,48 @@ internal fun File.isParentOf(childCandidate: File): Boolean { return childCandidatePath.startsWith(parentPath) } -private fun KotlinCompilerArguments.fromTask(task: KspTaskStandalone) { - useK2 = false - incrementalCompilation = false - pluginClasspaths = task.pluginClasspath.files.toList() - apiVersion = task.kotlinApiVersion.orNull - languageVersion = task.kotlinLanguageVersion.orNull - verbose = task.verbose.get() - multiPlatform = task.multiplatform.get() - allWarningsAsErrors = task.allWarningAsErrors.get() - - addPluginOptions(task.options.get()) +private fun CompilerCommonToolOptions.from(that: CompilerCommonToolOptions) { + // Copy from compilation task + allWarningsAsErrors.value(that.allWarningsAsErrors) + suppressWarnings.value(that.suppressWarnings) + verbose.value(that.verbose) + + // NOT COPIED: freeCompilerArgs +} + +private fun CompilerCommonOptions.from(that: CompilerCommonOptions) { + // Copy from compilation task + (this as CompilerCommonToolOptions).from(that) + apiVersion.value(that.apiVersion) + languageVersion.value(that.languageVersion) + + // NOT COPIED: + useK2.value(false) +} + +private fun CompilerJvmOptions.from(that: CompilerJvmOptions) { + // Copy from compilation task + (this as CompilerCommonOptions).from(that) + javaParameters.value(that.javaParameters) + jvmTarget.value(that.jvmTarget) + moduleName.value(that.moduleName) + + // NOT COPIED: + noJdk.value(true) +} + +private fun CompilerJsOptions.from(that: CompilerJsOptions) { + // Copy from compilation task + (this as CompilerCommonOptions).from(that) + friendModulesDisabled.value(that.friendModulesDisabled) + main.value(that.main) + metaInfo.value(that.metaInfo) + moduleKind.value(that.moduleKind) + moduleName.value(that.moduleName) + noStdlib.value(that.noStdlib) + target.value(that.target) + + // NOT COPIED: outputFile, sourceMap, sourceMapEmbedSources, sourceMapPrefix, typedArrays } @CacheableTask @@ -346,9 +378,6 @@ abstract class KspTaskJvm @Inject constructor( @get:Nested val compilerRunner = createKotlinJvmCompilerRunner(this, objectFactory) - @get:Input - abstract val moduleName: Property - @get:Input var isIntermoduleIncremental: Boolean = false @@ -370,25 +399,21 @@ abstract class KspTaskJvm @Inject constructor( classpathSnapshotProperties.classpathSnapshot ) - @get:Internal - abstract val noJdk: Property + @get:Nested + val compilerOptions: CompilerJvmOptions = CompilerOptionsFactory.createCompilerJvmOptions(objectFactory) @TaskAction fun execute(inputChanges: InputChanges) { - val args = KotlinJvmCompilerArguments().apply { - // Common - fromTask(this@KspTaskJvm) - - // JVM - noJdk = this@KspTaskJvm.noJdk.get() - allowNoSourceFiles = true - destination = this@KspTaskJvm.destination - noReflect = true - noStdlib = true - libraries = this@KspTaskJvm.libraries.files.toList() - friendPaths = this@KspTaskJvm.friendPaths.files.toList() - moduleName = this@KspTaskJvm.moduleName.get() - } + val freeArgs = mutableListOf( + "-Xallow-no-source-files", + "-no-stdlib", + ) + + if (multiplatform.get()) + freeArgs.add("-Xmulti-platform") + + freeArgs.addAll(pluginClasspath.map { "-Xplugin=${it.absolutePath}" }) + freeArgs.addPluginOptions(options.get()) // Clean outputs. Backups will be copied back if incremental. // TODO: leave outputs untouched and only restore outputs if build fails. @@ -402,7 +427,7 @@ abstract class KspTaskJvm @Inject constructor( // 1. unknown changes, or // 2. changes in annotation processors. val classpathChanges = findClasspathChanges(changedFiles) - args.addChangedClasses(classpathChanges) + freeArgs.addChangedClasses(classpathChanges) } else { if (changedFiles.hasNonSourceChange()) { clearIncCache() @@ -411,13 +436,16 @@ abstract class KspTaskJvm @Inject constructor( } else { clearIncCache() } - args.addChangedFiles(changedFiles) + freeArgs.addChangedFiles(changedFiles) compilerRunner.runJvmCompilerAsync( - args, + compilerOptions, + freeArgs, allSources.files.filter { !destination.isParentOf(it) }, commonSources.files.filter { !destination.isParentOf(it) }, - outputs.files.toList() + friendPaths.files.toList(), + libraries.files.toList(), + destination ) } @@ -433,12 +461,12 @@ abstract class KspTaskJvm @Inject constructor( compilerRunner.useDaemonFallbackStrategy.set(kotlinCompile.useDaemonFallbackStrategy) compilerRunner.kotlinDaemonJvmArguments.set(kotlinCompile.kotlinDaemonJvmArguments) + kotlinCompile as KotlinCompilationTask + compilerOptions.from(kotlinCompile.compilerOptions) // Android compilation doesn't include JDK libraries. // Copying the settings from KotlinCompilation is complicated and implementation dependent. So let's check // for Android explicitly. - noJdk.value(kotlinCompilation is KotlinJvmAndroidCompilation) - - moduleName.value(kotlinCompilation.moduleName) + compilerOptions.noJdk.value(kotlinCompilation is KotlinJvmAndroidCompilation) isIntermoduleIncremental = (project.findProperty("ksp.incremental.intermodule")?.toString()?.toBoolean() ?: true) && isKspIncremental @@ -538,6 +566,9 @@ abstract class KspTaskJs @Inject constructor( @get:Nested val compilerRunner = createKotlinJsCompilerRunner(this, objectFactory) + @get:Nested + val compilerOptions: CompilerJsOptions = CompilerOptionsFactory.createCompilerJsOptions(objectFactory) + @get:Internal abstract val freeArgs: ListProperty @@ -550,28 +581,14 @@ abstract class KspTaskJs @Inject constructor( @TaskAction fun execute(inputChanges: InputChanges) { - val args = KotlinJsCompilerArguments().apply { - // Common - fromTask(this@KspTaskJs) - - // JS - destination = this@KspTaskJs.destination - noStdlib = true - libraries = this@KspTaskJs.libraries.files.toList() - friendPaths = this@KspTaskJs.friendPaths.files.toList() - destination = File(destination, "dummyOutput.js") - - this@KspTaskJs.freeArgs.get().forEach { - when (it) { - "-Xir-only" -> irOnly = true - "-Xir-produce-js" -> irProduceJs = true - "-Xir-produce-klib-file" -> irProduceKlibFile = true - "-Xir-produce-klib-dir" -> irProduceKlibDir = true - "-Xir-build-cache" -> irBuildCache = true - "-Xwasm" -> wasm = true - } - } - } + val freeArgs = mutableListOf() + + if (multiplatform.get()) + freeArgs.add("-Xmulti-platform") + + freeArgs.addAll(pluginClasspath.map { "-Xplugin=${it.absolutePath}" }) + freeArgs.addPluginOptions(options.get()) + freeArgs.addAll(this@KspTaskJs.freeArgs.get().filter { it in backendSelectionArgs }) // Clean outputs. Backups will be copied back if incremental. // TODO: leave outputs untouched and only restore outputs if build fails. @@ -586,16 +603,28 @@ abstract class KspTaskJs @Inject constructor( } else { clearIncCache() } - args.addChangedFiles(changedFiles) + freeArgs.addChangedFiles(changedFiles) compilerRunner.runJsCompilerAsync( - args, + compilerOptions, + freeArgs, allSources.files.toList(), commonSources.files.toList(), - outputs.files.toList() + friendPaths.files.toList(), + libraries.files.toList(), + destination ) } + private val backendSelectionArgs = setOf( + "-Xir-only", + "-Xir-produce-js", + "-Xir-produce-klib-file", + "-Xir-produce-klib-dir", + "-Xir-build-cache", + "-Xwasm", + ) + override fun configureCompilation( kotlinCompilation: KotlinCompilation<*>, kotlinCompile: KotlinCompileTool, @@ -608,7 +637,9 @@ abstract class KspTaskJs @Inject constructor( compilerRunner.useDaemonFallbackStrategy.set(kotlinCompile.useDaemonFallbackStrategy) compilerRunner.kotlinDaemonJvmArguments.set(kotlinCompile.kotlinDaemonJvmArguments) - freeArgs.value(kotlinCompilation.kotlinOptions.freeCompilerArgs) + kotlinCompile as KotlinCompilationTask + compilerOptions.from(kotlinCompile.compilerOptions) + freeArgs.value(kotlinCompile.compilerOptions.freeCompilerArgs) } } @@ -626,18 +657,20 @@ abstract class KspTaskMetadata @Inject constructor( commonSources, ) + @get:Nested + val compilerOptions: CompilerCommonOptions = CompilerOptionsFactory.createCompilerCommonOptions(objectFactory) + @TaskAction fun execute(inputChanges: InputChanges) { - val args = KotlinMetadataCompilerArguments().apply { - // Common - fromTask(this@KspTaskMetadata) - - // Metadata - destination = this@KspTaskMetadata.destination - libraries = this@KspTaskMetadata.libraries.files.toList() - friendPaths = this@KspTaskMetadata.friendPaths.files.toList() - expectActualLinker = true - } + val freeArgs = mutableListOf( + "-Xexpect-actual-linker" + ) + + if (multiplatform.get()) + freeArgs.add("-Xmulti-platform") + + freeArgs.addAll(pluginClasspath.map { "-Xplugin=${it.absolutePath}" }) + freeArgs.addPluginOptions(options.get()) // Clean outputs. Backups will be copied back if incremental. // TODO: leave outputs untouched and only restore outputs if build fails. @@ -652,13 +685,16 @@ abstract class KspTaskMetadata @Inject constructor( } else { clearIncCache() } - args.addChangedFiles(changedFiles) + freeArgs.addChangedFiles(changedFiles) compilerRunner.runMetadataCompilerAsync( - args, - allSources.files.toList(), - commonSources.files.toList(), - outputs.files.toList() + compilerOptions, + freeArgs, + allSources.files.filter { !destination.isParentOf(it) }, + commonSources.files.filter { !destination.isParentOf(it) }, + friendPaths.files.toList(), + libraries.files.toList(), + destination ) } @@ -673,6 +709,9 @@ abstract class KspTaskMetadata @Inject constructor( compilerRunner.compilerExecutionStrategy.set(kotlinCompile.compilerExecutionStrategy) compilerRunner.useDaemonFallbackStrategy.set(kotlinCompile.useDaemonFallbackStrategy) compilerRunner.kotlinDaemonJvmArguments.set(kotlinCompile.kotlinDaemonJvmArguments) + + kotlinCompile as KotlinCompilationTask + compilerOptions.from(kotlinCompile.compilerOptions) } } @@ -693,19 +732,20 @@ abstract class KspTaskNative @Inject constructor( @get:Input abstract val target: Property + @get:Nested + val compilerOptions: CompilerCommonOptions = CompilerOptionsFactory.createCompilerCommonOptions(objectFactory) + @TaskAction fun execute(inputChanges: InputChanges) { - val args = KotlinNativeCompilerArguments().apply { - // Common - fromTask(this@KspTaskNative) - - // Native - destination = this@KspTaskNative.destination - libraries = this@KspTaskNative.libraries.files.toList() - expectActualLinker = true - friendPaths = this@KspTaskNative.friendPaths.files.toList() - target = this@KspTaskNative.target.get() - } + val freeArgs = mutableListOf( + "-Xexpect-actual-linker" + ) + + if (multiplatform.get()) + freeArgs.add("-Xmulti-platform") + + freeArgs.addAll(pluginClasspath.map { "-Xplugin=${it.absolutePath}" }) + freeArgs.addPluginOptions(options.get()) // Clean outputs. Backups will be copied back if incremental. // TODO: leave outputs untouched and only restore outputs if build fails. @@ -720,13 +760,17 @@ abstract class KspTaskNative @Inject constructor( } else { clearIncCache() } - args.addChangedFiles(changedFiles) + freeArgs.addChangedFiles(changedFiles) compilerRunner.runNativeCompilerAsync( - args, - allSources.toList(), - commonSources.toList(), - outputs.files.toList(), + compilerOptions, + freeArgs, + allSources.files.filter { !destination.isParentOf(it) }, + commonSources.files.filter { !destination.isParentOf(it) }, + friendPaths.files.toList(), + libraries.files.toList(), + File(destination, "dummy.out"), + target.get() ) } @@ -735,6 +779,9 @@ abstract class KspTaskNative @Inject constructor( kotlinCompile: KotlinCompileTool, ) { target.value((kotlinCompile as KotlinNativeCompile).target) + + kotlinCompile as KotlinCompilationTask + compilerOptions.from(kotlinCompile.compilerOptions) } }