Rewrite based on code from the GitHub Dependency Graph Gradle Plugin

This commit is contained in:
Tad Fisher
2023-10-06 16:01:15 -07:00
parent 68327d0c5d
commit 00f8deb8f2
151 changed files with 6425 additions and 3491 deletions

View File

@@ -1,5 +0,0 @@
# DO NOT MODIFY: Generated by Stutter plugin.
5.0
5.6.4
6.0.1
6.8.1

View File

@@ -1,7 +0,0 @@
# DO NOT MODIFY: Generated by Stutter plugin.
4.4.1
4.10.3
5.0
5.6.4
6.0.1
6.8.1

View File

@@ -1,52 +1,29 @@
buildscript {
configurations.classpath {
resolutionStrategy.activateDependencyLocking()
}
}
import com.github.jengelman.gradle.plugins.shadow.relocation.SimpleRelocator
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.utils.extendsFrom
plugins {
`kotlin-dsl`
id("org.jetbrains.kotlin.jvm")
id("com.gradle.plugin-publish")
id("com.github.johnrengelman.shadow")
id("org.ajoberstar.stutter")
}
sourceSets {
test {
java.srcDir("src/test/kotlin")
}
}
dependencyLocking {
lockAllConfigurations()
}
configurations {
compile {
dependencies.remove(project.dependencies.gradleApi())
}
}
dependencies {
compileOnly("org.gradle:gradle-tooling-api:${gradle.gradleVersion}")
implementation("org.apache.maven:maven-repository-metadata:latest.release")
implementation(project(":ivy"))
compileOnly(kotlin("stdlib-jdk8"))
compileOnly(kotlin("reflect"))
implementation(project(":model"))
shadow(gradleApi())
implementation(libs.serialization.json)
}
compatTestImplementation("com.adobe.testing:s3mock-junit5:latest.release")
compatTestImplementation("com.squareup.okio:okio:latest.release")
compatTestImplementation("dev.minutest:minutest:latest.release")
compatTestImplementation("io.javalin:javalin:latest.release")
compatTestImplementation("io.strikt:strikt-core:latest.release")
compatTestImplementation("org.junit.jupiter:junit-jupiter-api:latest.release")
compatTestImplementation("org.junit.jupiter:junit-jupiter-params:latest.release")
compatTestImplementation(embeddedKotlin("reflect"))
compatTestImplementation(embeddedKotlin("stdlib-jdk8"))
compatTestImplementation(embeddedKotlin("test-junit5"))
compatTestImplementation(gradleTestKit())
compatTestImplementation(project(":model"))
compatTestRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:latest.release")
compatTestRuntimeOnly("org.junit.platform:junit-platform-launcher:latest.release")
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_1_8)
}
}
gradlePlugin {
@@ -60,35 +37,25 @@ gradlePlugin {
}
}
kotlinDslPluginOptions {
experimentalWarning.set(false)
}
stutter {
isSparse = true
java(8) {
compatibleRange("4.4")
}
java(11) {
compatibleRange("5.0")
}
}
tasks {
pluginUnderTestMetadata {
pluginClasspath.setFrom(files(shadowJar))
jar {
manifest {
attributes["Implementation-Version"] = archiveVersion.get()
attributes["Implementation-Title"] = "Gradle2Nix Plugin"
attributes["Implementation-Vendor"] = "Tad Fisher"
}
}
withType<Test> {
useJUnitPlatform {
includeEngines("junit-jupiter")
}
shadowJar {
archiveClassifier.set("")
relocate("kotlin", "${project.group}.shadow.kotlin")
relocate("kotlinx.serialization", "${project.group}.shadow.serialization")
relocate("net.swiftzer.semver", "${project.group}.shadow.semver")
relocate("org.intellij", "${project.group}.shadow.intellij")
relocate("org.jetbrains", "${project.group}.shadow.jetbrains")
}
// Default logging config exposes a classpath conflict between
// the Gradle API and SFL4J.
// (Sprint Boot is used in S3Mock)
systemProperty("org.springframework.boot.logging.LoggingSystem", "org.springframework.boot.logging.java.JavaLoggingSystem")
systemProperty("fixtures", "$rootDir/fixtures")
validatePlugins {
enableStricterValidation.set(true)
}
}

View File

@@ -1,36 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.github.gundy:semver4j:0.16.4=classpath
com.google.code.gson:gson:2.8.6=classpath
de.undercouch:gradle-download-task:4.0.2=classpath
org.antlr:antlr4-runtime:4.5.2-1=classpath
org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:1.4.9=classpath
org.gradle.kotlin:gradle-kotlin-dsl-plugins:1.4.9=classpath
org.jetbrains.intellij.deps:trove4j:1.0.20181211=classpath
org.jetbrains.kotlin:kotlin-android-extensions:1.4.20=classpath
org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.4.20=classpath
org.jetbrains.kotlin:kotlin-build-common:1.4.20=classpath
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.20=classpath
org.jetbrains.kotlin:kotlin-compiler-runner:1.4.20=classpath
org.jetbrains.kotlin:kotlin-daemon-client:1.4.20=classpath
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.4.20=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.4.20=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.4.20=classpath
org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.20=classpath
org.jetbrains.kotlin:kotlin-reflect:1.4.20=classpath
org.jetbrains.kotlin:kotlin-sam-with-receiver:1.4.20=classpath
org.jetbrains.kotlin:kotlin-script-runtime:1.4.20=classpath
org.jetbrains.kotlin:kotlin-scripting-common:1.4.20=classpath
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.4.20=classpath
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.4.20=classpath
org.jetbrains.kotlin:kotlin-scripting-jvm:1.4.20=classpath
org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.20=classpath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.20=classpath
org.jetbrains.kotlin:kotlin-stdlib:1.4.20=classpath
org.jetbrains.kotlin:kotlin-util-io:1.4.20=classpath
org.jetbrains.kotlin:kotlin-util-klib:1.4.20=classpath
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7=classpath
org.jetbrains:annotations:13.0=classpath
empty=

View File

@@ -1,170 +0,0 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
ch.qos.logback:logback-classic:1.2.3=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
ch.qos.logback:logback-core:1.2.3=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.adobe.testing:s3mock-junit5:2.1.28=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.adobe.testing:s3mock-testsupport-common:2.1.28=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.adobe.testing:s3mock:2.1.28=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.amazonaws:aws-java-sdk-core:1.11.488=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.amazonaws:aws-java-sdk-core:1.11.946=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.amazonaws:aws-java-sdk-kms:1.11.488=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.amazonaws:aws-java-sdk-kms:1.11.946=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.amazonaws:aws-java-sdk-s3:1.11.488=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.amazonaws:aws-java-sdk-s3:1.11.946=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.amazonaws:jmespath-java:1.11.488=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.amazonaws:jmespath-java:1.11.946=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.christophsturm:filepeek:0.1.2=compatTestRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.6.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-annotations:2.9.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.6.7=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-core:2.9.9=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.6.7.4=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson.core:jackson-databind:2.9.9.3=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-cbor:2.6.7=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.9.9=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jdk8:2.9.9=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.9.9=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.fasterxml.jackson.module:jackson-module-jaxb-annotations:2.9.9=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.fasterxml.jackson.module:jackson-module-parameter-names:2.9.9=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.fasterxml.woodstox:woodstox-core:5.1.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.fasterxml:classmate:1.3.4=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.squareup.moshi:moshi:1.11.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.squareup.okio:okio-metadata:3.0.0-alpha.1=compatTestImplementationDependenciesMetadata
com.squareup.okio:okio:1.17.5=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
com.squareup.okio:okio:3.0.0-alpha.1=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
com.typesafe.netty:netty-reactive-streams-http:2.0.0=compatTestRuntimeClasspath
com.typesafe.netty:netty-reactive-streams:2.0.0=compatTestRuntimeClasspath
commons-codec:commons-codec:1.11=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
commons-io:commons-io:2.6=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
commons-logging:commons-logging:1.2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
dev.minutest:minutest:2.0.0-alpha=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
io.github.classgraph:classgraph:4.8.28=compatTestRuntimeClasspath
io.javalin:javalin:3.13.3=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
io.netty:netty-buffer:4.1.33.Final=compatTestRuntimeClasspath
io.netty:netty-codec-http2:4.1.33.Final=compatTestRuntimeClasspath
io.netty:netty-codec-http:4.1.33.Final=compatTestRuntimeClasspath
io.netty:netty-codec:4.1.33.Final=compatTestRuntimeClasspath
io.netty:netty-common:4.1.33.Final=compatTestRuntimeClasspath
io.netty:netty-handler:4.1.33.Final=compatTestRuntimeClasspath
io.netty:netty-resolver:4.1.33.Final=compatTestRuntimeClasspath
io.netty:netty-transport-native-epoll:4.1.33.Final=compatTestRuntimeClasspath
io.netty:netty-transport-native-unix-common:4.1.33.Final=compatTestRuntimeClasspath
io.netty:netty-transport:4.1.33.Final=compatTestRuntimeClasspath
io.strikt:strikt-core:0.28.2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
javax.annotation:javax.annotation-api:1.3.2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
javax.servlet:javax.servlet-api:3.1.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
javax.validation:validation-api:2.0.1.Final=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
joda-time:joda-time:2.8.1=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
net.swiftzer.semver:semver:1.1.1=compatTestRuntimeClasspath,default,runtimeClasspath,testRuntimeClasspath
org.apache.commons:commons-lang3:3.8.1=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.apache.httpcomponents:httpclient:4.5.13=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.apache.httpcomponents:httpclient:4.5.5=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata
org.apache.httpcomponents:httpclient:4.5.9=compatTestRuntimeClasspath
org.apache.httpcomponents:httpcore:4.4.11=compatTestRuntimeClasspath
org.apache.httpcomponents:httpcore:4.4.13=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.apache.httpcomponents:httpcore:4.4.9=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata
org.apache.ivy:ivy:2.5.0=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.apache.logging.log4j:log4j-api:2.11.2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.apache.logging.log4j:log4j-to-slf4j:2.11.2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.apache.maven:maven-repository-metadata:3.6.3=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.apiguardian:apiguardian-api:1.1.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compatTestRuntimeOnlyDependenciesMetadata
org.codehaus.plexus:plexus-utils:3.2.1=compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.codehaus.woodstox:stax2-api:4.1=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty.websocket:websocket-api:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty.websocket:websocket-client:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty.websocket:websocket-common:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty.websocket:websocket-server:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty.websocket:websocket-servlet:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-client:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-continuation:9.4.19.v20190610=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-http:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-io:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-security:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-server:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-servlet:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-servlets:9.4.19.v20190610=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-util-ajax:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-util:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-webapp:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.eclipse.jetty:jetty-xml:9.4.35.v20201120=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.gradle:gradle-tooling-api:6.8.1=compileClasspath,compileOnly,compileOnlyDependenciesMetadata
org.hibernate.validator:hibernate-validator:6.0.17.Final=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jboss.logging:jboss-logging:3.3.2.Final=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jetbrains.intellij.deps:trove4j:1.0.20181211=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.4.20=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.4.20=kotlinCompilerClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.4.20=kotlinCompilerPluginClasspath
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.4.20=kotlinCompilerPluginClasspath
org.jetbrains.kotlin:kotlin-klib-commonizer-embeddable:1.4.20=kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-reflect:1.4.20=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compileClasspath,compileOnly,compileOnlyDependenciesMetadata,embeddedKotlin,kotlinCompilerClasspath,kotlinKlibCommonizerClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-reflect:1.4.21-2=compatTestRuntimeClasspath
org.jetbrains.kotlin:kotlin-sam-with-receiver:1.4.20=kotlinCompilerPluginClasspath
org.jetbrains.kotlin:kotlin-script-runtime:1.4.20=kotlinCompilerClasspath,kotlinCompilerPluginClasspath,kotlinKlibCommonizerClasspath
org.jetbrains.kotlin:kotlin-scripting-common:1.4.20=kotlinCompilerPluginClasspath
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.4.20=kotlinCompilerPluginClasspath
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.4.20=kotlinCompilerPluginClasspath
org.jetbrains.kotlin:kotlin-scripting-jvm:1.4.20=kotlinCompilerPluginClasspath
org.jetbrains.kotlin:kotlin-stdlib-common:1.4.20=compileClasspath,compileOnly,compileOnlyDependenciesMetadata,default,embeddedKotlin,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspath,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-common:1.4.21-2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.20=compileClasspath,compileOnly,compileOnlyDependenciesMetadata,embeddedKotlin,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.4.21-2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.20=compileClasspath,compileOnly,compileOnlyDependenciesMetadata,embeddedKotlin,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.4.21-2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:1.4.20=compileClasspath,compileOnly,compileOnlyDependenciesMetadata,default,embeddedKotlin,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspath,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.jetbrains.kotlin:kotlin-stdlib:1.4.21-2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jetbrains.kotlin:kotlin-test-annotations-common:1.4.20=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jetbrains.kotlin:kotlin-test-common:1.4.20=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jetbrains.kotlin:kotlin-test-junit5:1.4.20=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jetbrains.kotlin:kotlin-test:1.4.20=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.4.2=compatTestRuntimeClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7=kotlinCompilerPluginClasspath
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2=compatTestRuntimeClasspath
org.jetbrains:annotations:13.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compileClasspath,compileOnly,compileOnlyDependenciesMetadata,default,embeddedKotlin,implementationDependenciesMetadata,kotlinCompilerClasspath,kotlinCompilerPluginClasspath,kotlinKlibCommonizerClasspath,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
org.junit.jupiter:junit-jupiter-api:5.7.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compatTestRuntimeOnlyDependenciesMetadata
org.junit.jupiter:junit-jupiter-engine:5.7.0=compatTestRuntimeClasspath,compatTestRuntimeOnlyDependenciesMetadata
org.junit.jupiter:junit-jupiter-params:5.7.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.junit.platform:junit-platform-commons:1.7.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compatTestRuntimeOnlyDependenciesMetadata
org.junit.platform:junit-platform-engine:1.7.0=compatTestRuntimeClasspath,compatTestRuntimeOnlyDependenciesMetadata
org.junit.platform:junit-platform-launcher:1.7.0=compatTestRuntimeClasspath,compatTestRuntimeOnlyDependenciesMetadata
org.junit:junit-bom:5.7.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compatTestRuntimeOnlyDependenciesMetadata
org.mortbay.jasper:apache-el:8.5.40=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.opentest4j:opentest4j:1.2.0=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compatTestRuntimeOnlyDependenciesMetadata
org.reactivestreams:reactive-streams:1.0.2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.slf4j:jul-to-slf4j:1.7.28=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.slf4j:slf4j-api:1.7.28=compileClasspath,compileOnly,compileOnlyDependenciesMetadata
org.slf4j:slf4j-api:1.7.30=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework.boot:spring-boot-autoconfigure:2.1.9.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework.boot:spring-boot-starter-jetty:2.1.9.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework.boot:spring-boot-starter-json:2.1.9.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework.boot:spring-boot-starter-logging:2.1.9.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework.boot:spring-boot-starter-web:2.1.9.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework.boot:spring-boot-starter:2.1.9.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework.boot:spring-boot:2.1.9.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework:spring-aop:5.1.10.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework:spring-beans:5.1.10.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework:spring-context:5.1.10.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework:spring-core:5.1.10.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework:spring-expression:5.1.10.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework:spring-jcl:5.1.10.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework:spring-web:5.1.10.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.springframework:spring-webmvc:5.1.10.RELEASE=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
org.yaml:snakeyaml:1.23=compatTestRuntimeClasspath
software.amazon.awssdk:annotations:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:apache-client:2.7.19=compatTestRuntimeClasspath
software.amazon.awssdk:auth:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:aws-core:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:aws-query-protocol:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:aws-xml-protocol:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:http-client-spi:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:netty-nio-client:2.7.19=compatTestRuntimeClasspath
software.amazon.awssdk:profiles:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:protocol-core:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:regions:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:s3:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:sdk-core:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:url-connection-client:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.awssdk:utils:2.7.19=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.eventstream:eventstream:1.0.1=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath
software.amazon.ion:ion-java:1.0.2=compatTestCompileClasspath,compatTestImplementationDependenciesMetadata,compatTestRuntimeClasspath,compileClasspath,default,implementationDependenciesMetadata,runtimeClasspath,testCompileClasspath,testImplementationDependenciesMetadata,testRuntimeClasspath
empty=annotationProcessor,apiDependenciesMetadata,archives,compatTestAnnotationProcessor,compatTestApiDependenciesMetadata,compatTestCompile,compatTestCompileOnly,compatTestCompileOnlyDependenciesMetadata,compatTestKotlinScriptDef,compatTestKotlinScriptDefExtensions,compatTestRuntime,compile,kotlinNativeCompilerPluginClasspath,kotlinScriptDef,kotlinScriptDefExtensions,runtime,runtimeOnlyDependenciesMetadata,shadow,testAnnotationProcessor,testApiDependenciesMetadata,testCompile,testCompileOnly,testCompileOnlyDependenciesMetadata,testKotlinScriptDef,testKotlinScriptDefExtensions,testRuntime,testRuntimeOnlyDependenciesMetadata

View File

@@ -1,74 +0,0 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.experimental.minus
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import dev.minutest.test
import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler.BINTRAY_JCENTER_URL
import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler.MAVEN_CENTRAL_URL
import strikt.api.expectThat
import strikt.assertions.all
import strikt.assertions.containsExactly
import strikt.assertions.flatMap
import strikt.assertions.get
import strikt.assertions.hasSize
import strikt.assertions.isEqualTo
import strikt.assertions.map
import strikt.assertions.none
import strikt.assertions.startsWith
class BasicTest : JUnit5Minutests {
@Tests
fun tests() = rootContext("basic tests") {
withFixture("basic/basic-java-project") {
test("builds basic java project") {
expectThat(build()) {
get("gradle version") { gradle.version }.isEqualTo(System.getProperty("compat.gradle.version"))
get("all dependencies") {
pluginDependencies +
rootProject.buildscriptDependencies +
rootProject.projectDependencies
}.flatMap { it.urls }.none { startsWith("file:") }
get("root project dependencies") { rootProject.projectDependencies }.and {
ids.containsExactly(
"com.squareup.moshi:moshi:1.8.0@jar",
"com.squareup.moshi:moshi:1.8.0@pom",
"com.squareup.moshi:moshi-parent:1.8.0@pom",
"com.squareup.okio:okio:2.2.2@jar",
"com.squareup.okio:okio:2.2.2@pom",
"org.jetbrains:annotations:13.0@jar",
"org.jetbrains:annotations:13.0@pom",
"org.jetbrains.kotlin:kotlin-stdlib:1.2.60@jar",
"org.jetbrains.kotlin:kotlin-stdlib:1.2.60@pom",
"org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@jar",
"org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@pom",
"org.sonatype.oss:oss-parent:7@pom"
)
map { it.urls }.all {
hasSize(2)
get(0).startsWith(BINTRAY_JCENTER_URL)
get(1).startsWith(MAVEN_CENTRAL_URL)
}
}
}
}
}
withFixture("basic/basic-kotlin-project") {
GRADLE_MIN("4.9") - test("excludes embedded kotlin repo") {
expectThat(build()) {
get("all dependencies") {
pluginDependencies +
rootProject.buildscriptDependencies +
rootProject.projectDependencies
}.flatMap { it.urls }.all { not { startsWith("file:") } }
}
}
}
}
}

View File

@@ -1,102 +0,0 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.experimental.minus
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import dev.minutest.test
import strikt.api.expectThat
import strikt.assertions.all
import strikt.assertions.containsExactly
import strikt.assertions.filter
import strikt.assertions.isEqualTo
import strikt.assertions.isNotNull
import strikt.assertions.isNull
class DependencyTest : JUnit5Minutests {
@Tests
fun tests() = rootContext("dependency tests") {
withRepository("m2") {
withFixture("dependency/classifier") {
test("resolves dependency with classifier") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }.ids.containsExactly(
"com.badlogicgames.gdx:gdx-parent:1.9.9@pom",
"com.badlogicgames.gdx:gdx-platform:1.9.9:natives-desktop@jar",
"com.badlogicgames.gdx:gdx-platform:1.9.9@pom",
"org.sonatype.oss:oss-parent:5@pom"
)
}
}
}
withFixture("dependency/maven-bom") {
GRADLE_MIN("5.0") - test("resolves dependencies from maven bom platform") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }
.ids
.containsExactly(
"io.micrometer:micrometer-bom:1.5.1@pom",
"io.micrometer:micrometer-core:1.5.1@jar",
"io.micrometer:micrometer-core:1.5.1@pom",
"org.hdrhistogram:HdrHistogram:2.1.12@jar",
"org.hdrhistogram:HdrHistogram:2.1.12@pom"
)
}
}
}
withFixture("dependency/snapshot") {
test("resolves snapshot dependency") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }
.and {
ids.containsExactly(
"org.apache:test-SNAPSHOT2:2.0.2-SNAPSHOT@jar",
"org.apache:test-SNAPSHOT2:2.0.2-SNAPSHOT@pom"
)
all {
get("timestamp") { timestamp }.isNull()
get("build") { build }.isNotNull()
}
}
}
}
}
withFixture("dependency/snapshot-dynamic") {
test("resolves snapshot dependency with dynamic version") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }
.and {
ids.containsExactly(
"org.apache:test-SNAPSHOT1:2.0.2-SNAPSHOT@jar",
"org.apache:test-SNAPSHOT1:2.0.2-SNAPSHOT@pom"
)
all {
get("timestamp") { timestamp }.isEqualTo("20070310.181613")
get("build") { build }.isEqualTo(3)
}
}
}
}
}
withFixture("dependency/snapshot-redirect") {
test("resolves snapshot dependency with redirect") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }
.filter { it.id.name == "packr" }
.all {
get("id.version") { id.version }.isEqualTo("-SNAPSHOT")
get("timestamp") { timestamp }.isNotNull()
get("build") { build }.isNotNull()
}
}
}
}
}
}
}

View File

@@ -1,34 +0,0 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import dev.minutest.test
import strikt.api.expectThat
import strikt.assertions.all
import strikt.assertions.containsExactly
import strikt.assertions.map
import strikt.assertions.single
import strikt.assertions.startsWith
class IvyTest : JUnit5Minutests {
@Tests
fun tests() = rootContext("ivy tests") {
withFixture("ivy/basic") {
test("resolves ivy dependencies") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }.and {
ids.containsExactly(
"org.opendof.core-java:dof-cipher-sms4:1.0@jar",
"org.opendof.core-java:dof-oal:7.0.2@jar"
)
map { it.urls }.all {
single().startsWith("https://asset.opendof.org/artifact")
}
}
}
}
}
}
}

View File

@@ -1,25 +0,0 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.experimental.FOCUS
import dev.minutest.experimental.minus
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import dev.minutest.test
import strikt.api.expectThat
import strikt.assertions.contains
import strikt.assertions.containsExactly
class PluginTest : JUnit5Minutests {
@Tests
fun tests() = rootContext("plugin tests") {
withFixture("plugin/resolves-from-default-repo") {
test("resolves plugin from default repo") {
expectThat(build()) {
get("plugin dependencies") { pluginDependencies }.ids
.contains("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:1.3.50@pom")
}
}
}
}
}

View File

@@ -1,52 +0,0 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import dev.minutest.test
import strikt.api.expectThat
import strikt.assertions.containsExactly
import strikt.assertions.flatMap
import strikt.assertions.map
class S3Test : JUnit5Minutests {
@Tests
fun tests() = rootContext("s3 tests") {
withBucket("repositories") {
withFixture("s3/maven") {
test("dependency from s3 maven repo") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }.and {
ids.containsExactly(
"org.apache:test:1.0.0@jar",
"org.apache:test:1.0.0@pom"
)
flatMap { it.urls }.containsExactly(
"s3://repositories/m2/org/apache/test/1.0.0/test-1.0.0.jar",
"s3://repositories/m2/org/apache/test/1.0.0/test-1.0.0.pom"
)
}
}
}
}
withFixture("s3/maven-snapshot") {
test("snapshot dependency from s3 maven repo") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }.and {
ids.containsExactly(
"org.apache:test-SNAPSHOT1:2.0.0-SNAPSHOT@jar",
"org.apache:test-SNAPSHOT1:2.0.0-SNAPSHOT@pom"
)
flatMap { it.urls }.containsExactly(
"s3://repositories/m2/org/apache/test-SNAPSHOT1/2.0.0-SNAPSHOT/test-SNAPSHOT1-2.0.0-20070310.181613-3.jar",
"s3://repositories/m2/org/apache/test-SNAPSHOT1/2.0.0-SNAPSHOT/test-SNAPSHOT1-2.0.0-20070310.181613-3.pom"
)
}
}
}
}
}
}
}

View File

@@ -1,39 +0,0 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.experimental.minus
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import dev.minutest.test
import strikt.api.expectThat
import strikt.assertions.containsExactly
class SettingsTest : JUnit5Minutests {
@Tests
fun tests() = rootContext("settings tests") {
withRepository("m2") {
withFixture("settings/buildscript") {
test("resolves settings plugin in buildscript classpath") {
expectThat(build()) {
get("settings dependencies") { settingsDependencies }.ids.containsExactly(
"org.apache:test:1.0.0@jar",
"org.apache:test:1.0.0@pom"
)
}
}
}
withFixture("settings/dependency-resolution-management") {
GRADLE_MIN("6.8") - test("uses repositories from settings script") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }.ids.containsExactly(
"org.apache:test:1.0.0@jar",
"org.apache:test:1.0.0@pom"
)
}
}
}
}
}
}

View File

@@ -1,152 +0,0 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import dev.minutest.test
import org.gradle.api.internal.artifacts.dsl.DefaultRepositoryHandler.BINTRAY_JCENTER_URL
import strikt.api.expectThat
import strikt.assertions.all
import strikt.assertions.containsExactly
import strikt.assertions.containsExactlyInAnyOrder
import strikt.assertions.get
import strikt.assertions.hasSize
import strikt.assertions.isEqualTo
import strikt.assertions.map
import strikt.assertions.single
import strikt.assertions.startsWith
class SubprojectsTest : JUnit5Minutests {
@Tests
fun tests() = rootContext("subproject tests") {
withFixture("subprojects/multi-module") {
test("builds multi-module project") {
expectThat(build().rootProject) {
get("root project dependencies") { projectDependencies }.and {
ids.containsExactly(
"junit:junit:4.12@jar",
"junit:junit:4.12@pom",
"org.hamcrest:hamcrest-core:1.3@jar",
"org.hamcrest:hamcrest-core:1.3@pom",
"org.hamcrest:hamcrest-parent:1.3@pom"
)
all {
get("urls") { urls }.single().startsWith(BINTRAY_JCENTER_URL)
}
}
get("children") { children }.and {
hasSize(2)
get(0).and {
get("name") { name }.isEqualTo("child-a")
get("projectDir") { projectDir }.isEqualTo("child-a")
get("child-a project dependencies") { projectDependencies }.and {
ids.containsExactly(
"com.squareup.okio:okio:2.2.2@jar",
"com.squareup.okio:okio:2.2.2@pom",
"org.jetbrains:annotations:13.0@jar",
"org.jetbrains:annotations:13.0@pom",
"org.jetbrains.kotlin:kotlin-stdlib:1.2.60@jar",
"org.jetbrains.kotlin:kotlin-stdlib:1.2.60@pom",
"org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@jar",
"org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@pom"
)
all {
get("urls") { urls }.single().startsWith(BINTRAY_JCENTER_URL)
}
}
}
get(1).and {
get("name") { name }.isEqualTo("child-b")
get("projectDir") { projectDir }.isEqualTo("child-b")
get("child-b project dependencies") { projectDependencies }.and {
ids.containsExactly(
"com.squareup.moshi:moshi:1.8.0@jar",
"com.squareup.moshi:moshi:1.8.0@pom",
"com.squareup.moshi:moshi-parent:1.8.0@pom",
"com.squareup.okio:okio:1.16.0@jar", // compileClasspath
"com.squareup.okio:okio:1.16.0@pom", // compileClasspath
"com.squareup.okio:okio:2.2.2@jar", // runtimeClasspath
"com.squareup.okio:okio:2.2.2@pom", // runtimeClasspath
"com.squareup.okio:okio-parent:1.16.0@pom", // compileClasspath
"org.jetbrains:annotations:13.0@jar",
"org.jetbrains:annotations:13.0@pom",
"org.jetbrains.kotlin:kotlin-stdlib:1.2.60@jar",
"org.jetbrains.kotlin:kotlin-stdlib:1.2.60@pom",
"org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@jar",
"org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@pom",
"org.sonatype.oss:oss-parent:7@pom"
)
all {
get("urls") { urls }.single().startsWith(BINTRAY_JCENTER_URL)
}
}
}
}
}
}
test("builds single subproject") {
expectThat(build(subprojects = listOf(":child-a")).rootProject) {
get("root project dependencies") { projectDependencies }.and {
ids.containsExactly(
"junit:junit:4.12@jar",
"junit:junit:4.12@pom",
"org.hamcrest:hamcrest-core:1.3@jar",
"org.hamcrest:hamcrest-core:1.3@pom",
"org.hamcrest:hamcrest-parent:1.3@pom"
)
all {
get("urls") { urls }.single().startsWith(BINTRAY_JCENTER_URL)
}
}
get("children") { children }.single().and {
get("name") { name }.isEqualTo("child-a")
get("projectDir") { projectDir }.isEqualTo("child-a")
get("child-a project dependencies") { projectDependencies }.and {
ids.containsExactly(
"com.squareup.okio:okio:2.2.2@jar",
"com.squareup.okio:okio:2.2.2@pom",
"org.jetbrains:annotations:13.0@jar",
"org.jetbrains:annotations:13.0@pom",
"org.jetbrains.kotlin:kotlin-stdlib:1.2.60@jar",
"org.jetbrains.kotlin:kotlin-stdlib:1.2.60@pom",
"org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@jar",
"org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60@pom"
)
all {
get("urls") { urls }.single().startsWith(BINTRAY_JCENTER_URL)
}
}
}
}
}
}
withFixture("subprojects/dependent-subprojects") {
test("includes dependent subprojects") {
expectThat(build(subprojects = listOf(":child-a"))) {
get("children") { rootProject.children }
.map { it.path }
.containsExactlyInAnyOrder(":child-a", ":child-b", ":child-c")
}
expectThat(build(subprojects = listOf(":child-b"))) {
get("children") { rootProject.children }
.map { it.path }
.containsExactlyInAnyOrder(":child-b", ":child-c")
}
}
}
}
}

View File

@@ -1,300 +0,0 @@
package org.nixos.gradle2nix
import com.adobe.testing.s3mock.S3MockApplication
import com.adobe.testing.s3mock.junit5.S3MockExtension
import com.adobe.testing.s3mock.testsupport.common.S3MockStarter
import com.squareup.moshi.Moshi
import dev.minutest.ContextBuilder
import dev.minutest.MinutestFixture
import dev.minutest.Node
import dev.minutest.TestContextBuilder
import dev.minutest.afterEach
import dev.minutest.beforeEach
import dev.minutest.experimental.SKIP
import dev.minutest.experimental.TransformingAnnotation
import dev.minutest.given
import dev.minutest.givenClosable
import dev.minutest.given_
import io.javalin.Javalin
import io.javalin.http.staticfiles.Location
import okio.buffer
import okio.source
import org.gradle.internal.classpath.DefaultClassPath
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.internal.PluginUnderTestMetadataReading
import org.gradle.util.GradleVersion
import org.junit.jupiter.api.Assumptions.assumeTrue
import strikt.api.Assertion
import strikt.assertions.map
import java.io.Closeable
import java.io.File
import java.io.StringWriter
import java.util.concurrent.atomic.AtomicBoolean
private val moshi = Moshi.Builder().build()
val fixtureRoot = File(System.getProperty("fixtures"))
val gradleVersion = System.getProperty("compat.gradle.version")
?.let(GradleVersion::version)
?: GradleVersion.current()
val GRADLE_4_5 = GradleVersion.version("4.5")
fun GRADLE_MIN(version: String) = object : TransformingAnnotation() {
override fun <F> transform(node: Node<F>): Node<F> =
if (gradleVersion < GradleVersion.version(version)) SKIP.transform(node) else node
}
private fun File.initscript() = resolve("init.gradle").also {
it.writer().use { out ->
val classpath = DefaultClassPath.of(PluginUnderTestMetadataReading.readImplementationClasspath())
.asFiles.joinToString { n -> "'$n'" }
out.append("""
initscript {
dependencies {
classpath files($classpath)
}
}
apply plugin: org.nixos.gradle2nix.Gradle2NixPlugin
""".trimIndent())
}
}
fun File.buildKotlin(
script: String,
configurations: List<String> = emptyList(),
subprojects: List<String> = emptyList()
): DefaultBuild {
assumeTrue(gradleVersion >= GRADLE_4_5)
resolve("build.gradle.kts").writeText(script)
return build(configurations, subprojects)
}
private fun File.build(
configurations: List<String>,
subprojects: List<String>,
extraArguments: List<String> = emptyList()
): DefaultBuild {
val log = StringWriter()
val result = GradleRunner.create()
.withGradleVersion(gradleVersion.version)
.withProjectDir(this)
.forwardStdOutput(log)
.forwardStdError(log)
.withArguments(
"nixModel",
"--init-script=${initscript()}",
"--stacktrace",
"-Porg.nixos.gradle2nix.configurations=${configurations.joinToString(",")}",
"-Porg.nixos.gradle2nix.subprojects=${subprojects.joinToString(",")}",
*(extraArguments.toTypedArray())
)
.runCatching { build() }
result.onFailure { error ->
System.err.print(log)
throw error
}
print(log)
return resolve("build/nix/model.json").run {
println(readText())
source().buffer().use { src ->
checkNotNull(moshi.adapter(DefaultBuild::class.java).fromJson(src))
}
}
}
val <T : Iterable<Artifact>> Assertion.Builder<T>.ids: Assertion.Builder<Iterable<String>>
get() = map { it.id.toString() }
private fun File.parents() = generateSequence(parentFile) { it.parentFile }
abstract class ArgumentsSupplier(private val parent: ArgumentsSupplier? = null) {
open val arguments: List<String> = emptyList()
val extraArguments: List<String> get() = (parent?.extraArguments ?: emptyList()) + arguments
}
@MinutestFixture
class RepositoryFixture(
private val server: Javalin,
parent: ArgumentsSupplier? = null
) : ArgumentsSupplier(parent), Closeable {
override fun close() {
server.stop()
}
}
@MinutestFixture
class S3Fixture(
private val name: String,
parent: ArgumentsSupplier? = null
) : ArgumentsSupplier(parent), Closeable {
private val s3mock = S3Mock(
initialBuckets = listOf(name),
secureConnection = false
)
override val arguments: List<String> get() = listOf(
"-Dorg.gradle.s3.endpoint=${s3mock.serviceEndpoint}",
"-Dorg.nixos.gradle2nix.s3test=true"
)
init {
s3mock.startServer()
val s3root = fixtureRoot.resolve(name)
val s3client = s3mock.createS3Client()
require(s3root.exists() && s3root.isDirectory) {
"$name: S3 fixture not found: $s3root"
}
s3root.walkTopDown()
.filter { it.isFile }
.forEach { file ->
val key = file.toRelativeString(s3root)
s3client.putObject(name, key, file)
}
}
override fun close() {
s3mock.stopServer()
}
}
@MinutestFixture
class TestFixture(
val name: String,
val source: File,
parent: ArgumentsSupplier? = null
) : ArgumentsSupplier(parent), Closeable {
val dest: File
init {
require(source.exists() && source.isDirectory) {
"$name: Test fixture not found: $source}"
}
dest = createTempDir(prefix = name, suffix = "")
}
override fun close() {
dest.deleteRecursively()
}
}
@MinutestFixture
data class ProjectFixture(
private val parent: TestFixture,
private val source: File
) : Closeable {
private val dest: File
init {
require(source.exists() && source.isDirectory && parent.source in source.parents()) {
"${parent.name}: Test project not found: $source"
}
val rel = source.toRelativeString(parent.source)
dest = parent.dest.resolve(rel)
}
fun copySource() {
source.copyRecursively(dest, true)
}
fun build(
configurations: List<String> = emptyList(),
subprojects: List<String> = emptyList()
) = dest.build(configurations, subprojects, parent.extraArguments)
override fun close() {
dest.deleteRecursively()
}
}
fun ContextBuilder<*>.withBucket(
name: String,
block: TestContextBuilder<*, S3Fixture>.() -> Unit
) = derivedContext<S3Fixture>("with s3 bucket: $name") {
given_ { parent ->
S3Fixture(name, parent as? ArgumentsSupplier)
}
afterEach { it.close() }
block()
}
fun ContextBuilder<*>.withRepository(
name: String,
block: TestContextBuilder<*, RepositoryFixture>.() -> Unit
) = derivedContext<RepositoryFixture>("with repository: $name") {
given_ { parent ->
RepositoryFixture(
server = Javalin.create { config ->
config.addStaticFiles("${fixtureRoot}/repositories/$name", Location.EXTERNAL)
}.start(9999),
parent = parent as? ArgumentsSupplier
)
}
afterEach { it.close() }
block()
}
fun ContextBuilder<*>.withFixture(
name: String,
block: TestContextBuilder<*, ProjectFixture>.() -> Unit
) = derivedContext<TestFixture>(name) {
val projectRoot = fixtureRoot.resolve(name).also {
check(it.exists()) { "$name: project fixture not found: $it" }
}
given_ { parent ->
TestFixture(name, projectRoot, parent as? ArgumentsSupplier)
}
val testRoots = projectRoot.listFiles()!!
.filter { it.isDirectory }
.map { it.absoluteFile }
.toList()
testRoots.forEach { testRoot ->
derivedContext<ProjectFixture>(testRoot.name) {
given_ { parent -> ProjectFixture(parent, testRoot) }
beforeEach { copySource() }
afterEach { close() }
block()
}
}
}
class S3Mock(
initialBuckets: List<String> = emptyList(),
secureConnection: Boolean = true
) : S3MockStarter(
mapOf(
S3MockApplication.PROP_INITIAL_BUCKETS to initialBuckets.joinToString(","),
S3MockApplication.PROP_SECURE_CONNECTION to secureConnection
)
) {
private val running = AtomicBoolean()
fun startServer() {
if (running.compareAndSet(false, true)) {
start()
}
}
fun stopServer() {
if (running.compareAndSet(true, false)) {
stop()
}
}
}

View File

@@ -1,27 +0,0 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.given
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import dev.minutest.test
import strikt.api.expectThat
import strikt.assertions.isEqualTo
import java.io.File
class WrapperTest : JUnit5Minutests {
@Tests
fun tests() = rootContext<File>("wrapper tests") {
given { createTempDir("gradle2nix") }
test("resolves gradle wrapper version") {
expectThat(buildKotlin("""
tasks.withType<org.gradle.api.tasks.wrapper.Wrapper> {
gradleVersion = "5.5.1"
}
""".trimIndent())) {
get("gradle version") { gradle.version }.isEqualTo("5.5.1")
}
}
}
}

View File

@@ -1,15 +0,0 @@
package org.nixos.gradle2nix;
import org.gradle.api.artifacts.Dependency;
import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency;
import javax.annotation.Nullable;
/**
* Workarounds for APIs improperly marked with @NonNullApi.
*/
interface ApiHack {
static Dependency defaultExternalModuleDependency(String group, String name, @Nullable String version) {
return new DefaultExternalModuleDependency(group, name, version);
}
}

View File

@@ -1,303 +0,0 @@
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.Project
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
enum class ConfigurationScope {
BUILDSCRIPT,
PLUGIN,
PROJECT,
SETTINGS
}
internal class ConfigurationResolverFactory(
project: Project,
scope: ConfigurationScope,
repositories: List<ResolutionAwareRepository>
) {
private val ivySettings = IvySettings().apply {
defaultInit()
// This doesn't appear to be used, but it's better to define it explicitly than to introduce
// impurities into artifact resolution.
setDefaultRepositoryCacheBasedir(project.buildDir.resolve("tmp/gradle2nix/_cache").absolutePath)
setDictatorResolver(ChainResolver().also { chain ->
chain.settings = this@apply
for (resolver in resolvers) chain.add(resolver)
})
}
private val resolvers = repositories
.filterNot { it.createResolver().isLocal }
.mapNotNull { it.repositoryResolver(project, scope, 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 failed = mutableSetOf<ArtifactIdentifier>()
private val ivy = Ivy.newInstance(ivySettings)
val unresolved: List<ArtifactIdentifier> = failed.toList()
fun resolve(configuration: Configuration): List<DefaultArtifact> {
val resolved = configuration.resolvedConfiguration.lenientConfiguration
failed.addAll(resolved.unresolvedModuleDependencies.map {
DefaultArtifactIdentifier(
group = it.selector.group,
name = it.selector.name,
version = it.selector.version ?: "",
type = "module"
)
})
val topLevelMetadata = resolved.firstLevelModuleDependencies
.flatMap { resolveMetadata(it.moduleGroup, it.moduleName, it.moduleVersion) }
val allArtifacts = resolved.artifacts
.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.computeHash()
val artifacts = resolvers.mapNotNull { it.resolve(artifactId, sha256) }.merge()
if (artifacts.isEmpty()) failed.add(artifactId)
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"
)
// Intentionally not computing hash from the cached result; see ResolvedArtifact.computeHash() below.
val artifacts = resolvers.mapNotNull { it.resolve(artifactId) }.merge()
if (artifacts.isEmpty()) failed.add(artifactId)
artifacts
}
}
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"
)
// Intentionally not computing hash from the cached result; see ResolvedArtifact.computeHash() below.
val artifacts = resolvers.mapNotNull { it.resolve(artifactId) }.merge()
if (artifacts.isEmpty()) failed.add(artifactId)
artifacts
}
}
private fun resolveGradleMetadata(
group: String,
name: String,
version: String
): List<DefaultArtifact> {
val artifactId = DefaultArtifactIdentifier(
group = group,
name = name,
version = version,
type = "module"
)
val artifacts = resolvers.mapNotNull { it.resolve(artifactId) }.merge()
if (artifacts.isEmpty()) failed.add(artifactId)
return artifacts
}
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::getId))
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()
}
private fun ResolvedArtifact.computeHash(): String? {
// Hack: Some POM files are served with CRLF line endings, e.g. javax.servlet:javax.servlet-api:3.1.0.
// Gradle stores these normalized with LF line endings, which will not match the eventual hash
// of the fixed-output derivation which produces the POM artifact.
//
// A similar issue can exist for Apache Ivy; see https://issues.apache.org/jira/browse/IVY-1156.
//
// Ignore these artifacts, and defer hash calculation to RepositoryResolver.
if (type == "pom" || type == "ivy") {
return null
}
return file.sha256()
}

View File

