mirror of
https://github.com/tadfisher/gradle2nix.git
synced 2026-01-12 07:50:53 -05:00
Miscellaneous cleanup
This commit is contained in:
@@ -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)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user