From 312830ac4b8137c4088d19c4ccc3830930c1884f Mon Sep 17 00:00:00 2001 From: Sergey Rostov Date: Sun, 14 Apr 2019 13:33:07 +0300 Subject: [PATCH] Gradle, npm: resolve node_modules at parents, return local NodeJsExtension at NodeJsPlugin.apply --- .../KotlinJsCompilationTestsConfigurator.kt | 12 +++- .../targets/js/nodejs/NodeJsExtension.kt | 13 +++-- .../gradle/targets/js/nodejs/NodeJsPlugin.kt | 25 +++------ .../targets/js/nodejs/NodeJsRootExtension.kt | 55 ++++++++++--------- .../gradle/targets/js/npm/NpmProjectLayout.kt | 13 ----- .../gradle/targets/js/npm/NpmResolver.kt | 2 +- .../gradle/targets/js/testing/KotlinJsTest.kt | 21 +++++-- .../testing/nodejs/KotlinNodeJsTestRunner.kt | 6 +- .../kotlin/gradle/targets/js/yarn/Yarn.kt | 2 +- .../gradle/targets/js/yarn/YarnPlugin.kt | 6 +- .../targets/js/yarn/YarnRootExtension.kt | 41 ++++++-------- 11 files changed, 93 insertions(+), 103 deletions(-) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt index b9366b82d3e..ecb3011096e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/KotlinJsCompilationTestsConfigurator.kt @@ -38,7 +38,7 @@ internal class KotlinJsCompilationTestsConfigurator( fun configure() { // apply plugin (cannot do it lazy) - val nodeJs = NodeJsPlugin[target.project] + val nodeJs = NodeJsPlugin.apply(target.project).root val testJs = project.createOrRegisterTask(testTaskName) { testJs -> testJs.group = LifecycleBasePlugin.VERIFICATION_GROUP @@ -56,6 +56,16 @@ internal class KotlinJsCompilationTestsConfigurator( testJs.configureConventions() } + testJs.doGetTask().useNodeJs { } + registerTestTask(testJs) + +// project.afterEvaluate { +// testJs.configure { +// if (it.testFramework == null) { +// it.useNodeJs { } +// } +// } +// } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt index 336ae386247..7d5620fdc3d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExtension.kt @@ -9,6 +9,9 @@ import org.gradle.api.Project import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson open class NodeJsExtension(val project: Project) { + val root: NodeJsRootExtension + get() = this as? NodeJsRootExtension ?: NodeJsRootExtension[project] + internal val packageJsonHandlers = mutableListOf Unit>() fun packageJson(handler: PackageJson.() -> Unit) { @@ -16,9 +19,9 @@ open class NodeJsExtension(val project: Project) { } companion object { - operator fun get(project: Project): NodeJsExtension { - NodeJsPlugin[project.rootProject] - return project.extensions.getByName(NodeJsRootExtension.NODE_JS) as NodeJsExtension - } + operator fun get(project: Project) = NodeJsPlugin.apply(project) } -} \ No newline at end of file +} + +val Project.nodeJs: NodeJsExtension + get() = NodeJsExtension[this] \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt index 74b386643ca..47804396cbe 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsPlugin.kt @@ -4,10 +4,7 @@ import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.plugins.BasePlugin import org.gradle.api.tasks.Delete -import org.jetbrains.kotlin.backend.common.onlyIf import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension.Companion.NODE_JS -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectLayout -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver open class NodeJsPlugin : Plugin { override fun apply(project: Project): Unit = project.run { @@ -15,10 +12,10 @@ open class NodeJsPlugin : Plugin { "NodeJsPlugin can be applied only to root project" } - val nodeJs = this.extensions.create(NODE_JS, NodeJsRootExtension::class.java, this) + this.extensions.create(NODE_JS, NodeJsRootExtension::class.java, this) tasks.create(NodeJsSetupTask.NAME, NodeJsSetupTask::class.java) -// setupCleanNodeModulesTask(project, nodeJs) + setupCleanNodeModulesTask(project) allprojects { if (it != project) { @@ -27,21 +24,13 @@ open class NodeJsPlugin : Plugin { } } - private fun setupCleanNodeModulesTask( - project: Project, - nodeJs: NodeJsRootExtension - ) { + private fun setupCleanNodeModulesTask(project: Project) { project.tasks.create("cleanNodeModules", Delete::class.java) { it.description = "Deletes node_modules and package.json file" it.group = BasePlugin.BUILD_GROUP - val npmProjectLayout = NpmProjectLayout[it.project] - - project.delete(npmProjectLayout.nodeModulesDir) - project.afterEvaluate { - if (nodeJs.manageNodeModules) { - project.delete(npmProjectLayout.packageJsonFile) - } + it.doLast { + project.nodeJs.root.packageManager.cleanProject(project) } project.tasks.maybeCreate(BasePlugin.CLEAN_TASK_NAME).dependsOn(it) @@ -49,10 +38,10 @@ open class NodeJsPlugin : Plugin { } companion object { - operator fun get(project: Project): NodeJsRootExtension { + fun apply(project: Project): NodeJsExtension { val rootProject = project.rootProject rootProject.plugins.apply(NodeJsPlugin::class.java) - return rootProject.extensions.getByName(NODE_JS) as NodeJsRootExtension + return rootProject.extensions.getByName(NODE_JS) as NodeJsExtension } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt index ad068a8d6c5..618ba3d5498 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt @@ -28,37 +28,38 @@ open class NodeJsRootExtension(project: Project) : NodeJsExtension(project) { val nodeJsSetupTask: NodeJsSetupTask get() = project.tasks.getByName(NodeJsSetupTask.NAME) as NodeJsSetupTask - internal fun buildEnv(): NodeJsEnv { - val platform = NodeJsPlatform.name - val architecture = NodeJsPlatform.architecture + internal val environment: NodeJsEnv + get() { + val platform = NodeJsPlatform.name + val architecture = NodeJsPlatform.architecture - val nodeDir = installationDir.resolve("node-v$nodeVersion-$platform-$architecture") - val isWindows = NodeJsPlatform.name == NodeJsPlatform.WIN - val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin") + val nodeDir = installationDir.resolve("node-v$nodeVersion-$platform-$architecture") + val isWindows = NodeJsPlatform.name == NodeJsPlatform.WIN + val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin") - fun getExecutable(command: String, customCommand: String, windowsExtension: String): String { - val finalCommand = if (isWindows && customCommand == command) "$command.$windowsExtension" else customCommand - return if (download) File(nodeBinDir, finalCommand).absolutePath else finalCommand + fun getExecutable(command: String, customCommand: String, windowsExtension: String): String { + val finalCommand = if (isWindows && customCommand == command) "$command.$windowsExtension" else customCommand + return if (download) File(nodeBinDir, finalCommand).absolutePath else finalCommand + } + + fun getIvyDependency(): String { + val type = if (isWindows) "zip" else "tar.gz" + return "org.nodejs:node:$nodeVersion:$platform-$architecture@$type" + } + + return NodeJsEnv( + nodeDir = nodeDir, + nodeBinDir = nodeBinDir, + nodeExecutable = getExecutable("node", nodeCommand, "exe"), + npmExecutable = getExecutable("npm", npmCommand, "cmd"), + platformName = platform, + architectureName = architecture, + ivyDependency = getIvyDependency() + ) } - fun getIvyDependency(): String { - val type = if (isWindows) "zip" else "tar.gz" - return "org.nodejs:node:$nodeVersion:$platform-$architecture@$type" - } - - return NodeJsEnv( - nodeDir = nodeDir, - nodeBinDir = nodeBinDir, - nodeExecutable = getExecutable("node", nodeCommand, "exe"), - npmExecutable = getExecutable("npm", npmCommand, "cmd"), - platformName = platform, - architectureName = architecture, - ivyDependency = getIvyDependency() - ) - } - internal fun executeSetup() { - val nodeJsEnv = buildEnv() + val nodeJsEnv = environment if (download) { if (!nodeJsEnv.nodeBinDir.isDirectory) { nodeJsSetupTask.exec() @@ -69,6 +70,6 @@ open class NodeJsRootExtension(project: Project) : NodeJsExtension(project) { companion object { const val NODE_JS: String = "nodeJs" - operator fun get(project: Project): NodeJsRootExtension = NodeJsPlugin[project] + operator fun get(project: Project) = NodeJsPlugin.apply(project.rootProject) as NodeJsRootExtension } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectLayout.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectLayout.kt index 59c69d6d370..f6704ea0491 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectLayout.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmProjectLayout.kt @@ -12,7 +12,6 @@ import org.gradle.api.Project import org.gradle.process.ExecSpec import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs -import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile import java.io.File class NpmProjectLayout( @@ -36,18 +35,6 @@ class NpmProjectLayout( exec.args = listOf(findModule(tool)) + args } - val hoistGradleNodeModules: Boolean - get() = project.nodeJs.root.packageManager.getHoistGradleNodeModules(project) - - val gradleNodeModulesDir: File - get() = - if (hoistGradleNodeModules) project.rootProject.npmProject.nodeModulesDir - else nodeModulesDir - - fun moduleOutput(compilationTask: Kotlin2JsCompile): File { - return gradleNodeModulesDir.resolve(compilationTask.outputFile.name) - } - @TailRecursive fun findModule(name: String, src: NpmProjectLayout = this): String { val file = nodeModulesDir.resolve(name) diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolver.kt index 748a895be73..6bd0963921f 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolver.kt @@ -54,7 +54,7 @@ internal class NpmResolver private constructor(val rootProject: Project) { class ResolvedNow(val resolution: ResolvedProject) : ResolutionCallResult() } - private val nodeJs = NodeJsPlugin.apply(rootProject) + private val nodeJs = NodeJsPlugin.apply(rootProject).root private val packageManager = nodeJs.packageManager private val hoistGradleNodeModules = packageManager.getHoistGradleNodeModules(rootProject) private val npmPackages = mutableListOf() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/KotlinJsTest.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/KotlinJsTest.kt index 850f42b9fcb..d0f1bc20eb0 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/KotlinJsTest.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/KotlinJsTest.kt @@ -7,6 +7,7 @@ package org.jetbrains.kotlin.gradle.targets.js.testing import org.gradle.api.tasks.Input import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.SkipWhenEmpty import org.gradle.process.internal.DefaultProcessForkOptions import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessagesTestExecutionSpec import org.jetbrains.kotlin.gradle.plugin.HasKotlinDependencies @@ -14,13 +15,14 @@ import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectLayout import org.jetbrains.kotlin.gradle.targets.js.npm.NpmResolver import org.jetbrains.kotlin.gradle.targets.js.testing.karma.KotlinKarma -import org.jetbrains.kotlin.gradle.targets.js.testing.nodejs.KotlinNodeJsTestRunner import org.jetbrains.kotlin.gradle.targets.js.testing.mocha.KotlinMocha +import org.jetbrains.kotlin.gradle.targets.js.testing.nodejs.KotlinNodeJsTestRunner import org.jetbrains.kotlin.gradle.tasks.KotlinTest open class KotlinJsTest : KotlinTest() { @Internal - private var testFramework: KotlinJsTestFramework = KotlinNodeJsTestRunner() + @SkipWhenEmpty + internal var testFramework: KotlinJsTestFramework? = null @Input var debug: Boolean = false @@ -37,13 +39,20 @@ open class KotlinJsTest : KotlinTest() { fun useKarma(body: KotlinKarma.() -> Unit) = use(KotlinKarma(), body) - private inline fun use(runner: T, body: T.() -> Unit) { - testFramework = runner.also(body) + private inline fun use(runner: T, body: T.() -> Unit): T { + check(testFramework == null) { + "testFramework already configured for task ${this.path}" + } + + val testFramework = runner.also(body) + this.testFramework = testFramework val dependenciesHolder = runtimeDependencyHandler if (dependenciesHolder != null) { testFramework.configure(dependenciesHolder) } + + return testFramework } override fun executeTests() { @@ -57,7 +66,7 @@ open class KotlinJsTest : KotlinTest() { NpmResolver.resolve(project) forkOptions.workingDir = NpmProjectLayout[project].nodeWorkDir - forkOptions.executable = NodeJsPlugin[project].buildEnv().nodeExecutable + forkOptions.executable = NodeJsPlugin.apply(project).root.environment.nodeExecutable val nodeJsArgs = mutableListOf() @@ -65,6 +74,6 @@ open class KotlinJsTest : KotlinTest() { nodeJsArgs.add("--inspect-brk") } - return testFramework.createTestExecutionSpec(this, forkOptions, nodeJsArgs) + return testFramework!!.createTestExecutionSpec(this, forkOptions, nodeJsArgs) } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/nodejs/KotlinNodeJsTestRunner.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/nodejs/KotlinNodeJsTestRunner.kt index 24e52ab0a05..ab54815af43 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/nodejs/KotlinNodeJsTestRunner.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/nodejs/KotlinNodeJsTestRunner.kt @@ -54,11 +54,7 @@ class KotlinNodeJsTestRunner : KotlinJsTestFramework { val args = nodeJsArgs + testRuntimeNodeModules.map { - npmProjectLayout.nodeModulesDir.resolve(it).also { file -> - check(file.isFile) { - "Cannot find ${file.canonicalPath}" - } - }.canonicalPath + npmProjectLayout.findModule(it) } + cliArgs.toList() diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt index cb96d8b52dd..d97c5842bac 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt @@ -28,7 +28,7 @@ object Yarn : NpmApi { vararg args: String, npmProjectLayout: NpmProjectLayout = NpmProjectLayout[project] ) { - val nodeJsEnv = NodeJsPlugin.apply(project).environment + val nodeJsEnv = NodeJsPlugin.apply(project).root.environment val yarnEnv = YarnPlugin.apply(project).environment val packageJsonHashFile = project.packageJsonHashFile diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt index dbcadf4c597..073f24bb154 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt @@ -15,14 +15,16 @@ open class YarnPlugin : Plugin { "YarnPlugin can be applied only to root project" } + val nodeJs = NodeJsPlugin.apply(this).root + this.extensions.create(YarnRootExtension.YARN, YarnRootExtension::class.java, this) tasks.create(YarnSetupTask.NAME, YarnSetupTask::class.java) { - it.dependsOn(NodeJsPlugin[this].nodeJsSetupTask) + it.dependsOn(nodeJs.nodeJsSetupTask) } } companion object { - operator fun get(project: Project): YarnRootExtension { + fun apply(project: Project): YarnRootExtension { val rootProject = project.rootProject rootProject.plugins.apply(YarnPlugin::class.java) return rootProject.extensions.getByName(YarnRootExtension.YARN) as YarnRootExtension diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt index 1a836c3854a..d0747884e58 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt @@ -5,10 +5,10 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn -import org.gradle.api.GradleException import org.gradle.api.Project import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin +import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs open class YarnRootExtension(val project: Project) { private val gradleHome = project.gradle.gradleUserHomeDir.also { @@ -23,43 +23,36 @@ open class YarnRootExtension(val project: Project) { val yarnSetupTask: YarnSetupTask get() = project.tasks.getByName(YarnSetupTask.NAME) as YarnSetupTask - var useWorkspaces: Boolean = false + var disableWorkspaces: Boolean = false - internal fun checkUseWorkspace(): Boolean = if (useWorkspaces) { - check(NodeJsPlugin[project].manageNodeModules) { - "Yarn workspace can be used only with `manageNodeModules`: (add `nodeJs { manageNodeModules = true }` to your root project)" - } - - true - } else false + val useWorkspaces: Boolean + get() = project.nodeJs.root.manageNodeModules && !disableWorkspaces internal fun executeSetup() { - NodeJsPlugin[project].executeSetup() + NodeJsPlugin.apply(project).root.executeSetup() - val env = buildEnv() + val env = environment if (!env.home.isDirectory) { yarnSetupTask.setup() } } - internal fun buildEnv() = YarnEnv( - downloadUrl = "$downloadBaseUrl/v$version/yarn-v$version.tar.gz", - home = installationDir.resolve("yarn-v$version") - ) + internal val environment + get() = YarnEnv( + downloadUrl = "$downloadBaseUrl/v$version/yarn-v$version.tar.gz", + home = installationDir.resolve("yarn-v$version") + ) companion object { const val YARN: String = "yarn" operator fun get(project: Project): YarnRootExtension { - val extension = project.extensions.findByType(YarnRootExtension::class.java) - if (extension != null) - return extension - - val parentProject = project.parent - if (parentProject != null) - return get(parentProject) - - throw GradleException("YarnRootExtension is not installed") + val rootProject = project.rootProject + rootProject.plugins.apply(YarnPlugin::class.java) + return rootProject.extensions.getByName(YARN) as YarnRootExtension } } } + +val Project.yarn: YarnRootExtension + get() = YarnRootExtension[this] \ No newline at end of file