Miscellaneous cleanup

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

View File

@@ -11,10 +11,8 @@ dependencies {
implementation(libs.clikt)
implementation(libs.gradle.toolingApi)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.okio)
implementation(libs.serialization.json)
runtimeOnly(libs.slf4j.simple)
implementation(libs.xmlutil)
"share"(project(":plugin", configuration = "shadow")) {
isTransitive = false
@@ -29,12 +27,13 @@ dependencies {
application {
mainClass.set("org.nixos.gradle2nix.MainKt")
applicationName = "gradle2nix"
applicationDefaultJvmArgs = listOf(
"-Dorg.nixos.gradle2nix.share=@APP_HOME@/share",
"-Dslf4j.internal.verbosity=ERROR"
)
applicationDefaultJvmArgs =
listOf(
"-Dorg.nixos.gradle2nix.share=@APP_HOME@/share",
"-Dslf4j.internal.verbosity=ERROR",
)
applicationDistribution
.from(configurations.named("share"), files("../gradle.nix"))
.from(configurations.named("share"))
.into("share")
.rename("plugin.*\\.jar", "plugin.jar")
}
@@ -51,32 +50,31 @@ val updateGolden = providers.gradleProperty("update-golden")
tasks {
(run) {
dependsOn(installDist)
doFirst {
systemProperties("org.nixos.gradle2nix.share" to installDist.get().destinationDir.resolve("share"))
}
enabled = false
}
startScripts {
doLast {
unixScript.writeText(
unixScript.readText().replace("@APP_HOME@", "'\$APP_HOME'")
unixScript.readText().replace("@APP_HOME@", "'\$APP_HOME'"),
)
windowsScript.writeText(
windowsScript.readText().replace("@APP_HOME@", "%APP_HOME%"))
windowsScript.readText().replace("@APP_HOME@", "%APP_HOME%"),
)
}
}
withType<Test> {
notCompatibleWithConfigurationCache("huh?")
dependsOn(installDist)
// TODO Find out why this fails the configuration cache
test {
notCompatibleWithConfigurationCache("contains a Task reference")
val shareDir = layout.dir(installDist.map { it.destinationDir.resolve("share") })
doFirst {
if (updateGolden.isPresent) {
systemProperty("org.nixos.gradle2nix.update-golden", "")
}
systemProperties(
"org.nixos.gradle2nix.share" to installDist.get().destinationDir.resolve("share"),
"org.nixos.gradle2nix.m2" to "http://0.0.0.0:8989/m2"
"org.nixos.gradle2nix.share" to shareDir.get().asFile,
"org.nixos.gradle2nix.m2" to "http://0.0.0.0:8989/m2",
)
}
useJUnitPlatform()

View File

@@ -1,9 +1,5 @@
package org.nixos.gradle2nix
import java.io.File
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.io.path.absolutePathString
import kotlinx.coroutines.suspendCancellableCoroutine
import org.gradle.tooling.GradleConnectionException
import org.gradle.tooling.GradleConnector
@@ -12,8 +8,15 @@ import org.gradle.tooling.ResultHandler
import org.gradle.tooling.model.gradle.GradleBuild
import org.nixos.gradle2nix.model.DependencySet
import org.nixos.gradle2nix.model.RESOLVE_ALL_TASK
import java.io.File
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.io.path.absolutePathString
fun connect(config: Config, projectDir: File = config.projectDir): ProjectConnection =
fun connect(
config: Config,
projectDir: File = config.projectDir,
): ProjectConnection =
GradleConnector.newConnector()
.apply {
when (val source = config.gradleSource) {
@@ -26,70 +29,75 @@ fun connect(config: Config, projectDir: File = config.projectDir): ProjectConnec
.forProjectDirectory(projectDir)
.connect()
suspend fun ProjectConnection.buildModel(): GradleBuild = suspendCancellableCoroutine { continuation ->
val cancellationTokenSource = GradleConnector.newCancellationTokenSource()
suspend fun ProjectConnection.buildModel(): GradleBuild =
suspendCancellableCoroutine { continuation ->
val cancellationTokenSource = GradleConnector.newCancellationTokenSource()
continuation.invokeOnCancellation { cancellationTokenSource.cancel() }
continuation.invokeOnCancellation { cancellationTokenSource.cancel() }
action { controller -> controller.buildModel }
.withCancellationToken(cancellationTokenSource.token())
.run(object : ResultHandler<GradleBuild> {
override fun onComplete(result: GradleBuild) {
continuation.resume(result)
action { controller -> controller.buildModel }
.withCancellationToken(cancellationTokenSource.token())
.run(
object : ResultHandler<GradleBuild> {
override fun onComplete(result: GradleBuild) {
continuation.resume(result)
}
override fun onFailure(failure: GradleConnectionException) {
continuation.resumeWithException(failure)
}
},
)
}
suspend fun ProjectConnection.build(config: Config): DependencySet =
suspendCancellableCoroutine { continuation ->
val cancellationTokenSource = GradleConnector.newCancellationTokenSource()
continuation.invokeOnCancellation { cancellationTokenSource.cancel() }
action { controller -> controller.getModel(DependencySet::class.java) }
.withCancellationToken(cancellationTokenSource.token())
.apply {
if (config.tasks.isNotEmpty()) {
forTasks(*config.tasks.toTypedArray())
} else {
forTasks(RESOLVE_ALL_TASK)
}
}
override fun onFailure(failure: GradleConnectionException) {
continuation.resumeWithException(failure)
}
})
}
suspend fun ProjectConnection.build(config: Config): DependencySet = suspendCancellableCoroutine { continuation ->
val cancellationTokenSource = GradleConnector.newCancellationTokenSource()
continuation.invokeOnCancellation { cancellationTokenSource.cancel() }
action { controller -> controller.getModel(DependencySet::class.java) }
.withCancellationToken(cancellationTokenSource.token())
.apply {
if (config.tasks.isNotEmpty()) {
forTasks(*config.tasks.toTypedArray())
} else {
forTasks(RESOLVE_ALL_TASK)
}
}
.setJavaHome(config.gradleJdk)
.addArguments(config.gradleArgs)
.addArguments(
"--no-parallel",
"--refresh-dependencies",
"--gradle-user-home=${config.gradleHome}",
"--init-script=${config.appHome}/init.gradle",
)
.apply {
if (config.logger.stacktrace) {
addArguments("--stacktrace")
}
if (config.logger.logLevel < LogLevel.error) {
setStandardOutput(System.err)
setStandardError(System.err)
}
if (config.dumpEvents) {
withSystemProperties(
mapOf(
"org.gradle.internal.operations.trace" to
config.outDir.toPath().resolve("debug").absolutePathString()
.setJavaHome(config.gradleJdk)
.addArguments(config.gradleArgs)
.addArguments(
"--refresh-dependencies",
"--gradle-user-home=${config.gradleHome}",
"--init-script=${config.appHome}/init.gradle",
)
.apply {
if (config.logger.stacktrace) {
addArguments("--stacktrace")
}
if (config.logger.logLevel < LogLevel.ERROR) {
setStandardOutput(System.err)
setStandardError(System.err)
}
if (config.dumpEvents) {
withSystemProperties(
mapOf(
"org.gradle.internal.operations.trace" to
config.outDir.toPath().resolve("debug").absolutePathString(),
),
)
)
}
}
.run(object : ResultHandler<DependencySet> {
override fun onComplete(result: DependencySet) {
continuation.resume(result)
}
}
.run(
object : ResultHandler<DependencySet> {
override fun onComplete(result: DependencySet) {
continuation.resume(result)
}
override fun onFailure(failure: GradleConnectionException) {
continuation.resumeWithException(failure)
}
})
}
override fun onFailure(failure: GradleConnectionException) {
continuation.resumeWithException(failure)
}
},
)
}

View File

@@ -5,32 +5,43 @@ import kotlin.system.exitProcess
class Logger(
val out: PrintStream = System.err,
val logLevel: LogLevel = LogLevel.warn,
val logLevel: LogLevel = LogLevel.WARN,
val stacktrace: Boolean = false,
) {
fun debug(message: String, error: Throwable? = null) {
if (logLevel <= LogLevel.debug) {
fun debug(
message: String,
error: Throwable? = null,
) {
if (logLevel <= LogLevel.DEBUG) {
out.println("[DEBUG] $message")
printError(error)
}
}
fun info(message: String, error: Throwable? = null) {
if (logLevel <= LogLevel.info) {
fun info(
message: String,
error: Throwable? = null,
) {
if (logLevel <= LogLevel.INFO) {
out.println("[INFO] $message")
printError(error)
}
}
fun warn(message: String, error: Throwable? = null) {
if (logLevel <= LogLevel.warn) {
fun warn(
message: String,
error: Throwable? = null,
) {
if (logLevel <= LogLevel.WARN) {
out.println("[WARN] $message")
printError(error)
}
}
fun error(message: String, error: Throwable? = null): Nothing {
fun error(
message: String,
error: Throwable? = null,
): Nothing {
out.println("[ERROR] $message")
printError(error)
exitProcess(1)
@@ -43,6 +54,8 @@ class Logger(
}
operator fun component1() = ::info
operator fun component2() = ::warn
operator fun component3() = ::error
}

View File

@@ -14,19 +14,16 @@ import com.github.ajalt.clikt.parameters.options.defaultLazy
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.optionalValue
import com.github.ajalt.clikt.parameters.options.validate
import com.github.ajalt.clikt.parameters.types.enum
import com.github.ajalt.clikt.parameters.types.file
import java.io.File
import java.net.URI
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.encodeToStream
import org.gradle.internal.service.scopes.Scopes.Gradle
import org.gradle.tooling.model.gradle.GradleBuild
import org.nixos.gradle2nix.model.DependencySet
import java.io.File
import java.net.URI
data class Config(
val appHome: File,
@@ -38,100 +35,106 @@ data class Config(
val projectDir: File,
val tasks: List<String>,
val logger: Logger,
val dumpEvents: Boolean
val dumpEvents: Boolean,
)
@OptIn(ExperimentalSerializationApi::class)
val JsonFormat = Json {
ignoreUnknownKeys = true
prettyPrint = true
prettyPrintIndent = " "
}
val JsonFormat =
Json {
ignoreUnknownKeys = true
prettyPrint = true
prettyPrintIndent = " "
}
sealed interface GradleSource {
data class Distribution(val uri: URI) : GradleSource
data class Path(val path: File) : GradleSource
data object Project : GradleSource
data class Wrapper(val version: String) : GradleSource
}
enum class LogLevel {
debug,
info,
warn,
error,
DEBUG,
INFO,
WARN,
ERROR,
}
class Gradle2Nix : CliktCommand(
name = "gradle2nix",
) {
private val tasks: List<String> by option(
"--task", "-t",
"--task",
"-t",
metavar = "TASK",
help = "Gradle tasks to run"
help = "Gradle tasks to run",
).multiple()
private val projectDir: File by option(
"--project", "-p",
help = "Path to the project root")
.file()
.default(File("."), "Current directory")
.validate { file ->
if (!file.exists()) fail("Directory \"$file\" does not exist.")
if (file.isFile) fail("Directory \"$file\" is a file.")
if (!file.canRead()) fail("Directory \"$file\" is not readable.")
if (!file.isProjectRoot()) fail("Directory \"$file\" is not a Gradle project.")
}
"--project",
"-p",
help = "Path to the project root",
).file(
mustExist = true,
mustBeReadable = true,
canBeFile = false,
).convert { file ->
getProjectRoot(file) ?: fail("Could not locate project root in \"$file\" or its parents.")
}.defaultLazy("Current directory") {
File(".")
}
private val outDir: File? by option(
"--out-dir", "-o",
"--out-dir",
"-o",
metavar = "DIR",
help = "Path to write generated files")
help = "Path to write generated files",
)
.file(canBeFile = false, canBeDir = true)
.defaultLazy("<project>") { projectDir }
internal val lockFile: String by option(
"--lock-file", "-l",
"--lock-file",
"-l",
metavar = "FILENAME",
help = "Name of the generated lock file"
help = "Name of the generated lock file",
).default("gradle.lock")
private val nixFile: String by option(
"--nix-file", "-n",
metavar = "FILENAME",
help = "Name of the generated Nix file"
).default("gradle.nix")
private val gradleSource: GradleSource by mutuallyExclusiveOptions(
option(
"--gradle-dist",
metavar = "URI",
help = "Gradle distribution URI"
help = "Gradle distribution URI",
).convert { GradleSource.Distribution(URI(it)) },
option(
"--gradle-home",
metavar = "DIR",
help = "Gradle home path (e.g. \\`nix eval nixpkgs#gradle.outPath\\`/lib/gradle)"
help = "Gradle home path (e.g. \\`nix eval --raw nixpkgs#gradle.outPath\\`/lib/gradle)",
).file(mustExist = true, canBeFile = false).convert { GradleSource.Path(it) },
option(
"--gradle-wrapper",
help = "Gradle wrapper version"
help = "Gradle wrapper version",
).convert { GradleSource.Wrapper(it) },
name = "Gradle installation",
help = "Where to find Gradle. By default, use the project's wrapper."
help = "Where to find Gradle. By default, use the project's wrapper.",
).single().default(GradleSource.Project)
private val gradleJdk: File? by option(
"--gradle-jdk", "-j",
"--gradle-jdk",
"-j",
metavar = "DIR",
help = "JDK home to use for launching Gradle (e.g. `nix eval nixpkgs#openjdk.home`)",
help = "JDK home to use for launching Gradle (e.g. \\`nix eval --raw nixpkgs#openjdk.home\\`)",
).file(canBeFile = false, canBeDir = true)
private val logLevel: LogLevel by option(
"--log",
help = "Print messages with this priority or higher")
.enum<LogLevel>()
.default(LogLevel.error)
help = "Print messages with this priority or higher",
)
.enum<LogLevel>(key = { it.name.lowercase() })
.default(LogLevel.INFO, "info")
private val dumpEvents: Boolean by option(
"--dump-events",
@@ -140,12 +143,12 @@ class Gradle2Nix : CliktCommand(
private val stacktrace: Boolean by option(
"--stacktrace",
help = "Print a stack trace on error"
help = "Print a stack trace on error",
).flag()
private val gradleArgs: List<String> by argument(
name = "ARGS",
help = "Extra arguments to pass to Gradle"
help = "Extra arguments to pass to Gradle",
).multiple()
init {
@@ -158,48 +161,39 @@ class Gradle2Nix : CliktCommand(
override fun run() {
val logger = Logger(logLevel = logLevel, stacktrace = stacktrace)
val appHome = System.getProperty("org.nixos.gradle2nix.share")?.let(::File)
?: error("could not locate the /share directory in the gradle2nix installation: ${System.getenv("APP_HOME")}")
val appHome =
System.getProperty("org.nixos.gradle2nix.share")?.let(::File)
?: error("Could not locate the /share directory in the gradle2nix installation: ${System.getenv("APP_HOME")}")
logger.debug("appHome=$appHome")
val gradleHome =
System.getenv("GRADLE_USER_HOME")?.let(::File) ?: File("${System.getProperty("user.home")}/.gradle")
val config = Config(
appHome,
gradleHome,
gradleSource,
gradleJdk,
gradleArgs,
outDir ?: projectDir,
projectDir,
tasks,
logger,
dumpEvents
)
logger.debug("gradleHome=$gradleHome")
val config =
Config(
appHome,
gradleHome,
gradleSource,
gradleJdk,
gradleArgs,
outDir ?: projectDir,
projectDir,
tasks,
logger,
dumpEvents,
)
val metadata = File("$projectDir/gradle/verification-metadata.xml")
if (metadata.exists()) {
val backup = metadata.resolveSibling("verification-metadata.xml.bak")
if (metadata.renameTo(backup)) {
Runtime.getRuntime().addShutdownHook(Thread {
metadata.delete()
backup.renameTo(metadata)
})
} else {
metadata.deleteOnExit()
val buildSrcs =
connect(config).use { connection ->
val root = runBlocking { connection.buildModel() }
val builds: List<GradleBuild> =
buildList {
add(root)
addAll(root.editableBuilds)
}
builds.mapNotNull { build ->
build.rootProject.projectDirectory.resolve("buildSrc").takeIf { it.exists() }
}
}
} else {
metadata.deleteOnExit()
}
val buildSrcs = connect(config).use { connection ->
val root = runBlocking { connection.buildModel() }
val builds: List<GradleBuild> = buildList {
add(root)
addAll(root.editableBuilds)
}
builds.mapNotNull { build ->
build.rootProject.projectDirectory.resolve("buildSrc").takeIf { it.exists() }
}
}
val dependencySets = mutableListOf<DependencySet>()
@@ -213,11 +207,12 @@ class Gradle2Nix : CliktCommand(
}
}
val env = try {
processDependencies(config, dependencySets)
} catch (e: Throwable) {
logger.error("dependency parsing failed", e)
}
val env =
try {
processDependencies(config, dependencySets)
} catch (e: Throwable) {
logger.error("Dependency parsing failed", e)
}
config.outDir.mkdirs()
@@ -226,12 +221,6 @@ class Gradle2Nix : CliktCommand(
outLockFile.outputStream().buffered().use { output ->
JsonFormat.encodeToStream(env, output)
}
val inNixFile = config.appHome.resolve("gradle.nix").takeIf { it.exists() }
?: error("Couldn't locate gradle.nix in the the gradle2nix installation: ${config.appHome}")
val outNixFile = config.outDir.resolve(nixFile)
logger.info("Writing Nix builder to $outNixFile")
inNixFile.copyTo(outNixFile, overwrite = true)
}
}

View File

@@ -1,12 +1,13 @@
package org.nixos.gradle2nix
import okio.ByteString.Companion.decodeHex
import org.nixos.gradle2nix.model.DependencyCoordinates
import org.nixos.gradle2nix.model.DependencySet
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
fun processDependencies(
config: Config,
dependencySets: Iterable<DependencySet>
dependencySets: Iterable<DependencySet>,
): Env {
return buildMap<DependencyCoordinates, Map<String, Artifact>> {
for (dependencySet in dependencySets) {
@@ -19,11 +20,13 @@ fun processDependencies(
for ((name, artifact) in b) {
merge(name, artifact) { aa, ba ->
check(aa.hash == ba.hash) {
config.logger.error("""
config.logger.error(
"""
Conflicting hashes found for $id:$name:
1: ${aa.hash}
2: ${ba.hash}
""".trimIndent())
""".trimIndent(),
)
}
aa
@@ -41,18 +44,22 @@ fun processDependencies(
private fun DependencySet.toEnv(): Map<DependencyCoordinates, Map<String, Artifact>> {
return dependencies.associate { dep ->
dep.coordinates to dep.artifacts.associate {
it.name to Artifact(it.url, it.hash.toSri())
}
dep.coordinates to
dep.artifacts.associate {
it.name to Artifact(it.url, it.hash.toSri())
}
}
}
internal fun String.toSri(): String {
val hash = decodeHex().base64()
return "sha256-$hash"
}
@OptIn(ExperimentalEncodingApi::class, ExperimentalStdlibApi::class)
internal fun String.toSri(): String =
buildString {
append("sha256-")
Base64.encodeToAppendable(hexToByteArray(), this)
}
private val coordinatesComparator: Comparator<DependencyCoordinates> = compareBy<DependencyCoordinates> { it.group }
.thenBy { it.artifact }
.thenByDescending { Version(it.version) }
.thenByDescending { it.timestamp }
private val coordinatesComparator: Comparator<DependencyCoordinates> =
compareBy<DependencyCoordinates> { it.group }
.thenBy { it.artifact }
.thenByDescending { Version(it.version) }
.thenByDescending { it.timestamp }

View File

@@ -2,5 +2,13 @@ package org.nixos.gradle2nix
import java.io.File
fun File.isProjectRoot(): Boolean =
isDirectory && (resolve("settings.gradle").isFile || resolve("settings.gradle.kts").isFile)
tailrec fun getProjectRoot(path: File): File? {
return if (path.isProjectRoot()) {
path
} else {
val parent = path.parentFile ?: return null
return getProjectRoot(parent)
}
}
fun File.isProjectRoot(): Boolean = isDirectory && (resolve("settings.gradle").isFile || resolve("settings.gradle.kts").isFile)

View File

@@ -3,95 +3,105 @@ package org.nixos.gradle2nix
import java.util.concurrent.ConcurrentHashMap
class Version(val source: String, val parts: List<String>, base: Version?) : Comparable<Version> {
private val base: Version
val numericParts: List<Long?>
init {
this.base = base ?: this
this.numericParts = parts.map {
try { it.toLong() } catch (e: NumberFormatException) { null }
}
this.numericParts =
parts.map {
try {
it.toLong()
} catch (e: NumberFormatException) {
null
}
}
}
override fun compareTo(other: Version): Int = compare(this, other)
override fun toString(): String = source
override fun equals(other: Any?): Boolean = when {
other === this -> true
other == null || other !is Version -> false
else -> source == other.source
}
override fun equals(other: Any?): Boolean =
when {
other === this -> true
other == null || other !is Version -> false
else -> source == other.source
}
override fun hashCode(): Int = source.hashCode()
companion object {
private val SPECIAL_MEANINGS: Map<String, Int> = mapOf(
"dev" to -1,
"rc" to 1,
"snapshot" to 2,
"final" to 3,
"ga" to 4,
"release" to 5,
"sp" to 6
)
private val SPECIAL_MEANINGS: Map<String, Int> =
mapOf(
"dev" to -1,
"rc" to 1,
"snapshot" to 2,
"final" to 3,
"ga" to 4,
"release" to 5,
"sp" to 6,
)
private val cache = ConcurrentHashMap<String, Version>()
// From org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.VersionParser
operator fun invoke(original: String): Version = cache.getOrPut(original) {
val parts = mutableListOf<String>()
var digit = false
var startPart = 0
var pos = 0
var endBase = 0
var endBaseStr = 0
while (pos < original.length) {
val ch = original[pos]
if (ch == '.' || ch == '_' || ch == '-' || ch == '+') {
parts.add(original.substring(startPart, pos))
startPart = pos + 1
digit = false
if (ch != '.' && endBaseStr == 0) {
endBase = parts.size
endBaseStr = pos
}
} else if (ch in '0'..'9') {
if (!digit && pos > startPart) {
if (endBaseStr == 0) {
endBase = parts.size + 1
operator fun invoke(original: String): Version =
cache.getOrPut(original) {
val parts = mutableListOf<String>()
var digit = false
var startPart = 0
var pos = 0
var endBase = 0
var endBaseStr = 0
while (pos < original.length) {
val ch = original[pos]
if (ch == '.' || ch == '_' || ch == '-' || ch == '+') {
parts.add(original.substring(startPart, pos))
startPart = pos + 1
digit = false
if (ch != '.' && endBaseStr == 0) {
endBase = parts.size
endBaseStr = pos
}
parts.add(original.substring(startPart, pos))
startPart = pos
}
digit = true
} else {
if (digit) {
if (endBaseStr == 0) {
endBase = parts.size + 1
endBaseStr = pos
} else if (ch in '0'..'9') {
if (!digit && pos > startPart) {
if (endBaseStr == 0) {
endBase = parts.size + 1
endBaseStr = pos
}
parts.add(original.substring(startPart, pos))
startPart = pos
}
parts.add(original.substring(startPart, pos))
startPart = pos
digit = true
} else {
if (digit) {
if (endBaseStr == 0) {
endBase = parts.size + 1
endBaseStr = pos
}
parts.add(original.substring(startPart, pos))
startPart = pos
}
digit = false
}
digit = false
pos++
}
pos++
if (pos > startPart) {
parts.add(original.substring(startPart, pos))
}
var base: Version? = null
if (endBaseStr > 0) {
base = Version(original.substring(0, endBaseStr), parts.subList(0, endBase), null)
}
Version(original, parts, base)
}
if (pos > startPart) {
parts.add(original.substring(startPart, pos))
}
var base: Version? = null
if (endBaseStr > 0) {
base = Version(original.substring(0, endBaseStr), parts.subList(0, endBase), null)
}
Version(original, parts, base)
}
// From org.gradle.api.internal.artifacts.ivyservice.ivyresolve.strategy.StaticVersionComparator
private fun compare(version1: Version, version2: Version): Int {
private fun compare(
version1: Version,
version2: Version,
): Int {
if (version1 == version2) {
return 0
}

View File

@@ -21,11 +21,6 @@ import io.ktor.server.http.content.staticFiles
import io.ktor.server.netty.Netty
import io.ktor.server.netty.NettyApplicationEngine
import io.ktor.server.routing.routing
import java.io.File
import java.io.FileFilter
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.random.Random
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -35,17 +30,22 @@ import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import kotlinx.serialization.json.encodeToStream
import okio.use
import java.io.File
import java.io.FileFilter
import java.nio.file.Files
import java.nio.file.Paths
import kotlin.random.Random
private val app = Gradle2Nix()
@OptIn(ExperimentalSerializationApi::class)
private val json = Json {
prettyPrint = true
prettyPrintIndent = " "
}
private val json =
Json {
prettyPrint = true
prettyPrintIndent = " "
}
val testLogger = Logger(logLevel = LogLevel.debug, stacktrace = true)
val testLogger = Logger(logLevel = LogLevel.DEBUG, stacktrace = true)
fun fixture(path: String): File {
return Paths.get("../fixtures", path).toFile()
@@ -55,17 +55,19 @@ fun fixture(path: String): File {
suspend fun TestScope.fixture(
project: String,
vararg args: String,
test: suspend TestScope.(File, Env) -> Unit
test: suspend TestScope.(File, Env) -> Unit,
) {
val tmp = Paths.get("build/tmp/gradle2nix").apply { toFile().mkdirs() }
val baseDir = Paths.get("../fixtures/projects", project).toFile()
val children = baseDir.listFiles(FileFilter { it.isDirectory && (it.name == "groovy" || it.name == "kotlin") })
?.toList()
val cases = if (children.isNullOrEmpty()) {
listOf(project to baseDir)
} else {
children.map { "$project.${it.name}" to it }
}
val children =
baseDir.listFiles(FileFilter { it.isDirectory && (it.name == "groovy" || it.name == "kotlin") })
?.toList()
val cases =
if (children.isNullOrEmpty()) {
listOf(project to baseDir)
} else {
children.map { "$project.${it.name}" to it }
}
for (case in cases) {
registerTestCase(
NestedTest(
@@ -73,7 +75,7 @@ suspend fun TestScope.fixture(
disabled = false,
config = null,
type = TestType.Dynamic,
source = sourceRef()
source = sourceRef(),
) {
var dirName = case.second.toString().replace("/", ".")
while (dirName.startsWith(".")) dirName = dirName.removePrefix(".")
@@ -88,22 +90,25 @@ suspend fun TestScope.fixture(
}
app.main(
listOf(
"-p", tempDir.toString(),
"--log", "debug",
"-p",
tempDir.toString(),
"--log",
"debug",
"--stacktrace",
"--dump-events",
"--",
"-Dorg.nixos.gradle2nix.m2=$m2",
"--info"
) + args
"--info",
) + args,
)
val file = tempDir.resolve(app.lockFile)
file.shouldBeAFile()
val env: Env = file.inputStream().buffered().use { input ->
Json.decodeFromStream(input)
}
val env: Env =
file.inputStream().buffered().use { input ->
Json.decodeFromStream(input)
}
test(tempDir, env)
}
},
)
}
}
@@ -127,11 +132,12 @@ suspend fun TestScope.golden(
if (!goldenFile.exists()) {
fail("Golden file '$filename' doesn't exist. Run with --update-golden to generate.")
}
val goldenData = try {
goldenFile.readText()
} catch (e: SerializationException) {
fail("Failed to load golden data from '$filename'. Run with --update-golden to regenerate.")
}
val goldenData =
try {
goldenFile.readText()
} catch (e: SerializationException) {
fail("Failed to load golden data from '$filename'. Run with --update-golden to regenerate.")
}
json.encodeToString(env) should beEqual(goldenData)
}
}
@@ -172,24 +178,25 @@ object MavenRepo : MountableExtension<MavenRepo.Config, NettyApplicationEngine>,
private fun tryStart(attempts: Int): NettyApplicationEngine {
return try {
val p = config.port ?: Random.nextInt(10000, 65000)
val s = embeddedServer(Netty, port = p, host = config.host) {
routing {
staticFiles(
remotePath = config.path,
dir = config.repository,
index = null,
) {
enableAutoHeadResponse()
contentType { path ->
when (path.extension) {
"pom", "xml" -> ContentType.Text.Xml
"jar" -> ContentType("application", "java-archive")
else -> ContentType.Text.Plain
val s =
embeddedServer(Netty, port = p, host = config.host) {
routing {
staticFiles(
remotePath = config.path,
dir = config.repository,
index = null,
) {
enableAutoHeadResponse()
contentType { path ->
when (path.extension) {
"pom", "xml" -> ContentType.Text.Xml
"jar" -> ContentType("application", "java-archive")
else -> ContentType.Text.Plain
}
}
}
}
}
}
coroutineScope.launch { s.start(wait = true) }
s
} catch (e: Throwable) {