@@ -1,282 +1,23 @@
@file:Suppress("UnstableApiUsage")
package org.nixos.gradle2nix
import com.squareup.moshi.Moshi
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.internal.GradleInternal
import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
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
import org.gradle.tooling.provider.model.ToolingModelBuilder
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.gradle.util.GradleVersion
import java.net.URL
import java.util.*
import org.nixos.gradle2nix.dependencygraph.AbstractDependencyExtractorPlugin
@Suppress("unused")
open class Gradle2NixPlugin : Plugin<Gradle> {
override fun apply(gradle: Gradle): Unit = gradle.run {
val pluginRequests = collectPlugins()
projectsLoaded {
val modelProperties = rootProject.loadModelProperties()
rootProject.serviceOf<ToolingModelBuilderRegistry>()
.register(NixToolingModelBuilder(modelProperties, pluginRequests))
rootProject.tasks.registerCompat("nixModel") {
doLast {
val outFile = project.mkdir(project.buildDir.resolve("nix")).resolve("model.json")
val model = project.buildModel(modelProperties, pluginRequests)
outFile.bufferedWriter().use { out ->
out.write(
Moshi.Builder().build()
.adapter(DefaultBuild::class.java)
.indent(" ")
.toJson(model)
)
out.flush()
}
}
}
class Gradle2NixPlugin : Plugin<Gradle> {
override fun apply(gradle: Gradle) {
// Only apply the dependency extractor to the root build
if (gradle.parent == null) {
gradle.pluginManager.apply(NixDependencyExtractorPlugin::class.java)
}
}
}
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,
private val pluginRequests: List<PluginRequest>
) : ToolingModelBuilder {
override fun canBuild(modelName: String): Boolean {
return modelName == NIX_MODEL_NAME
gradle.pluginManager.apply(ForceDependencyResolutionPlugin::class.java)
}
override fun buildAll(modelName: String, project: Project): Build =
project.buildModel(modelProperties, pluginRequests)
}
private fun Gradle.collectPlugins(): List<PluginRequest> {
val pluginRequests = mutableListOf<PluginRequest>()
gradle.settingsEvaluated {
pluginManagement.resolutionStrategy.eachPlugin {
if (requested.id.namespace != null && requested.id.namespace != "org.gradle") {
pluginRequests.add(target)
}
}
}
return pluginRequests
}
private fun Project.buildModel(
modelProperties: ModelProperties,
pluginRequests: List<PluginRequest>
): DefaultBuild {
val settingsDependencies = settingsDependencies()
val plugins = buildPlugins(pluginRequests)
val subprojects = if (modelProperties.subprojects.isEmpty()) {
project.subprojects
} else {
project.subprojects
.filter { it.path in modelProperties.subprojects }
// Include dependent subprojects as well
.flatMap { setOf(it) + it.dependentSubprojects(modelProperties.configurations) }
.toSet()
}
return DefaultBuild(
gradle = buildGradle(),
settingsDependencies = settingsDependencies,
pluginDependencies = plugins,
rootProject = buildProject(modelProperties.configurations, subprojects, plugins),
includedBuilds = includedBuilds()
)
}
@Suppress("UnstableApiUsage")
private fun Project.buildGradle(): DefaultGradle =
with(tasks.getByName<Wrapper>("wrapper")) {
DefaultGradle(
version = gradleVersion,
type = distributionType.name.toLowerCase(Locale.US),
url = distributionUrl,
sha256 = sha256,
nativeVersion = gradle.gradleHomeDir?.resolve("lib")?.listFiles()
?.firstOrNull { f -> nativePlatformJarRegex matches f.name }?.let { nf ->
nativePlatformJarRegex.find(nf.name)?.groupValues?.get(1)
}
?: throw IllegalStateException(
"""
Failed to find native-platform jar in ${gradle.gradleHomeDir}.
Ask Tad to fix this.
""".trimIndent()
)
)
}
private fun Project.settingsDependencies(): List<DefaultArtifact> {
val buildscript = (gradle as GradleInternal).settings.buildscript
val resolverFactory = ConfigurationResolverFactory(
this,
ConfigurationScope.SETTINGS,
buildscript.repositories.filterIsInstance<ResolutionAwareRepository>()
)
val resolver = resolverFactory.create(buildscript.dependencies)
logger.lifecycle(" Settings script")
val dependencies = buildscript.configurations
.flatMap(resolver::resolve)
.distinct()
.sorted()
if (resolver.unresolved.isNotEmpty()) {
logger.warn(buildString {
append(" Failed to resolve settings script dependencies:\n")
for (id in resolver.unresolved) {
append(" - $id\n")
}
})
}
return dependencies
}
private fun Project.buildPlugins(pluginRequests: List<PluginRequest>): List<DefaultArtifact> {
return objects.newInstance<PluginResolver>(this).resolve(pluginRequests).distinct().sorted()
}
private fun Project.includedBuilds(): List<DefaultIncludedBuild> =
gradle.includedBuilds.map {
DefaultIncludedBuild(it.name, it.projectDir.toRelativeString(project.projectDir))
}
private fun Project.buildProject(
explicitConfigurations: List<String>,
explicitSubprojects: Collection<Project>,
pluginArtifacts: List<DefaultArtifact>
): DefaultProject {
logger.lifecycle(" Subproject: $path")
val (buildscriptDependencies, buildscriptUnresolved) = buildscriptDependencies(pluginArtifacts)
if (buildscriptUnresolved.isNotEmpty()) {
logger.warn(buildString {
append(" Failed to resolve buildscript dependencies:\n")
for (id in buildscriptUnresolved) {
append(" - $id\n")
}
})
}
val (projectDependencies, projectUnresolved) = projectDependencies(explicitConfigurations)
if (projectUnresolved.isNotEmpty()) {
logger.warn(buildString {
append(" Failed to resolve project dependencies:\n")
for (id in projectUnresolved) {
append(" - $id\n")
}
})
}
return DefaultProject(
name = name,
version = version.toString(),
path = path,
projectDir = projectDir.toRelativeString(rootProject.projectDir),
buildscriptDependencies = buildscriptDependencies,
projectDependencies = projectDependencies,
children = explicitSubprojects.map {
it.buildProject(explicitConfigurations, emptyList(), pluginArtifacts)
}
)
}
private fun Project.buildscriptDependencies(
pluginArtifacts: List<DefaultArtifact>
): Pair<List<DefaultArtifact>, List<ArtifactIdentifier>> {
val resolverFactory = ConfigurationResolverFactory(
this,
ConfigurationScope.BUILDSCRIPT,
buildscript.repositories.filterIsInstance<ResolutionAwareRepository>()
)
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() to resolver.unresolved
}
private fun Project.projectDependencies(
explicitConfigurations: List<String>
): Pair<List<DefaultArtifact>, List<ArtifactIdentifier>> {
val resolver = ConfigurationResolverFactory(
this,
ConfigurationScope.PROJECT,
RepositoriesCollector.create(project).collectRepositories()
).create(dependencies)
return collectConfigurations(explicitConfigurations)
.flatMap(resolver::resolve)
.distinct()
.sorted() to resolver.unresolved
}
private fun Project.dependentSubprojects(explicitConfigurations: List<String>): Set<Project> {
return collectConfigurations(explicitConfigurations)
.flatMap { it.allDependencies.withType<ProjectDependency>() }
.map { it.dependencyProject }
.toSet()
.flatMap { setOf(it) + it.dependentSubprojects(explicitConfigurations) }
.toSet()
}
private fun Project.collectConfigurations(
explicitConfigurations: List<String>
): Set<Configuration> {
return if (explicitConfigurations.isEmpty()) {
configurations.filter { it.isCanBeResolved }.toSet()
} else {
configurations.filter { it.name in explicitConfigurations }.toSet()
class NixDependencyExtractorPlugin : AbstractDependencyExtractorPlugin() {
override fun getRendererClassName(): String =
NixDependencyGraphRenderer::class.java.name
}
}
private fun fetchDistSha256(url: String): String {
return URL("$url.sha256").openConnection().run {
connect()
getInputStream().reader().use { it.readText() }
}
}
private val nativePlatformJarRegex = Regex("""native-platform-([\d.]+(-(alpha|beta|milestone)-\d+)?)\.jar""")
private val Wrapper.sha256: String
get() {
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"

View File

@@ -1,20 +0,0 @@
package org.nixos.gradle2nix
import org.gradle.api.Project
data class ModelProperties(
val configurations: List<String>,
val subprojects: List<String>
)
internal fun Project.loadModelProperties(): ModelProperties {
return ModelProperties(
configurations = this["org.nixos.gradle2nix.configurations"]?.split(",") ?: emptyList(),
subprojects = this["org.nixos.gradle2nix.subprojects"]?.split(",") ?: emptyList()
)
}
private operator fun Project.get(key: String): String? {
return System.getProperty(key)?.takeIf { it.isNotEmpty() }
?: (properties[key] as? String)?.takeIf { it.isNotEmpty() }
}

View File

@@ -0,0 +1,21 @@
package org.nixos.gradle2nix
import java.io.File
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream
import org.nixos.gradle2nix.dependencygraph.DependencyGraphRenderer
import org.nixos.gradle2nix.dependencygraph.model.ResolvedConfiguration
class NixDependencyGraphRenderer : DependencyGraphRenderer {
@OptIn(ExperimentalSerializationApi::class)
override fun outputDependencyGraph(
resolvedConfigurations: List<ResolvedConfiguration>,
outputDirectory: File
) {
val graphOutputFile = File(outputDirectory, "dependency-graph.json")
graphOutputFile.outputStream().buffered().use { output ->
Json.encodeToStream(resolvedConfigurations, output)
}
}
}

View File

@@ -1,31 +0,0 @@
package org.nixos.gradle2nix
import org.gradle.api.Project
import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
import org.gradle.plugin.management.PluginRequest
import org.gradle.plugin.use.internal.PluginDependencyResolutionServices
import javax.inject.Inject
internal open class PluginResolver @Inject constructor(
project: Project,
pluginDependencyResolutionServices: PluginDependencyResolutionServices
) {
private val configurations = pluginDependencyResolutionServices.configurationContainer
private val resolver = ConfigurationResolverFactory(
project,
ConfigurationScope.PLUGIN,
pluginDependencyResolutionServices.resolveRepositoryHandler.filterIsInstance<ResolutionAwareRepository>()
).create(pluginDependencyResolutionServices.dependencyHandler)
fun resolve(pluginRequests: List<PluginRequest>): List<DefaultArtifact> {
val markerDependencies = pluginRequests.map { request ->
request.module?.let { module ->
ApiHack.defaultExternalModuleDependency(module.group, module.name, module.version)
} ?: request.id.id.let { id ->
ApiHack.defaultExternalModuleDependency(id, "$id.gradle.plugin", request.version)
}
}
return resolver.resolve(configurations.detachedConfiguration(*markerDependencies.toTypedArray()))
}
}

View File

@@ -1,37 +0,0 @@
package org.nixos.gradle2nix
import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
import org.gradle.api.Project
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.initialization.dsl.ScriptHandler
import org.gradle.api.internal.artifacts.RepositoriesSupplier
import org.gradle.kotlin.dsl.newInstance
import org.gradle.util.GradleVersion
import javax.inject.Inject
interface RepositoriesCollector {
fun collectRepositories(): List<ResolutionAwareRepository>
companion object {
fun create(project: Project): RepositoriesCollector =
if (GradleVersion.current() >= GradleVersion.version("6.8")) {
project.objects.newInstance<RepositoriesCollector68>()
} else {
project.objects.newInstance<RepositoriesCollectorBase>()
}
}
}
open class RepositoriesCollectorBase @Inject constructor(
private val repositories: RepositoryHandler
): RepositoriesCollector {
override fun collectRepositories(): List<ResolutionAwareRepository> =
repositories.filterIsInstance<ResolutionAwareRepository>()
}
open class RepositoriesCollector68 @Inject constructor(
private val repositoriesSupplier: RepositoriesSupplier
): RepositoriesCollector {
override fun collectRepositories(): List<ResolutionAwareRepository> =
repositoriesSupplier.get()
}

View File

@@ -1,317 +0,0 @@
package org.nixos.gradle2nix
import com.amazonaws.auth.BasicAWSCredentials
import com.amazonaws.auth.BasicSessionCredentials
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.core.settings.TimeoutConstraint
import org.apache.ivy.plugins.repository.Repository
import org.apache.ivy.plugins.repository.Resource
import org.apache.ivy.plugins.repository.url.URLRepository
import org.apache.ivy.plugins.repository.url.URLResource
import org.apache.ivy.plugins.resolver.AbstractResolver
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.Project
import org.gradle.api.artifacts.repositories.ArtifactRepository
import org.gradle.api.artifacts.repositories.AuthenticationContainer
import org.gradle.api.artifacts.repositories.AuthenticationSupported
import org.gradle.api.artifacts.repositories.IvyArtifactRepository
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.artifacts.repositories.UrlArtifactRepository
import org.gradle.api.credentials.AwsCredentials
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 org.gradle.authentication.aws.AwsImAuthentication
import org.gradle.internal.authentication.AllSchemesAuthentication
import org.gradle.kotlin.dsl.getCredentials
import java.io.IOException
import java.net.URI
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(
project: Project,
scope: ConfigurationScope,
ivySettings: IvySettings
): RepositoryResolver? =
when(this) {
is MavenArtifactRepository -> MavenResolver(project, scope, ivySettings, this)
is IvyArtifactRepository -> IvyResolver(project, scope, 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(
project: Project,
scope: ConfigurationScope,
ivySettings: IvySettings,
repository: MavenArtifactRepository
) : RepositoryResolver() {
override val ivyResolver: IBiblioResolver = IBiblioResolver().apply {
name = repository.name
root = repository.url.toString()
isM2compatible = true
settings = ivySettings
setCache(cacheManager(project, scope, ivySettings, repository).name)
setRepository(resolverRepository(repository))
}
override fun resolve(artifactId: DefaultArtifactIdentifier, sha256: String?): DefaultArtifact? {
val ivyArtifact: IvyArtifact = artifactId.toArtifact()
val origin = ivyResolver.locate(ivyArtifact)
if (origin == null || !origin.isExists) return null
val hash = if (sha256 != null) sha256 else {
val report = ivyResolver.download(origin, downloadOptions)
report.localFile?.sha256().also {
if (it == null) log.error(report.toString())
}
}
if (hash == null) {
log.error("Failed to download '$artifactId' from repository '${ivyResolver.repository.name}'")
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"
val metadataFile = ivyResolver.repositoryCacheManager.downloadRepositoryResource(
ivyResolver.repository.getResource(metadataLocation),
"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(
project: Project,
scope: ConfigurationScope,
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(project, scope, ivySettings, repository).name)
setRepository(resolverRepository(repository))
}
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 = if (sha256 != null) sha256 else {
val report = ivyResolver.download(origin, downloadOptions)
report.localFile?.sha256().also {
if (it == null) log.error(report.toString())
}
}
if (hash == null) {
log.error("Failed to download '$artifactId' from repository '${ivyResolver.repository.name}'")
return null
}
return DefaultArtifact(
id = DefaultArtifactIdentifier(artifactId),
name = artifactId.filename(null),
path = artifactId.repoPath(),
urls = listOf(origin.location),
sha256 = hash
)
}
}
private fun cacheManager(
project: Project,
scope: ConfigurationScope,
ivySettings: IvySettings,
repository: ArtifactRepository
): RepositoryCacheManager {
return DefaultRepositoryCacheManager(
"${scope.name.toLowerCase()}-${repository.name}-cache",
ivySettings,
project.buildDir.resolve("tmp/gradle2nix/${repository.name}").also {
it.mkdirs()
}
).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_DEFAULT
}
private fun <T> AbstractResolver.resolverRepository(
repository: T
) : Repository
where T : ArtifactRepository,
T : AuthenticationSupported =
when (val scheme = repository.url.scheme) {
"s3" -> s3Repository(
repository.getCredentials(AwsCredentials::class),
LazyTimeoutConstraint(this)
)
"http", "https" -> URLRepository(LazyTimeoutConstraint(this))
else -> throw IllegalStateException("Unknown repository URL scheme: $scheme")
}
private fun s3Repository(
credentials: AwsCredentials?,
timeoutConstraint: TimeoutConstraint
): Repository {
val awsCredentials = credentials?.let {
if (it.sessionToken == null) {
BasicAWSCredentials(it.accessKey, it.secretKey)
} else {
BasicSessionCredentials(it.accessKey, it.secretKey, it.sessionToken)
}
}
return S3Repository(
credentials = awsCredentials,
endpoint = System.getProperty("org.gradle.s3.endpoint")?.let { URI(it) },
timeoutConstraint = timeoutConstraint
).apply {
name = "AWS S3"
}
}
private class LazyTimeoutConstraint(
private val resolver: AbstractResolver
) : TimeoutConstraint {
override fun getConnectionTimeout(): Int =
resolver.timeoutConstraint?.connectionTimeout ?: -1
override fun getReadTimeout(): Int =
resolver.timeoutConstraint?.readTimeout ?: -1
}
// Compatibility shim as UrlArtifactRepository was added in Gradle 6.0
private val ArtifactRepository.url: URI get() = when (this) {
is MavenArtifactRepository -> url
is IvyArtifactRepository -> url
else -> throw IllegalStateException("Unhandled repository type: ${this::class.simpleName}")
}

View File

@@ -1,24 +0,0 @@
package org.nixos.gradle2nix
import java.io.File
import java.net.URI
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)
internal fun String.toUri(): URI = URI(this)

View File

@@ -0,0 +1,128 @@
package org.nixos.gradle2nix.dependencygraph
import org.gradle.api.Plugin
import org.gradle.api.invocation.Gradle
import org.gradle.api.provider.Provider
import org.gradle.internal.build.event.BuildEventListenerRegistryInternal
import org.gradle.util.GradleVersion
import org.nixos.gradle2nix.dependencygraph.extractor.DependencyExtractor
import org.nixos.gradle2nix.dependencygraph.extractor.DependencyExtractorBuildService
import org.nixos.gradle2nix.dependencygraph.extractor.LegacyDependencyExtractor
import org.nixos.gradle2nix.dependencygraph.util.service
abstract class AbstractDependencyExtractorPlugin : Plugin<Gradle> {
// Register extension functions on `Gradle` type
private companion object : org.nixos.gradle2nix.dependencygraph.util.GradleExtensions()
/**
* The name of an accessible class that implements `org.gradle.dependencygraph.DependencyGraphRenderer`.
*/
abstract fun getRendererClassName(): String
internal lateinit var dependencyExtractorProvider: Provider<out DependencyExtractor>
override fun apply(gradle: Gradle) {
val gradleVersion = GradleVersion.current()
// Create the adapter based upon the version of Gradle
val applicatorStrategy = when {
gradleVersion < GradleVersion.version("8.0") -> PluginApplicatorStrategy.LegacyPluginApplicatorStrategy
else -> PluginApplicatorStrategy.DefaultPluginApplicatorStrategy
}
// Create the service
dependencyExtractorProvider = applicatorStrategy.createExtractorService(gradle, getRendererClassName())
gradle.rootProject { project ->
dependencyExtractorProvider
.get()
.rootProjectBuildDirectory = project.buildDir
}
// Register the service to listen for Build Events
applicatorStrategy.registerExtractorListener(gradle, dependencyExtractorProvider)
// Register the shutdown hook that should execute at the completion of the Gradle build.
applicatorStrategy.registerExtractorServiceShutdown(gradle, dependencyExtractorProvider)
}
/**
* Adapters for creating the [DependencyExtractor] and installing it into [Gradle] based upon the Gradle version.
*/
private interface PluginApplicatorStrategy {
fun createExtractorService(
gradle: Gradle,
rendererClassName: String
): Provider<out DependencyExtractor>
fun registerExtractorListener(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
)
fun registerExtractorServiceShutdown(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
)
@Suppress("DEPRECATION")
object LegacyPluginApplicatorStrategy : PluginApplicatorStrategy {
override fun createExtractorService(
gradle: Gradle,
rendererClassName: String
): Provider<out DependencyExtractor> {
val dependencyExtractor = LegacyDependencyExtractor(rendererClassName)
return gradle.providerFactory.provider { dependencyExtractor }
}
override fun registerExtractorListener(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
gradle.buildOperationListenerManager
.addListener(extractorServiceProvider.get())
}
override fun registerExtractorServiceShutdown(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
gradle.buildFinished {
extractorServiceProvider.get().close()
gradle.buildOperationListenerManager
.removeListener(extractorServiceProvider.get())
}
}
}
object DefaultPluginApplicatorStrategy : PluginApplicatorStrategy {
private const val SERVICE_NAME = "dependencyExtractorService"
override fun createExtractorService(
gradle: Gradle,
rendererClassName: String
): Provider<out DependencyExtractor> {
return gradle.sharedServices.registerIfAbsent(
SERVICE_NAME,
DependencyExtractorBuildService::class.java
) { it.parameters.rendererClassName.set(rendererClassName) }
}
override fun registerExtractorListener(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
gradle.service<BuildEventListenerRegistryInternal>()
.onOperationCompletion(extractorServiceProvider)
}
override fun registerExtractorServiceShutdown(
gradle: Gradle,
extractorServiceProvider: Provider<out DependencyExtractor>
) {
// No-op as DependencyExtractorService is Auto-Closable
}
}
}
}

View File

@@ -0,0 +1,11 @@
package org.nixos.gradle2nix.dependencygraph
import java.io.File
import org.nixos.gradle2nix.dependencygraph.model.ResolvedConfiguration
interface DependencyGraphRenderer {
fun outputDependencyGraph(
resolvedConfigurations: List<ResolvedConfiguration>,
outputDirectory: File
)
}

View File

@@ -0,0 +1,342 @@
package org.nixos.gradle2nix.dependencygraph.extractor
import java.io.File
import java.net.URI
import java.util.Collections
import org.gradle.api.GradleException
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.artifacts.result.ResolvedDependencyResult
import org.gradle.api.internal.artifacts.DefaultProjectComponentIdentifier
import org.gradle.api.internal.artifacts.configurations.ResolveConfigurationDependenciesBuildOperationType
import org.gradle.api.logging.Logging
import org.gradle.internal.exceptions.DefaultMultiCauseException
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
import org.nixos.gradle2nix.PARAM_INCLUDE_CONFIGURATIONS
import org.nixos.gradle2nix.PARAM_INCLUDE_PROJECTS
import org.nixos.gradle2nix.PARAM_REPORT_DIR
import org.nixos.gradle2nix.dependencygraph.DependencyGraphRenderer
import org.nixos.gradle2nix.dependencygraph.model.DependencyCoordinates
import org.nixos.gradle2nix.dependencygraph.model.DependencySource
import org.nixos.gradle2nix.dependencygraph.model.Repository
import org.nixos.gradle2nix.dependencygraph.model.ResolvedConfiguration
import org.nixos.gradle2nix.dependencygraph.model.ResolvedDependency
import org.nixos.gradle2nix.dependencygraph.util.loadOptionalParam
abstract class DependencyExtractor :
BuildOperationListener,
AutoCloseable {
private val resolvedConfigurations = Collections.synchronizedList(mutableListOf<ResolvedConfiguration>())
private val thrownExceptions = Collections.synchronizedList(mutableListOf<Throwable>())
var rootProjectBuildDirectory: File? = null
// Properties are lazily initialized so that System Properties are initialized by the time
// the values are used. This is required due to a bug in older Gradle versions. (https://github.com/gradle/gradle/issues/6825)
private val configurationFilter by lazy {
ResolvedConfigurationFilter(
loadOptionalParam(PARAM_INCLUDE_PROJECTS),
loadOptionalParam(PARAM_INCLUDE_CONFIGURATIONS)
)
}
private val dependencyGraphReportDir by lazy {
loadOptionalParam(PARAM_REPORT_DIR)
}
abstract fun getRendererClassName(): String
override fun started(buildOperation: BuildOperationDescriptor, startEvent: OperationStartEvent) {
// This method will never be called when registered in a `BuildServiceRegistry` (ie. Gradle 6.1 & higher)
// No-op
}
override fun progress(operationIdentifier: OperationIdentifier, progressEvent: OperationProgressEvent) {
// This method will never be called when registered in a `BuildServiceRegistry` (ie. Gradle 6.1 & higher)
// No-op
}
override fun finished(buildOperation: BuildOperationDescriptor, finishEvent: OperationFinishEvent) {
handleBuildOperationType<
ResolveConfigurationDependenciesBuildOperationType.Details,
ResolveConfigurationDependenciesBuildOperationType.Result
>(buildOperation, finishEvent) { details, result -> extractConfigurationDependencies(details, result) }
}
private inline fun <reified D, reified R> handleBuildOperationType(
buildOperation: BuildOperationDescriptor,
finishEvent: OperationFinishEvent,
handler: (details: D, result: R) -> Unit
) {
try {
handleBuildOperationTypeRaw<D, R>(buildOperation, finishEvent, handler)
} catch (e: Throwable) {
thrownExceptions.add(e)
throw e
}
}
private inline fun <reified D, reified R> handleBuildOperationTypeRaw(
buildOperation: BuildOperationDescriptor,
finishEvent: OperationFinishEvent,
handler: (details: D, result: R) -> Unit
) {
val details: D? = buildOperation.details.let {
if (it is D) it else null
}
val result: R? = finishEvent.result.let {
if (it is R) it else null
}
if (details == null && result == null) {
return
} else if (details == null || result == null) {
throw IllegalStateException("buildOperation.details & finishedEvent.result were unexpected types")
}
handler(details, result)
}
private fun extractConfigurationDependencies(
details: ResolveConfigurationDependenciesBuildOperationType.Details,
result: ResolveConfigurationDependenciesBuildOperationType.Result
) {
val repositories = details.repositories?.mapNotNull {
@Suppress("UNCHECKED_CAST")
Repository(
id = it.id,
type = enumValueOf(it.type),
name = it.name,
m2Compatible = it.type == "MAVEN" || (it.properties["M2_COMPATIBLE"] as? Boolean) ?: false,
metadataSources = (it.properties["METADATA_SOURCES"] as? List<String>) ?: emptyList(),
metadataResources = metadataResources(it),
artifactResources = artifactResources(it),
)
} ?: emptyList()
val rootComponent = result.rootComponent
if (rootComponent.dependencies.isEmpty()) {
// No dependencies to extract: can safely ignore
return
}
val projectIdentityPath = (rootComponent.id as? DefaultProjectComponentIdentifier)?.identityPath?.path
// TODO: At this point, any resolution not bound to a particular project will be assigned to the root "build :"
// This is because `details.buildPath` is always ':', which isn't correct in a composite build.
// It is possible to do better. By tracking the current build operation context, we can assign more precisely.
// See the Gradle Enterprise Build Scan Plugin: `ConfigurationResolutionCapturer_5_0`
val rootPath = projectIdentityPath ?: details.buildPath
if (!configurationFilter.include(rootPath, details.configurationName)) {
LOGGER.debug("Ignoring resolved configuration: $rootPath - ${details.configurationName}")
return
}
val rootId = if (projectIdentityPath == null) "build $rootPath" else componentId(rootComponent)
val rootSource = DependencySource(rootId, rootPath)
val resolvedConfiguration = ResolvedConfiguration(rootSource, details.configurationName, repositories)
for (directDependency in getResolvedDependencies(rootComponent)) {
val directDep = createComponentNode(
componentId(directDependency),
rootSource,
true,
directDependency,
result.getRepositoryId(directDependency)
)
resolvedConfiguration.addDependency(directDep)
walkComponentDependencies(result, directDependency, directDep.source, resolvedConfiguration)
}
resolvedConfigurations.add(resolvedConfiguration)
}
private fun walkComponentDependencies(
result: ResolveConfigurationDependenciesBuildOperationType.Result,
component: ResolvedComponentResult,
parentSource: DependencySource,
resolvedConfiguration: ResolvedConfiguration
) {
val componentSource = getSource(component, parentSource)
val direct = componentSource != parentSource
val dependencyComponents = getResolvedDependencies(component)
for (dependencyComponent in dependencyComponents) {
val dependencyId = componentId(dependencyComponent)
if (!resolvedConfiguration.hasDependency(dependencyId)) {
val dependencyNode = createComponentNode(
dependencyId,
componentSource,
direct,
dependencyComponent,
result.getRepositoryId(component)
)
resolvedConfiguration.addDependency(dependencyNode)
walkComponentDependencies(result, dependencyComponent, componentSource, resolvedConfiguration)
}
}
}
private fun getSource(component: ResolvedComponentResult, source: DependencySource): DependencySource {
val componentId = component.id
if (componentId is DefaultProjectComponentIdentifier) {
return DependencySource(componentId(component), componentId.identityPath.path)
}
return source
}
private fun getResolvedDependencies(component: ResolvedComponentResult): List<ResolvedComponentResult> {
return component.dependencies.filterIsInstance<ResolvedDependencyResult>().map { it.selected }.filter { it != component }
}
private fun createComponentNode(componentId: String, source: DependencySource, direct: Boolean, component: ResolvedComponentResult, repositoryId: String?): ResolvedDependency {
val componentDependencies = component.dependencies.filterIsInstance<ResolvedDependencyResult>().map { componentId(it.selected) }
return ResolvedDependency(
componentId,
source,
direct,
coordinates(component),
repositoryId,
componentDependencies
)
}
private fun componentId(component: ResolvedComponentResult): String {
return component.id.displayName
}
private fun coordinates(component: ResolvedComponentResult): DependencyCoordinates {
// TODO: Consider and handle null moduleVersion
val moduleVersionIdentifier = component.moduleVersion!!
return DependencyCoordinates(
moduleVersionIdentifier.group,
moduleVersionIdentifier.name,
moduleVersionIdentifier.version
)
}
private fun writeDependencyGraph() {
val outputDirectory = getOutputDir()
outputDirectory.mkdirs()
createRenderer().outputDependencyGraph(resolvedConfigurations, outputDirectory)
LOGGER.info("Wrote dependency graph to ${getOutputDir()}")
}
private fun createRenderer(): DependencyGraphRenderer {
LOGGER.info("Constructing renderer: ${getRendererClassName()}")
return Class.forName(getRendererClassName()).getDeclaredConstructor().newInstance() as DependencyGraphRenderer
}
private fun getOutputDir(): File {
if (dependencyGraphReportDir != null) {
return File(dependencyGraphReportDir!!)
}
if (rootProjectBuildDirectory == null) {
throw RuntimeException("Cannot determine report file location")
}
return File(
rootProjectBuildDirectory,
"reports/nix-dependency-graph"
)
}
override fun close() {
if (thrownExceptions.isNotEmpty()) {
throw DefaultMultiCauseException(
"The Gradle2Nix plugin encountered errors while extracting dependencies. " +
"Please report this issue at: https://github.com/tadfisher/gradle2nix/issues",
thrownExceptions
)
}
try {
writeDependencyGraph()
} catch (e: RuntimeException) {
throw GradleException(
"The Gradle2Nix plugin encountered errors while writing the dependency snapshot json file. " +
"Please report this issue at: https://github.com/tadfisher/gradle2nix/issues",
e
)
}
}
companion object {
private val LOGGER = Logging.getLogger(DependencyExtractor::class.java)
private const val M2_PATTERN =
"[organisation]/[module]/[revision]/[artifact]-[revision](-[classifier])(.[ext])"
private const val IVY_ARTIFACT_PATTERN = "[organisation]/[module]/[revision]/[type]s/[artifact](.[ext])";
private fun resources(urls: List<URI>, patterns: List<String>): List<String> {
if (urls.isEmpty()) {
return patterns
}
if (patterns.isEmpty()) {
return urls.map { it.toString() }
}
return mutableListOf<String>().apply {
for (pattern in patterns) {
for (url in urls) {
add(
url.toString()
.removeSuffix("/")
.plus("/")
.plus(pattern.removePrefix("/"))
)
}
}
}
}
private fun metadataResources(
repository: ResolveConfigurationDependenciesBuildOperationType.Repository
): List<String> {
return when (repository.type) {
Repository.Type.MAVEN.name -> {
resources(
listOfNotNull(repository.properties["URL"] as? URI),
listOf(M2_PATTERN)
)
}
Repository.Type.IVY.name -> {
@Suppress("UNCHECKED_CAST")
resources(
listOfNotNull(repository.properties["URL"] as? URI),
repository.properties["IVY_PATTERNS"] as? List<String> ?: listOf(IVY_ARTIFACT_PATTERN)
)
}
else -> emptyList()
}
}
private fun artifactResources(
repository: ResolveConfigurationDependenciesBuildOperationType.Repository
): List<String> {
return when (repository.type) {
Repository.Type.MAVEN.name -> {
@Suppress("UNCHECKED_CAST")
resources(
listOfNotNull(repository.properties["URL"] as? URI)
.plus(repository.properties["ARTIFACT_URLS"] as? List<URI> ?: emptyList()),
listOf(M2_PATTERN)
)
}
Repository.Type.IVY.name -> {
@Suppress("UNCHECKED_CAST")
resources(
listOfNotNull(repository.properties["URL"] as? URI),
repository.properties["ARTIFACT_PATTERNS"] as? List<String> ?: listOf(IVY_ARTIFACT_PATTERN)
)
}
else -> emptyList()
}
}
}
}

View File

@@ -0,0 +1,19 @@
package org.nixos.gradle2nix.dependencygraph.extractor
import org.gradle.api.provider.Property
import org.gradle.api.services.BuildService
import org.gradle.api.services.BuildServiceParameters
abstract class DependencyExtractorBuildService :
DependencyExtractor(),
BuildService<DependencyExtractorBuildService.Params>
{
// Some parameters for the web server
internal interface Params : BuildServiceParameters {
val rendererClassName: Property<String>
}
override fun getRendererClassName(): String {
return parameters.rendererClassName.get()
}
}

View File

@@ -0,0 +1,10 @@
package org.nixos.gradle2nix.dependencygraph.extractor
open class LegacyDependencyExtractor(
private val rendererClassName: String
) : DependencyExtractor() {
override fun getRendererClassName(): String {
return rendererClassName
}
}

View File

@@ -0,0 +1,16 @@
package org.nixos.gradle2nix.dependencygraph.extractor
class ResolvedConfigurationFilter(projectFilter: String?, configurationFilter: String?) {
val projectRegex = projectFilter?.toRegex()
val configurationRegex = configurationFilter?.toRegex()
fun include(projectPath: String, configurationName: String): Boolean {
if (projectRegex != null && !projectRegex.matches(projectPath)) {
return false
}
if (configurationRegex != null && !configurationRegex.matches(configurationName)) {
return false
}
return true
}
}

View File

@@ -0,0 +1,19 @@
package org.nixos.gradle2nix.dependencygraph.util
import org.gradle.api.internal.GradleInternal
import org.gradle.api.invocation.Gradle
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ProviderFactory
import org.gradle.internal.operations.BuildOperationListenerManager
internal abstract class GradleExtensions {
inline val Gradle.providerFactory: ProviderFactory
get() = service()
inline val Gradle.buildOperationListenerManager: BuildOperationListenerManager
get() = service()
}
internal inline fun <reified T> Gradle.service(): T =
(this as GradleInternal).services.get(T::class.java)

View File

@@ -0,0 +1,6 @@
package org.nixos.gradle2nix.dependencygraph.util
internal fun loadOptionalParam(envName: String): String? {
return System.getProperty(envName)
?: System.getenv()[envName]
}

View File

@@ -0,0 +1,65 @@
package org.nixos.gradle2nix
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.forceresolve.LegacyResolveProjectDependenciesTask
import org.nixos.gradle2nix.forceresolve.ResolveProjectDependenciesTask
// 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

@@ -0,0 +1,20 @@
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: DefaultTask() {
private fun getReportableConfigurations(): List<Configuration> {
return project.configurations.filter { it.isCanBeResolved }
}
@TaskAction
fun action() {
for (configuration in getReportableConfigurations()) {
configuration.incoming.resolutionResult.root
}
}
}

View File

@@ -0,0 +1,31 @@
package org.nixos.gradle2nix.forceresolve
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.result.ResolvedComponentResult
import org.gradle.api.provider.Provider
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: DefaultTask() {
private val configurationResolvers = Cached.of { createConfigurationResolvers() }
private fun createConfigurationResolvers(): List<Provider<ResolvedComponentResult>> {
return getReportableConfigurations().map {
it.incoming.resolutionResult.rootComponent
}
}
private fun getReportableConfigurations(): List<Configuration> {
return project.configurations.filter { it.isCanBeResolved }
}
@TaskAction
fun action() {
for (configuration in configurationResolvers.get()) {
configuration.get()
}
}
}