Use custom dependency resolution

- Use Apache Ivy to resolve artifact URLs
- Update build model with full artifact IDs
- Generate Maven module metadata to support dynamic version constraints
- Resolve snapshot versions and generate snapshot metadata
- Add test fixtures and rewrite Gradle plugin tests
- Update dependencies
This commit is contained in:
Tad Fisher
2020-01-23 10:01:38 -08:00
parent 9a47ead9cb
commit 648be6bd07
72 changed files with 5163 additions and 3060 deletions

View File

@@ -6,9 +6,6 @@ plugins {
application
}
group = "org.nixos"
version = "1.0.0-SNAPSHOT"
dependencies {
implementation(project(":model"))
implementation(kotlin("stdlib-jdk8"))

View File

@@ -1,16 +1,16 @@
# 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.ajalt:clikt:2.2.0
com.squareup.moshi:moshi-adapters:1.9.1
com.squareup.moshi:moshi-kotlin:1.9.1
com.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
org.gradle:gradle-tooling-api:5.6.3
org.jetbrains.kotlin:kotlin-reflect:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
com.github.ajalt:clikt:2.3.0
com.squareup.moshi:moshi-adapters:1.9.2
com.squareup.moshi:moshi-kotlin:1.9.2
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:2.4.3
org.gradle:gradle-tooling-api:6.1
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0
org.slf4j:slf4j-api:2.0.0-alpha1

View File

@@ -1,17 +1,18 @@
# 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.ajalt:clikt:2.2.0
com.squareup.moshi:moshi-adapters:1.9.1
com.squareup.moshi:moshi-kotlin:1.9.1
com.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
org.gradle:gradle-tooling-api:5.6.3
org.jetbrains.kotlin:kotlin-reflect:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
com.github.ajalt:clikt:2.3.0
com.squareup.moshi:moshi-adapters:1.9.2
com.squareup.moshi:moshi-kotlin:1.9.2
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:2.4.3
net.swiftzer.semver:semver:1.1.1
org.gradle:gradle-tooling-api:6.1
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0
org.slf4j:slf4j-api:2.0.0-alpha1
org.slf4j:slf4j-simple:2.0.0-alpha1

View File

@@ -1,16 +1,16 @@
# 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.ajalt:clikt:2.2.0
com.squareup.moshi:moshi-adapters:1.9.1
com.squareup.moshi:moshi-kotlin:1.9.1
com.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
org.gradle:gradle-tooling-api:5.6.3
org.jetbrains.kotlin:kotlin-reflect:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
com.github.ajalt:clikt:2.3.0
com.squareup.moshi:moshi-adapters:1.9.2
com.squareup.moshi:moshi-kotlin:1.9.2
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:2.4.3
org.gradle:gradle-tooling-api:6.1
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0
org.slf4j:slf4j-api:2.0.0-alpha1

View File

@@ -1,17 +1,18 @@
# 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.ajalt:clikt:2.2.0
com.squareup.moshi:moshi-adapters:1.9.1
com.squareup.moshi:moshi-kotlin:1.9.1
com.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
org.gradle:gradle-tooling-api:5.6.3
org.jetbrains.kotlin:kotlin-reflect:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
com.github.ajalt:clikt:2.3.0
com.squareup.moshi:moshi-adapters:1.9.2
com.squareup.moshi:moshi-kotlin:1.9.2
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:2.4.3
net.swiftzer.semver:semver:1.1.1
org.gradle:gradle-tooling-api:6.1
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0
org.slf4j:slf4j-api:2.0.0-alpha1
org.slf4j:slf4j-simple:2.0.0-alpha1

View File

@@ -19,7 +19,7 @@
# '';
# }
{ stdenv, lib, buildEnv, fetchurl, gradleGen, writeText }:
{ stdenv, buildEnv, fetchurl, gradleGen, writeText, writeTextDir }:
{ envSpec
, pname ? null
@@ -27,9 +27,18 @@
, enableParallelBuilding ? true
, gradleFlags ? [ "build" ]
, gradlePackage ? null
, enableDebug ? false
, ... } @ args:
let
inherit (builtins)
filter sort replaceStrings attrValues match fromJSON
concatStringsSep;
inherit (stdenv.lib)
versionOlder unique mapAttrs last concatMapStringsSep removeSuffix
optionalString groupBy' readFile hasSuffix;
mkDep = depSpec: stdenv.mkDerivation {
inherit (depSpec) name;
@@ -41,40 +50,171 @@ let
installPhase = ''
mkdir -p $out/${depSpec.path}
ln -s $src $out/${depSpec.path}/${depSpec.filename}
ln -s $src $out/${depSpec.path}/${depSpec.name}
'';
};
mkModuleMetadata = deps:
let
ids = filter
(id: id.type == "pom")
(map (dep: dep.id) deps);
modules = groupBy'
(meta: id:
let
isNewer = versionOlder meta.latest id.version;
isNewerRelease =
!(hasSuffix "-SNAPSHOT" id.version) &&
versionOlder meta.release id.version;
in {
groupId = id.group;
artifactId = id.name;
latest = if isNewer then id.version else meta.latest;
release = if isNewerRelease then id.version else meta.release;
versions = meta.versions ++ [id.version];
}
)
{
latest = "";
release = "";
versions = [];
}
(id: "${replaceStrings ["."] ["/"] id.group}/${id.name}/maven-metadata.xml")
ids;
in
attrValues (mapAttrs (path: meta:
let
versions' = sort versionOlder (unique meta.versions);
in
with meta; writeTextDir path ''
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1">
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<versioning>
${optionalString (latest != "") "<latest>${latest}</latest>"}
${optionalString (release != "") "<release>${release}</release>"}
<versions>
${concatMapStringsSep "\n " (v: "<version>${v}</version>") versions'}
</versions>
</versioning>
</metadata>
''
) modules);
mkSnapshotMetadata = deps:
let
snapshotDeps = filter (dep: dep ? build && dep ? timestamp) deps;
modules = groupBy'
(meta: dep:
let
id = dep.id;
isNewer = dep.build > meta.buildNumber;
# Timestamp values can be bogus, e.g. jitpack.io
updated = if (match "[0-9]{8}\.[0-9]{6}" dep.timestamp) != null
then replaceStrings ["."] [""] dep.timestamp
else "";
in {
groupId = id.group;
artifactId = id.name;
version = id.version;
timestamp = if isNewer then dep.timestamp else meta.timestamp;
buildNumber = if isNewer then dep.build else meta.buildNumber;
lastUpdated = if isNewer then updated else meta.lastUpdated;
versions = meta.versions or [] ++ [{
classifier = id.classifier or "";
extension = id.extension;
value = "${removeSuffix "-SNAPSHOT" id.version}-${dep.timestamp}-${toString dep.build}";
updated = updated;
}];
}
)
{
timestamp = "";
buildNumber = -1;
lastUpdated = "";
}
(dep: "${replaceStrings ["."] ["/"] dep.id.group}/${dep.id.name}/${dep.id.version}/maven-metadata.xml")
snapshotDeps;
mkSnapshotVersion = version: ''
<snapshotVersion>
${optionalString (version.classifier != "") "<classifier>${version.classifier}</classifier>"}
<extension>${version.extension}</extension>
<value>${version.value}</value>
${optionalString (version.updated != "") "<updated>${version.updated}</updated>"}
</snapshotVersion>
'';
in
attrValues (mapAttrs (path: meta:
with meta; writeTextDir path ''
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1">
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
<versioning>
<snapshot>
${optionalString (timestamp != "") "<timestamp>${timestamp}</timestamp>"}
${optionalString (buildNumber != -1) "<buildNumber>${toString buildNumber}</buildNumber>"}
</snapshot>
${optionalString (lastUpdated != "") "<lastUpdated>${lastUpdated}</lastUpdated>"}
<snapshotVersions>
${concatMapStringsSep "\n " mkSnapshotVersion versions}
</snapshotVersions>
</versioning>
</metadata>
''
) modules);
mkRepo = project: type: deps: buildEnv {
name = "${project}-gradle-${type}-env";
paths = map mkDep deps;
paths = map mkDep deps ++ mkModuleMetadata deps ++ mkSnapshotMetadata deps;
};
mkInitScript = projectSpec:
let
repos = builtins.mapAttrs (mkRepo projectSpec.name) projectSpec.dependencies;
repos = mapAttrs (mkRepo projectSpec.name) projectSpec.dependencies;
in
writeText "init.gradle" ''
static def offlineRepo(RepositoryHandler repositories, String env, String path) {
repositories.clear()
repositories.maven {
name "Nix''${env.capitalize()}MavenOffline"
url path
metadataSources {
it.gradleMetadata()
it.mavenPom()
it.artifact()
}
}
repositories.ivy {
name "Nix''${env.capitalize()}IvyOffline"
url path
layout "maven"
metadataSources {
it.gradleMetadata()
it.ivyDescriptor()
it.artifact()
}
}
}
gradle.settingsEvaluated {
it.pluginManagement.repositories {
clear()
maven { url = uri("${repos.plugin}") }
}
offlineRepo(it.pluginManagement.repositories, "plugin", "${repos.plugin}")
}
gradle.projectsLoaded {
allprojects {
buildscript {
repositories {
clear()
maven { url = uri("${repos.buildscript}") }
}
allprojects {
buildscript {
offlineRepo(repositories, "buildscript", "${repos.buildscript}")
}
offlineRepo(repositories, "project", "${repos.project}")
}
repositories {
clear()
maven { url = uri("${repos.project}") }
}
}
}
'';
@@ -95,9 +235,9 @@ let
gradle = args.gradlePackage or mkGradle projectSpec.gradle;
};
gradleEnv = builtins.mapAttrs
gradleEnv = mapAttrs
(_: p: mkProjectEnv p)
(builtins.fromJSON (builtins.readFile envSpec));
(fromJSON (readFile envSpec));
projectEnv = gradleEnv."";
pname = args.pname or projectEnv.name;
@@ -118,9 +258,10 @@ in stdenv.mkDerivation (args // {
"GRADLE_USER_HOME=$(mktemp -d)" \
gradle --offline --no-daemon --no-build-cache \
--info --full-stacktrace --warning-mode=all \
${lib.optionalString enableParallelBuilding "--parallel"} \
${optionalString enableParallelBuilding "--parallel"} \
${optionalString enableDebug "-Dorg.gradle.debug=true"} \
--init-script ${projectEnv.initScript} \
${builtins.concatStringsSep " " gradleFlags}
${concatStringsSep " " gradleFlags}
)
runHook postBuild

View File

@@ -8,16 +8,7 @@ data class NixGradleEnv(
val version: String,
val path: String,
val gradle: DefaultGradle,
val dependencies: Map<String, List<Dependency>>
)
@JsonClass(generateAdapter = true)
data class Dependency(
val name: String,
val filename: String,
val path: String,
val urls: List<String>,
val sha256: String
val dependencies: Map<String, List<DefaultArtifact>>
)
fun buildEnv(builds: Map<String, DefaultBuild>): Map<String, NixGradleEnv> =
@@ -28,58 +19,31 @@ fun buildEnv(builds: Map<String, DefaultBuild>): Map<String, NixGradleEnv> =
path = path,
gradle = build.gradle,
dependencies = mapOf(
"plugin" to buildRepo(build.pluginDependencies).values.toList(),
"buildscript" to build.rootProject.collectDependencies(DefaultProject::buildscriptDependencies)
.values.toList(),
"plugin" to build.pluginDependencies,
"buildscript" to build.rootProject.collectDependencies(DefaultProject::buildscriptDependencies),
"project" to build.rootProject.collectDependencies(DefaultProject::projectDependencies)
.values.toList()
)
)
}
private fun DefaultProject.collectDependencies(chooser: DefaultProject.() -> DefaultDependencies): Map<DefaultArtifact, Dependency> {
val result = mutableMapOf<DefaultArtifact, Dependency>()
mergeRepo(result, buildRepo(chooser()))
private fun DefaultProject.collectDependencies(
chooser: DefaultProject.() -> List<DefaultArtifact>
): List<DefaultArtifact> {
val result = mutableMapOf<ArtifactIdentifier, DefaultArtifact>()
mergeRepo(result, chooser())
for (child in children) {
mergeRepo(result, child.collectDependencies(chooser))
}
return result
return result.values.toList()
}
private fun buildRepo(deps: DefaultDependencies): Map<DefaultArtifact, Dependency> =
deps.artifacts.associate { artifact ->
val name = with(artifact) {
buildString {
append("$groupId-$artifactId-$version")
if (classifier.isNotEmpty()) append("-$classifier")
append("-$extension")
replace(Regex("[^A-Za-z0-9+\\-._?=]"), "_")
}
}
val filename = with(artifact) {
buildString {
append("$artifactId-$version")
if (classifier.isNotEmpty()) append("-$classifier")
append(".$extension")
}
}
val path = with(artifact) { "${groupId.replace(".", "/")}/$artifactId/$version" }
val dep = Dependency(
name = name,
filename = filename,
path = path,
urls = deps.repositories.maven.flatMap { repo ->
repo.urls.map { "${it.removeSuffix("/")}/$path/$filename" }
},
sha256 = artifact.sha256
)
artifact to dep
}
private fun mergeRepo(base: MutableMap<DefaultArtifact, Dependency>, extra: Map<DefaultArtifact, Dependency>) {
extra.forEach { (artifact, dep) ->
base.merge(artifact, dep) { old, new ->
old.copy(urls = old.urls + (new.urls - old.urls))
private fun mergeRepo(
base: MutableMap<ArtifactIdentifier, DefaultArtifact>,
extra: List<DefaultArtifact>
) {
extra.forEach { artifact ->
base.merge(artifact.id, artifact) { old, new ->
old.copy(urls = old.urls.union(new.urls).toList())
}
}
}

View File

@@ -21,8 +21,10 @@ fun ProjectConnection.getBuildModel(config: Config, path: String): DefaultBuild
"-Porg.nixos.gradle2nix.configurations=${config.configurations.joinToString(",")}",
"-Porg.nixos.gradle2nix.subprojects=${config.subprojects.joinToString(",")}"
)
if (config.gradleArgs != null) addArguments(config.gradleArgs)
if (path.isNotEmpty()) addArguments("--project-dir=$path")
if (!config.quiet) {
setColorOutput(true)
setStandardOutput(System.err)
setStandardError(System.err)
}

View File

@@ -4,7 +4,8 @@ import java.io.PrintStream
class Logger(
val out: PrintStream = System.err,
val verbose: Boolean) {
val verbose: Boolean
) {
val log: (String) -> Unit = { if (verbose) out.println(it) }
val warn: (String) -> Unit = { out.println("Warning: $it")}

View File

@@ -8,7 +8,12 @@ import com.github.ajalt.clikt.parameters.arguments.ProcessedArgument
import com.github.ajalt.clikt.parameters.arguments.argument
import com.github.ajalt.clikt.parameters.arguments.convert
import com.github.ajalt.clikt.parameters.arguments.default
import com.github.ajalt.clikt.parameters.options.*
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.multiple
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.split
import com.github.ajalt.clikt.parameters.options.validate
import com.github.ajalt.clikt.parameters.types.file
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
@@ -20,6 +25,7 @@ val shareDir: String = System.getProperty("org.nixos.gradle2nix.share")
data class Config(
val gradleVersion: String?,
val gradleArgs: String?,
val configurations: List<String>,
val projectDir: File,
val includes: List<File>,
@@ -37,6 +43,10 @@ class Main : CliktCommand(
metavar = "VERSION",
help = "Use a specific Gradle version")
private val gradleArgs: String? by option("--gradle-args", "-a",
metavar = "ARGS",
help = "Extra arguments to pass to Gradle")
private val configurations: List<String> by option("--configuration", "-c",
metavar = "NAME",
help = "Add a configuration to resolve (default: all configurations)")
@@ -96,7 +106,16 @@ class Main : CliktCommand(
}
override fun run() {
val config = Config(gradleVersion, configurations, projectDir, includes, subprojects, buildSrc, quiet)
val config = Config(
gradleVersion,
gradleArgs,
configurations,
projectDir,
includes,
subprojects,
buildSrc,
quiet
)
val (log, _, _) = Logger(verbose = !config.quiet)
val paths = resolveProjects(config).map { p ->

View File

@@ -3,15 +3,20 @@ plugins {
idea
kotlin("jvm") version embeddedKotlinVersion apply false
kotlin("kapt") version embeddedKotlinVersion apply false
id("com.github.johnrengelman.shadow") version "5.1.0" apply false
id("org.ajoberstar.stutter") version "0.5.0" apply false
id("com.github.johnrengelman.shadow") version "5.2.0" apply false
id("org.ajoberstar.stutter") version "0.5.1" apply false
}
group = "org.nixos.gradle2nix"
version = property("VERSION") ?: "unspecified"
subprojects {
repositories {
jcenter()
maven { url = uri("https://repo.gradle.org/gradle/libs-releases") }
}
group = rootProject.group
version = rootProject.version
}
allprojects {
@@ -45,7 +50,7 @@ allprojects {
tasks {
wrapper {
gradleVersion = "5.6.3"
gradleVersion = "6.1"
distributionType = Wrapper.DistributionType.ALL
}
}

View File

@@ -0,0 +1,13 @@
plugins {
id 'java'
}
repositories {
jcenter()
mavenCentral()
}
dependencies {
implementation 'com.squareup.okio:okio:2.2.2'
implementation 'com.squareup.moshi:moshi:1.8.0'
}

View File

@@ -0,0 +1,13 @@
plugins {
java
}
repositories {
jcenter()
mavenCentral()
}
dependencies {
implementation("com.squareup.okio:okio:2.2.2")
implementation("com.squareup.moshi:moshi:1.8.0")
}

View File

@@ -0,0 +1,11 @@
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.badlogicgames.gdx:gdx-platform:1.9.9:natives-desktop'
}

View File

@@ -0,0 +1,11 @@
plugins {
java
}
repositories {
mavenCentral()
}
dependencies {
implementation("com.badlogicgames.gdx:gdx-platform:1.9.9:natives-desktop")
}

View File

@@ -0,0 +1,12 @@
plugins {
id 'java'
}
repositories {
jcenter()
maven { url 'https://jitpack.io' }
}
dependencies {
implementation 'com.github.anuken:packr:-SNAPSHOT'
}

View File

@@ -0,0 +1,12 @@
plugins {
id "java"
}
repositories {
mavenCentral()
maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}
dependencies {
implementation "com.squareup.okio:okio:2.5.0-SNAPSHOT"
}

View File

@@ -0,0 +1,12 @@
plugins {
java
}
repositories {
mavenCentral()
maven { url = uri("https://oss.sonatype.org/content/repositories/snapshots/") }
}
dependencies {
"implementation"("com.squareup.okio:okio:2.5.0-SNAPSHOT")
}

View File

@@ -0,0 +1,20 @@
plugins {
java
}
repositories {
ivy {
url = uri("https://asset.opendof.org")
layout("pattern") {
this as IvyPatternRepositoryLayout
ivy("ivy2/[organisation]/[module]/[revision]/ivy(.[platform]).xml")
artifact("artifact/[organisation]/[module]/[revision](/[platform])(/[type]s)/[artifact]-[revision](-[classifier]).[ext]")
}
}
}
dependencies {
dependencies {
implementation("org.opendof.core-java:dof-cipher-sms4:1.0")
}
}

View File

@@ -0,0 +1,7 @@
plugins {
id "org.jetbrains.kotlin.jvm" version "1.3.50"
}
repositories {
jcenter()
}

View File

@@ -0,0 +1,7 @@
plugins {
kotlin("jvm") version "1.3.50"
}
repositories {
jcenter()
}

View File

@@ -0,0 +1,15 @@
subprojects {
apply plugin: 'java'
}
project(':child-a') {
dependencies {
implementation project(':child-b')
}
}
project(':child-b') {
dependencies {
implementation project(':child-c')
}
}

View File

@@ -0,0 +1 @@
include ':child-a', ':child-b', ':child-c', ':child-d'

View File

@@ -0,0 +1,13 @@
plugins {
id 'java'
}
allprojects {
repositories {
jcenter()
}
}
dependencies {
testImplementation 'junit:junit:4.12'
}

View File

@@ -0,0 +1,7 @@
plugins {
id 'java'
}
dependencies {
implementation 'com.squareup.okio:okio:2.2.2'
}

View File

@@ -0,0 +1,8 @@
plugins {
id 'java'
}
dependencies {
implementation project(':child-a')
implementation 'com.squareup.moshi:moshi:1.8.0'
}

View File

@@ -0,0 +1 @@
include ':child-a', ':child-b'

View File

@@ -0,0 +1,13 @@
plugins {
java
}
allprojects {
repositories {
jcenter()
}
}
dependencies {
testImplementation("junit:junit:4.12")
}

View File

@@ -0,0 +1,7 @@
plugins {
java
}
dependencies {
implementation("com.squareup.okio:okio:2.2.2")
}

View File

@@ -0,0 +1,8 @@
plugins {
java
}
dependencies {
implementation(project(":child-a"))
implementation("com.squareup.moshi:moshi:1.8.0")
}

View File

@@ -0,0 +1 @@
include(":child-a", ":child-b")

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@
# '';
# }
{ stdenv, lib, buildEnv, fetchurl, gradleGen, writeText }:
{ stdenv, buildEnv, fetchurl, gradleGen, writeText, writeTextDir }:
{ envSpec
, pname ? null
@@ -27,9 +27,18 @@
, enableParallelBuilding ? true
, gradleFlags ? [ "build" ]
, gradlePackage ? null
, enableDebug ? false
, ... } @ args:
let
inherit (builtins)
filter sort replaceStrings attrValues match fromJSON
concatStringsSep;
inherit (stdenv.lib)
versionOlder unique mapAttrs last concatMapStringsSep removeSuffix
optionalString groupBy' readFile hasSuffix;
mkDep = depSpec: stdenv.mkDerivation {
inherit (depSpec) name;
@@ -41,40 +50,171 @@ let
installPhase = ''
mkdir -p $out/${depSpec.path}
ln -s $src $out/${depSpec.path}/${depSpec.filename}
ln -s $src $out/${depSpec.path}/${depSpec.name}
'';
};
mkModuleMetadata = deps:
let
ids = filter
(id: id.type == "pom")
(map (dep: dep.id) deps);
modules = groupBy'
(meta: id:
let
isNewer = versionOlder meta.latest id.version;
isNewerRelease =
!(hasSuffix "-SNAPSHOT" id.version) &&
versionOlder meta.release id.version;
in {
groupId = id.group;
artifactId = id.name;
latest = if isNewer then id.version else meta.latest;
release = if isNewerRelease then id.version else meta.release;
versions = meta.versions ++ [id.version];
}
)
{
latest = "";
release = "";
versions = [];
}
(id: "${replaceStrings ["."] ["/"] id.group}/${id.name}/maven-metadata.xml")
ids;
in
attrValues (mapAttrs (path: meta:
let
versions' = sort versionOlder (unique meta.versions);
in
with meta; writeTextDir path ''
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1">
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<versioning>
${optionalString (latest != "") "<latest>${latest}</latest>"}
${optionalString (release != "") "<release>${release}</release>"}
<versions>
${concatMapStringsSep "\n " (v: "<version>${v}</version>") versions'}
</versions>
</versioning>
</metadata>
''
) modules);
mkSnapshotMetadata = deps:
let
snapshotDeps = filter (dep: dep ? build && dep ? timestamp) deps;
modules = groupBy'
(meta: dep:
let
id = dep.id;
isNewer = dep.build > meta.buildNumber;
# Timestamp values can be bogus, e.g. jitpack.io
updated = if (match "[0-9]{8}\.[0-9]{6}" dep.timestamp) != null
then replaceStrings ["."] [""] dep.timestamp
else "";
in {
groupId = id.group;
artifactId = id.name;
version = id.version;
timestamp = if isNewer then dep.timestamp else meta.timestamp;
buildNumber = if isNewer then dep.build else meta.buildNumber;
lastUpdated = if isNewer then updated else meta.lastUpdated;
versions = meta.versions or [] ++ [{
classifier = id.classifier or "";
extension = id.extension;
value = "${removeSuffix "-SNAPSHOT" id.version}-${dep.timestamp}-${toString dep.build}";
updated = updated;
}];
}
)
{
timestamp = "";
buildNumber = -1;
lastUpdated = "";
}
(dep: "${replaceStrings ["."] ["/"] dep.id.group}/${dep.id.name}/${dep.id.version}/maven-metadata.xml")
snapshotDeps;
mkSnapshotVersion = version: ''
<snapshotVersion>
${optionalString (version.classifier != "") "<classifier>${version.classifier}</classifier>"}
<extension>${version.extension}</extension>
<value>${version.value}</value>
${optionalString (version.updated != "") "<updated>${version.updated}</updated>"}
</snapshotVersion>
'';
in
attrValues (mapAttrs (path: meta:
with meta; writeTextDir path ''
<?xml version="1.0" encoding="UTF-8"?>
<metadata modelVersion="1.1">
<groupId>${groupId}</groupId>
<artifactId>${artifactId}</artifactId>
<version>${version}</version>
<versioning>
<snapshot>
${optionalString (timestamp != "") "<timestamp>${timestamp}</timestamp>"}
${optionalString (buildNumber != -1) "<buildNumber>${toString buildNumber}</buildNumber>"}
</snapshot>
${optionalString (lastUpdated != "") "<lastUpdated>${lastUpdated}</lastUpdated>"}
<snapshotVersions>
${concatMapStringsSep "\n " mkSnapshotVersion versions}
</snapshotVersions>
</versioning>
</metadata>
''
) modules);
mkRepo = project: type: deps: buildEnv {
name = "${project}-gradle-${type}-env";
paths = map mkDep deps;
paths = map mkDep deps ++ mkModuleMetadata deps ++ mkSnapshotMetadata deps;
};
mkInitScript = projectSpec:
let
repos = builtins.mapAttrs (mkRepo projectSpec.name) projectSpec.dependencies;
repos = mapAttrs (mkRepo projectSpec.name) projectSpec.dependencies;
in
writeText "init.gradle" ''
static def offlineRepo(RepositoryHandler repositories, String env, String path) {
repositories.clear()
repositories.maven {
name "Nix''${env.capitalize()}MavenOffline"
url path
metadataSources {
it.gradleMetadata()
it.mavenPom()
it.artifact()
}
}
repositories.ivy {
name "Nix''${env.capitalize()}IvyOffline"
url path
layout "maven"
metadataSources {
it.gradleMetadata()
it.ivyDescriptor()
it.artifact()
}
}
}
gradle.settingsEvaluated {
it.pluginManagement.repositories {
clear()
maven { url = uri("${repos.plugin}") }
}
offlineRepo(it.pluginManagement.repositories, "plugin", "${repos.plugin}")
}
gradle.projectsLoaded {
allprojects {
buildscript {
repositories {
clear()
maven { url = uri("${repos.buildscript}") }
}
allprojects {
buildscript {
offlineRepo(repositories, "buildscript", "${repos.buildscript}")
}
offlineRepo(repositories, "project", "${repos.project}")
}
repositories {
clear()
maven { url = uri("${repos.project}") }
}
}
}
'';
@@ -95,9 +235,9 @@ let
gradle = args.gradlePackage or mkGradle projectSpec.gradle;
};
gradleEnv = builtins.mapAttrs
gradleEnv = mapAttrs
(_: p: mkProjectEnv p)
(builtins.fromJSON (builtins.readFile envSpec));
(fromJSON (readFile envSpec));
projectEnv = gradleEnv."";
pname = args.pname or projectEnv.name;
@@ -118,9 +258,10 @@ in stdenv.mkDerivation (args // {
"GRADLE_USER_HOME=$(mktemp -d)" \
gradle --offline --no-daemon --no-build-cache \
--info --full-stacktrace --warning-mode=all \
${lib.optionalString enableParallelBuilding "--parallel"} \
${optionalString enableParallelBuilding "--parallel"} \
${optionalString enableDebug "-Dorg.gradle.debug=true"} \
--init-script ${projectEnv.initScript} \
${builtins.concatStringsSep " " gradleFlags}
${concatStringsSep " " gradleFlags}
)
runHook postBuild

3
gradle.properties Normal file
View File

@@ -0,0 +1,3 @@
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
VERSION=1.0.0-rc1

Binary file not shown.

View File

@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.3-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

29
gradlew vendored
View File

@@ -154,19 +154,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
i=`expr $i + 1`
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -175,14 +175,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=$(save "$@")
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View File

@@ -4,7 +4,7 @@ plugins {
}
dependencies {
api("com.squareup.moshi:moshi:+")
api("com.squareup.okio:okio:+")
kapt("com.squareup.moshi:moshi-kotlin-codegen:+")
api("com.squareup.moshi:moshi:latest.release")
kapt("com.squareup.moshi:moshi-kotlin-codegen:latest.release")
implementation("net.swiftzer.semver:semver:latest.release")
}

View File

@@ -1,11 +1,12 @@
# 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.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
org.jetbrains.kotlin:kotlin-reflect:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:1.16.0
net.swiftzer.semver:semver:1.1.1
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0

View File

@@ -1,8 +1,6 @@
# 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.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
org.jetbrains:annotations:13.0
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:1.16.0
net.swiftzer.semver:semver:1.1.1

View File

@@ -1,11 +1,12 @@
# 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.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
org.jetbrains.kotlin:kotlin-reflect:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:1.16.0
net.swiftzer.semver:semver:1.1.1
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0

View File

@@ -1,11 +1,12 @@
# 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.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
org.jetbrains.kotlin:kotlin-reflect:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:1.16.0
net.swiftzer.semver:semver:1.1.1
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0

View File

@@ -1,20 +1,22 @@
package org.nixos.gradle2nix
import com.squareup.moshi.JsonClass
import net.swiftzer.semver.SemVer
import java.io.Serializable
import java.lang.IllegalArgumentException
@JsonClass(generateAdapter = true)
data class DefaultBuild(
override val gradle: DefaultGradle,
override val pluginDependencies: DefaultDependencies,
override val pluginDependencies: List<DefaultArtifact>,
override val rootProject: DefaultProject,
override val includedBuilds: List<DefaultIncludedBuild>
) : Build, Serializable {
constructor(model: Build) : this(
DefaultGradle(model.gradle),
DefaultDependencies(model.pluginDependencies),
model.pluginDependencies.map(::DefaultArtifact),
DefaultProject(model.rootProject),
model.includedBuilds.map { DefaultIncludedBuild(it) }
model.includedBuilds.map(::DefaultIncludedBuild)
)
}
@@ -52,8 +54,8 @@ data class DefaultProject(
override val version: String,
override val path: String,
override val projectDir: String,
override val buildscriptDependencies: DefaultDependencies,
override val projectDependencies: DefaultDependencies,
override val buildscriptDependencies: List<DefaultArtifact>,
override val projectDependencies: List<DefaultArtifact>,
override val children: List<DefaultProject>
) : Project, Serializable {
constructor(model: Project) : this(
@@ -61,59 +63,75 @@ data class DefaultProject(
model.version,
model.path,
model.projectDir,
DefaultDependencies(model.buildscriptDependencies),
DefaultDependencies(model.projectDependencies),
model.buildscriptDependencies.map(::DefaultArtifact),
model.projectDependencies.map(::DefaultArtifact),
model.children.map { DefaultProject(it) }
)
}
@JsonClass(generateAdapter = true)
data class DefaultDependencies(
override val repositories: DefaultRepositories,
override val artifacts: List<DefaultArtifact>
) : Dependencies, Serializable {
constructor(model: Dependencies) : this(
DefaultRepositories(model.repositories),
model.artifacts.map { DefaultArtifact(it) }
)
}
@JsonClass(generateAdapter = true)
data class DefaultRepositories(
override val maven: List<DefaultMaven>
) : Repositories, Serializable {
constructor(model: Repositories) : this(
model.maven.map { DefaultMaven(it) }
)
}
@JsonClass(generateAdapter = true)
data class DefaultMaven(
override val urls: List<String>
) : Maven, Serializable {
constructor(model: Maven) : this(
model.urls.toList()
)
}
@JsonClass(generateAdapter = true)
data class DefaultArtifact(
override val groupId: String,
override val artifactId: String,
override val version: String,
override val classifier: String,
override val extension: String,
override val id: DefaultArtifactIdentifier,
override val name: String,
override val path: String,
override val timestamp: String? = null,
override val build: Int? = null,
override val urls: List<String>,
override val sha256: String
) : Artifact, Comparable<DefaultArtifact>, Serializable {
constructor(model: Artifact) : this(
model.groupId,
model.artifactId,
model.version,
model.classifier,
model.extension,
DefaultArtifactIdentifier(model.id),
model.name,
model.path,
model.timestamp,
model.build,
model.urls,
model.sha256
)
override fun toString() = "$groupId:$artifactId:$version:$classifier:$extension"
override fun compareTo(other: DefaultArtifact): Int = toString().compareTo(other.toString())
override fun toString() = id.toString()
override fun compareTo(other: DefaultArtifact): Int = id.compareTo(other.id)
}
@JsonClass(generateAdapter = true)
data class DefaultArtifactIdentifier(
override val group: String,
override val name: String,
override val version: String,
override val type: String,
override val extension: String = type,
override val classifier: String? = null
) : ArtifactIdentifier, Comparable<DefaultArtifactIdentifier>, Serializable {
constructor(model: ArtifactIdentifier) : this(
model.group,
model.name,
model.version,
model.type,
model.extension,
model.classifier
)
@delegate:Transient
private val semver: SemVer? by lazy {
try {
SemVer.parse(version)
} catch (_: IllegalArgumentException) {
null
}
}
override fun compareTo(other: DefaultArtifactIdentifier): Int {
return group.compareTo(other.group).takeUnless { it == 0 }
?: name.compareTo(other.name).takeUnless { it == 0 }
?: other.semver?.let { semver?.compareTo(it) }?.takeUnless { it == 0 }
?: type.compareTo(other.type).takeUnless { it == 0 }
?: other.classifier?.let { classifier?.compareTo(it) }?.takeUnless { it == 0 }
?: 0
}
override fun toString(): String = buildString {
append("$group:$name:$version")
if (classifier != null) append(":$classifier")
append("@$type")
}
}

View File

@@ -2,7 +2,7 @@ package org.nixos.gradle2nix
interface Build {
val gradle: Gradle
val pluginDependencies: Dependencies
val pluginDependencies: List<Artifact>
val rootProject: Project
val includedBuilds: List<IncludedBuild>
}
@@ -25,29 +25,26 @@ interface Project {
val version: String
val path: String
val projectDir: String
val buildscriptDependencies: Dependencies
val projectDependencies: Dependencies
val buildscriptDependencies: List<Artifact>
val projectDependencies: List<Artifact>
val children: List<Project>
}
interface Dependencies {
val repositories: Repositories
val artifacts: List<Artifact>
}
interface Repositories {
val maven: List<Maven>
}
interface Maven {
val urls: List<String>
}
interface Artifact {
val groupId: String
val artifactId: String
val version: String
val classifier: String
val extension: String
val id: ArtifactIdentifier
val name: String
val path: String
val timestamp: String?
val build: Int?
val urls: List<String>
val sha256: String
}
interface ArtifactIdentifier {
val group: String
val name: String
val version: String
val type: String
val extension: String
val classifier: String?
}

View File

@@ -5,4 +5,6 @@
5.3.1
5.4.1
5.5.1
5.6.3
5.6.4
6.0.1
6.1

View File

@@ -12,4 +12,6 @@
5.3.1
5.4.1
5.5.1
5.6.3
5.6.4
6.0.1
6.1

View File

@@ -9,12 +9,14 @@ plugins {
id("com.github.johnrengelman.shadow")
id("org.ajoberstar.stutter")
}
apply {
plugin("kotlin")
}
group = "org.nixos"
version = "1.0.0-SNAPSHOT"
sourceSets {
compatTest {
resources {
srcDir("$rootDir/fixtures")
}
}
}
dependencyLocking {
lockAllConfigurations()
@@ -30,17 +32,21 @@ dependencies {
implementation(project(":model"))
shadow(gradleApi())
compileOnly("org.gradle:gradle-tooling-api:${gradle.gradleVersion}")
implementation("org.apache.maven:maven-model:latest.release")
implementation("org.apache.maven:maven-model-builder:latest.release")
implementation("org.apache.ivy:ivy:latest.release")
implementation("org.apache.maven:maven-repository-metadata:latest.release")
compatTestImplementation(embeddedKotlin("stdlib-jdk8"))
compatTestImplementation(embeddedKotlin("test-junit5"))
compatTestImplementation(embeddedKotlin("reflect"))
compatTestImplementation("org.junit.jupiter:junit-jupiter-api:5.4+")
compatTestRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.4+")
compatTestImplementation("org.junit.jupiter:junit-jupiter-api:latest.release")
compatTestRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:latest.release")
compatTestImplementation("org.junit.jupiter:junit-jupiter-params:latest.release")
compatTestRuntimeOnly("org.junit.platform:junit-platform-launcher:latest.release")
compatTestImplementation("dev.minutest:minutest:latest.release")
compatTestImplementation(gradleTestKit())
compatTestImplementation(project(":model"))
compatTestImplementation("io.strikt:strikt-core:latest.release")
compatTestImplementation("com.squareup.okio:okio:latest.release")
}
gradlePlugin {

View File

@@ -7,28 +7,31 @@ commons-logging:commons-logging:1.2
de.undercouch:gradle-download-task:3.4.3
org.apache.httpcomponents:httpclient:4.5.3
org.apache.httpcomponents:httpcore:4.4.6
org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:1.2.9
org.gradle.kotlin:plugins:1.2.9
org.gradle.kotlin.kotlin-dsl:org.gradle.kotlin.kotlin-dsl.gradle.plugin:1.3.3
org.gradle.kotlin:plugins:1.3.3
org.jetbrains.intellij.deps:trove4j:1.0.20181211
org.jetbrains.kotlin:kotlin-android-extensions:1.3.41
org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.3.41
org.jetbrains.kotlin:kotlin-build-common:1.3.41
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.3.41
org.jetbrains.kotlin:kotlin-compiler-runner:1.3.41
org.jetbrains.kotlin:kotlin-daemon-client:1.3.41
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.3.41
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.3.41
org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.41
org.jetbrains.kotlin:kotlin-reflect:1.3.41
org.jetbrains.kotlin:kotlin-sam-with-receiver:1.3.41
org.jetbrains.kotlin:kotlin-script-runtime:1.3.41
org.jetbrains.kotlin:kotlin-scripting-common:1.3.41
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.3.41
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.3.41
org.jetbrains.kotlin:kotlin-scripting-jvm:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.41
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1
org.jetbrains.kotlin:kotlin-android-extensions:1.3.61
org.jetbrains.kotlin:kotlin-annotation-processing-gradle:1.3.61
org.jetbrains.kotlin:kotlin-build-common:1.3.61
org.jetbrains.kotlin:kotlin-compiler-embeddable:1.3.61
org.jetbrains.kotlin:kotlin-compiler-runner:1.3.61
org.jetbrains.kotlin:kotlin-daemon-client:1.3.61
org.jetbrains.kotlin:kotlin-daemon-embeddable:1.3.61
org.jetbrains.kotlin:kotlin-gradle-plugin-api:1.3.61
org.jetbrains.kotlin:kotlin-gradle-plugin-model:1.3.61
org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.61
org.jetbrains.kotlin:kotlin-native-utils:1.3.61
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-sam-with-receiver:1.3.61
org.jetbrains.kotlin:kotlin-script-runtime:1.3.61
org.jetbrains.kotlin:kotlin-scripting-common:1.3.61
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:1.3.61
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:1.3.61
org.jetbrains.kotlin:kotlin-scripting-jvm:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains.kotlin:kotlin-util-io:1.3.61
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.2.1
org.jetbrains:annotations:13.0

View File

@@ -1,20 +1,23 @@
# 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.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
io.strikt:strikt-core:0.22.2
org.apiguardian:apiguardian-api:1.0.0
org.jetbrains.kotlin:kotlin-reflect:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
org.jetbrains.kotlin:kotlin-test-annotations-common:1.3.41
org.jetbrains.kotlin:kotlin-test-common:1.3.41
org.jetbrains.kotlin:kotlin-test-junit5:1.3.41
org.jetbrains.kotlin:kotlin-test:1.3.41
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:2.4.3
dev.minutest:minutest:1.10.0
io.strikt:strikt-core:0.23.4
org.apiguardian:apiguardian-api:1.1.0
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains.kotlin:kotlin-test-annotations-common:1.3.61
org.jetbrains.kotlin:kotlin-test-common:1.3.61
org.jetbrains.kotlin:kotlin-test-junit5:1.3.61
org.jetbrains.kotlin:kotlin-test:1.3.61
org.jetbrains:annotations:13.0
org.junit.jupiter:junit-jupiter-api:5.4.2
org.junit.platform:junit-platform-commons:1.4.2
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-params:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit:junit-bom:5.6.0
org.opentest4j:opentest4j:1.2.0

View File

@@ -2,23 +2,29 @@
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.christophsturm:filepeek:0.1.1
com.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
io.strikt:strikt-core:0.22.2
org.apiguardian:apiguardian-api:1.0.0
org.jetbrains.kotlin:kotlin-reflect:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.50
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
org.jetbrains.kotlin:kotlin-test-annotations-common:1.3.41
org.jetbrains.kotlin:kotlin-test-common:1.3.41
org.jetbrains.kotlin:kotlin-test-junit5:1.3.41
org.jetbrains.kotlin:kotlin-test:1.3.41
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:2.4.3
dev.minutest:minutest:1.10.0
io.github.classgraph:classgraph:4.8.28
io.strikt:strikt-core:0.23.4
net.swiftzer.semver:semver:1.1.1
org.apiguardian:apiguardian-api:1.1.0
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains.kotlin:kotlin-test-annotations-common:1.3.61
org.jetbrains.kotlin:kotlin-test-common:1.3.61
org.jetbrains.kotlin:kotlin-test-junit5:1.3.61
org.jetbrains.kotlin:kotlin-test:1.3.61
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.3
org.jetbrains:annotations:13.0
org.junit.jupiter:junit-jupiter-api:5.4.2
org.junit.jupiter:junit-jupiter-engine:5.4.2
org.junit.platform:junit-platform-commons:1.4.2
org.junit.platform:junit-platform-engine:1.4.2
org.junit.jupiter:junit-jupiter-api:5.6.0
org.junit.jupiter:junit-jupiter-engine:5.6.0
org.junit.jupiter:junit-jupiter-params:5.6.0
org.junit.platform:junit-platform-commons:1.6.0
org.junit.platform:junit-platform-engine:1.6.0
org.junit.platform:junit-platform-launcher:1.6.0
org.junit:junit-bom:5.6.0
org.opentest4j:opentest4j:1.2.0

View File

@@ -1,21 +1,15 @@
# 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.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
javax.inject:javax.inject:1
org.apache.commons:commons-lang3:3.8.1
org.apache.maven:maven-artifact:3.6.2
org.apache.maven:maven-builder-support:3.6.2
org.apache.maven:maven-model-builder:3.6.2
org.apache.maven:maven-model:3.6.2
org.codehaus.plexus:plexus-interpolation:1.25
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:1.16.0
org.apache.ivy:ivy:2.5.0
org.apache.maven:maven-repository-metadata:3.6.3
org.codehaus.plexus:plexus-utils:3.2.1
org.eclipse.sisu:org.eclipse.sisu.inject:0.3.3
org.gradle:gradle-tooling-api:5.6.3
org.jetbrains.kotlin:kotlin-reflect:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
org.gradle:gradle-tooling-api:6.1
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0

View File

@@ -1,17 +1,9 @@
# 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.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
javax.inject:javax.inject:1
org.apache.commons:commons-lang3:3.8.1
org.apache.maven:maven-artifact:3.6.2
org.apache.maven:maven-builder-support:3.6.2
org.apache.maven:maven-model-builder:3.6.2
org.apache.maven:maven-model:3.6.2
org.codehaus.plexus:plexus-interpolation:1.25
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:1.16.0
net.swiftzer.semver:semver:1.1.1
org.apache.ivy:ivy:2.5.0
org.apache.maven:maven-repository-metadata:3.6.3
org.codehaus.plexus:plexus-utils:3.2.1
org.eclipse.sisu:org.eclipse.sisu.inject:0.3.3
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
org.jetbrains:annotations:13.0

View File

@@ -1,20 +1,14 @@
# 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.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
javax.inject:javax.inject:1
org.apache.commons:commons-lang3:3.8.1
org.apache.maven:maven-artifact:3.6.2
org.apache.maven:maven-builder-support:3.6.2
org.apache.maven:maven-model-builder:3.6.2
org.apache.maven:maven-model:3.6.2
org.codehaus.plexus:plexus-interpolation:1.25
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:1.16.0
org.apache.ivy:ivy:2.5.0
org.apache.maven:maven-repository-metadata:3.6.3
org.codehaus.plexus:plexus-utils:3.2.1
org.eclipse.sisu:org.eclipse.sisu.inject:0.3.3
org.jetbrains.kotlin:kotlin-reflect:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0

View File

@@ -1,20 +1,15 @@
# 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.squareup.moshi:moshi:1.9.1
com.squareup.okio:okio:2.4.1
javax.inject:javax.inject:1
org.apache.commons:commons-lang3:3.8.1
org.apache.maven:maven-artifact:3.6.2
org.apache.maven:maven-builder-support:3.6.2
org.apache.maven:maven-model-builder:3.6.2
org.apache.maven:maven-model:3.6.2
org.codehaus.plexus:plexus-interpolation:1.25
com.squareup.moshi:moshi:1.9.2
com.squareup.okio:okio:1.16.0
net.swiftzer.semver:semver:1.1.1
org.apache.ivy:ivy:2.5.0
org.apache.maven:maven-repository-metadata:3.6.3
org.codehaus.plexus:plexus-utils:3.2.1
org.eclipse.sisu:org.eclipse.sisu.inject:0.3.3
org.jetbrains.kotlin:kotlin-reflect:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.50
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.41
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.41
org.jetbrains.kotlin:kotlin-stdlib:1.3.50
org.jetbrains.kotlin:kotlin-reflect:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-common:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.61
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.3.61
org.jetbrains.kotlin:kotlin-stdlib:1.3.61
org.jetbrains:annotations:13.0

View File

@@ -1,97 +1,51 @@
package org.nixos.gradle2nix
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import java.io.File
import kotlin.test.assertEquals
import dev.minutest.Tests
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
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.get
import strikt.assertions.hasSize
import strikt.assertions.isEqualTo
import strikt.assertions.map
import strikt.assertions.startsWith
class BasicTest : JUnit5Minutests {
@Tests
fun tests() = rootContext<Fixture>("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"))
class BasicTest {
@TempDir lateinit var projectDir: 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"
)
@Test
fun `builds basic project with kotlin dsl`() {
val model = projectDir.buildKotlin("""
plugins {
java
map { it.urls }.all {
hasSize(2)
get(0).startsWith(BINTRAY_JCENTER_URL)
get(1).startsWith(MAVEN_CENTRAL_URL)
}
}
}
}
repositories {
jcenter()
}
dependencies {
implementation("com.squareup.okio:okio:2.2.2")
implementation("com.squareup.moshi:moshi:1.8.0")
}
""".trimIndent())
assertEquals(model.gradle.version, System.getProperty("compat.gradle.version"))
with(model.rootProject.projectDependencies) {
with(repositories) {
assertEquals(1, maven.size)
assertEquals(maven[0].urls[0], "https://jcenter.bintray.com/")
}
assertArtifacts(
pom("com.squareup.moshi:moshi-parent:1.8.0"),
jar("com.squareup.moshi:moshi:1.8.0"),
pom("com.squareup.moshi:moshi:1.8.0"),
jar("com.squareup.okio:okio:2.2.2"),
pom("com.squareup.okio:okio:2.2.2"),
jar("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
jar("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
jar("org.jetbrains:annotations:13.0"),
pom("org.jetbrains:annotations:13.0"),
pom("org.sonatype.oss:oss-parent:7"),
actual = artifacts
)
}
}
@Test
fun `builds basic project with groovy dsl`() {
val model = projectDir.buildGroovy("""
plugins {
id("java")
}
repositories {
jcenter()
}
dependencies {
implementation 'com.squareup.okio:okio:2.2.2'
implementation 'com.squareup.moshi:moshi:1.8.0'
}
""".trimIndent())
assertEquals(model.gradle.version, System.getProperty("compat.gradle.version"))
with(model.rootProject.projectDependencies) {
with(repositories) {
assertEquals(1, maven.size)
assertEquals(maven[0].urls[0], "https://jcenter.bintray.com/")
}
assertArtifacts(
pom("com.squareup.moshi:moshi-parent:1.8.0"),
jar("com.squareup.moshi:moshi:1.8.0"),
pom("com.squareup.moshi:moshi:1.8.0"),
jar("com.squareup.okio:okio:2.2.2"),
pom("com.squareup.okio:okio:2.2.2"),
jar("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
jar("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
jar("org.jetbrains:annotations:13.0"),
pom("org.jetbrains:annotations:13.0"),
pom("org.sonatype.oss:oss-parent:7"),
actual = artifacts
)
}
}
}

View File

@@ -0,0 +1,69 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import strikt.api.expectThat
import strikt.assertions.all
import strikt.assertions.contains
import strikt.assertions.containsExactly
import strikt.assertions.filter
import strikt.assertions.isEqualTo
import strikt.assertions.isNotEqualTo
import strikt.assertions.isNotNull
import strikt.assertions.single
import strikt.assertions.startsWith
class DependencyTest : JUnit5Minutests {
@Tests
fun tests() = rootContext<Fixture>("dependency tests") {
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/dynamic-snapshot") {
test("resolves snapshot dependency with dynamic version") {
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()
}
}
}
}
withFixture("dependency/snapshot") {
test("resolves snapshot dependency") {
expectThat(build()) {
get("root project dependencies") { rootProject.projectDependencies }
.filter { it.id.name == "okio" }
.and {
ids.containsExactly(
"com.squareup.okio:okio:2.5.0-SNAPSHOT@jar",
"com.squareup.okio:okio:2.5.0-SNAPSHOT@module",
"com.squareup.okio:okio:2.5.0-SNAPSHOT@pom"
)
all {
get("timestamp") { timestamp }.isNotNull()
get("build") { build }.isNotNull()
get("urls") { urls }.single().startsWith(SONATYPE_OSS_URL)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,33 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
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<Fixture>("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

@@ -0,0 +1,21 @@
package org.nixos.gradle2nix
import dev.minutest.Tests
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import strikt.api.expectThat
import strikt.assertions.contains
class PluginTest : JUnit5Minutests {
@Tests
fun tests() = rootContext<Fixture>("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,461 +1,151 @@
package org.nixos.gradle2nix
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import dev.minutest.Tests
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
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 java.io.File
import kotlin.test.assertEquals
import strikt.assertions.single
import strikt.assertions.startsWith
class SubprojectsTest {
@TempDir
lateinit var root: File
class SubprojectsTest : JUnit5Minutests {
@Tests
fun tests() = rootContext<Fixture>("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)
}
}
@Test
fun `builds multi-module project with kotlin dsl`() {
root.resolve("child-a").also { it.mkdirs() }
.resolve("build.gradle.kts").writeText("""
plugins {
java
}
get("children") { children }.and {
hasSize(2)
dependencies {
implementation("com.squareup.okio:okio:2.2.2")
}
""".trimIndent())
get(0).and {
get("name") { name }.isEqualTo("child-a")
get("projectDir") { projectDir }.isEqualTo("child-a")
root.resolve("child-b").also { it.mkdirs() }
.resolve("build.gradle.kts").writeText("""
plugins {
java
}
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"
)
dependencies {
implementation(project(":child-a"))
implementation("com.squareup.moshi:moshi:1.8.0")
}
""".trimIndent())
all {
get("urls") { urls }.single().startsWith(BINTRAY_JCENTER_URL)
}
}
}
root.resolve("settings.gradle.kts").writeText("""
include(":child-a", ":child-b")
""".trimIndent())
get(1).and {
get("name") { name }.isEqualTo("child-b")
get("projectDir") { projectDir }.isEqualTo("child-b")
val model = root.buildKotlin("""
plugins {
java
}
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"
)
allprojects {
repositories {
jcenter()
all {
get("urls") { urls }.single().startsWith(BINTRAY_JCENTER_URL)
}
}
}
}
}
}
dependencies {
testImplementation("junit:junit:4.12")
}
""".trimIndent())
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"
)
with(model.rootProject) {
with(projectDependencies) {
assertEquals(listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven)
all {
get("urls") { urls }.single().startsWith(BINTRAY_JCENTER_URL)
}
}
assertArtifacts(
jar("junit:junit:4.12"),
pom("junit:junit:4.12"),
jar("org.hamcrest:hamcrest-core:1.3"),
pom("org.hamcrest:hamcrest-core:1.3"),
pom("org.hamcrest:hamcrest-parent:1.3"),
actual = artifacts)
}
get("children") { children }.single().and {
get("name") { name }.isEqualTo("child-a")
get("projectDir") { projectDir }.isEqualTo("child-a")
assertEquals(2, children.size)
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"
)
with(children[0]) {
assertEquals("child-a", name)
assertEquals(root.resolve("child-a").toRelativeString(root), projectDir)
with(projectDependencies) {
assertEquals(
listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven
)
assertArtifacts(
jar("com.squareup.okio:okio:2.2.2"),
pom("com.squareup.okio:okio:2.2.2"),
jar("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
jar("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
jar("org.jetbrains:annotations:13.0"),
pom("org.jetbrains:annotations:13.0"),
actual = artifacts
)
}
}
with(children[1]) {
assertEquals("child-b", name)
assertEquals(root.resolve("child-b").toRelativeString(root), projectDir)
with(projectDependencies) {
assertEquals(
listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven
)
assertArtifacts(
pom("com.squareup.moshi:moshi-parent:1.8.0"),
jar("com.squareup.moshi:moshi:1.8.0"),
pom("com.squareup.moshi:moshi:1.8.0"),
pom("com.squareup.okio:okio-parent:1.16.0"),
jar("com.squareup.okio:okio:1.16.0"),
pom("com.squareup.okio:okio:1.16.0"),
jar("com.squareup.okio:okio:2.2.2"),
pom("com.squareup.okio:okio:2.2.2"),
jar("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
jar("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
jar("org.jetbrains:annotations:13.0"),
pom("org.jetbrains:annotations:13.0"),
pom("org.sonatype.oss:oss-parent:7"),
actual = artifacts
)
all {
get("urls") { urls }.single().startsWith(BINTRAY_JCENTER_URL)
}
}
}
}
}
}
}
@Test
fun `builds multi-module project with groovy dsl`() {
root.resolve("child-a").also { it.mkdirs() }
.resolve("build.gradle").writeText("""
plugins {
id 'java'
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")
}
dependencies {
implementation 'com.squareup.okio:okio:2.2.2'
}
""".trimIndent())
root.resolve("child-b").also { it.mkdirs() }
.resolve("build.gradle").writeText("""
plugins {
id 'java'
}
dependencies {
implementation project(':child-a')
implementation 'com.squareup.moshi:moshi:1.8.0'
}
""".trimIndent())
root.resolve("settings.gradle").writeText("""
include ':child-a', ':child-b'
""".trimIndent())
val model = root.buildGroovy("""
plugins {
id 'java'
}
allprojects {
repositories {
jcenter()
expectThat(build(subprojects = listOf(":child-b"))) {
get("children") { rootProject.children }
.map { it.path }
.containsExactlyInAnyOrder(":child-b", ":child-c")
}
}
dependencies {
testImplementation 'junit:junit:4.12'
}
""".trimIndent())
with(model.rootProject) {
with(projectDependencies) {
assertEquals(listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven)
assertArtifacts(
jar("junit:junit:4.12"),
pom("junit:junit:4.12"),
jar("org.hamcrest:hamcrest-core:1.3"),
pom("org.hamcrest:hamcrest-core:1.3"),
pom("org.hamcrest:hamcrest-parent:1.3"),
actual = artifacts)
}
assertEquals(2, children.size)
with(children[0]) {
assertEquals("child-a", name)
assertEquals(root.resolve("child-a").toRelativeString(root), projectDir)
with(projectDependencies) {
assertEquals(
listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven
)
assertArtifacts(
jar("com.squareup.okio:okio:2.2.2"),
pom("com.squareup.okio:okio:2.2.2"),
jar("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
jar("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
jar("org.jetbrains:annotations:13.0"),
pom("org.jetbrains:annotations:13.0"),
actual = artifacts
)
}
}
with(children[1]) {
assertEquals("child-b", name)
assertEquals(root.resolve("child-b").toRelativeString(root), projectDir)
with(projectDependencies) {
assertEquals(listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven)
assertArtifacts(
pom("com.squareup.moshi:moshi-parent:1.8.0"),
jar("com.squareup.moshi:moshi:1.8.0"),
pom("com.squareup.moshi:moshi:1.8.0"),
pom("com.squareup.okio:okio-parent:1.16.0"),
jar("com.squareup.okio:okio:1.16.0"),
pom("com.squareup.okio:okio:1.16.0"),
jar("com.squareup.okio:okio:2.2.2"),
pom("com.squareup.okio:okio:2.2.2"),
jar("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
jar("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
jar("org.jetbrains:annotations:13.0"),
pom("org.jetbrains:annotations:13.0"),
pom("org.sonatype.oss:oss-parent:7"),
actual = artifacts)
}
}
}
}
@Test
fun `builds single subproject in multi-module project with kotlin dsl`() {
root.resolve("child-a").also { it.mkdirs() }
.resolve("build.gradle.kts").writeText("""
plugins {
java
}
dependencies {
implementation("com.squareup.okio:okio:2.2.2")
}
""".trimIndent())
root.resolve("child-b").also { it.mkdirs() }
.resolve("build.gradle.kts").writeText("""
plugins {
java
}
dependencies {
implementation("com.squareup.moshi:moshi:1.8.0")
}
""".trimIndent())
root.resolve("settings.gradle.kts").writeText("""
include(":child-a", ":child-b")
""".trimIndent())
val model = root.buildKotlin("""
plugins {
java
}
allprojects {
repositories {
jcenter()
}
}
dependencies {
testImplementation("junit:junit:4.12")
}
""".trimIndent(),
subprojects = listOf(":child-a"))
with(model.rootProject) {
with(projectDependencies) {
assertEquals(listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven)
assertArtifacts(
jar("junit:junit:4.12"),
pom("junit:junit:4.12"),
jar("org.hamcrest:hamcrest-core:1.3"),
pom("org.hamcrest:hamcrest-core:1.3"),
pom("org.hamcrest:hamcrest-parent:1.3"),
actual = artifacts)
}
assertEquals(1, children.size)
with(children[0]) {
assertEquals("child-a", name)
assertEquals(root.resolve("child-a").toRelativeString(root), projectDir)
with(projectDependencies) {
assertEquals(
listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven
)
assertArtifacts(
jar("com.squareup.okio:okio:2.2.2"),
pom("com.squareup.okio:okio:2.2.2"),
jar("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
jar("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
jar("org.jetbrains:annotations:13.0"),
pom("org.jetbrains:annotations:13.0"),
actual = artifacts
)
}
}
}
}
@Test
fun `builds single subproject in multi-module project with groovy dsl`() {
root.resolve("child-a").also { it.mkdirs() }
.resolve("build.gradle").writeText("""
plugins {
id 'java'
}
dependencies {
implementation 'com.squareup.okio:okio:2.2.2'
}
""".trimIndent())
root.resolve("child-b").also { it.mkdirs() }
.resolve("build.gradle").writeText("""
plugins {
id 'java'
}
dependencies {
implementation 'com.squareup.moshi:moshi:1.8.0'
}
""".trimIndent())
root.resolve("settings.gradle").writeText("""
include ':child-a', ':child-b'
""".trimIndent())
val model = root.buildGroovy("""
plugins {
id 'java'
}
allprojects {
repositories {
jcenter()
}
}
dependencies {
testImplementation 'junit:junit:4.12'
}
""".trimIndent(),
subprojects = listOf(":child-a"))
with(model.rootProject) {
with(projectDependencies) {
assertEquals(listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven)
assertArtifacts(
jar("junit:junit:4.12"),
pom("junit:junit:4.12"),
jar("org.hamcrest:hamcrest-core:1.3"),
pom("org.hamcrest:hamcrest-core:1.3"),
pom("org.hamcrest:hamcrest-parent:1.3"),
actual = artifacts)
}
assertEquals(1, children.size)
with(children[0]) {
assertEquals("child-a", name)
assertEquals(root.resolve("child-a").toRelativeString(root), projectDir)
with(projectDependencies) {
assertEquals(
listOf(DefaultMaven(urls = listOf("https://jcenter.bintray.com/"))),
repositories.maven
)
assertArtifacts(
jar("com.squareup.okio:okio:2.2.2"),
pom("com.squareup.okio:okio:2.2.2"),
jar("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib-common:1.2.60"),
jar("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
pom("org.jetbrains.kotlin:kotlin-stdlib:1.2.60"),
jar("org.jetbrains:annotations:13.0"),
pom("org.jetbrains:annotations:13.0"),
actual = artifacts
)
}
}
}
}
@Test
fun `includes subproject dependencies`() {
root.resolve("child-a").also { it.mkdirs() }.resolve("build.gradle.kts").writeText("")
root.resolve("child-b").also { it.mkdirs() }.resolve("build.gradle.kts").writeText("")
root.resolve("child-c").also { it.mkdirs() }.resolve("build.gradle.kts").writeText("")
root.resolve("child-d").also { it.mkdirs() }.resolve("build.gradle.kts").writeText("")
root.resolve("settings.gradle.kts").writeText("""
include(":child-a", ":child-b", ":child-c", ":child-d")
""".trimIndent())
val buildscript = """
subprojects {
apply(plugin = "java")
}
project(":child-a") {
dependencies {
"implementation"(project(":child-b"))
}
}
project(":child-b") {
dependencies {
"implementation"(project(":child-c"))
}
}
""".trimIndent()
with(root.buildKotlin(buildscript, subprojects = listOf(":child-a"))) {
expectThat(rootProject.children).map { it.path }
.containsExactlyInAnyOrder(":child-a", ":child-b", ":child-c")
}
with(root.buildKotlin(buildscript, subprojects = listOf(":child-b"))) {
expectThat(rootProject.children).map { it.path }.containsExactlyInAnyOrder(":child-b", ":child-c")
}
}
}

View File

@@ -1,20 +1,33 @@
package org.nixos.gradle2nix
import com.squareup.moshi.Moshi
import dev.minutest.ContextBuilder
import dev.minutest.MinutestFixture
import dev.minutest.TestContextBuilder
import okio.buffer
import okio.source
import org.gradle.api.internal.artifacts.dsl.ParsedModuleStringNotation
import org.gradle.internal.classpath.DefaultClassPath
import org.gradle.testkit.runner.GradleRunner
import org.gradle.testkit.runner.internal.DefaultGradleRunner
import org.gradle.testkit.runner.internal.PluginUnderTestMetadataReading
import org.gradle.tooling.GradleConnector
import org.gradle.tooling.events.ProgressListener
import org.gradle.util.GradleVersion
import org.junit.jupiter.api.Assumptions.assumeTrue
import strikt.api.Assertion
import strikt.assertions.map
import java.io.File
import kotlin.test.assertTrue
import java.io.StringWriter
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import kotlin.streams.toList
const val SONATYPE_OSS_URL = "https://oss.sonatype.org/content/repositories/snapshots/"
private val moshi = Moshi.Builder().build()
private val gradleVersion = GradleVersion.version(System.getProperty("compat.gradle.version"))
val GRADLE_4_5 = GradleVersion.version("4.5")
private fun File.initscript() = resolve("init.gradle").also {
it.writer().use { out ->
val classpath = DefaultClassPath.of(PluginUnderTestMetadataReading.readImplementationClasspath())
@@ -31,20 +44,12 @@ private fun File.initscript() = resolve("init.gradle").also {
}
}
fun File.buildGroovy(
script: String,
configurations: List<String> = emptyList(),
subprojects: List<String> = emptyList()
): DefaultBuild {
resolve("build.gradle").writeText(script)
return build(configurations, subprojects)
}
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)
}
@@ -53,10 +58,13 @@ private fun File.build(
configurations: List<String>,
subprojects: List<String>
): DefaultBuild {
GradleRunner.create()
.withGradleVersion(System.getProperty("compat.gradle.version"))
val log = StringWriter()
val result = GradleRunner.create()
.withGradleVersion(gradleVersion.version)
.withProjectDir(this)
.forwardOutput()
.forwardStdOutput(log)
.forwardStdError(log)
.withArguments(
"nixModel",
"--init-script=${initscript()}",
@@ -64,8 +72,12 @@ private fun File.build(
"-Porg.nixos.gradle2nix.configurations=${configurations.joinToString(",")}",
"-Porg.nixos.gradle2nix.subprojects=${subprojects.joinToString(",")}"
)
.build()
.runCatching { build() }
result.onFailure { error ->
System.err.print(log)
throw error
}
return resolve("build/nix/model.json").run {
println(readText())
source().buffer().use { src ->
@@ -73,75 +85,48 @@ private fun File.build(
}
}
}
//
// return GradleConnector.newConnector()
// .useGradleVersion(System.getProperty("compat.gradle.version"))
// .forProjectDirectory(this)
// .connect()
// .model(Build::class.java).apply {
// addArguments("--init-script=${initscript()}", "--stacktrace")
// addJvmArguments(
// "-Dorg.gradle.debug=true",
// "-Dorg.nixos.gradle2nix.configurations=${configurations.joinToString(",")}",
// "-Dorg.nixos.gradle2nix.subprojects=${subprojects.joinToString(",")}"
// )
// setStandardOutput(System.out)
// setStandardError(System.out)
// }
// .get()
// .let { DefaultBuild(it) }
fun jar(notation: String, sha256: String = ""): DefaultArtifact =
artifact(notation, sha256, "jar")
val <T : Iterable<Artifact>> Assertion.Builder<T>.ids: Assertion.Builder<Iterable<String>>
get() = map { it.id.toString() }
fun pom(notation: String, sha256: String = ""): DefaultArtifact =
artifact(notation, sha256, "pom")
@MinutestFixture
class Fixture(val testRoots: List<Path>)
private fun artifact(notation: String, sha256: String, type: String): DefaultArtifact {
val parsed = ParsedModuleStringNotation(notation, type)
return DefaultArtifact(
groupId = parsed.group ?: "",
artifactId = parsed.name ?: "",
version = parsed.version ?: "",
classifier = parsed.classifier ?: "",
extension = type,
sha256 = sha256
)
@MinutestFixture
class ProjectFixture(val testRoot: Path) {
fun build(
configurations: List<String> = emptyList(),
subprojects: List<String> = emptyList()
) = testRoot.toFile().build(configurations, subprojects)
}
private fun artifactEquals(expected: DefaultArtifact, actual: DefaultArtifact?): Boolean {
return actual != null && with (expected) {
groupId == actual.groupId &&
artifactId == actual.artifactId &&
version == actual.version &&
classifier == actual.classifier &&
extension == actual.extension &&
(sha256.takeIf { it.isNotEmpty() }?.equals(actual.sha256) ?: true)
fun ContextBuilder<Fixture>.withFixture(
name: String,
block: TestContextBuilder<Fixture, ProjectFixture>.() -> Unit
) = context(name) {
val url = checkNotNull(Thread.currentThread().contextClassLoader.getResource(name)?.toURI()) {
"$name: No test fixture found"
}
}
val fixtureRoot = Paths.get(url)
val dest = createTempDir("gradle2nix").toPath()
val src = checkNotNull(fixtureRoot.takeIf(Files::exists)) {
"$name: Test fixture not found: $fixtureRoot}"
}
src.toFile().copyRecursively(dest.toFile())
val testRoots = Files.list(dest).filter { Files.isDirectory(it) }.toList()
fun assertArtifacts(vararg expected: DefaultArtifact, actual: List<DefaultArtifact>) {
val mismatches = mutableListOf<Mismatch>()
val remaining = mutableListOf<DefaultArtifact>().also { it.addAll(actual) }
expected.forEachIndexed { i: Int, exp: DefaultArtifact ->
val act = actual.elementAtOrNull(i)
if (!artifactEquals(exp, act)) {
mismatches += Mismatch(i, exp, act)
} else if (act != null) {
remaining -= act
fixture {
Fixture(testRoots)
}
afterAll {
dest.toFile().deleteRecursively()
}
testRoots.forEach { testRoot ->
derivedContext<ProjectFixture>(testRoot.fileName.toString()) {
deriveFixture { ProjectFixture(testRoot) }
block()
}
}
assertTrue(mismatches.isEmpty() && remaining.isEmpty(), """
Artifact mismatches:
${mismatches.joinToString("\n ", prefix = " ")}
Missing artifacts:
${remaining.joinToString("\n ", prefix = " ")}
""")
}
data class Mismatch(
val index: Int,
val expected: DefaultArtifact,
val actual: DefaultArtifact?
)

View File

@@ -1,22 +1,25 @@
package org.nixos.gradle2nix
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.io.TempDir
import dev.minutest.Tests
import dev.minutest.junit.JUnit5Minutests
import dev.minutest.rootContext
import strikt.api.expectThat
import strikt.assertions.isEqualTo
import java.io.File
import kotlin.test.assertEquals
class WrapperTest {
@TempDir
lateinit var root: File
class WrapperTest : JUnit5Minutests {
@Tests
fun tests() = rootContext<File>("wrapper tests") {
fixture { createTempDir("gradle2nix") }
@Test
fun `resolves gradle version from wrapper configuration`() {
val model = root.buildKotlin("""
tasks.withType<org.gradle.api.tasks.wrapper.Wrapper> {
gradleVersion = "5.5.1"
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")
}
""".trimIndent())
assertEquals(model.gradle.version, "5.5.1")
}
}
}

View File

@@ -0,0 +1,255 @@
package org.nixos.gradle2nix
import org.apache.ivy.Ivy
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.plugins.parser.m2.PomReader
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser
import org.apache.ivy.plugins.repository.url.URLResource
import org.apache.ivy.plugins.resolver.ChainResolver
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ModuleIdentifier
import org.gradle.api.artifacts.ResolvedArtifact
import org.gradle.api.artifacts.component.ComponentArtifactIdentifier
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.query.ArtifactResolutionQuery
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
import org.gradle.ivy.IvyDescriptorArtifact
import org.gradle.ivy.IvyModule
import org.gradle.kotlin.dsl.getArtifacts
import org.gradle.kotlin.dsl.withArtifacts
import org.gradle.maven.MavenModule
import org.gradle.maven.MavenPomArtifact
import org.gradle.util.GradleVersion
import java.io.File
internal class ConfigurationResolverFactory(repositories: RepositoryHandler) {
private val ivySettings = IvySettings().apply {
defaultInit()
setDefaultRepositoryCacheBasedir(createTempDir("gradle2nix-cache").apply(File::deleteOnExit).absolutePath)
setDictatorResolver(ChainResolver().also { chain ->
chain.settings = this@apply
for (resolver in resolvers) chain.add(resolver)
})
}
private val resolvers = repositories.filterIsInstance<ResolutionAwareRepository>()
.mapNotNull { it.repositoryResolver(ivySettings) }
fun create(dependencies: DependencyHandler): ConfigurationResolver =
ConfigurationResolver(ivySettings, resolvers, dependencies)
}
internal class ConfigurationResolver(
ivySettings: IvySettings,
private val resolvers: List<RepositoryResolver>,
private val dependencies: DependencyHandler
) {
private val ivy = Ivy.newInstance(ivySettings)
fun resolve(configuration: Configuration): List<DefaultArtifact> {
val resolved = configuration.resolvedConfiguration
val topLevelMetadata = resolved.firstLevelModuleDependencies
.flatMap { resolveMetadata(it.moduleGroup, it.moduleName, it.moduleVersion) }
val allArtifacts = resolved.resolvedArtifacts
.filter { it.id.componentIdentifier is ModuleComponentIdentifier }
.flatMap(::resolve)
return (topLevelMetadata + allArtifacts).filter { it.urls.isNotEmpty() }
}
private fun resolve(resolvedArtifact: ResolvedArtifact): List<DefaultArtifact> {
val componentId = resolvedArtifact.id.componentIdentifier as ModuleComponentIdentifier
val artifactId = DefaultArtifactIdentifier(
group = componentId.group,
name = componentId.module,
version = componentId.version,
type = resolvedArtifact.type,
extension = resolvedArtifact.extension,
classifier = resolvedArtifact.classifier
)
val sha256 = resolvedArtifact.file.sha256()
val artifacts = resolvers.mapNotNull { it.resolve(artifactId, sha256) }.merge()
return artifacts + componentId.run { resolveMetadata(group, module, version) }
}
private fun resolveMetadata(
group: String,
name: String,
version: String
): List<DefaultArtifact> {
return resolvePoms(group, name, version) +
resolveDescriptors(group, name, version) +
resolveGradleMetadata(group, name, version)
}
private fun resolvePoms(
group: String,
name: String,
version: String
): List<DefaultArtifact> {
return dependencies.createArtifactResolutionQuery()
.forModuleCompat(group, name, version)
.withArtifacts(MavenModule::class, MavenPomArtifact::class)
.execute()
.resolvedComponents
.flatMap { it.getArtifacts(MavenPomArtifact::class) }
.filterIsInstance<ResolvedArtifactResult>()
.flatMap { it.withParentPoms() }
.flatMap { resolvedPom ->
val componentId = resolvedPom.id.componentIdentifier as ModuleComponentIdentifier
val artifactId = DefaultArtifactIdentifier(
group = componentId.group,
name = componentId.module,
version = componentId.version,
type = "pom"
)
val sha256 = resolvedPom.file.sha256()
resolvers.mapNotNull { it.resolve(artifactId, sha256) }.merge()
}
}
private fun resolveDescriptors(
group: String,
name: String,
version: String
): List<DefaultArtifact> {
return dependencies.createArtifactResolutionQuery()
.forModuleCompat(group, name, version)
.withArtifacts(IvyModule::class, IvyDescriptorArtifact::class)
.execute()
.resolvedComponents
.flatMap { it.getArtifacts(IvyDescriptorArtifact::class) }
.filterIsInstance<ResolvedArtifactResult>()
.flatMap { it.withParentDescriptors() }
.flatMap { resolvedDesc ->
val componentId = resolvedDesc.id.componentIdentifier as ModuleComponentIdentifier
val artifactId = DefaultArtifactIdentifier(
group = componentId.group,
name = componentId.module,
version = componentId.version,
type = "ivy",
extension = "xml"
)
val sha256 = resolvedDesc.file.sha256()
resolvers.mapNotNull { it.resolve(artifactId, sha256) }.merge()
}
}
private fun resolveGradleMetadata(
group: String,
name: String,
version: String
): List<DefaultArtifact> {
val artifactId = DefaultArtifactIdentifier(
group = group,
name = name,
version = version,
type = "module"
)
return resolvers.mapNotNull { it.resolve(artifactId) }.merge()
}
private fun ResolvedArtifactResult.parentPom(): ResolvedArtifactResult? {
val resource = URLResource(file.toURI().toURL())
val reader = PomReader(resource.url, resource)
return if (reader.hasParent()) {
dependencies.createArtifactResolutionQuery()
.forModuleCompat(reader.parentGroupId, reader.parentArtifactId, reader.parentVersion)
.withArtifacts(MavenModule::class, MavenPomArtifact::class)
.execute()
.resolvedComponents
.flatMap { it.getArtifacts(MavenPomArtifact::class) }
.filterIsInstance<ResolvedArtifactResult>()
.firstOrNull()
} else {
null
}
}
private fun ResolvedArtifactResult.withParentPoms(): List<ResolvedArtifactResult> =
generateSequence(this) { it.parentPom() }.toList()
private fun ResolvedArtifactResult.parentDescriptors(seen: Set<ComponentArtifactIdentifier>): List<ResolvedArtifactResult> {
val url = file.toURI().toURL()
val parser = XmlModuleDescriptorParser.getInstance()
val descriptor = parser.parseDescriptor(ivy.settings, url, false)
return descriptor.inheritedDescriptors.mapNotNull { desc ->
dependencies.createArtifactResolutionQuery()
.forModuleCompat(
desc.parentRevisionId.organisation,
desc.parentRevisionId.name,
desc.parentRevisionId.revision
)
.withArtifacts(IvyModule::class, IvyDescriptorArtifact::class)
.execute()
.resolvedComponents
.flatMap { it.getArtifacts(IvyDescriptorArtifact::class) }
.filterIsInstance<ResolvedArtifactResult>()
.firstOrNull()
}.filter { it.id !in seen }
}
private fun ResolvedArtifactResult.withParentDescriptors(): List<ResolvedArtifactResult> {
val seen = mutableSetOf<ComponentArtifactIdentifier>()
return generateSequence(listOf(this)) { descs ->
val parents = descs.flatMap { it.parentDescriptors(seen) }
seen.addAll(parents.map(ResolvedArtifactResult::id))
parents.takeUnless { it.isEmpty() }
}.flatten().distinct().toList()
}
}
private fun ArtifactResolutionQuery.forModuleCompat(
group: String,
name: String,
version: String
): ArtifactResolutionQuery {
return if (GradleVersion.current() >= GradleVersion.version("4.5")) {
forModule(group, name, version)
} else {
forComponents(ModuleComponentId(group, name, version))
}
}
private data class ModuleComponentId(
private val moduleId: ModuleId,
private val version: String
) : ModuleComponentIdentifier {
constructor(
group: String,
name: String,
version: String
) : this(ModuleId(group, name), version)
override fun getGroup(): String = moduleId.group
override fun getModule(): String = moduleId.name
override fun getVersion(): String = version
override fun getModuleIdentifier(): ModuleIdentifier = moduleId
override fun getDisplayName(): String =
arrayOf(group, module, version).joinToString(":")
}
private data class ModuleId(
private val group: String,
private val name: String
) : ModuleIdentifier {
override fun getGroup(): String = group
override fun getName(): String = name
}
private fun List<DefaultArtifact>.merge(): List<DefaultArtifact> {
return groupingBy { it.id }
.reduce { _, dest, next -> dest.copy(urls = dest.urls + next.urls) }
.values.toList()
}

View File

@@ -1,169 +0,0 @@
package org.nixos.gradle2nix
import org.apache.maven.model.Parent
import org.apache.maven.model.Repository
import org.apache.maven.model.building.DefaultModelBuilderFactory
import org.apache.maven.model.building.DefaultModelBuildingRequest
import org.apache.maven.model.building.ModelBuildingRequest
import org.apache.maven.model.building.ModelSource2
import org.apache.maven.model.resolution.ModelResolver
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ConfigurationContainer
import org.gradle.api.artifacts.Dependency
import org.gradle.api.artifacts.component.ModuleComponentIdentifier
import org.gradle.api.artifacts.component.ProjectComponentIdentifier
import org.gradle.api.artifacts.dsl.DependencyHandler
import org.gradle.api.artifacts.result.ResolvedArtifactResult
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import org.gradle.maven.MavenModule
import org.gradle.maven.MavenPomArtifact
import java.io.File
import java.io.InputStream
import java.net.URI
import java.security.MessageDigest
internal class DependencyResolver(
private val configurations: ConfigurationContainer,
private val dependencies: DependencyHandler,
private val logger: Logger = Logging.getLogger(DependencyResolver::class.simpleName)
) {
private val mavenPomResolver = MavenPomResolver(configurations, dependencies)
fun resolveDependencies(configuration: Configuration): Set<DefaultArtifact> {
if (!configuration.isCanBeResolved) {
logger.warn("Cannot resolve configuration ${configuration.name}; ignoring.")
return emptySet()
}
return configuration.resolvedConfiguration.resolvedArtifacts
.filterNot { it.id.componentIdentifier is ProjectComponentIdentifier }
.mapTo(sortedSetOf()) {
with (it) {
DefaultArtifact(
groupId = moduleVersion.id.group,
artifactId = moduleVersion.id.name,
version = moduleVersion.id.version,
classifier = classifier ?: "",
extension = extension,
sha256 = sha256(file)
)
}
}
}
fun resolveDependencies(
dependencies: Collection<Dependency>,
includeTransitive: Boolean = false
): Set<DefaultArtifact> {
val configuration = configurations.detachedConfiguration(*(dependencies.toTypedArray()))
configuration.isTransitive = includeTransitive
return resolveDependencies(configuration)
}
fun resolvePoms(configuration: Configuration): Set<DefaultArtifact> {
return dependencies.createArtifactResolutionQuery()
.forComponents(configuration.incoming.resolutionResult.allComponents.map { it.id })
.withArtifacts(MavenModule::class.java, MavenPomArtifact::class.java)
.execute()
.resolvedComponents.asSequence()
.flatMap { component ->
val id = component.id
if (id !is ModuleComponentIdentifier) {
emptySequence()
} else {
component.getArtifacts(MavenPomArtifact::class.java).asSequence()
.filterIsInstance<ResolvedArtifactResult>()
.map { id to it }
}
}
.flatMapTo(sortedSetOf()) { (id, artifact) ->
sequenceOf(DefaultArtifact(
groupId = id.group,
artifactId = id.module,
version = id.version,
classifier = "",
extension = artifact.file.extension,
sha256 = sha256(artifact.file)
)) + mavenPomResolver.resolve(artifact.file).asSequence()
}
}
fun resolvePoms(
dependencies: Collection<Dependency>,
includeTransitive: Boolean = false
): Set<DefaultArtifact> {
val configuration = configurations.detachedConfiguration(*(dependencies.toTypedArray()))
configuration.isTransitive = includeTransitive
return resolvePoms(configuration)
}
}
private class MavenPomResolver(
private val configurations: ConfigurationContainer,
private val dependencies: DependencyHandler
) : ModelResolver {
private val modelBuilder = DefaultModelBuilderFactory().newInstance()
private val resolvedDependencies = mutableSetOf<DefaultArtifact>()
@Synchronized
fun resolve(pom: File): Set<DefaultArtifact> {
resolvedDependencies.clear()
modelBuilder.build(
DefaultModelBuildingRequest()
.setModelResolver(this)
.setPomFile(pom)
.setSystemProperties(System.getProperties())
.setValidationLevel(ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL)
).effectiveModel
return resolvedDependencies.toSet()
}
override fun newCopy() = this
override fun resolveModel(
groupId: String,
artifactId: String,
version: String
): ModelSource2 {
val file = configurations
.detachedConfiguration(dependencies.create("$groupId:$artifactId:$version@pom"))
.singleFile
resolvedDependencies.add(DefaultArtifact(
groupId = groupId,
artifactId = artifactId,
version = version,
classifier = "",
extension = file.extension,
sha256 = sha256(file)
))
return object : ModelSource2 {
override fun getLocation(): String = file.absolutePath
override fun getLocationURI(): URI = file.absoluteFile.toURI()
override fun getRelatedSource(relPath: String?): ModelSource2? = null
override fun getInputStream(): InputStream = file.inputStream()
}
}
override fun resolveModel(parent: Parent): ModelSource2 =
resolveModel(parent.groupId, parent.artifactId, parent.version)
override fun resolveModel(dependency: org.apache.maven.model.Dependency): ModelSource2 =
resolveModel(dependency.groupId, dependency.artifactId, dependency.version)
override fun addRepository(repository: Repository) {}
override fun addRepository(repository: Repository, replace: Boolean) {}
}
private const val HEX = "0123456789abcdef"
private fun sha256(file: File): String = buildString {
MessageDigest.getInstance("SHA-256").digest(file.readBytes())
.asSequence()
.map { it.toInt() }
.forEach {
append(HEX[it shr 4 and 0x0f])
append(HEX[it and 0x0f])
}
}

View File

@@ -1,19 +1,16 @@
package org.nixos.gradle2nix
import com.squareup.moshi.Moshi
import okio.buffer
import okio.sink
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.artifacts.ArtifactRepositoryContainer
import org.gradle.api.Task
import org.gradle.api.artifacts.Configuration
import org.gradle.api.artifacts.ProjectDependency
import org.gradle.api.artifacts.dsl.RepositoryHandler
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.internal.GradleInternal
import org.gradle.api.invocation.Gradle
import org.gradle.api.tasks.TaskContainer
import org.gradle.api.tasks.wrapper.Wrapper
import org.gradle.kotlin.dsl.getByName
import org.gradle.kotlin.dsl.newInstance
import org.gradle.kotlin.dsl.support.serviceOf
import org.gradle.kotlin.dsl.withType
import org.gradle.plugin.management.PluginRequest
@@ -33,15 +30,17 @@ open class Gradle2NixPlugin : Plugin<Gradle> {
rootProject.serviceOf<ToolingModelBuilderRegistry>()
.register(NixToolingModelBuilder(modelProperties, pluginRequests))
rootProject.tasks.register("nixModel") {
rootProject.tasks.registerCompat("nixModel") {
doLast {
val outFile = project.mkdir(project.buildDir.resolve("nix")).resolve("model.json")
val model = project.buildModel(modelProperties, pluginRequests)
outFile.sink().buffer().use { out ->
Moshi.Builder().build()
.adapter(DefaultBuild::class.java)
.indent(" ")
.toJson(out, model)
outFile.bufferedWriter().use { out ->
out.write(
Moshi.Builder().build()
.adapter(DefaultBuild::class.java)
.indent(" ")
.toJson(model)
)
out.flush()
}
}
@@ -50,7 +49,13 @@ open class Gradle2NixPlugin : Plugin<Gradle> {
}
}
private const val NIX_MODEL_NAME = "org.nixos.gradle2nix.Build"
private fun TaskContainer.registerCompat(name: String, configureAction: Task.() -> Unit) {
if (GradleVersion.current() >= GradleVersion.version("4.9")) {
register(name, configureAction)
} else {
create(name, configureAction)
}
}
private class NixToolingModelBuilder(
private val modelProperties: ModelProperties,
@@ -100,6 +105,7 @@ private fun Project.buildModel(
)
}
@Suppress("UnstableApiUsage")
private fun Project.buildGradle(): DefaultGradle =
with(tasks.getByName<Wrapper>("wrapper")) {
DefaultGradle(
@@ -113,18 +119,16 @@ private fun Project.buildGradle(): DefaultGradle =
}
?: throw IllegalStateException(
"""
Failed to find native-platform jar in ${gradle.gradleHomeDir}.
Ask Tad to fix this.
""".trimIndent()
Failed to find native-platform jar in ${gradle.gradleHomeDir}.
Ask Tad to fix this.
""".trimIndent()
)
)
}
private fun Project.buildPlugins(pluginRequests: List<PluginRequest>): DefaultDependencies =
with(PluginResolver(gradle as GradleInternal, pluginRequests)) {
DefaultDependencies(repositories.repositories(), artifacts())
}
private fun Project.buildPlugins(pluginRequests: List<PluginRequest>): List<DefaultArtifact> {
return objects.newInstance<PluginResolver>().resolve(pluginRequests).distinct().sorted()
}
private fun Project.includedBuilds(): List<DefaultIncludedBuild> =
gradle.includedBuilds.map {
@@ -134,7 +138,7 @@ private fun Project.includedBuilds(): List<DefaultIncludedBuild> =
private fun Project.buildProject(
explicitConfigurations: List<String>,
explicitSubprojects: Collection<Project>,
plugins: DefaultDependencies
pluginArtifacts: List<DefaultArtifact>
): DefaultProject {
logger.lifecycle(" Subproject: $path")
return DefaultProject(
@@ -142,34 +146,33 @@ private fun Project.buildProject(
version = version.toString(),
path = path,
projectDir = projectDir.toRelativeString(rootProject.projectDir),
buildscriptDependencies = buildscriptDependencies(plugins),
buildscriptDependencies = buildscriptDependencies(pluginArtifacts),
projectDependencies = projectDependencies(explicitConfigurations),
children = explicitSubprojects.map { it.buildProject(explicitConfigurations, emptyList(), plugins) }
children = explicitSubprojects.map {
it.buildProject(explicitConfigurations, emptyList(), pluginArtifacts)
}
)
}
private fun Project.buildscriptDependencies(plugins: DefaultDependencies): DefaultDependencies =
with(DependencyResolver(buildscript.configurations, buildscript.dependencies)) {
DefaultDependencies(
repositories = buildscript.repositories.repositories(),
artifacts = buildscript.configurations
.filter { it.isCanBeResolved }
.flatMap { resolveDependencies(it) + resolvePoms(it) }
.minus(plugins.artifacts)
.distinct()
)
}
private fun Project.buildscriptDependencies(pluginArtifacts: List<DefaultArtifact>): List<DefaultArtifact> {
val resolverFactory = ConfigurationResolverFactory(buildscript.repositories)
val resolver = resolverFactory.create(buildscript.dependencies)
val pluginIds = pluginArtifacts.map(DefaultArtifact::id)
return buildscript.configurations
.flatMap(resolver::resolve)
.distinct()
.filter { it.id !in pluginIds }
.sorted()
}
private fun Project.projectDependencies(explicitConfigurations: List<String>): DefaultDependencies =
with(DependencyResolver(configurations, dependencies)) {
val toResolve = collectConfigurations(explicitConfigurations)
DefaultDependencies(
repositories = repositories.repositories(),
artifacts = toResolve.flatMap { resolveDependencies(it) + resolvePoms(it) }
.sorted()
.distinct()
)
}
private fun Project.projectDependencies(explicitConfigurations: List<String>): List<DefaultArtifact> {
val resolverFactory = ConfigurationResolverFactory(repositories)
val resolver = resolverFactory.create(dependencies)
return collectConfigurations(explicitConfigurations)
.flatMap(resolver::resolve)
.distinct()
.sorted()
}
private fun Project.dependentSubprojects(explicitConfigurations: List<String>): Set<Project> {
return collectConfigurations(explicitConfigurations)
@@ -190,19 +193,6 @@ private fun Project.collectConfigurations(
}
}
private val excludedRepoNames = setOf(
"Embedded Kotlin Repository",
ArtifactRepositoryContainer.DEFAULT_MAVEN_LOCAL_REPO_NAME
)
internal fun RepositoryHandler.repositories() = DefaultRepositories(
maven = filterIsInstance<MavenArtifactRepository>()
.filter { it.name !in excludedRepoNames }
.map { repo ->
DefaultMaven(listOf(repo.url.toString()) + repo.artifactUrls.map { it.toString() })
}
)
private fun fetchDistSha256(url: String): String {
return URL("$url.sha256").openConnection().run {
connect()
@@ -217,6 +207,9 @@ private val Wrapper.sha256: String
return if (GradleVersion.current() < GradleVersion.version("4.5")) {
fetchDistSha256(distributionUrl)
} else {
@Suppress("UnstableApiUsage")
distributionSha256Sum ?: fetchDistSha256(distributionUrl)
}
}
private const val NIX_MODEL_NAME = "org.nixos.gradle2nix.Build"

View File

@@ -1,6 +1,5 @@
package org.nixos.gradle2nix
import java.util.Properties
import org.gradle.api.Project
data class ModelProperties(

View File

@@ -1,101 +1,27 @@
package org.nixos.gradle2nix
import org.gradle.api.artifacts.ExternalModuleDependency
import org.gradle.api.internal.GradleInternal
import org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionSelectorScheme
import org.gradle.api.internal.plugins.PluginImplementation
import org.gradle.kotlin.dsl.support.serviceOf
import org.gradle.api.internal.artifacts.dependencies.DefaultExternalModuleDependency
import org.gradle.plugin.management.PluginRequest
import org.gradle.plugin.management.internal.PluginRequestInternal
import org.gradle.plugin.use.PluginId
import org.gradle.plugin.use.internal.PluginDependencyResolutionServices
import org.gradle.plugin.use.resolve.internal.ArtifactRepositoriesPluginResolver
import org.gradle.plugin.use.resolve.internal.PluginResolution
import org.gradle.plugin.use.resolve.internal.PluginResolutionResult
import org.gradle.plugin.use.resolve.internal.PluginResolveContext
import javax.inject.Inject
internal class PluginResolver(
gradle: GradleInternal,
private val pluginRequests: Collection<PluginRequest>
internal open class PluginResolver @Inject constructor(
pluginDependencyResolutionServices: PluginDependencyResolutionServices
) {
private val pluginDependencyResolutionServices = gradle.serviceOf<PluginDependencyResolutionServices>()
private val versionSelectorScheme = gradle.serviceOf<VersionSelectorScheme>()
private val configurations = pluginDependencyResolutionServices.configurationContainer
private val artifactRepositoriesPluginResolver = ArtifactRepositoriesPluginResolver(
pluginDependencyResolutionServices,
versionSelectorScheme
)
private val resolver = ConfigurationResolverFactory(pluginDependencyResolutionServices.resolveRepositoryHandler)
.create(pluginDependencyResolutionServices.dependencyHandler)
val repositories = pluginDependencyResolutionServices.resolveRepositoryHandler
private val resolver by lazy {
DependencyResolver(
pluginDependencyResolutionServices.configurationContainer,
pluginDependencyResolutionServices.dependencyHandler
)
}
private val pluginResult by lazy {
PluginResult().apply {
for (request in pluginRequests.filterIsInstance<PluginRequestInternal>()) {
artifactRepositoriesPluginResolver.resolve(request, this)
}
}
}
private val pluginContext by lazy {
PluginContext().apply {
for (result in pluginResult.found) result.execute(this)
}
}
fun artifacts(): List<DefaultArtifact> {
return (resolver.resolveDependencies(pluginContext.dependencies, true) +
resolver.resolvePoms(pluginContext.dependencies, true))
.sorted()
.distinct()
}
private class PluginResult : PluginResolutionResult {
val found = mutableSetOf<PluginResolution>()
override fun notFound(sourceDescription: String?, notFoundMessage: String?) {}
override fun notFound(
sourceDescription: String?,
notFoundMessage: String?,
notFoundDetail: String?
) {
}
override fun isFound(): Boolean = true
override fun found(sourceDescription: String, pluginResolution: PluginResolution) {
found.add(pluginResolution)
}
}
private class PluginContext : PluginResolveContext {
val dependencies = mutableSetOf<ExternalModuleDependency>()
val repositories = mutableSetOf<String>()
override fun add(plugin: PluginImplementation<*>) {
println("add: $plugin")
}
override fun addFromDifferentLoader(plugin: PluginImplementation<*>) {
println("addFromDifferentLoader: $plugin")
}
override fun addLegacy(pluginId: PluginId, m2RepoUrl: String, dependencyNotation: Any) {
repositories.add(m2RepoUrl)
}
override fun addLegacy(pluginId: PluginId, dependencyNotation: Any) {
if (dependencyNotation is ExternalModuleDependency) {
dependencies.add(dependencyNotation)
fun resolve(pluginRequests: List<PluginRequest>): List<DefaultArtifact> {
val markerDependencies = pluginRequests.map {
it.module?.let { selector ->
DefaultExternalModuleDependency(selector.group, selector.name, selector.version)
} ?: it.id.run {
DefaultExternalModuleDependency(id, "$id.gradle.plugin", it.version)
}
}
return resolver.resolve(configurations.detachedConfiguration(*markerDependencies.toTypedArray()))
}
}

View File

@@ -0,0 +1,212 @@
package org.nixos.gradle2nix
import org.apache.ivy.core.LogOptions
import org.apache.ivy.core.cache.ArtifactOrigin
import org.apache.ivy.core.cache.CacheResourceOptions
import org.apache.ivy.core.cache.DefaultRepositoryCacheManager
import org.apache.ivy.core.cache.RepositoryCacheManager
import org.apache.ivy.core.module.id.ArtifactRevisionId
import org.apache.ivy.core.module.id.ModuleRevisionId
import org.apache.ivy.core.resolve.DownloadOptions
import org.apache.ivy.core.settings.IvySettings
import org.apache.ivy.plugins.repository.url.URLResource
import org.apache.ivy.plugins.resolver.IBiblioResolver
import org.apache.ivy.plugins.resolver.URLResolver
import org.apache.maven.artifact.repository.metadata.io.xpp3.MetadataXpp3Reader
import org.codehaus.plexus.util.ReaderFactory
import org.codehaus.plexus.util.xml.pull.XmlPullParserException
import org.gradle.api.artifacts.repositories.ArtifactRepository
import org.gradle.api.artifacts.repositories.IvyArtifactRepository
import org.gradle.api.artifacts.repositories.MavenArtifactRepository
import org.gradle.api.internal.artifacts.repositories.ResolutionAwareRepository
import org.gradle.api.internal.artifacts.repositories.resolver.IvyResolver
import org.gradle.api.logging.Logger
import org.gradle.api.logging.Logging
import java.io.IOException
import org.apache.ivy.core.module.descriptor.Artifact as IvyArtifact
import org.apache.ivy.core.module.descriptor.DefaultArtifact as IvyDefaultArtifact
import org.apache.ivy.plugins.resolver.RepositoryResolver as IvyRepositoryResolver
internal fun ResolutionAwareRepository.repositoryResolver(ivySettings: IvySettings): RepositoryResolver? =
when(this) {
is MavenArtifactRepository -> MavenResolver(ivySettings, this)
is IvyArtifactRepository -> IvyResolver(ivySettings, this)
else -> null
}
internal sealed class RepositoryResolver {
companion object {
@JvmStatic
protected val log: Logger = Logging.getLogger("gradle2nix")
}
abstract val ivyResolver: IvyRepositoryResolver
abstract fun resolve(
artifactId: DefaultArtifactIdentifier,
sha256: String? = null
): DefaultArtifact?
}
internal class MavenResolver(
ivySettings: IvySettings,
repository: MavenArtifactRepository
) : RepositoryResolver() {
override val ivyResolver: IBiblioResolver = IBiblioResolver().apply {
name = repository.name
root = repository.url.toString()
isM2compatible = true
settings = ivySettings
setCache(cacheManager(ivySettings, repository).name)
}
override fun resolve(artifactId: DefaultArtifactIdentifier, sha256: String?): DefaultArtifact? {
val ivyArtifact: IvyArtifact = artifactId.toArtifact()
val origin = ivyResolver.locate(ivyArtifact)?.takeIf(ArtifactOrigin::isExists) ?: return null
val hash = sha256 ?: ivyResolver.download(origin, downloadOptions).localFile?.sha256() ?: return null
val snapshotVersion: SnapshotVersion? = artifactId.version.snapshotVersion()?.let {
findSnapshotVersion(artifactId, it)
}
return DefaultArtifact(
id = artifactId,
name = artifactId.filename(snapshotVersion),
path = artifactId.repoPath(),
timestamp = snapshotVersion?.timestamp,
build = snapshotVersion?.build,
urls = listOf(origin.location),
sha256 = hash
)
}
private fun findSnapshotVersion(
artifactId: ArtifactIdentifier,
snapshotVersion: SnapshotVersion
): SnapshotVersion {
if (snapshotVersion.timestamp != null) return snapshotVersion
val metadataLocation = "${ivyResolver.root}${artifactId.repoPath()}/maven-metadata.xml".toUrl()
val metadataFile = ivyResolver.repositoryCacheManager.downloadRepositoryResource(
URLResource(metadataLocation, ivyResolver.timeoutConstraint),
"maven-metadata",
"maven-metadata",
"xml",
CacheResourceOptions(),
ivyResolver.repository
).localFile
if (metadataFile == null) {
log.warn("maven-metadata.xml not found for snapshot dependency: $artifactId")
return snapshotVersion
}
fun parseError(e: Throwable): Pair<String?, Int?> {
log.error("Failed to parse maven-metadata.xml for artifact: $artifactId")
log.error("Error was: ${e.message}", e)
return null to null
}
val (timestamp: String?, build: Int?) = try {
MetadataXpp3Reader()
.read(ReaderFactory.newXmlReader(metadataFile))
.versioning?.snapshot?.run { timestamp to buildNumber }
?: null to null
} catch (e: IOException) {
parseError(e)
} catch (e: XmlPullParserException) {
parseError(e)
}
return snapshotVersion.copy(timestamp = timestamp, build = build)
}
}
internal class IvyResolver(
ivySettings: IvySettings,
repository: IvyArtifactRepository
) : RepositoryResolver() {
override val ivyResolver: URLResolver = URLResolver().apply {
name = repository.name
val ivyResolver = (repository as ResolutionAwareRepository).createResolver() as IvyResolver
isM2compatible = ivyResolver.isM2compatible
for (p in ivyResolver.ivyPatterns) addIvyPattern(p)
for (p in ivyResolver.artifactPatterns) addArtifactPattern(p)
settings = ivySettings
setCache(cacheManager(ivySettings, repository).name)
}
override fun resolve(artifactId: DefaultArtifactIdentifier, sha256: String?): DefaultArtifact? {
val ivyArtifact: IvyArtifact = artifactId.toArtifact()
val origin = ivyResolver.locate(ivyArtifact)?.takeIf(ArtifactOrigin::isExists) ?: return null
val hash = sha256 ?: ivyResolver.download(origin, downloadOptions).localFile?.sha256() ?: return null
return DefaultArtifact(
id = DefaultArtifactIdentifier(artifactId),
name = artifactId.filename(null),
path = artifactId.repoPath(),
urls = listOf(origin.location),
sha256 = hash
)
}
}
private fun cacheManager(ivySettings: IvySettings, repository: ArtifactRepository): RepositoryCacheManager {
return DefaultRepositoryCacheManager(
"${repository.name}-cache",
ivySettings,
createTempDir("gradle2nix-${repository.name}-cache")
).also {
ivySettings.addRepositoryCacheManager(it)
}
}
private val metadataTypes = setOf("pom", "ivy")
private fun ArtifactIdentifier.toArtifact(): IvyArtifact {
val moduleRevisionId = ModuleRevisionId.newInstance(group, name, version)
val artifactRevisionId = ArtifactRevisionId.newInstance(
moduleRevisionId,
name,
type,
extension,
classifier?.let { mapOf("classifier" to it) }
)
return IvyDefaultArtifact(artifactRevisionId, null, null, type in metadataTypes)
}
private data class SnapshotVersion(
val base: String,
val timestamp: String?,
val build: Int?
) {
override fun toString(): String {
return if (timestamp != null && build != null) {
"$base-$timestamp-$build"
} else {
"$base-SNAPSHOT"
}
}
}
private val SNAPSHOT_REGEX = Regex("^(.*)-SNAPSHOT$")
private val SNAPSHOT_TIMESTAMPED_REGEX = Regex("^(.*)-([0-9]{8}.[0-9]{6})-([0-9]+)$")
private fun String.snapshotVersion(): SnapshotVersion? {
return SNAPSHOT_REGEX.find(this)?.destructured?.let { (base) ->
SnapshotVersion(base, null, null)
} ?: SNAPSHOT_TIMESTAMPED_REGEX.find(this)?.destructured?.let { (base, timestamp, build) ->
SnapshotVersion(base, timestamp, build.toInt())
}
}
private fun ArtifactIdentifier.repoPath(): String =
"${group.replace('.', '/')}/$name/$version"
private fun ArtifactIdentifier.filename(
snapshotVersion: SnapshotVersion?
): String = buildString {
append(name, "-", snapshotVersion ?: version)
if (classifier != null) append("-", classifier)
append(".", extension)
}
private val downloadOptions = DownloadOptions().apply { log = LogOptions.LOG_QUIET }

View File

@@ -0,0 +1,21 @@
package org.nixos.gradle2nix
import java.io.File
import java.net.URL
import java.security.MessageDigest
private const val HEX = "0123456789abcdef"
internal fun File.sha256(): String = readBytes().sha256()
private fun ByteArray.sha256() = buildString {
MessageDigest.getInstance("SHA-256").digest(this@sha256)
.asSequence()
.map(Byte::toInt)
.forEach {
append(HEX[it shr 4 and 0x0f])
append(HEX[it and 0x0f])
}
}
internal fun String.toUrl(): URL = URL(this)