Gradle, js, yarn: split into workspaces and simple implementations

#KT-31018
	#KT-31017
This commit is contained in:
Sergey Rostov
2019-05-17 11:16:37 +03:00
parent fab879bf33
commit 77dd806742
5 changed files with 188 additions and 137 deletions
@@ -6,141 +6,20 @@
package org.jetbrains.kotlin.gradle.targets.js.yarn
import org.gradle.api.Project
import org.jetbrains.kotlin.gradle.internal.execWithProgress
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.*
import java.io.File
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectPackage
object Yarn : NpmApi {
override fun setup(project: Project) {
YarnPlugin.apply(project).executeSetup()
}
private fun getDelegate(project: Project): NpmApi =
if (project.yarn.useWorkspaces) YarnWorkspaces
else YarnSimple
val NpmProject.packageJsonHashFile: File
get() = dir.resolve("package.json.hash")
override fun setup(project: Project) =
getDelegate(project.rootProject).setup(project)
fun yarnExec(
project: Project,
dir: File,
description: String,
vararg args: String
) {
val nodeJsEnv = NodeJsPlugin.apply(project).root.environment
val yarnEnv = YarnPlugin.apply(project).environment
override fun resolveProject(resolvedNpmProject: NpmProjectPackage) =
getDelegate(resolvedNpmProject.project).resolveProject(resolvedNpmProject)
project.execWithProgress(description) { exec ->
exec.executable = nodeJsEnv.nodeExecutable
exec.args = listOf(yarnEnv.home.resolve("bin/yarn.js").absolutePath) + args
exec.workingDir = dir
}
}
private fun yarnLockReadTransitiveDependencies(
nodeWorkDir: File,
srcDependenciesList: Collection<NpmDependency>
) {
val yarnLock = nodeWorkDir.resolve("yarn.lock")
if (yarnLock.isFile) {
val byKey = YarnLock.parse(yarnLock).entries.associateBy { it.key }
val visited = mutableSetOf<NpmDependency>()
fun resolveRecursively(src: NpmDependency): NpmDependency {
if (src in visited) return src
visited.add(src)
val key = YarnLock.key(src.key, src.version)
val deps = byKey[key]
if (deps != null) {
src.dependencies.addAll(deps.dependencies.map { dep ->
val scopedName = dep.scopedName
resolveRecursively(
NpmDependency(
src.project,
scopedName.scope,
scopedName.name,
dep.version ?: "*"
)
)
})
} else {
// todo: [WARN] cannot find $key in yarn.lock
}
return src
}
srcDependenciesList.forEach { src ->
resolveRecursively(src)
}
}
}
override fun resolveProject(resolvedNpmProject: NpmProjectPackage) {
val project = resolvedNpmProject.project
if (!project.yarn.useWorkspaces) {
YarnAvoidance(resolvedNpmProject.npmProject).updateIfNeeded {
yarnExec(project, resolvedNpmProject.npmProject.dir, NpmApi.resolveOperationDescription("yarn for ${project.path}"))
yarnLockReadTransitiveDependencies(resolvedNpmProject.npmProject.dir, resolvedNpmProject.npmDependencies)
}
}
}
override fun resolveRootProject(rootProject: Project, subProjects: MutableList<NpmProjectPackage>) {
check(rootProject == rootProject.rootProject)
if (rootProject.yarn.useWorkspaces) {
resolveWorkspaces(rootProject, subProjects)
}
}
private fun resolveWorkspaces(
rootProject: Project,
npmProjects: MutableList<NpmProjectPackage>
) {
val upToDateChecks = npmProjects.map {
YarnAvoidance(it.npmProject)
}
if (upToDateChecks.all { it.upToDate }) return
val nodeJsWorldDir = rootProject.nodeJs.root.rootPackageDir
saveRootProjectWorkspacesPackageJson(rootProject, npmProjects, nodeJsWorldDir)
yarnExec(rootProject, nodeJsWorldDir, NpmApi.resolveOperationDescription("yarn"))
yarnLockReadTransitiveDependencies(nodeJsWorldDir, npmProjects.flatMap { it.npmDependencies })
upToDateChecks.forEach {
it.commit()
}
}
private fun saveRootProjectWorkspacesPackageJson(
rootProject: Project,
npmProjects: MutableList<NpmProjectPackage>,
nodeJsWorldDir: File
) {
val rootPackageJson = PackageJson(rootProject.name, rootProject.version.toString())
rootPackageJson.private = true
val npmProjectWorkspaces = npmProjects.map { it.npmProject.dir.relativeTo(nodeJsWorldDir).path }
val importedProjectWorkspaces = npmProjects.flatMapTo(mutableSetOf()) {
it.gradleDependencies.externalModules.map { importedProject ->
importedProject.path.relativeTo(nodeJsWorldDir).path
}
}
rootPackageJson.workspaces = npmProjectWorkspaces + importedProjectWorkspaces
rootProject.nodeJs.packageJsonHandlers.forEach {
it(rootPackageJson)
}
rootPackageJson.saveTo(
nodeJsWorldDir.resolve(NpmProject.PACKAGE_JSON)
)
}
override fun resolveRootProject(rootProject: Project, subProjects: MutableList<NpmProjectPackage>) =
getDelegate(rootProject.project).resolveRootProject(rootProject, subProjects)
}
@@ -0,0 +1,78 @@
/*
* Copyright 2010-2019 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.Project
import org.jetbrains.kotlin.gradle.internal.execWithProgress
import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsPlugin
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmDependency
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import java.io.File
abstract class YarnBasics : NpmApi {
override fun setup(project: Project) {
YarnPlugin.apply(project).executeSetup()
}
fun yarnExec(
project: Project,
dir: File,
description: String,
vararg args: String
) {
val nodeJsEnv = NodeJsPlugin.apply(project).root.environment
val yarnEnv = YarnPlugin.apply(project).environment
project.execWithProgress(description) { exec ->
exec.executable = nodeJsEnv.nodeExecutable
exec.args = listOf(yarnEnv.home.resolve("bin/yarn.js").absolutePath) + args
exec.workingDir = dir
}
}
protected fun yarnLockReadTransitiveDependencies(
nodeWorkDir: File,
srcDependenciesList: Collection<NpmDependency>
) {
val yarnLock = nodeWorkDir.resolve("yarn.lock")
if (yarnLock.isFile) {
val byKey = YarnLock.parse(yarnLock).entries.associateBy { it.key }
val visited = mutableSetOf<NpmDependency>()
fun resolveRecursively(src: NpmDependency): NpmDependency {
if (src in visited) return src
visited.add(src)
val key = YarnLock.key(src.key, src.version)
val deps = byKey[key]
if (deps != null) {
src.dependencies.addAll(deps.dependencies.map { dep ->
val scopedName = dep.scopedName
resolveRecursively(
NpmDependency(
src.project,
scopedName.scope,
scopedName.name,
dep.version ?: "*"
)
)
})
} else {
// todo: [WARN] cannot find $key in yarn.lock
}
return src
}
srcDependenciesList.forEach { src ->
resolveRecursively(src)
}
}
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2019 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.Project
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectPackage
object YarnSimple : YarnBasics() {
override fun resolveProject(resolvedNpmProject: NpmProjectPackage) {
val project = resolvedNpmProject.project
YarnUpToDateCheck(resolvedNpmProject.npmProject).updateIfNeeded {
yarnExec(project, resolvedNpmProject.npmProject.dir, NpmApi.resolveOperationDescription("yarn for ${project.path}"))
yarnLockReadTransitiveDependencies(resolvedNpmProject.npmProject.dir, resolvedNpmProject.npmDependencies)
}
}
override fun resolveRootProject(rootProject: Project, subProjects: MutableList<NpmProjectPackage>) = Unit
}
@@ -10,16 +10,16 @@ import org.gradle.api.internal.project.ProjectInternal
import org.gradle.internal.hash.FileHasher
import org.jetbrains.kotlin.daemon.common.toHexString
import org.jetbrains.kotlin.gradle.internal.ensureParentDirsCreated
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinJsCompilation
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.npmProject
import org.jetbrains.kotlin.gradle.targets.js.yarn.Yarn.packageJsonHashFile
import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
import java.io.File
class YarnAvoidance(val npmProject: NpmProject) {
class YarnUpToDateCheck(val npmProject: NpmProject) {
val project: Project
get() = npmProject.project
private val NpmProject.packageJsonHashFile: File
get() = dir.resolve("package.json.hash")
private val prevHash by lazy {
val packageJsonHashFile = npmProject.packageJsonHashFile
if (packageJsonHashFile.exists()) packageJsonHashFile.readText() else null
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2019 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.Project
import org.jetbrains.kotlin.gradle.targets.js.nodejs.nodeJs
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmApi
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProject
import org.jetbrains.kotlin.gradle.targets.js.npm.NpmProjectPackage
import org.jetbrains.kotlin.gradle.targets.js.npm.PackageJson
import java.io.File
object YarnWorkspaces : YarnBasics() {
override fun resolveProject(resolvedNpmProject: NpmProjectPackage) = Unit
override fun resolveRootProject(rootProject: Project, subProjects: MutableList<NpmProjectPackage>) {
check(rootProject == rootProject.rootProject)
resolveWorkspaces(rootProject, subProjects)
}
private fun resolveWorkspaces(
rootProject: Project,
npmProjects: MutableList<NpmProjectPackage>
) {
val upToDateChecks = npmProjects.map {
YarnUpToDateCheck(it.npmProject)
}
if (upToDateChecks.all { it.upToDate }) return
val nodeJsWorldDir = rootProject.nodeJs.root.rootPackageDir
saveRootProjectWorkspacesPackageJson(rootProject, npmProjects, nodeJsWorldDir)
yarnExec(rootProject, nodeJsWorldDir, NpmApi.resolveOperationDescription("yarn"))
yarnLockReadTransitiveDependencies(nodeJsWorldDir, npmProjects.flatMap { it.npmDependencies })
upToDateChecks.forEach {
it.commit()
}
}
private fun saveRootProjectWorkspacesPackageJson(
rootProject: Project,
npmProjects: MutableList<NpmProjectPackage>,
nodeJsWorldDir: File
) {
val rootPackageJson = PackageJson(rootProject.name, rootProject.version.toString())
rootPackageJson.private = true
val npmProjectWorkspaces = npmProjects.map { it.npmProject.dir.relativeTo(nodeJsWorldDir).path }
val importedProjectWorkspaces = npmProjects.flatMapTo(mutableSetOf()) {
it.gradleDependencies.externalModules.map { importedProject ->
importedProject.path.relativeTo(nodeJsWorldDir).path
}
}
rootPackageJson.workspaces = npmProjectWorkspaces + importedProjectWorkspaces
rootProject.nodeJs.packageJsonHandlers.forEach {
it(rootPackageJson)
}
rootPackageJson.saveTo(
nodeJsWorldDir.resolve(NpmProject.PACKAGE_JSON)
)
}
}