From d1b775f1576b604413715ccc77b274919e10ed2f Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Mon, 5 Jun 2023 15:21:34 +0000 Subject: [PATCH] [Gradle, JS] Fix uname system call for configuration cache compatibility [Gradle, JS] Fix problem with configuration cache in KotlinKarma ^KT-58969 fixed ^KT-58970 fixed --- .../gradle/JsSetupConfigurationCacheIT.kt | 50 +++++ .../gradle/plugin/KotlinPluginWrapper.kt | 7 + .../targets/js/nodejs/NodeJsRootExtension.kt | 18 +- .../targets/js/nodejs/NodeJsRootPlugin.kt | 29 ++- .../gradle/targets/js/nodejs/Platform.kt | 76 +++++++ .../targets/js/nodejs/PlatformHelper.kt | 91 -------- .../gradle/targets/js/nodejs/UnameExecutor.kt | 37 ++++ .../targets/js/testing/karma/KotlinKarma.kt | 4 +- .../gradle/targets/js/yarn/YarnPlugin.kt | 3 + .../targets/js/yarn/YarnRootExtension.kt | 9 +- .../kotlin/gradle/plugin/PluginWrappers.kt | 3 + .../plugin/internal/UnameExecutorG70.kt | 35 +++ .../kotlin/gradle/plugin/PluginWrappers.kt | 3 + .../plugin/internal/UnameExecutorG71.kt | 35 +++ .../kotlin/gradle/plugin/PluginWrappers.kt | 3 + .../plugin/internal/UnameExecutorG74.kt | 35 +++ .../kotlin/gradle/plugin/PluginWrappers.kt | 3 + .../gradle/plugin/internal/UnameExecutorG6.kt | 35 +++ license/README.md | 4 + .../gradle-node-plugin_LICENSE.txt | 201 ++++++++++++++++++ 20 files changed, 571 insertions(+), 110 deletions(-) create mode 100644 libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsSetupConfigurationCacheIT.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/Platform.kt delete mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/PlatformHelper.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/UnameExecutor.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/gradle70/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG70.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/gradle71/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG71.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/gradle74/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG74.kt create mode 100644 libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG6.kt create mode 100644 license/third_party/gradle-node-plugin_LICENSE.txt 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 new file mode 100644 index 00000000000..2794c7663e8 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/JsSetupConfigurationCacheIT.kt @@ -0,0 +1,50 @@ +/* + * 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 + +import org.gradle.util.GradleVersion +import org.jetbrains.kotlin.gradle.plugin.KotlinJsCompilerType +import org.jetbrains.kotlin.gradle.testbase.* +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.condition.OS + +// This test is a JS test +// but, as it is the only one to required to run on MacOS +// we are running this test as a part of Native tests +// This allows us to keep CI configuration simpler. +@OsCondition(supportedOn = [OS.LINUX, OS.MAC, OS.WINDOWS], enabledOnCI = [OS.LINUX, OS.MAC, OS.WINDOWS]) +@NativeGradlePluginTests +class JsSetupConfigurationCacheIT : KGPBaseTest() { + @Suppress("DEPRECATION") + private val defaultJsOptions = BuildOptions.JsOptions( + useIrBackend = true, + jsCompilerType = KotlinJsCompilerType.IR + ) + + override val defaultBuildOptions = + super.defaultBuildOptions.copy( + jsOptions = defaultJsOptions, + configurationCache = true, + configurationCacheProblems = BaseGradleIT.ConfigurationCacheProblems.FAIL + ) + + // hack to be run on Mac m* + @DisplayName("Check Node.JS setup on different platforms") + @GradleTest + fun checkNodeJsSetup(gradleVersion: GradleVersion) { + project("kotlin-js-browser-project", gradleVersion) { + build("kotlinUpgradeYarnLock") { + assertTasksExecuted(":kotlinUpgradeYarnLock") + assertConfigurationCacheStored() + } + + build("kotlinUpgradeYarnLock") { + assertTasksUpToDate(":kotlinUpgradeYarnLock") + assertConfigurationCacheReused() + } + } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt index d601f1c2087..6f688aee5f4 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginWrapper.kt @@ -45,6 +45,8 @@ import org.jetbrains.kotlin.gradle.report.BuildMetricsService import org.jetbrains.kotlin.gradle.plugin.statistics.BuildFlowService import org.jetbrains.kotlin.gradle.targets.js.KotlinJsCompilerAttribute import org.jetbrains.kotlin.gradle.targets.js.KotlinJsPlugin +import org.jetbrains.kotlin.gradle.targets.js.nodejs.DefaultUnameExecutorVariantFactory +import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor import org.jetbrains.kotlin.gradle.targets.js.npm.addNpmDependencyExtension import org.jetbrains.kotlin.gradle.targets.metadata.isKotlinGranularMetadataEnabled import org.jetbrains.kotlin.gradle.targets.native.internal.CInteropKlibLibraryElements @@ -185,6 +187,11 @@ abstract class DefaultKotlinBasePlugin : KotlinBasePlugin { CompatibilityConventionRegistrar.Factory::class, DefaultCompatibilityConventionRegistrar.Factory() ) + + factories.putIfAbsent( + UnameExecutor.UnameExecutorVariantFactory::class, + DefaultUnameExecutorVariantFactory() + ) } protected fun setupAttributeMatchingStrategy( 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 8db0551fc1a..285b3150e72 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 @@ -21,10 +21,11 @@ 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.tasks.internal.CleanableStore +import org.jetbrains.kotlin.gradle.utils.property import java.io.File open class NodeJsRootExtension( - val project: Project + val project: Project, ) : ConfigurationPhaseAware() { init { @@ -81,17 +82,18 @@ open class NodeJsRootExtension( val nodeModulesGradleCacheDir: File get() = rootPackageDir.resolve("packages_imported") + internal val platform: org.gradle.api.provider.Property = project.objects.property() + val versions = NpmVersions() override fun finalizeConfiguration(): NodeJsEnv { - val platformHelper = PlatformHelper - val platform = platformHelper.osName - val architecture = platformHelper.osArch + val name = platform.get().name + val architecture = platform.get().arch - val nodeDirName = "node-v$nodeVersion-$platform-$architecture" + val nodeDirName = "node-v$nodeVersion-$name-$architecture" val cleanableStore = CleanableStore[installationDir.absolutePath] val nodeDir = cleanableStore[nodeDirName].use() - val isWindows = platformHelper.isWindows + val isWindows = platform.get().isWindows() val nodeBinDir = if (isWindows) nodeDir else nodeDir.resolve("bin") fun getExecutable(command: String, customCommand: String, windowsExtension: String): String { @@ -101,7 +103,7 @@ open class NodeJsRootExtension( fun getIvyDependency(): String { val type = if (isWindows) "zip" else "tar.gz" - return "org.nodejs:node:$nodeVersion:$platform-$architecture@$type" + return "org.nodejs:node:$nodeVersion:$name-$architecture@$type" } return NodeJsEnv( @@ -110,7 +112,7 @@ open class NodeJsRootExtension( nodeDir = nodeDir, nodeBinDir = nodeBinDir, nodeExecutable = getExecutable("node", nodeCommand, "exe"), - platformName = platform, + platformName = name, architectureName = architecture, ivyDependency = getIvyDependency(), downloadBaseUrl = nodeDownloadBaseUrl, 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 d81fa19341f..f0317320436 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 @@ -10,6 +10,10 @@ import org.gradle.api.Project import org.gradle.api.Task import org.gradle.api.plugins.BasePlugin import org.gradle.api.provider.Provider +import org.gradle.util.GradleVersion +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 @@ -23,11 +27,6 @@ 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.SingleActionPerProject -import org.jetbrains.kotlin.gradle.utils.castIsolatedKotlinPluginClassLoaderAware -import org.jetbrains.kotlin.gradle.utils.doNotTrackStateCompat -import org.jetbrains.kotlin.gradle.utils.markResolvable -import org.jetbrains.kotlin.gradle.utils.providerWithLazyConvention open class NodeJsRootPlugin : Plugin { override fun apply(project: Project) { @@ -45,6 +44,8 @@ open class NodeJsRootPlugin : Plugin { project ) + addPlatform(project, nodeJs) + val setupTask = project.registerTask(NodeJsSetupTask.NAME) { it.group = TASKS_GROUP_NAME it.description = "Download and install a local node/npm version" @@ -131,6 +132,24 @@ open class NodeJsRootPlugin : Plugin { } } + // from https://github.com/node-gradle/gradle-node-plugin + private fun addPlatform(project: Project, extension: NodeJsRootExtension) { + val uname = project.variantImplementationFactory() + .getInstance(project) + .unameExecResult + + extension.platform.value( + project.providers.systemProperty("os.name") + .usedAtConfigurationTime(project.configurationTimePropertiesAccessor) + .zip( + project.providers.systemProperty("os.arch") + .usedAtConfigurationTime(project.configurationTimePropertiesAccessor) + ) { name, arch -> + parsePlatform(name, arch, uname) + } + ).disallowChanges() + } + companion object { const val TASKS_GROUP_NAME: String = "nodeJs" diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/Platform.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/Platform.kt new file mode 100644 index 00000000000..e3aa22812e5 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/Platform.kt @@ -0,0 +1,76 @@ +// from https://github.com/node-gradle/gradle-node-plugin +package org.jetbrains.kotlin.gradle.targets.js.nodejs + +import org.gradle.api.provider.Provider +import java.io.File + +internal data class Platform( + val name: String, + val arch: String, +) { + fun isWindows(): Boolean { + return name == "win" + } +} + +internal fun parsePlatform(name: String, arch: String, uname: Provider): Platform { + return Platform( + parseOsName(name.toLowerCase()), + parseOsArch( + arch.toLowerCase(), + uname + ) + ) +} + +internal fun parseOsName(name: String): String { + return when { + name.contains("windows") -> "win" + name.contains("mac") -> "darwin" + name.contains("linux") -> "linux" + name.contains("freebsd") -> "linux" + name.contains("sunos") -> "sunos" + else -> error("Unsupported OS: $name") + } +} + +internal fun parseOsArch(arch: String, uname: Provider): String { + return when { + /* + * As Java just returns "arm" on all ARM variants, we need a system call to determine the exact arch. Unfortunately some JVMs say aarch32/64, so we need an additional + * conditional. Additionally, the node binaries for 'armv8l' are called 'arm64', so we need to distinguish here. + */ + arch == "arm" || arch.startsWith("aarch") -> uname.get() + .let { + if (it == "armv8l" || it == "aarch64") { + "arm64" + } else it + } + .let { + if (it == "x86_64") { + "x64" + } else it + } + arch == "ppc64le" -> "ppc64le" + arch == "s390x" -> "s390x" + arch.contains("64") -> "x64" + else -> "x86" + } +} + +internal fun computeNpmScriptFile( + nodeDirProvider: File, + command: String, + isWindows: Boolean +): String { + return nodeDirProvider.let { nodeDir -> + if (isWindows) nodeDir.resolve("node_modules/npm/bin/$command-cli.js").path + else nodeDir.resolve("lib/node_modules/npm/bin/$command-cli.js").path + } +} + +internal fun computeNodeBinDir( + nodeDirProvider: File, + isWindows: Boolean +) = + if (isWindows) nodeDirProvider else nodeDirProvider.resolve("bin") \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/PlatformHelper.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/PlatformHelper.kt deleted file mode 100644 index d4ac94810b7..00000000000 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/PlatformHelper.kt +++ /dev/null @@ -1,91 +0,0 @@ -package org.jetbrains.kotlin.gradle.targets.js.nodejs - -import java.io.File -import java.util.concurrent.TimeUnit - -object PlatformHelper { - val osName: String by lazy { - val name = property("os.name").toLowerCase() - when { - name.contains("windows") -> "win" - name.contains("mac") -> "darwin" - name.contains("linux") -> "linux" - name.contains("freebsd") -> "linux" - name.contains("sunos") -> "sunos" - else -> error("Unsupported OS: $name") - } - } - - - val osArch: String by lazy { - val arch = property("os.arch").toLowerCase() - when { - /* - * As Java just returns "arm" on all ARM variants, we need a system call to determine the exact arch. Unfortunately some JVMs say aarch32/64, so we need an additional - * conditional. Additionally, the node binaries for 'armv8l' are called 'arm64', so we need to distinguish here. - */ - arch == "arm" || arch.startsWith("aarch") -> property("uname") - .let { - if (it == "armv8l" || it == "aarch64") { - "arm64" - } else it - } - .let { - if (it == "x86_64") { - "x64" - } else it - } - arch == "ppc64le" -> "ppc64le" - arch == "s390x" -> "s390x" - arch.contains("64") -> "x64" - else -> "x86" - } - } - - - val isWindows: Boolean by lazy { osName == "win" } - - - private fun property(name: String): String { - return getSystemProperty(name) ?: - // Added so that we can test osArch on Windows and on non-arm systems - if (name == "uname") execute("uname", "-m") - else error("Unable to find a value for property [$name].") - } - - - private fun getSystemProperty(name: String): String? { - return System.getProperty(name); - } -} - -fun computeNpmScriptFile( - nodeDirProvider: File, - command: String, - isWindows: Boolean -): String { - return nodeDirProvider.let { nodeDir -> - if (isWindows) nodeDir.resolve("node_modules/npm/bin/$command-cli.js").path - else nodeDir.resolve("lib/node_modules/npm/bin/$command-cli.js").path - } -} - -fun computeNodeBinDir( - nodeDirProvider: File, - isWindows: Boolean -) = - if (isWindows) nodeDirProvider else nodeDirProvider.resolve("bin") - -/** - * Executes the given command and returns its output. - * - * This is based on an aggregate of the answers provided here: [https://stackoverflow.com/questions/35421699/how-to-invoke-external-command-from-within-kotlin-code] - */ -private fun execute(vararg args: String, timeout: Long = 60): String { - return ProcessBuilder(args.toList()) - .redirectOutput(ProcessBuilder.Redirect.PIPE) - .redirectError(ProcessBuilder.Redirect.PIPE) - .start() - .apply { waitFor(timeout, TimeUnit.SECONDS) } - .inputStream.bufferedReader().readText().trim() -} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/UnameExecutor.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/UnameExecutor.kt new file mode 100644 index 00000000000..69e8b297aa3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/UnameExecutor.kt @@ -0,0 +1,37 @@ +/* + * 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 org.gradle.api.Project +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.jetbrains.kotlin.gradle.plugin.VariantImplementationFactories + +internal interface UnameExecutor { + val unameExecResult: Provider + + interface UnameExecutorVariantFactory : VariantImplementationFactories.VariantImplementationFactory { + fun getInstance(project: Project): UnameExecutor + } +} + +internal class DefaultUnameExecutorVariantFactory : UnameExecutor.UnameExecutorVariantFactory { + override fun getInstance(project: Project): UnameExecutor = DefaultUnameExecutor(project.providers) +} + +internal class DefaultUnameExecutor( + private val providerFactory: ProviderFactory, +) : UnameExecutor { + override val unameExecResult: Provider + get() { + val cmd = providerFactory.exec { + it.executable = "uname" + it.args = listOf("-m") + } + + return cmd.standardOutput.asText.map { it.trim() } + } +} \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt index 641c134925d..a821a635639 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/testing/karma/KotlinKarma.kt @@ -53,6 +53,8 @@ class KotlinKarma( private val project: Project = compilation.target.project private val npmProject = compilation.npmProject + private val platformType = compilation.platformType + @Transient private val nodeJs = project.rootProject.kotlinNodeJsExtension private val nodeRootPackageDir by lazy { nodeJs.rootPackageDir } @@ -333,7 +335,7 @@ class KotlinKarma( config.files.add(npmProject.require("kotlin-test-js-runner/kotlin-test-karma-runner.js")) if (!debug) { - if (compilation.platformType == KotlinPlatformType.wasm) { + if (platformType == KotlinPlatformType.wasm) { val wasmFile = file.parentFile.resolve("${file.nameWithoutExtension}.wasm") val wasmFileString = wasmFile.normalize().absolutePath config.files.add( 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 5229b8d7e66..6df2288924d 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 @@ -39,6 +39,9 @@ open class YarnPlugin : Plugin { val nodeJs = this.kotlinNodeJsExtension val nodeJsTaskProviders = this.kotlinNodeJsExtension + yarnRootExtension.platform.value(nodeJs.platform) + .disallowChanges() + val setupTask = registerTask(YarnSetupTask.NAME) { it.dependsOn(nodeJsTaskProviders.nodeJsSetupTaskProvider) 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 bb4691e1eb2..ff872e0c4e7 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 @@ -5,15 +5,12 @@ package org.jetbrains.kotlin.gradle.targets.js.yarn -import org.jetbrains.kotlin.gradle.targets.js.nodejs.PlatformHelper import org.gradle.api.Action import org.gradle.api.Project 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.NodeJsRootPlugin -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.nodejs.Platform import org.jetbrains.kotlin.gradle.targets.js.npm.tasks.RootPackageJsonTask import org.jetbrains.kotlin.gradle.tasks.internal.CleanableStore import java.io.File @@ -58,6 +55,8 @@ open class YarnRootExtension( .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()) fun resolution(path: String, configure: Action) { @@ -76,7 +75,7 @@ open class YarnRootExtension( override fun finalizeConfiguration(): YarnEnv { val cleanableStore = CleanableStore[installationDir.path] - val isWindows = PlatformHelper.isWindows + val isWindows = platform.get().isWindows() val home = cleanableStore["yarn-v$version"].use() diff --git a/libraries/tools/kotlin-gradle-plugin/src/gradle70/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt b/libraries/tools/kotlin-gradle-plugin/src/gradle70/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt index e92a37d0fa2..af7d71ec3e6 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/gradle70/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/gradle70/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt @@ -15,6 +15,7 @@ import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry import org.jetbrains.kotlin.gradle.plugin.internal.* import org.jetbrains.kotlin.gradle.plugin.internal.BasePluginConfigurationG70 import org.jetbrains.kotlin.gradle.plugin.internal.JavaSourceSetsAccessorG70 +import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor import javax.inject.Inject private const val PLUGIN_VARIANT_NAME = "gradle70" @@ -163,4 +164,6 @@ private fun Project.registerVariantImplementations() { ProjectIsolationStartParameterAccessorG70.Factory() factories[CompatibilityConventionRegistrar.Factory::class] = CompatibilityConventionRegistrarG70.Factory() + factories[UnameExecutor.UnameExecutorVariantFactory::class] = + UnameExecutorG70.UnameExecutorVariantFactoryG70() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/gradle70/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG70.kt b/libraries/tools/kotlin-gradle-plugin/src/gradle70/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG70.kt new file mode 100644 index 00000000000..bfaecfc3f08 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/gradle70/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG70.kt @@ -0,0 +1,35 @@ +/* + * 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.plugin.internal + +import org.gradle.api.Project +import org.gradle.api.provider.Provider +import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor +import java.io.ByteArrayOutputStream + +internal class UnameExecutorG70( + private val project: Project, +) : UnameExecutor { + override val unameExecResult: Provider + get() { + return project.provider { + val out = ByteArrayOutputStream() + val cmd = project.exec { + it.executable = "uname" + it.args = listOf("-m") + it.standardOutput = out + } + + cmd.assertNormalExitValue() + out.toString().trim() + } + } + + internal class UnameExecutorVariantFactoryG70 : UnameExecutor.UnameExecutorVariantFactory { + override fun getInstance(project: Project): UnameExecutor = UnameExecutorG70(project) + } +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/gradle71/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt b/libraries/tools/kotlin-gradle-plugin/src/gradle71/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt index e1dfbfa1705..dc0172afaf7 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/gradle71/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/gradle71/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt @@ -15,6 +15,7 @@ import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry import org.jetbrains.kotlin.gradle.plugin.internal.* import org.jetbrains.kotlin.gradle.plugin.internal.ConfigurationTimePropertiesAccessorG71 import org.jetbrains.kotlin.gradle.plugin.internal.IdeaSyncDetectorG71 +import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor import javax.inject.Inject private const val PLUGIN_VARIANT_NAME = "gradle71" @@ -164,4 +165,6 @@ private fun Project.registerVariantImplementations() { ProjectIsolationStartParameterAccessorG71.Factory() factories[CompatibilityConventionRegistrar.Factory::class] = CompatibilityConventionRegistrarG71.Factory() + factories[UnameExecutor.UnameExecutorVariantFactory::class] = + UnameExecutorG71.UnameExecutorVariantFactoryG71() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/gradle71/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG71.kt b/libraries/tools/kotlin-gradle-plugin/src/gradle71/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG71.kt new file mode 100644 index 00000000000..7a5f0b757b3 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/gradle71/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG71.kt @@ -0,0 +1,35 @@ +/* + * 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.plugin.internal + +import org.gradle.api.Project +import org.gradle.api.provider.Provider +import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor +import java.io.ByteArrayOutputStream + +internal class UnameExecutorG71( + private val project: Project, +) : UnameExecutor { + override val unameExecResult: Provider + get() { + return project.provider { + val out = ByteArrayOutputStream() + val cmd = project.exec { + it.executable = "uname" + it.args = listOf("-m") + it.standardOutput = out + } + + cmd.assertNormalExitValue() + out.toString().trim() + } + } + + internal class UnameExecutorVariantFactoryG71 : UnameExecutor.UnameExecutorVariantFactory { + override fun getInstance(project: Project): UnameExecutor = UnameExecutorG71(project) + } +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/gradle74/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt b/libraries/tools/kotlin-gradle-plugin/src/gradle74/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt index 67913c76975..b534da38811 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/gradle74/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/gradle74/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt @@ -13,6 +13,7 @@ import org.gradle.api.file.SourceDirectorySet import org.gradle.api.model.ObjectFactory import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry import org.jetbrains.kotlin.gradle.plugin.internal.* +import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor import javax.inject.Inject private const val PLUGIN_VARIANT_NAME = "gradle74" @@ -153,4 +154,6 @@ private fun Project.registerVariantImplementations() { ProjectIsolationStartParameterAccessorG74.Factory() factories[CompatibilityConventionRegistrar.Factory::class] = CompatibilityConventionRegistrarG74.Factory() + factories[UnameExecutor.UnameExecutorVariantFactory::class] = + UnameExecutorG74.UnameExecutorVariantFactoryG74() } diff --git a/libraries/tools/kotlin-gradle-plugin/src/gradle74/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG74.kt b/libraries/tools/kotlin-gradle-plugin/src/gradle74/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG74.kt new file mode 100644 index 00000000000..ed21cbdb840 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/gradle74/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG74.kt @@ -0,0 +1,35 @@ +/* + * 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.plugin.internal + +import org.gradle.api.Project +import org.gradle.api.provider.Provider +import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor +import java.io.ByteArrayOutputStream + +internal class UnameExecutorG74( + private val project: Project, +) : UnameExecutor { + override val unameExecResult: Provider + get() { + return project.provider { + val out = ByteArrayOutputStream() + val cmd = project.exec { + it.executable = "uname" + it.args = listOf("-m") + it.standardOutput = out + } + + cmd.assertNormalExitValue() + out.toString().trim() + } + } + + internal class UnameExecutorVariantFactoryG74 : UnameExecutor.UnameExecutorVariantFactory { + override fun getInstance(project: Project): UnameExecutor = UnameExecutorG74(project) + } +} + diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt index b72e2592a80..ca04049fb66 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/PluginWrappers.kt @@ -14,6 +14,7 @@ import org.gradle.api.model.ObjectFactory import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry import org.jetbrains.kotlin.gradle.plugin.internal.* import org.jetbrains.kotlin.gradle.plugin.internal.JavaSourceSetsAccessorG6 +import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor import javax.inject.Inject private const val PLUGIN_VARIANT_NAME = "main" @@ -169,4 +170,6 @@ private fun Project.registerVariantImplementations() { ProjectIsolationStartParameterAccessorG6.Factory() factories[CompatibilityConventionRegistrar.Factory::class] = CompatibilityConventionRegistrarG6.Factory() + factories[UnameExecutor.UnameExecutorVariantFactory::class] = + UnameExecutorG6.UnameExecutorVariantFactoryG6() } \ No newline at end of file diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG6.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG6.kt new file mode 100644 index 00000000000..f536b476d20 --- /dev/null +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/plugin/internal/UnameExecutorG6.kt @@ -0,0 +1,35 @@ +/* + * 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.plugin.internal + +import org.gradle.api.Project +import org.gradle.api.provider.Provider +import org.jetbrains.kotlin.gradle.targets.js.nodejs.UnameExecutor +import java.io.ByteArrayOutputStream + +internal class UnameExecutorG6( + private val project: Project, +) : UnameExecutor { + override val unameExecResult: Provider + get() { + return project.provider { + val out = ByteArrayOutputStream() + val cmd = project.exec { + it.executable = "uname" + it.args = listOf("-m") + it.standardOutput = out + } + + cmd.assertNormalExitValue() + out.toString().trim() + } + } + + internal class UnameExecutorVariantFactoryG6 : UnameExecutor.UnameExecutorVariantFactory { + override fun getInstance(project: Project): UnameExecutor = UnameExecutorG6(project) + } +} + diff --git a/license/README.md b/license/README.md index 57715b9c906..60cabbb1556 100644 --- a/license/README.md +++ b/license/README.md @@ -233,6 +233,10 @@ any distributions of the tools or libraries: - License: Eclipse Public License v1.0 ([license/third_party/testdata/eclipse_license.txt][eclipse]) and Eclipse Distribution License - v1.0 ([license/third_party/testdata/eclipse_distribution_license.txt][eclipse-distribution]) - Origin: javax.persistence, Copyright (c) 2008, 2017 Sun Microsystems, Oracle Corporation. + + - Path: libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/targets/js/nodejs/Platform.kt + - License: Apache License 2.0 ([license/third_party/gradle-node-plugin_LICENSE.txt](third_party/gradle-node-plugin_LICENSE.txt)) + - Origin: Copyright (c) 2013 node-gradle/gradle-node-plugin - Path: libraries/tools/kotlin-test-js-runner/karma-kotlin-reporter.js - License: MIT ([license/third_party/karma_LICENSE.txt](third_party/karma_LICENSE.txt) diff --git a/license/third_party/gradle-node-plugin_LICENSE.txt b/license/third_party/gradle-node-plugin_LICENSE.txt new file mode 100644 index 00000000000..ad410e11302 --- /dev/null +++ b/license/third_party/gradle-node-plugin_LICENSE.txt @@ -0,0 +1,201 @@ +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file