Implement diagnostics for ignored compiler arguments on script compilation

This commit is contained in:
Ilya Chernikov
2019-01-23 16:33:30 +01:00
parent 7ee9801c5f
commit 95d782b6ca
4 changed files with 127 additions and 24 deletions
@@ -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<CompiledScript<*>> {
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<SourceCode, ScriptCompilationConfiguration>()
}
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<SourceCode, ScriptCompilationConfiguration>
refinedScriptCompilationConfigurations: HashMap<SourceCode, ScriptCompilationConfiguration>,
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<K2JVMCompilerArguments, *>
) {
val ignoredArgKeys = toIgnore.mapNotNull { argProperty ->
if (argProperty.get(arguments) != argProperty.get(reportingState.currentArguments)) {
argProperty.findAnnotation<Argument>()?.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(", ")}")
}
}
}
}
@@ -173,6 +173,26 @@ class ScriptingHostTest : TestCase() {
assertTrue(res2 is ResultWithDiagnostics.Success)
}
@Test
fun testIgnoredOptionsWarning() {
val script = "println(\"Hi\")"
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> {
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\")"
@@ -71,25 +71,26 @@ private fun splitJarUrl(url: String): Pair<String, String>? {
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)
@@ -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
)
}