@@ -24,10 +24,13 @@ import org.jetbrains.kotlin.base.kapt3.KaptFlag
2424import org.jetbrains.kotlin.base.kapt3.KaptOptions
2525import org.jetbrains.kotlin.cli.common.ExitCode
2626import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
27+ import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
28+ import org.jetbrains.kotlin.cli.common.arguments.validateArguments
2729import org.jetbrains.kotlin.cli.common.messages.MessageRenderer
2830import org.jetbrains.kotlin.cli.common.messages.PrintingMessageCollector
2931import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
3032import org.jetbrains.kotlin.cli.jvm.plugins.ServiceLoaderLite
33+ import org.jetbrains.kotlin.compiler.plugin.CommandLineProcessor
3134import org.jetbrains.kotlin.compiler.plugin.ComponentRegistrar
3235import org.jetbrains.kotlin.config.JVMAssertionsMode
3336import org.jetbrains.kotlin.config.JvmDefaultMode
@@ -45,6 +48,11 @@ import java.nio.file.Paths
4548import javax.annotation.processing.Processor
4649import javax.tools.*
4750
51+ data class PluginOption (val pluginId : PluginId , val optionName : OptionName , val optionValue : OptionValue )
52+
53+ typealias PluginId = String
54+ typealias OptionName = String
55+ typealias OptionValue = String
4856
4957@Suppress(" MemberVisibilityCanBePrivate" )
5058class KotlinCompilation {
@@ -56,7 +64,7 @@ class KotlinCompilation {
5664 }
5765
5866 /* * Arbitrary arguments to be passed to kapt */
59- var kaptArgs: MutableMap <String , String > = mutableMapOf ()
67+ var kaptArgs: MutableMap <OptionName , OptionValue > = mutableMapOf ()
6068
6169 /* *
6270 * Paths to directories or .jar files that contain classes
@@ -75,6 +83,11 @@ class KotlinCompilation {
7583 */
7684 var compilerPlugins: List <ComponentRegistrar > = emptyList()
7785
86+ /* *
87+ * Commandline processors for compiler plugins that should be added to the compilation
88+ */
89+ var commandLineProcessors: List <CommandLineProcessor > = emptyList()
90+
7891 /* * Source files to be compiled */
7992 var sources: List <SourceFile > = emptyList()
8093
@@ -213,7 +226,12 @@ class KotlinCompilation {
213226 var sanitizeParentheses: Boolean = false
214227
215228 /* * Paths to output directories for friend modules (whose internals should be visible) */
216- var friendPaths: MutableList <File > = mutableListOf ()
229+ var friendPaths: List <File > = emptyList()
230+
231+ /* * Additional string arguments to the Kotlin compiler */
232+ var kotlincArguments: List <String > = emptyList()
233+ /* * Options to be passed to compiler plugins: -P plugin:<pluginId>:<optionName>=<value>*/
234+ var pluginOptions: List <PluginOption > = emptyList()
217235
218236 /* *
219237 * Path to the JDK to be used
@@ -355,87 +373,119 @@ class KotlinCompilation {
355373 = sourcesGeneratedByAnnotationProcessor + compiledClassAndResourceFiles + generatedStubFiles
356374 }
357375
376+
358377 // setup common arguments for the two kotlinc calls
359- private fun commonK2JVMArgs () = K2JVMCompilerArguments ().also { it ->
360- it .destination = classesDir.absolutePath
361- it .classpath = commonClasspaths().joinToString(separator = File .pathSeparator)
378+ private fun commonK2JVMArgs () = K2JVMCompilerArguments ().also { args ->
379+ args .destination = classesDir.absolutePath
380+ args .classpath = commonClasspaths().joinToString(separator = File .pathSeparator)
362381
363- it .pluginClasspaths = pluginClasspaths.map(File ::getAbsolutePath).toTypedArray()
382+ args .pluginClasspaths = pluginClasspaths.map(File ::getAbsolutePath).toTypedArray()
364383
365384 if (jdkHome != null ) {
366- it .jdkHome = jdkHome!! .absolutePath
385+ args .jdkHome = jdkHome!! .absolutePath
367386 }
368387 else {
369388 log(" Using option -no-jdk. Kotlinc won't look for a JDK." )
370- it .noJdk = true
389+ args .noJdk = true
371390 }
372391
373- it .verbose = verbose
374- it .includeRuntime = includeRuntime
392+ args .verbose = verbose
393+ args .includeRuntime = includeRuntime
375394
376395 // the compiler should never look for stdlib or reflect in the
377396 // kotlinHome directory (which is null anyway). We will put them
378397 // in the classpath manually if they're needed
379- it .noStdlib = true
380- it .noReflect = true
398+ args .noStdlib = true
399+ args .noReflect = true
381400
382401 if (moduleName != null )
383- it .moduleName = moduleName
402+ args .moduleName = moduleName
384403
385- it .jvmTarget = jvmTarget
386- it .javaParameters = javaParameters
387- it .useIR = useIR
404+ args .jvmTarget = jvmTarget
405+ args .javaParameters = javaParameters
406+ args .useIR = useIR
388407
389408 if (javaModulePath != null )
390- it .javaModulePath = javaModulePath!! .toString()
409+ args .javaModulePath = javaModulePath!! .toString()
391410
392- it .additionalJavaModules = additionalJavaModules.map(File ::getAbsolutePath).toTypedArray()
393- it .noCallAssertions = noCallAssertions
394- it .noParamAssertions = noParamAssertions
395- it .noReceiverAssertions = noReceiverAssertions
396- it .strictJavaNullabilityAssertions = strictJavaNullabilityAssertions
397- it .noOptimize = noOptimize
411+ args .additionalJavaModules = additionalJavaModules.map(File ::getAbsolutePath).toTypedArray()
412+ args .noCallAssertions = noCallAssertions
413+ args .noParamAssertions = noParamAssertions
414+ args .noReceiverAssertions = noReceiverAssertions
415+ args .strictJavaNullabilityAssertions = strictJavaNullabilityAssertions
416+ args .noOptimize = noOptimize
398417
399418 if (constructorCallNormalizationMode != null )
400- it .constructorCallNormalizationMode = constructorCallNormalizationMode
419+ args .constructorCallNormalizationMode = constructorCallNormalizationMode
401420
402421 if (assertionsMode != null )
403- it .assertionsMode = assertionsMode
422+ args .assertionsMode = assertionsMode
404423
405424 if (buildFile != null )
406- it .buildFile = buildFile!! .toString()
425+ args .buildFile = buildFile!! .toString()
407426
408- it .inheritMultifileParts = inheritMultifileParts
409- it .useTypeTable = useTypeTable
427+ args .inheritMultifileParts = inheritMultifileParts
428+ args .useTypeTable = useTypeTable
410429
411430 if (declarationsOutputPath != null )
412- it .declarationsOutputPath = declarationsOutputPath!! .toString()
431+ args .declarationsOutputPath = declarationsOutputPath!! .toString()
413432
414- it .singleModule = singleModule
433+ args .singleModule = singleModule
415434
416435 if (javacArguments.isNotEmpty())
417- it .javacArguments = javacArguments.toTypedArray()
436+ args .javacArguments = javacArguments.toTypedArray()
418437
419438 if (supportCompatqualCheckerFrameworkAnnotations != null )
420- it .supportCompatqualCheckerFrameworkAnnotations = supportCompatqualCheckerFrameworkAnnotations
439+ args .supportCompatqualCheckerFrameworkAnnotations = supportCompatqualCheckerFrameworkAnnotations
421440
422- it .jvmDefault = jvmDefault
423- it .strictMetadataVersionSemantics = strictMetadataVersionSemantics
424- it .sanitizeParentheses = sanitizeParentheses
441+ args .jvmDefault = jvmDefault
442+ args .strictMetadataVersionSemantics = strictMetadataVersionSemantics
443+ args .sanitizeParentheses = sanitizeParentheses
425444
426445 if (friendPaths.isNotEmpty())
427- it .friendPaths = friendPaths.map(File ::getAbsolutePath).toTypedArray()
446+ args .friendPaths = friendPaths.map(File ::getAbsolutePath).toTypedArray()
428447
429448 if (scriptResolverEnvironment.isNotEmpty())
430- it.scriptResolverEnvironment = scriptResolverEnvironment.map { (key, value) -> " $key =\" $value \" " }.toTypedArray()
431-
432- it.noExceptionOnExplicitEqualsForBoxedNull = noExceptionOnExplicitEqualsForBoxedNull
433- it.skipRuntimeVersionCheck = skipRuntimeVersionCheck
434- it.suppressWarnings = suppressWarnings
435- it.allWarningsAsErrors = allWarningsAsErrors
436- it.reportOutputFiles = reportOutputFiles
437- it.reportPerf = reportPerformance
449+ args.scriptResolverEnvironment = scriptResolverEnvironment.map { (key, value) -> " $key =\" $value \" " }.toTypedArray()
450+
451+ args.noExceptionOnExplicitEqualsForBoxedNull = noExceptionOnExplicitEqualsForBoxedNull
452+ args.skipRuntimeVersionCheck = skipRuntimeVersionCheck
453+ args.suppressWarnings = suppressWarnings
454+ args.allWarningsAsErrors = allWarningsAsErrors
455+ args.reportOutputFiles = reportOutputFiles
456+ args.reportPerf = reportPerformance
457+ args.javaPackagePrefix = javaPackagePrefix
458+ args.suppressMissingBuiltinsError = suppressMissingBuiltinsError
459+
460+ /* *
461+ * It's not possible to pass dynamic [CommandLineProcessor] instancess directly to the [K2JVMCompiler]
462+ * because the compiler discoveres them on the classpath through a service locator, so we need to apply
463+ * the same trick as with [ComponentRegistrar]s: We put our own static [CommandLineProcessor] on the
464+ * classpath which in turn calls the user's dynamic [CommandLineProcessor] instances.
465+ */
466+ MainCommandLineProcessor .threadLocalParameters.set(
467+ MainCommandLineProcessor .ThreadLocalParameters (commandLineProcessors)
468+ )
469+
470+ /* *
471+ * Our [MainCommandLineProcessor] only has access to the CLI options that belong to its own plugin ID.
472+ * So in order to be able to access CLI options that are meant for other [CommandLineProcessor]s we
473+ * wrap these CLI options, send them to our own plugin ID and later unwrap them again to forward them
474+ * to the correct [CommandLineProcessor].
475+ */
476+ args.pluginOptions = pluginOptions.map { (pluginId, optionName, optionValue) ->
477+ " plugin:${MainCommandLineProcessor .pluginId} :${MainCommandLineProcessor .encodeForeignOptionName(pluginId, optionName)} =$optionValue "
478+ }.toTypedArray()
479+
480+ /* Parse extra CLI arguments that are given as strings so users can specify arguments that are not yet
481+ implemented here as well-typed properties. */
482+ parseCommandLineArguments(kotlincArguments, args)
483+
484+ validateArguments(args.errors)?.let {
485+ throw IllegalArgumentException (" Errors parsing kotlinc CLI arguments:\n $it " )
486+ }
438487 }
488+
439489 /* * Performs the 1st and 2nd compilation step to generate stubs and run annotation processors */
440490 private fun stubsAndApt (sourceFiles : List <File >): ExitCode {
441491 if (annotationProcessors.isEmpty()) {
@@ -467,7 +517,7 @@ class KotlinCompilation {
467517 *
468518 */
469519 MainComponentRegistrar .threadLocalParameters.set(
470- MainComponentRegistrar .Parameters (
520+ MainComponentRegistrar .ThreadLocalParameters (
471521 annotationProcessors.map { IncrementalProcessor (it, DeclaredProcType .NON_INCREMENTAL ) },
472522 kaptOptions,
473523 compilerPlugins
@@ -555,14 +605,13 @@ class KotlinCompilation {
555605 * the list is set to empty
556606 */
557607 MainComponentRegistrar .threadLocalParameters.set(
558- MainComponentRegistrar .Parameters (
608+ MainComponentRegistrar .ThreadLocalParameters (
559609 listOf (),
560610 KaptOptions .Builder (),
561611 compilerPlugins
562612 )
563613 )
564614
565-
566615 val sources = sourceFiles +
567616 kaptKotlinGeneratedDir.listFilesRecursively() +
568617 kaptSourceDir.listFilesRecursively()
@@ -572,7 +621,7 @@ class KotlinCompilation {
572621 return ExitCode .OK
573622
574623 // in this step also include source files generated by kapt in the previous step
575- val k2JvmArgs = commonK2JVMArgs().also {jvmArgs->
624+ val k2JvmArgs = commonK2JVMArgs().also { jvmArgs->
576625 jvmArgs.pluginClasspaths = (jvmArgs.pluginClasspaths ? : emptyArray()) + arrayOf(getResourcesPath())
577626 jvmArgs.freeArgs = sources.map(File ::getAbsolutePath).distinct()
578627 }
@@ -777,7 +826,9 @@ class KotlinCompilation {
777826
778827 private fun commonClasspaths () = mutableListOf<File >().apply {
779828 addAll(classpaths)
780- addAll(listOfNotNull(kotlinStdLibJar, kotlinReflectJar, kotlinScriptRuntimeJar))
829+ addAll(listOfNotNull(kotlinStdLibJar, kotlinStdLibCommonJar, kotlinStdLibJdkJar,
830+ kotlinReflectJar, kotlinScriptRuntimeJar
831+ ))
781832
782833 if (inheritClassPath) {
783834 addAll(hostClasspaths)
0 commit comments