Implement base jvm scripting infrastructure

This commit is contained in:
Ilya Chernikov
2018-02-14 17:05:37 +01:00
parent aac7f97121
commit c48a74b84a
11 changed files with 542 additions and 0 deletions
@@ -0,0 +1,29 @@
import org.jetbrains.kotlin.gradle.dsl.Coroutines
apply {
plugin("kotlin")
}
jvmTarget = "1.6"
dependencies {
compile(project(":kotlin-script-runtime"))
compile(project(":kotlin-stdlib"))
compile(project(":kotlin-scripting-common"))
compile(project(":kotlin-scripting-jvm"))
compileOnly(project(":compiler:cli"))
compileOnly(intellijCoreDep())
}
sourceSets {
"main" { projectDefault() }
"test" {}
}
kotlin.experimental.coroutines = Coroutines.ENABLE
standardPublicJars()
publish()
@@ -0,0 +1,223 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvmhost.impl
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.text.StringUtil
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.cli.common.CLIConfigurationKeys
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.jvm.compiler.*
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots
import org.jetbrains.kotlin.cli.jvm.config.addJvmSdkRoots
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
import org.jetbrains.kotlin.codegen.GeneratedClassLoader
import org.jetbrains.kotlin.codegen.KotlinCodegenFacade
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.parsing.KotlinParserDefinition
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.script.KotlinScriptDefinition
import org.jetbrains.kotlin.utils.PathUtil
import java.io.File
import java.net.URLClassLoader
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.host.getMergedScriptText
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.JvmScriptCompileConfigurationParams
import kotlin.script.experimental.jvm.JvmScriptEvaluationEnvironmentParams
import kotlin.script.experimental.jvm.KJVMCompilerProxy
import kotlin.script.experimental.jvm.impl.BridgeDependenciesResolver
class KJVMCompiledScript<out ScriptBase : Any>(
override val configuration: ScriptCompileConfiguration,
val generationState: GenerationState,
val scriptClassFQName: String
) : CompiledScript<ScriptBase> {
override suspend fun instantiate(scriptEvaluationEnvironment: ScriptEvaluationEnvironment): ResultWithDiagnostics<ScriptBase> = try {
val baseClassLoader = scriptEvaluationEnvironment.getOrNull(JvmScriptEvaluationEnvironmentParams.baseClassLoader)
val dependencies = configuration.getOrNull(ScriptCompileConfigurationParams.dependencies)
?.flatMap { (it as? JvmDependency)?.classpath?.map { it.toURI().toURL() } ?: emptyList() }
// TODO: previous dependencies and classloaders should be taken into account here
val classLoaderWithDeps =
if (dependencies == null) baseClassLoader
else URLClassLoader(dependencies.toTypedArray(), baseClassLoader)
val classLoader = GeneratedClassLoader(generationState.factory, classLoaderWithDeps)
val clazz = classLoader.loadClass(scriptClassFQName)
(clazz as? ScriptBase)?.asSuccess()
?: ResultWithDiagnostics.Failure("Compiled class expected to be a subclass of the <ScriptBase>, but got ${clazz.javaClass.name}".asErrorDiagnostics())
} catch (e: Throwable) {
ResultWithDiagnostics.Failure(ScriptDiagnostic("Unable to instantiate class $scriptClassFQName", exception = e))
}
}
class KJVMCompilerImpl : KJVMCompilerProxy {
override fun compile(
scriptCompilerConfiguration: ScriptCompileConfiguration,
configurator: ScriptConfigurator?
): ResultWithDiagnostics<CompiledScript<*>> {
val messageCollector = ScriptDiagnosticsMessageCollector()
fun failure(vararg diagnostics: ScriptDiagnostic): ResultWithDiagnostics.Failure =
ResultWithDiagnostics.Failure(*messageCollector.diagnostics.toTypedArray(), *diagnostics)
try {
var environment: KotlinCoreEnvironment? = null
var updatedScriptCompileConfiguration = scriptCompilerConfiguration
fun updateClasspath(classpath: Iterable<File>) {
environment!!.updateClasspath(classpath.map(::JvmClasspathRoot))
val updatedDeps = updatedScriptCompileConfiguration.getOrNull(ScriptCompileConfigurationParams.dependencies)?.plus(
JvmDependency(classpath)
) ?: listOf(JvmDependency(classpath))
updatedScriptCompileConfiguration = updatedScriptCompileConfiguration.cloneWith(
ScriptCompileConfigurationParams.dependencies to updatedDeps
)
}
val disposable = Disposer.newDisposable()
val kotlinCompilerConfiguration = org.jetbrains.kotlin.config.CompilerConfiguration().apply {
add(
JVMConfigurationKeys.SCRIPT_DEFINITIONS,
BridgeScriptDefinition(scriptCompilerConfiguration, configurator, ::updateClasspath)
)
put<MessageCollector>(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY, messageCollector)
put(JVMConfigurationKeys.RETAIN_OUTPUT_IN_MEMORY, true)
scriptCompilerConfiguration.getOrNull(JvmScriptCompileConfigurationParams.javaHomeDir)?.let {
put(JVMConfigurationKeys.JDK_HOME, it)
}
scriptCompilerConfiguration.getOrNull(ScriptCompileConfigurationParams.dependencies)?.let {
addJvmClasspathRoots(it.flatMap { (it as JvmDependency).classpath })
}
put(CommonConfigurationKeys.MODULE_NAME, "kotlin-script") // TODO" take meaningful and valid name from somewhere
languageVersionSettings = LanguageVersionSettingsImpl(
LanguageVersion.LATEST_STABLE, ApiVersion.LATEST_STABLE, mapOf(AnalysisFlag.skipMetadataVersionCheck to true)
)
}
environment = KotlinCoreEnvironment.createForProduction(
disposable,
kotlinCompilerConfiguration,
EnvironmentConfigFiles.JVM_CONFIG_FILES
)
val analyzerWithCompilerReport = AnalyzerWithCompilerReport(messageCollector, environment.configuration.languageVersionSettings)
val psiFileFactory: PsiFileFactoryImpl = PsiFileFactory.getInstance(environment.project) as PsiFileFactoryImpl
val scriptText = scriptCompilerConfiguration[ScriptCompileConfigurationParams.scriptSourceFragments].getMergedScriptText()
val scriptFileName = "script" // TODO: extract from file/url if available
val virtualFile = LightVirtualFile(
"$scriptFileName${KotlinParserDefinition.STD_SCRIPT_EXT}",
KotlinLanguage.INSTANCE,
StringUtil.convertLineSeparators(scriptText)
).apply {
charset = CharsetToolkit.UTF8_CHARSET
}
val psiFile: KtFile = psiFileFactory.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile?
?: return failure("Unable to make PSI file from script".asErrorDiagnostics())
val sourceFiles = listOf(psiFile)
analyzerWithCompilerReport.analyzeAndReport(sourceFiles) {
val project = environment.project
TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
project,
sourceFiles,
NoScopeRecordCliBindingTrace(),
environment.configuration,
environment::createPackagePartProvider
)
}
val analysisResult = analyzerWithCompilerReport.analysisResult
if (!analysisResult.shouldGenerateCode) return failure("no code to generate".asErrorDiagnostics())
if (analysisResult.isError() || messageCollector.hasErrors()) return failure()
val generationState = GenerationState.Builder(
psiFile.project,
ClassBuilderFactories.binaries(false),
analysisResult.moduleDescriptor,
analysisResult.bindingContext,
sourceFiles,
kotlinCompilerConfiguration
).build()
generationState.beforeCompile()
KotlinCodegenFacade.generatePackage(
generationState,
psiFile.script!!.containingKtFile.packageFqName,
setOf(psiFile.script!!.containingKtFile),
org.jetbrains.kotlin.codegen.CompilationErrorHandler.THROW_EXCEPTION
)
val res = KJVMCompiledScript<Any>(updatedScriptCompileConfiguration, generationState, scriptFileName.capitalize())
return ResultWithDiagnostics.Success(res, messageCollector.diagnostics)
} catch (ex: Throwable) {
return failure(ex.asDiagnostics())
}
}
}
class ScriptDiagnosticsMessageCollector : MessageCollector {
private val _diagnostics = arrayListOf<ScriptDiagnostic>()
val diagnostics: List<ScriptDiagnostic> get() = _diagnostics
override fun clear() {
_diagnostics.clear()
}
override fun hasErrors(): Boolean =
_diagnostics.any { it.severity == ScriptDiagnostic.Severity.ERROR }
override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) {
val mappedSeverity = when (severity) {
CompilerMessageSeverity.EXCEPTION,
CompilerMessageSeverity.ERROR -> ScriptDiagnostic.Severity.ERROR
CompilerMessageSeverity.STRONG_WARNING,
CompilerMessageSeverity.WARNING -> ScriptDiagnostic.Severity.WARNING
CompilerMessageSeverity.INFO -> ScriptDiagnostic.Severity.INFO
CompilerMessageSeverity.LOGGING -> ScriptDiagnostic.Severity.DEBUG
else -> null
}
if (mappedSeverity != null) {
val mappedLocation = location?.let {
ScriptSource.Location(ScriptSource.Position(it.line, it.column))
}
_diagnostics.add(ScriptDiagnostic(message, mappedSeverity, mappedLocation))
}
}
}
// A bridge to the current scripting
internal class BridgeScriptDefinition(
scriptCompilerConfiguration: ScriptCompileConfiguration,
scriptConfigurator: ScriptConfigurator?,
updateClasspath: (List<File>) -> Unit
) : KotlinScriptDefinition(scriptCompilerConfiguration[ScriptCompileConfigurationParams.scriptSignature].scriptBase as KClass<out Any>) {
override val acceptedAnnotations =
scriptCompilerConfiguration.getOrNull(ScriptCompileConfigurationParams.updateConfigurationOnAnnotations)?.toList() ?: emptyList()
override val dependencyResolver: DependenciesResolver =
BridgeDependenciesResolver(scriptConfigurator, scriptCompilerConfiguration, updateClasspath)
}
+1
View File
@@ -0,0 +1 @@
import org.jetbrains.kotlin.gradle.dsl.Coroutines
@@ -0,0 +1,96 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm.impl
import kotlinx.coroutines.experimental.runBlocking
import java.io.File
import kotlin.script.dependencies.Environment
import kotlin.script.dependencies.ScriptContents
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.AsyncDependenciesResolver
import kotlin.script.experimental.dependencies.DependenciesResolver
import kotlin.script.experimental.dependencies.ScriptDependencies
import kotlin.script.experimental.dependencies.ScriptReport
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.defaultConfiguration
import kotlin.script.experimental.jvm.mapToLegacyScriptReportPosition
import kotlin.script.experimental.jvm.mapToLegacyScriptReportSeverity
class BridgeDependenciesResolver(
val scriptConfigurator: ScriptConfigurator?,
val baseScriptCompilerConfiguration: ScriptCompileConfiguration = scriptConfigurator.defaultConfiguration,
val onClasspathUpdated: (List<File>) -> Unit = {}
) : AsyncDependenciesResolver {
override fun resolve(scriptContents: ScriptContents, environment: Environment): DependenciesResolver.ResolveResult =
runBlocking {
resolveAsync(scriptContents, environment)
}
override suspend fun resolveAsync(scriptContents: ScriptContents, environment: Environment): DependenciesResolver.ResolveResult {
return try {
val diagnostics = arrayListOf<ScriptReport>()
val processedScriptData =
ProcessedScriptData(ProcessedScriptDataParams.annotations to scriptContents.annotations)
val scriptCompilerConfiguration = baseScriptCompilerConfiguration.cloneWith(
ScriptCompileConfigurationParams.scriptSourceFragments to scriptContents.toScriptSourceFragments()
)
val refinedConfiguration = scriptConfigurator?.let {
val res = scriptConfigurator.refineConfiguration(scriptCompilerConfiguration, processedScriptData)
when (res) {
is ResultWithDiagnostics.Failure ->
return@resolveAsync DependenciesResolver.ResolveResult.Failure(res.reports.mapScriptReportsToDiagnostics())
is ResultWithDiagnostics.Success -> {
diagnostics.addAll(res.reports.mapScriptReportsToDiagnostics())
res.value
}
}
} ?: scriptCompilerConfiguration
val newClasspath = refinedConfiguration.getOrNull(ScriptCompileConfigurationParams.dependencies)
?.flatMap { (it as JvmDependency).classpath } ?: emptyList()
if (refinedConfiguration != scriptCompilerConfiguration) {
val oldClasspath = scriptCompilerConfiguration.getOrNull(ScriptCompileConfigurationParams.dependencies)
?.flatMap { (it as JvmDependency).classpath } ?: emptyList()
if (newClasspath != oldClasspath) {
onClasspathUpdated(newClasspath)
}
}
DependenciesResolver.ResolveResult.Success(
ScriptDependencies(
classpath = newClasspath, // TODO: maybe it should return only increment from the initial config
imports = refinedConfiguration.getOrNull(ScriptCompileConfigurationParams.importedPackages)?.toList()
?: emptyList()
),
diagnostics
)
} catch (e: Throwable) {
DependenciesResolver.ResolveResult.Failure(
ScriptReport(
e.message ?: "unknown error $e"
)
)
}
}
}
internal fun List<ScriptDiagnostic>.mapScriptReportsToDiagnostics() =
map { ScriptReport(it.message, mapToLegacyScriptReportSeverity(it.severity), mapToLegacyScriptReportPosition(it.location)) }
internal fun ScriptContents.toScriptSourceFragments(): ScriptSourceFragments =
ScriptSourceFragments(
when {
text != null -> text!!.toString().toScriptSource()
file != null -> file!!.toScriptSource()
else -> throw IllegalArgumentException("Unable to convert script contents $this into script source")
},
null
)
@@ -0,0 +1,53 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package kotlin.script.experimental.jvm
import kotlin.script.experimental.api.*
open class JvmScriptCompiler(
val compilerProxy: KJVMCompilerProxy,
val cache: CompiledJvmScriptsCache
) : ScriptCompiler {
override suspend fun compile(configuration: ScriptCompileConfiguration, configurator: ScriptConfigurator?): ResultWithDiagnostics<CompiledScript<*>> {
val refinedConfiguration = configurator?.refineConfiguration(configuration)?.let {
when (it) {
is ResultWithDiagnostics.Failure -> return it
is ResultWithDiagnostics.Success -> it.value
?: return ResultWithDiagnostics.Failure("Null script compile configuration received".asErrorDiagnostics())
}
} ?: configuration
val cached = cache[refinedConfiguration[ScriptCompileConfigurationParams.scriptSourceFragments]]
if (cached != null) return cached.asSuccess()
return compilerProxy.compile(refinedConfiguration, configurator).also {
if (it is ResultWithDiagnostics.Success) {
cache.store(it.value as CompiledScript<*>)
}
}
}
}
interface CompiledJvmScriptsCache {
operator fun get(script: ScriptSourceFragments): CompiledScript<*>?
fun store(compiledScript: CompiledScript<*>)
}
interface KJVMCompilerProxy {
fun compile(
scriptCompilerConfiguration: ScriptCompileConfiguration,
configurator: ScriptConfigurator?
): ResultWithDiagnostics<CompiledScript<*>>
}
class DummyCompiledJvmScriptCache : CompiledJvmScriptsCache {
override operator fun get(script: ScriptSourceFragments): CompiledScript<*>? = null
override fun store(compiledScript: CompiledScript<*>) {}
}
@@ -0,0 +1,19 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm
import kotlinx.coroutines.experimental.runBlocking
import kotlin.script.experimental.api.*
import java.io.File
fun jvmConfigWithJavaHome(vararg params: Pair<TypedKey<*>, Any?>): ScriptCompileConfiguration =
ScriptCompileConfiguration(
JvmScriptCompileConfigurationParams.javaHomeDir to File(System.getProperty("java.home")),
*params
)
val ScriptConfigurator?.defaultConfiguration: ScriptCompileConfiguration
get() = this?.let { runBlocking { baseConfiguration(null) } }?.resultOrNull() ?: ScriptCompileConfiguration()
@@ -0,0 +1,18 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm
import java.io.File
import kotlin.script.experimental.api.ScriptDependency
import kotlin.script.experimental.api.TypedKey
object JvmScriptCompileConfigurationParams {
val javaHomeDir = TypedKey<File>("javaHomeDir")
}
class JvmDependency(val classpath: Iterable<File>): ScriptDependency
@@ -0,0 +1,23 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package kotlin.script.experimental.jvm
import kotlin.script.experimental.api.ScriptConfigurator
import kotlin.script.experimental.api.ScriptRunner
import kotlin.script.experimental.api.typedKey
import kotlin.script.experimental.host.BasicScriptingHost
open class JvmBasicScriptingHost<ScriptBase : Any>(
configurationExtractor: ScriptConfigurator,
compiler: JvmScriptCompiler,
runner: ScriptRunner<ScriptBase>
) : BasicScriptingHost<ScriptBase>(configurationExtractor, compiler, runner)
object JvmScriptEvaluationEnvironmentParams {
val baseClassLoader by typedKey<ClassLoader?>()
}
@@ -0,0 +1,34 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("unused")
package kotlin.script.experimental.jvm
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.ScriptSource
import kotlin.script.dependencies.ScriptContents
import kotlin.script.dependencies.ScriptDependenciesResolver
import kotlin.script.experimental.dependencies.ScriptReport
fun mapLegacyDiagnosticSeverity(severity: ScriptDependenciesResolver.ReportSeverity): ScriptDiagnostic.Severity = when (severity) {
ScriptDependenciesResolver.ReportSeverity.ERROR -> ScriptDiagnostic.Severity.ERROR
ScriptDependenciesResolver.ReportSeverity.WARNING -> ScriptDiagnostic.Severity.WARNING
ScriptDependenciesResolver.ReportSeverity.INFO -> ScriptDiagnostic.Severity.INFO
ScriptDependenciesResolver.ReportSeverity.DEBUG -> ScriptDiagnostic.Severity.DEBUG
}
fun mapToLegacyScriptReportSeverity(severity: ScriptDiagnostic.Severity): ScriptReport.Severity = when (severity) {
ScriptDiagnostic.Severity.ERROR -> ScriptReport.Severity.ERROR
ScriptDiagnostic.Severity.WARNING -> ScriptReport.Severity.WARNING
ScriptDiagnostic.Severity.INFO -> ScriptReport.Severity.INFO
ScriptDiagnostic.Severity.DEBUG -> ScriptReport.Severity.DEBUG
}
fun mapLegacyScriptPosition(pos: ScriptContents.Position?): ScriptSource.Location? =
pos?.let { ScriptSource.Location(ScriptSource.Position(pos.line, pos.col)) }
fun mapToLegacyScriptReportPosition(pos: ScriptSource.Location?): ScriptReport.Position? =
pos?.let { ScriptReport.Position(pos.start.line, pos.start.col) }
@@ -0,0 +1,38 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.script.experimental.jvm.runners
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
open class BasicJvmScriptRunner<ScriptBase : Any>(val baseClass: KClass<ScriptBase>? = null) : ScriptRunner<ScriptBase> {
override suspend fun run(
compiledScript: CompiledScript<ScriptBase>,
scriptEvaluationEnvironment: ScriptEvaluationEnvironment
): ResultWithDiagnostics<EvaluationResult> =
try {
val obj = compiledScript.instantiate(scriptEvaluationEnvironment)
when (obj) {
is ResultWithDiagnostics.Failure -> obj
is ResultWithDiagnostics.Success -> {
// in the future, when (if) we'll stop to compile everything into constructor
// run as SAM
// return res
val scriptObject = obj.value
if (scriptObject !is Class<*>)
ResultWithDiagnostics.Failure(ScriptDiagnostic("expecting class in this implementation, got ${scriptObject?.javaClass}"))
else {
scriptObject.getConstructor().newInstance()
ResultWithDiagnostics.Success(EvaluationResult(null, scriptEvaluationEnvironment))
}
}
}
} catch (e: Throwable) {
ResultWithDiagnostics.Failure(e.asDiagnostics())
}
}
+8
View File
@@ -151,6 +151,10 @@ include ":kotlin-build-common",
":kotlin-annotations-jvm",
":kotlin-annotations-android",
":kotlin-scripting-common",
":kotlin-scripting-jvm",
":kotlin-scripting-jvm-host",
":examples:scripting-jvm-simple-script",
":examples:scripting-jvm-maven-deps",
":pill:generate-all-tests",
":include:kotlin-compiler",
@@ -245,6 +249,10 @@ project(':examples:kotlin-jsr223-daemon-local-eval-example').projectDir = "$root
project(':kotlin-annotations-jvm').projectDir = "$rootDir/libraries/tools/kotlin-annotations-jvm" as File
project(':kotlin-annotations-android').projectDir = "$rootDir/libraries/tools/kotlin-annotations-android" as File
project(':kotlin-scripting-common').projectDir = "$rootDir/libraries/scripting/common" as File
project(':kotlin-scripting-jvm').projectDir = "$rootDir/libraries/scripting/jvm" as File
project(':kotlin-scripting-jvm-host').projectDir = "$rootDir/libraries/scripting/jvm-host" as File
project(':examples:scripting-jvm-simple-script').projectDir = "$rootDir/libraries/examples/scripting/jvm-simple-script" as File
project(':examples:scripting-jvm-maven-deps').projectDir = "$rootDir/libraries/examples/scripting/jvm-maven-deps" as File
project(':pill:generate-all-tests').projectDir = "$rootDir/plugins/pill/generate-all-tests" as File
// plugin markers: