Separate plugins for different Gradle APIs

This commit is contained in:
Tad Fisher
2024-06-04 13:11:18 -07:00
parent a935331795
commit 85cebdd557
40 changed files with 1258 additions and 3993 deletions

View File

@@ -16,4 +16,5 @@ ktlint_function_naming_ignore_when_annotated_with = [unset]
ktlint_function_signature_body_expression_wrapping = multiline ktlint_function_signature_body_expression_wrapping = multiline
ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2 ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2
ktlint_ignore_back_ticked_identifier = false ktlint_ignore_back_ticked_identifier = false
ktlint_standard_filename = disabled
max_line_length = 140 max_line_length = 140

View File

@@ -12,13 +12,14 @@ dependencies {
implementation(project(":model")) implementation(project(":model"))
implementation(libs.clikt) implementation(libs.clikt)
implementation(libs.gradle.toolingApi) implementation(libs.gradle.toolingApi)
implementation(libs.kotlin.stdlib)
implementation(libs.kotlinx.coroutines.core) implementation(libs.kotlinx.coroutines.core)
implementation(libs.serialization.json) implementation(libs.serialization.json)
runtimeOnly(libs.slf4j.simple) runtimeOnly(libs.slf4j.simple)
"share"(project(":plugin", configuration = "shadow")) { "share"(project(":plugin:base", configuration = "shadow"))
isTransitive = false "share"(project(":plugin:gradle80", configuration = "shadow"))
} "share"(project(":plugin:gradle81", configuration = "shadow"))
testImplementation(libs.kotest.assertions) testImplementation(libs.kotest.assertions)
testImplementation(libs.kotest.runner) testImplementation(libs.kotest.runner)
@@ -37,7 +38,6 @@ application {
applicationDistribution applicationDistribution
.from(configurations.named("share")) .from(configurations.named("share"))
.into("share") .into("share")
.rename("plugin.*\\.jar", "plugin.jar")
} }
java { java {

View File

@@ -1,6 +1,14 @@
import org.gradle.util.GradleVersion
initscript { initscript {
dependencies { dependencies {
classpath files("plugin.jar") if (GradleVersion.current() >= GradleVersion.version("8.1")) {
classpath files("plugin-gradle81.jar")
} else if (GradleVersion.current() >= GradleVersion.version("8.0")) {
classpath files("plugin-gradle80.jar")
} else {
classpath files("plugin-base.jar")
}
} }
} }

View File

@@ -1,9 +1,6 @@
plugins { plugins {
base base
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.kotlin.serialization) apply false
alias(libs.plugins.pluginPublish) apply false
alias(libs.plugins.shadow) apply false
} }
group = "org.nixos.gradle2nix" group = "org.nixos.gradle2nix"

View File

@@ -0,0 +1,8 @@
plugins {
`kotlin-dsl`
}
dependencies {
implementation(libs.gradle.kotlin)
implementation(libs.gradle.shadow)
}

View File

@@ -0,0 +1,14 @@
@file:Suppress("UnstableApiUsage")
dependencyResolutionManagement {
repositories {
mavenCentral()
gradlePluginPortal()
}
versionCatalogs {
register("libs") {
from(files("../gradle/libs.versions.toml"))
}
}
}

View File

@@ -0,0 +1,35 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
id("org.jetbrains.kotlin.jvm")
}
dependencies {
compileOnly(kotlin("stdlib"))
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlin.compilerOptions {
@Suppress("DEPRECATION") // we can't use api version greater than 1.4 as minimal supported Gradle version uses kotlin-stdlib 1.4
apiVersion.set(KotlinVersion.KOTLIN_1_4)
@Suppress("DEPRECATION") // we can't use language version greater than 1.5 as minimal supported Gradle embeds Kotlin 1.4
languageVersion.set(KotlinVersion.KOTLIN_1_5)
jvmTarget.set(JvmTarget.JVM_1_8)
optIn.add("kotlin.RequiresOptIn")
freeCompilerArgs.addAll(
listOf(
"-Xskip-prerelease-check",
"-Xsuppress-version-warnings",
// We have to override the default value for `-Xsam-conversions` to `class`
// otherwise the compiler would compile lambdas using invokedynamic,
// such lambdas are not serializable so are not compatible with Gradle configuration cache.
// It doesn't lead to a significant difference in binaries sizes, and previously (before LV 1.5) the `class` value was set by default.
"-Xsam-conversions=class",
),
)
}

View File

@@ -0,0 +1,60 @@
@file:Suppress("UnstableApiUsage")
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
plugins {
id("gradle-kotlin-conventions")
id("io.github.goooler.shadow")
`java-gradle-plugin`
}
dependencies {
}
configure<GradlePluginDevelopmentExtension> {
plugins {
register("gradle2nix") {
id = "org.nixos.gradle2nix"
displayName = "gradle2nix"
description = "Expose Gradle tooling model for the gradle2nix tool"
implementationClass = "org.nixos.gradle2nix.Gradle2NixPlugin"
}
}
}
configurations {
"api" {
dependencies.remove(project.dependencies.gradleApi())
}
}
tasks {
"jar" {
enabled = false
}
named("shadowJar", ShadowJar::class) {
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
val mode644 = 0b110100100
val mode755 = 0b111101101
fileMode = mode644
dirMode = mode755
filesMatching("**/bin/*") { mode = mode755 }
filesMatching("**/bin/*.bat") { mode = mode644 }
relocate("kotlinx", "${project.group}.shadow.kotlinx")
relocate("org.intellij", "${project.group}.shadow.intellij")
relocate("org.jetbrains", "${project.group}.shadow.jetbrains")
dependencies {
exclude { it.moduleGroup == "org.jetbrains.kotlin" && it.moduleName == "kotlin-stdlib" }
exclude { it.moduleGroup == "org.jetbrains.kotlin" && it.moduleName == "kotlin-stdlib-common" }
exclude { it.moduleGroup == "org.jetbrains.kotlin" && it.moduleName == "kotlin-stdlib-jdk7" }
exclude { it.moduleGroup == "org.jetbrains.kotlin" && it.moduleName == "kotlin-stdlib-jdk8" }
exclude { it.moduleGroup == "org.jetbrains.kotlin" && it.moduleName == "kotlin-reflect" }
exclude { it.moduleGroup == "org.jetbrains.kotlin" && it.moduleName == "kotlin-script-runtime" }
}
}
}

View File

@@ -42,7 +42,7 @@
formatter = pkgs.writeShellScriptBin "gradle2nix-fmt" '' formatter = pkgs.writeShellScriptBin "gradle2nix-fmt" ''
fail=0 fail=0
${lib.getExe pkgs.nixfmt-rfc-style} $@ || fail=1 ${lib.getExe pkgs.nixfmt-rfc-style} $@ || fail=1
${lib.getExe pkgs.ktlint} --relative -l warn -F || fail=1 ${lib.getExe pkgs.git} ls-files -z '*.kt' '*.kts' | ${lib.getExe pkgs.ktlint} --relative -l warn -F --patterns-from-stdin= || fail=1
[ $fail -eq 0 ] || echo "Formatting failed." >&2 [ $fail -eq 0 ] || echo "Formatting failed." >&2
exit $fail exit $fail
''; '';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,4 @@
kotlin.stdlib.default.dependency=false
org.gradle.jvmargs='-Dfile.encoding=UTF-8' org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.caching=true org.gradle.caching=true
org.gradle.configuration-cache=true org.gradle.configuration-cache=true

View File

@@ -1,27 +1,34 @@
[versions] [versions]
gradle = "8.7" gradle = "8.7"
junit = "5.8.2" junit = "5.8.2"
kotlin = "1.9.24" # Intentionally using the version embedded in Gradle to reduce dependency count
# See https://docs.gradle.org/current/userguide/compatibility.html#kotlin
kotlin = { strictly = "1.9.22" }
ktor = "2.3.11" ktor = "2.3.11"
kotest = "5.9.0" kotest = "5.9.0"
shadow = "8.1.7"
[libraries] [libraries]
clikt = "com.github.ajalt.clikt:clikt:4.4.0" clikt = "com.github.ajalt.clikt:clikt:4.4.0"
gradle-api-69 = "dev.gradleplugins:gradle-api:6.9"
gradle-api-80 = "dev.gradleplugins:gradle-api:8.0"
gradle-api-81 = "dev.gradleplugins:gradle-api:8.1"
gradle-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
gradle-shadow = { module = "io.github.goooler.shadow:shadow-gradle-plugin", version.ref = "shadow" }
gradle-toolingApi = { module = "org.gradle:gradle-tooling-api", version.ref = "gradle" } gradle-toolingApi = { module = "org.gradle:gradle-tooling-api", version.ref = "gradle" }
kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" } kotest-assertions = { module = "io.kotest:kotest-assertions-core", version.ref = "kotest" }
kotest-runner = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" } kotest-runner = { module = "io.kotest:kotest-runner-junit5", version.ref = "kotest" }
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlinx-coroutines-core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1" kotlinx-coroutines-core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" } ktor-server-core = { module = "io.ktor:ktor-server-core", version.ref = "ktor" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" } ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
okio = "com.squareup.okio:okio:3.9.0" okio = "com.squareup.okio:okio:3.9.0"
serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3" serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3"
slf4j-simple = "org.slf4j:slf4j-simple:1.7.36" slf4j-simple = "org.slf4j:slf4j-simple:1.7.36"
xmlutil = "io.github.pdvrieze.xmlutil:serialization-jvm:0.90.0-RC1"
[plugins] [plugins]
pluginPublish = { id = "com.gradle.plugin-publish", version = "1.2.1" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
shadow = { id = "com.github.johnrengelman.shadow", version = "8.1.1" } shadow = { id = "io.github.goooler.shadow", version.ref = "shadow" }
[bundles] [bundles]

View File

@@ -1,16 +1,34 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins { plugins {
id("org.jetbrains.kotlin.jvm") id("org.jetbrains.kotlin.jvm")
} }
dependencies {
compileOnly(libs.kotlin.stdlib)
}
java { java {
sourceCompatibility = JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
} }
kotlin { kotlin.compilerOptions {
compilerOptions { @Suppress("DEPRECATION") // we can't use api version greater than 1.4 as minimal supported Gradle version uses kotlin-stdlib 1.4
jvmTarget.set(JvmTarget.JVM_1_8) apiVersion.set(KotlinVersion.KOTLIN_1_4)
} @Suppress("DEPRECATION") // we can't use language version greater than 1.5 as minimal supported Gradle embeds Kotlin 1.4
languageVersion.set(KotlinVersion.KOTLIN_1_5)
jvmTarget.set(JvmTarget.JVM_1_8)
freeCompilerArgs.addAll(
listOf(
"-Xskip-prerelease-check",
"-Xsuppress-version-warnings",
// We have to override the default value for `-Xsam-conversions` to `class`
// otherwise the compiler would compile lambdas using invokedynamic,
// such lambdas are not serializable so are not compatible with Gradle configuration cache.
// It doesn't lead to a significant difference in binaries sizes, and previously (before LV 1.5) the `class` value was set by default.
"-Xsam-conversions=class",
),
)
} }

View File

@@ -0,0 +1,12 @@
plugins {
`plugin-conventions`
}
dependencies {
implementation(project(":plugin:common"))
compileOnly(libs.gradle.api.get69())
}
tasks.shadowJar {
archiveFileName = "plugin-base.jar"
}

View File

@@ -0,0 +1,18 @@
package org.nixos.gradle2nix
import org.gradle.api.invocation.Gradle
import org.gradle.internal.operations.BuildOperationListenerManager
object DependencyExtractorApplierBase : DependencyExtractorApplier {
override fun apply(
gradle: Gradle,
extractor: DependencyExtractor,
) {
val buildOperationListenerManager = gradle.service<BuildOperationListenerManager>()
buildOperationListenerManager.addListener(extractor)
gradle.buildFinished {
buildOperationListenerManager.removeListener(extractor)
}
}
}

View File

@@ -0,0 +1,7 @@
package org.nixos.gradle2nix
abstract class Gradle2NixPlugin : AbstractGradle2NixPlugin(
GradleCacheAccessFactoryBase,
DependencyExtractorApplierBase,
ResolveAllArtifactsApplierBase,
)

View File

@@ -0,0 +1,18 @@
package org.nixos.gradle2nix
import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.invocation.Gradle
object GradleCacheAccessFactoryBase : GradleCacheAccessFactory {
override fun create(gradle: Gradle): GradleCacheAccess {
return GradleCacheAccessBase(gradle)
}
}
class GradleCacheAccessBase(gradle: Gradle) : GradleCacheAccess {
private val artifactCachesProvider = gradle.service<ArtifactCachesProvider>()
override fun useCache(block: () -> Unit) {
artifactCachesProvider.writableCacheLockingManager.useCache(block)
}
}

View File

@@ -0,0 +1,20 @@
package org.nixos.gradle2nix
import org.gradle.api.Project
import org.gradle.api.tasks.TaskAction
import org.gradle.api.tasks.TaskProvider
import org.nixos.gradle2nix.model.RESOLVE_PROJECT_TASK
object ResolveAllArtifactsApplierBase : AbstractResolveAllArtifactsApplier() {
override fun Project.registerProjectTask(): TaskProvider<*> =
tasks.register(RESOLVE_PROJECT_TASK, ResolveProjectDependenciesTaskBase::class.java)
}
abstract class ResolveProjectDependenciesTaskBase : ResolveProjectDependenciesTask() {
@TaskAction
fun action() {
for (configuration in getReportableConfigurations()) {
configuration.artifactFiles().count()
}
}
}

View File

@@ -1,61 +0,0 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins {
id("org.jetbrains.kotlin.jvm")
id("com.gradle.plugin-publish")
id("com.github.johnrengelman.shadow")
}
dependencies {
shadow(kotlin("stdlib-jdk8"))
shadow(kotlin("reflect"))
implementation(project(":model"))
implementation(libs.serialization.json)
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlin {
compilerOptions {
apiVersion.set(KotlinVersion.KOTLIN_1_6)
languageVersion.set(KotlinVersion.KOTLIN_1_6)
jvmTarget.set(JvmTarget.JVM_1_8)
optIn.add("kotlin.RequiresOptIn")
}
}
gradlePlugin {
plugins {
register("gradle2nix") {
id = "org.nixos.gradle2nix"
displayName = "gradle2nix"
description = "Expose Gradle tooling model for the gradle2nix tool"
implementationClass = "org.nixos.gradle2nix.Gradle2NixPlugin"
}
}
}
tasks {
jar {
manifest {
attributes["Implementation-Version"] = archiveVersion.get()
attributes["Implementation-Title"] = "Gradle2Nix Plugin"
attributes["Implementation-Vendor"] = "Tad Fisher"
}
}
shadowJar {
archiveClassifier.set("")
relocate("kotlin", "${project.group}.shadow.kotlin")
relocate("org.intellij", "${project.group}.shadow.intellij")
relocate("org.jetbrains", "${project.group}.shadow.jetbrains")
}
validatePlugins {
enableStricterValidation.set(true)
}
}

View File

@@ -0,0 +1,9 @@
plugins {
`gradle-kotlin-conventions`
}
dependencies {
compileOnly(libs.gradle.api.get69())
api(project(":model"))
implementation(libs.serialization.json)
}

View File

@@ -7,10 +7,8 @@ import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive 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.internal.artifacts.ivyservice.modulecache.FileStoreAndIndexProvider
import org.gradle.api.services.BuildService import org.gradle.api.invocation.Gradle
import org.gradle.api.services.BuildServiceParameters
import org.gradle.internal.hash.ChecksumService import org.gradle.internal.hash.ChecksumService
import org.gradle.internal.operations.BuildOperationDescriptor import org.gradle.internal.operations.BuildOperationDescriptor
import org.gradle.internal.operations.BuildOperationListener import org.gradle.internal.operations.BuildOperationListener
@@ -29,30 +27,11 @@ import org.nixos.gradle2nix.model.impl.DefaultResolvedDependency
import java.io.File import java.io.File
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
internal abstract class DependencyExtractorService : interface DependencyExtractorApplier {
BuildService<BuildServiceParameters.None>, BuildOperationListener, AutoCloseable { fun apply(
var extractor: DependencyExtractor? = null gradle: Gradle,
extractor: DependencyExtractor,
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 { class DependencyExtractor : BuildOperationListener {
@@ -80,14 +59,14 @@ class DependencyExtractor : BuildOperationListener {
} }
fun buildDependencySet( fun buildDependencySet(
artifactCachesProvider: ArtifactCachesProvider, cacheAccess: GradleCacheAccess,
checksumService: ChecksumService, checksumService: ChecksumService,
fileStoreAndIndexProvider: FileStoreAndIndexProvider, fileStoreAndIndexProvider: FileStoreAndIndexProvider,
): DependencySet { ): DependencySet {
val files = mutableMapOf<DependencyCoordinates, MutableMap<File, String>>() val files = mutableMapOf<DependencyCoordinates, MutableMap<File, String>>()
val mappings = mutableMapOf<DependencyCoordinates, Map<String, String>>() val mappings = mutableMapOf<DependencyCoordinates, Map<String, String>>()
artifactCachesProvider.writableCacheAccessCoordinator.useCache { cacheAccess.useCache {
for ((url, _) in urls) { for ((url, _) in urls) {
fileStoreAndIndexProvider.externalResourceIndex.lookup(url)?.let { cached -> fileStoreAndIndexProvider.externalResourceIndex.lookup(url)?.let { cached ->
cached.cachedFile?.let { file -> cached.cachedFile?.let { file ->
@@ -131,6 +110,8 @@ class DependencyExtractor : BuildOperationListener {
} }
} }
private fun <T> buildList(block: MutableList<T>.() -> Unit): List<T> = mutableListOf<T>().apply(block).toList()
private fun cachedComponentId(file: File): DependencyCoordinates? { private fun cachedComponentId(file: File): DependencyCoordinates? {
val parts = file.invariantSeparatorsPath.split('/') val parts = file.invariantSeparatorsPath.split('/')
if (parts.size < 6) return null if (parts.size < 6) return null
@@ -148,7 +129,7 @@ private fun parseFileMappings(file: File): Map<String, String>? =
?.mapNotNull { ?.mapNotNull {
val name = it["name"]?.jsonPrimitive?.content ?: return@mapNotNull null val name = it["name"]?.jsonPrimitive?.content ?: return@mapNotNull null
val url = it["url"]?.jsonPrimitive?.content ?: return@mapNotNull null val url = it["url"]?.jsonPrimitive?.content ?: return@mapNotNull null
name to url if (name != url) name to url else null
} }
?.toMap() ?.toMap()
?.takeUnless { it.isEmpty() } ?.takeUnless { it.isEmpty() }

View File

@@ -0,0 +1,27 @@
package org.nixos.gradle2nix
import org.gradle.api.Project
import org.gradle.api.internal.artifacts.ivyservice.modulecache.FileStoreAndIndexProvider
import org.gradle.internal.hash.ChecksumService
import org.gradle.tooling.provider.model.ToolingModelBuilder
import org.nixos.gradle2nix.model.DependencySet
class DependencySetModelBuilder(
private val dependencyExtractor: DependencyExtractor,
private val cacheAccess: GradleCacheAccess,
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(
cacheAccess,
checksumService,
fileStoreAndIndexProvider,
)
}
}

View File

@@ -0,0 +1,32 @@
package org.nixos.gradle2nix
import org.gradle.api.Plugin
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.ToolingModelBuilderRegistry
abstract class AbstractGradle2NixPlugin(
private val cacheAccessFactory: GradleCacheAccessFactory,
private val dependencyExtractorApplier: DependencyExtractorApplier,
private val resolveAllArtifactsApplier: ResolveAllArtifactsApplier,
) : Plugin<Gradle> {
override fun apply(gradle: Gradle) {
val extractor = DependencyExtractor()
gradle.service<ToolingModelBuilderRegistry>().register(
DependencySetModelBuilder(
extractor,
cacheAccessFactory.create(gradle),
gradle.service<ChecksumService>(),
gradle.service<FileStoreAndIndexProvider>(),
),
)
dependencyExtractorApplier.apply(gradle, extractor)
gradle.projectsEvaluated {
resolveAllArtifactsApplier.apply(gradle)
}
}
}

View File

@@ -0,0 +1,11 @@
package org.nixos.gradle2nix
import org.gradle.api.invocation.Gradle
fun interface GradleCacheAccessFactory {
fun create(gradle: Gradle): GradleCacheAccess
}
interface GradleCacheAccess {
fun useCache(block: () -> Unit)
}

View File

@@ -0,0 +1,6 @@
package org.nixos.gradle2nix
import org.gradle.api.internal.GradleInternal
import org.gradle.api.invocation.Gradle
inline fun <reified T> Gradle.service(): T = (this as GradleInternal).services.get(T::class.java)

View File

@@ -0,0 +1,50 @@
package org.nixos.gradle2nix
import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.file.FileCollection
import org.gradle.api.invocation.Gradle
import org.gradle.api.tasks.Internal
import org.gradle.api.tasks.TaskProvider
import org.gradle.internal.deprecation.DeprecatableConfiguration
import org.nixos.gradle2nix.model.RESOLVE_ALL_TASK
fun interface ResolveAllArtifactsApplier {
fun apply(gradle: Gradle)
}
abstract class AbstractResolveAllArtifactsApplier : ResolveAllArtifactsApplier {
abstract fun Project.registerProjectTask(): TaskProvider<*>
final override fun apply(gradle: Gradle) {
val resolveAll = gradle.rootProject.tasks.register(RESOLVE_ALL_TASK)
// Depend on "dependencies" task in all projects
gradle.allprojects { project ->
val resolveProject = project.registerProjectTask()
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"))
}
}
}
}
abstract class ResolveProjectDependenciesTask : DefaultTask() {
@Internal
protected fun getReportableConfigurations(): List<Configuration> {
return project.configurations.filter { (it as? DeprecatableConfiguration)?.canSafelyBeResolved() ?: true }
}
protected fun Configuration.artifactFiles(): FileCollection {
return incoming.artifactView { viewConfiguration ->
viewConfiguration.componentFilter { it is ModuleComponentIdentifier }
}.files
}
}

View File

@@ -0,0 +1,8 @@
plugins {
`gradle-kotlin-conventions`
}
dependencies {
compileOnly(libs.gradle.api.get80())
api(project(":plugin:common"))
}

View File

@@ -0,0 +1,57 @@
package org.nixos.gradle2nix
import org.gradle.api.invocation.Gradle
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
import org.gradle.internal.build.event.BuildEventListenerRegistryInternal
import org.gradle.internal.operations.BuildOperationDescriptor
import org.gradle.internal.operations.BuildOperationListener
import org.gradle.internal.operations.OperationFinishEvent
import org.gradle.internal.operations.OperationIdentifier
import org.gradle.internal.operations.OperationProgressEvent
import org.gradle.internal.operations.OperationStartEvent
object DependencyExtractorApplierG8 : DependencyExtractorApplier {
@Suppress("UnstableApiUsage")
override fun apply(
gradle: Gradle,
extractor: DependencyExtractor,
) {
val serviceProvider =
gradle.sharedServices.registerIfAbsent(
"nixDependencyExtractor",
DependencyExtractorService::class.java,
) {}.map { service ->
service.apply { this.extractor = extractor }
}
gradle.service<BuildEventListenerRegistryInternal>().onOperationCompletion(serviceProvider)
}
}
@Suppress("UnstableApiUsage")
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
}
}

View File

@@ -0,0 +1,38 @@
package org.nixos.gradle2nix
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.model.ObjectFactory
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
object ResolveAllArtifactsApplierG8 : AbstractResolveAllArtifactsApplier() {
override fun Project.registerProjectTask(): TaskProvider<*> =
tasks.register(RESOLVE_PROJECT_TASK, ResolveProjectDependenciesTaskG8::class.java)
}
@DisableCachingByDefault(because = "Not worth caching")
abstract class ResolveProjectDependenciesTaskG8
@Inject
constructor(
private val objects: ObjectFactory,
) : ResolveProjectDependenciesTask() {
private val artifactFiles = Cached.of { artifactFiles() }
private fun artifactFiles(): FileCollection {
return objects.fileCollection().from(
getReportableConfigurations().map { configuration ->
configuration.artifactFiles()
},
)
}
@TaskAction
fun action() {
artifactFiles.get().count()
}
}

View File

@@ -0,0 +1,12 @@
plugins {
`plugin-conventions`
}
dependencies {
implementation(project(":plugin:gradle8"))
compileOnly(libs.gradle.api.get80())
}
tasks.shadowJar {
archiveFileName = "plugin-gradle80.jar"
}

View File

@@ -0,0 +1,7 @@
package org.nixos.gradle2nix
abstract class Gradle2NixPlugin : AbstractGradle2NixPlugin(
GradleCacheAccessFactoryG80,
DependencyExtractorApplierG8,
ResolveAllArtifactsApplierG8,
)

View File

@@ -0,0 +1,18 @@
package org.nixos.gradle2nix
import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.invocation.Gradle
object GradleCacheAccessFactoryG80 : GradleCacheAccessFactory {
override fun create(gradle: Gradle): GradleCacheAccess {
return GradleCacheAccessG80(gradle)
}
}
class GradleCacheAccessG80(gradle: Gradle) : GradleCacheAccess {
private val artifactCachesProvider = gradle.service<ArtifactCachesProvider>()
override fun useCache(block: () -> Unit) {
artifactCachesProvider.writableCacheLockingManager.useCache(block)
}
}

View File

@@ -0,0 +1,12 @@
plugins {
`plugin-conventions`
}
dependencies {
implementation(project(":plugin:gradle8"))
compileOnly(libs.gradle.api.get81())
}
tasks.shadowJar {
archiveFileName = "plugin-gradle81.jar"
}

View File

@@ -0,0 +1,7 @@
package org.nixos.gradle2nix
abstract class Gradle2NixPlugin : AbstractGradle2NixPlugin(
GradleCacheAccessFactoryG81,
DependencyExtractorApplierG8,
ResolveAllArtifactsApplierG8,
)

View File

@@ -0,0 +1,18 @@
package org.nixos.gradle2nix
import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.invocation.Gradle
object GradleCacheAccessFactoryG81 : GradleCacheAccessFactory {
override fun create(gradle: Gradle): GradleCacheAccess {
return GradleCacheAccessG81(gradle)
}
}
class GradleCacheAccessG81(gradle: Gradle) : GradleCacheAccess {
private val artifactCachesProvider = gradle.service<ArtifactCachesProvider>()
override fun useCache(block: () -> Unit) {
artifactCachesProvider.writableCacheAccessCoordinator.useCache(block)
}
}

View File

@@ -1,93 +0,0 @@
@file:Suppress("UnstableApiUsage")
package org.nixos.gradle2nix
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.gradle.util.GradleVersion
import org.nixos.gradle2nix.model.DependencySet
import org.nixos.gradle2nix.model.RESOLVE_ALL_TASK
import javax.inject.Inject
@Suppress("UNUSED")
abstract class Gradle2NixPlugin
@Inject
constructor(
private val toolingModelBuilderRegistry: ToolingModelBuilderRegistry,
) : Plugin<Gradle> {
override fun apply(gradle: Gradle) {
val dependencyExtractor = DependencyExtractor()
toolingModelBuilderRegistry.register(
DependencySetModelBuilder(
dependencyExtractor,
gradle.artifactCachesProvider,
gradle.checksumService,
gradle.fileStoreAndIndexProvider,
),
)
if (GradleVersion.current() < GradleVersion.version("8.0")) {
val extractor = DependencyExtractor()
gradle.buildOperationListenerManager.addListener(extractor)
@Suppress("DEPRECATION")
gradle.buildFinished {
gradle.buildOperationListenerManager.removeListener(extractor)
}
} else {
val serviceProvider =
gradle.sharedServices.registerIfAbsent(
"nixDependencyExtractor",
DependencyExtractorService::class.java,
) {}.map { service ->
service.apply { extractor = 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"))
}
}
}
}
}
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(
artifactCachesProvider,
checksumService,
fileStoreAndIndexProvider,
)
}
}

View File

@@ -1,42 +0,0 @@
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

@@ -1,66 +0,0 @@
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

@@ -11,5 +11,9 @@ dependencyResolutionManagement {
include( include(
":app", ":app",
":model", ":model",
":plugin", ":plugin:base",
":plugin:common",
":plugin:gradle8",
":plugin:gradle80",
":plugin:gradle81",
) )