From d18660c8f1540b9dfb54f001c3c6a0cb4196a877 Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Fri, 26 Jan 2024 13:38:23 +0000 Subject: [PATCH] [Gradle, JS] Add npm support but default Yarn package manager left Co-authored-by: Alexander Likhachev ^KT-64119 fixed --- .../kotlin/gradle/JsConfigurationCacheIT.kt | 22 +- .../gradle/JsSetupConfigurationCacheIT.kt | 49 +- .../kotlin/gradle/Kotlin2JsGradlePluginIT.kt | 324 ++---------- .../gradle/PackageManagerGradlePluginIT.kt | 464 ++++++++++++++++++ .../kotlin/gradle/testbase/BuildOptions.kt | 6 +- .../js-project-repos/build.gradle.kts | 4 + .../build.gradle.kts | 8 +- .../kotlin-js-npm-overrides/settings.gradle | 4 + .../build.gradle.kts | 0 .../gradle.properties | 0 .../src/main/kotlin/Base.kt | 0 .../settings.gradle | 4 - .../new-mpp-wasm-test/build.gradle.kts | 4 +- .../{yarnLockStub => packageLockStub} | 0 .../build.gradle.kts | 4 +- .../{yarnLockStub => packageLockStub} | 0 .../new-mpp-wasm-wasi-test/build.gradle.kts | 4 +- .../{yarnLockStub => packageLockStub} | 0 .../jetbrains/kotlin/gradle/internal/exec.kt | 4 - .../gradle/plugin/PropertiesProvider.kt | 5 + .../gradle/targets/js/AbstractSetupTask.kt | 15 +- .../gradle/targets/js/ir/KotlinBrowserJsIr.kt | 5 +- .../gradle/targets/js/ir/KotlinNodeJsIr.kt | 2 +- .../gradle/targets/js/nodejs/NodeJsEnv.kt | 4 +- .../gradle/targets/js/nodejs/NodeJsExec.kt | 2 +- .../targets/js/nodejs/NodeJsRootExtension.kt | 25 +- .../targets/js/nodejs/NodeJsRootPlugin.kt | 221 +++++++-- .../targets/js/nodejs/NodeJsSetupTask.kt | 2 +- .../targets/js/nodejs/NpmApiExtension.kt | 25 + .../js/nodejs/PackageManagerEnvironment.kt | 10 + .../targets/js/npm/GradleNodeModuleBuilder.kt | 2 +- .../js/npm/KotlinNpmResolutionManager.kt | 77 ++- .../gradle/targets/js/npm/LockCopyTask.kt | 76 +++ .../gradle/targets/js/npm/LockStoreTask.kt | 59 +++ .../kotlin/gradle/targets/js/npm/Npm.kt | 139 ++++++ .../js/npm/{NpmApi.kt => NpmApiExecution.kt} | 41 +- .../kotlin/gradle/targets/js/npm/NpmEnv.kt | 16 + .../gradle/targets/js/npm/NpmEnvironment.kt | 23 + .../gradle/targets/js/npm/NpmExtension.kt | 126 +++++ .../NpmImportedPackagesVersionResolver.kt} | 9 +- .../gradle/targets/js/npm/NpmOverride.kt | 27 + .../targets/js/npm/NpmResolverPlugin.kt | 3 +- .../gradle/targets/js/npm/PackageJson.kt | 2 +- .../npm/resolved/KotlinRootNpmResolution.kt | 27 +- .../js/npm/tasks/KotlinNpmInstallTask.kt | 45 +- .../js/npm/tasks/RootPackageJsonTask.kt | 17 +- .../kotlin/gradle/targets/js/utils.kt | 2 +- .../gradle/targets/js/yarn/LockCopyTask.kt | 48 ++ .../kotlin/gradle/targets/js/yarn/Yarn.kt | 19 +- .../gradle/targets/js/yarn/YarnBasics.kt | 15 +- .../targets/js/yarn/YarnEntryRegistry.kt | 17 - .../kotlin/gradle/targets/js/yarn/YarnEnv.kt | 6 +- .../gradle/targets/js/yarn/YarnEnvironment.kt | 23 + .../targets/js/yarn/YarnLockCopyTask.kt | 103 ---- .../gradle/targets/js/yarn/YarnPlugin.kt | 116 ++--- .../targets/js/yarn/YarnRootExtension.kt | 47 +- .../gradle/targets/js/yarn/YarnWorkspaces.kt | 24 +- 57 files changed, 1609 insertions(+), 717 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/PackageManagerGradlePluginIT.kt rename libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/{kotlin-js-yarn-resolutions => kotlin-js-npm-overrides}/build.gradle.kts (64%) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-npm-overrides/settings.gradle rename libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/{kotlin-js-yarn-lock-project => kotlin-js-package-lock-project}/build.gradle.kts (100%) rename libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/{kotlin-js-yarn-lock-project => kotlin-js-package-lock-project}/gradle.properties (100%) rename libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/{kotlin-js-yarn-lock-project => kotlin-js-package-lock-project}/src/main/kotlin/Base.kt (100%) delete mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-resolutions/settings.gradle rename libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-test/{yarnLockStub => packageLockStub} (100%) rename libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-js-test/{yarnLockStub => packageLockStub} (100%) rename libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-test/{yarnLockStub => packageLockStub} (100%) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NpmApiExtension.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/PackageManagerEnvironment.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/LockCopyTask.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/LockStoreTask.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/Npm.kt rename libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/{NpmApi.kt => NpmApiExecution.kt} (59%) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmEnv.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmEnvironment.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmExtension.kt rename libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/{yarn/YarnImportedPackagesVersionResolver.kt => npm/NpmImportedPackagesVersionResolver.kt} (83%) create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmOverride.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/LockCopyTask.kt delete mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEntryRegistry.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnvironment.kt delete mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnLockCopyTask.kt diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsConfigurationCacheIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsConfigurationCacheIT.kt index 9d5673661b6..ace6e966238 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsConfigurationCacheIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsConfigurationCacheIT.kt @@ -112,10 +112,30 @@ class JsIrConfigurationCacheIT : KGPBaseTest() { @DisplayName("KT-48241: configuration cache works with test dependencies") @GradleTest fun testTestDependencies(gradleVersion: GradleVersion) { + project("kotlin-js-project-with-test-dependencies", gradleVersion) { + assertSimpleConfigurationCacheScenarioWorks( + "assemble", "kotlinStorePackageLock", + buildOptions = defaultBuildOptions.copy( + jsOptions = defaultBuildOptions.jsOptions?.copy( + yarn = false + ) + ), + executedTaskNames = listOf(":rootPackageJson") + ) + } + } + + @DisplayName("KT-48241: configuration cache works with test dependencies for yarn.lock") + @GradleTest + fun testTestDependenciesYarnLock(gradleVersion: GradleVersion) { project("kotlin-js-project-with-test-dependencies", gradleVersion) { assertSimpleConfigurationCacheScenarioWorks( "assemble", "kotlinStoreYarnLock", - buildOptions = defaultBuildOptions, + buildOptions = defaultBuildOptions.copy( + jsOptions = defaultBuildOptions.jsOptions?.copy( + yarn = true + ) + ), executedTaskNames = listOf(":rootPackageJson") ) } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsSetupConfigurationCacheIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsSetupConfigurationCacheIT.kt index 60eebb57e2b..75c4395f87e 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsSetupConfigurationCacheIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsSetupConfigurationCacheIT.kt @@ -28,16 +28,47 @@ class JsSetupConfigurationCacheIT : KGPBaseTest() { @DisplayName("Check Node.JS setup on different platforms") @GradleTest fun checkNodeJsSetup(gradleVersion: GradleVersion) { - project("kotlin-js-browser-project", gradleVersion) { - build("kotlinUpgradeYarnLock") { - assertTasksExecuted(":kotlinUpgradeYarnLock") - assertConfigurationCacheStored() - } + project( + "kotlin-js-browser-project", + gradleVersion, + buildOptions = defaultBuildOptions.copy( + jsOptions = defaultBuildOptions.jsOptions?.copy( + yarn = false + ) + ) + ) { + checkNodeJsSetup("kotlinUpgradePackageLock") + } + } - build("kotlinUpgradeYarnLock") { - assertTasksUpToDate(":kotlinUpgradeYarnLock") - assertConfigurationCacheReused() - } + // hack to be run on Mac m* + @DisplayName("Check Node.JS setup on different platforms with Yarn") + @GradleTest + fun checkNodeJsSetupYarn(gradleVersion: GradleVersion) { + project( + "kotlin-js-browser-project", + gradleVersion, + buildOptions = defaultBuildOptions.copy( + jsOptions = defaultBuildOptions.jsOptions?.copy( + yarn = true + ) + ) + ) { + checkNodeJsSetup("kotlinUpgradeYarnLock") + } + } + + private fun TestProject.checkNodeJsSetup( + upgradeTask: String + ) { + build(upgradeTask) { + assertTasksExecuted(":$upgradeTask") + assertConfigurationCacheStored() + } + + build(upgradeTask) { + assertTasksUpToDate(":$upgradeTask") + assertConfigurationCacheReused() } } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt index 21cf4568154..325e81fd7ac 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt @@ -12,12 +12,15 @@ import org.gradle.testkit.runner.BuildResult import org.gradle.util.GradleVersion import org.jetbrains.kotlin.gradle.targets.js.dsl.Distribution import org.jetbrains.kotlin.gradle.targets.js.ir.KLIB_TYPE +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask.Companion.PACKAGE_LOCK_MISMATCH_MESSAGE +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask.Companion.RESTORE_PACKAGE_LOCK_NAME +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask.Companion.STORE_PACKAGE_LOCK_NAME +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask.Companion.UPGRADE_PACKAGE_LOCK import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson import org.jetbrains.kotlin.gradle.targets.js.npm.fromSrcPackageJson -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.STORE_YARN_LOCK_NAME -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.UPGRADE_YARN_LOCK -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.YARN_LOCK_MISMATCH_MESSAGE +import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin import org.jetbrains.kotlin.gradle.testbase.* import org.jetbrains.kotlin.gradle.testbase.TestVersions.Gradle.G_7_6 import org.jetbrains.kotlin.gradle.util.replaceText @@ -34,6 +37,7 @@ import kotlin.test.assertTrue @JsGradlePluginTests class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { + @DisplayName("TS type declarations are generated") @GradleTest fun generateDts(gradleVersion: GradleVersion) { @@ -171,37 +175,6 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { } } - @DisplayName("js composite build works with yarn.lock persistence") - @GradleTest - @GradleTestVersions(minVersion = G_7_6) - fun testJsCompositeBuildWithUpgradeYarnLock(gradleVersion: GradleVersion) { - project("js-composite-build", gradleVersion) { - buildGradleKts.modify(::transformBuildScriptWithPluginsDsl) - - subProject("lib").apply { - buildGradleKts.modify(::transformBuildScriptWithPluginsDsl) - } - - subProject("base").apply { - buildGradleKts.modify(::transformBuildScriptWithPluginsDsl) - } - - build("kotlinUpgradeYarnLock") { - assertTasksExecuted(":base:publicPackageJson") - assertTasksExecuted(":lib:lib-2:publicPackageJson") - assertTasksExecuted(":kotlinNpmInstall") - assertTasksExecuted(":kotlinUpgradeYarnLock") - } - - build(":nodeTest") { - assertTasksUpToDate(":base:publicPackageJson") - assertTasksUpToDate(":lib:lib-2:publicPackageJson") - assertTasksUpToDate(":kotlinNpmInstall") - assertTasksExecuted(":kotlinStoreYarnLock") - } - } - } - @GradleTest fun testJsIrIncrementalInParallel(gradleVersion: GradleVersion) { project("kotlin-js-browser-project", gradleVersion) { @@ -1029,7 +1002,15 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { @DisplayName("public NPM dependencies are included into package.json") @GradleTest fun testPackageJsonWithPublicNpmDependencies(gradleVersion: GradleVersion) { - project("npm-dependencies", gradleVersion) { + project( + "npm-dependencies", + gradleVersion, + buildOptions = defaultBuildOptions.copy( + jsOptions = defaultBuildOptions.jsOptions?.copy( + yarn = false + ) + ) + ) { build("jsJar") { val archive = projectPath .resolve("build/libs") @@ -1097,8 +1078,8 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { } } - build(UPGRADE_YARN_LOCK) { - assertTasksExecuted(":$UPGRADE_YARN_LOCK") + build(UPGRADE_PACKAGE_LOCK) { + assertTasksExecuted(":$UPGRADE_PACKAGE_LOCK") } build("jsJar") { @@ -1216,10 +1197,18 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { } } - @DisplayName("yarn resolutions works") + @DisplayName("npm overrides works") @GradleTest - fun testYarnResolution(gradleVersion: GradleVersion) { - project("kotlin-js-yarn-resolutions", gradleVersion) { + fun testNpmOverrides(gradleVersion: GradleVersion) { + project( + "kotlin-js-npm-overrides", + gradleVersion, + buildOptions = defaultBuildOptions.copy( + jsOptions = defaultBuildOptions.jsOptions?.copy( + yarn = false + ) + ) + ) { build("packageJson", "rootPackageJson", "kotlinNpmInstall") { fun getPackageJson() = projectPath.resolve("build/js") @@ -1229,16 +1218,16 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { } val name = "lodash" - val version = getPackageJson().resolutions?.get(name) + val version = getPackageJson().overrides?.get(name) val requiredVersion = ">=1.0.0 <1.2.1 || >1.4.0 <2.0.0" - assertTrue("Root package.json must have resolution $name with version $requiredVersion, but $version found") { + assertTrue("Root package.json must override $name with version $requiredVersion, but $version found") { version == requiredVersion } val react = "react" - val reactVersion = getPackageJson().resolutions?.get(react) + val reactVersion = getPackageJson().overrides?.get(react) val requiredReactVersion = "16.0.0" - assertTrue("Root package.json must have resolution $react with version $requiredReactVersion, but $reactVersion found") { + assertTrue("Root package.json must override $react with version $requiredReactVersion, but $reactVersion found") { reactVersion == requiredReactVersion } } @@ -1374,176 +1363,6 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { } } - @DisplayName("Failing with yarn.lock update") - @GradleTest - fun testFailingWithYarnLockUpdate(gradleVersion: GradleVersion) { - project("kotlin-js-yarn-lock-project", gradleVersion) { - build(STORE_YARN_LOCK_NAME) { - assertTasksExecuted(":$STORE_YARN_LOCK_NAME") - } - - projectPath.resolve("kotlin-js-store").deleteRecursively() - - buildGradleKts.modify { - it + "\n" + - """ - rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) { - rootProject.the().reportNewYarnLock = true - } - """.trimIndent() - } - - buildAndFail(STORE_YARN_LOCK_NAME) { - assertTasksFailed(":$STORE_YARN_LOCK_NAME") - } - - buildGradleKts.modify { - it + "\n" + - """ - dependencies { - implementation(npm("decamelize", "6.0.0")) - } - - rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) { - rootProject.the().yarnLockMismatchReport = - org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.FAIL - } - """.trimIndent() - } - - buildAndFail(STORE_YARN_LOCK_NAME) { - assertTasksFailed(":$STORE_YARN_LOCK_NAME") - } - - // yarn.lock was not updated - buildAndFail(STORE_YARN_LOCK_NAME) { - assertTasksFailed(":$STORE_YARN_LOCK_NAME") - } - - build(UPGRADE_YARN_LOCK) { - assertTasksExecuted(":$UPGRADE_YARN_LOCK") - } - - buildGradleKts.modify { - val replaced = it.replace("implementation(npm(\"decamelize\", \"6.0.0\"))", "") - replaced + "\n" + - """ - rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) { - rootProject.the().yarnLockMismatchReport = - org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.WARNING - } - """.trimIndent() - } - - build(STORE_YARN_LOCK_NAME) { - assertTasksExecuted(":$STORE_YARN_LOCK_NAME") - - assertOutputContains(YARN_LOCK_MISMATCH_MESSAGE) - } - - build(UPGRADE_YARN_LOCK) { - assertTasksExecuted(":$UPGRADE_YARN_LOCK") - } - - buildGradleKts.modify { - it + "\n" + - """ - dependencies { - implementation(npm("decamelize", "6.0.0")) - } - - rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) { - rootProject.the().yarnLockMismatchReport = - org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.NONE - } - """.trimIndent() - } - - build(STORE_YARN_LOCK_NAME) { - assertTasksExecuted(":$STORE_YARN_LOCK_NAME") - - assertOutputDoesNotContain(YARN_LOCK_MISMATCH_MESSAGE) - } - - build(UPGRADE_YARN_LOCK) { - assertTasksExecuted(":$UPGRADE_YARN_LOCK") - } - - buildGradleKts.modify { - it.replace("implementation(npm(\"decamelize\", \"6.0.0\"))", "") - } - - projectPath.resolve("kotlin-js-store").deleteRecursively() - - buildGradleKts.modify { - it + "\n" + - """ - rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) { - rootProject.the().yarnLockMismatchReport = - org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.NONE - rootProject.the().reportNewYarnLock = - true - } - """.trimIndent() - } - - build(STORE_YARN_LOCK_NAME) { - assertTasksExecuted(":$STORE_YARN_LOCK_NAME") - - assertOutputDoesNotContain(YARN_LOCK_MISMATCH_MESSAGE) - } - - buildGradleKts.modify { - it + "\n" + - """ - dependencies { - implementation(npm("decamelize", "6.0.0")) - } - - rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) { - rootProject.the().yarnLockMismatchReport = - org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockMismatchReport.FAIL - rootProject.the().yarnLockAutoReplace = - true - } - """.trimIndent() - } - - buildAndFail(STORE_YARN_LOCK_NAME) { - assertTasksFailed(":$STORE_YARN_LOCK_NAME") - } - - //yarn.lock was updated - build(STORE_YARN_LOCK_NAME) { - assertTasksExecuted(":$STORE_YARN_LOCK_NAME") - } - - buildGradleKts.modify { - it.replace("implementation(npm(\"decamelize\", \"6.0.0\"))", "") - } - - // check if everything ok without build/js/yarn.lock - build("clean") { - assertTasksExecuted(":clean") - } - - build("clean") { - assertTasksUpToDate(":clean") - } - - buildAndFail(STORE_YARN_LOCK_NAME) { - assertTasksFailed(":$STORE_YARN_LOCK_NAME") - } - - projectPath.resolve("kotlin-js-store").deleteRecursively() - - //check if independent tasks can be executed - build("help") { - assertTasksExecuted(":help") - } - } - } - @DisplayName("webpack configuration is valid") @GradleTest fun testWebpackConfig(gradleVersion: GradleVersion) { @@ -1622,7 +1441,15 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { @DisplayName("Disable download should not download Node.JS and Yarn") @GradleTest fun testNodeJsAndYarnNotDownloaded(gradleVersion: GradleVersion) { - project("nodeJsDownload", gradleVersion) { + project( + "nodeJsDownload", + gradleVersion, + buildOptions = defaultBuildOptions.copy( + jsOptions = defaultBuildOptions.jsOptions?.copy( + yarn = true + ) + ) + ) { buildGradleKts.modify { it + "\n" + """ @@ -1640,71 +1467,6 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { } } - @DisplayName("Yarn.lock persistence") - @GradleTest - fun testYarnLockStore(gradleVersion: GradleVersion) { - project("nodeJsDownload", gradleVersion) { - build("assemble", "kotlinStoreYarnLock") { - assertFileExists(projectPath.resolve("kotlin-js-store").resolve("yarn.lock")) - assert( - projectPath - .resolve("kotlin-js-store") - .resolve("yarn.lock") - .readText() == projectPath.resolve("build/js/yarn.lock").readText() - ) - } - } - } - - @DisplayName("Yarn ignore scripts") - @GradleTest - fun testYarnIgnoreScripts(gradleVersion: GradleVersion) { - project("nodeJsDownload", gradleVersion) { - buildGradleKts.modify { - it + "\n" + - """ - dependencies { - implementation(npm("puppeteer", "11.0.0")) - } - """.trimIndent() - } - build("assemble", "kotlinNpmInstall") { - assert( - projectPath - .resolve("build") - .resolve("js") - .resolve("node_modules") - .resolve("puppeteer") - .resolve(".local-chromium") - .notExists() - ) { - "Chromium should not be installed with --ignore-scripts" - } - } - buildGradleKts.modify { - it + "\n" + - """ - rootProject.plugins.withType { - rootProject.the().ignoreScripts = false - } - """.trimIndent() - } - - build("clean") - - build("assemble", "kotlinNpmInstall") { - assertDirectoryExists( - projectPath - .resolve("build") - .resolve("js") - .resolve("node_modules") - .resolve("puppeteer") - .resolve(".local-chromium") - ) - } - } - } - @DisplayName("js files from dependency are installed") @GradleTest fun testKotlinJsDependencyWithJsFiles(gradleVersion: GradleVersion) { @@ -1754,7 +1516,7 @@ class Kotlin2JsIrGradlePluginIT : KGPBaseTest() { } } - @DisplayName("test package.jsom skipped with main package.json was up-to-date") + @DisplayName("test package.json skipped with main package.json was up-to-date") @GradleTest fun testPackageJsonSkippedWithUpToDatePackageJsons(gradleVersion: GradleVersion) { project("kotlin-js-browser-library-project", gradleVersion) { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/PackageManagerGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/PackageManagerGradlePluginIT.kt new file mode 100644 index 00000000000..1acdcd16a00 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/PackageManagerGradlePluginIT.kt @@ -0,0 +1,464 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle + +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask.Companion.PACKAGE_LOCK_MISMATCH_MESSAGE +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask.Companion.RESTORE_PACKAGE_LOCK_NAME +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask.Companion.STORE_PACKAGE_LOCK_NAME +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask.Companion.UPGRADE_PACKAGE_LOCK +import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin +import org.jetbrains.kotlin.gradle.testbase.* +import org.jetbrains.kotlin.gradle.testbase.TestVersions.Gradle.G_7_6 +import org.junit.jupiter.api.DisplayName +import kotlin.io.path.deleteRecursively +import kotlin.io.path.notExists +import kotlin.io.path.readText + +class NpmGradlePluginIT : PackageManagerGradlePluginIT() { + override val yarn: Boolean = false + + override val plugin = "nodejs.NodeJsRootPlugin" + + override val extension = "npm.NpmExtension" + + override val upgradeTaskName: String = UPGRADE_PACKAGE_LOCK + + override val storeTaskName = STORE_PACKAGE_LOCK_NAME + + override val restoreTaskName = RESTORE_PACKAGE_LOCK_NAME + + override val reportNewLockFile = "reportNewPackageLock" + + override val lockMismatchReport = "packageLockMismatchReport" + + override val lockFileAutoReplace = "packageLockAutoReplace" + + override val mismatchReportType = "npm.LockFileMismatchReport" + + override val lockFileName: String = LockCopyTask.PACKAGE_LOCK + + override val setProperty: (String) -> String = { ".set($it)" } +} + +class YarnGradlePluginIT : PackageManagerGradlePluginIT() { + override val yarn: Boolean = true + + override val plugin: String = "yarn.YarnPlugin" + + override val extension: String = "yarn.YarnRootExtension" + + override val upgradeTaskName: String = YarnPlugin.UPGRADE_YARN_LOCK + + override val storeTaskName = YarnPlugin.STORE_YARN_LOCK_NAME + + override val restoreTaskName: String = YarnPlugin.RESTORE_YARN_LOCK_NAME + + override val reportNewLockFile: String = "reportNewYarnLock" + + override val lockMismatchReport: String = "yarnLockMismatchReport" + + override val lockFileAutoReplace: String = "yarnLockAutoReplace" + + override val mismatchReportType: String = "yarn.YarnLockMismatchReport" + + override val lockFileName: String = LockCopyTask.YARN_LOCK + + override val setProperty: (String) -> String = { " = $it" } +} + +@JsGradlePluginTests +abstract class PackageManagerGradlePluginIT : KGPBaseTest() { + + abstract val yarn: Boolean + + abstract val plugin: String + + abstract val extension: String + + abstract val upgradeTaskName: String + + abstract val storeTaskName: String + + abstract val restoreTaskName: String + + abstract val reportNewLockFile: String + + abstract val lockMismatchReport: String + + abstract val lockFileAutoReplace: String + + abstract val mismatchReportType: String + + abstract val lockFileName: String + + abstract val setProperty: (String) -> String + + override val defaultBuildOptions: BuildOptions + get() = super.defaultBuildOptions.copy( + jsOptions = super.defaultBuildOptions.jsOptions?.copy( + yarn = yarn + ) + ) + + @DisplayName("js composite build works with lock file persistence") + @GradleTest + @GradleTestVersions(minVersion = G_7_6) + fun testJsCompositeBuildWithUpgradeLockFile(gradleVersion: GradleVersion) { + project("js-composite-build", gradleVersion) { + testJsCompositeBuildWithUpgradeLockFile( + upgradeTaskName, + storeTaskName + ) + } + } + + private fun TestProject.testJsCompositeBuildWithUpgradeLockFile( + upgradeTask: String, + storeTask: String, + ) { + buildGradleKts.modify(::transformBuildScriptWithPluginsDsl) + + subProject("lib").apply { + buildGradleKts.modify(::transformBuildScriptWithPluginsDsl) + } + + subProject("base").apply { + buildGradleKts.modify(::transformBuildScriptWithPluginsDsl) + } + + build(upgradeTask) { + assertTasksExecuted(":base:publicPackageJson") + assertTasksExecuted(":lib:lib-2:publicPackageJson") + assertTasksExecuted(":kotlinNpmInstall") + assertTasksExecuted(":$upgradeTask") + } + + build(":nodeTest") { + assertTasksUpToDate(":base:publicPackageJson") + assertTasksUpToDate(":lib:lib-2:publicPackageJson") + assertTasksUpToDate(":kotlinNpmInstall") + assertTasksExecuted(":$storeTask") + } + } + + @DisplayName("Failing with lock file update") + @GradleTest + fun testFailingWithLockFileUpdate(gradleVersion: GradleVersion) { + project("kotlin-js-package-lock-project", gradleVersion) { + testFailingWithLockFileUpdate( + storeTaskName = storeTaskName, + restoreTaskName = restoreTaskName, + upgradeTaskName = upgradeTaskName, + extension = extension, + reportNewLockFile = reportNewLockFile, + lockMismatchReport = lockMismatchReport, + lockFileAutoReplace = lockFileAutoReplace, + mismatchReport = mismatchReportType, + set = setProperty + ) + } + } + + private fun TestProject.testFailingWithLockFileUpdate( + storeTaskName: String, + restoreTaskName: String, + upgradeTaskName: String, + extension: String, + reportNewLockFile: String, + lockMismatchReport: String, + lockFileAutoReplace: String, + mismatchReport: String, + set: (String) -> String, + ) { + build(storeTaskName) { + assertTasksSkipped(":$restoreTaskName") + assertTasksExecuted(":$storeTaskName") + } + + projectPath.resolve(LockCopyTask.KOTLIN_JS_STORE).deleteRecursively() + + buildGradleKts.modify { + it + "\n" + + """ + rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) { + rootProject.the().$reportNewLockFile${set("true")} + } + """.trimIndent() + } + + buildAndFail(storeTaskName) { + assertTasksSkipped(":$restoreTaskName") + assertTasksFailed(":$storeTaskName") + } + + buildGradleKts.modify { + it + "\n" + + """ + dependencies { + implementation(npm("decamelize", "6.0.0")) + } + + rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) { + rootProject.the().$lockMismatchReport${ + set( + "org.jetbrains.kotlin.gradle.targets.js.$mismatchReport.FAIL" + ) + } + } + """.trimIndent() + } + + buildAndFail(storeTaskName) { + assertTasksExecuted(":$restoreTaskName") + assertTasksFailed(":$storeTaskName") + } + + // yarn.lock was not updated + buildAndFail(storeTaskName) { + assertTasksExecuted(":$restoreTaskName") + assertTasksFailed(":$storeTaskName") + } + + build(upgradeTaskName) { + assertTasksExecuted(":$restoreTaskName") + assertTasksExecuted(":$upgradeTaskName") + } + + buildGradleKts.modify { + val replaced = it.replace("implementation(npm(\"decamelize\", \"6.0.0\"))", "") + replaced + "\n" + + """ + rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) { + rootProject.the().$lockMismatchReport${ + set( + "org.jetbrains.kotlin.gradle.targets.js.$mismatchReport.WARNING" + ) + } + } + """.trimIndent() + } + + build(storeTaskName) { + assertTasksExecuted(":$restoreTaskName") + assertTasksExecuted(":$storeTaskName") + + assertOutputContains(PACKAGE_LOCK_MISMATCH_MESSAGE) + } + + build(upgradeTaskName) { + assertTasksExecuted(":$restoreTaskName") + assertTasksExecuted(":$upgradeTaskName") + } + + buildGradleKts.modify { + it + "\n" + + """ + dependencies { + implementation(npm("decamelize", "6.0.0")) + } + + rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) { + rootProject.the().$lockMismatchReport${ + set( + "org.jetbrains.kotlin.gradle.targets.js.$mismatchReport.NONE" + ) + } + } + """.trimIndent() + } + + build(storeTaskName) { + assertTasksExecuted(":$restoreTaskName") + assertTasksExecuted(":$storeTaskName") + + assertOutputDoesNotContain(PACKAGE_LOCK_MISMATCH_MESSAGE) + } + + build(upgradeTaskName) { + assertTasksExecuted(":$restoreTaskName") + assertTasksExecuted(":$upgradeTaskName") + } + + buildGradleKts.modify { + it.replace("implementation(npm(\"decamelize\", \"6.0.0\"))", "") + } + + projectPath.resolve(LockCopyTask.KOTLIN_JS_STORE).deleteRecursively() + + buildGradleKts.modify { + it + "\n" + + """ + rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) { + rootProject.the().$lockMismatchReport${ + set( + "org.jetbrains.kotlin.gradle.targets.js.$mismatchReport.NONE" + ) + } + rootProject.the().$reportNewLockFile${ + set( + "true" + ) + } + } + """.trimIndent() + } + + build(storeTaskName) { + assertTasksSkipped(":$restoreTaskName") + assertTasksExecuted(":$storeTaskName") + + assertOutputDoesNotContain(PACKAGE_LOCK_MISMATCH_MESSAGE) + } + + buildGradleKts.modify { + it + "\n" + + """ + dependencies { + implementation(npm("decamelize", "6.0.0")) + } + + rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) { + rootProject.the().$lockMismatchReport${ + set( + "org.jetbrains.kotlin.gradle.targets.js.$mismatchReport.FAIL" + ) + } + rootProject.the().$lockFileAutoReplace${ + set( + "true" + ) + } + } + """.trimIndent() + } + + buildAndFail(storeTaskName) { + assertTasksUpToDate(":$restoreTaskName") + assertTasksFailed(":$storeTaskName") + } + + //yarn.lock was updated + build(storeTaskName) { + assertTasksExecuted(":$restoreTaskName") + assertTasksExecuted(":$storeTaskName") + } + + buildGradleKts.modify { + it.replace("implementation(npm(\"decamelize\", \"6.0.0\"))", "") + } + + // check if everything ok without build/js/yarn.lock + build("clean") { + assertTasksExecuted(":clean") + } + + build("clean") { + assertTasksUpToDate(":clean") + } + + buildAndFail(storeTaskName) { + assertTasksExecuted(":$restoreTaskName") + assertTasksFailed(":$storeTaskName") + } + + projectPath.resolve(LockCopyTask.KOTLIN_JS_STORE).deleteRecursively() + + //check if independent tasks can be executed + build("help") { + assertTasksExecuted(":help") + } + } + + @DisplayName("Lock file persistence") + @GradleTest + fun testLockStore(gradleVersion: GradleVersion) { + project("nodeJsDownload", gradleVersion) { + testLockStore( + storeTaskName, + lockFileName + ) + } + } + + private fun TestProject.testLockStore( + taskName: String, + lockFile: String, + ) { + build("assemble", taskName) { + assertFileExists(projectPath.resolve(LockCopyTask.KOTLIN_JS_STORE).resolve(lockFile)) + assert( + projectPath + .resolve(LockCopyTask.KOTLIN_JS_STORE) + .resolve(lockFile) + .readText() == projectPath.resolve("build/js/${lockFile}").readText() + ) + } + } + + @DisplayName("Package manager ignore scripts") + @GradleTest + fun testIgnoreScripts(gradleVersion: GradleVersion) { + project("nodeJsDownload", gradleVersion) { + testIgnoreScripts( + plugin, + extension, + setProperty + ) + } + } + + private fun TestProject.testIgnoreScripts( + plugin: String, + extension: String, + set: (String) -> String, + ) { + buildGradleKts.modify { + it + "\n" + + """ + dependencies { + implementation(npm("puppeteer", "11.0.0")) + } + """.trimIndent() + } + build("assemble", "kotlinNpmInstall") { + assert( + projectPath + .resolve("build") + .resolve("js") + .resolve("node_modules") + .resolve("puppeteer") + .resolve(".local-chromium") + .notExists() + ) { + "Chromium should not be installed with --ignore-scripts" + } + } + + buildGradleKts.modify { + it + "\n" + + """ + rootProject.plugins.withType { + rootProject.the().ignoreScripts${set("false")} + } + """.trimIndent() + } + + build("clean") + + build("assemble", "kotlinNpmInstall") { + assertDirectoryExists( + projectPath + .resolve("build") + .resolve("js") + .resolve("node_modules") + .resolve("puppeteer") + .resolve(".local-chromium") + ) + } + } +} diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt index 0b2e436c22c..7322426ad86 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/testbase/BuildOptions.kt @@ -37,7 +37,7 @@ data class BuildOptions( val buildCacheEnabled: Boolean = false, val kaptOptions: KaptOptions? = null, val androidVersion: String? = null, - val jsOptions: JsOptions? = null, + val jsOptions: JsOptions? = JsOptions(), val buildReport: List = emptyList(), val usePreciseJavaTracking: Boolean? = null, val languageVersion: String? = null, @@ -79,7 +79,8 @@ data class BuildOptions( data class JsOptions( val incrementalJs: Boolean? = null, val incrementalJsKlib: Boolean? = null, - val incrementalJsIr: Boolean? = null + val incrementalJsIr: Boolean? = null, + val yarn: Boolean? = null, ) data class NativeOptions( @@ -163,6 +164,7 @@ data class BuildOptions( jsOptions.incrementalJs?.let { arguments.add("-Pkotlin.incremental.js=$it") } jsOptions.incrementalJsKlib?.let { arguments.add("-Pkotlin.incremental.js.klib=$it") } jsOptions.incrementalJsIr?.let { arguments.add("-Pkotlin.incremental.js.ir=$it") } + jsOptions.yarn?.let { arguments.add("-Pkotlin.js.yarn=$it") } } if (androidVersion != null) { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/build.gradle.kts index 6788d08735b..5c3d4495805 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/js-project-repos/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.targets.js.yarn.yarn + plugins { kotlin("js") } @@ -10,6 +12,8 @@ kotlin { } } +yarn + rootProject.plugins.withType { rootProject.the().downloadBaseUrl = null } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-resolutions/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-npm-overrides/build.gradle.kts similarity index 64% rename from libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-resolutions/build.gradle.kts rename to libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-npm-overrides/build.gradle.kts index b84da5c562e..322bea11d78 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-resolutions/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-npm-overrides/build.gradle.kts @@ -10,13 +10,13 @@ repositories { mavenCentral() } -rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin::class.java) { - rootProject.the().apply { - resolution("lodash") { +rootProject.plugins.withType(org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin::class.java) { + rootProject.the().apply { + override("lodash") { include("^1.0.0") exclude("~1.2.1", "1.3.0 - 1.4.0") } - resolution("react", "16.0.0") + override("react", "16.0.0") } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-npm-overrides/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-npm-overrides/settings.gradle new file mode 100644 index 00000000000..01a6f9cf146 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-npm-overrides/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = "kotlin-js-npm-overrides" + +include("base") +include("lib") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-lock-project/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-package-lock-project/build.gradle.kts similarity index 100% rename from libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-lock-project/build.gradle.kts rename to libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-package-lock-project/build.gradle.kts diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-lock-project/gradle.properties b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-package-lock-project/gradle.properties similarity index 100% rename from libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-lock-project/gradle.properties rename to libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-package-lock-project/gradle.properties diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-lock-project/src/main/kotlin/Base.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-package-lock-project/src/main/kotlin/Base.kt similarity index 100% rename from libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-lock-project/src/main/kotlin/Base.kt rename to libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-package-lock-project/src/main/kotlin/Base.kt diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-resolutions/settings.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-resolutions/settings.gradle deleted file mode 100644 index 657e14a0917..00000000000 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin-js-yarn-resolutions/settings.gradle +++ /dev/null @@ -1,4 +0,0 @@ -rootProject.name = "kotlin-js-yarn-resolutions" - -include("base") -include("lib") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-test/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-test/build.gradle.kts index 3b5928c4547..e0489f4e4e1 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-test/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-test/build.gradle.kts @@ -25,9 +25,9 @@ allprojects.forEach { } } -tasks.named("kotlinStoreYarnLock") { +tasks.named("kotlinStorePackageLock") { //A little hacky way to make yarn results - inputFile.fileValue(projectDir.resolve("yarnLockStub")) + inputFile.fileValue(projectDir.resolve("packageLockStub")) } kotlin { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-test/yarnLockStub b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-test/packageLockStub similarity index 100% rename from libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-test/yarnLockStub rename to libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-test/packageLockStub diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-js-test/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-js-test/build.gradle.kts index 0168c9f32b5..6ca5532cd56 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-js-test/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-js-test/build.gradle.kts @@ -5,9 +5,9 @@ plugins { rootProject.plugins.withType { rootProject.the().nodeVersion = "20.2.0" - tasks.named("kotlinStoreYarnLock") { + tasks.named("kotlinStorePackageLock") { //A little hacky way to make yarn results - inputFile.fileValue(projectDir.resolve("yarnLockStub")) + inputFile.fileValue(projectDir.resolve("packageLockStub")) } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-js-test/yarnLockStub b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-js-test/packageLockStub similarity index 100% rename from libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-js-test/yarnLockStub rename to libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-js-test/packageLockStub diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-test/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-test/build.gradle.kts index 20d6a946ff9..ebcfb721159 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-test/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-test/build.gradle.kts @@ -20,9 +20,9 @@ allprojects.forEach { } } -tasks.named("kotlinStoreYarnLock") { +tasks.named("kotlinStorePackageLock") { //A little hacky way to make yarn results - inputFile.fileValue(projectDir.resolve("yarnLockStub")) + inputFile.fileValue(projectDir.resolve("packageLockStub")) } repositories { diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-test/yarnLockStub b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-test/packageLockStub similarity index 100% rename from libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-test/yarnLockStub rename to libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/new-mpp-wasm-wasi-test/packageLockStub diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/exec.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/exec.kt index c4331952ef5..9ac2f8607a1 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/exec.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/internal/exec.kt @@ -5,15 +5,11 @@ package org.jetbrains.kotlin.gradle.internal -import jetbrains.buildServer.messages.serviceMessages.ServiceMessageParserCallback -import org.gradle.api.Project -import org.gradle.api.internal.project.ProjectInternal import org.gradle.internal.logging.progress.ProgressLogger import org.gradle.internal.service.ServiceRegistry import org.gradle.process.ExecResult import org.gradle.process.internal.ExecAction import org.gradle.process.internal.ExecActionFactory -import org.jetbrains.kotlin.gradle.internal.testing.TCServiceMessageOutputStreamHandler import java.io.ByteArrayOutputStream import java.io.PipedInputStream import java.io.PipedOutputStream diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt index 1e8701606f3..bbfe83d8e80 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/PropertiesProvider.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLI import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_INTERNAL_DIAGNOSTICS_USE_PARSABLE_FORMATTING import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_JS_KARMA_BROWSERS import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_JS_STDLIB_DOM_API_INCLUDED +import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_JS_YARN import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ALLOW_LEGACY_DEPENDENCIES import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_GRADLE_PLUGIN_COMPATIBILITY_NO_WARN import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider.PropertyNames.KOTLIN_MPP_ANDROID_SOURCE_SET_LAYOUT_ANDROID_STYLE_NO_WARN @@ -418,6 +419,9 @@ internal class PropertiesProvider private constructor(private val project: Proje val stdlibDomApiIncluded: Boolean get() = booleanProperty(KOTLIN_JS_STDLIB_DOM_API_INCLUDED) ?: true + val yarn: Boolean + get() = booleanProperty(KOTLIN_JS_YARN) ?: true + val kotlinTestInferJvmVariant: Boolean get() = booleanProperty("kotlin.test.infer.jvm.variant") ?: true @@ -613,6 +617,7 @@ internal class PropertiesProvider private constructor(private val project: Proje val KOTLIN_STDLIB_DEFAULT_DEPENDENCY = property("kotlin.stdlib.default.dependency") val KOTLIN_STDLIB_JDK_VARIANTS_VERSION_ALIGNMENT = property("kotlin.stdlib.jdk.variants.version.alignment") val KOTLIN_JS_STDLIB_DOM_API_INCLUDED = property("kotlin.js.stdlib.dom.api.included") + val KOTLIN_JS_YARN = property("kotlin.js.yarn") val KOTLIN_MPP_ENABLE_GRANULAR_SOURCE_SETS_METADATA = property("kotlin.mpp.enableGranularSourceSetsMetadata") val KOTLIN_MPP_ENABLE_COMPATIBILITY_METADATA_VARIANT = property("kotlin.mpp.enableCompatibilityMetadataVariant") val KOTLIN_MPP_ENABLE_CINTEROP_COMMONIZATION = property("kotlin.mpp.enableCInteropCommonization") diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSetupTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSetupTask.kt index 98a5e94819d..ceb350ae2fb 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSetupTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/AbstractSetupTask.kt @@ -138,9 +138,10 @@ abstract class AbstractSetupTask val list = seq.first().split(" ") - list.size == 2 && - list[0] == fileHasher.calculateDirHash(destination) && - list[1] == fileHasher.hash(dist).toByteArray().toHex().also { distHash = it } + list.size == 3 && + list[0] == CACHE_VERSION && + list[1] == fileHasher.calculateDirHash(destination) && + list[2] == fileHasher.hash(dist).toByteArray().toHex().also { distHash = it } } } else false } @@ -156,11 +157,17 @@ abstract class AbstractSetupTask, ) : AbstractEnv { val isWindows: Boolean get() = platformName == "win" diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExec.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExec.kt index 7f9eabfd2b6..612f78a3b18 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExec.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsExec.kt @@ -113,8 +113,8 @@ constructor( it.workingDir(npmProject.dir) it.dependsOn( nodeJsTaskProviders.npmInstallTaskProvider, - nodeJsTaskProviders.storeYarnLockTaskProvider, ) + it.dependsOn(nodeJs.packageManagerExtension.map { it.postInstallTasks }) } it.dependsOn(nodeJsTaskProviders.nodeJsSetupTaskProvider) it.dependsOn(compilation.compileKotlinTaskProvider) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt index 47ed9a07ff2..14de27c39e9 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootExtension.kt @@ -14,14 +14,14 @@ import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.targets.js.AbstractSettings import org.jetbrains.kotlin.gradle.targets.js.NpmVersions -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.PACKAGE_JSON_UMBRELLA_TASK_NAME import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmCachesSetup import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask -import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask +import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockStoreTask +import org.jetbrains.kotlin.gradle.targets.js.yarn.yarn import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore import org.jetbrains.kotlin.gradle.utils.getFile import org.jetbrains.kotlin.gradle.utils.property @@ -80,7 +80,7 @@ open class NodeJsRootExtension( // Release schedule: https://github.com/nodejs/Release // Actual LTS and Current versions: https://nodejs.org/en/download/ // Older versions and more information, e.g. V8 version inside: https://nodejs.org/en/download/releases/ - override var version by Property("18.12.1") + override var version by Property("20.10.0") override var command by Property("node") @@ -91,7 +91,7 @@ open class NodeJsRootExtension( command = value } - var packageManager: NpmApi by Property(Yarn()) + val packageManagerExtension: org.gradle.api.provider.Property = project.objects.property() val taskRequirements: TasksRequirements get() = resolver.tasksRequirements @@ -135,7 +135,7 @@ open class NodeJsRootExtension( val name = platform.get().name val architecture = platform.get().arch - val nodeDirName = "node-v$nodeVersion-$name-$architecture" + val nodeDirName = "node-v$version-$name-$architecture" val cleanableStore = CleanableStore[installationDir.absolutePath] val nodeDir = cleanableStore[nodeDirName].use() val isWindows = platform.get().isWindows() @@ -148,21 +148,23 @@ open class NodeJsRootExtension( fun getIvyDependency(): String { val type = if (isWindows) "zip" else "tar.gz" - return "org.nodejs:node:$nodeVersion:$name-$architecture@$type" + return "org.nodejs:node:$version:$name-$architecture@$type" } + packageManagerExtension.disallowChanges() + return NodeJsEnv( download = download, cleanableStore = cleanableStore, rootPackageDir = rootPackageDirectory.getFile(), dir = nodeDir, nodeBinDir = nodeBinDir, - nodeExecutable = getExecutable("node", nodeCommand, "exe"), + nodeExecutable = getExecutable("node", command, "exe"), platformName = name, architectureName = architecture, ivyDependency = getIvyDependency(), - downloadBaseUrl = nodeDownloadBaseUrl, - packageManager = packageManager + downloadBaseUrl = downloadBaseUrl, + packageManager = packageManagerExtension.get().packageManager ) } @@ -181,8 +183,9 @@ open class NodeJsRootExtension( val npmCachesSetupTaskProvider: TaskProvider get() = project.tasks.withType(KotlinNpmCachesSetup::class.java).named(KotlinNpmCachesSetup.NAME) - val storeYarnLockTaskProvider: TaskProvider - get() = project.tasks.withType(YarnLockCopyTask::class.java).named(YarnLockCopyTask.STORE_YARN_LOCK_NAME) + @Deprecated("This is deprecated and will be removed. Use corresponding property from YarnRootExtension") + val storeYarnLockTaskProvider: TaskProvider + get() = project.yarn.storeYarnLockTaskProvider companion object { const val EXTENSION_NAME: String = "kotlinNodeJs" diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootPlugin.kt index a4d380ceb73..8f140049daf 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsRootPlugin.kt @@ -8,24 +8,29 @@ package org.jetbrains.kotlin.gradle.targets.js.nodejs import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.file.RegularFile import org.gradle.api.plugins.BasePlugin import org.gradle.api.provider.Provider +import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.gradle.plugin.PropertiesProvider import org.jetbrains.kotlin.gradle.plugin.internal.configurationTimePropertiesAccessor import org.jetbrains.kotlin.gradle.plugin.internal.usedAtConfigurationTime import org.jetbrains.kotlin.gradle.plugin.variantImplementationFactory import org.jetbrains.kotlin.gradle.targets.js.MultiplePluginDeclarationDetector -import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModulesCache -import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager -import org.jetbrains.kotlin.gradle.targets.js.npm.UsesKotlinNpmResolutionManager +import org.jetbrains.kotlin.gradle.targets.js.npm.* import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.PACKAGE_JSON_UMBRELLA_TASK_NAME +import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.implementing import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmCachesSetup import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask +import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin import org.jetbrains.kotlin.gradle.tasks.CleanDataTask import org.jetbrains.kotlin.gradle.tasks.registerTask -import org.jetbrains.kotlin.gradle.tasks.withType -import org.jetbrains.kotlin.gradle.utils.* +import org.jetbrains.kotlin.gradle.utils.castIsolatedKotlinPluginClassLoaderAware +import org.jetbrains.kotlin.gradle.utils.getFile +import org.jetbrains.kotlin.gradle.utils.onlyIfCompat +import org.jetbrains.kotlin.gradle.utils.providerWithLazyConvention open class NodeJsRootPlugin : Plugin { override fun apply(project: Project) { @@ -43,8 +48,24 @@ open class NodeJsRootPlugin : Plugin { project ) + val npm = project.extensions.create( + NpmExtension.EXTENSION_NAME, + NpmExtension::class.java, + project + ) + addPlatform(project, nodeJs) + npm.nodeJsEnvironment.value( + project.provider { + nodeJs.requireConfigured() + } + ).disallowChanges() + + nodeJs.packageManagerExtension.convention( + npm + ) + val setupTask = project.registerTask(NodeJsSetupTask.NAME) { it.group = TASKS_GROUP_NAME it.description = "Download and install a local node/npm version" @@ -97,21 +118,125 @@ open class NodeJsRootPlugin : Plugin { val objectFactory = project.objects - // TODO: Could we use common approach with build services to KotlinNpmResolutionManager? - val npmResolutionManager = project.gradle.sharedServices.registerIfAbsent( - KotlinNpmResolutionManager::class.java.name, - KotlinNpmResolutionManager::class.java - ) { - it.parameters.resolution.set( - objectFactory.providerWithLazyConvention { - nodeJs.resolver.close() - } - ) - it.parameters.gradleNodeModulesProvider.set(gradleNodeModulesProvider) - it.parameters.packagesDir.set(nodeJs.projectPackagesDirectory) + val npmResolutionManager: Provider = KotlinNpmResolutionManager.registerIfAbsent( + project, + objectFactory.providerWithLazyConvention { + nodeJs.resolver.close() + }, + gradleNodeModulesProvider, + nodeJs.projectPackagesDirectory + ) + + val rootPackageJson = project.tasks.register(RootPackageJsonTask.NAME, RootPackageJsonTask::class.java) { task -> + task.dependsOn(nodeJs.npmCachesSetupTaskProvider) + task.group = TASKS_GROUP_NAME + task.description = "Create root package.json" + + task.npmResolutionManager.value(npmResolutionManager) + .disallowChanges() + + task.onlyIfCompat("Prepare NPM project only in configuring state") { + it as RootPackageJsonTask + it.npmResolutionManager.get().isConfiguringState() + } } - YarnPlugin.apply(project) + configureRequiresNpmDependencies(project, rootPackageJson) + + val packageJsonUmbrella = nodeJs + .packageJsonUmbrellaTaskProvider + + nodeJs.rootPackageJsonTaskProvider.configure { + it.dependsOn(packageJsonUmbrella) + } + + npmInstall.configure { + it.dependsOn(rootPackageJson) + it.inputs.property("npmIgnoreScripts", { npm.ignoreScripts }) + } + + project.tasks.register(LockCopyTask.STORE_PACKAGE_LOCK_NAME, LockStoreTask::class.java) { task -> + task.dependsOn(npmInstall) + task.inputFile.set(nodeJs.rootPackageDirectory.map { it.file(LockCopyTask.PACKAGE_LOCK) }) + + task.additionalInputFiles.from( + nodeJs.rootPackageDirectory.map { it.file(LockCopyTask.YARN_LOCK) } + ) + task.additionalInputFiles.from( + task.outputDirectory.map { it.file(LockCopyTask.YARN_LOCK) } + ) + + task.outputDirectory.set(npm.lockFileDirectory) + task.fileName.set(npm.lockFileName) + + task.lockFileMismatchReport.value( + project.provider { npm.requireConfigured().packageLockMismatchReport } + ).disallowChanges() + task.reportNewLockFile.value( + project.provider { npm.requireConfigured().reportNewPackageLock } + ).disallowChanges() + task.lockFileAutoReplace.value( + project.provider { npm.requireConfigured().packageLockAutoReplace } + ).disallowChanges() + } + + project.tasks.register(LockCopyTask.UPGRADE_PACKAGE_LOCK, LockStoreTask::class.java) { task -> + task.dependsOn(npmInstall) + task.inputFile.set(nodeJs.rootPackageDirectory.map { it.file(LockCopyTask.PACKAGE_LOCK) }) + task.outputDirectory.set(npm.lockFileDirectory) + task.fileName.set(npm.lockFileName) + + task.additionalInputFiles.from( + nodeJs.rootPackageDirectory.map { it.file(LockCopyTask.YARN_LOCK) } + ) + task.additionalInputFiles.from( + task.outputDirectory.map { it.file(LockCopyTask.YARN_LOCK) } + ) + + task.lockFileMismatchReport.value( + LockFileMismatchReport.NONE + ).disallowChanges() + task.reportNewLockFile.value( + false + ).disallowChanges() + task.lockFileAutoReplace.value( + true + ).disallowChanges() + } + + project.tasks.register(LockCopyTask.RESTORE_PACKAGE_LOCK_NAME, LockCopyTask::class.java) { task -> + task.inputFile.set( + npm.lockFileDirectory.flatMap { dir -> + dir.file(npm.lockFileName) + } + ) + task.additionalInputFiles.from( + npm.lockFileDirectory.map { it.file(LockCopyTask.YARN_LOCK) } + ) + task.outputDirectory.set(nodeJs.rootPackageDirectory) + task.fileName.set(LockCopyTask.PACKAGE_LOCK) + task.onlyIf { + val inputFileExists = task.inputFile.getOrNull()?.asFile?.exists() == true + // Workaround for "skip if not exists" + // https://github.com/gradle/gradle/issues/2919 + if (!inputFileExists) { + task.inputFile.set(null as RegularFile?) + } + inputFileExists || task.additionalInputFiles.files.any { it.exists() } + } + } + + npm.preInstallTasks.value( + listOf(npm.restorePackageLockTaskProvider) + ).disallowChanges() + + npm.postInstallTasks.value( + listOf(npm.storePackageLockTaskProvider) + ).disallowChanges() + + npmInstall.configure { + it.dependsOn(nodeJs.packageManagerExtension.map { it.preInstallTasks }) + } npmInstall.configure { it.npmResolutionManager.value(npmResolutionManager).disallowChanges() @@ -122,6 +247,12 @@ open class NodeJsRootPlugin : Plugin { it.group = TASKS_GROUP_NAME it.description = "Clean unused local node version" } + + val propertiesProvider = PropertiesProvider(project) + + if (propertiesProvider.yarn) { + YarnPlugin.apply(project) + } } // from https://github.com/node-gradle/gradle-node-plugin @@ -142,6 +273,42 @@ open class NodeJsRootPlugin : Plugin { ).disallowChanges() } + // Yes, we need to break Task Configuration Avoidance here + // In case when we need to create package.json's files and execute kotlinNpmInstall, + // We need to configure all RequiresNpmDependencies tasks to install them, + // Because we need to persist lock file + // We execute this block in configure phase of rootPackageJson to be sure, + // That Task Configuration Avoidance will not be broken for tasks not related with NPM installing + // https://youtrack.jetbrains.com/issue/KT-48241 + private fun configureRequiresNpmDependencies( + project: Project, + rootPackageJson: TaskProvider, + ) { + val fn: (Project) -> Unit = { + it.tasks.implementing(RequiresNpmDependencies::class) + .forEach {} + } + rootPackageJson.configure { + project.allprojects + .forEach { project -> + if (it.state.executed) { + fn(project) + } + } + } + + project.allprojects + .forEach { + if (!it.state.executed) { + it.afterEvaluate { project -> + rootPackageJson.configure { + fn(project) + } + } + } + } + } + companion object { const val TASKS_GROUP_NAME: String = "nodeJs" @@ -154,30 +321,14 @@ open class NodeJsRootPlugin : Plugin { val Project.kotlinNodeJsExtension: NodeJsRootExtension get() = extensions.getByName(NodeJsRootExtension.EXTENSION_NAME).castIsolatedKotlinPluginClassLoaderAware() - private val Project.gradleNodeModules - get() = GradleNodeModulesCache.registerIfAbsent( - this, - null, - null - ) - val Project.kotlinNpmResolutionManager: Provider get() { - val npmResolutionManager = project.gradle.sharedServices.registerIfAbsent( + return project.gradle.sharedServices.registerIfAbsent( KotlinNpmResolutionManager::class.java.name, KotlinNpmResolutionManager::class.java ) { error("Must be already registered") } - - SingleActionPerProject.run(project, UsesKotlinNpmResolutionManager::class.java.name) { - project.tasks.withType().configureEach { task -> - task.usesService(npmResolutionManager) - task.usesService(gradleNodeModules) - } - } - - return npmResolutionManager } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt index 344b61a7f12..46811526e66 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/NodeJsSetupTask.kt @@ -43,7 +43,7 @@ abstract class NodeJsSetupTask : AbstractSetupTask> { + val packageManager: NpmApi + + val environment: Env + + val additionalInstallOutput: FileCollection + + val preInstallTasks: ListProperty> + + val postInstallTasks: ListProperty> +} + +typealias NpmApiExt = NpmApiExtension> \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/PackageManagerEnvironment.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/PackageManagerEnvironment.kt new file mode 100644 index 00000000000..b6b46afddee --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/PackageManagerEnvironment.kt @@ -0,0 +1,10 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.nodejs + +import java.io.Serializable + +interface PackageManagerEnvironment : Serializable \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModuleBuilder.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModuleBuilder.kt index ecaaa956163..22978f12f79 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModuleBuilder.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/GradleNodeModuleBuilder.kt @@ -77,7 +77,7 @@ internal class GradleNodeModuleBuilder( packageJson.devDependencies.clear() - // yarn requires semver + // npm requires semver packageJson.version = fixSemver(packageJson.version) return makeNodeModule(cacheDir, packageJson) { nodeModule -> diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/KotlinNpmResolutionManager.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/KotlinNpmResolutionManager.kt index 00d98d4080d..1f0180c407e 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/KotlinNpmResolutionManager.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/KotlinNpmResolutionManager.kt @@ -5,20 +5,27 @@ package org.jetbrains.kotlin.gradle.targets.js.npm +import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.file.Directory import org.gradle.api.file.DirectoryProperty import org.gradle.api.logging.Logger import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider import org.gradle.api.services.BuildService import org.gradle.api.services.BuildServiceParameters import org.gradle.api.tasks.Internal import org.gradle.internal.service.ServiceRegistry +import org.jetbrains.kotlin.gradle.targets.js.nodejs.PackageManagerEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.Installation import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.KotlinRootNpmResolution import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinCompilationNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinProjectNpmResolver import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver +import org.jetbrains.kotlin.gradle.tasks.withType +import org.jetbrains.kotlin.gradle.utils.SingleActionPerProject +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract internal interface UsesKotlinNpmResolutionManager : Task { @get:Internal @@ -78,16 +85,16 @@ abstract class KotlinNpmResolutionManager : BuildService = emptyList(), services: ServiceRegistry, logger: Logger, - npmEnvironment: NpmEnvironment, - yarnEnvironment: YarnEnvironment, + nodeJsEnvironment: NodeJsEnvironment, + packageManagerEnvironment: PackageManagerEnvironment, ): Unit? { synchronized(this) { if (state is ResolutionState.Installed) { @@ -99,8 +106,8 @@ abstract class KotlinNpmResolutionManager : BuildService { state1.resolution.prepareInstallation( logger, - npmEnvironment, - yarnEnvironment, + nodeJsEnvironment, + packageManagerEnvironment, this ).also { this.state = ResolutionState.Prepared(it) @@ -146,4 +153,52 @@ abstract class KotlinNpmResolutionManager : BuildService throw state0.wrappedException } } + + companion object { + private val serviceClass = KotlinNpmResolutionManager::class.java + private val serviceName = serviceClass.name + + private fun registerIfAbsentImpl( + project: Project, + resolution: Provider?, + gradleNodeModulesProvider: Provider?, + packagesDir: Provider + ): Provider { + project.gradle.sharedServices.registrations.findByName(serviceName)?.let { + @Suppress("UNCHECKED_CAST") + return it.service as Provider + } + + val message = { + "Build service KotlinNpmResolutionManager should be already registered" + } + + requireNotNull(resolution, message) + requireNotNull(gradleNodeModulesProvider, message) + + return project.gradle.sharedServices.registerIfAbsent(serviceName, serviceClass) { + it.parameters.resolution.set( + resolution + ) + it.parameters.gradleNodeModulesProvider.set(gradleNodeModulesProvider) + it.parameters.packagesDir.set(packagesDir) + } + } + + fun registerIfAbsent( + project: Project, + resolution: Provider?, + gradleNodeModulesProvider: Provider?, + packagesDir: Provider + ) = registerIfAbsentImpl(project, resolution, gradleNodeModulesProvider, packagesDir).also { serviceProvider -> + SingleActionPerProject.run(project, UsesKotlinNpmResolutionManager::class.java.name) { + project.tasks.withType().configureEach { task -> + task.usesService(serviceProvider) + if (gradleNodeModulesProvider != null) { + task.usesService(gradleNodeModulesProvider) + } + } + } + } + } } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/LockCopyTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/LockCopyTask.kt new file mode 100644 index 00000000000..288b03babc7 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/LockCopyTask.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.npm + +import org.gradle.api.DefaultTask +import org.gradle.api.file.* +import org.gradle.api.provider.Property +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.* +import org.gradle.work.DisableCachingByDefault +import org.gradle.work.NormalizeLineEndings +import java.io.File +import javax.inject.Inject + +@DisableCachingByDefault +abstract class LockCopyTask : DefaultTask() { + + @get:NormalizeLineEndings + @get:InputFile + @get:Optional + @get:PathSensitive(PathSensitivity.RELATIVE) + abstract val inputFile: RegularFileProperty + + @get:NormalizeLineEndings + @get:InputFiles + @get:PathSensitive(PathSensitivity.RELATIVE) + val additionalInputFiles: ConfigurableFileCollection = project.objects.fileCollection() + + @get:Internal + abstract val outputDirectory: DirectoryProperty + + @get:Internal + abstract val fileName: Property + + @get:OutputFile + val outputFile: Provider + get() = outputDirectory.map { dir -> + dir.asFile.resolve(fileName.get()) + } + + @get:Inject + abstract val fs: FileSystemOperations + + @TaskAction + open fun copy() { + fs.copy { copy -> + inputFile.getOrNull()?.let { inputFile -> + copy.from(inputFile) { + it.rename { fileName.get() } + } + } + + copy.from(additionalInputFiles) + copy.into(outputDirectory) + } + } + + companion object { + const val STORE_PACKAGE_LOCK_NAME = "kotlinStorePackageLock" + const val RESTORE_PACKAGE_LOCK_NAME = "kotlinRestorePackageLock" + const val UPGRADE_PACKAGE_LOCK = "kotlinUpgradePackageLock" + const val PACKAGE_LOCK_MISMATCH_MESSAGE = "Lock file was changed. Run the `$UPGRADE_PACKAGE_LOCK` task to actualize lock file" + const val KOTLIN_JS_STORE = "kotlin-js-store" + const val PACKAGE_LOCK = "package-lock.json" + const val YARN_LOCK = "yarn.lock" + } +} + +enum class LockFileMismatchReport { + NONE, + WARNING, + FAIL, +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/LockStoreTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/LockStoreTask.kt new file mode 100644 index 00000000000..b33c705bf43 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/LockStoreTask.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.npm + +import org.gradle.api.GradleException +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.work.DisableCachingByDefault +import org.jetbrains.kotlin.gradle.utils.contentEquals + +@DisableCachingByDefault +abstract class LockStoreTask : LockCopyTask() { + @get:Input + abstract val lockFileMismatchReport: Property + + @get:Input + abstract val reportNewLockFile: Property + + @get:Input + abstract val lockFileAutoReplace: Property + + override fun copy() { + val outputFile = outputDirectory.get().asFile.resolve(fileName.get()) + + val value = inputFile.orNull + requireNotNull(value) { + "Input file $fileName should exist" + } + + val shouldReportMismatch = if (!outputFile.exists()) { + reportNewLockFile.get() + } else { + lockFileMismatchReport.get() != LockFileMismatchReport.NONE && !contentEquals(value.asFile, outputFile) + } + + // outputFile is updated only with auto replace or not existed, but we need delete all other files initially + fs.delete { + it.delete(additionalInputFiles) + } + + if (!outputFile.exists() || lockFileAutoReplace.get()) { + super.copy() + } + + if (shouldReportMismatch) { + when (lockFileMismatchReport.get()) { + LockFileMismatchReport.NONE -> {} + LockFileMismatchReport.WARNING -> { + logger.warn(PACKAGE_LOCK_MISMATCH_MESSAGE) + } + LockFileMismatchReport.FAIL -> throw GradleException(PACKAGE_LOCK_MISMATCH_MESSAGE) + else -> error("Unknown mismatch report kind") + } + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/Npm.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/Npm.kt new file mode 100644 index 00000000000..42fbc29ecb7 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/Npm.kt @@ -0,0 +1,139 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.npm + +import org.gradle.api.logging.Logger +import org.gradle.internal.service.ServiceRegistry +import org.jetbrains.kotlin.gradle.internal.execWithProgress +import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution +import org.jetbrains.kotlin.gradle.utils.getFile +import java.io.File + +class Npm : NpmApiExecution { + + override fun preparedFiles(nodeJs: NodeJsEnvironment): Collection { + return listOf( + nodeJs + .rootPackageDir + .resolve(NpmProject.PACKAGE_JSON) + ) + } + + override fun prepareRootProject( + nodeJs: NodeJsEnvironment, + packageManagerEnvironment: NpmEnvironment, + rootProjectName: String, + rootProjectVersion: String, + subProjects: Collection, + ) { + return prepareRootPackageJson( + nodeJs, + rootProjectName, + rootProjectVersion, + subProjects, + packageManagerEnvironment.overrides + .associate { it.path to it.toVersionString() }, + ) + } + + private fun prepareRootPackageJson( + nodeJs: NodeJsEnvironment, + rootProjectName: String, + rootProjectVersion: String, + npmProjects: Collection, + overrides: Map, + ) { + val rootPackageJsonFile = preparedFiles(nodeJs).single() + + saveRootProjectWorkspacesPackageJson( + rootProjectName, + rootProjectVersion, + npmProjects, + overrides, + rootPackageJsonFile + ) + } + + override fun resolveRootProject( + services: ServiceRegistry, + logger: Logger, + nodeJs: NodeJsEnvironment, + packageManagerEnvironment: NpmEnvironment, + npmProjects: Collection, + cliArgs: List, + ) { + val nodeJsWorldDir = nodeJs.rootPackageDir + + npmExec( + services, + logger, + nodeJs, + packageManagerEnvironment, + nodeJsWorldDir, + NpmApiExecution.resolveOperationDescription("npm"), + cliArgs + ) + } + + fun npmExec( + services: ServiceRegistry, + logger: Logger, + nodeJs: NodeJsEnvironment, + environment: NpmEnvironment, + dir: File, + description: String, + args: List, + ) { + services.execWithProgress(description) { exec -> + val arguments = listOf("install") + args + .plus( + if (logger.isDebugEnabled) "--verbose" else "" + ) + .plus( + if (environment.ignoreScripts) "--ignore-scripts" else "" + ).filter { it.isNotEmpty() } + + if (!environment.standalone) { + val nodeExecutable = nodeJs.nodeExecutable + val nodePath = File(nodeExecutable).parent + exec.environment( + "PATH", + "$nodePath${File.pathSeparator}${System.getenv("PATH")}" + ) + } + + val command = environment.executable + + exec.executable = command + exec.args = arguments + + exec.workingDir = dir + } + + } + + private fun saveRootProjectWorkspacesPackageJson( + rootProjectName: String, + rootProjectVersion: String, + npmProjects: Collection, + overrides: Map, + rootPackageJsonFile: File, + ) { + val nodeJsWorldDir = rootPackageJsonFile.parentFile + val rootPackageJson = PackageJson(rootProjectName, rootProjectVersion) + rootPackageJson.private = true + + val npmProjectWorkspaces = npmProjects.map { it.npmProjectDir.getFile().relativeTo(nodeJsWorldDir).path } + val importedProjectWorkspaces = + NpmImportedPackagesVersionResolver(npmProjects, nodeJsWorldDir).resolveAndUpdatePackages() + + rootPackageJson.workspaces = npmProjectWorkspaces + importedProjectWorkspaces + rootPackageJson.overrides = overrides + rootPackageJson.saveTo( + rootPackageJsonFile + ) + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApi.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApiExecution.kt similarity index 59% rename from libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApi.kt rename to libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApiExecution.kt index 30362c1cd09..93f62163380 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApi.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmApiExecution.kt @@ -8,32 +8,30 @@ package org.jetbrains.kotlin.gradle.targets.js.npm import org.gradle.api.logging.Logger import org.gradle.internal.service.ServiceRegistry import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnv +import org.jetbrains.kotlin.gradle.targets.js.nodejs.PackageManagerEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnEnv -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnResolution import java.io.File import java.io.Serializable /** * NodeJS package manager API */ -interface NpmApi : Serializable { - fun preparedFiles(nodeJs: NpmEnvironment): Collection +interface NpmApiExecution : Serializable { + fun preparedFiles(nodeJs: NodeJsEnvironment): Collection fun prepareRootProject( - nodeJs: NpmEnvironment, + nodeJs: NodeJsEnvironment, + packageManagerEnvironment: @UnsafeVariance T, rootProjectName: String, rootProjectVersion: String, - logger: Logger, subProjects: Collection, - resolutions: Map, ) fun resolveRootProject( services: ServiceRegistry, logger: Logger, - nodeJs: NpmEnvironment, - yarn: YarnEnvironment, + nodeJs: NodeJsEnvironment, + packageManagerEnvironment: @UnsafeVariance T, npmProjects: Collection, cliArgs: List ) @@ -44,32 +42,15 @@ interface NpmApi : Serializable { } } -data class NpmEnvironment( +data class NodeJsEnvironment( val rootPackageDir: File, val nodeExecutable: String, - val isWindows: Boolean, - val packageManager: NpmApi + val packageManager: NpmApiExecution ) : Serializable -internal val NodeJsEnv.asNpmEnvironment - get() = NpmEnvironment( +internal val NodeJsEnv.asNodeJsEnvironment + get() = NodeJsEnvironment( rootPackageDir, nodeExecutable, - isWindows, packageManager - ) - -data class YarnEnvironment( - val executable: String, - val standalone: Boolean, - val ignoreScripts: Boolean, - val yarnResolutions: List -) : Serializable - -internal val YarnEnv.asYarnEnvironment - get() = YarnEnvironment( - executable, - standalone, - ignoreScripts, - yarnResolutions ) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmEnv.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmEnv.kt new file mode 100644 index 00000000000..c8d0842c97a --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmEnv.kt @@ -0,0 +1,16 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.npm + +data class NpmEnv( + val executable: String, + val ignoreScripts: Boolean, + val standalone: Boolean, + val packageLockMismatchReport: LockFileMismatchReport, + val reportNewPackageLock: Boolean, + val packageLockAutoReplace: Boolean, + val overrides: List +) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmEnvironment.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmEnvironment.kt new file mode 100644 index 00000000000..1a416cedf6e --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmEnvironment.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.npm + +import org.jetbrains.kotlin.gradle.targets.js.nodejs.PackageManagerEnvironment + +data class NpmEnvironment( + val executable: String, + val ignoreScripts: Boolean, + val standalone: Boolean, + val overrides: List +) : PackageManagerEnvironment + +internal val NpmEnv.asNpmEnvironment + get() = NpmEnvironment( + executable, + ignoreScripts, + standalone, + overrides + ) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmExtension.kt new file mode 100644 index 00000000000..ce176ddc3a8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmExtension.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.npm + +import org.gradle.api.Action +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.file.FileCollection +import org.gradle.api.provider.ListProperty +import org.gradle.api.tasks.TaskProvider +import org.jetbrains.kotlin.gradle.internal.ConfigurationPhaseAware +import org.jetbrains.kotlin.gradle.logging.kotlinInfo +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnv +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NpmApiExtension + +open class NpmExtension( + val project: Project, +) : ConfigurationPhaseAware(), NpmApiExtension { + init { + check(project == project.rootProject) + } + + private val gradleHome = project.gradle.gradleUserHomeDir.also { + project.logger.kotlinInfo("Storing cached files in $it") + } + + override val packageManager: Npm by lazy { + Npm() + } + + override val environment: NpmEnvironment by lazy { + requireConfigured().asNpmEnvironment + } + + override val additionalInstallOutput: FileCollection = project.objects.fileCollection().from( + { + nodeJsEnvironment.get().rootPackageDir.resolve("package-lock.json") + } + ) + + override val preInstallTasks: ListProperty> = project.objects.listProperty(TaskProvider::class.java) + + override val postInstallTasks: ListProperty> = project.objects.listProperty(TaskProvider::class.java) + + val command: org.gradle.api.provider.Property = project.objects.property(String::class.java) + .convention("npm") + + val lockFileName: org.gradle.api.provider.Property = project.objects.property(String::class.java) + .convention(LockCopyTask.PACKAGE_LOCK) + val lockFileDirectory: DirectoryProperty = project.objects.directoryProperty() + .convention(project.layout.projectDirectory.dir(LockCopyTask.KOTLIN_JS_STORE)) + + val ignoreScripts: org.gradle.api.provider.Property = project.objects.property(Boolean::class.java) + .convention(true) + + val packageLockMismatchReport: org.gradle.api.provider.Property = + project.objects.property(LockFileMismatchReport::class.java) + .convention(LockFileMismatchReport.FAIL) + + val reportNewPackageLock: org.gradle.api.provider.Property = project.objects.property(Boolean::class.java) + .convention(false) + + val packageLockAutoReplace: org.gradle.api.provider.Property = project.objects.property(Boolean::class.java) + .convention(false) + + val overrides: ListProperty = project.objects.listProperty(NpmOverride::class.java) + + fun override(path: String, configure: Action) { + overrides.add( + NpmOverride(path) + .apply { configure.execute(this) } + ) + } + + fun override(path: String, version: String) { + override(path, Action { + it.include(version) + }) + } + + internal val nodeJsEnvironment: org.gradle.api.provider.Property = project.objects.property(NodeJsEnv::class.java) + + override fun finalizeConfiguration(): NpmEnv { + val nodeJsEnvironmentValue = nodeJsEnvironment.get() + val isWindows = nodeJsEnvironmentValue.isWindows + + fun getExecutable(command: String, customCommand: String, windowsExtension: String): String { + val finalCommand = if (isWindows && customCommand == command) "$command.$windowsExtension" else customCommand + return if (nodeJsEnvironmentValue.download) + nodeJsEnvironmentValue.nodeBinDir + .resolve(finalCommand).absolutePath + else + finalCommand + } + return NpmEnv( + executable = getExecutable("npm", command.get(), "cmd"), + ignoreScripts = ignoreScripts.get(), + standalone = !nodeJsEnvironmentValue.download, + packageLockMismatchReport = packageLockMismatchReport.get(), + reportNewPackageLock = reportNewPackageLock.get(), + packageLockAutoReplace = packageLockAutoReplace.get(), + overrides = overrides.get() + ) + } + + val restorePackageLockTaskProvider: TaskProvider + get() = project.tasks.named(LockCopyTask.RESTORE_PACKAGE_LOCK_NAME) + + val storePackageLockTaskProvider: TaskProvider + get() = project.tasks.named(LockCopyTask.STORE_PACKAGE_LOCK_NAME) + + companion object { + const val EXTENSION_NAME: String = "kotlinNpm" + + operator fun get(project: Project): NpmExtension { + val rootProject = project.rootProject + rootProject.plugins.apply(NodeJsRootPlugin::class.java) + return rootProject.extensions.getByName(EXTENSION_NAME) as NpmExtension + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnImportedPackagesVersionResolver.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmImportedPackagesVersionResolver.kt similarity index 83% rename from libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnImportedPackagesVersionResolver.kt rename to libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmImportedPackagesVersionResolver.kt index da2cd29983d..a90dbb6a727 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnImportedPackagesVersionResolver.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmImportedPackagesVersionResolver.kt @@ -1,17 +1,14 @@ /* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ -package org.jetbrains.kotlin.gradle.targets.js.yarn +package org.jetbrains.kotlin.gradle.targets.js.npm -import org.gradle.api.logging.Logger -import org.jetbrains.kotlin.gradle.targets.js.npm.GradleNodeModule import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution import java.io.File -class YarnImportedPackagesVersionResolver( - private val logger: Logger, +class NpmImportedPackagesVersionResolver( npmProjects: Collection, private val nodeJsWorldDir: File ) { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmOverride.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmOverride.kt new file mode 100644 index 00000000000..d6a7995e8fc --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmOverride.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.npm + +import java.io.Serializable + +class NpmOverride( + val path: String +) : Serializable { + var includedVersions = mutableListOf() + var excludedVersions = mutableListOf() + + fun include(vararg include: String) { + includedVersions.addAll(include) + } + + fun exclude(vararg exclude: String) { + excludedVersions.addAll(exclude) + } +} + +fun NpmOverride.toVersionString(): String { + return buildNpmVersion(includedVersions, excludedVersions) +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolverPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolverPlugin.kt index d701f2ebe12..cd6c67fdc34 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolverPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/NpmResolverPlugin.kt @@ -30,8 +30,9 @@ class NpmResolverPlugin : Plugin { } task.dependsOn( kotlinNodeJsTaskProvidersExtension.npmInstallTaskProvider, - kotlinNodeJsTaskProvidersExtension.storeYarnLockTaskProvider, ) + + task.dependsOn(nodeJs.packageManagerExtension.map { it.postInstallTasks }) } } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt index af02c6eecac..a6a49a06f1d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/PackageJson.kt @@ -35,7 +35,7 @@ class PackageJson( var workspaces: Collection? = null - var resolutions: Map? = null + var overrides: Map? = null var types: String? = null diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolved/KotlinRootNpmResolution.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolved/KotlinRootNpmResolution.kt index 8f7ade1fa08..f9d946a15d3 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolved/KotlinRootNpmResolution.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/resolved/KotlinRootNpmResolution.kt @@ -7,10 +7,9 @@ package org.jetbrains.kotlin.gradle.targets.js.npm.resolved import org.gradle.api.logging.Logger import org.gradle.internal.service.ServiceRegistry +import org.jetbrains.kotlin.gradle.targets.js.nodejs.PackageManagerEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment -import org.jetbrains.kotlin.gradle.targets.js.npm.YarnEnvironment -import org.jetbrains.kotlin.gradle.targets.js.yarn.toVersionString +import org.jetbrains.kotlin.gradle.targets.js.npm.NodeJsEnvironment import java.io.Serializable class KotlinRootNpmResolution( @@ -25,8 +24,8 @@ class KotlinRootNpmResolution( */ internal fun prepareInstallation( logger: Logger, - npmEnvironment: NpmEnvironment, - yarnEnvironment: YarnEnvironment, + nodeJsEnvironment: NodeJsEnvironment, + packageManagerEnvironment: PackageManagerEnvironment, npmResolutionManager: KotlinNpmResolutionManager, ): Installation { synchronized(projects) { @@ -41,14 +40,12 @@ class KotlinRootNpmResolution( ) } - npmEnvironment.packageManager.prepareRootProject( - npmEnvironment, + nodeJsEnvironment.packageManager.prepareRootProject( + nodeJsEnvironment, + packageManagerEnvironment, rootProjectName, rootProjectVersion, - logger, projectResolutions, - yarnEnvironment.yarnResolutions - .associate { it.path to it.toVersionString() }, ) return Installation( @@ -63,15 +60,15 @@ class Installation(val compilationResolutions: Collection, services: ServiceRegistry, logger: Logger, - npmEnvironment: NpmEnvironment, - yarnEnvironment: YarnEnvironment, + nodeJsEnvironment: NodeJsEnvironment, + packageManagerEnvironment: PackageManagerEnvironment, ) { synchronized(compilationResolutions) { - npmEnvironment.packageManager.resolveRootProject( + nodeJsEnvironment.packageManager.resolveRootProject( services, logger, - npmEnvironment, - yarnEnvironment, + nodeJsEnvironment, + packageManagerEnvironment, compilationResolutions, args ) diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/tasks/KotlinNpmInstallTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/tasks/KotlinNpmInstallTask.kt index 34b35392b95..4d9816b5f50 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/tasks/KotlinNpmInstallTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/tasks/KotlinNpmInstallTask.kt @@ -10,14 +10,7 @@ import org.gradle.api.file.Directory import org.gradle.api.file.FileCollection import org.gradle.api.file.RegularFile import org.gradle.api.provider.Provider -import org.gradle.api.tasks.IgnoreEmptyDirectories -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.PathSensitive -import org.gradle.api.tasks.PathSensitivity -import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.* import org.gradle.work.DisableCachingByDefault import org.gradle.work.NormalizeLineEndings import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension @@ -25,10 +18,8 @@ import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion. import org.jetbrains.kotlin.gradle.targets.js.npm.KotlinNpmResolutionManager import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject import org.jetbrains.kotlin.gradle.targets.js.npm.UsesKotlinNpmResolutionManager -import org.jetbrains.kotlin.gradle.targets.js.npm.asNpmEnvironment -import org.jetbrains.kotlin.gradle.targets.js.npm.asYarnEnvironment +import org.jetbrains.kotlin.gradle.targets.js.npm.asNodeJsEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver -import org.jetbrains.kotlin.gradle.targets.js.yarn.yarn import org.jetbrains.kotlin.gradle.utils.getFile import java.io.File @@ -46,9 +37,6 @@ abstract class KotlinNpmInstallTask : private val nodeJs: NodeJsRootExtension get() = project.rootProject.kotlinNodeJsExtension - private val yarn - get() = project.rootProject.yarn - private val rootResolver: KotlinRootNpmResolver get() = nodeJs.resolver @@ -57,12 +45,12 @@ abstract class KotlinNpmInstallTask : // ----- - private val npmEnvironment by lazy { - nodeJs.requireConfigured().asNpmEnvironment + private val nodsJsEnvironment by lazy { + nodeJs.requireConfigured().asNodeJsEnvironment } - private val yarnEnv by lazy { - yarn.requireConfigured().asYarnEnvironment + private val packageManagerEnv by lazy { + nodeJs.packageManagerExtension.get().environment } @Input @@ -73,7 +61,7 @@ abstract class KotlinNpmInstallTask : @get:NormalizeLineEndings @get:InputFiles val preparedFiles: Collection by lazy { - nodeJs.packageManager.preparedFiles(npmEnvironment) + nodeJs.packageManagerExtension.get().packageManager.preparedFiles(nodsJsEnvironment) } @get:PathSensitive(PathSensitivity.RELATIVE) @@ -92,12 +80,21 @@ abstract class KotlinNpmInstallTask : } ) - @get:OutputFile + @get:OutputFiles + val additionalFiles: FileCollection by lazy { + nodeJs.packageManagerExtension.get().additionalInstallOutput + } + + @Deprecated( + "This property is deprecated and will be removed in future. Use additionalFiles instead", + replaceWith = ReplaceWith("additionalFiles") + ) + @get:Internal val yarnLockFile: Provider = nodeJs.rootPackageDirectory.map { it.file("yarn.lock") } @Deprecated( - "This property is deprecated and will be removed in future. Use yarnLockFile instead", - replaceWith = ReplaceWith("yarnLockFile") + "This property is deprecated and will be removed in future. Use additionalFiles instead", + replaceWith = ReplaceWith("additionalFiles") ) @get:Internal val yarnLock: File @@ -115,8 +112,8 @@ abstract class KotlinNpmInstallTask : args = args, services = services, logger = logger, - npmEnvironment, - yarnEnv + nodsJsEnvironment, + packageManagerEnv ) ?: throw (npmResolutionManager.get().state as KotlinNpmResolutionManager.ResolutionState.Error).wrappedException } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/tasks/RootPackageJsonTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/tasks/RootPackageJsonTask.kt index b63160f0e57..3e3e8170023 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/tasks/RootPackageJsonTask.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/npm/tasks/RootPackageJsonTask.kt @@ -16,10 +16,8 @@ import org.gradle.work.NormalizeLineEndings import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNodeJsExtension import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject import org.jetbrains.kotlin.gradle.targets.js.npm.UsesKotlinNpmResolutionManager -import org.jetbrains.kotlin.gradle.targets.js.npm.asNpmEnvironment -import org.jetbrains.kotlin.gradle.targets.js.npm.asYarnEnvironment +import org.jetbrains.kotlin.gradle.targets.js.npm.asNodeJsEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.KotlinRootNpmResolver -import org.jetbrains.kotlin.gradle.targets.js.yarn.yarn import org.jetbrains.kotlin.gradle.utils.getFile import java.io.File @@ -37,9 +35,6 @@ abstract class RootPackageJsonTask : private val nodeJs get() = project.rootProject.kotlinNodeJsExtension - private val yarn - get() = project.rootProject.yarn - private val rootResolver: KotlinRootNpmResolver get() = nodeJs.resolver @@ -48,12 +43,12 @@ abstract class RootPackageJsonTask : // ----- - private val npmEnvironment by lazy { - nodeJs.requireConfigured().asNpmEnvironment + private val nodeJsEnvironment by lazy { + nodeJs.requireConfigured().asNodeJsEnvironment } - private val yarnEnv by lazy { - yarn.requireConfigured().asYarnEnvironment + private val packageManagerEnv by lazy { + nodeJs.packageManagerExtension.get().environment } @get:OutputFile @@ -87,7 +82,7 @@ abstract class RootPackageJsonTask : @TaskAction fun resolve() { - npmResolutionManager.get().prepare(logger, npmEnvironment, yarnEnv) + npmResolutionManager.get().prepare(logger, nodeJsEnvironment, packageManagerEnv) } companion object { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt index 648b365fef7..cf2f29b8d98 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/utils.kt @@ -46,7 +46,7 @@ fun FileHasher.calculateDirHash( dir.walk() .forEach { file -> hasher.putString(file.toRelativeString(dir)) - if (file.isFile && !Files.isSymbolicLink(file.toPath())) { + if (file.isFile) { if (!Files.isSymbolicLink(file.toPath())) { hasher.putHash(hash(file)) } else { diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/LockCopyTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/LockCopyTask.kt new file mode 100644 index 00000000000..5ae40df1a02 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/LockCopyTask.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.yarn + +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.Internal +import org.gradle.work.DisableCachingByDefault +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask +import org.jetbrains.kotlin.gradle.targets.js.npm.LockFileMismatchReport +import org.jetbrains.kotlin.gradle.targets.js.npm.LockStoreTask + +@DisableCachingByDefault +abstract class YarnLockCopyTask : LockCopyTask() + +@DisableCachingByDefault +abstract class YarnLockStoreTask : LockStoreTask() { + @Internal + val yarnLockMismatchReport: Provider = lockFileMismatchReport.map { it.fromLockFileMismatchReport() } + + @Internal + val reportNewYarnLock: Provider = reportNewLockFile + + @Internal + val yarnLockAutoReplace: Provider = lockFileAutoReplace +} + +enum class YarnLockMismatchReport { + NONE, + WARNING, + FAIL; + + fun toLockFileMismatchReport(): LockFileMismatchReport = + when(this) { + NONE -> LockFileMismatchReport.NONE + WARNING -> LockFileMismatchReport.WARNING + FAIL -> LockFileMismatchReport.FAIL + } +} + +fun LockFileMismatchReport.fromLockFileMismatchReport(): YarnLockMismatchReport = + when(this) { + LockFileMismatchReport.NONE -> YarnLockMismatchReport.NONE + LockFileMismatchReport.WARNING -> YarnLockMismatchReport.WARNING + LockFileMismatchReport.FAIL -> YarnLockMismatchReport.FAIL + } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt index 8d2470bf090..c461ca3d52d 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/Yarn.kt @@ -7,39 +7,36 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn import org.gradle.api.logging.Logger import org.gradle.internal.service.ServiceRegistry -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment -import org.jetbrains.kotlin.gradle.targets.js.npm.YarnEnvironment +import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApiExecution +import org.jetbrains.kotlin.gradle.targets.js.npm.NodeJsEnvironment import org.jetbrains.kotlin.gradle.targets.js.npm.resolved.PreparedKotlinCompilationNpmResolution import java.io.File -class Yarn : NpmApi { +class Yarn : NpmApiExecution { private val yarnWorkspaces = YarnWorkspaces() - override fun preparedFiles(nodeJs: NpmEnvironment): Collection = + override fun preparedFiles(nodeJs: NodeJsEnvironment): Collection = yarnWorkspaces.preparedFiles(nodeJs) override fun prepareRootProject( - nodeJs: NpmEnvironment, + nodeJs: NodeJsEnvironment, + yarn: YarnEnvironment, rootProjectName: String, rootProjectVersion: String, - logger: Logger, subProjects: Collection, - resolutions: Map, ) = yarnWorkspaces .prepareRootProject( nodeJs, + yarn, rootProjectName, rootProjectVersion, - logger, subProjects, - resolutions, ) override fun resolveRootProject( services: ServiceRegistry, logger: Logger, - nodeJs: NpmEnvironment, + nodeJs: NodeJsEnvironment, yarn: YarnEnvironment, npmProjects: Collection, cliArgs: List diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnBasics.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnBasics.kt index 1a75abeceaa..5fddccf0b7a 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnBasics.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnBasics.kt @@ -8,17 +8,16 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn import org.gradle.api.logging.Logger import org.gradle.internal.service.ServiceRegistry import org.jetbrains.kotlin.gradle.internal.execWithProgress -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi -import org.jetbrains.kotlin.gradle.targets.js.npm.NpmEnvironment -import org.jetbrains.kotlin.gradle.targets.js.npm.YarnEnvironment +import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApiExecution +import org.jetbrains.kotlin.gradle.targets.js.npm.NodeJsEnvironment import java.io.File -abstract class YarnBasics : NpmApi { +abstract class YarnBasics : NpmApiExecution { fun yarnExec( services: ServiceRegistry, logger: Logger, - nodeJs: NpmEnvironment, + nodeJs: NodeJsEnvironment, yarn: YarnEnvironment, dir: File, description: String, @@ -35,11 +34,7 @@ abstract class YarnBasics : NpmApi { val nodeExecutable = nodeJs.nodeExecutable if (!yarn.ignoreScripts) { - val nodePath = if (nodeJs.isWindows) { - File(nodeExecutable).parent - } else { - nodeExecutable - } + val nodePath = File(nodeExecutable).parent exec.environment( "PATH", "$nodePath${File.pathSeparator}${System.getenv("PATH")}" diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEntryRegistry.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEntryRegistry.kt deleted file mode 100644 index 16fab9ebd21..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEntryRegistry.kt +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.gradle.targets.js.yarn - -import org.gradle.api.GradleException -import org.jetbrains.kotlin.gradle.targets.js.npm.FILE_VERSION_PREFIX -import org.jetbrains.kotlin.gradle.targets.js.npm.includedRange -import org.jetbrains.kotlin.gradle.targets.js.npm.intersect -import java.io.File - -private const val SEPARATOR = "@" - -private const val FILE_MARKER = "${SEPARATOR}$FILE_VERSION_PREFIX" -private const val GITHUB_MARKER = "${SEPARATOR}github:" \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnv.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnv.kt index 9206660dd76..dd20fa38bb7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnv.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnv.kt @@ -16,10 +16,12 @@ data class YarnEnv( override val dir: File, val executable: String, override val ivyDependency: String, - val standalone: Boolean, val ignoreScripts: Boolean, val yarnLockMismatchReport: YarnLockMismatchReport, val reportNewYarnLock: Boolean, val yarnLockAutoReplace: Boolean, val yarnResolutions: List -) : AbstractEnv +) : AbstractEnv { + val standalone: Boolean + get() = !download +} diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnvironment.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnvironment.kt new file mode 100644 index 00000000000..1bc881bd9ae --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnEnvironment.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. + * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.gradle.targets.js.yarn + +import org.jetbrains.kotlin.gradle.targets.js.nodejs.PackageManagerEnvironment + +data class YarnEnvironment( + val executable: String, + val standalone: Boolean, + val ignoreScripts: Boolean, + val yarnResolutions: List +) : PackageManagerEnvironment + +internal val YarnEnv.asYarnEnvironment + get() = YarnEnvironment( + executable, + standalone, + ignoreScripts, + yarnResolutions + ) \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnLockCopyTask.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnLockCopyTask.kt deleted file mode 100644 index 512c9597d6c..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnLockCopyTask.kt +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors. - * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. - */ - -package org.jetbrains.kotlin.gradle.targets.js.yarn - -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.file.FileSystemOperations -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.provider.Property -import org.gradle.api.provider.Provider -import org.gradle.api.tasks.* -import org.gradle.work.DisableCachingByDefault -import org.gradle.work.NormalizeLineEndings -import org.jetbrains.kotlin.gradle.utils.contentEquals -import java.io.File -import javax.inject.Inject - -@DisableCachingByDefault -abstract class YarnLockCopyTask : DefaultTask() { - - @get:NormalizeLineEndings - @get:InputFile - @get:PathSensitive(PathSensitivity.RELATIVE) - abstract val inputFile: RegularFileProperty - - @get:Internal - abstract val outputDirectory: DirectoryProperty - - @get:Internal - abstract val fileName: Property - - @get:OutputFile - val outputFile: Provider - get() = outputDirectory.map { regularFile -> - regularFile.asFile.resolve(fileName.get()) - } - - @get:Inject - abstract val fs: FileSystemOperations - - @TaskAction - open fun copy() { - fs.copy { copy -> - copy.from(inputFile) - copy.rename { fileName.get() } - copy.into(outputDirectory) - } - } - - companion object { - val STORE_YARN_LOCK_NAME = "kotlinStoreYarnLock" - val RESTORE_YARN_LOCK_NAME = "kotlinRestoreYarnLock" - val UPGRADE_YARN_LOCK = "kotlinUpgradeYarnLock" - val YARN_LOCK_MISMATCH_MESSAGE = "yarn.lock was changed. Run the `${YarnLockCopyTask.UPGRADE_YARN_LOCK}` task to actualize yarn.lock file" - } -} - -@DisableCachingByDefault -abstract class YarnLockStoreTask : YarnLockCopyTask() { - @Input - lateinit var yarnLockMismatchReport: Provider - - @Input - lateinit var reportNewYarnLock: Provider - - @Input - lateinit var yarnLockAutoReplace: Provider - - override fun copy() { - val outputFile = outputDirectory.get().asFile.resolve(fileName.get()) - - val shouldReportMismatch = if (!outputFile.exists()) { - reportNewYarnLock.get() - } else { - yarnLockMismatchReport.get() != YarnLockMismatchReport.NONE && !contentEquals(inputFile.get().asFile, outputFile) - } - - if (!outputFile.exists() || yarnLockAutoReplace.get()) { - super.copy() - } - - if (shouldReportMismatch) { - when (yarnLockMismatchReport.get()) { - YarnLockMismatchReport.NONE -> {} - YarnLockMismatchReport.WARNING -> { - logger.warn(YARN_LOCK_MISMATCH_MESSAGE) - } - YarnLockMismatchReport.FAIL -> throw GradleException(YARN_LOCK_MISMATCH_MESSAGE) - else -> error("Unknown yarn.lock mismatch report kind") - } - } - } -} - -enum class YarnLockMismatchReport { - NONE, - WARNING, - FAIL, -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt index dfd9e970ff5..75af8d65620 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnPlugin.kt @@ -7,22 +7,14 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn import org.gradle.api.Plugin import org.gradle.api.Project -import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.targets.js.MultiplePluginDeclarationDetector import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNodeJsExtension -import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin.Companion.kotlinNpmResolutionManager -import org.jetbrains.kotlin.gradle.targets.js.npm.RequiresNpmDependencies -import org.jetbrains.kotlin.gradle.targets.js.npm.resolver.implementing +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.KotlinNpmInstallTask -import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.RESTORE_YARN_LOCK_NAME -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.STORE_YARN_LOCK_NAME -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnLockCopyTask.Companion.UPGRADE_YARN_LOCK import org.jetbrains.kotlin.gradle.tasks.CleanDataTask import org.jetbrains.kotlin.gradle.tasks.registerTask import org.jetbrains.kotlin.gradle.utils.detachedResolvable -import org.jetbrains.kotlin.gradle.utils.onlyIfCompat open class YarnPlugin : Plugin { override fun apply(project: Project): Unit = project.run { @@ -40,6 +32,10 @@ open class YarnPlugin : Plugin { yarnRootExtension.platform.value(nodeJs.platform) .disallowChanges() + nodeJs.packageManagerExtension.set( + yarnRootExtension + ) + val setupTask = registerTask(YarnSetupTask.NAME) { it.dependsOn(nodeJsTaskProviders.nodeJsSetupTaskProvider) @@ -52,110 +48,64 @@ open class YarnPlugin : Plugin { } } - val kotlinNpmResolutionManager = project.kotlinNpmResolutionManager - - val rootPackageJson = tasks.register(RootPackageJsonTask.NAME, RootPackageJsonTask::class.java) { task -> - task.dependsOn(nodeJsTaskProviders.npmCachesSetupTaskProvider) - task.group = NodeJsRootPlugin.TASKS_GROUP_NAME - task.description = "Create root package.json" - - task.npmResolutionManager.value(kotlinNpmResolutionManager) - .disallowChanges() - - task.onlyIfCompat("Prepare NPM project only in configuring state") { - it as RootPackageJsonTask - it.npmResolutionManager.get().isConfiguringState() - } - } - - configureRequiresNpmDependencies(project, rootPackageJson) - val kotlinNpmInstall = tasks.named(KotlinNpmInstallTask.NAME) kotlinNpmInstall.configure { - it.dependsOn(rootPackageJson) it.dependsOn(setupTask) - it.inputs.property("ignoreScripts", { yarnRootExtension.ignoreScripts }) + it.inputs.property("yarnIgnoreScripts", { yarnRootExtension.ignoreScripts }) } + yarnRootExtension.nodeJsEnvironment.value( + project.provider { + nodeJs.requireConfigured() + } + ).disallowChanges() + tasks.register("yarn" + CleanDataTask.NAME_SUFFIX, CleanDataTask::class.java) { it.cleanableStoreProvider = provider { yarnRootExtension.requireConfigured().cleanableStore } it.description = "Clean unused local yarn version" } - val packageJsonUmbrella = nodeJsTaskProviders - .packageJsonUmbrellaTaskProvider - - yarnRootExtension.rootPackageJsonTaskProvider.configure { - it.dependsOn(packageJsonUmbrella) - } - tasks.register(STORE_YARN_LOCK_NAME, YarnLockStoreTask::class.java) { task -> task.dependsOn(kotlinNpmInstall) - task.inputFile.set(nodeJs.rootPackageDirectory.map { it.file("yarn.lock") }) + task.inputFile.set(nodeJs.rootPackageDirectory.map { it.file(LockCopyTask.YARN_LOCK) }) task.outputDirectory.set(yarnRootExtension.lockFileDirectory) task.fileName.set(yarnRootExtension.lockFileName) - task.yarnLockMismatchReport = provider { yarnRootExtension.requireConfigured().yarnLockMismatchReport } - task.reportNewYarnLock = provider { yarnRootExtension.requireConfigured().reportNewYarnLock } - task.yarnLockAutoReplace = provider { yarnRootExtension.requireConfigured().yarnLockAutoReplace } + task.lockFileMismatchReport.value( + provider { yarnRootExtension.requireConfigured().yarnLockMismatchReport.toLockFileMismatchReport() } + ).disallowChanges() + task.reportNewLockFile.value( + provider { yarnRootExtension.requireConfigured().reportNewYarnLock } + ).disallowChanges() + task.lockFileAutoReplace.value( + provider { yarnRootExtension.requireConfigured().yarnLockAutoReplace } + ).disallowChanges() } tasks.register(UPGRADE_YARN_LOCK, YarnLockCopyTask::class.java) { task -> task.dependsOn(kotlinNpmInstall) - task.inputFile.set(nodeJs.rootPackageDirectory.map { it.file("yarn.lock") }) + task.inputFile.set(nodeJs.rootPackageDirectory.map { it.file(LockCopyTask.YARN_LOCK) }) task.outputDirectory.set(yarnRootExtension.lockFileDirectory) task.fileName.set(yarnRootExtension.lockFileName) } - val restoreYarnLock = tasks.register(RESTORE_YARN_LOCK_NAME, YarnLockCopyTask::class.java) { + tasks.register(RESTORE_YARN_LOCK_NAME, YarnLockCopyTask::class.java) { val lockFile = yarnRootExtension.lockFileDirectory.resolve(yarnRootExtension.lockFileName) it.inputFile.set(yarnRootExtension.lockFileDirectory.resolve(yarnRootExtension.lockFileName)) it.outputDirectory.set(nodeJs.rootPackageDirectory) - it.fileName.set("yarn.lock") + it.fileName.set(LockCopyTask.YARN_LOCK) it.onlyIf { lockFile.exists() } } - kotlinNpmInstall.configure { - it.dependsOn(restoreYarnLock) - } - } + yarnRootExtension.preInstallTasks.value( + listOf(yarnRootExtension.restoreYarnLockTaskProvider) + ).disallowChanges() - // Yes, we need to break Task Configuration Avoidance here - // In case when we need to create package.json's files and execute kotlinNpmInstall, - // We need to configure all RequiresNpmDependencies tasks to install them, - // Because we need to persist yarn.lock - // We execute this block in configure phase of rootPackageJson to be sure, - // That Task Configuration Avoidance will not be broken for tasks not related with NPM installing - // https://youtrack.jetbrains.com/issue/KT-48241 - private fun configureRequiresNpmDependencies( - project: Project, - rootPackageJson: TaskProvider, - ) { - val fn: (Project) -> Unit = { - it.tasks.implementing(RequiresNpmDependencies::class) - .forEach {} - } - rootPackageJson.configure { - project.allprojects - .forEach { project -> - if (it.state.executed) { - fn(project) - } - } - } - - project.allprojects - .forEach { - if (!it.state.executed) { - it.afterEvaluate { project -> - rootPackageJson.configure { - fn(project) - } - } - } - } + yarnRootExtension.postInstallTasks.value( + listOf(yarnRootExtension.storeYarnLockTaskProvider) + ).disallowChanges() } companion object { @@ -164,5 +114,9 @@ open class YarnPlugin : Plugin { rootProject.plugins.apply(YarnPlugin::class.java) return rootProject.extensions.getByName(YarnRootExtension.YARN) as YarnRootExtension } + + const val STORE_YARN_LOCK_NAME = "kotlinStoreYarnLock" + const val RESTORE_YARN_LOCK_NAME = "kotlinRestoreYarnLock" + const val UPGRADE_YARN_LOCK = "kotlinUpgradeYarnLock" } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt index 0899872c730..1ca3dc9f042 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnRootExtension.kt @@ -7,17 +7,24 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn import org.gradle.api.Action import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.file.FileCollection +import org.gradle.api.provider.ListProperty import org.gradle.api.tasks.TaskProvider import org.jetbrains.kotlin.gradle.logging.kotlinInfo import org.jetbrains.kotlin.gradle.targets.js.AbstractSettings +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsEnv +import org.jetbrains.kotlin.gradle.targets.js.nodejs.NpmApiExtension import org.jetbrains.kotlin.gradle.targets.js.nodejs.Platform -import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask +import org.jetbrains.kotlin.gradle.targets.js.npm.LockCopyTask +import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin.Companion.RESTORE_YARN_LOCK_NAME +import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin.Companion.STORE_YARN_LOCK_NAME import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore import java.io.File open class YarnRootExtension( val project: Project -) : AbstractSettings() { +) : AbstractSettings(), NpmApiExtension { init { check(project == project.rootProject) } @@ -26,6 +33,24 @@ open class YarnRootExtension( project.logger.kotlinInfo("Storing cached files in $it") } + override val packageManager: Yarn by lazy { + Yarn() + } + + override val environment: YarnEnvironment by lazy { + requireConfigured().asYarnEnvironment + } + + override val additionalInstallOutput: FileCollection = project.objects.fileCollection().from( + { + nodeJsEnvironment.get().rootPackageDir.resolve(LockCopyTask.YARN_LOCK) + } + ) + + override val preInstallTasks: ListProperty> = project.objects.listProperty(TaskProvider::class.java) + + override val postInstallTasks: ListProperty> = project.objects.listProperty(TaskProvider::class.java) + override var installationDir by Property(gradleHome.resolve("yarn")) override var downloadBaseUrl: String? by Property("https://github.com/yarnpkg/yarn/releases/download") @@ -34,8 +59,8 @@ open class YarnRootExtension( override var command by Property("yarn") override var download by Property(true) - var lockFileName by Property("yarn.lock") - var lockFileDirectory: File by Property(project.rootDir.resolve("kotlin-js-store")) + var lockFileName by Property(LockCopyTask.YARN_LOCK) + var lockFileDirectory: File by Property(project.rootDir.resolve(LockCopyTask.KOTLIN_JS_STORE)) var ignoreScripts by Property(true) @@ -50,11 +75,6 @@ open class YarnRootExtension( .withType(YarnSetupTask::class.java) .named(YarnSetupTask.NAME) - val rootPackageJsonTaskProvider: TaskProvider - get() = project.tasks - .withType(RootPackageJsonTask::class.java) - .named(RootPackageJsonTask.NAME) - internal val platform: org.gradle.api.provider.Property = project.objects.property(Platform::class.java) var resolutions: MutableList by Property(mutableListOf()) @@ -72,6 +92,8 @@ open class YarnRootExtension( }) } + internal val nodeJsEnvironment: org.gradle.api.provider.Property = project.objects.property(NodeJsEnv::class.java) + override fun finalizeConfiguration(): YarnEnv { val cleanableStore = CleanableStore[installationDir.path] @@ -93,7 +115,6 @@ open class YarnRootExtension( cleanableStore = cleanableStore, dir = home, executable = getExecutable("yarn", command, "cmd"), - standalone = !download, ivyDependency = "com.yarnpkg:yarn:$version@tar.gz", ignoreScripts = ignoreScripts, yarnLockMismatchReport = yarnLockMismatchReport, @@ -103,6 +124,12 @@ open class YarnRootExtension( ) } + val restoreYarnLockTaskProvider: TaskProvider + get() = project.tasks.withType(YarnLockCopyTask::class.java).named(RESTORE_YARN_LOCK_NAME) + + val storeYarnLockTaskProvider: TaskProvider + get() = project.tasks.withType(YarnLockStoreTask::class.java).named(STORE_YARN_LOCK_NAME) + companion object { const val YARN: String = "kotlinYarn" diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnWorkspaces.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnWorkspaces.kt index dbd6e17215a..7edba137471 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnWorkspaces.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/yarn/YarnWorkspaces.kt @@ -13,7 +13,7 @@ import org.jetbrains.kotlin.gradle.utils.getFile import java.io.File class YarnWorkspaces : YarnBasics() { - override fun preparedFiles(nodeJs: NpmEnvironment): Collection { + override fun preparedFiles(nodeJs: NodeJsEnvironment): Collection { return listOf( nodeJs .rootPackageDir @@ -22,28 +22,26 @@ class YarnWorkspaces : YarnBasics() { } override fun prepareRootProject( - nodeJs: NpmEnvironment, + nodeJs: NodeJsEnvironment, + yarn: YarnEnvironment, rootProjectName: String, rootProjectVersion: String, - logger: Logger, subProjects: Collection, - resolutions: Map, ) { return prepareRootPackageJson( nodeJs, rootProjectName, rootProjectVersion, - logger, subProjects, - resolutions + yarn.yarnResolutions + .associate { it.path to it.toVersionString() }, ) } private fun prepareRootPackageJson( - nodeJs: NpmEnvironment, + nodeJs: NodeJsEnvironment, rootProjectName: String, rootProjectVersion: String, - logger: Logger, npmProjects: Collection, resolutions: Map ) { @@ -52,7 +50,6 @@ class YarnWorkspaces : YarnBasics() { saveRootProjectWorkspacesPackageJson( rootProjectName, rootProjectVersion, - logger, npmProjects, resolutions, rootPackageJsonFile @@ -62,7 +59,7 @@ class YarnWorkspaces : YarnBasics() { override fun resolveRootProject( services: ServiceRegistry, logger: Logger, - nodeJs: NpmEnvironment, + nodeJs: NodeJsEnvironment, yarn: YarnEnvironment, npmProjects: Collection, cliArgs: List @@ -75,7 +72,7 @@ class YarnWorkspaces : YarnBasics() { nodeJs, yarn, nodeJsWorldDir, - NpmApi.resolveOperationDescription("yarn"), + NpmApiExecution.resolveOperationDescription("yarn"), cliArgs ) } @@ -83,7 +80,6 @@ class YarnWorkspaces : YarnBasics() { private fun saveRootProjectWorkspacesPackageJson( rootProjectName: String, rootProjectVersion: String, - logger: Logger, npmProjects: Collection, resolutions: Map, rootPackageJsonFile: File @@ -94,10 +90,10 @@ class YarnWorkspaces : YarnBasics() { val npmProjectWorkspaces = npmProjects.map { it.npmProjectDir.getFile().relativeTo(nodeJsWorldDir).path } val importedProjectWorkspaces = - YarnImportedPackagesVersionResolver(logger, npmProjects, nodeJsWorldDir).resolveAndUpdatePackages() + NpmImportedPackagesVersionResolver(npmProjects, nodeJsWorldDir).resolveAndUpdatePackages() rootPackageJson.workspaces = npmProjectWorkspaces + importedProjectWorkspaces - rootPackageJson.resolutions = resolutions + rootPackageJson.customField("resolutions", resolutions) rootPackageJson.saveTo( rootPackageJsonFile )