Gradle, js, npm: resolver between projects

#KT-30530
This commit is contained in:
Sergey Rostov
2019-05-15 18:14:04 +03:00
parent 5cfe117fd7
commit eb3c701040
4 changed files with 40 additions and 37 deletions
@@ -10,9 +10,7 @@ import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ResolvedArtifact import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.ResolvedDependency import org.gradle.api.artifacts.ResolvedDependency
import org.gradle.api.artifacts.component.ProjectComponentIdentifier import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.jetbrains.kotlin.gradle.dsl.KotlinCommonOptions
import org.jetbrains.kotlin.gradle.internal.ProcessedFilesCache import org.jetbrains.kotlin.gradle.internal.ProcessedFilesCache
import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation
internal class GradleNodeModulesBuilder(val project: Project) : AutoCloseable { internal class GradleNodeModulesBuilder(val project: Project) : AutoCloseable {
companion object { companion object {
@@ -28,6 +26,8 @@ internal class GradleNodeModulesBuilder(val project: Project) : AutoCloseable {
GradleNodeModule(it, dir.resolve(it)) GradleNodeModule(it, dir.resolve(it))
} }
val projects = mutableSetOf<Project>()
fun visitConfiguration(configuration: Configuration) { fun visitConfiguration(configuration: Configuration) {
if (configuration.isCanBeResolved) { if (configuration.isCanBeResolved) {
configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach { configuration.resolvedConfiguration.firstLevelModuleDependencies.forEach {
@@ -57,7 +57,13 @@ internal class GradleNodeModulesBuilder(val project: Project) : AutoCloseable {
} }
artifacts.forEach { artifact -> artifacts.forEach { artifact ->
if (artifact.id.componentIdentifier !is ProjectComponentIdentifier) { val componentIdentifier = artifact.id.componentIdentifier
if (componentIdentifier is ProjectComponentIdentifier) {
val dependentProject = project.findProject(componentIdentifier.projectPath)
?: error("Cannot find project ${componentIdentifier.projectPath}")
projects.add(dependentProject)
} else {
cache.getOrCompute(artifact.file) { cache.getOrCompute(artifact.file) {
lazyDirName lazyDirName
} }
@@ -56,14 +56,14 @@ data class NpmDependency(
return when (result) { return when (result) {
is AlreadyInProgress -> null is AlreadyInProgress -> null
is AlreadyResolved -> { is AlreadyResolved -> {
check(this in result.resolution.dependencies) { check(this in result.resolution.npmPackage!!.npmDependencies) {
"Project hierarchy is already resolved in NPM without $this" "Project hierarchy is already resolved in NPM without $this"
} }
result.resolution result.resolution
} }
is ResolvedNow -> { is ResolvedNow -> {
check(this in result.resolution.dependencies) { check(this in result.resolution.npmPackage!!.npmDependencies) {
"$this was not visited during resolution" "$this was not visited during resolution"
} }
@@ -28,7 +28,7 @@ import org.jetbrains.kotlin.gradle.tasks.Kotlin2JsCompile
/** /**
* Generates `package.json` file for projects with npm or js dependencies and * Generates `package.json` file for projects with npm or js dependencies and
* runs selected [NodeJsRootExtension.packageManager] to download and install it. * runs selected [NodeJsRootExtension.packageManager] to download and install all of it's dependencies.
* *
* All [NpmDependency] for configurations related to kotlin/js will be added to `package.json`. * All [NpmDependency] for configurations related to kotlin/js will be added to `package.json`.
* For external gradle modules, fake npm packages will be created and added to `package.json` * For external gradle modules, fake npm packages will be created and added to `package.json`
@@ -45,14 +45,10 @@ internal class NpmResolver private constructor(val rootProject: Project) {
val resolved = process?.resolved val resolved = process?.resolved
return if (resolved != null) AlreadyResolved(resolved) return if (resolved != null) AlreadyResolved(ProjectData[project]!!.resolved!!)
else { else {
val resolver = NpmResolver(rootProject) NpmResolver(rootProject).resolve(rootProject, null)!!
check(resolver.resolve(rootProject, null)) ResolvedNow(ProjectData[project]!!.resolved!!)
val resolution = ResolvedProject(resolver.npmPackages, resolver.requiredByTasks)
ProjectData[rootProject]!!.resolved = resolution
ResolvedNow(resolution)
} }
} }
@@ -61,7 +57,7 @@ internal class NpmResolver private constructor(val rootProject: Project) {
?: error("NPM dependencies should be resolved$reason") ?: error("NPM dependencies should be resolved$reason")
fun checkRequiredDependencies(project: Project, target: RequiresNpmDependencies) { fun checkRequiredDependencies(project: Project, target: RequiresNpmDependencies) {
val required = requireResolved(project, "before $target execution").requiredByTasks val required = requireResolved(project, "before $target execution").taskDependencies
val targetRequired = required[target]?.toSet() ?: setOf() val targetRequired = required[target]?.toSet() ?: setOf()
target.requiredNpmDependencies.forEach { target.requiredNpmDependencies.forEach {
@@ -98,13 +94,9 @@ internal class NpmResolver private constructor(val rootProject: Project) {
} }
class ResolvedProject( class ResolvedProject(
val npmPackages: Collection<NpmPackage>, val npmPackage: NpmPackage?,
val requiredByTasks: Map<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>> val taskDependencies: Map<RequiresNpmDependencies, Collection<RequiredKotlinJsDependency>>
) { )
val dependencies by lazy {
npmPackages.flatMapTo(mutableSetOf()) { it.npmDependencies }
}
}
class NpmPackage( class NpmPackage(
val project: Project, val project: Project,
@@ -128,19 +120,15 @@ internal class NpmResolver private constructor(val rootProject: Project) {
packageManagerInstalled = true packageManagerInstalled = true
} }
private fun getOrResolve(project: Project, parentGradleModules: GradleNodeModulesBuilder?): ResolvedProject {
return (ProjectData[project] ?: resolve(project, parentGradleModules)!!).resolved!!
}
private fun resolve( private fun resolve(
project: Project, project: Project,
parentGradleModules: GradleNodeModulesBuilder? parentGradleModules: GradleNodeModulesBuilder?
): Boolean { ): ProjectData? {
val existedProcess = ProjectData[project] val result = ProjectData().also {
if (existedProcess != null) {
if (existedProcess.resolved != null) error("yarn dependencies for $project already resolved")
else {
// called inside resolution of classpath (from visitTarget)
// we should return as we are already in progress
return false
}
} else ProjectData().also {
ProjectData[project] = it ProjectData[project] = it
} }
@@ -149,10 +137,10 @@ internal class NpmResolver private constructor(val rootProject: Project) {
else GradleNodeModulesBuilder(project) else GradleNodeModulesBuilder(project)
project.subprojects.forEach { project.subprojects.forEach {
resolve(it, gradleModules) getOrResolve(it, gradleModules)
} }
val npmPackage = extractNpmPackage(project, gradleModules) val npmPackage = extractNpmPackage(project, gradleModules, parentGradleModules)
if (npmPackage != null) { if (npmPackage != null) {
npmPackage.savePackageJson(gson) npmPackage.savePackageJson(gson)
@@ -162,6 +150,8 @@ internal class NpmResolver private constructor(val rootProject: Project) {
npmPackages.add(npmPackage) npmPackages.add(npmPackage)
} }
result.resolved = ResolvedProject(npmPackage, requiredByTasks)
if (project == rootProject) { if (project == rootProject) {
if (npmPackages.isNotEmpty()) { if (npmPackages.isNotEmpty()) {
requirePackageManagerSetup() requirePackageManagerSetup()
@@ -173,12 +163,13 @@ internal class NpmResolver private constructor(val rootProject: Project) {
gradleModules.close() gradleModules.close()
} }
return true return result
} }
private fun extractNpmPackage( private fun extractNpmPackage(
project: Project, project: Project,
gradleComponents: GradleNodeModulesBuilder gradleComponents: GradleNodeModulesBuilder,
parentGradleModules: GradleNodeModulesBuilder?
): NpmPackage? { ): NpmPackage? {
val packageJson = PackageJson(project.name, project.version.toString()) val packageJson = PackageJson(project.name, project.version.toString())
val npmDependencies = mutableSetOf<NpmDependency>() val npmDependencies = mutableSetOf<NpmDependency>()
@@ -192,6 +183,12 @@ internal class NpmResolver private constructor(val rootProject: Project) {
val relativePath = it.path.relativeTo(NpmProject[project].nodeWorkDir) val relativePath = it.path.relativeTo(NpmProject[project].nodeWorkDir)
packageJson.dependencies[it.name] = "file:$relativePath" packageJson.dependencies[it.name] = "file:$relativePath"
} }
gradleComponents.projects.forEach {
val npmPackage = getOrResolve(it, parentGradleModules).npmPackage
if (npmPackage != null) {
packageJson.dependencies[npmPackage.packageJson.name] = npmPackage.packageJson.version
}
}
} }
project.nodeJs.packageJsonHandlers.forEach { project.nodeJs.packageJsonHandlers.forEach {
@@ -111,8 +111,8 @@ object Yarn : NpmApi {
return false return false
} }
override fun shouldHoistGradleNodeModules(project: Project): Boolean = // override fun shouldHoistGradleNodeModules(project: Project): Boolean =
project.rootProject.yarn.useWorkspaces // project.rootProject.yarn.useWorkspaces
@Suppress("EXPOSED_PARAMETER_TYPE") @Suppress("EXPOSED_PARAMETER_TYPE")
override fun resolveRootProject( override fun resolveRootProject(