From 7ee9801c5fb9fa1e2c5fa1c5accd6d09657533b5 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Wed, 9 Jan 2019 18:23:33 +0100 Subject: [PATCH] Implement additional compiler arguments support in scripts --- .../jvmhost/impl/KJvmCompilerImpl.kt | 316 +++++++++++++----- .../jvmhost/test/ScriptingHostTest.kt | 56 +++- .../jvm/impl/BridgeDependenciesResolver.kt | 34 +- .../script/experimental/jvm/impl/pathUtil.kt | 13 + .../experimental/jvm/util/jvmClasspathUtil.kt | 15 + 5 files changed, 322 insertions(+), 112 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 0c46b79e09c..fbd99ad2380 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 @@ -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) { - 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(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() + val ktScript = mainKtFile.declarations.firstIsInstanceOrNull() ?: 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() + } + + 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 + ) { + 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, 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, 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, + getScriptConfiguration: (KtFile) -> ScriptCompilationConfiguration + ): KJvmCompiledScript { val scriptDependenciesStack = ArrayDeque() fun makeOtherScripts(script: KtScript): List> { @@ -195,7 +337,10 @@ class KJvmCompilerImpl(val hostConfiguration: ScriptingHostConfiguration) : KJvm sourceDependencies.find { it.scriptFile == containingKtFile }?.sourceDependencies?.mapNotNull { sourceFile -> sourceFile.declarations.firstIsInstanceOrNull()?.let { KJvmCompiledScript( - 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( + 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) -> 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] 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 f4ff8c59717..46b3dbd2c1c 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 @@ -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 { + 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() - 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 { + val compilationConfiguration = createJvmCompilationConfigurationFromTemplate(body = body) + return host.eval(script.toScriptSource(), compilationConfiguration, null) } diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/BridgeDependenciesResolver.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/BridgeDependenciesResolver.kt index 527aedf6ba8..f743069f073 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/BridgeDependenciesResolver.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/BridgeDependenciesResolver.kt @@ -22,7 +22,8 @@ import kotlin.script.experimental.jvm.compat.mapToLegacyScriptReportSeverity class BridgeDependenciesResolver( val scriptCompilationConfiguration: ScriptCompilationConfiguration, - val onClasspathUpdated: (List) -> 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): 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?.toFilesOrEmpty() = this?.map { } ?: emptyList() fun ScriptCompilationConfiguration.refineWith( - handler: RefineScriptCompilationConfigurationHandler?, scriptContents: ScriptContents, processedScriptData: ScriptCollectedData -): ResultWithDiagnostics { - - if (handler == null) return this.asSuccess() - - return handler( - ScriptConfigurationRefinementContext(scriptContents.toScriptSource(), this, processedScriptData) - ) -} \ No newline at end of file + handler: RefineScriptCompilationConfigurationHandler?, + processedScriptData: ScriptCollectedData, + script: SourceCode +): ResultWithDiagnostics = + if (handler == null) this.asSuccess() + else handler(ScriptConfigurationRefinementContext(script, this, processedScriptData)) \ No newline at end of file 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 b650ef68cb0..1885b81a773 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 @@ -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) 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 fb5a77c74eb..db2e3779f95 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 @@ -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,