package org.nixos.gradle2nix import com.github.ajalt.clikt.completion.CompletionCandidates import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.context import com.github.ajalt.clikt.output.CliktHelpFormatter 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.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.validate import com.github.ajalt.clikt.parameters.types.file import com.squareup.moshi.Moshi import com.squareup.moshi.Types import okio.buffer import okio.sink import java.io.File val shareDir: String = System.getProperty("org.nixos.gradle2nix.share") data class Config( val gradleVersion: String?, val gradleArgs: String?, val configurations: List, val projectDir: File, val includes: List, val subprojects: List, val buildSrc: Boolean, val quiet: Boolean ) { val allProjects = listOf(projectDir) + includes } class Main : CliktCommand( name = "gradle2nix" ) { private val gradleVersion: String? by option("--gradle-version", "-g", 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 by option("--configuration", "-c", metavar = "NAME", help = "Add a configuration to resolve (default: all configurations)") .multiple() private val includes: List by option("--include", "-i", metavar = "DIR", help = "Add an additional project to include") .file(mustExist = true, canBeFile = false, canBeDir = true, mustBeReadable = true) .multiple() .validate { files -> val failures = files.filterNot { it.isProjectRoot() } if (failures.isNotEmpty()) { val message = failures.joinToString("\n ") fail("Included builds are not Gradle projects:\n$message\n" + "Gradle projects must contain a settings.gradle or settings.gradle.kts script.") } } private val subprojects: List by option("--project", "-p", metavar = "PATH", help = "Only resolve these subproject paths, e.g. ':', or ':sub:project' (default: all projects)") .multiple() .validate { paths -> val failures = paths.filterNot { it.startsWith(":") } if (failures.isNotEmpty()) { val message = failures.joinToString("\n ") fail("Subproject paths must be absolute:\n$message\n" + "Paths are in the form ':parent:child'.") } } val outDir: File? by option("--out-dir", "-o", metavar = "DIR", help = "Path to write generated files (default: PROJECT-DIR)") .file(canBeFile = false, canBeDir = true) val envFile: String by option("--env", "-e", metavar = "FILENAME", help = "Prefix for environment files (.json and .nix)") .default("gradle-env") private val buildSrc: Boolean by option("--build-src", "-b", help = "Include buildSrc project (default: true)") .flag("--no-build-src", "-nb", default = true) private val quiet: Boolean by option("--quiet", "-q", help = "Disable logging") .flag(default = false) private val projectDir: File by argument("PROJECT-DIR", help = "Path to the project root (default: .)") .projectDir() .default(File(".")) init { context { helpFormatter = CliktHelpFormatter(showDefaultValues = true) } } // Visible for testing lateinit var config: Config override fun run() { config = Config( gradleVersion, gradleArgs, configurations, projectDir, includes, subprojects, buildSrc, quiet ) val (log, _, _) = Logger(verbose = !config.quiet) val paths = resolveProjects(config).map { p -> p.toRelativeString(config.projectDir) } val models = connect(config).use { connection -> paths.associateWith { project -> log("Resolving project model: ${project.takeIf { it.isNotEmpty() } ?: "root project"}...") connection.getBuildModel(config, project) } } log("Building environment...") val nixGradleEnv = buildEnv(models) val outDir = outDir ?: projectDir val json = outDir.resolve("$envFile.json") log("Writing environment to $json") json.sink().buffer().use { out -> Moshi.Builder().build() .adapter>( Types.newParameterizedType(Map::class.java, String::class.java, NixGradleEnv::class.java) ) .indent(" ") .toJson(out, nixGradleEnv) out.flush() } val nix = outDir.resolve("$envFile.nix") log("Writing Nix script to $nix") File(shareDir).resolve("gradle-env.nix").copyTo(nix, overwrite = true) } } fun ProcessedArgument.projectDir(): ProcessedArgument { return convert(completionCandidates = CompletionCandidates.Path) { File(it).also { 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.") } } } fun main(args: Array) = Main().main(args)