| /* | |
| * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors. | |
| * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. | |
| */ | |
| @file:JvmName("KaptCli") | |
| package org.jetbrains.kotlin.kapt.cli | |
| import com.intellij.util.PathUtil | |
| import org.jetbrains.kotlin.cli.common.ExitCode | |
| import org.jetbrains.kotlin.cli.common.arguments.ArgumentParseErrors | |
| import org.jetbrains.kotlin.cli.common.arguments.preprocessCommandLineArguments | |
| import org.jetbrains.kotlin.cli.common.arguments.validateArgumentsAllErrors | |
| import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity | |
| import org.jetbrains.kotlin.cli.common.messages.MessageCollector | |
| import org.jetbrains.kotlin.cli.common.messages.MessageRenderer | |
| import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector | |
| import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler | |
| import org.jetbrains.kotlin.cli.jvm.modules.isAtLeastJava9 | |
| import org.jetbrains.kotlin.kapt.cli.CliToolOption.Format.* | |
| import java.io.File | |
| import kotlin.system.exitProcess | |
| fun main(args: Array<String>) { | |
| val messageCollector = PrintingMessageCollector(System.err, MessageRenderer.PLAIN_RELATIVE_PATHS, false) | |
| if (args.isEmpty() || args.contains("-help")) { | |
| printHelp() | |
| return | |
| } | |
| val kaptTransformed = transformArgs(args.asList(), messageCollector, false) | |
| if (messageCollector.hasErrors()) { | |
| exitProcess(ExitCode.COMPILATION_ERROR.code) | |
| } | |
| K2JVMCompiler.main(kaptTransformed.toTypedArray()) | |
| } | |
| internal fun transformArgs(args: List<String>, messageCollector: MessageCollector, isTest: Boolean): List<String> { | |
| val parseErrors = ArgumentParseErrors() | |
| val kotlincTransformed = preprocessCommandLineArguments(args, lazy { parseErrors }) | |
| val errorMessages = validateArgumentsAllErrors(parseErrors) | |
| if (errorMessages.isNotEmpty()) { | |
| errorMessages.forEach { messageCollector.report(CompilerMessageSeverity.ERROR, it) } | |
| return emptyList() | |
| } | |
| return try { | |
| transformKaptToolArgs(kotlincTransformed, messageCollector, isTest) | |
| } catch (e: IllegalArgumentException) { | |
| messageCollector.report(CompilerMessageSeverity.ERROR, e.localizedMessage) | |
| emptyList() | |
| } | |
| } | |
| // Repeatable options that also accept a comma-separated list. | |
| private val LIST_OPTIONS = setOf(KaptCliOption.CLASSPATH_CHANGES, KaptCliOption.ANNOTATION_PROCESSORS_OPTION) | |
| private const val KAPT_COMPILER_PLUGIN_JAR_NAME = "kotlin-annotation-processing.jar" | |
| private fun transformKaptToolArgs(args: List<String>, messageCollector: MessageCollector, isTest: Boolean): List<String> { | |
| val transformed = mutableListOf<String>() | |
| if (!isTest) { | |
| val kaptCompilerPluginFile = findKaptCompilerPlugin() | |
| ?: throw IllegalStateException("Can't find $KAPT_COMPILER_PLUGIN_JAR_NAME") | |
| transformed += "-Xplugin=${kaptCompilerPluginFile.absolutePath}" | |
| } | |
| var toolsJarPassed = false | |
| var aptModePassed = false | |
| var kaptVerboseModePassed = false | |
| data class Option(val cliToolOption: CliToolOption, val pluginOption: KaptCliOption) | |
| val cliOptions = KaptCliOption.entries.mapNotNull { Option(it.cliToolOption ?: return@mapNotNull null, it) } | |
| val iterator = args.asIterable().iterator() | |
| loop@ while (iterator.hasNext()) { | |
| val arg = iterator.next() | |
| if (arg == "--") { | |
| transformed += arg | |
| iterator.forEach { transformed += it } | |
| break | |
| } | |
| if (arg == "-help") { | |
| throw IllegalStateException("-help option should be already processed") | |
| } | |
| val option = cliOptions.firstOrNull { it.cliToolOption.matches(arg) } | |
| if (option == null) { | |
| transformed += arg | |
| continue | |
| } | |
| val transformedOption = option.cliToolOption.transform(arg) | |
| // kotlinc splits `-P` and `-Xplugin` values on ',', so such a value cannot reach the plugin as is. | |
| if (',' in transformedOption) { | |
| if (option.pluginOption !in LIST_OPTIONS) { | |
| argError("$arg: values containing ',' are not supported by ${option.cliToolOption.name}") | |
| } | |
| transformedOption.split(',').map { it.trim() }.filter { it.isNotEmpty() } | |
| .forEach { transformed += kaptArg(option.pluginOption, it) } | |
| continue | |
| } | |
| when (option.pluginOption) { | |
| KaptCliOption.TOOLS_JAR_OPTION -> { | |
| // TOOLS_JAR option is not passed as other compiler plugin options. | |
| // It is only used in kapt-cli, and we add a -Xplugin compiler option instead. | |
| toolsJarPassed = true | |
| transformed.add(0, "-Xplugin=$transformedOption") | |
| continue@loop | |
| } | |
| KaptCliOption.APT_MODE_OPTION -> aptModePassed = true | |
| KaptCliOption.VERBOSE_MODE_OPTION -> kaptVerboseModePassed = true | |
| else -> {} | |
| } | |
| transformed += kaptArg(option.pluginOption, transformedOption) | |
| } | |
| if (!aptModePassed) { | |
| transformed.addAll(0, kaptArg(KaptCliOption.APT_MODE_OPTION, "stubsAndApt")) | |
| } | |
| if (!isTest && !isAtLeastJava9() && !areJavacComponentsAvailable() && !toolsJarPassed) { | |
| val toolsJarFile = findToolsJar() | |
| ?: argError("'tools.jar' location should be specified (${KaptCliOption.TOOLS_JAR_OPTION.cliToolOption!!.name}=<path>)") | |
| transformed.add(0, "-Xplugin=" + toolsJarFile.absolutePath) | |
| } | |
| if (kaptVerboseModePassed) { | |
| messageCollector.report(CompilerMessageSeverity.INFO, "Options passed to kotlinc: " + transformed.joinToString(" ")) | |
| } | |
| return transformed | |
| } | |
| private fun CliToolOption.matches(arg: String) = when (format) { | |
| FLAG, VALUE -> arg.startsWith("$name=") | |
| KEY_VALUE -> arg.startsWith("$name:") | |
| } | |
| private fun CliToolOption.transform(arg: String): String { | |
| val optionName = name | |
| return when (format) { | |
| FLAG -> { | |
| fun err(): Nothing = argError("Invalid option format, should be $optionName=true/false") | |
| if (arg.length < (optionName.length + 2)) err() | |
| arg.drop(optionName.length + 1).takeIf { it == "true" || it == "false" } ?: err() | |
| } | |
| VALUE -> { | |
| fun err(): Nothing = argError("Invalid option format, should be $optionName=<value>") | |
| if (arg.length < (optionName.length + 2)) err() | |
| arg.drop(optionName.length + 1) | |
| } | |
| KEY_VALUE -> { | |
| fun err(): Nothing = argError("Invalid option format, should be $optionName:<key>=<value>") | |
| if (arg.length < (optionName.length + 3) || arg[optionName.length] != ':') err() | |
| arg.drop(optionName.length + 1).takeIf { it.contains('=') } ?: err() | |
| } | |
| } | |
| } | |
| private fun kaptArg(option: KaptCliOption, value: String): List<String> { | |
| return listOf("-P", "plugin:" + KaptCliOption.ANNOTATION_PROCESSING_COMPILER_PLUGIN_ID + ":" + option.optionName + "=" + value) | |
| } | |
| private fun argError(text: String): Nothing { | |
| throw IllegalArgumentException(text) | |
| } | |
| private fun findKaptCompilerPlugin(): File? { | |
| val pathToThisJar = File(PathUtil.getJarPathForClass(CliToolOption::class.java)) | |
| if (pathToThisJar.extension.lowercase() != "jar") { | |
| return null | |
| } | |
| return File(pathToThisJar.parentFile, KAPT_COMPILER_PLUGIN_JAR_NAME).takeIf { it.exists() } | |
| } |