From 95d782b6ca4466cd93306ceb453defd9d6ed0681 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Wed, 23 Jan 2019 16:33:30 +0100 Subject: [PATCH] Implement diagnostics for ignored compiler arguments on script compilation --- .../jvmhost/impl/KJvmCompilerImpl.kt | 92 +++++++++++++++++-- .../jvmhost/test/ScriptingHostTest.kt | 20 ++++ .../script/experimental/jvm/impl/pathUtil.kt | 29 +++--- .../experimental/jvm/util/jvmClasspathUtil.kt | 10 +- 4 files changed, 127 insertions(+), 24 deletions(-) diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt index fbd99ad2380..6e9542806d0 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/impl/KJvmCompilerImpl.kt @@ -13,6 +13,7 @@ import com.intellij.psi.impl.PsiFileFactoryImpl import com.intellij.testFramework.LightVirtualFile import org.jetbrains.kotlin.analyzer.AnalysisResult import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.arguments.Argument import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback @@ -41,7 +42,9 @@ import org.jetbrains.kotlin.script.KotlinScriptDefinition import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull import java.util.* import kotlin.reflect.KClass +import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KType +import kotlin.reflect.full.findAnnotation import kotlin.reflect.full.starProjectedType import kotlin.script.dependencies.ScriptContents import kotlin.script.experimental.api.* @@ -66,6 +69,7 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm scriptCompilationConfiguration: ScriptCompilationConfiguration ): ResultWithDiagnostics> { val messageCollector = ScriptDiagnosticsMessageCollector() + val reportingState = ReportingState() fun failure(vararg diagnostics: ScriptDiagnostic): ResultWithDiagnostics.Failure = ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics) @@ -80,7 +84,8 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm // TODO: refactor/cleanup when the internal resolving API will allow easier info passing between resolver and compiler - val kotlinCompilerConfiguration = createInitialCompilerConfiguration(scriptCompilationConfiguration, messageCollector) + val kotlinCompilerConfiguration = + createInitialCompilerConfiguration(scriptCompilationConfiguration, messageCollector, reportingState) val initialScriptCompilationConfiguration = scriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration) @@ -113,7 +118,7 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm // collectScriptsCompilationDependencies calls resolver for every file, so at this point all updated configurations are collected environment.configuration.updateWithRefinedConfigurations( - initialScriptCompilationConfiguration, sourcesWithRefinementsState.refinedConfigurations + initialScriptCompilationConfiguration, sourcesWithRefinementsState.refinedConfigurations, messageCollector, reportingState ) val analysisResult = analyze(sourceFiles, environment) @@ -142,6 +147,10 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm val refinedConfigurations = hashMapOf() } + private class ReportingState { + var currentArguments = K2JVMCompilerArguments() + } + private fun makeScriptDefinition( scriptCompilationConfiguration: ScriptCompilationConfiguration, mainScript: SourceCode, @@ -174,7 +183,8 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm private fun createInitialCompilerConfiguration( scriptCompilationConfiguration: ScriptCompilationConfiguration, - messageCollector: MessageCollector + messageCollector: MessageCollector, + reportingState: ReportingState ): CompilerConfiguration { val baseArguments = K2JVMCompilerArguments() @@ -183,6 +193,9 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm baseArguments ) + reportArgumentsIgnoredGenerally(baseArguments, messageCollector, reportingState) + reportingState.currentArguments = baseArguments + return org.jetbrains.kotlin.config.CompilerConfiguration().apply { // default value differs from the argument'ss default (see #KT-29405 and #KT-29319) @@ -260,7 +273,9 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm private fun CompilerConfiguration.updateWithRefinedConfigurations( initialScriptCompilationConfiguration: ScriptCompilationConfiguration, - refinedScriptCompilationConfigurations: HashMap + refinedScriptCompilationConfigurations: HashMap, + messageCollector: ScriptDiagnosticsMessageCollector, + reportingState: ReportingState ) { val updatedCompilerOptions = refinedScriptCompilationConfigurations.flatMap { it.value[ScriptCompilationConfiguration.compilerOptions] ?: emptyList() @@ -272,13 +287,14 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm val updatedArguments = K2JVMCompilerArguments() parseCommandLineArguments(updatedCompilerOptions, updatedArguments) + reportArgumentsIgnoredGenerally(updatedArguments, messageCollector, reportingState) + reportArgumentsIgnoredFromRefinement(updatedArguments, messageCollector, reportingState) + setupCommonArguments(updatedArguments) setupJvmSpecificArguments(updatedArguments) configureAdvancedJvmOptions(updatedArguments) - - // TODO: report warnings for all ignored options } } @@ -357,6 +373,70 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm KJvmCompiledModule(generationState) ) } + + private fun reportArgumentsIgnoredGenerally( + arguments: K2JVMCompilerArguments, + messageCollector: MessageCollector, + reportingState: ReportingState + ) { + + reportIgnoredArguments( + arguments, + "The following compiler arguments are ignored on script compilation: ", + messageCollector, + reportingState, + K2JVMCompilerArguments::version, + K2JVMCompilerArguments::destination, + K2JVMCompilerArguments::buildFile, + K2JVMCompilerArguments::commonSources, + K2JVMCompilerArguments::allWarningsAsErrors, + K2JVMCompilerArguments::script, + K2JVMCompilerArguments::scriptTemplates, + K2JVMCompilerArguments::scriptResolverEnvironment, + K2JVMCompilerArguments::disableStandardScript, + K2JVMCompilerArguments::disableDefaultScriptingPlugin, + K2JVMCompilerArguments::pluginClasspaths, + K2JVMCompilerArguments::pluginOptions, + K2JVMCompilerArguments::useJavac, + K2JVMCompilerArguments::compileJava, + K2JVMCompilerArguments::reportPerf, + K2JVMCompilerArguments::dumpPerf + ) + } + + private fun reportArgumentsIgnoredFromRefinement( + arguments: K2JVMCompilerArguments, messageCollector: MessageCollector, reportingState: ReportingState + ) { + reportIgnoredArguments( + arguments, + "The following compiler arguments are ignored when configured from refinement callbacks: ", + messageCollector, + reportingState, + K2JVMCompilerArguments::noJdk, + K2JVMCompilerArguments::jdkHome, + K2JVMCompilerArguments::javaModulePath, + K2JVMCompilerArguments::classpath, + K2JVMCompilerArguments::noStdlib, + K2JVMCompilerArguments::noReflect + ) + } + + private fun reportIgnoredArguments( + arguments: K2JVMCompilerArguments, message: String, + messageCollector: MessageCollector, reportingState: ReportingState, + vararg toIgnore: KMutableProperty1 + ) { + val ignoredArgKeys = toIgnore.mapNotNull { argProperty -> + if (argProperty.get(arguments) != argProperty.get(reportingState.currentArguments)) { + argProperty.findAnnotation()?.value + ?: throw IllegalStateException("unknown compiler argument property: $argProperty: no Argument annotation found") + } else null + } + + if (ignoredArgKeys.isNotEmpty()) { + messageCollector.report(CompilerMessageSeverity.STRONG_WARNING, "$message${ignoredArgKeys.joinToString(", ")}") + } + } } } diff --git a/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt b/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt index 46b3dbd2c1c..4e64d6dfb84 100644 --- a/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt +++ b/libraries/scripting/jvm-host/test/kotlin/script/experimental/jvmhost/test/ScriptingHostTest.kt @@ -173,6 +173,26 @@ class ScriptingHostTest : TestCase() { assertTrue(res2 is ResultWithDiagnostics.Success) } + @Test + fun testIgnoredOptionsWarning() { + val script = "println(\"Hi\")" + val compilationConfiguration = createJvmCompilationConfigurationFromTemplate { + compilerOptions("-version", "-d", "destDir", "-Xreport-perf", "-no-reflect") + refineConfiguration { + beforeCompiling { ctx -> + ScriptCompilationConfiguration(ctx.compilationConfiguration) { + compilerOptions.append("-no-jdk", "-version", "-no-stdlib", "-Xdump-perf", "-no-inline") + }.asSuccess() + } + } + } + val res = BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration, null) + assertTrue(res is ResultWithDiagnostics.Success) + assertNotNull(res.reports.find { it.message == "The following compiler arguments are ignored on script compilation: -version, -d, -Xreport-perf" }) + assertNotNull(res.reports.find { it.message == "The following compiler arguments are ignored on script compilation: -Xdump-perf" }) + assertNotNull(res.reports.find { it.message == "The following compiler arguments are ignored when configured from refinement callbacks: -no-jdk, -no-stdlib" }) + } + @Test fun testMemoryCache() { val script = "val x = 1\nprintln(\"x = \$x\")" diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/pathUtil.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/pathUtil.kt index 1885b81a773..9a515afd8fb 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/pathUtil.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/pathUtil.kt @@ -71,25 +71,26 @@ private fun splitJarUrl(url: String): Pair? { return Pair(jarPath, resourcePath) } -fun getResourcePathForClass(aClass: Class<*>): File { +fun tryGetResourcePathForClass(aClass: Class<*>): File? { val path = "/" + aClass.name.replace('.', '/') + ".class" - val resourceRoot = getResourceRoot(aClass, path) ?: throw IllegalStateException("Resource not found: $path") - return File(resourceRoot).absoluteFile -} - -fun tryGetResourcePathForClassByName(name: String, classLoader: ClassLoader): File? = try { - classLoader.loadClass(name)?.let { - val path = "/" + name.replace('.', '/') + ".class" - getResourceRoot(it, path) - }?.let { + return getResourceRoot(aClass, path)?.let { File(it).absoluteFile } -} catch (_: ClassNotFoundException) { - null -} catch (_: NoClassDefFoundError) { - null } +fun getResourcePathForClass(aClass: Class<*>): File { + return tryGetResourcePathForClass(aClass) ?: throw IllegalStateException("Resource for class: ${aClass.name} not found") +} + +fun tryGetResourcePathForClassByName(name: String, classLoader: ClassLoader): File? = + try { + classLoader.loadClass(name)?.let(::tryGetResourcePathForClass) + } catch (_: ClassNotFoundException) { + null + } catch (_: NoClassDefFoundError) { + null + } + internal fun URL.toFile() = try { File(toURI().schemeSpecificPart) diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClasspathUtil.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClasspathUtil.kt index db2e3779f95..ff4a88eda4d 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClasspathUtil.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/util/jvmClasspathUtil.kt @@ -187,14 +187,16 @@ object KotlinJars { } fun getLib(propertyName: String, jarName: String, markerClass: KClass<*>): File? = - System.getProperty(propertyName)?.let(::File)?.takeIf(File::exists) - ?: explicitCompilerClasspath?.firstOrNull { it.matchMaybeVersionedFile(jarName) }?.takeIf(File::exists) + getExplicitLib(propertyName, jarName) ?: getResourcePathForClass(markerClass.java).takeIf(File::exists) fun getLib(propertyName: String, jarName: String, markerClassName: String): File? = + getExplicitLib(propertyName, jarName) + ?: tryGetResourcePathForClassByName(markerClassName, Thread.currentThread().contextClassLoader)?.takeIf(File::exists) + + private fun getExplicitLib(propertyName: String, jarName: String) = System.getProperty(propertyName)?.let(::File)?.takeIf(File::exists) ?: explicitCompilerClasspath?.firstOrNull { it.matchMaybeVersionedFile(jarName) }?.takeIf(File::exists) - ?: tryGetResourcePathForClassByName(markerClassName, Thread.currentThread().contextClassLoader)?.takeIf(File::exists) val stdlibOrNull: File? by lazy { System.getProperty(KOTLIN_STDLIB_JAR_PROPERTY)?.let(::File)?.takeIf(File::exists) @@ -214,7 +216,7 @@ object KotlinJars { getLib( KOTLIN_REFLECT_JAR_PROPERTY, KOTLIN_JAVA_REFLECT_JAR, - "kotlin.reflect.full.IllegalCallableAccessException" + "kotlin.reflect.full.KClasses" // using a class that is a part of the kotlin-reflect.jar ) }