Miscellaneous cleanup

This commit is contained in:
Tad Fisher
2024-05-29 17:47:31 -07:00
parent d13b7b0d6d
commit 301c64fa2f
46 changed files with 1512 additions and 1441 deletions

View File

@@ -1,4 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
id("org.jetbrains.kotlin.jvm")
@@ -11,8 +12,6 @@ dependencies {
shadow(kotlin("reflect"))
implementation(project(":model"))
implementation(libs.serialization.json)
testImplementation(libs.kotest.assertions)
testImplementation(libs.kotest.runner)
}
java {
@@ -22,7 +21,10 @@ java {
kotlin {
compilerOptions {
apiVersion.set(KotlinVersion.KOTLIN_1_6)
languageVersion.set(KotlinVersion.KOTLIN_1_6)
jvmTarget.set(JvmTarget.JVM_1_8)
optIn.add("kotlin.RequiresOptIn")
}
}
@@ -56,11 +58,4 @@ tasks {
validatePlugins {
enableStricterValidation.set(true)
}
withType<Test> {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
}
}

View File

@@ -1,7 +1,5 @@
package org.nixos.gradle2nix.dependencygraph
package org.nixos.gradle2nix
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonObject
@@ -11,6 +9,8 @@ import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.internal.artifacts.ivyservice.modulecache.FileStoreAndIndexProvider
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.internal.hash.ChecksumService
import org.gradle.internal.operations.BuildOperationDescriptor
import org.gradle.internal.operations.BuildOperationListener
@@ -26,20 +26,52 @@ import org.nixos.gradle2nix.model.impl.DefaultDependencyCoordinates
import org.nixos.gradle2nix.model.impl.DefaultDependencySet
import org.nixos.gradle2nix.model.impl.DefaultResolvedArtifact
import org.nixos.gradle2nix.model.impl.DefaultResolvedDependency
import java.io.File
import java.util.concurrent.ConcurrentHashMap
class DependencyExtractor(
private val artifactCachesProvider: ArtifactCachesProvider,
private val checksumService: ChecksumService,
private val fileStoreAndIndexProvider: FileStoreAndIndexProvider,
) : BuildOperationListener {
internal abstract class DependencyExtractorService :
BuildService<BuildServiceParameters.None>, BuildOperationListener, AutoCloseable {
var extractor: DependencyExtractor? = null
override fun started(
buildOperation: BuildOperationDescriptor,
startEvent: OperationStartEvent,
) {}
override fun progress(
operationIdentifier: OperationIdentifier,
progressEvent: OperationProgressEvent,
) {}
override fun finished(
buildOperation: BuildOperationDescriptor,
finishEvent: OperationFinishEvent,
) {
extractor?.finished(buildOperation, finishEvent)
}
override fun close() {
extractor = null
}
}
class DependencyExtractor : BuildOperationListener {
private val urls = ConcurrentHashMap<String, Unit>()
override fun started(buildOperation: BuildOperationDescriptor, startEvent: OperationStartEvent) {}
override fun started(
buildOperation: BuildOperationDescriptor,
startEvent: OperationStartEvent,
) {}
override fun progress(operationIdentifier: OperationIdentifier, progressEvent: OperationProgressEvent) {}
override fun progress(
operationIdentifier: OperationIdentifier,
progressEvent: OperationProgressEvent,
) {}
override fun finished(buildOperation: BuildOperationDescriptor, finishEvent: OperationFinishEvent) {
override fun finished(
buildOperation: BuildOperationDescriptor,
finishEvent: OperationFinishEvent,
) {
when (val details = buildOperation.details) {
is ExternalResourceReadBuildOperationType.Details -> urls.computeIfAbsent(details.location) { Unit }
is ExternalResourceReadMetadataBuildOperationType.Details -> urls.computeIfAbsent(details.location) { Unit }
@@ -47,7 +79,11 @@ class DependencyExtractor(
} ?: return
}
fun buildDependencySet(): DependencySet {
fun buildDependencySet(
artifactCachesProvider: ArtifactCachesProvider,
checksumService: ChecksumService,
fileStoreAndIndexProvider: FileStoreAndIndexProvider,
): DependencySet {
val files = mutableMapOf<DependencyCoordinates, MutableMap<File, String>>()
val mappings = mutableMapOf<DependencyCoordinates, Map<String, String>>()
@@ -69,23 +105,28 @@ class DependencyExtractor(
}
return DefaultDependencySet(
dependencies = buildList {
for ((componentId, componentFiles) in files) {
add(DefaultResolvedDependency(
componentId,
buildList {
val remoteMappings = mappings[componentId]
for ((file, url) in componentFiles) {
add(DefaultResolvedArtifact(
remoteMappings?.get(file.name) ?: file.name,
checksumService.sha256(file).toString(),
url
))
}
}
))
}
}
dependencies =
buildList {
for ((componentId, componentFiles) in files) {
add(
DefaultResolvedDependency(
componentId,
buildList {
val remoteMappings = mappings[componentId]
for ((file, url) in componentFiles) {
add(
DefaultResolvedArtifact(
remoteMappings?.get(file.name) ?: file.name,
checksumService.sha256(file).toString(),
url,
),
)
}
},
),
)
}
},
)
}
}
@@ -98,18 +139,19 @@ private fun cachedComponentId(file: File): DependencyCoordinates? {
}
@OptIn(ExperimentalSerializationApi::class)
private fun parseFileMappings(file: File): Map<String, String>? = try {
Json.decodeFromStream<JsonObject>(file.inputStream())
.jsonObject["variants"]?.jsonArray
?.flatMap { it.jsonObject["files"]?.jsonArray ?: emptyList() }
?.map { it.jsonObject }
?.mapNotNull {
val name = it["name"]?.jsonPrimitive?.content ?: return@mapNotNull null
val url = it["url"]?.jsonPrimitive?.content ?: return@mapNotNull null
name to url
}
?.toMap()
?.takeUnless { it.isEmpty() }
} catch (e: Throwable) {
null
}
private fun parseFileMappings(file: File): Map<String, String>? =
try {
Json.decodeFromStream<JsonObject>(file.inputStream())
.jsonObject["variants"]?.jsonArray
?.flatMap { it.jsonObject["files"]?.jsonArray ?: emptyList() }
?.map { it.jsonObject }
?.mapNotNull {
val name = it["name"]?.jsonPrimitive?.content ?: return@mapNotNull null
val url = it["url"]?.jsonPrimitive?.content ?: return@mapNotNull null
name to url
}
?.toMap()
?.takeUnless { it.isEmpty() }
} catch (e: Throwable) {
null
}

View File

@@ -2,53 +2,91 @@
package org.nixos.gradle2nix
import javax.inject.Inject
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.internal.artifacts.ivyservice.modulecache.FileStoreAndIndexProvider
import org.gradle.api.invocation.Gradle
import org.gradle.internal.hash.ChecksumService
import org.gradle.tooling.provider.model.ToolingModelBuilder
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.nixos.gradle2nix.dependencygraph.DependencyExtractor
import org.nixos.gradle2nix.forceresolve.ForceDependencyResolutionPlugin
import org.gradle.util.GradleVersion
import org.nixos.gradle2nix.model.DependencySet
import org.nixos.gradle2nix.util.artifactCachesProvider
import org.nixos.gradle2nix.util.buildOperationListenerManager
import org.nixos.gradle2nix.util.checksumService
import org.nixos.gradle2nix.util.fileStoreAndIndexProvider
import org.nixos.gradle2nix.model.RESOLVE_ALL_TASK
import javax.inject.Inject
abstract class Gradle2NixPlugin @Inject constructor(
private val toolingModelBuilderRegistry: ToolingModelBuilderRegistry
): Plugin<Gradle> {
abstract class Gradle2NixPlugin
@Inject
constructor(
private val toolingModelBuilderRegistry: ToolingModelBuilderRegistry,
) : Plugin<Gradle> {
override fun apply(gradle: Gradle) {
val dependencyExtractor = DependencyExtractor()
override fun apply(gradle: Gradle) {
val dependencyExtractor = DependencyExtractor(
gradle.artifactCachesProvider,
gradle.checksumService,
gradle.fileStoreAndIndexProvider,
)
toolingModelBuilderRegistry.register(
DependencySetModelBuilder(
dependencyExtractor,
gradle.artifactCachesProvider,
gradle.checksumService,
gradle.fileStoreAndIndexProvider,
),
)
toolingModelBuilderRegistry.register(DependencySetModelBuilder(dependencyExtractor))
if (GradleVersion.current() < GradleVersion.version("8.0")) {
val extractor = DependencyExtractor()
gradle.buildOperationListenerManager.addListener(extractor)
gradle.buildOperationListenerManager.addListener(dependencyExtractor)
@Suppress("DEPRECATION")
gradle.buildFinished {
gradle.buildOperationListenerManager.removeListener(extractor)
}
} else {
val serviceProvider =
gradle.sharedServices.registerIfAbsent(
"nixDependencyExtractor",
DependencyExtractorService::class.java,
).map { service ->
service.apply { extractor = dependencyExtractor }
}
// Configuration caching is not enabled with dependency verification so this is fine for now.
// Gradle 9.x might remove this though.
@Suppress("DEPRECATION")
gradle.buildFinished {
gradle.buildOperationListenerManager.removeListener(dependencyExtractor)
gradle.buildEventListenerRegistryInternal.onOperationCompletion(serviceProvider)
}
gradle.projectsEvaluated {
val resolveAll = gradle.rootProject.tasks.register(RESOLVE_ALL_TASK)
// Depend on "dependencies" task in all projects
gradle.allprojects { project ->
val resolveProject = project.createResolveTask()
resolveAll.configure { it.dependsOn(resolveProject) }
}
// Depend on all 'resolveBuildDependencies' task in each included build
gradle.includedBuilds.forEach { includedBuild ->
resolveAll.configure {
it.dependsOn(includedBuild.task(":$RESOLVE_ALL_TASK"))
}
}
}
}
gradle.pluginManager.apply(ForceDependencyResolutionPlugin::class.java)
}
}
internal class DependencySetModelBuilder(
private val dependencyExtractor: DependencyExtractor,
private val artifactCachesProvider: ArtifactCachesProvider,
private val checksumService: ChecksumService,
private val fileStoreAndIndexProvider: FileStoreAndIndexProvider,
) : ToolingModelBuilder {
override fun canBuild(modelName: String): Boolean = modelName == DependencySet::class.qualifiedName
override fun buildAll(modelName: String, project: Project): DependencySet {
return dependencyExtractor.buildDependencySet()
override fun buildAll(
modelName: String,
project: Project,
): DependencySet {
return dependencyExtractor.buildDependencySet(
artifactCachesProvider,
checksumService,
fileStoreAndIndexProvider,
)
}
}

View File

@@ -0,0 +1,42 @@
package org.nixos.gradle2nix
import org.gradle.api.artifacts.Configuration
import org.gradle.api.internal.GradleInternal
import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.internal.artifacts.ivyservice.modulecache.FileStoreAndIndexProvider
import org.gradle.api.invocation.Gradle
import org.gradle.internal.build.event.BuildEventListenerRegistryInternal
import org.gradle.internal.hash.ChecksumService
import org.gradle.internal.operations.BuildOperationListenerManager
import org.gradle.util.GradleVersion
import java.lang.reflect.Method
internal inline val Gradle.artifactCachesProvider: ArtifactCachesProvider
get() = service()
internal inline val Gradle.buildEventListenerRegistryInternal: BuildEventListenerRegistryInternal
get() = service()
internal inline val Gradle.buildOperationListenerManager: BuildOperationListenerManager
get() = service()
internal inline val Gradle.checksumService: ChecksumService
get() = service()
internal inline val Gradle.fileStoreAndIndexProvider: FileStoreAndIndexProvider
get() = service()
internal inline fun <reified T> Gradle.service(): T = (this as GradleInternal).services.get(T::class.java)
private val canSafelyBeResolvedMethod: Method? =
try {
val dc = Class.forName("org.gradle.internal.deprecation.DeprecatableConfiguration")
dc.getMethod("canSafelyBeResolved")
} catch (e: ReflectiveOperationException) {
null
}
internal fun Configuration.canSafelyBeResolved(): Boolean = canSafelyBeResolvedMethod?.invoke(this) as? Boolean ?: isCanBeResolved
internal val gradleVersionIsAtLeast8: Boolean =
GradleVersion.current() >= GradleVersion.version("8.0")

View File

@@ -0,0 +1,66 @@
package org.nixos.gradle2nix
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.file.FileCollection
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.gradle.internal.serialization.Cached
import org.gradle.work.DisableCachingByDefault
import org.nixos.gradle2nix.model.RESOLVE_PROJECT_TASK
import javax.inject.Inject
internal fun Project.createResolveTask(): TaskProvider<out Task> {
return if (gradleVersionIsAtLeast8) {
tasks.register(RESOLVE_PROJECT_TASK, ResolveProjectDependenciesTask::class.java)
} else {
tasks.register(RESOLVE_PROJECT_TASK, LegacyResolveProjectDependenciesTask::class.java)
}
}
@DisableCachingByDefault(because = "Not worth caching")
sealed class AbstractResolveProjectDependenciesTask : DefaultTask() {
@Internal
protected fun getReportableConfigurations(): List<Configuration> {
return project.configurations.filter { it.canSafelyBeResolved() }
}
}
@DisableCachingByDefault(because = "Not worth caching")
abstract class LegacyResolveProjectDependenciesTask : AbstractResolveProjectDependenciesTask() {
@TaskAction
fun action() {
for (configuration in getReportableConfigurations()) {
configuration.incoming.resolutionResult.root
}
}
}
@DisableCachingByDefault(because = "Not worth caching")
abstract class ResolveProjectDependenciesTask
@Inject
constructor(
private val objects: ObjectFactory,
) : AbstractResolveProjectDependenciesTask() {
private val artifactFiles = Cached.of { artifactFiles() }
private fun artifactFiles(): FileCollection {
return objects.fileCollection().from(
getReportableConfigurations().map { configuration ->
configuration.incoming.artifactView { viewConfiguration ->
viewConfiguration.componentFilter { it is ModuleComponentIdentifier }
}.files
},
)
}
@TaskAction
fun action() {
artifactFiles.get().count()
}
}

View File

@@ -1,15 +0,0 @@
package org.nixos.gradle2nix.forceresolve
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.Internal
import org.gradle.work.DisableCachingByDefault
import org.nixos.gradle2nix.util.canSafelyBeResolved
@DisableCachingByDefault(because = "Not worth caching")
abstract class AbstractResolveProjectDependenciesTask : DefaultTask() {
@Internal
protected fun getReportableConfigurations(): List<Configuration> {
return project.configurations.filter { it.canSafelyBeResolved() }
}
}

View File

@@ -1,63 +0,0 @@
package org.nixos.gradle2nix.forceresolve
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.invocation.Gradle
import org.gradle.api.tasks.TaskProvider
import org.gradle.util.GradleVersion
import org.nixos.gradle2nix.model.RESOLVE_ALL_TASK
import org.nixos.gradle2nix.model.RESOLVE_PROJECT_TASK
// TODO: Rename these
/**
* Adds a task to resolve all dependencies in a Gradle build tree.
*/
class ForceDependencyResolutionPlugin : Plugin<Gradle> {
override fun apply(gradle: Gradle) {
gradle.projectsEvaluated {
val resolveAllDeps = gradle.rootProject.tasks.register(RESOLVE_ALL_TASK)
// Depend on "dependencies" task in all projects
gradle.allprojects { project ->
val projectTaskFactory = getResolveProjectDependenciesTaskFactory()
val resolveProjectDeps = projectTaskFactory.create(project)
resolveAllDeps.configure { it.dependsOn(resolveProjectDeps) }
}
// Depend on all 'resolveBuildDependencies' task in each included build
gradle.includedBuilds.forEach { includedBuild ->
resolveAllDeps.configure {
it.dependsOn(includedBuild.task(":$RESOLVE_ALL_TASK"))
}
}
}
}
private fun getResolveProjectDependenciesTaskFactory(): ResolveProjectDependenciesTaskFactory {
val gradleVersion = GradleVersion.current()
val gradle8 = GradleVersion.version("8.0")
return if (gradleVersion >= gradle8) {
ResolveProjectDependenciesTaskFactory.Current
} else {
ResolveProjectDependenciesTaskFactory.Legacy
}
}
private sealed interface ResolveProjectDependenciesTaskFactory {
fun create(project: Project): TaskProvider<out Task>
data object Current : ResolveProjectDependenciesTaskFactory {
override fun create(project: Project): TaskProvider<out Task> {
return project.tasks.register(RESOLVE_PROJECT_TASK, ResolveProjectDependenciesTask::class.java)
}
}
data object Legacy : ResolveProjectDependenciesTaskFactory {
override fun create(project: Project): TaskProvider<out Task> {
return project.tasks.register(RESOLVE_PROJECT_TASK, LegacyResolveProjectDependenciesTask::class.java)
}
}
}
}

View File

@@ -1,16 +0,0 @@
package org.nixos.gradle2nix.forceresolve
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.tasks.TaskAction
import org.gradle.work.DisableCachingByDefault
@DisableCachingByDefault(because = "Not worth caching")
abstract class LegacyResolveProjectDependenciesTask : AbstractResolveProjectDependenciesTask() {
@TaskAction
fun action() {
for (configuration in getReportableConfigurations()) {
configuration.incoming.resolutionResult.root
}
}
}

View File

@@ -1,31 +0,0 @@
package org.nixos.gradle2nix.forceresolve
import javax.inject.Inject
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.file.FileCollection
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.TaskAction
import org.gradle.internal.serialization.Cached
import org.gradle.work.DisableCachingByDefault
@DisableCachingByDefault(because = "Not worth caching")
abstract class ResolveProjectDependenciesTask @Inject constructor(
private val objects: ObjectFactory
): AbstractResolveProjectDependenciesTask() {
private val artifactFiles = Cached.of { artifactFiles() }
private fun artifactFiles(): FileCollection {
return objects.fileCollection().from(
getReportableConfigurations().map { configuration ->
configuration.incoming.artifactView { viewConfiguration ->
viewConfiguration.componentFilter { it is ModuleComponentIdentifier }
}.files
}
)
}
@TaskAction
fun action() {
artifactFiles.get().count()
}
}

View File

@@ -1,36 +0,0 @@
package org.nixos.gradle2nix.util
import java.lang.reflect.Method
import org.gradle.api.artifacts.Configuration
import org.gradle.api.internal.GradleInternal
import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.internal.artifacts.ivyservice.modulecache.FileStoreAndIndexProvider
import org.gradle.api.invocation.Gradle
import org.gradle.internal.hash.ChecksumService
import org.gradle.internal.operations.BuildOperationAncestryTracker
import org.gradle.internal.operations.BuildOperationListenerManager
internal inline val Gradle.artifactCachesProvider: ArtifactCachesProvider
get() = service()
internal inline val Gradle.buildOperationListenerManager: BuildOperationListenerManager
get() = service()
internal inline val Gradle.checksumService: ChecksumService
get() = service()
internal inline val Gradle.fileStoreAndIndexProvider: FileStoreAndIndexProvider
get() = service()
internal inline fun <reified T> Gradle.service(): T =
(this as GradleInternal).services.get(T::class.java)
private val canSafelyBeResolvedMethod: Method? = try {
val dc = Class.forName("org.gradle.internal.deprecation.DeprecatableConfiguration")
dc.getMethod("canSafelyBeResolved")
} catch (e: ReflectiveOperationException) {
null
}
internal fun Configuration.canSafelyBeResolved(): Boolean =
canSafelyBeResolvedMethod?.invoke(this) as? Boolean ?: isCanBeResolved