Implement additional compiler arguments support in scripts

This commit is contained in:
Ilya Chernikov
2019-01-09 18:23:33 +01:00
parent 04b04ea0ee
commit 7ee9801c5f
5 changed files with 322 additions and 112 deletions
@@ -11,17 +11,21 @@ import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.PsiFileFactory
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.K2JVMCompilerArguments
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
import org.jetbrains.kotlin.cli.common.environment.setIdeaIoUseFallback
import org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.common.messages.MessageCollector
import org.jetbrains.kotlin.cli.common.setupCommonArguments
import org.jetbrains.kotlin.cli.jvm.*
import org.jetbrains.kotlin.cli.jvm.compiler.*
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.JvmModulePathRoot
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.modules.CoreJrtFileSystem
import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.CompilationErrorHandler
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
@@ -34,13 +38,12 @@ import org.jetbrains.kotlin.name.NameUtils
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtScript
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.script.util.KotlinJars
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.io.File
import java.util.*
import kotlin.reflect.KClass
import kotlin.reflect.KType
import kotlin.reflect.full.starProjectedType
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.host.FileScriptSource
@@ -51,6 +54,7 @@ import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver
import kotlin.script.experimental.jvm.jdkHome
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvm.util.KotlinJars
import kotlin.script.experimental.jvm.withUpdatedClasspath
import kotlin.script.experimental.jvmhost.KJvmCompilerProxy
import kotlin.script.experimental.util.getOrError
@@ -67,94 +71,222 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics)
fun failure(message: String): ResultWithDiagnostics.Failure =
ResultWithDiagnostics.Failure(
*messageCollector.diagnostics.toTypedArray(),
message.asErrorDiagnostics(path = script.locationId)
)
failure(message.asErrorDiagnostics(path = script.locationId))
val disposable = Disposer.newDisposable()
try {
setIdeaIoUseFallback()
var environment: KotlinCoreEnvironment? = null
var updatedConfiguration = scriptCompilationConfiguration
// TODO: refactor/cleanup when the internal resolving API will allow easier info passing between resolver and compiler
fun updateClasspath(classpath: List<File>) {
environment!!.updateClasspath(classpath.map(::JvmClasspathRoot))
val kotlinCompilerConfiguration = createInitialCompilerConfiguration(scriptCompilationConfiguration, messageCollector)
updatedConfiguration = updatedConfiguration.withUpdatedClasspath(classpath)
}
val initialScriptCompilationConfiguration =
scriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration)
val disposable = Disposer.newDisposable()
val kotlinCompilerConfiguration = org.jetbrains.kotlin.config.CompilerConfiguration().apply {
add(
JVMConfigurationKeys.SCRIPT_DEFINITIONS,
BridgeScriptDefinition(scriptCompilationConfiguration, hostConfiguration, ::updateClasspath)
)
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
val sourcesWithRefinementsState = SourcesWithRefinedConfigurations(script)
var isModularJava = false
(updatedConfiguration.getNoDefault(ScriptCompilationConfiguration.jvm.jdkHome)
?: hostConfiguration[ScriptingHostConfiguration.jvm.jdkHome])?.let {
put(JVMConfigurationKeys.JDK_HOME, it)
isModularJava = CoreJrtFileSystem.isModularJdk(it)
}
updatedConfiguration[ScriptCompilationConfiguration.dependencies]?.let { dependencies ->
addJvmClasspathRoots(
dependencies.flatMap {
(it as JvmDependency).classpath
}
)
}
// TODO: implement logic similar to compiler's -no-stdlib (and -no-reflect?)
val standardLibs = arrayListOf("kotlin.stdlib" to KotlinJars.stdlib)
KotlinJars.scriptRuntimeOrNull?.let { standardLibs.add("kotlin.script.runtime" to it) }
for ((moduleName, file) in standardLibs) {
if (isModularJava) {
add(CLIConfigurationKeys.CONTENT_ROOTS, JvmModulePathRoot(file))
add(JVMConfigurationKeys.ADDITIONAL_JAVA_MODULES, moduleName)
} else {
add(CLIConfigurationKeys.CONTENT_ROOTS, JvmClasspathRoot(file))
}
}
updatedConfiguration = updatedConfiguration.withUpdatedClasspath(standardLibs.map { it.second })
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script") // TODO" take meaningful and valid name from somewhere
languageVersionSettings = LanguageVersionSettingsImpl(
LanguageVersion.LATEST_STABLE, ApiVersion.LATEST_STABLE, mapOf(AnalysisFlags.skipMetadataVersionCheck to true)
)
put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
}
environment = KotlinCoreEnvironment.createForProduction(
disposable,
kotlinCompilerConfiguration,
EnvironmentConfigFiles.JVM_CONFIG_FILES
kotlinCompilerConfiguration.add(
JVMConfigurationKeys.SCRIPT_DEFINITIONS,
makeScriptDefinition(initialScriptCompilationConfiguration, script, sourcesWithRefinementsState)
)
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector, environment.configuration.languageVersionSettings)
val environment = KotlinCoreEnvironment.createForProduction(
disposable, kotlinCompilerConfiguration, EnvironmentConfigFiles.JVM_CONFIG_FILES
)
val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
val scriptText = getMergedScriptText(script, updatedConfiguration)
val scriptFileName = script.name ?: "script.${updatedConfiguration[ScriptCompilationConfiguration.fileExtension]}"
val virtualFile = ScriptLightVirtualFile(scriptFileName, (script as? FileScriptSource)?.file?.path, scriptText)
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
val mainKtFile = getMainKtFile(script, initialScriptCompilationConfiguration, environment)
?: return failure("Unable to make PSI file from script")
val ktScript = psiFile.declarations.firstIsInstanceOrNull<KtScript>()
val ktScript = mainKtFile.declarations.firstIsInstanceOrNull<KtScript>()
?: return failure("Not a script file")
val sourceFiles = arrayListOf(psiFile)
val sourceFiles = arrayListOf(mainKtFile)
val (classpath, newSources, sourceDependencies) =
collectScriptsCompilationDependencies(kotlinCompilerConfiguration, environment.project, sourceFiles)
// TODO: consider removing, it is probably redundant: the actual index update is performed with environment.updateClasspath
kotlinCompilerConfiguration.addJvmClasspathRoots(classpath)
environment.updateClasspath(classpath.map(::JvmClasspathRoot))
sourceFiles.addAll(newSources)
// collectScriptsCompilationDependencies calls resolver for every file, so at this point all updated configurations are collected
environment.configuration.updateWithRefinedConfigurations(
initialScriptCompilationConfiguration, sourcesWithRefinementsState.refinedConfigurations
)
val analysisResult = analyze(sourceFiles, environment)
if (!analysisResult.shouldGenerateCode) return failure("no code to generate")
if (analysisResult.isError() || messageCollector.hasErrors()) return failure()
val generationState = generate(analysisResult, sourceFiles, kotlinCompilerConfiguration)
val compiledScript =
makeCompiledScript(generationState, script, ktScript, sourceDependencies) { ktFile ->
sourcesWithRefinementsState.refinedConfigurations.entries.find { ktFile.name == it.key.name }?.value
?: initialScriptCompilationConfiguration
}
return ResultWithDiagnostics.Success(compiledScript, messageCollector.diagnostics)
} catch (ex: Throwable) {
return failure(ex.asDiagnostics(path = script.locationId))
} finally {
disposable.dispose()
}
}
private class SourcesWithRefinedConfigurations(rootScript: SourceCode) {
val knownSources = hashSetOf(rootScript)
val refinedConfigurations = hashMapOf<SourceCode, ScriptCompilationConfiguration>()
}
private fun makeScriptDefinition(
scriptCompilationConfiguration: ScriptCompilationConfiguration,
mainScript: SourceCode,
sourcsesWithConfigurationsState: SourcesWithRefinedConfigurations
): BridgeScriptDefinition =
BridgeScriptDefinition(
scriptCompilationConfiguration,
hostConfiguration,
{ script, updatedConfiguration ->
sourcsesWithConfigurationsState.refinedConfigurations[script] = updatedConfiguration
updatedConfiguration[ScriptCompilationConfiguration.importScripts]?.let {
sourcsesWithConfigurationsState.knownSources.addAll(it)
}
},
{ scriptContents ->
val name = scriptContents.file?.name
sourcsesWithConfigurationsState.knownSources.find {
// TODO: consider using merged text (likely should be cached)
// on the other hand it may become obsolete when scripting internals will be redesigned properly
(name != null && name == it.scriptFileName(
mainScript,
scriptCompilationConfiguration
)) || it.text == scriptContents.text
}
}
)
private fun ScriptCompilationConfiguration.withUpdatesFromCompilerConfiguration(kotlinCompilerConfiguration: CompilerConfiguration) =
withUpdatedClasspath(kotlinCompilerConfiguration.jvmClasspathRoots)
private fun createInitialCompilerConfiguration(
scriptCompilationConfiguration: ScriptCompilationConfiguration,
messageCollector: MessageCollector
): CompilerConfiguration {
val baseArguments = K2JVMCompilerArguments()
parseCommandLineArguments(
scriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions] ?: emptyList(),
baseArguments
)
return org.jetbrains.kotlin.config.CompilerConfiguration().apply {
// default value differs from the argument'ss default (see #KT-29405 and #KT-29319)
put(JVMConfigurationKeys.JVM_TARGET, JvmTarget.JVM_1_8)
put(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
setupCommonArguments(baseArguments)
setupJvmSpecificArguments(baseArguments)
val jdkHomeFromConfigurations = scriptCompilationConfiguration.getNoDefault(ScriptCompilationConfiguration.jvm.jdkHome)
?: hostConfiguration[ScriptingHostConfiguration.jvm.jdkHome]
if (jdkHomeFromConfigurations != null) {
messageCollector.report(CompilerMessageSeverity.LOGGING, "Using JDK home directory $jdkHomeFromConfigurations")
put(JVMConfigurationKeys.JDK_HOME, jdkHomeFromConfigurations)
} else {
configureJdkHome(baseArguments)
}
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
val isModularJava = isModularJava()
scriptCompilationConfiguration[ScriptCompilationConfiguration.dependencies]?.let { dependencies ->
addJvmClasspathRoots(
dependencies.flatMap {
(it as JvmDependency).classpath
}
)
}
configureExplicitContentRoots(baseArguments)
if (!baseArguments.noStdlib) {
addModularRootIfNotNull(isModularJava, "kotlin.stdlib", KotlinJars.stdlib)
addModularRootIfNotNull(isModularJava, "kotlin.script.runtime", KotlinJars.scriptRuntimeOrNull)
}
// see comments about logic in CompilerConfiguration.configureStandardLibs
if (!baseArguments.noReflect && !baseArguments.noStdlib) {
addModularRootIfNotNull(isModularJava, "kotlin.reflect", KotlinJars.reflectOrNull)
}
put(CommonConfigurationKeys.MODULE_NAME, baseArguments.moduleName ?: "kotlin-script")
configureAdvancedJvmOptions(baseArguments)
}
}
companion object {
private fun SourceCode.scriptFileName(
mainScript: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration
): String =
when {
name != null -> name!!
mainScript == this -> "script.${scriptCompilationConfiguration[ScriptCompilationConfiguration.fileExtension]}"
else -> throw Exception("Unexpected script without name: $this")
}
private fun getMainKtFile(
mainScript: SourceCode,
scriptCompilationConfiguration: ScriptCompilationConfiguration,
environment: KotlinCoreEnvironment
): KtFile? {
val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
val scriptText = getMergedScriptText(mainScript, scriptCompilationConfiguration)
val virtualFile = ScriptLightVirtualFile(
mainScript.scriptFileName(mainScript, scriptCompilationConfiguration),
(mainScript as? FileScriptSource)?.file?.path,
scriptText
)
return psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
}
private fun CompilerConfiguration.updateWithRefinedConfigurations(
initialScriptCompilationConfiguration: ScriptCompilationConfiguration,
refinedScriptCompilationConfigurations: HashMap<SourceCode, ScriptCompilationConfiguration>
) {
val updatedCompilerOptions = refinedScriptCompilationConfigurations.flatMap {
it.value[ScriptCompilationConfiguration.compilerOptions] ?: emptyList()
}
if (updatedCompilerOptions.isNotEmpty() &&
updatedCompilerOptions != initialScriptCompilationConfiguration[ScriptCompilationConfiguration.compilerOptions]
) {
val updatedArguments = K2JVMCompilerArguments()
parseCommandLineArguments(updatedCompilerOptions, updatedArguments)
setupCommonArguments(updatedArguments)
setupJvmSpecificArguments(updatedArguments)
configureAdvancedJvmOptions(updatedArguments)
// TODO: report warnings for all ignored options
}
}
private fun analyze(sourceFiles: Collection<KtFile>, environment: KotlinCoreEnvironment): AnalysisResult {
val messageCollector = environment.configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY]!!
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector, environment.configuration.languageVersionSettings)
analyzerWithCompilerReport.analyzeAndReport(sourceFiles) {
val project = environment.project
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
@@ -165,13 +297,14 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
environment::createPackagePartProvider
)
}
val analysisResult = analyzerWithCompilerReport.analysisResult
if (!analysisResult.shouldGenerateCode) return failure("no code to generate")
if (analysisResult.isError() || messageCollector.hasErrors()) return failure()
return analyzerWithCompilerReport.analysisResult
}
private fun generate(
analysisResult: AnalysisResult, sourceFiles: List<KtFile>, kotlinCompilerConfiguration: CompilerConfiguration
): GenerationState {
val generationState = GenerationState.Builder(
psiFile.project,
sourceFiles.first().project,
ClassBuilderFactories.BINARIES,
analysisResult.moduleDescriptor,
analysisResult.bindingContext,
@@ -180,7 +313,16 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
).build()
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
return generationState
}
private fun makeCompiledScript(
generationState: GenerationState,
script: SourceCode,
ktScript: KtScript,
sourceDependencies: List<ScriptsCompilationDependencies.SourceDependencies>,
getScriptConfiguration: (KtFile) -> ScriptCompilationConfiguration
): KJvmCompiledScript<Any> {
val scriptDependenciesStack = ArrayDeque<KtScript>()
fun makeOtherScripts(script: KtScript): List<KJvmCompiledScript<*>> {
@@ -195,7 +337,10 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.mapNotNull { sourceFile ->
sourceFile.declarations.firstIsInstanceOrNull<KtScript>()?.let {
KJvmCompiledScript<Any>(
containingKtFile.virtualFile?.path, updatedConfiguration, it.fqName.asString(), makeOtherScripts(it)
containingKtFile.virtualFile?.path,
getScriptConfiguration(sourceFile),
it.fqName.asString(),
makeOtherScripts(it)
)
}
} ?: emptyList()
@@ -204,17 +349,13 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm
return otherScripts
}
val compiledScript = KJvmCompiledScript<Any>(
return KJvmCompiledScript(
script.locationId,
updatedConfiguration,
getScriptConfiguration(ktScript.containingKtFile),
ktScript.fqName.asString(),
makeOtherScripts(ktScript),
KJvmCompiledModule(generationState)
)
return ResultWithDiagnostics.Success(compiledScript, messageCollector.diagnostics)
} catch (ex: Throwable) {
return failure(ex.asDiagnostics(path = script.locationId))
}
}
}
@@ -260,7 +401,8 @@ internal class ScriptDiagnosticsMessageCollector : MessageCollector {
internal class BridgeScriptDefinition(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val hostConfiguration: ScriptingHostConfiguration,
updateClasspath: (List<File>) -> Unit
updateConfiguration: (SourceCode, ScriptCompilationConfiguration) -> Unit,
getScriptSource: (ScriptContents) -> SourceCode?
) : KotlinScriptDefinition(Any::class) {
val baseClass: KClass<*> by lazy(LazyThreadSafetyMode.PUBLICATION) {
@@ -308,7 +450,7 @@ internal class BridgeScriptDefinition(
.orEmpty()
override val dependencyResolver: DependenciesResolver =
BridgeDependenciesResolver(scriptCompilationConfiguration, updateClasspath)
BridgeDependenciesResolver(scriptCompilationConfiguration, updateConfiguration, getScriptSource)
private val scriptingClassGetter by lazy(LazyThreadSafetyMode.PUBLICATION) {
hostConfiguration[ScriptingHostConfiguration.getScriptingClass]
@@ -33,7 +33,7 @@ class ScriptingHostTest : TestCase() {
fun testSimpleUsage() {
val greeting = "Hello from script!"
val output = captureOut {
evalScript("println(\"$greeting\")")
evalScript("println(\"$greeting\")").throwOnFailure()
}
Assert.assertEquals(greeting, output)
}
@@ -137,6 +137,41 @@ class ScriptingHostTest : TestCase() {
assertEquals("/script.kts", report?.sourcePath)
}
@Test
fun testCompileOptionsLanguageVersion() {
val script = "typealias MyInt = Int\nval x: MyInt = 3"
val compilationConfiguration1 = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate> {
compilerOptions("-language-version", "1.0")
}
val res = BasicJvmScriptingHost().eval(script.toScriptSource(), compilationConfiguration1, null)
assertTrue(res is ResultWithDiagnostics.Failure)
res.reports.find { it.message.startsWith("The feature \"type aliases\" is only available since language version 1.1") }
?: fail("Error report about language version not found. Reported:\n ${res.reports.joinToString("\n ") { it.message }}")
}
@Test
fun testCompileOptionsNoStdlib() {
val script = "println(\"Hi\")"
val res1 = evalScriptWithConfiguration(script) {
compilerOptions("-no-stdlib")
}
assertTrue(res1 is ResultWithDiagnostics.Failure)
res1.reports.find { it.message.startsWith("Unresolved reference: println") }
?: fail("Expected unresolved reference report. Reported:\n ${res1.reports.joinToString("\n ") { it.message }}")
val res2 = evalScriptWithConfiguration(script) {
refineConfiguration {
beforeCompiling { ctx ->
ScriptCompilationConfiguration(ctx.compilationConfiguration) {
compilerOptions("-no-stdlib")
}.asSuccess()
}
}
}
// -no-stdlib in refined configuration has no effect
assertTrue(res2 is ResultWithDiagnostics.Success)
}
@Test
fun testMemoryCache() {
@@ -147,7 +182,7 @@ class ScriptingHostTest : TestCase() {
val host = BasicJvmScriptingHost(compiler = compiler, evaluator = evaluator)
Assert.assertTrue(cache.data.isEmpty())
val output = captureOut { evalScript(script, host) }
val output = captureOut { evalScript(script, host).throwOnFailure() }
Assert.assertEquals("x = 1", output)
Assert.assertEquals(1, cache.data.size)
@@ -157,7 +192,7 @@ class ScriptingHostTest : TestCase() {
Assert.assertEquals(output, output2)
// TODO: check if cached script is actually used
val output3 = captureOut { evalScript(script, host) }.trim()
val output3 = captureOut { evalScript(script, host).throwOnFailure() }.trim()
Assert.assertEquals(output, output3)
}
@@ -205,7 +240,7 @@ class ScriptingHostTest : TestCase() {
Assert.assertEquals(output, output2)
// TODO: check if cached script is actually used
val output3 = captureOut { evalScript(script, host) }.trim()
val output3 = captureOut { evalScript(script, host).throwOnFailure() }.trim()
Assert.assertEquals(output, output3)
} finally {
cacheDir.deleteRecursively()
@@ -255,9 +290,16 @@ fun ResultWithDiagnostics<*>.throwOnFailure(): ResultWithDiagnostics<*> = apply
}
}
private fun evalScript(script: String, host: BasicScriptingHost = BasicJvmScriptingHost()) {
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>()
host.eval(script.toScriptSource(), compilationConfiguration, null).throwOnFailure()
private fun evalScript(script: String, host: BasicScriptingHost = BasicJvmScriptingHost()): ResultWithDiagnostics<*> =
evalScriptWithConfiguration(script, host)
private fun evalScriptWithConfiguration(
script: String,
host: BasicScriptingHost = BasicJvmScriptingHost(),
body: ScriptCompilationConfiguration.Builder.() -> Unit = {}
): ResultWithDiagnostics<EvaluationResult> {
val compilationConfiguration = createJvmCompilationConfigurationFromTemplate<SimpleScriptTemplate>(body = body)
return host.eval(script.toScriptSource(), compilationConfiguration, null)
}
@@ -22,7 +22,8 @@ import kotlin.script.experimental.jvm.compat.mapToLegacyScriptReportSeverity
class BridgeDependenciesResolver(
val scriptCompilationConfiguration: ScriptCompilationConfiguration,
val onClasspathUpdated: (List<File>) -> Unit = {}
val onConfigurationUpdated: (SourceCode, ScriptCompilationConfiguration) -> Unit = { _, _ -> },
val getScriptSource: (ScriptContents) -> SourceCode? = { null }
) : AsyncDependenciesResolver {
override fun resolve(scriptContents: ScriptContents, environment: Environment): DependenciesResolver.ResolveResult =
@@ -40,9 +41,6 @@ class BridgeDependenciesResolver(
)
)
val oldClasspath =
scriptCompilationConfiguration[ScriptCompilationConfiguration.dependencies].toClassPathOrEmpty()
val defaultImports = scriptCompilationConfiguration[ScriptCompilationConfiguration.defaultImports]?.toList() ?: emptyList()
fun ScriptCompilationConfiguration.toDependencies(classpath: List<File>): ScriptDependencies =
@@ -53,13 +51,15 @@ class BridgeDependenciesResolver(
scripts = this[ScriptCompilationConfiguration.importScripts].toFilesOrEmpty()
)
val script = getScriptSource(scriptContents) ?: scriptContents.toScriptSource()
val refineResults = scriptCompilationConfiguration.refineWith(
scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationOnAnnotations]?.handler,
scriptContents, processedScriptData
processedScriptData, script
).onSuccess {
it.refineWith(
scriptCompilationConfiguration[ScriptCompilationConfiguration.refineConfigurationBeforeCompiling]?.handler,
scriptContents, processedScriptData
processedScriptData, script
)
}
@@ -72,11 +72,12 @@ class BridgeDependenciesResolver(
}
}
if (refinedConfiguration != scriptCompilationConfiguration) {
onConfigurationUpdated(script, refinedConfiguration)
}
val newClasspath = refinedConfiguration[ScriptCompilationConfiguration.dependencies]
?.flatMap { (it as JvmDependency).classpath } ?: emptyList()
if (newClasspath != oldClasspath) {
onClasspathUpdated(newClasspath)
}
return DependenciesResolver.ResolveResult.Success(
// TODO: consider returning only increment from the initial config
@@ -109,12 +110,9 @@ internal fun List<SourceCode>?.toFilesOrEmpty() = this?.map {
} ?: emptyList()
fun ScriptCompilationConfiguration.refineWith(
handler: RefineScriptCompilationConfigurationHandler?, scriptContents: ScriptContents, processedScriptData: ScriptCollectedData
): ResultWithDiagnostics<ScriptCompilationConfiguration> {
if (handler == null) return this.asSuccess()
return handler(
ScriptConfigurationRefinementContext(scriptContents.toScriptSource(), this, processedScriptData)
)
}
handler: RefineScriptCompilationConfigurationHandler?,
processedScriptData: ScriptCollectedData,
script: SourceCode
): ResultWithDiagnostics<ScriptCompilationConfiguration> =
if (handler == null) this.asSuccess()
else handler(ScriptConfigurationRefinementContext(script, this, processedScriptData))
@@ -77,6 +77,19 @@ fun getResourcePathForClass(aClass: Class<*>): File {
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 {
File(it).absoluteFile
}
} catch (_: ClassNotFoundException) {
null
} catch (_: NoClassDefFoundError) {
null
}
internal fun URL.toFile() =
try {
File(toURI().schemeSpecificPart)
@@ -12,6 +12,7 @@ import java.net.URLClassLoader
import kotlin.reflect.KClass
import kotlin.script.experimental.jvm.impl.getResourcePathForClass
import kotlin.script.experimental.jvm.impl.toFile
import kotlin.script.experimental.jvm.impl.tryGetResourcePathForClassByName
import kotlin.script.templates.standard.ScriptTemplateWithArgs
// TODO: consider moving all these utilites to the build-common or some other shared compiler API module
@@ -29,6 +30,7 @@ internal const val KOTLIN_SCRIPT_CLASSPATH_PROPERTY = "kotlin.script.classpath"
internal const val KOTLIN_COMPILER_CLASSPATH_PROPERTY = "kotlin.compiler.classpath"
internal const val KOTLIN_COMPILER_JAR_PROPERTY = "kotlin.compiler.jar"
internal const val KOTLIN_STDLIB_JAR_PROPERTY = "kotlin.java.stdlib.jar"
internal const val KOTLIN_REFLECT_JAR_PROPERTY = "kotlin.java.reflect.jar"
// obsolete name, but maybe still used in the wild
// TODO: consider removing
internal const val KOTLIN_RUNTIME_JAR_PROPERTY = "kotlin.java.runtime.jar"
@@ -189,6 +191,11 @@ object KotlinJars {
?: explicitCompilerClasspath?.firstOrNull { it.matchMaybeVersionedFile(jarName) }?.takeIf(File::exists)
?: getResourcePathForClass(markerClass.java).takeIf(File::exists)
fun getLib(propertyName: String, jarName: String, markerClassName: String): File? =
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)
?: getLib(
@@ -203,6 +210,14 @@ object KotlinJars {
?: throw Exception("Unable to find kotlin stdlib, please specify it explicitly via \"$KOTLIN_STDLIB_JAR_PROPERTY\" property")
}
val reflectOrNull: File? by lazy {
getLib(
KOTLIN_REFLECT_JAR_PROPERTY,
KOTLIN_JAVA_REFLECT_JAR,
"kotlin.reflect.full.IllegalCallableAccessException"
)
}
val scriptRuntimeOrNull: File? by lazy {
getLib(
KOTLIN_SCRIPT_RUNTIME_JAR_PROPERTY,