mirror of
https://github.com/tadfisher/gradle2nix.git
synced 2026-01-12 07:50:53 -05:00
Use custom dependency resolution
- Use Apache Ivy to resolve artifact URLs - Update build model with full artifact IDs - Generate Maven module metadata to support dynamic version constraints - Resolve snapshot versions and generate snapshot metadata - Add test fixtures and rewrite Gradle plugin tests - Update dependencies
This commit is contained in:
@@ -0,0 +1,255 @@
|
||||
package org.nixos.gradle2nix
|
||||
|
||||
import org.apache.ivy.Ivy
|
||||
import org.apache.ivy.core.settings.IvySettings
|
||||
import org.apache.ivy.plugins.parser.m2.PomReader
|
||||
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser
|
||||
import org.apache.ivy.plugins.repository.url.URLResource
|
||||
import org.apache.ivy.plugins.resolver.ChainResolver
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.ModuleIdentifier
|
||||
import org.gradle.api.artifacts.ResolvedArtifact
|
||||
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
import org.gradle.api.artifacts.dsl.RepositoryHandler
|
||||
import org.gradle.api.artifacts.query.ArtifactResolutionQuery
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult
|
||||
import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
|
||||
import org.gradle.ivy.IvyDescriptorArtifact
|
||||
import org.gradle.ivy.IvyModule
|
||||
import org.gradle.kotlin.dsl.getArtifacts
|
||||
import org.gradle.kotlin.dsl.withArtifacts
|
||||
import org.gradle.maven.MavenModule
|
||||
import org.gradle.maven.MavenPomArtifact
|
||||
import org.gradle.util.GradleVersion
|
||||
import java.io.File
|
||||
|
||||
internal class ConfigurationResolverFactory(repositories: RepositoryHandler) {
|
||||
private val ivySettings = IvySettings().apply {
|
||||
defaultInit()
|
||||
setDefaultRepositoryCacheBasedir(createTempDir("gradle2nix-cache").apply(File::deleteOnExit).absolutePath)
|
||||
setDictatorResolver(ChainResolver().also { chain ->
|
||||
chain.settings = this@apply
|
||||
for (resolver in resolvers) chain.add(resolver)
|
||||
})
|
||||
}
|
||||
|
||||
private val resolvers = repositories.filterIsInstance<ResolutionAwareRepository>()
|
||||
.mapNotNull { it.repositoryResolver(ivySettings) }
|
||||
|
||||
fun create(dependencies: DependencyHandler): ConfigurationResolver =
|
||||
ConfigurationResolver(ivySettings, resolvers, dependencies)
|
||||
}
|
||||
|
||||
internal class ConfigurationResolver(
|
||||
ivySettings: IvySettings,
|
||||
private val resolvers: List<RepositoryResolver>,
|
||||
private val dependencies: DependencyHandler
|
||||
) {
|
||||
private val ivy = Ivy.newInstance(ivySettings)
|
||||
|
||||
fun resolve(configuration: Configuration): List<DefaultArtifact> {
|
||||
val resolved = configuration.resolvedConfiguration
|
||||
|
||||
val topLevelMetadata = resolved.firstLevelModuleDependencies
|
||||
.flatMap { resolveMetadata(it.moduleGroup, it.moduleName, it.moduleVersion) }
|
||||
|
||||
val allArtifacts = resolved.resolvedArtifacts
|
||||
.filter { it.id.componentIdentifier is ModuleComponentIdentifier }
|
||||
.flatMap(::resolve)
|
||||
|
||||
return (topLevelMetadata + allArtifacts).filter { it.urls.isNotEmpty() }
|
||||
}
|
||||
|
||||
private fun resolve(resolvedArtifact: ResolvedArtifact): List<DefaultArtifact> {
|
||||
val componentId = resolvedArtifact.id.componentIdentifier as ModuleComponentIdentifier
|
||||
|
||||
val artifactId = DefaultArtifactIdentifier(
|
||||
group = componentId.group,
|
||||
name = componentId.module,
|
||||
version = componentId.version,
|
||||
type = resolvedArtifact.type,
|
||||
extension = resolvedArtifact.extension,
|
||||
classifier = resolvedArtifact.classifier
|
||||
)
|
||||
|
||||
val sha256 = resolvedArtifact.file.sha256()
|
||||
val artifacts = resolvers.mapNotNull { it.resolve(artifactId, sha256) }.merge()
|
||||
return artifacts + componentId.run { resolveMetadata(group, module, version) }
|
||||
}
|
||||
|
||||
private fun resolveMetadata(
|
||||
group: String,
|
||||
name: String,
|
||||
version: String
|
||||
): List<DefaultArtifact> {
|
||||
return resolvePoms(group, name, version) +
|
||||
resolveDescriptors(group, name, version) +
|
||||
resolveGradleMetadata(group, name, version)
|
||||
}
|
||||
|
||||
private fun resolvePoms(
|
||||
group: String,
|
||||
name: String,
|
||||
version: String
|
||||
): List<DefaultArtifact> {
|
||||
return dependencies.createArtifactResolutionQuery()
|
||||
.forModuleCompat(group, name, version)
|
||||
.withArtifacts(MavenModule::class, MavenPomArtifact::class)
|
||||
.execute()
|
||||
.resolvedComponents
|
||||
.flatMap { it.getArtifacts(MavenPomArtifact::class) }
|
||||
.filterIsInstance<ResolvedArtifactResult>()
|
||||
.flatMap { it.withParentPoms() }
|
||||
.flatMap { resolvedPom ->
|
||||
val componentId = resolvedPom.id.componentIdentifier as ModuleComponentIdentifier
|
||||
val artifactId = DefaultArtifactIdentifier(
|
||||
group = componentId.group,
|
||||
name = componentId.module,
|
||||
version = componentId.version,
|
||||
type = "pom"
|
||||
)
|
||||
val sha256 = resolvedPom.file.sha256()
|
||||
resolvers.mapNotNull { it.resolve(artifactId, sha256) }.merge()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveDescriptors(
|
||||
group: String,
|
||||
name: String,
|
||||
version: String
|
||||
): List<DefaultArtifact> {
|
||||
return dependencies.createArtifactResolutionQuery()
|
||||
.forModuleCompat(group, name, version)
|
||||
.withArtifacts(IvyModule::class, IvyDescriptorArtifact::class)
|
||||
.execute()
|
||||
.resolvedComponents
|
||||
.flatMap { it.getArtifacts(IvyDescriptorArtifact::class) }
|
||||
.filterIsInstance<ResolvedArtifactResult>()
|
||||
.flatMap { it.withParentDescriptors() }
|
||||
.flatMap { resolvedDesc ->
|
||||
val componentId = resolvedDesc.id.componentIdentifier as ModuleComponentIdentifier
|
||||
val artifactId = DefaultArtifactIdentifier(
|
||||
group = componentId.group,
|
||||
name = componentId.module,
|
||||
version = componentId.version,
|
||||
type = "ivy",
|
||||
extension = "xml"
|
||||
)
|
||||
val sha256 = resolvedDesc.file.sha256()
|
||||
resolvers.mapNotNull { it.resolve(artifactId, sha256) }.merge()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resolveGradleMetadata(
|
||||
group: String,
|
||||
name: String,
|
||||
version: String
|
||||
): List<DefaultArtifact> {
|
||||
val artifactId = DefaultArtifactIdentifier(
|
||||
group = group,
|
||||
name = name,
|
||||
version = version,
|
||||
type = "module"
|
||||
)
|
||||
return resolvers.mapNotNull { it.resolve(artifactId) }.merge()
|
||||
}
|
||||
|
||||
private fun ResolvedArtifactResult.parentPom(): ResolvedArtifactResult? {
|
||||
val resource = URLResource(file.toURI().toURL())
|
||||
val reader = PomReader(resource.url, resource)
|
||||
|
||||
return if (reader.hasParent()) {
|
||||
dependencies.createArtifactResolutionQuery()
|
||||
.forModuleCompat(reader.parentGroupId, reader.parentArtifactId, reader.parentVersion)
|
||||
.withArtifacts(MavenModule::class, MavenPomArtifact::class)
|
||||
.execute()
|
||||
.resolvedComponents
|
||||
.flatMap { it.getArtifacts(MavenPomArtifact::class) }
|
||||
.filterIsInstance<ResolvedArtifactResult>()
|
||||
.firstOrNull()
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun ResolvedArtifactResult.withParentPoms(): List<ResolvedArtifactResult> =
|
||||
generateSequence(this) { it.parentPom() }.toList()
|
||||
|
||||
private fun ResolvedArtifactResult.parentDescriptors(seen: Set<ComponentArtifactIdentifier>): List<ResolvedArtifactResult> {
|
||||
val url = file.toURI().toURL()
|
||||
val parser = XmlModuleDescriptorParser.getInstance()
|
||||
|
||||
val descriptor = parser.parseDescriptor(ivy.settings, url, false)
|
||||
|
||||
return descriptor.inheritedDescriptors.mapNotNull { desc ->
|
||||
dependencies.createArtifactResolutionQuery()
|
||||
.forModuleCompat(
|
||||
desc.parentRevisionId.organisation,
|
||||
desc.parentRevisionId.name,
|
||||
desc.parentRevisionId.revision
|
||||
)
|
||||
.withArtifacts(IvyModule::class, IvyDescriptorArtifact::class)
|
||||
.execute()
|
||||
.resolvedComponents
|
||||
.flatMap { it.getArtifacts(IvyDescriptorArtifact::class) }
|
||||
.filterIsInstance<ResolvedArtifactResult>()
|
||||
.firstOrNull()
|
||||
}.filter { it.id !in seen }
|
||||
}
|
||||
|
||||
private fun ResolvedArtifactResult.withParentDescriptors(): List<ResolvedArtifactResult> {
|
||||
val seen = mutableSetOf<ComponentArtifactIdentifier>()
|
||||
return generateSequence(listOf(this)) { descs ->
|
||||
val parents = descs.flatMap { it.parentDescriptors(seen) }
|
||||
seen.addAll(parents.map(ResolvedArtifactResult::id))
|
||||
parents.takeUnless { it.isEmpty() }
|
||||
}.flatten().distinct().toList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun ArtifactResolutionQuery.forModuleCompat(
|
||||
group: String,
|
||||
name: String,
|
||||
version: String
|
||||
): ArtifactResolutionQuery {
|
||||
return if (GradleVersion.current() >= GradleVersion.version("4.5")) {
|
||||
forModule(group, name, version)
|
||||
} else {
|
||||
forComponents(ModuleComponentId(group, name, version))
|
||||
}
|
||||
}
|
||||
|
||||
private data class ModuleComponentId(
|
||||
private val moduleId: ModuleId,
|
||||
private val version: String
|
||||
) : ModuleComponentIdentifier {
|
||||
|
||||
constructor(
|
||||
group: String,
|
||||
name: String,
|
||||
version: String
|
||||
) : this(ModuleId(group, name), version)
|
||||
|
||||
override fun getGroup(): String = moduleId.group
|
||||
override fun getModule(): String = moduleId.name
|
||||
override fun getVersion(): String = version
|
||||
override fun getModuleIdentifier(): ModuleIdentifier = moduleId
|
||||
override fun getDisplayName(): String =
|
||||
arrayOf(group, module, version).joinToString(":")
|
||||
}
|
||||
|
||||
private data class ModuleId(
|
||||
private val group: String,
|
||||
private val name: String
|
||||
) : ModuleIdentifier {
|
||||
override fun getGroup(): String = group
|
||||
override fun getName(): String = name
|
||||
}
|
||||
|
||||
private fun List<DefaultArtifact>.merge(): List<DefaultArtifact> {
|
||||
return groupingBy { it.id }
|
||||
.reduce { _, dest, next -> dest.copy(urls = dest.urls + next.urls) }
|
||||
.values.toList()
|
||||
}
|
||||
@@ -1,169 +0,0 @@
|
||||
package org.nixos.gradle2nix
|
||||
|
||||
import org.apache.maven.model.Parent
|
||||
import org.apache.maven.model.Repository
|
||||
import org.apache.maven.model.building.DefaultModelBuilderFactory
|
||||
import org.apache.maven.model.building.DefaultModelBuildingRequest
|
||||
import org.apache.maven.model.building.ModelBuildingRequest
|
||||
import org.apache.maven.model.building.ModelSource2
|
||||
import org.apache.maven.model.resolution.ModelResolver
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.ConfigurationContainer
|
||||
import org.gradle.api.artifacts.Dependency
|
||||
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
|
||||
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
|
||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
||||
import org.gradle.api.artifacts.result.ResolvedArtifactResult
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.gradle.api.logging.Logging
|
||||
import org.gradle.maven.MavenModule
|
||||
import org.gradle.maven.MavenPomArtifact
|
||||
import java.io.File
|
||||
import java.io.InputStream
|
||||
import java.net.URI
|
||||
import java.security.MessageDigest
|
||||
|
||||
internal class DependencyResolver(
|
||||
private val configurations: ConfigurationContainer,
|
||||
private val dependencies: DependencyHandler,
|
||||
private val logger: Logger = Logging.getLogger(DependencyResolver::class.simpleName)
|
||||
) {
|
||||
private val mavenPomResolver = MavenPomResolver(configurations, dependencies)
|
||||
|
||||
fun resolveDependencies(configuration: Configuration): Set<DefaultArtifact> {
|
||||
if (!configuration.isCanBeResolved) {
|
||||
logger.warn("Cannot resolve configuration ${configuration.name}; ignoring.")
|
||||
return emptySet()
|
||||
}
|
||||
return configuration.resolvedConfiguration.resolvedArtifacts
|
||||
.filterNot { it.id.componentIdentifier is ProjectComponentIdentifier }
|
||||
.mapTo(sortedSetOf()) {
|
||||
with (it) {
|
||||
DefaultArtifact(
|
||||
groupId = moduleVersion.id.group,
|
||||
artifactId = moduleVersion.id.name,
|
||||
version = moduleVersion.id.version,
|
||||
classifier = classifier ?: "",
|
||||
extension = extension,
|
||||
sha256 = sha256(file)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun resolveDependencies(
|
||||
dependencies: Collection<Dependency>,
|
||||
includeTransitive: Boolean = false
|
||||
): Set<DefaultArtifact> {
|
||||
val configuration = configurations.detachedConfiguration(*(dependencies.toTypedArray()))
|
||||
configuration.isTransitive = includeTransitive
|
||||
return resolveDependencies(configuration)
|
||||
}
|
||||
|
||||
fun resolvePoms(configuration: Configuration): Set<DefaultArtifact> {
|
||||
return dependencies.createArtifactResolutionQuery()
|
||||
.forComponents(configuration.incoming.resolutionResult.allComponents.map { it.id })
|
||||
.withArtifacts(MavenModule::class.java, MavenPomArtifact::class.java)
|
||||
.execute()
|
||||
.resolvedComponents.asSequence()
|
||||
.flatMap { component ->
|
||||
val id = component.id
|
||||
if (id !is ModuleComponentIdentifier) {
|
||||
emptySequence()
|
||||
} else {
|
||||
component.getArtifacts(MavenPomArtifact::class.java).asSequence()
|
||||
.filterIsInstance<ResolvedArtifactResult>()
|
||||
.map { id to it }
|
||||
}
|
||||
}
|
||||
.flatMapTo(sortedSetOf()) { (id, artifact) ->
|
||||
sequenceOf(DefaultArtifact(
|
||||
groupId = id.group,
|
||||
artifactId = id.module,
|
||||
version = id.version,
|
||||
classifier = "",
|
||||
extension = artifact.file.extension,
|
||||
sha256 = sha256(artifact.file)
|
||||
)) + mavenPomResolver.resolve(artifact.file).asSequence()
|
||||
}
|
||||
}
|
||||
|
||||
fun resolvePoms(
|
||||
dependencies: Collection<Dependency>,
|
||||
includeTransitive: Boolean = false
|
||||
): Set<DefaultArtifact> {
|
||||
val configuration = configurations.detachedConfiguration(*(dependencies.toTypedArray()))
|
||||
configuration.isTransitive = includeTransitive
|
||||
return resolvePoms(configuration)
|
||||
}
|
||||
}
|
||||
|
||||
private class MavenPomResolver(
|
||||
private val configurations: ConfigurationContainer,
|
||||
private val dependencies: DependencyHandler
|
||||
) : ModelResolver {
|
||||
private val modelBuilder = DefaultModelBuilderFactory().newInstance()
|
||||
private val resolvedDependencies = mutableSetOf<DefaultArtifact>()
|
||||
|
||||
@Synchronized
|
||||
fun resolve(pom: File): Set<DefaultArtifact> {
|
||||
resolvedDependencies.clear()
|
||||
modelBuilder.build(
|
||||
DefaultModelBuildingRequest()
|
||||
.setModelResolver(this)
|
||||
.setPomFile(pom)
|
||||
.setSystemProperties(System.getProperties())
|
||||
.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL)
|
||||
).effectiveModel
|
||||
return resolvedDependencies.toSet()
|
||||
}
|
||||
|
||||
override fun newCopy() = this
|
||||
|
||||
override fun resolveModel(
|
||||
groupId: String,
|
||||
artifactId: String,
|
||||
version: String
|
||||
): ModelSource2 {
|
||||
val file = configurations
|
||||
.detachedConfiguration(dependencies.create("$groupId:$artifactId:$version@pom"))
|
||||
.singleFile
|
||||
resolvedDependencies.add(DefaultArtifact(
|
||||
groupId = groupId,
|
||||
artifactId = artifactId,
|
||||
version = version,
|
||||
classifier = "",
|
||||
extension = file.extension,
|
||||
sha256 = sha256(file)
|
||||
))
|
||||
|
||||
return object : ModelSource2 {
|
||||
override fun getLocation(): String = file.absolutePath
|
||||
override fun getLocationURI(): URI = file.absoluteFile.toURI()
|
||||
override fun getRelatedSource(relPath: String?): ModelSource2? = null
|
||||
override fun getInputStream(): InputStream = file.inputStream()
|
||||
}
|
||||
}
|
||||
|
||||
override fun resolveModel(parent: Parent): ModelSource2 =
|
||||
resolveModel(parent.groupId, parent.artifactId, parent.version)
|
||||
|
||||
override fun resolveModel(dependency: org.apache.maven.model.Dependency): ModelSource2 =
|
||||
resolveModel(dependency.groupId, dependency.artifactId, dependency.version)
|
||||
|
||||
override fun addRepository(repository: Repository) {}
|
||||
|
||||
override fun addRepository(repository: Repository, replace: Boolean) {}
|
||||
}
|
||||
|
||||
private const val HEX = "0123456789abcdef"
|
||||
|
||||
private fun sha256(file: File): String = buildString {
|
||||
MessageDigest.getInstance("SHA-256").digest(file.readBytes())
|
||||
.asSequence()
|
||||
.map { it.toInt() }
|
||||
.forEach {
|
||||
append(HEX[it shr 4 and 0x0f])
|
||||
append(HEX[it and 0x0f])
|
||||
}
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
package org.nixos.gradle2nix
|
||||
|
||||
import com.squareup.moshi.Moshi
|
||||
import okio.buffer
|
||||
import okio.sink
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.artifacts.ArtifactRepositoryContainer
|
||||
import org.gradle.api.Task
|
||||
import org.gradle.api.artifacts.Configuration
|
||||
import org.gradle.api.artifacts.ProjectDependency
|
||||
import org.gradle.api.artifacts.dsl.RepositoryHandler
|
||||
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
|
||||
import org.gradle.api.internal.GradleInternal
|
||||
import org.gradle.api.invocation.Gradle
|
||||
import org.gradle.api.tasks.TaskContainer
|
||||
import org.gradle.api.tasks.wrapper.Wrapper
|
||||
import org.gradle.kotlin.dsl.getByName
|
||||
import org.gradle.kotlin.dsl.newInstance
|
||||
import org.gradle.kotlin.dsl.support.serviceOf
|
||||
import org.gradle.kotlin.dsl.withType
|
||||
import org.gradle.plugin.management.PluginRequest
|
||||
@@ -33,15 +30,17 @@ open class Gradle2NixPlugin : Plugin<Gradle> {
|
||||
rootProject.serviceOf<ToolingModelBuilderRegistry>()
|
||||
.register(NixToolingModelBuilder(modelProperties, pluginRequests))
|
||||
|
||||
rootProject.tasks.register("nixModel") {
|
||||
rootProject.tasks.registerCompat("nixModel") {
|
||||
doLast {
|
||||
val outFile = project.mkdir(project.buildDir.resolve("nix")).resolve("model.json")
|
||||
val model = project.buildModel(modelProperties, pluginRequests)
|
||||
outFile.sink().buffer().use { out ->
|
||||
Moshi.Builder().build()
|
||||
.adapter(DefaultBuild::class.java)
|
||||
.indent(" ")
|
||||
.toJson(out, model)
|
||||
outFile.bufferedWriter().use { out ->
|
||||
out.write(
|
||||
Moshi.Builder().build()
|
||||
.adapter(DefaultBuild::class.java)
|
||||
.indent(" ")
|
||||
.toJson(model)
|
||||
)
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
@@ -50,7 +49,13 @@ open class Gradle2NixPlugin : Plugin<Gradle> {
|
||||
}
|
||||
}
|
||||
|
||||
private const val NIX_MODEL_NAME = "org.nixos.gradle2nix.Build"
|
||||
private fun TaskContainer.registerCompat(name: String, configureAction: Task.() -> Unit) {
|
||||
if (GradleVersion.current() >= GradleVersion.version("4.9")) {
|
||||
register(name, configureAction)
|
||||
} else {
|
||||
create(name, configureAction)
|
||||
}
|
||||
}
|
||||
|
||||
private class NixToolingModelBuilder(
|
||||
private val modelProperties: ModelProperties,
|
||||
@@ -100,6 +105,7 @@ private fun Project.buildModel(
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("UnstableApiUsage")
|
||||
private fun Project.buildGradle(): DefaultGradle =
|
||||
with(tasks.getByName<Wrapper>("wrapper")) {
|
||||
DefaultGradle(
|
||||
@@ -113,18 +119,16 @@ private fun Project.buildGradle(): DefaultGradle =
|
||||
}
|
||||
?: throw IllegalStateException(
|
||||
"""
|
||||
Failed to find native-platform jar in ${gradle.gradleHomeDir}.
|
||||
|
||||
Ask Tad to fix this.
|
||||
""".trimIndent()
|
||||
Failed to find native-platform jar in ${gradle.gradleHomeDir}.
|
||||
Ask Tad to fix this.
|
||||
""".trimIndent()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun Project.buildPlugins(pluginRequests: List<PluginRequest>): DefaultDependencies =
|
||||
with(PluginResolver(gradle as GradleInternal, pluginRequests)) {
|
||||
DefaultDependencies(repositories.repositories(), artifacts())
|
||||
}
|
||||
private fun Project.buildPlugins(pluginRequests: List<PluginRequest>): List<DefaultArtifact> {
|
||||
return objects.newInstance<PluginResolver>().resolve(pluginRequests).distinct().sorted()
|
||||
}
|
||||
|
||||
private fun Project.includedBuilds(): List<DefaultIncludedBuild> =
|
||||
gradle.includedBuilds.map {
|
||||
@@ -134,7 +138,7 @@ private fun Project.includedBuilds(): List<DefaultIncludedBuild> =
|
||||
private fun Project.buildProject(
|
||||
explicitConfigurations: List<String>,
|
||||
explicitSubprojects: Collection<Project>,
|
||||
plugins: DefaultDependencies
|
||||
pluginArtifacts: List<DefaultArtifact>
|
||||
): DefaultProject {
|
||||
logger.lifecycle(" Subproject: $path")
|
||||
return DefaultProject(
|
||||
@@ -142,34 +146,33 @@ private fun Project.buildProject(
|
||||
version = version.toString(),
|
||||
path = path,
|
||||
projectDir = projectDir.toRelativeString(rootProject.projectDir),
|
||||
buildscriptDependencies = buildscriptDependencies(plugins),
|
||||
buildscriptDependencies = buildscriptDependencies(pluginArtifacts),
|
||||
projectDependencies = projectDependencies(explicitConfigurations),
|
||||
children = explicitSubprojects.map { it.buildProject(explicitConfigurations, emptyList(), plugins) }
|
||||
children = explicitSubprojects.map {
|
||||
it.buildProject(explicitConfigurations, emptyList(), pluginArtifacts)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private fun Project.buildscriptDependencies(plugins: DefaultDependencies): DefaultDependencies =
|
||||
with(DependencyResolver(buildscript.configurations, buildscript.dependencies)) {
|
||||
DefaultDependencies(
|
||||
repositories = buildscript.repositories.repositories(),
|
||||
artifacts = buildscript.configurations
|
||||
.filter { it.isCanBeResolved }
|
||||
.flatMap { resolveDependencies(it) + resolvePoms(it) }
|
||||
.minus(plugins.artifacts)
|
||||
.distinct()
|
||||
)
|
||||
}
|
||||
private fun Project.buildscriptDependencies(pluginArtifacts: List<DefaultArtifact>): List<DefaultArtifact> {
|
||||
val resolverFactory = ConfigurationResolverFactory(buildscript.repositories)
|
||||
val resolver = resolverFactory.create(buildscript.dependencies)
|
||||
val pluginIds = pluginArtifacts.map(DefaultArtifact::id)
|
||||
return buildscript.configurations
|
||||
.flatMap(resolver::resolve)
|
||||
.distinct()
|
||||
.filter { it.id !in pluginIds }
|
||||
.sorted()
|
||||
}
|
||||
|
||||
private fun Project.projectDependencies(explicitConfigurations: List<String>): DefaultDependencies =
|
||||
with(DependencyResolver(configurations, dependencies)) {
|
||||
val toResolve = collectConfigurations(explicitConfigurations)
|
||||
DefaultDependencies(
|
||||
repositories = repositories.repositories(),
|
||||
artifacts = toResolve.flatMap { resolveDependencies(it) + resolvePoms(it) }
|
||||
.sorted()
|
||||
.distinct()
|
||||
)
|
||||
}
|
||||
private fun Project.projectDependencies(explicitConfigurations: List<String>): List<DefaultArtifact> {
|
||||
val resolverFactory = ConfigurationResolverFactory(repositories)
|
||||
val resolver = resolverFactory.create(dependencies)
|
||||
return collectConfigurations(explicitConfigurations)
|
||||
.flatMap(resolver::resolve)
|
||||
.distinct()
|
||||
.sorted()
|
||||
}
|
||||
|
||||
private fun Project.dependentSubprojects(explicitConfigurations: List<String>): Set<Project> {
|
||||
return collectConfigurations(explicitConfigurations)
|
||||
@@ -190,19 +193,6 @@ private fun Project.collectConfigurations(
|
||||
}
|
||||
}
|
||||
|
||||
private val excludedRepoNames = setOf(
|
||||
"Embedded Kotlin Repository",
|
||||
ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME
|
||||
)
|
||||
|
||||
internal fun RepositoryHandler.repositories() = DefaultRepositories(
|
||||
maven = filterIsInstance<MavenArtifactRepository>()
|
||||
.filter { it.name !in excludedRepoNames }
|
||||
.map { repo ->
|
||||
DefaultMaven(listOf(repo.url.toString()) + repo.artifactUrls.map { it.toString() })
|
||||
}
|
||||
)
|
||||
|
||||
private fun fetchDistSha256(url: String): String {
|
||||
return URL("$url.sha256").openConnection().run {
|
||||
connect()
|
||||
@@ -217,6 +207,9 @@ private val Wrapper.sha256: String
|
||||
return if (GradleVersion.current() < GradleVersion.version("4.5")) {
|
||||
fetchDistSha256(distributionUrl)
|
||||
} else {
|
||||
@Suppress("UnstableApiUsage")
|
||||
distributionSha256Sum ?: fetchDistSha256(distributionUrl)
|
||||
}
|
||||
}
|
||||
|
||||
private const val NIX_MODEL_NAME = "org.nixos.gradle2nix.Build"
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package org.nixos.gradle2nix
|
||||
|
||||
import java.util.Properties
|
||||
import org.gradle.api.Project
|
||||
|
||||
data class ModelProperties(
|
||||
|
||||
@@ -1,101 +1,27 @@
|
||||
package org.nixos.gradle2nix
|
||||
|
||||
import org.gradle.api.artifacts.ExternalModuleDependency
|
||||
import org.gradle.api.internal.GradleInternal
|
||||
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelectorScheme
|
||||
import org.gradle.api.internal.plugins.PluginImplementation
|
||||
import org.gradle.kotlin.dsl.support.serviceOf
|
||||
import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
|
||||
import org.gradle.plugin.management.PluginRequest
|
||||
import org.gradle.plugin.management.internal.PluginRequestInternal
|
||||
import org.gradle.plugin.use.PluginId
|
||||
import org.gradle.plugin.use.internal.PluginDependencyResolutionServices
|
||||
import org.gradle.plugin.use.resolve.internal.ArtifactRepositoriesPluginResolver
|
||||
import org.gradle.plugin.use.resolve.internal.PluginResolution
|
||||
import org.gradle.plugin.use.resolve.internal.PluginResolutionResult
|
||||
import org.gradle.plugin.use.resolve.internal.PluginResolveContext
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class PluginResolver(
|
||||
gradle: GradleInternal,
|
||||
private val pluginRequests: Collection<PluginRequest>
|
||||
internal open class PluginResolver @Inject constructor(
|
||||
pluginDependencyResolutionServices: PluginDependencyResolutionServices
|
||||
) {
|
||||
private val pluginDependencyResolutionServices = gradle.serviceOf<PluginDependencyResolutionServices>()
|
||||
private val versionSelectorScheme = gradle.serviceOf<VersionSelectorScheme>()
|
||||
private val configurations = pluginDependencyResolutionServices.configurationContainer
|
||||
|
||||
private val artifactRepositoriesPluginResolver = ArtifactRepositoriesPluginResolver(
|
||||
pluginDependencyResolutionServices,
|
||||
versionSelectorScheme
|
||||
)
|
||||
private val resolver = ConfigurationResolverFactory(pluginDependencyResolutionServices.resolveRepositoryHandler)
|
||||
.create(pluginDependencyResolutionServices.dependencyHandler)
|
||||
|
||||
val repositories = pluginDependencyResolutionServices.resolveRepositoryHandler
|
||||
|
||||
private val resolver by lazy {
|
||||
DependencyResolver(
|
||||
pluginDependencyResolutionServices.configurationContainer,
|
||||
pluginDependencyResolutionServices.dependencyHandler
|
||||
)
|
||||
}
|
||||
|
||||
private val pluginResult by lazy {
|
||||
PluginResult().apply {
|
||||
for (request in pluginRequests.filterIsInstance<PluginRequestInternal>()) {
|
||||
artifactRepositoriesPluginResolver.resolve(request, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val pluginContext by lazy {
|
||||
PluginContext().apply {
|
||||
for (result in pluginResult.found) result.execute(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun artifacts(): List<DefaultArtifact> {
|
||||
return (resolver.resolveDependencies(pluginContext.dependencies, true) +
|
||||
resolver.resolvePoms(pluginContext.dependencies, true))
|
||||
.sorted()
|
||||
.distinct()
|
||||
}
|
||||
|
||||
private class PluginResult : PluginResolutionResult {
|
||||
val found = mutableSetOf<PluginResolution>()
|
||||
|
||||
override fun notFound(sourceDescription: String?, notFoundMessage: String?) {}
|
||||
|
||||
override fun notFound(
|
||||
sourceDescription: String?,
|
||||
notFoundMessage: String?,
|
||||
notFoundDetail: String?
|
||||
) {
|
||||
}
|
||||
|
||||
override fun isFound(): Boolean = true
|
||||
|
||||
override fun found(sourceDescription: String, pluginResolution: PluginResolution) {
|
||||
found.add(pluginResolution)
|
||||
}
|
||||
}
|
||||
|
||||
private class PluginContext : PluginResolveContext {
|
||||
val dependencies = mutableSetOf<ExternalModuleDependency>()
|
||||
val repositories = mutableSetOf<String>()
|
||||
|
||||
override fun add(plugin: PluginImplementation<*>) {
|
||||
println("add: $plugin")
|
||||
}
|
||||
|
||||
override fun addFromDifferentLoader(plugin: PluginImplementation<*>) {
|
||||
println("addFromDifferentLoader: $plugin")
|
||||
}
|
||||
|
||||
override fun addLegacy(pluginId: PluginId, m2RepoUrl: String, dependencyNotation: Any) {
|
||||
repositories.add(m2RepoUrl)
|
||||
}
|
||||
|
||||
override fun addLegacy(pluginId: PluginId, dependencyNotation: Any) {
|
||||
if (dependencyNotation is ExternalModuleDependency) {
|
||||
dependencies.add(dependencyNotation)
|
||||
fun resolve(pluginRequests: List<PluginRequest>): List<DefaultArtifact> {
|
||||
val markerDependencies = pluginRequests.map {
|
||||
it.module?.let { selector ->
|
||||
DefaultExternalModuleDependency(selector.group, selector.name, selector.version)
|
||||
} ?: it.id.run {
|
||||
DefaultExternalModuleDependency(id, "$id.gradle.plugin", it.version)
|
||||
}
|
||||
}
|
||||
return resolver.resolve(configurations.detachedConfiguration(*markerDependencies.toTypedArray()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
package org.nixos.gradle2nix
|
||||
|
||||
import org.apache.ivy.core.LogOptions
|
||||
import org.apache.ivy.core.cache.ArtifactOrigin
|
||||
import org.apache.ivy.core.cache.CacheResourceOptions
|
||||
import org.apache.ivy.core.cache.DefaultRepositoryCacheManager
|
||||
import org.apache.ivy.core.cache.RepositoryCacheManager
|
||||
import org.apache.ivy.core.module.id.ArtifactRevisionId
|
||||
import org.apache.ivy.core.module.id.ModuleRevisionId
|
||||
import org.apache.ivy.core.resolve.DownloadOptions
|
||||
import org.apache.ivy.core.settings.IvySettings
|
||||
import org.apache.ivy.plugins.repository.url.URLResource
|
||||
import org.apache.ivy.plugins.resolver.IBiblioResolver
|
||||
import org.apache.ivy.plugins.resolver.URLResolver
|
||||
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader
|
||||
import org.codehaus.plexus.util.ReaderFactory
|
||||
import org.codehaus.plexus.util.xml.pull.XmlPullParserException
|
||||
import org.gradle.api.artifacts.repositories.ArtifactRepository
|
||||
import org.gradle.api.artifacts.repositories.IvyArtifactRepository
|
||||
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
|
||||
import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
|
||||
import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver
|
||||
import org.gradle.api.logging.Logger
|
||||
import org.gradle.api.logging.Logging
|
||||
import java.io.IOException
|
||||
import org.apache.ivy.core.module.descriptor.Artifact as IvyArtifact
|
||||
import org.apache.ivy.core.module.descriptor.DefaultArtifact as IvyDefaultArtifact
|
||||
import org.apache.ivy.plugins.resolver.RepositoryResolver as IvyRepositoryResolver
|
||||
|
||||
internal fun ResolutionAwareRepository.repositoryResolver(ivySettings: IvySettings): RepositoryResolver? =
|
||||
when(this) {
|
||||
is MavenArtifactRepository -> MavenResolver(ivySettings, this)
|
||||
is IvyArtifactRepository -> IvyResolver(ivySettings, this)
|
||||
else -> null
|
||||
}
|
||||
|
||||
internal sealed class RepositoryResolver {
|
||||
companion object {
|
||||
@JvmStatic
|
||||
protected val log: Logger = Logging.getLogger("gradle2nix")
|
||||
}
|
||||
|
||||
abstract val ivyResolver: IvyRepositoryResolver
|
||||
|
||||
abstract fun resolve(
|
||||
artifactId: DefaultArtifactIdentifier,
|
||||
sha256: String? = null
|
||||
): DefaultArtifact?
|
||||
}
|
||||
|
||||
internal class MavenResolver(
|
||||
ivySettings: IvySettings,
|
||||
repository: MavenArtifactRepository
|
||||
) : RepositoryResolver() {
|
||||
|
||||
override val ivyResolver: IBiblioResolver = IBiblioResolver().apply {
|
||||
name = repository.name
|
||||
root = repository.url.toString()
|
||||
isM2compatible = true
|
||||
settings = ivySettings
|
||||
setCache(cacheManager(ivySettings, repository).name)
|
||||
}
|
||||
|
||||
override fun resolve(artifactId: DefaultArtifactIdentifier, sha256: String?): DefaultArtifact? {
|
||||
val ivyArtifact: IvyArtifact = artifactId.toArtifact()
|
||||
val origin = ivyResolver.locate(ivyArtifact)?.takeIf(ArtifactOrigin::isExists) ?: return null
|
||||
val hash = sha256 ?: ivyResolver.download(origin, downloadOptions).localFile?.sha256() ?: return null
|
||||
val snapshotVersion: SnapshotVersion? = artifactId.version.snapshotVersion()?.let {
|
||||
findSnapshotVersion(artifactId, it)
|
||||
}
|
||||
return DefaultArtifact(
|
||||
id = artifactId,
|
||||
name = artifactId.filename(snapshotVersion),
|
||||
path = artifactId.repoPath(),
|
||||
timestamp = snapshotVersion?.timestamp,
|
||||
build = snapshotVersion?.build,
|
||||
urls = listOf(origin.location),
|
||||
sha256 = hash
|
||||
)
|
||||
}
|
||||
|
||||
private fun findSnapshotVersion(
|
||||
artifactId: ArtifactIdentifier,
|
||||
snapshotVersion: SnapshotVersion
|
||||
): SnapshotVersion {
|
||||
if (snapshotVersion.timestamp != null) return snapshotVersion
|
||||
val metadataLocation = "${ivyResolver.root}${artifactId.repoPath()}/maven-metadata.xml".toUrl()
|
||||
val metadataFile = ivyResolver.repositoryCacheManager.downloadRepositoryResource(
|
||||
URLResource(metadataLocation, ivyResolver.timeoutConstraint),
|
||||
"maven-metadata",
|
||||
"maven-metadata",
|
||||
"xml",
|
||||
CacheResourceOptions(),
|
||||
ivyResolver.repository
|
||||
).localFile
|
||||
|
||||
if (metadataFile == null) {
|
||||
log.warn("maven-metadata.xml not found for snapshot dependency: $artifactId")
|
||||
return snapshotVersion
|
||||
}
|
||||
|
||||
fun parseError(e: Throwable): Pair<String?, Int?> {
|
||||
log.error("Failed to parse maven-metadata.xml for artifact: $artifactId")
|
||||
log.error("Error was: ${e.message}", e)
|
||||
return null to null
|
||||
}
|
||||
|
||||
val (timestamp: String?, build: Int?) = try {
|
||||
MetadataXpp3Reader()
|
||||
.read(ReaderFactory.newXmlReader(metadataFile))
|
||||
.versioning?.snapshot?.run { timestamp to buildNumber }
|
||||
?: null to null
|
||||
} catch (e: IOException) {
|
||||
parseError(e)
|
||||
} catch (e: XmlPullParserException) {
|
||||
parseError(e)
|
||||
}
|
||||
|
||||
return snapshotVersion.copy(timestamp = timestamp, build = build)
|
||||
}
|
||||
}
|
||||
|
||||
internal class IvyResolver(
|
||||
ivySettings: IvySettings,
|
||||
repository: IvyArtifactRepository
|
||||
) : RepositoryResolver() {
|
||||
|
||||
override val ivyResolver: URLResolver = URLResolver().apply {
|
||||
name = repository.name
|
||||
val ivyResolver = (repository as ResolutionAwareRepository).createResolver() as IvyResolver
|
||||
isM2compatible = ivyResolver.isM2compatible
|
||||
for (p in ivyResolver.ivyPatterns) addIvyPattern(p)
|
||||
for (p in ivyResolver.artifactPatterns) addArtifactPattern(p)
|
||||
settings = ivySettings
|
||||
setCache(cacheManager(ivySettings, repository).name)
|
||||
}
|
||||
|
||||
override fun resolve(artifactId: DefaultArtifactIdentifier, sha256: String?): DefaultArtifact? {
|
||||
val ivyArtifact: IvyArtifact = artifactId.toArtifact()
|
||||
val origin = ivyResolver.locate(ivyArtifact)?.takeIf(ArtifactOrigin::isExists) ?: return null
|
||||
val hash = sha256 ?: ivyResolver.download(origin, downloadOptions).localFile?.sha256() ?: return null
|
||||
return DefaultArtifact(
|
||||
id = DefaultArtifactIdentifier(artifactId),
|
||||
name = artifactId.filename(null),
|
||||
path = artifactId.repoPath(),
|
||||
urls = listOf(origin.location),
|
||||
sha256 = hash
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cacheManager(ivySettings: IvySettings, repository: ArtifactRepository): RepositoryCacheManager {
|
||||
return DefaultRepositoryCacheManager(
|
||||
"${repository.name}-cache",
|
||||
ivySettings,
|
||||
createTempDir("gradle2nix-${repository.name}-cache")
|
||||
).also {
|
||||
ivySettings.addRepositoryCacheManager(it)
|
||||
}
|
||||
}
|
||||
|
||||
private val metadataTypes = setOf("pom", "ivy")
|
||||
|
||||
private fun ArtifactIdentifier.toArtifact(): IvyArtifact {
|
||||
val moduleRevisionId = ModuleRevisionId.newInstance(group, name, version)
|
||||
val artifactRevisionId = ArtifactRevisionId.newInstance(
|
||||
moduleRevisionId,
|
||||
name,
|
||||
type,
|
||||
extension,
|
||||
classifier?.let { mapOf("classifier" to it) }
|
||||
)
|
||||
return IvyDefaultArtifact(artifactRevisionId, null, null, type in metadataTypes)
|
||||
}
|
||||
|
||||
private data class SnapshotVersion(
|
||||
val base: String,
|
||||
val timestamp: String?,
|
||||
val build: Int?
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return if (timestamp != null && build != null) {
|
||||
"$base-$timestamp-$build"
|
||||
} else {
|
||||
"$base-SNAPSHOT"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val SNAPSHOT_REGEX = Regex("^(.*)-SNAPSHOT$")
|
||||
private val SNAPSHOT_TIMESTAMPED_REGEX = Regex("^(.*)-([0-9]{8}.[0-9]{6})-([0-9]+)$")
|
||||
|
||||
private fun String.snapshotVersion(): SnapshotVersion? {
|
||||
return SNAPSHOT_REGEX.find(this)?.destructured?.let { (base) ->
|
||||
SnapshotVersion(base, null, null)
|
||||
} ?: SNAPSHOT_TIMESTAMPED_REGEX.find(this)?.destructured?.let { (base, timestamp, build) ->
|
||||
SnapshotVersion(base, timestamp, build.toInt())
|
||||
}
|
||||
}
|
||||
|
||||
private fun ArtifactIdentifier.repoPath(): String =
|
||||
"${group.replace('.', '/')}/$name/$version"
|
||||
|
||||
private fun ArtifactIdentifier.filename(
|
||||
snapshotVersion: SnapshotVersion?
|
||||
): String = buildString {
|
||||
append(name, "-", snapshotVersion ?: version)
|
||||
if (classifier != null) append("-", classifier)
|
||||
append(".", extension)
|
||||
}
|
||||
|
||||
private val downloadOptions = DownloadOptions().apply { log = LogOptions.LOG_QUIET }
|
||||
21
plugin/src/main/kotlin/org/nixos/gradle2nix/Util.kt
Normal file
21
plugin/src/main/kotlin/org/nixos/gradle2nix/Util.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
package org.nixos.gradle2nix
|
||||
|
||||
import java.io.File
|
||||
import java.net.URL
|
||||
import java.security.MessageDigest
|
||||
|
||||
private const val HEX = "0123456789abcdef"
|
||||
|
||||
internal fun File.sha256(): String = readBytes().sha256()
|
||||
|
||||
private fun ByteArray.sha256() = buildString {
|
||||
MessageDigest.getInstance("SHA-256").digest(this@sha256)
|
||||
.asSequence()
|
||||
.map(Byte::toInt)
|
||||
.forEach {
|
||||
append(HEX[it shr 4 and 0x0f])
|
||||
append(HEX[it and 0x0f])
|
||||
}
|
||||
}
|
||||
|
||||
internal fun String.toUrl(): URL = URL(this)
|
||||
Reference in New Issue
Block a user