From 9e1c9bd87bcdf4a7f044c927bb8fab200d829b5f Mon Sep 17 00:00:00 2001 From: Cedric Beust Date: Mon, 17 Apr 2017 16:27:12 -0700 Subject: [PATCH] Add support for kapt3. --- .../com/beust/kobalt/plugin/apt/AptPlugin.kt | 164 +++++++++++------- .../kobalt/plugin/kotlin/KotlinCompiler.kt | 83 +++++---- 2 files changed, 151 insertions(+), 96 deletions(-) diff --git a/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt b/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt index 039e22be..8c2873cf 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/apt/AptPlugin.kt @@ -3,8 +3,8 @@ package com.beust.kobalt.plugin.apt import com.beust.kobalt.Constants import com.beust.kobalt.TaskResult import com.beust.kobalt.api.* +import com.beust.kobalt.api.annotation.AnnotationDefault import com.beust.kobalt.api.annotation.Directive -import com.beust.kobalt.api.annotation.Task import com.beust.kobalt.homeDir import com.beust.kobalt.internal.CompilerUtils import com.beust.kobalt.maven.DependencyManager @@ -12,6 +12,7 @@ import com.beust.kobalt.maven.aether.Filters import com.beust.kobalt.maven.aether.Scope import com.beust.kobalt.maven.dependency.FileDependency import com.beust.kobalt.misc.KFiles +import com.beust.kobalt.misc.KobaltLogger import com.beust.kobalt.misc.warn import com.beust.kobalt.plugin.kotlin.KotlinPlugin import com.google.common.collect.ArrayListMultimap @@ -21,7 +22,7 @@ import java.util.* import javax.inject.Singleton /** - * The AptPlugin has two components: + * The AptPlugin manages both apt and kapt. Each of them has two components: * 1) A new apt directive inside a dependency{} block (similar to compile()) that declares where * the annotation processor is found * 2) An apt{} configuration on Project that lets the user configure how the annotation is performed @@ -30,13 +31,53 @@ import javax.inject.Singleton @Singleton class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, val kotlinPlugin: KotlinPlugin, val compilerUtils: CompilerUtils) - : BasePlugin(), ICompilerFlagContributor, ISourceDirectoryContributor { + : BasePlugin(), ICompilerFlagContributor, ISourceDirectoryContributor, IClasspathContributor, ITaskContributor { - // ISourceDirectoryContributor + companion object { + const val PLUGIN_NAME = "Apt" + const val KAPT_CONFIG = "kaptConfig" + const val APT_CONFIG = "aptConfig" + } + + override val name = PLUGIN_NAME + + var kaptConfig: KaptConfig? = null + + override fun apply(project: Project, context: KobaltContext) { + super.apply(project, context) + kaptConfig = kaptConfigs[project.name] + + // Delete the output directories + listOf(aptConfigs[project.name]?.outputDir, kaptConfig?.outputDir) + .filterNotNull() + .distinct() + .map { generatedDir(project, it) } + .forEach { + it.normalize().absolutePath.let { path -> + context.logger.log(project.name, 1, " Deleting " + path) + val success = it.deleteRecursively() + if (!success) warn(" Couldn't delete " + path) + } + } + } + + // IClasspathContributor + override fun classpathEntriesFor(project: Project?, context: KobaltContext): Collection { + val result = arrayListOf() + if (project != null && kaptConfig != null) { + kaptConfig?.let { config -> + val c = generatedClasses(project, context, config.outputDir) + File(c).mkdirs() + result.add(FileDependency(c)) + } + } + return result + } private fun generatedDir(project: Project, outputDir: String) : File - = File(KFiles.joinDir(project.directory, KFiles.KOBALT_BUILD_DIR, outputDir)) + = File(KFiles.joinDir(project.directory, KFiles.KOBALT_BUILD_DIR, outputDir)) + // ISourceDirectoryContributor override fun sourceDirectoriesFor(project: Project, context: KobaltContext): List { val result = arrayListOf() aptConfigs[project.name]?.let { config -> @@ -50,29 +91,6 @@ class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, va return result } - companion object { - const val PLUGIN_NAME = "Apt" - const val KAPT_CONFIG = "kaptConfig" - const val APT_CONFIG = "aptConfig" - } - - override val name = PLUGIN_NAME - - override fun apply(project: Project, context: KobaltContext) { - super.apply(project, context) - listOf(aptConfigs[project.name]?.outputDir, aptConfigs[project.name]?.outputDir) - .filterNotNull() - .distinct() - .map { generatedDir(project, it) } - .forEach { - it.normalize().absolutePath.let { path -> - context.logger.log(project.name, 1, " Deleting " + path) - val success = it.deleteRecursively() - if (!success) warn(" Couldn't delete " + path) - } - } - } - private fun generated(project: Project, context: KobaltContext, outputDir: String) = KFiles.joinAndMakeDir(project.directory, project.buildDirectory, outputDir, context.variant.toIntermediateDir()) @@ -84,13 +102,32 @@ class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, va private fun generatedClasses(project: Project, context: KobaltContext, outputDir: String) = KFiles.joinDir(generated(project, context, outputDir), "classes") -// @Task(name = "compileKapt", dependsOn = arrayOf("runKapt"), reverseDependsOn = arrayOf("compile")) + // ITaskContributor + override fun tasksFor(project: Project, context: KobaltContext): List { + val result = + if (kaptConfig != null) { + listOf( + DynamicTask(this, "runKapt", "Run kapt", AnnotationDefault.GROUP, project, + reverseDependsOn = listOf("compile"), runAfter = listOf("clean"), + closure = {p: Project -> taskRunKapt(p)}), + DynamicTask(this, "compileKapt", "Compile the sources generated by kapt", + AnnotationDefault.GROUP, project, + dependsOn = listOf("runKapt"), reverseDependsOn = listOf("compile"), + closure = {p: Project -> taskCompileKapt(p)}) + ) + } else { + emptyList() + } + return result + } + fun taskCompileKapt(project: Project) : TaskResult { + var success = true kaptConfigs[project.name]?.let { config -> val sourceDirs = listOf( generatedStubs(project, context, config.outputDir), generatedSources(project, context, config.outputDir)) - val sourceFiles = KFiles.findSourceFiles(project.directory, sourceDirs, listOf("kt", "java")).toList() + val sourceFiles = KFiles.findSourceFiles(project.directory, sourceDirs, listOf("kt")).toList() val buildDirectory = File(KFiles.joinDir(project.directory, generatedClasses(project, context, config.outputDir))) val flags = listOf() @@ -98,10 +135,10 @@ class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, va buildDirectory, flags, emptyList(), forceRecompile = true) val cr = compilerUtils.invokeCompiler(project, context, kotlinPlugin.compiler, cai) - println("") + success = cr.failedResult == null } - return TaskResult() + return TaskResult(success) } val annotationDependencyId = "org.jetbrains.kotlin:kotlin-annotation-processing:" + @@ -121,31 +158,41 @@ class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, va return allDeps } -// @Task(name = "runKapt", reverseDependsOn = arrayOf("compile"), runAfter = arrayOf("clean")) fun taskRunKapt(project: Project) : TaskResult { + var success = true val flags = arrayListOf() - kaptConfigs[project.name]?.let { config -> + kaptConfig?.let { config -> - fun kaptPluginFlag(flagValue: String): String { - return "plugin:org.jetbrains.kotlin.kapt3:$flagValue" - } val generated = generated(project, context, config.outputDir) - val generatedSources = generatedSources(project, context, config.outputDir) + val generatedSources = generatedSources(project, context, config.outputDir).replace("//", "/") File(generatedSources).mkdirs() + // + // Tell the Kotlin compiler to use the annotation plug-in + // val allDeps = allDependencies() flags.add("-Xplugin") flags.add(annotationProcessorDependency().jarFile.get().absolutePath) flags.add("-P") - val kaptPluginFlags = arrayListOf() -// kaptPluginFlags.add(kaptPluginFlag("aptOnly=true")) - kaptPluginFlags.add(kaptPluginFlag("sources=" + generatedSources)) - kaptPluginFlags.add(kaptPluginFlag("classes=" + generatedClasses(project, context, config.outputDir))) - kaptPluginFlags.add(kaptPluginFlag("stubs=" + generatedStubs(project, context, config.outputDir))) - kaptPluginFlags.add(kaptPluginFlag("verbose=true")) - kaptPluginFlags.add(kaptPluginFlag("aptOnly=false")) + // + // Pass options to the annotation plugin + // + fun kaptPluginFlag(flagValue: String) = "plugin:org.jetbrains.kotlin.kapt3:$flagValue" + val kaptPluginFlags = arrayListOf() + val verbose = KobaltLogger.LOG_LEVEL >= 2 + listOf("sources=" + generatedSources, + "classes=" + generatedClasses(project, context, config.outputDir), + "stubs=" + generatedStubs(project, context, config.outputDir), + "verbose=$verbose", + "aptOnly=true").forEach { + kaptPluginFlags.add(kaptPluginFlag(it)) + } + + // + // Dependencies for the annotation plug-in and the generation + // val dependencies = dependencyManager.calculateDependencies(project, context, Filters.EXCLUDE_OPTIONAL_FILTER, listOf(Scope.COMPILE), @@ -159,35 +206,30 @@ class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, va listOf("-language-version", "1.1", " -api-version", "1.1").forEach { flags.add(it) } - val sourceFiles = -// KFiles.findSourceFiles(project.directory, -// listOf("src/tmp/kotlin"), -// listOf("kt")) -// .toList() - KFiles.findSourceFiles(project.directory, project.sourceDirectories, listOf("kt")).toList() + - generatedSources -// + val sourceFiles = + KFiles.findSourceFiles(project.directory, project.sourceDirectories, listOf("kt")) + .toList() + generatedSources val buildDirectory = File(KFiles.joinDir(project.directory, generated)) val cai = CompilerActionInfo(project.directory, allDeps, sourceFiles, listOf(".kt"), buildDirectory, flags, emptyList(), forceRecompile = true) - println("FLAGS: " + flags.joinToString("\n")) - println(" " + kaptPluginFlags.joinToString("\n ")) + context.logger.log(project.name, 2, " " + kaptPluginFlags.joinToString("\n ")) val cr = compilerUtils.invokeCompiler(project, context, kotlinPlugin.compiler, cai) - println("") + success = cr.failedResult == null } - return TaskResult() + return TaskResult(success) } // ICompilerFlagContributor override fun compilerFlagsFor(project: Project, context: KobaltContext, currentFlags: List, suffixesBeingCompiled: List): List { - if (!suffixesBeingCompiled.contains("java")) return emptyList() - val result = arrayListOf() + // Only run for Java files + if (!suffixesBeingCompiled.contains("java")) return emptyList() + fun addFlags(outputDir: String) { aptDependencies[project.name]?.let { result.add("-s") @@ -199,10 +241,6 @@ class AptPlugin @Inject constructor(val dependencyManager: DependencyManager, va addFlags(config.outputDir) } - kaptConfigs[project.name]?.let { config -> - addFlags(config.outputDir) - } - context.logger.log(project.name, 2, "New flags from apt: " + result.joinToString(" ")) return result } diff --git a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt index 5fa6af71..7d5892b0 100644 --- a/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt +++ b/src/main/kotlin/com/beust/kobalt/plugin/kotlin/KotlinCompiler.kt @@ -44,14 +44,16 @@ class KotlinCompiler @Inject constructor( var filesToCompile = 0 if (! info.outputDir.path.endsWith("ript.jar")) { // Don't display the message if compiling Build.kt - filesToCompile = - info.sourceFiles.map(::File).map { - if (it.isDirectory) KFiles.findRecursively(it).size else 1 - }.reduce { a, b -> - a + b - } - kobaltLog.log(projectName ?: "", 1, - " Kotlin $version compiling " + Strings.pluralizeAll(filesToCompile, "file")) + if (info.sourceFiles.isNotEmpty()) { + filesToCompile = + info.sourceFiles.map(::File).map { + if (it.isDirectory) KFiles.findRecursively(it).size else 1 + }.reduce { a, b -> + a + b + } + kobaltLog.log(projectName ?: "", 1, + " Kotlin $version compiling " + Strings.pluralizeAll(filesToCompile, "file")) + } } val cp = compilerFirst(info.dependencies.map { it.jarFile.get() }) val infoDir = info.directory @@ -135,8 +137,10 @@ class KotlinCompiler @Inject constructor( val friends = info.friendPaths.toTypedArray() // Collect the compiler args from kotlinCompiler{} and from settings.xml and parse them - val args2 = (kotlinConfig(project)?.args ?: arrayListOf()) + - (settings.kobaltCompilerFlags?.split(" ") ?: listOf()) + val args2 = + info.compilerArgs + + (kotlinConfig(project)?.args ?: arrayListOf()) + + (settings.kobaltCompilerFlags?.split(" ") ?: listOf()) val args = K2JVMCompilerArguments() val compiler = K2JVMCompiler() compiler.parseArguments(args2.toTypedArray(), args) @@ -185,13 +189,27 @@ class KotlinCompiler @Inject constructor( fun logk(level: Int, message: CharSequence) = kobaltLog.log(projectName, level, message) - logk(2, " Invoking K2JVMCompiler with arguments:" + fun pluginClasspaths(args: K2JVMCompilerArguments) : String { + var result = "" + args.pluginClasspaths?.forEach { + result += " -Xplugin " + it + } + args.pluginOptions?.let { + result += " -P " + result += it.joinToString(",") + } + return result + } + + logk(2, " Invoking K2JVMCompiler with arguments: kotlinc " + if (args.skipMetadataVersionCheck) " -Xskip-metadata-version-check" else "" - + " -moduleName " + args.moduleName + " -d " + args.destination - + " -friendPaths " + args.friendPaths.joinToString(";") + " -classpath " + args.classpath + + pluginClasspaths(args) + " " + sourceFiles.joinToString(" ")) + logk(2, " Additional kotlinc arguments: " + + " -moduleName " + args.moduleName + + " -friendPaths " + args.friendPaths.joinToString(";")) val collector = object : MessageCollector { override fun clear() { throw UnsupportedOperationException("not implemented") @@ -214,7 +232,6 @@ class KotlinCompiler @Inject constructor( message: String, location: CompilerMessageLocation) { if (severity.isError) { "Couldn't compile file: ${dump(location, message)}".let { fullMessage -> - System.err.println(fullMessage) throw KobaltException(fullMessage) } } else if (severity == CompilerMessageSeverity.WARNING && KobaltLogger.LOG_LEVEL >= 2) { @@ -224,28 +241,28 @@ class KotlinCompiler @Inject constructor( } } } - - System.setProperty("kotlin.incremental.compilation", "true") - // TODO: experimental should be removed as soon as it becomes standard - System.setProperty("kotlin.incremental.compilation.experimental", "true") +// +// System.setProperty("kotlin.incremental.compilation", "true") +// // TODO: experimental should be removed as soon as it becomes standard +// System.setProperty("kotlin.incremental.compilation.experimental", "true") val result = - if (cliArgs.noIncrementalKotlin || Kobalt.context?.internalContext?.noIncrementalKotlin ?: false) { - log(2, " Kotlin incremental compilation is disabled") - val duration = benchmarkMillis { - compiler.exec(collector, Services.Builder().build(), args) + if (cliArgs.noIncrementalKotlin || Kobalt.context?.internalContext?.noIncrementalKotlin ?: false) { + log(2, " Kotlin incremental compilation is disabled") + val duration = benchmarkMillis { + compiler.exec(collector, Services.Builder().build(), args) + } + log(1, " Regular compilation time: ${duration.first} ms") + TaskResult(duration.second == ExitCode.OK) + } else { + log(1, " Kotlin incremental compilation is enabled") + val start = System.currentTimeMillis() + val duration = benchmarkMillis { + compileIncrementally(filesToCompile, sourceFiles, outputDir, info, args, collector) + } + log(1, " Incremental compilation time: ${duration.first} ms") + TaskResult() } - log(1, " Regular compilation time: ${duration.first} ms") - TaskResult(duration.second == ExitCode.OK) - } else { - log(1, " Kotlin incremental compilation is enabled") - val start = System.currentTimeMillis() - val duration = benchmarkMillis { - compileIncrementally(filesToCompile, sourceFiles, outputDir, info, args, collector) - } - log(1, " Incremental compilation time: ${duration.first} ms") - TaskResult() - } return result }