Pill: Resolve dependencies using ResolvedArtifact data

This commit is contained in:
Yan Zhulanow
2019-04-11 21:47:56 +03:00
parent 977d3ef840
commit d5fa4f983e
6 changed files with 179 additions and 245 deletions
+23 -20
View File
@@ -2,27 +2,30 @@
package org.jetbrains.kotlin.pill
import org.gradle.api.artifacts.*
import org.gradle.api.artifacts.component.*
class DependencyMapper(
val predicate: (ResolvedDependency) -> Boolean,
vararg val configurations: String,
val mapping: (ResolvedDependency) -> MappedDependency?
) {
constructor(
group: String,
module: String,
vararg configurations: String,
version: String? = null,
mapping: (ResolvedDependency) -> MappedDependency?
) : this(
{ dep ->
dep.moduleGroup == group
&& dep.moduleName == module
&& (version == null || dep.moduleVersion == version)
},
configurations = *configurations,
mapping = mapping
)
class DependencyMapper(val predicate: (ResolvedArtifact) -> Boolean, val mapping: (ResolvedArtifact) -> MappedDependency?) {
companion object {
fun forProject(path: String, mapping: (ResolvedArtifact) -> MappedDependency?): DependencyMapper {
return DependencyMapper(
predicate = { artifact ->
val identifier = artifact.id.componentIdentifier as? ProjectComponentIdentifier
identifier?.projectPath == path
},
mapping = mapping
)
}
fun forModule(group: String, module: String, version: String?, mapping: (ResolvedArtifact) -> MappedDependency?): DependencyMapper {
return DependencyMapper(
predicate = { artifact ->
val identifier = artifact.id.componentIdentifier as? ModuleComponentIdentifier
identifier?.group == group && identifier?.module == module && (version == null || identifier?.version == version)
},
mapping = mapping
)
}
}
}
class MappedDependency(val main: PDependency?, val deferred: List<PDependency> = emptyList())
@@ -0,0 +1,91 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
@file:Suppress("PackageDirectoryMismatch")
package org.jetbrains.kotlin.pill
import java.io.File
import org.gradle.api.Project
import org.gradle.api.tasks.SourceSet
import org.gradle.api.artifacts.*
import org.gradle.api.artifacts.component.*
data class PDependencies(val main: List<PDependency>, val deferred: List<PDependency>) {
fun join(): List<PDependency> {
return main + deferred
}
}
fun Project.resolveDependencies(
configuration: Configuration,
forTests: Boolean,
dependencyMappers: List<DependencyMapper>,
withEmbedded: Boolean = false
): PDependencies {
val dependencies = mutableListOf<PDependency>()
val deferred = mutableListOf<PDependency>()
nextArtifact@ for (artifact in configuration.resolvedConfiguration.resolvedArtifacts) {
val identifier = artifact.id.componentIdentifier
for (mapper in dependencyMappers) {
if (mapper.predicate(artifact)) {
val mapped = mapper.mapping(artifact)
if (mapped != null) {
mapped.main?.let { dependencies += it }
deferred += mapped.deferred
}
continue@nextArtifact
}
}
fun addProjectDependency(projectPath: String) {
val project = rootProject.findProject(projectPath) ?: error("Cannot find project $projectPath")
fun addSourceSet(name: String, suffix: String): Boolean {
val sourceSet = project.sourceSets?.findByName(name)?.takeIf { !it.allSource.isEmpty() } ?: return false
dependencies += PDependency.Module(project.pillModuleName + '.' + suffix)
return true
}
if (forTests) {
addSourceSet(SourceSet.TEST_SOURCE_SET_NAME, "test") || addSourceSet(SourceSet.MAIN_SOURCE_SET_NAME, "src")
} else {
addSourceSet(SourceSet.MAIN_SOURCE_SET_NAME, "src")
}
if (withEmbedded) {
val embeddedConfiguration = project.configurations.findByName(EmbeddedComponents.CONFIGURATION_NAME)
if (embeddedConfiguration != null) {
dependencies += resolveDependencies(embeddedConfiguration, forTests, dependencyMappers, withEmbedded).join()
}
}
}
when (identifier) {
is ProjectComponentIdentifier -> addProjectDependency(identifier.projectPath)
is LibraryBinaryIdentifier -> addProjectDependency(identifier.projectPath)
is ModuleComponentIdentifier -> {
val file = artifact.file
val library = PLibrary(file.name, classes = listOf(file))
dependencies += PDependency.ModuleLibrary(library)
}
}
}
for (dependency in configuration.dependencies) {
if (dependency !is SelfResolvingDependency) {
continue
}
val files = dependency.resolve().takeIf { it.isNotEmpty() } ?: continue
val library = PLibrary(dependency.name, classes = files.toList())
deferred += PDependency.ModuleLibrary(library)
}
return PDependencies(dependencies, deferred)
}
@@ -11,7 +11,6 @@ import org.gradle.api.plugins.JavaPlugin
import org.gradle.api.tasks.Copy
import org.gradle.jvm.tasks.Jar
import org.gradle.kotlin.dsl.extra
import org.jetbrains.kotlin.pill.DependencyInfo.ResolvedDependencyInfo
import org.jetbrains.kotlin.pill.ArtifactElement.*
import org.jetbrains.kotlin.pill.POrderRoot.*
import java.io.File
@@ -114,7 +113,6 @@ fun generateKotlinPluginArtifactFile(rootProject: Project, dependencyMappers: Li
fun Project.getProject(name: String) = findProject(name) ?: error("Cannot find project $name")
val prepareIdeaPluginProject = rootProject.getProject(":prepare:idea-plugin")
val prepareJpsPluginProject = rootProject.getProject(":kotlin-jps-plugin")
root.add(Directory("kotlinc").apply {
val kotlincDirectory = rootProject.extra["distKotlinHomeDir"].toString()
@@ -123,20 +121,21 @@ fun generateKotlinPluginArtifactFile(rootProject: Project, dependencyMappers: Li
root.add(Directory("lib").apply {
val librariesConfiguration = prepareIdeaPluginProject.configurations.getByName("libraries")
add(getArtifactElements(librariesConfiguration, dependencyMappers, false))
add(getArtifactElements(rootProject, librariesConfiguration, dependencyMappers, false))
add(Directory("jps").apply {
add(Archive(prepareJpsPluginProject.name).apply {
val embeddedConfiguration = prepareJpsPluginProject.configurations.getByName(EmbeddedComponents.CONFIGURATION_NAME)
add(getArtifactElements(embeddedConfiguration, dependencyMappers, true))
val prepareJpsPluginProject = rootProject.getProject(":kotlin-jps-plugin")
add(Archive(prepareJpsPluginProject.name + ".jar").apply {
val jpsPluginConfiguration = prepareIdeaPluginProject.configurations.getByName("jpsPlugin")
add(getArtifactElements(rootProject, jpsPluginConfiguration, dependencyMappers, true))
})
})
add(Archive(prepareIdeaPluginProject.name + ".jar").apply {
add(Archive("kotlin-plugin.jar").apply {
add(FileCopy(File(rootProject.projectDir, "resources/kotlinManifest.properties")))
val embeddedConfiguration = prepareIdeaPluginProject.configurations.getByName(EmbeddedComponents.CONFIGURATION_NAME)
add(getArtifactElements(embeddedConfiguration, dependencyMappers, true))
add(getArtifactElements(rootProject, embeddedConfiguration, dependencyMappers, true))
})
})
@@ -148,42 +147,28 @@ fun generateKotlinPluginArtifactFile(rootProject: Project, dependencyMappers: Li
}
private fun getArtifactElements(
rootProject: Project,
configuration: Configuration,
dependencyMappers: List<DependencyMapper>,
extractDependencies: Boolean
): List<ArtifactElement> {
val resolved = configuration.resolvedConfiguration
val collected = CollectedConfiguration(resolved, Scope.COMPILE)
val dependencies = listOf(collected).collectDependencies()
val mainRoots = mutableListOf<POrderRoot>()
val deferredRoots = mutableListOf<POrderRoot>()
dependencies.forEach { it.processResolvedDependency(mainRoots, deferredRoots, dependencyMappers) }
val dependencies = rootProject.resolveDependencies(configuration, false, dependencyMappers, withEmbedded = true).join()
val artifacts = mutableListOf<ArtifactElement>()
for (dependency in configuration.allDependencies) {
if (dependency !is ProjectDependency) continue
for (dependency in dependencies) {
when (dependency) {
is PDependency.Module -> {
val moduleOutput = ModuleOutput(dependency.name)
val project = dependency.dependencyProject
val moduleOutput = ModuleOutput(project.name + ".src")
if (extractDependencies) {
artifacts += moduleOutput
} else {
artifacts += Archive(project.name + ".jar").apply {
add(moduleOutput)
if (extractDependencies) {
artifacts += moduleOutput
} else {
artifacts += Archive(dependency.name + ".jar").apply {
add(moduleOutput)
}
}
}
}
val embeddedConfiguration = project.configurations.findByName(EmbeddedComponents.CONFIGURATION_NAME) ?: continue
artifacts += getArtifactElements(embeddedConfiguration, dependencyMappers, extractDependencies)
}
for (orderRoot in listOf(mainRoots, deferredRoots).flatten()) {
when (val dependency = orderRoot.dependency) {
is PDependency.Module -> {} // already added above
is PDependency.Library -> artifacts += ProjectLibrary(dependency.name)
is PDependency.ModuleLibrary -> {
val files = dependency.library.classes
+25 -152
View File
@@ -33,13 +33,11 @@ data class PProject(
data class PModule(
val name: String,
val bundleName: String,
val rootDirectory: File,
val moduleFile: File,
val contentRoots: List<PContentRoot>,
val orderRoots: List<POrderRoot>,
val moduleForProductionSources: PModule? = null,
val group: String? = null
val moduleForProductionSources: PModule? = null
)
data class PContentRoot(
@@ -98,7 +96,8 @@ data class PLibrary(
val javadoc: List<File> = emptyList(),
val sources: List<File> = emptyList(),
val annotations: List<File> = emptyList(),
val dependencies: List<PLibrary> = emptyList()
val dependencies: List<PLibrary> = emptyList(),
val originalName: String = name
) {
fun attachSource(file: File): PLibrary {
return this.copy(sources = this.sources + listOf(file))
@@ -158,7 +157,7 @@ private fun ParserContext.parseModules(project: Project, excludedProjects: List<
var productionSourcesModule: PModule? = null
fun getModuleFile(suffix: String = ""): File {
val relativePath = File(project.projectDir, project.name + suffix + ".iml")
val relativePath = File(project.projectDir, project.pillModuleName + suffix + ".iml")
.toRelativeString(project.rootProject.projectDir)
return File(project.rootProject.projectDir, ".idea/modules/$relativePath")
@@ -173,13 +172,12 @@ private fun ParserContext.parseModules(project: Project, excludedProjects: List<
var dependencies = parseDependencies(project, mainRoot.forTests)
if (productionContentRoots.isNotEmpty() && mainRoot.forTests) {
val productionModuleDependency = PDependency.Module(project.name + ".src")
val productionModuleDependency = PDependency.Module(project.pillModuleName + ".src")
dependencies += POrderRoot(productionModuleDependency, Scope.COMPILE, true)
}
val module = PModule(
project.name + nameSuffix,
project.name,
project.pillModuleName + nameSuffix,
mainRoot.path,
getModuleFile(nameSuffix),
roots,
@@ -195,13 +193,12 @@ private fun ParserContext.parseModules(project: Project, excludedProjects: List<
}
val mainModuleFileRelativePath = when (project) {
project.rootProject -> File(project.rootProject.projectDir, project.name + ".iml")
project.rootProject -> File(project.rootProject.projectDir, project.rootProject.name + ".iml")
else -> getModuleFile()
}
modules += PModule(
project.name,
project.name,
project.pillModuleName,
project.projectDir,
mainModuleFileRelativePath,
listOf(PContentRoot(project.projectDir, false, emptyList(), allExcludedDirs)),
@@ -238,7 +235,7 @@ private fun parseSourceRoots(project: Project): List<PSourceRoot> {
val sourceRoots = mutableListOf<PSourceRoot>()
for (sourceSet in project.sourceSets) {
for (sourceSet in (project.sourceSets ?: emptyList())) {
val kotlinCompileTask = kotlinTasksBySourceSet[sourceSet.name]
val kind = if (sourceSet.isTestSourceSet) Kind.TEST else Kind.PRODUCTION
@@ -355,101 +352,25 @@ private fun Any.invokeInternal(name: String, instance: Any = this): Any? {
}
private fun ParserContext.parseDependencies(project: Project, forTests: Boolean): List<POrderRoot> {
val configurations = project.configurations
val configurationMapping = if (forTests) TEST_CONFIGURATION_MAPPING else CONFIGURATION_MAPPING
with(project.configurations) {
val mainRoots = mutableListOf<POrderRoot>()
val deferredRoots = mutableListOf<POrderRoot>()
val mainRoots = mutableListOf<POrderRoot>()
val deferredRoots = mutableListOf<POrderRoot>()
fun collectConfigurations(): List<CollectedConfiguration> {
val configurations = mutableListOf<CollectedConfiguration>()
for ((configurationNames, scope) in configurationMapping) {
for (configurationName in configurationNames) {
val configuration = findByName(configurationName)?.also { it.resolve() } ?: continue
val extraDependencies = resolveExtraDependencies(configuration)
configurations += CollectedConfiguration(configuration.resolvedConfiguration, scope, extraDependencies)
}
}
return configurations
}
for (dependencyInfo in collectConfigurations().collectDependencies()) {
val scope = dependencyInfo.scope
if (dependencyInfo is DependencyInfo.CustomDependencyInfo) {
val files = dependencyInfo.files
val library = PLibrary(files.firstOrNull()?.nameWithoutExtension ?: "unnamed", classes = files)
mainRoots += POrderRoot(PDependency.ModuleLibrary(library), scope)
continue
}
dependencyInfo.processResolvedDependency(mainRoots, deferredRoots, dependencyMappers)
}
return removeDuplicates(mainRoots + deferredRoots)
}
}
fun DependencyInfo.processResolvedDependency(
mainConsumer: MutableList<POrderRoot>,
deferredConsumer: MutableList<POrderRoot>,
dependencyMappers: List<DependencyMapper>
) {
val dependency = (this as? DependencyInfo.ResolvedDependencyInfo)?.dependency ?: return
for (mapper in dependencyMappers) {
if (dependency.configuration in mapper.configurations && mapper.predicate(dependency)) {
val mappedDependency = mapper.mapping(dependency)
if (mappedDependency != null) {
val mainDependency = mappedDependency.main
if (mainDependency != null) {
mainConsumer += POrderRoot(mainDependency, scope)
}
for (deferredDep in mappedDependency.deferred) {
deferredConsumer += POrderRoot(deferredDep, scope)
}
}
return
for ((configurationNames, scope) in configurationMapping) {
for (configurationName in configurationNames) {
val configuration = configurations.findByName(configurationName) ?: continue
val (main, deferred) = project.resolveDependencies(configuration, forTests, dependencyMappers)
mainRoots += main.map { POrderRoot(it, scope) }
deferredRoots += deferred.map { POrderRoot(it, scope) }
}
}
mainConsumer += if (dependency.isModuleDependency && scope != Scope.TEST) {
POrderRoot(PDependency.Module(dependency.moduleName + ".src"), scope)
} else if (dependency.configuration == "tests-jar" || dependency.configuration == "jpsTest") {
POrderRoot(
PDependency.Module(dependency.moduleName + ".test"),
scope,
isProductionOnTestDependency = true
)
} else {
val classes = dependency.moduleArtifacts.map { it.file }
val library = PLibrary(dependency.moduleName, classes)
POrderRoot(PDependency.ModuleLibrary(library), scope)
}
return removeDuplicates(mainRoots + deferredRoots)
}
private fun resolveExtraDependencies(configuration: Configuration): List<File> {
return configuration.dependencies
.filterIsInstance<SelfResolvingDependency>()
.map { it.resolve() }
.filter { isGradleApiDependency(it) }
.flatMap { it }
}
private fun isGradleApiDependency(files: Iterable<File>): Boolean {
return listOf("gradle-api", "groovy-all").all { dep ->
files.any { it.extension == "jar" && it.name.startsWith("$dep-") }
}
}
private fun removeDuplicates(roots: List<POrderRoot>): List<POrderRoot> {
fun removeDuplicates(roots: List<POrderRoot>): List<POrderRoot> {
val dependenciesByScope = roots.groupBy { it.scope }.mapValues { it.value.mapTo(mutableSetOf()) { it.dependency } }
fun dependenciesFor(scope: Scope) = dependenciesByScope[scope] ?: emptySet<PDependency>()
@@ -482,59 +403,11 @@ private fun removeDuplicates(roots: List<POrderRoot>): List<POrderRoot> {
return result.toList()
}
data class CollectedConfiguration(
val configuration: ResolvedConfiguration,
val scope: Scope,
val extraDependencies: List<File> = emptyList())
val Project.pillModuleName: String
get() = path.removePrefix(":").replace(':', '.')
sealed class DependencyInfo(val scope: Scope) {
class ResolvedDependencyInfo(scope: Scope, val dependency: ResolvedDependency) : DependencyInfo(scope)
class CustomDependencyInfo(scope: Scope, val files: List<File>) : DependencyInfo(scope)
}
val ResolvedDependency.isModuleDependency
get() = configuration in JpsCompatiblePlugin.MODULE_CONFIGURATIONS
fun List<CollectedConfiguration>.collectDependencies(): List<DependencyInfo> {
val dependencies = mutableListOf<DependencyInfo>()
val unprocessed = LinkedList<DependencyInfo>()
val existing = mutableSetOf<Pair<Scope, ResolvedDependency>>()
for ((configuration, scope, extraDependencies) in this) {
for (dependency in configuration.firstLevelModuleDependencies) {
unprocessed += DependencyInfo.ResolvedDependencyInfo(scope, dependency)
}
if (!extraDependencies.isEmpty()) {
unprocessed += DependencyInfo.CustomDependencyInfo(scope, extraDependencies)
}
}
while (unprocessed.isNotEmpty()) {
val info = unprocessed.removeAt(0)
dependencies += info
info as? DependencyInfo.ResolvedDependencyInfo ?: continue
val data = Pair(info.scope, info.dependency)
existing += data
for (child in info.dependency.children) {
if (Pair(info.scope, child) in existing) {
continue
}
unprocessed += DependencyInfo.ResolvedDependencyInfo(info.scope, child)
}
}
return dependencies
}
private val Project.sourceSets: SourceSetContainer
val Project.sourceSets: SourceSetContainer?
get() {
lateinit var result: SourceSetContainer
project.configure<JavaPluginConvention> { result = sourceSets }
return result
val convention = project.convention.findPlugin(JavaPluginConvention::class.java) ?: return null
return convention.sourceSets
}
+17 -30
View File
@@ -20,44 +20,30 @@ class PillConfigurablePlugin : Plugin<Project> {
class JpsCompatiblePlugin : Plugin<Project> {
companion object {
val MODULE_CONFIGURATIONS = arrayOf("apiElements", "runtimeElements")
private fun mapper(module: String, vararg configurations: String): DependencyMapper {
return DependencyMapper("org.jetbrains.kotlin", module, *configurations) { MappedDependency(PDependency.Library(module)) }
}
private fun getDependencyMappers(projectLibraries: List<PLibrary>): List<DependencyMapper> {
val mappersForKotlinLibrariesExeptStdlib = projectLibraries
.filter { it.name != "kotlin-stdlib" }
.mapTo(mutableListOf()) { mapper(it.name, "default", "distJar", *MODULE_CONFIGURATIONS) }
.map { DependencyMapper.forProject(it.originalName) { MappedDependency(PDependency.Library(it.name)) } }
return mappersForKotlinLibrariesExeptStdlib + listOf(
DependencyMapper("org.jetbrains.kotlin", "kotlin-stdlib", "distJar", *MODULE_CONFIGURATIONS) {
val disabledModuleMappers = listOf(
":kotlin-stdlib-common",
":core:builtins",
":kotlin-compiler",
":kotlin-compiler-embeddable",
":kotlin-test:kotlin-test-common",
":kotlin-test:kotlin-test-annotations-common"
).map { DependencyMapper.forProject(it) { null } }
return listOf(
DependencyMapper.forProject(":kotlin-stdlib") {
MappedDependency(
PDependency.Library("kotlin-stdlib"),
listOf(PDependency.Library("annotations-13.0"))
)
},
DependencyMapper("org.jetbrains", "annotations", "default", "runtime", version = "13.0") {
MappedDependency(
null,
listOf(PDependency.Library("annotations-13.0"))
)
},
DependencyMapper("org.jetbrains.kotlin", "kotlin-reflect-api", *MODULE_CONFIGURATIONS) {
MappedDependency(PDependency.Library("kotlin-reflect"))
},
DependencyMapper("org.jetbrains.kotlin", "kotlin-compiler-embeddable", "runtimeJar") { null },
DependencyMapper("org.jetbrains.kotlin", "kotlin-stdlib-js", "distJar") { null },
DependencyMapper("org.jetbrains.kotlin", "kotlin-compiler", "runtimeJar") { null },
DependencyMapper("org.jetbrains.kotlin", "compiler", *MODULE_CONFIGURATIONS) { null },
DependencyMapper("kotlin.build", "android", "default") { dep ->
val (sdkCommon, otherJars) = dep.moduleArtifacts.map { it.file }.partition { it.name == "sdk-common.jar" }
val mainLibrary = PDependency.ModuleLibrary(PLibrary(dep.moduleName, otherJars))
val deferredLibrary = PDependency.ModuleLibrary(PLibrary(dep.moduleName + "-deferred", sdkCommon))
MappedDependency(mainLibrary, listOf(deferredLibrary))
}
)
DependencyMapper.forProject(":kotlin-test:kotlin-test-jvm") { MappedDependency(PDependency.Library("kotlin-test-jvm")) },
DependencyMapper.forProject(":kotlin-reflect-api") { MappedDependency(PDependency.Library("kotlin-reflect")) }
) + mappersForKotlinLibrariesExeptStdlib + disabledModuleMappers
}
fun getProjectLibraries(rootProject: Project): List<PLibrary> {
@@ -78,7 +64,8 @@ class JpsCompatiblePlugin : Plugin<Project> {
PLibrary(
library.name,
classes = listOf(File(libraryPath, "$archivesBaseName.jar")).filterExisting(),
sources = listOf(File(libraryPath, "$archivesBaseName-sources.jar")).filterExisting()
sources = listOf(File(libraryPath, "$archivesBaseName-sources.jar")).filterExisting(),
originalName = library.path
)
}
+3 -8
View File
@@ -31,12 +31,7 @@ private fun renderModulesFile(project: PProject) = PFile(
for (module in project.modules) {
val moduleFilePath = pathContext(module.moduleFile)
if (module.group != null) {
xml("module", "fileurl" to "file://$moduleFilePath", "filepath" to moduleFilePath, "group" to module.group)
} else {
xml("module", "fileurl" to "file://$moduleFilePath", "filepath" to moduleFilePath)
}
xml("module", "fileurl" to "file://$moduleFilePath", "filepath" to moduleFilePath)
}
}
}
@@ -85,7 +80,7 @@ private fun renderModule(project: PProject, module: PModule) = PFile(
kotlinCompileOptions.noStdlib.option("noStdlib")
kotlinCompileOptions.noReflect.option("noReflect")
kotlinCompileOptions.moduleName.option("moduleName")
module.name.option("moduleName")
xml("option", "name" to "jvmTarget", "value" to platformVersion)
kotlinCompileOptions.languageVersion.option("languageVersion")
kotlinCompileOptions.apiVersion.option("apiVersion")
@@ -202,4 +197,4 @@ private fun renderLibraryToXml(library: PLibrary, pathContext: PathContext, name
}
}
fun PLibrary.renderName() = name?.takeIf { it != "unspecified" } ?: classes.first().nameWithoutExtension
fun PLibrary.renderName() = name.takeIf { it != "unspecified" } ?: classes.first().nameWithoutExtension