Move Gradle build logic to a setup hook

This commit is contained in:
Tad Fisher
2024-06-13 15:21:58 -07:00
parent a4ef499401
commit b32bf21a6c
26 changed files with 768 additions and 470 deletions

View File

@@ -2,7 +2,8 @@
[[./assets/gradle2nix.png]] [[./assets/gradle2nix.png]]
Generate [[https://nixos.org/nix/][Nix]] expressions which build [[https://gradle.org/][Gradle]]-based projects. Generate [[https://nixos.org/nix/][Nix]] expressions which build
[[https://gradle.org/][Gradle]]-based projects.
** Table of contents ** Table of contents
@@ -13,6 +14,10 @@ Generate [[https://nixos.org/nix/][Nix]] expressions which build [[https://gradl
- [[#usage][Usage]] - [[#usage][Usage]]
- [[#for-packagers][For packagers]] - [[#for-packagers][For packagers]]
- [[#specifying-the-gradle-installation][Specifying the Gradle installation]] - [[#specifying-the-gradle-installation][Specifying the Gradle installation]]
- [[#reference][Reference]]
- [[#buildGradlePackage][=buildGradlePackage=]]
- [[#buildMavenRepo][=buildMavenRepo=]]
- [[#gradleSetupHook][=gradleSetupHook=]]
- [[#contributing][Contributing]] - [[#contributing][Contributing]]
- [[#license][License]] - [[#license][License]]
#+END_QUOTE #+END_QUOTE
@@ -25,7 +30,8 @@ is that it is purely functional; a "package" is a function which
accepts inputs (source code, configuration, etc) and produces an accepts inputs (source code, configuration, etc) and produces an
output (binaries, a Java JAR, documentation, really anything). output (binaries, a Java JAR, documentation, really anything).
One benefit of a functional build system is [[https://reproducible-builds.org/][reproducibility]]. If you One benefit of a functional build system is
[[https://reproducible-builds.org/][reproducibility]]. If you
specify your inputs precisely, and take care not to introduce specify your inputs precisely, and take care not to introduce
impurities—such as files retrieved over a network without tracking impurities—such as files retrieved over a network without tracking
their content—you will receive, byte-for-byte, the exact output as their content—you will receive, byte-for-byte, the exact output as
@@ -42,31 +48,33 @@ inputs, including:
- Environment variables and command-line options - Environment variables and command-line options
- Artifacts cached on the system hosting the build - Artifacts cached on the system hosting the build
=gradle2nix= helps to solve this problem by leveraging Nix to control =gradle2nix= helps to solve this problem by leveraging Nix to
the most common inputs to a Gradle build. When run on a project, it control the most common inputs to a Gradle build. When run on a
will record all dependencies for both the build environment (including project, it will record all dependencies for both the build
=plugins= and =buildscript= blocks) and the project, and provide a Nix environment (including =plugins= and =buildscript= blocks) and the
expression to run the build given these dependencies. The build itself project, and provide a Nix expression to run the build given these
is then run in a sandbox, where only content-tracked network requests dependencies. The build itself is then run in a sandbox, where only
are allowed to fetch dependencies, and a local Maven repository is content-tracked network requests are allowed to fetch dependencies,
created on-the-fly to host the dependency artifacts somewhere Gradle and a local Maven repository is created on-the-fly to host the
can resolve them without a network. dependency artifacts somewhere Gradle can resolve them without a
network.
This tool is useful for both development and packaging. You can use This tool is useful for both development and packaging. You can use
=gradle2nix= to: =gradle2nix= to:
- Create isolated and reproducible development environments that work - Create isolated and reproducible development environments that
anywhere Nix itself can run; work anywhere Nix itself can run.
- Reduce or eliminate flakiness and maintenance headaches from CI/CD - Reduce or eliminate flakiness and maintenance headaches from CI/CD
pipelines pipelines.
- Distribute a recipe which can reliably build a Gradle project in - Distribute a recipe which can reliably build a Gradle project in
repositories such as the [[https://nixos.org/nixpkgs/][Nix Package Collection]]. repositories such as the [[https://nixos.org/nixpkgs/][Nix Package
Collection]].
** Installation ** Installation
A [[./gradle.nix][Nix expression]] (generated by =gradle2nix= itself) is provided for A [[./gradle.nix][Nix expression]] (generated by =gradle2nix=
convenience. The following expression will fetch and build the latest itself) is provided for convenience. The following expression will
version of this package: fetch and build the latest version of this package:
#+begin_src nix #+begin_src nix
import (fetchTarball "https://github.com/tadfisher/gradle2nix/archive/master.tar.gz") {} import (fetchTarball "https://github.com/tadfisher/gradle2nix/archive/master.tar.gz") {}
@@ -93,8 +101,9 @@ nix-env -if "https://github.com/tadfisher/gradle2nix/archive/master.tar.gz"
=gradle2nix= is not yet packaged in =nixpkgs= itself, but work is =gradle2nix= is not yet packaged in =nixpkgs= itself, but work is
[[https://github.com/NixOS/nixpkgs/pull/77422][in progress]]. [[https://github.com/NixOS/nixpkgs/pull/77422][in progress]].
The [[./gradle.nix][buildGradlePackage]] function is provided via the The [[./nix/build-gradle-package.nix][buildGradlePackage]] function
=gradle2nix.passthru.buildGradlePackage= attribute. is provided via the =gradle2nix.passthru.buildGradlePackage=
attribute.
#+begin_src nix #+begin_src nix
{ pkgs ? import <nixpkgs> {} }: { pkgs ? import <nixpkgs> {} }:
@@ -106,23 +115,24 @@ gradle2nix.buildGradlePackage {
pname = "my-package"; pname = "my-package";
version = "1.0"; version = "1.0";
lockFile = ./gradle.lock; lockFile = ./gradle.lock;
gradleFlags = [ "installDist" ]; gradleInstallFlags = [ "installDist" ];
# ... # ...
} }
#+end_src #+end_src
*** Flake *** Flake
A [[./flake.nix][flake.nix]] is provided for those using [[https://nixos.wiki/wiki/Flakes][Nix flakes]]. For example, the A [[./flake.nix][flake.nix]] is provided for those using
following will build and run =gradle2nix= with the arguments provided [[https://nixos.wiki/wiki/Flakes][Nix flakes]]. For example, the
after =--=: following will build and run =gradle2nix= with the arguments
provided after =--=:
#+begin_example #+begin_example
nix run github:tadfisher/gradle2nix -- --help nix run github:tadfisher/gradle2nix -- --help
#+end_example #+end_example
The [[./gradle.nix][buildGradlePackage]] function is provided via the The [[./nix/build-gradle-package.nix][buildGradlePackage]] function
=builders= output. is provided via the =builders= output.
#+begin_src nix #+begin_src nix
{ {
@@ -134,7 +144,7 @@ The [[./gradle.nix][buildGradlePackage]] function is provided via the
pname = "my-package"; pname = "my-package";
version = "1.0"; version = "1.0";
lockFile = ./gradle.lock; lockFile = ./gradle.lock;
gradleFlags = [ "installDist" ]; gradleInstallFlags = [ "installDist" ];
# ... # ...
}; };
}; };
@@ -169,12 +179,12 @@ Arguments:
<args> Extra arguments to pass to Gradle <args> Extra arguments to pass to Gradle
#+end_example #+end_example
Simply running =gradle2nix= in the root directory of a project should Simply running =gradle2nix= in the root directory of a project
be enough for most projects. This will produce two files, by default should be enough for most projects. This will produce a lock file,
called =gradle.lock= and =gradle.nix=, which contain the by default called =gradle.lock=, which contains the pinned
pinned dependencies for the project and a standard build expression dependencies for the project. An example of a build expression using
which can be imported or called by other Nix expressions. An example this lock file can be found in this project's
of such an expression can be found in this project's [[./gradle2nix.nix][gradle2nix.nix]]. [[./default.nix][default.nix]].
*** For packagers *** For packagers
@@ -211,16 +221,203 @@ gradle2nix --gradle-home=`nix eval nixpkgs#gradle.outPath`/lib/gradle
gradle2nix --gradle-wrapper=8.7 gradle2nix --gradle-wrapper=8.7
#+end_example #+end_example
** Reference
*** =buildGradlePackage=
This function is a convenience wrapper around =stdenv.mkDerivation=
that simplifies building Gradle projects with the lock files
produced by =gradle2nix=. It performs the following:
1. Applies [[#gradleSetupHook][=gradleSetupHook=]], overriding the
required =gradle= package if specified.
2. Builds the offline Maven repository with
[[#buildMavenRepo][=buildMavenRepo=]].
3. Sets the JDK used to run Gradle if specified.
4. Applies the offline repo to the Gradle build using an
initialization script.
- Source:
[[./nix/build-gradle-package.nix][build-gradle-package.nix]]
- Location:
- Nix :: =gradle2nix.passthru.buildGradlePackage=
- Flake :: =builders.${system}.buildGradlePackage=
**** Arguments to =buildGradlePackage=
- =lockFile= :: Path to the lock file generated by =gradle2nix=
(e.g. =gradle.lock=).
- =gradlePackage= :: The Gradle package to use. Default is
=pkgs.gradle=.
- =buildJdk= :: Override the default JDK used to run Gradle itself.
- =fetchers= :: Override functions which fetch dependency
artifacts.
See [[#fetchers][detailed documentation]] below.
- =overrides= :: Override artifacts in the offline Maven repository.
See [[#override][detailed documentation]] below.
In addition, this function accepts:
- All arguments to =stdenv.mkDerivation=.
- Arguments specific to =gradleSetupHook= (see
[[#gradleSetupHook][below]]).
*** =buildMavenRepo=
This function takes a lock file and produces a derivation which
downloads all dependencies into a Maven local repository. The
derivation provides a passthru =gradleInitScript= attribute, which
is a Gradle initialization script that can be applied using =gradle
--init-script== or placed in =$GRADLE_USER_HOME/init.d=. The init
script replaces all repositories referenced in the project with the
local repository.
- Source: [[./nix/build-maven-repo.nix][build-maven-repo.nix]]
- Location:
- Nix :: =gradle2nix.passthru.buildMavenRepo=
- Flake :: =builders.${system}.buildMavenRepo=
**** Arguments to =buildMavenRepo=
- =lockFile= :: Path to the lock file generated by gradle2nix (e.g.
=gradle.lock=).
- =fetchers= :: Override functions which fetch dependency
artifacts.
See [[#fetchers][detailed documentation]] below.
- =overrides= :: Override artifacts in the offline Maven repository.
See [[#override][detailed documentation]] below.
*** =gradleSetupHook=
A
[[https://nixos.org/manual/nixpkgs/unstable/#ssec-setup-hooks][setup
hook]] to simplify building Gradle packages. Overrides the default
configure, build, check, and install phases.
To use, add =gradleSetupHook= to a derivation's =nativeBuildInputs=.
- Source: [[./nix/setup-hook.sh][setup-hook.sh]]
- Location:
- Nix :: =gradle2nix.passthru.gradleSetupHook=
- Flake :: =packages.${system}.gradleSetupHook=
**** Variables controlling =gradleSetupHook=
- =gradleInitScript= :: Path to an
[[https://docs.gradle.org/current/userguide/init_scripts.html][initialization
script]] used by =gradle= during all phases.
- =gradleFlags= :: Controls the arguments passed to =gradle= during
all phases.
- =gradleBuildFlags= :: Controls the arguments passed to =gradle=
during the build phase. The build phase is skipped if this is
unset.
- =gradleCheckFlags= :: Controls the arguments passed to =gradle=
during the check phase. The check phase is skipped if this is
unset.
- =gradleInstallFlags= :: Controls the arguments passed to =gradle=
during the install phase. This install phase is skipped if this is
unset.
- =dontUseGradleConfigure= :: When set to true, don't use the
predefined =gradleConfigurePhase=. This will also disable the use
of =gradleInitScript=.
- =dontUseGradleCheck= :: When set to true, don't use the predefined
=gradleCheckPhase=.
- =dontUseGradleInstall= :: When set to true, don't use the
predefined =gradleInstallPhase=.
**** Honored variables
The following variables commonly used by =stdenv.mkDerivation= are
honored by =gradleSetupHook=.
- =enableParallelBuilding=
- =enableParallelChecking=
- =enableParallelInstalling=
*** Common arguments
**** =fetchers=
Names in this set are URL schemes such as "https" or "s3". Values
are functions which take an artifact in the form ={ url, hash }=
and fetches it into the Nix store. For example:
#+begin_src nix
{
s3 = { name, url, hash }: fetchs3 {
s3url = url;
# TODO This doesn't work without patching fetchs3 to accept SRI hashes
inherit name hash;
region = "us-west-2";
credentials = {
access_key_id = "foo";
secret_access_key = "bar";
};
};
}
#+end_src
**** =overrides=
This is an attrset of the form:
#+begin_src nix
{
"${group}:${module}:${version}" = {
"${filename}" = <override function>;
}
}
#+end_src
The override function takes the original derivation from 'fetchers'
(e.g. the result of 'fetchurl') and produces a new derivation to
replace it.
- Replace a dependency's JAR artifact:
#+begin_src nix
{
"com.squareup.okio:okio:3.9.0"."okio-3.9.0.jar" = _: fetchurl {
url = "https://repo.maven.apache.org/maven2/com/squareup/okio/okio/3.9.0/okio-3.9.0.jar";
hash = "...";
downloadToTemp = true;
postFetch = "install -Dt $out/com/squareup/okio/okio/3.9.0/ $downloadedFile"
};
}
#+end_src
- Patch a JAR containing native binaries:
#+begin_src nix
{
"com.android.tools.build:aapt2:8.5.0-rc02-11315950" = {
"aapt2-8.5.0-rc02-11315950-linux.jar" = src: runCommandCC src.name {
nativeBuildInputs = [ jdk autoPatchelfHook ];
dontAutoPatchelf = true;
} ''
cp ${src} aapt2.jar
jar xf aapt2.jar aapt2
chmod +x aapt2
autoPatchelf aapt2
jar uf aapt2.jar aapt2
cp aapt2.jar $out
'';
}
}
#+end_src
** Contributing ** Contributing
Bug reports and feature requests are encouraged. Bug reports and feature requests are encouraged.
[[https://github.com/tadfisher/gradle2nix/issues/new][Create an issue]] [[https://github.com/tadfisher/gradle2nix/issues/new][Create an
issue]]
Code contributions are also encouraged. Please review the test cases Code contributions are also encouraged. Please review the test cases
in the [[./fixtures][fixtures]] directory and create a new one to reproduce any fixes in the [[./fixtures][fixtures]] directory and create a new one to
or test new features. See the [[./app/src/test/kotlin/org/nixos/gradle2nix/GoldenTest.kt][existing tests]] reproduce any fixes or test new features. See the
for examples of testing with these fixtures. [[./app/src/test/kotlin/org/nixos/gradle2nix/GoldenTest.kt][existing
tests]] for examples of testing with these fixtures.
** License ** License

View File

@@ -16,7 +16,8 @@ fun connect(
config: Config, config: Config,
projectDir: File = config.projectDir, projectDir: File = config.projectDir,
): ProjectConnection = ): ProjectConnection =
GradleConnector.newConnector() GradleConnector
.newConnector()
.apply { .apply {
when (val source = config.gradleSource) { when (val source = config.gradleSource) {
is GradleSource.Distribution -> useDistribution(source.uri) is GradleSource.Distribution -> useDistribution(source.uri)
@@ -24,8 +25,7 @@ fun connect(
GradleSource.Project -> useBuildDistribution() GradleSource.Project -> useBuildDistribution()
is GradleSource.Wrapper -> useGradleVersion(source.version) is GradleSource.Wrapper -> useGradleVersion(source.version)
} }
} }.forProjectDirectory(projectDir)
.forProjectDirectory(projectDir)
.connect() .connect()
suspend fun ProjectConnection.buildModel(): GradleBuild = suspend fun ProjectConnection.buildModel(): GradleBuild =
@@ -67,8 +67,7 @@ suspend fun ProjectConnection.build(
"--refresh-dependencies", "--refresh-dependencies",
"--gradle-user-home=${config.gradleHome}", "--gradle-user-home=${config.gradleHome}",
"--init-script=${config.appHome}/init.gradle", "--init-script=${config.appHome}/init.gradle",
) ).apply {
.apply {
if (config.logger.stacktrace) { if (config.logger.stacktrace) {
addArguments("--stacktrace") addArguments("--stacktrace")
} }
@@ -80,12 +79,14 @@ suspend fun ProjectConnection.build(
withSystemProperties( withSystemProperties(
mapOf( mapOf(
"org.gradle.internal.operations.trace" to "org.gradle.internal.operations.trace" to
config.outDir.toPath().resolve("debug").absolutePathString(), config.outDir
.toPath()
.resolve("debug")
.absolutePathString(),
), ),
) )
} }
} }.run(
.run(
object : ResultHandler<DependencySet> { object : ResultHandler<DependencySet> {
override fun onComplete(result: DependencySet) { override fun onComplete(result: DependencySet) {
continuation.resume(result) continuation.resume(result)

View File

@@ -48,13 +48,19 @@ val JsonFormat =
} }
sealed interface GradleSource { sealed interface GradleSource {
data class Distribution(val uri: URI) : GradleSource data class Distribution(
val uri: URI,
) : GradleSource
data class Path(val path: File) : GradleSource data class Path(
val path: File,
) : GradleSource
data object Project : GradleSource data object Project : GradleSource
data class Wrapper(val version: String) : GradleSource data class Wrapper(
val version: String,
) : GradleSource
} }
enum class LogLevel { enum class LogLevel {
@@ -64,9 +70,10 @@ enum class LogLevel {
ERROR, ERROR,
} }
class Gradle2Nix : CliktCommand( class Gradle2Nix :
CliktCommand(
name = "gradle2nix", name = "gradle2nix",
) { ) {
private val tasks: List<String> by option( private val tasks: List<String> by option(
"--task", "--task",
"-t", "-t",
@@ -93,8 +100,7 @@ class Gradle2Nix : CliktCommand(
"-o", "-o",
metavar = "DIR", metavar = "DIR",
help = "Path to write generated files", help = "Path to write generated files",
) ).file(canBeFile = false, canBeDir = true)
.file(canBeFile = false, canBeDir = true)
.defaultLazy("<project>") { projectDir } .defaultLazy("<project>") { projectDir }
internal val lockFile: String by option( internal val lockFile: String by option(
@@ -133,8 +139,7 @@ class Gradle2Nix : CliktCommand(
private val logLevel: LogLevel by option( private val logLevel: LogLevel by option(
"--log", "--log",
help = "Print messages with this priority or higher", help = "Print messages with this priority or higher",
) ).enum<LogLevel>(key = { it.name.lowercase() })
.enum<LogLevel>(key = { it.name.lowercase() })
.default(LogLevel.INFO, "info") .default(LogLevel.INFO, "info")
private val dumpEvents: Boolean by option( private val dumpEvents: Boolean by option(
@@ -192,7 +197,9 @@ class Gradle2Nix : CliktCommand(
addAll(root.editableBuilds) addAll(root.editableBuilds)
} }
builds.mapNotNull { build -> builds.mapNotNull { build ->
build.rootProject.projectDirectory.resolve("buildSrc").takeIf { it.exists() } build.rootProject.projectDirectory
.resolve("buildSrc")
.takeIf { it.exists() }
} }
} }

View File

@@ -8,8 +8,8 @@ import kotlin.io.encoding.ExperimentalEncodingApi
fun processDependencies( fun processDependencies(
config: Config, config: Config,
dependencySets: Iterable<DependencySet>, dependencySets: Iterable<DependencySet>,
): Env { ): Env =
return buildMap<DependencyCoordinates, Map<String, Artifact>> { buildMap<DependencyCoordinates, Map<String, Artifact>> {
for (dependencySet in dependencySets) { for (dependencySet in dependencySets) {
val env = dependencySet.toEnv() val env = dependencySet.toEnv()
@@ -40,16 +40,14 @@ fun processDependencies(
artifacts.toSortedMap() artifacts.toSortedMap()
}.toSortedMap(coordinatesComparator) }.toSortedMap(coordinatesComparator)
.mapKeys { (coordinates, _) -> coordinates.id } .mapKeys { (coordinates, _) -> coordinates.id }
}
private fun DependencySet.toEnv(): Map<DependencyCoordinates, Map<String, Artifact>> { private fun DependencySet.toEnv(): Map<DependencyCoordinates, Map<String, Artifact>> =
return dependencies.associate { dep -> dependencies.associate { dep ->
dep.coordinates to dep.coordinates to
dep.artifacts.associate { dep.artifacts.associate {
it.name to Artifact(it.url, it.hash.toSri()) it.name to Artifact(it.url, it.hash.toSri())
} }
} }
}
@OptIn(ExperimentalEncodingApi::class, ExperimentalStdlibApi::class) @OptIn(ExperimentalEncodingApi::class, ExperimentalStdlibApi::class)
internal fun String.toSri(): String = internal fun String.toSri(): String =

View File

@@ -2,7 +2,11 @@ package org.nixos.gradle2nix
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class Version(val source: String, val parts: List<String>, base: Version?) : Comparable<Version> { class Version(
val source: String,
val parts: List<String>,
base: Version?,
) : Comparable<Version> {
private val base: Version private val base: Version
val numericParts: List<Long?> val numericParts: List<Long?>

View File

@@ -3,7 +3,8 @@ package org.nixos.gradle2nix
import io.kotest.core.extensions.install import io.kotest.core.extensions.install
import io.kotest.core.spec.style.FunSpec import io.kotest.core.spec.style.FunSpec
class GoldenTest : FunSpec({ class GoldenTest :
FunSpec({
install(MavenRepo) install(MavenRepo)
context("basic") { context("basic") {
@@ -44,4 +45,4 @@ class GoldenTest : FunSpec({
context("subprojects") { context("subprojects") {
golden("subprojects/multi-module") golden("subprojects/multi-module")
} }
}) })

View File

@@ -47,9 +47,7 @@ private val json =
val testLogger = Logger(logLevel = LogLevel.DEBUG, stacktrace = true) val testLogger = Logger(logLevel = LogLevel.DEBUG, stacktrace = true)
fun fixture(path: String): File { fun fixture(path: String): File = Paths.get("../fixtures", path).toFile()
return Paths.get("../fixtures", path).toFile()
}
@OptIn(ExperimentalKotest::class, ExperimentalSerializationApi::class, KotestInternal::class) @OptIn(ExperimentalKotest::class, ExperimentalSerializationApi::class, KotestInternal::class)
suspend fun TestScope.fixture( suspend fun TestScope.fixture(
@@ -60,7 +58,8 @@ suspend fun TestScope.fixture(
val tmp = Paths.get("build/tmp/gradle2nix").apply { toFile().mkdirs() } val tmp = Paths.get("build/tmp/gradle2nix").apply { toFile().mkdirs() }
val baseDir = Paths.get("../fixtures/projects", project).toFile() val baseDir = Paths.get("../fixtures/projects", project).toFile()
val children = val children =
baseDir.listFiles(FileFilter { it.isDirectory && (it.name == "groovy" || it.name == "kotlin") }) baseDir
.listFiles(FileFilter { it.isDirectory && (it.name == "groovy" || it.name == "kotlin") })
?.toList() ?.toList()
val cases = val cases =
if (children.isNullOrEmpty()) { if (children.isNullOrEmpty()) {
@@ -175,8 +174,8 @@ object MavenRepo : MountableExtension<MavenRepo.Config, NettyApplicationEngine>,
return tryStart(3).also { this.server = it } return tryStart(3).also { this.server = it }
} }
private fun tryStart(attempts: Int): NettyApplicationEngine { private fun tryStart(attempts: Int): NettyApplicationEngine =
return try { try {
val p = config.port ?: Random.nextInt(10000, 65000) val p = config.port ?: Random.nextInt(10000, 65000)
val s = val s =
embeddedServer(Netty, port = p, host = config.host) { embeddedServer(Netty, port = p, host = config.host) {
@@ -202,7 +201,6 @@ object MavenRepo : MountableExtension<MavenRepo.Config, NettyApplicationEngine>,
} catch (e: Throwable) { } catch (e: Throwable) {
if (config.port == null && attempts > 0) tryStart(attempts - 1) else throw e if (config.port == null && attempts > 0) tryStart(attempts - 1) else throw e
} }
}
override suspend fun afterSpec(spec: Spec) { override suspend fun afterSpec(spec: Spec) {
server?.stop() server?.stop()

View File

@@ -5,9 +5,16 @@
with pkgs; with pkgs;
let let
buildMavenRepo = callPackage ./maven-repo.nix { }; buildMavenRepo = callPackage ./nix/build-maven-repo.nix { };
buildGradlePackage = callPackage ./gradle.nix { inherit buildMavenRepo; }; gradleSetupHook = makeSetupHook {
name = "gradle-setup-hook";
propagatedBuildInputs = [ gradle ];
} ./nix/setup-hook.sh;
buildGradlePackage = callPackage ./nix/build-gradle-package.nix {
inherit buildMavenRepo gradleSetupHook;
};
gradle2nix = buildGradlePackage { gradle2nix = buildGradlePackage {
pname = "gradle2nix"; pname = "gradle2nix";
@@ -30,9 +37,9 @@ let
}; };
}; };
gradleFlags = [ "installDist" ]; gradleInstallFlags = [ ":app:installDist" ];
installPhase = '' postInstall = ''
mkdir -p $out/{bin,/lib/gradle2nix} mkdir -p $out/{bin,/lib/gradle2nix}
cp -r app/build/install/gradle2nix/* $out/lib/gradle2nix/ cp -r app/build/install/gradle2nix/* $out/lib/gradle2nix/
rm $out/lib/gradle2nix/bin/gradle2nix.bat rm $out/lib/gradle2nix/bin/gradle2nix.bat
@@ -40,7 +47,7 @@ let
''; '';
passthru = { passthru = {
inherit buildGradlePackage buildMavenRepo; inherit buildGradlePackage buildMavenRepo gradleSetupHook;
}; };
meta = with lib; { meta = with lib; {

6
flake.lock generated
View File

@@ -20,11 +20,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1716769173, "lastModified": 1718160348,
"narHash": "sha256-7EXDb5WBw+d004Agt+JHC/Oyh/KTUglOaQ4MNjBbo5w=", "narHash": "sha256-9YrUjdztqi4Gz8n3mBuqvCkMo4ojrA6nASwyIKWMpus=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9ca3f649614213b2aaf5f1e16ec06952fe4c2632", "rev": "57d6973abba7ea108bac64ae7629e7431e0199b6",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@@ -20,15 +20,15 @@
inherit (nixpkgs) lib; inherit (nixpkgs) lib;
in in
{ {
builders = rec { builders = {
buildMavenRepo = pkgs.callPackage ./maven-repo.nix { }; inherit (self.packages.${system}.gradle2nix) buildGradlePackage buildMavenRepo;
buildGradlePackage = pkgs.callPackage ./gradle.nix { inherit buildMavenRepo; }; default = self.packages.${system}.buildGradlePackage;
default = buildGradlePackage;
}; };
packages = rec { packages = {
inherit (self.packages.${system}.gradle2nix) gradleSetupHook;
gradle2nix = pkgs.callPackage ./default.nix { }; gradle2nix = pkgs.callPackage ./default.nix { };
default = gradle2nix; default = self.packages.${system}.gradle2nix;
}; };
apps = rec { apps = rec {

View File

@@ -1,238 +0,0 @@
# This file is generated by gradle2nix.
#
# Example usage (e.g. in default.nix):
#
# with (import <nixpkgs> {});
# let
# buildGradle = callPackage ./gradle.nix {};
# in
# buildGradle {
# lockFile = ./gradle.lock;
#
# src = ./.;
#
# gradleFlags = [ "installDist" ];
#
# installPhase = ''
# mkdir -p $out
# cp -r app/build/install/myproject $out
# '';
# }
{
lib,
stdenv,
gradle,
buildMavenRepo,
writeText,
}:
let
defaultGradle = gradle;
in
{
# Path to the lockfile generated by gradle2nix (e.g. gradle.lock).
lockFile ? null,
pname ? "project",
version ? null,
enableParallelBuilding ? true,
# The Gradle package to use. Default is 'pkgs.gradle'.
gradle ? defaultGradle,
# Arguments to Gradle used to build the project in buildPhase.
gradleFlags ? [ "build" ],
# Enable debugging for the Gradle build; this will cause Gradle to run
# a debug server and wait for a JVM debugging client to attach.
enableDebug ? false,
# Additional code to run in the Gradle init script (init.gradle).
extraInit ? "",
# Override the default JDK used to run Gradle itself.
buildJdk ? null,
# Override functions which fetch dependency artifacts.
# Keys in this set are URL schemes such as "https" or "s3".
# Values are functions which take a dependency in the form
# `{ urls, hash }` and fetch into the Nix store. For example:
#
# {
# s3 = { name, urls, hash }: fetchs3 {
# s3url = builtins.head urls;
# # TODO This doesn't work without patching fetchs3 to accept SRI hashes
# inherit name hash;
# region = "us-west-2";
# credentials = {
# access_key_id = "foo";
# secret_access_key = "bar";
# };
# };
# }
fetchers ? { },
# Overlays for dependencies in the offline Maven repository.
#
# Acceps an attrset of dependencies (usually parsed from 'lockFile'), and produces an attrset
# containing dependencies to merge into the final set.
#
# The attrset is of the form:
#
# {
# "${group}:${module}:${version}" = <derivation>;
# # ...
# }
#
# A dependency derivation unpacks multiple source files into a single Maven-style directory named
# "${out}/${groupPath}/${module}/${version}/", where 'groupPath' is the dependency group ID with dot
# characters ('.') replaced by the path separator ('/').
#
# Examples:
#
# 1. Add or replace a dependency with a single JAR file:
#
# (_: {
# "com.squareup.okio:okio:3.9.0" = fetchurl {
# url = "https://repo.maven.apache.org/maven2/com/squareup/okio/okio/3.9.0/okio-3.9.0.jar";
# hash = "...";
# downloadToTemmp = true;
# postFetch = "install -Dt $out/com/squareup/okio/okio/3.9.0/ $downloadedFile"
# };
# })
#
# 2. Remove a dependency entirely:
#
# # This works because the result is filtered for values that are derivations.
# (_: {
# "org.apache.log4j:core:2.23.1" = null;
# })
overlays ? [ ],
...
}@args:
let
inherit (builtins) removeAttrs;
inherit (lib) versionAtLeast versionOlder;
offlineRepo = buildMavenRepo {
inherit
lockFile
pname
version
fetchers
overlays
;
};
initScript = writeText "init.gradle" ''
import org.gradle.util.GradleVersion
static boolean versionAtLeast(String version) {
return GradleVersion.current() >= GradleVersion.version(version)
}
static void configureRepos(RepositoryHandler repositories) {
repositories.configureEach { ArtifactRepository repo ->
if (repo instanceof MavenArtifactRepository) {
repo.setArtifactUrls(new HashSet<URI>())
repo.url 'file:${offlineRepo}'
repo.metadataSources {
gradleMetadata()
mavenPom()
artifact()
}
} else if (repo instanceof IvyArtifactRepository) {
repo.url 'file:${offlineRepo}'
repo.layout('maven')
repo.metadataSources {
gradleMetadata()
ivyDescriptor()
artifact()
}
} else if (repo instanceof UrlArtifactRepository) {
repo.url 'file:/homeless-shelter'
}
}
}
beforeSettings { settings ->
configureRepos(settings.pluginManagement.repositories)
configureRepos(settings.buildscript.repositories)
if (versionAtLeast("6.8")) {
configureRepos(settings.dependencyResolutionManagement.repositories)
}
}
beforeProject { project ->
configureRepos(project.buildscript.repositories)
configureRepos(project.repositories)
}
${extraInit}
'';
buildGradlePackage = stdenv.mkDerivation (
finalAttrs:
{
inherit
buildJdk
enableParallelBuilding
enableDebug
gradle
gradleFlags
pname
version
;
dontStrip = true;
nativeBuildInputs = [ finalAttrs.gradle ];
buildPhase =
let
finalGradleFlags =
[
"--console=plain"
"--no-build-cache"
"--no-configuration-cache"
"--no-daemon"
"--no-watch-fs"
"--offline"
]
++ lib.optional (finalAttrs.buildJdk != null) "-Dorg.gradle.java.home=${finalAttrs.buildJdk.home}"
++ lib.optional finalAttrs.enableDebug "-Dorg.gradle.debug=true"
++ lib.optional finalAttrs.enableParallelBuilding "--parallel"
++ lib.optional (versionAtLeast finalAttrs.gradle.version "8.0") "--init-script=${initScript}"
++ finalAttrs.gradleFlags;
in
''
runHook preBuild
(
set -eux
export NIX_OFFLINE_REPO='${offlineRepo}'
export GRADLE_USER_HOME="$(mktemp -d)"
${lib.optionalString (versionOlder finalAttrs.gradle.version "8.0") ''
# Work around https://github.com/gradle/gradle/issues/1055
mkdir -p "$GRADLE_USER_HOME/init.d"
ln -s ${initScript} "$GRADLE_USER_HOME/init.d/nix-init.gradle"
''}
gradle ${builtins.toString finalGradleFlags}
)
runHook postBuild
'';
passthru = {
inherit offlineRepo;
};
}
// removeAttrs args [
"lockFile"
"extraInit"
"fetchers"
"overlays"
]
);
in
buildGradlePackage

View File

@@ -0,0 +1,145 @@
# This file is generated by gradle2nix.
#
# Example usage (e.g. in default.nix):
#
# with (import <nixpkgs> {});
# let
# buildGradle = callPackage ./gradle.nix {};
# in
# buildGradle {
# lockFile = ./gradle.lock;
#
# src = ./.;
#
# gradleFlags = [ "installDist" ];
#
# installPhase = ''
# mkdir -p $out
# cp -r app/build/install/myproject $out
# '';
# }
{
lib,
stdenv,
gradle,
buildMavenRepo,
gradleSetupHook,
writeText,
}:
{
# Path to the lockfile generated by gradle2nix (e.g. gradle.lock).
lockFile ? null,
pname ? "project",
version ? null,
# The Gradle package to use. Default is 'pkgs.gradle'.
gradlePackage ? gradle,
# Override the default JDK used to run Gradle itself.
buildJdk ? null,
# Override functions which fetch dependency artifacts.
# Names in this set are URL schemes such as "https" or "s3".
# Values are functions which take a dependency in the form
# `{ urls, hash }` and fetch into the Nix store. For example:
#
# {
# s3 = { name, urls, hash }: fetchs3 {
# s3url = builtins.head urls;
# # TODO This doesn't work without patching fetchs3 to accept SRI hashes
# inherit name hash;
# region = "us-west-2";
# credentials = {
# access_key_id = "foo";
# secret_access_key = "bar";
# };
# };
# }
fetchers ? { },
# Override artifacts in the offline Maven repository.
#
# This is an attrset is of the form:
#
# {
# "${group}:${module}:${version}" = {
# "${filename}" = <override function>;
# }
# }
#
# The override function takes the original derivation from 'fetchers' (e.g. the result of
# 'fetchurl') and produces a new derivation to replace it.
#
# Examples:
#
# 1. Replace a dependency's JAR artifact:
#
# {
# "com.squareup.okio:okio:3.9.0"."okio-3.9.0.jar" = _: fetchurl {
# url = "https://repo.maven.apache.org/maven2/com/squareup/okio/okio/3.9.0/okio-3.9.0.jar";
# hash = "...";
# downloadToTemp = true;
# postFetch = "install -Dt $out/com/squareup/okio/okio/3.9.0/ $downloadedFile"
# };
# }
#
# 2. Patch a JAR containing native binaries:
#
# {
# "com.android.tools.build:aapt2:8.5.0-rc02-11315950" = {
# "aapt2-8.5.0-rc02-11315950-linux.jar" = src: runCommandCC src.name {
# nativeBuildInputs = [ jdk autoPatchelfHook ];
# dontAutoPatchelf = true;
# } ''
# cp ${src} aapt2.jar
# jar xf aapt2.jar aapt2
# chmod +x aapt2
# autoPatchelf aapt2
# jar uf aapt2.jar aapt2
# cp aapt2.jar $out
# '';
# }
# }
overrides ? { },
...
}@args:
let
inherit (builtins) removeAttrs;
gradleSetupHook' = gradleSetupHook.overrideAttrs (_: {
propagatedBuildInputs = [ gradlePackage ];
});
offlineRepo =
if lockFile != null then buildMavenRepo { inherit lockFile fetchers overrides; } else null;
buildGradlePackage = stdenv.mkDerivation (
finalAttrs:
{
inherit buildJdk pname version;
inherit (offlineRepo) gradleInitScript;
dontStrip = true;
nativeBuildInputs = (args.nativeBuildInputs or [ ]) ++ [ gradleSetupHook' ];
gradleFlags =
[ "--console=plain" ]
++ lib.optional (finalAttrs.buildJdk != null) "-Dorg.gradle.java.home=${finalAttrs.buildJdk.home}";
passthru =
lib.optionalAttrs (offlineRepo != null) { inherit offlineRepo; } // (args.passthru or { });
}
// removeAttrs args [
"gradle"
"gradleInitScript"
"lockFile"
"fetchers"
"nativeBuildInputs"
"overlays"
"passthru"
]
);
in
buildGradlePackage

View File

@@ -2,16 +2,15 @@
lib, lib,
stdenv, stdenv,
fetchurl, fetchurl,
substitute,
symlinkJoin, symlinkJoin,
}: }:
{ {
# Path to the lockfile generated by gradle2nix (e.g. gradle.lock). # Path to the lockfile generated by gradle2nix (e.g. gradle.lock).
lockFile, lockFile,
pname ? "project",
version ? null,
# Override functions which fetch dependency artifacts. # Override functions which fetch dependency artifacts.
# Keys in this set are URL schemes such as "https" or "s3". # Names in this set are URL schemes such as "https" or "s3".
# Values are functions which take a dependency in the form # Values are functions which take a dependency in the form
# `{ urls, hash }` and fetch into the Nix store. For example: # `{ urls, hash }` and fetch into the Nix store. For example:
# #
@@ -28,42 +27,50 @@
# }; # };
# } # }
fetchers ? { }, fetchers ? { },
# Overlays for dependencies in the offline Maven repository. # Override artifacts in the offline Maven repository.
# #
# Acceps an attrset of dependencies (usually parsed from 'lockFile'), and produces an attrset # This is an attrset is of the form:
# containing dependencies to merge into the final set.
#
# The attrset is of the form:
# #
# { # {
# "${group}:${module}:${version}" = <derivation>; # "${group}:${module}:${version}" = {
# # ... # "${filename}" = <override function>;
# }
# } # }
# #
# A dependency derivation unpacks multiple source files into a single Maven-style directory named # The override function takes the original derivation from 'fetchers' (e.g. the result of
# "${out}/${groupPath}/${module}/${version}/", where 'groupPath' is the dependency group ID with dot # 'fetchurl') and produces a new derivation to replace it.
# characters ('.') replaced by the path separator ('/').
# #
# Examples: # Examples:
# #
# 1. Add or replace a dependency with a single JAR file: # 1. Replace a dependency's JAR artifact:
# #
# (_: { # {
# "com.squareup.okio:okio:3.9.0" = fetchurl { # "com.squareup.okio:okio:3.9.0"."okio-3.9.0.jar" = _: fetchurl {
# url = "https://repo.maven.apache.org/maven2/com/squareup/okio/okio/3.9.0/okio-3.9.0.jar"; # url = "https://repo.maven.apache.org/maven2/com/squareup/okio/okio/3.9.0/okio-3.9.0.jar";
# hash = "..."; # hash = "...";
# downloadToTemmp = true; # downloadToTemp = true;
# postFetch = "install -Dt $out/com/squareup/okio/okio/3.9.0/ $downloadedFile" # postFetch = "install -Dt $out/com/squareup/okio/okio/3.9.0/ $downloadedFile"
# }; # };
# }) # }
# #
# 2. Remove a dependency entirely: # 2. Patch a JAR containing native binaries:
# #
# # This works because the result is filtered for values that are derivations. # {
# (_: { # "com.android.tools.build:aapt2:8.5.0-rc02-11315950" = {
# "org.apache.log4j:core:2.23.1" = null; # "aapt2-8.5.0-rc02-11315950-linux.jar" = src: runCommandCC src.name {
# }) # nativeBuildInputs = [ jdk autoPatchelfHook ];
overlays ? [ ], # dontAutoPatchelf = true;
# } ''
# cp ${src} aapt2.jar
# jar xf aapt2.jar aapt2
# chmod +x aapt2
# autoPatchelf aapt2
# jar uf aapt2.jar aapt2
# cp aapt2.jar $out
# '';
# }
# }
overrides ? { },
}: }:
let let
@@ -116,25 +123,28 @@ let
} // fetchers; } // fetchers;
fetch = fetch =
name: coords: overrides: name:
{ url, hash }: { url, hash }:
let let
scheme = head (builtins.match "([a-z0-9+.-]+)://.*" url); scheme = head (builtins.match "([a-z0-9+.-]+)://.*" url);
fetch' = getAttr scheme fetchers'; fetch' = getAttr scheme fetchers';
artifact = fetch' { inherit url hash; };
override = overrides.name or lib.id;
in in
fetch' { inherit url hash; }; override artifact;
mkModule = mkModule =
id: artifacts: id: artifacts:
let let
coords = toCoordinates id; coords = toCoordinates id;
modulePath = "${replaceStrings [ "." ] [ "/" ] coords.group}/${coords.module}/${coords.version}"; modulePath = "${replaceStrings [ "." ] [ "/" ] coords.group}/${coords.module}/${coords.version}";
moduleOverrides = overrides.${id} or { };
in in
stdenv.mkDerivation { stdenv.mkDerivation {
pname = sanitizeDerivationName "${coords.group}-${coords.module}"; pname = sanitizeDerivationName "${coords.group}-${coords.module}";
version = coords.uniqueVersion; version = coords.uniqueVersion;
srcs = mapAttrsToList fetch artifacts; srcs = mapAttrsToList (fetch coords moduleOverrides) artifacts;
dontPatch = true; dontPatch = true;
dontConfigure = true; dontConfigure = true;
@@ -156,34 +166,24 @@ let
allowSubstitutes = false; allowSubstitutes = false;
}; };
# Intermediate dependency spec. modulePaths =
#
# We want to allow overriding dependencies via the 'dependencies' function,
# so we pass an intermediate set that maps each Maven coordinate to the
# derivation created with 'mkModule'. This allows users extra flexibility
# to do things like patching native libraries with patchelf or replacing
# artifacts entirely.
lockedDependencies =
final:
if lockFile == null then
{ }
else
let let
lockedDependencySpecs = fromJSON (readFile lockFile); dependencies = fromJSON (readFile lockFile);
modules = mapAttrs mkModule dependencies;
in in
mapAttrs mkModule lockedDependencySpecs; filter lib.isDerivation (attrValues modules);
finalDependencies = mavenRepo = symlinkJoin {
let name = "gradle-maven-repo";
composedExtension = lib.composeManyExtensions overlays; paths = modulePaths;
extended = lib.extends composedExtension lockedDependencies; passthru.gradleInitScript = substitute {
fixed = lib.fix extended; src = ./init.gradle;
in substitutions = [
filter lib.isDerivation (attrValues fixed); "--replace"
"@mavenRepo@"
offlineRepo = symlinkJoin { "${mavenRepo}"
name = if version != null then "${pname}-${version}-gradle-repo" else "${pname}-gradle-repo"; ];
paths = finalDependencies; };
}; };
in in
offlineRepo mavenRepo

42
nix/init.gradle Normal file
View File

@@ -0,0 +1,42 @@
import org.gradle.util.GradleVersion
static boolean versionAtLeast(String version) {
return GradleVersion.current() >= GradleVersion.version(version)
}
static void configureRepos(RepositoryHandler repositories) {
repositories.configureEach { ArtifactRepository repo ->
if (repo instanceof MavenArtifactRepository) {
repo.setArtifactUrls(new HashSet<URI>())
repo.url 'file:@mavenRepo@'
repo.metadataSources {
gradleMetadata()
mavenPom()
artifact()
}
} else if (repo instanceof IvyArtifactRepository) {
repo.url 'file:@mavenRepo@'
repo.layout('maven')
repo.metadataSources {
gradleMetadata()
ivyDescriptor()
artifact()
}
} else if (repo instanceof UrlArtifactRepository) {
repo.url 'file:/homeless-shelter'
}
}
}
beforeSettings { settings ->
configureRepos(settings.pluginManagement.repositories)
configureRepos(settings.buildscript.repositories)
if (versionAtLeast("6.8")) {
configureRepos(settings.dependencyResolutionManagement.repositories)
}
}
beforeProject { project ->
configureRepos(project.buildscript.repositories)
configureRepos(project.repositories)
}

125
nix/setup-hook.sh Normal file
View File

@@ -0,0 +1,125 @@
# shellcheck shell=bash disable=SC2206,SC2155
gradleConfigurePhase() {
runHook preConfigure
if ! [[ -v enableParallelBuilding ]]; then
enableParallelBuilding=1
echo "gradle: enabled parallel building"
fi
if ! [[ -v enableParallelChecking ]]; then
enableParallelChecking=1
echo "gradle: enabled parallel checking"
fi
if ! [[ -v enableParallelInstalling ]]; then
enableParallelInstalling=1
echo "gradle: enabled parallel installing"
fi
export GRADLE_USER_HOME="$(mktemp -d)"
if [ -n "$gradleInitScript" ]; then
if [ ! -f "$gradleInitScript" ]; then
echo "gradleInitScript is not a file path: $gradleInitScript"
exit 1
fi
mkdir -p "$GRADLE_USER_HOME/init.d"
ln -s "$gradleInitScript" "$GRADLE_USER_HOME/init.d"
fi
runHook postConfigure
}
gradleBuildPhase() {
runHook preBuild
if [ -z "${gradleBuildFlags:-}" ] && [ -z "${gradleBuildFlagsArray[*]}" ]; then
echo "gradleBuildFlags is not set, doing nothing"
else
local flagsArray=(
$gradleFlags "${gradleFlagsArray[@]}"
$gradleBuildFlags "${gradleBuildFlagsArray[@]}"
)
if [ -n "$enableParallelBuilding" ]; then
flagsArray+=(--parallel --max-workers ${NIX_BUILD_CORES})
else
flagsArray+=(--no-parallel)
fi
echoCmd 'gradleBuildPhase flags' "${flagsArray[@]}"
gradle "${flagsArray[@]}"
fi
runHook postBuild
}
gradleCheckPhase() {
runHook preCheck
if [ -z "${gradleCheckFlags:-}" ] && [ -z "${gradleCheckFlagsArray[*]}" ]; then
echo "gradleCheckFlags is not set, doing nothing"
else
local flagsArray=(
$gradleFlags "${gradleFlagsArray[@]}"
$gradleCheckFlags "${gradleCheckFlagsArray[@]}"
${gradleCheckTasks:-check}
)
if [ -n "$enableParallelChecking" ]; then
flagsArray+=(--parallel --max-workers ${NIX_BUILD_CORES})
else
flagsArray+=(--no-parallel)
fi
echoCmd 'gradleCheckPhase flags' "${flagsArray[@]}"
gradle "${flagsArray[@]}"
fi
runHook postCheck
}
gradleInstallPhase() {
runHook preInstall
if [ -z "${gradleInstallFlags:-}" ] && [ -z "${gradleInstallFlagsArray[*]}" ]; then
echo "gradleInstallFlags is not set, doing nothing"
else
local flagsArray=(
$gradleFlags "${gradleFlagsArray[@]}"
$gradleInstallFlags "${gradleInstallFlagsArray[@]}"
)
if [ -n "$enableParallelInstalling" ]; then
flagsArray+=(--parallel --max-workers ${NIX_BUILD_CORES})
else
flagsArray+=(--no-parallel)
fi
echoCmd 'gradleInstallPhase flags' "${flagsArray[@]}"
gradle "${flagsArray[@]}"
fi
runHook postInstall
}
if [ -z "${dontUseGradleConfigure-}" ] && [ -z "${configurePhase-}" ]; then
configurePhase=gradleConfigurePhase
fi
if [ -z "${dontUseGradleBuild-}" ] && [ -z "${buildPhase-}" ]; then
buildPhase=gradleBuildPhase
fi
if [ -z "${dontUseGradleCheck-}" ] && [ -z "${checkPhase-}" ]; then
checkPhase=gradleCheckPhase
fi
if [ -z "${dontUseGradleInstall-}" ] && [ -z "${installPhase-}" ]; then
installPhase=gradleInstallPhase
fi

View File

@@ -1,7 +1,8 @@
package org.nixos.gradle2nix package org.nixos.gradle2nix
abstract class Gradle2NixPlugin : AbstractGradle2NixPlugin( abstract class Gradle2NixPlugin :
AbstractGradle2NixPlugin(
GradleCacheAccessFactoryBase, GradleCacheAccessFactoryBase,
DependencyExtractorApplierBase, DependencyExtractorApplierBase,
ResolveAllArtifactsApplierBase, ResolveAllArtifactsApplierBase,
) )

View File

@@ -4,12 +4,12 @@ import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.invocation.Gradle import org.gradle.api.invocation.Gradle
object GradleCacheAccessFactoryBase : GradleCacheAccessFactory { object GradleCacheAccessFactoryBase : GradleCacheAccessFactory {
override fun create(gradle: Gradle): GradleCacheAccess { override fun create(gradle: Gradle): GradleCacheAccess = GradleCacheAccessBase(gradle)
return GradleCacheAccessBase(gradle)
}
} }
class GradleCacheAccessBase(gradle: Gradle) : GradleCacheAccess { class GradleCacheAccessBase(
gradle: Gradle,
) : GradleCacheAccess {
private val artifactCachesProvider = gradle.service<ArtifactCachesProvider>() private val artifactCachesProvider = gradle.service<ArtifactCachesProvider>()
override fun useCache(block: () -> Unit) { override fun useCache(block: () -> Unit) {

View File

@@ -116,22 +116,27 @@ private fun cachedComponentId(file: File): DependencyCoordinates? {
val parts = file.invariantSeparatorsPath.split('/') val parts = file.invariantSeparatorsPath.split('/')
if (parts.size < 6) return null if (parts.size < 6) return null
if (parts[parts.size - 6] != "files-2.1") return null if (parts[parts.size - 6] != "files-2.1") return null
return parts.dropLast(2).takeLast(3).joinToString(":").let(DefaultDependencyCoordinates::parse) return parts
.dropLast(2)
.takeLast(3)
.joinToString(":")
.let(DefaultDependencyCoordinates::parse)
} }
@OptIn(ExperimentalSerializationApi::class) @OptIn(ExperimentalSerializationApi::class)
private fun parseFileMappings(file: File): Map<String, String>? = private fun parseFileMappings(file: File): Map<String, String>? =
try { try {
Json.decodeFromStream<JsonObject>(file.inputStream()) Json
.jsonObject["variants"]?.jsonArray .decodeFromStream<JsonObject>(file.inputStream())
.jsonObject["variants"]
?.jsonArray
?.flatMap { it.jsonObject["files"]?.jsonArray ?: emptyList() } ?.flatMap { it.jsonObject["files"]?.jsonArray ?: emptyList() }
?.map { it.jsonObject } ?.map { it.jsonObject }
?.mapNotNull { ?.mapNotNull {
val name = it["name"]?.jsonPrimitive?.content ?: return@mapNotNull null val name = it["name"]?.jsonPrimitive?.content ?: return@mapNotNull null
val url = it["url"]?.jsonPrimitive?.content ?: return@mapNotNull null val url = it["url"]?.jsonPrimitive?.content ?: return@mapNotNull null
if (name != url) name to url else null if (name != url) name to url else null
} }?.toMap()
?.toMap()
?.takeUnless { it.isEmpty() } ?.takeUnless { it.isEmpty() }
} catch (e: Throwable) { } catch (e: Throwable) {
null null

View File

@@ -17,11 +17,10 @@ class DependencySetModelBuilder(
override fun buildAll( override fun buildAll(
modelName: String, modelName: String,
project: Project, project: Project,
): DependencySet { ): DependencySet =
return dependencyExtractor.buildDependencySet( dependencyExtractor.buildDependencySet(
cacheAccess, cacheAccess,
checksumService, checksumService,
fileStoreAndIndexProvider, fileStoreAndIndexProvider,
) )
}
} }

View File

@@ -38,14 +38,15 @@ abstract class AbstractResolveAllArtifactsApplier : ResolveAllArtifactsApplier {
abstract class ResolveProjectDependenciesTask : DefaultTask() { abstract class ResolveProjectDependenciesTask : DefaultTask() {
@Internal @Internal
protected fun getReportableConfigurations(): List<Configuration> { protected fun getReportableConfigurations(): List<Configuration> =
return project.configurations.filter { (it as? DeprecatableConfiguration)?.canSafelyBeResolved() ?: true } project.configurations.filter {
(it as? DeprecatableConfiguration)?.canSafelyBeResolved() ?: true
} }
protected fun Configuration.artifactFiles(): FileCollection { protected fun Configuration.artifactFiles(): FileCollection =
return incoming.artifactView { viewConfiguration -> incoming
.artifactView { viewConfiguration ->
viewConfiguration.isLenient = true viewConfiguration.isLenient = true
viewConfiguration.componentFilter { it is ModuleComponentIdentifier } viewConfiguration.componentFilter { it is ModuleComponentIdentifier }
}.files }.files
}
} }

View File

@@ -18,10 +18,12 @@ object DependencyExtractorApplierG8 : DependencyExtractorApplier {
extractor: DependencyExtractor, extractor: DependencyExtractor,
) { ) {
val serviceProvider = val serviceProvider =
gradle.sharedServices.registerIfAbsent( gradle.sharedServices
.registerIfAbsent(
"nixDependencyExtractor", "nixDependencyExtractor",
DependencyExtractorService::class.java, DependencyExtractorService::class.java,
) {}.map { service -> ) {}
.map { service ->
service.apply { this.extractor = extractor } service.apply { this.extractor = extractor }
} }
@@ -31,7 +33,9 @@ object DependencyExtractorApplierG8 : DependencyExtractorApplier {
@Suppress("UnstableApiUsage") @Suppress("UnstableApiUsage")
internal abstract class DependencyExtractorService : internal abstract class DependencyExtractorService :
BuildService<BuildServiceParameters.None>, BuildOperationListener, AutoCloseable { BuildService<BuildServiceParameters.None>,
BuildOperationListener,
AutoCloseable {
var extractor: DependencyExtractor? = null var extractor: DependencyExtractor? = null
override fun started( override fun started(

View File

@@ -23,13 +23,12 @@ abstract class ResolveProjectDependenciesTaskG8
) : ResolveProjectDependenciesTask() { ) : ResolveProjectDependenciesTask() {
private val artifactFiles = Cached.of { artifactFiles() } private val artifactFiles = Cached.of { artifactFiles() }
private fun artifactFiles(): FileCollection { private fun artifactFiles(): FileCollection =
return objects.fileCollection().from( objects.fileCollection().from(
getReportableConfigurations().map { configuration -> getReportableConfigurations().map { configuration ->
configuration.artifactFiles() configuration.artifactFiles()
}, },
) )
}
@TaskAction @TaskAction
fun action() { fun action() {

View File

@@ -1,7 +1,8 @@
package org.nixos.gradle2nix package org.nixos.gradle2nix
abstract class Gradle2NixPlugin : AbstractGradle2NixPlugin( abstract class Gradle2NixPlugin :
AbstractGradle2NixPlugin(
GradleCacheAccessFactoryG80, GradleCacheAccessFactoryG80,
DependencyExtractorApplierG8, DependencyExtractorApplierG8,
ResolveAllArtifactsApplierG8, ResolveAllArtifactsApplierG8,
) )

View File

@@ -4,12 +4,12 @@ import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.invocation.Gradle import org.gradle.api.invocation.Gradle
object GradleCacheAccessFactoryG80 : GradleCacheAccessFactory { object GradleCacheAccessFactoryG80 : GradleCacheAccessFactory {
override fun create(gradle: Gradle): GradleCacheAccess { override fun create(gradle: Gradle): GradleCacheAccess = GradleCacheAccessG80(gradle)
return GradleCacheAccessG80(gradle)
}
} }
class GradleCacheAccessG80(gradle: Gradle) : GradleCacheAccess { class GradleCacheAccessG80(
gradle: Gradle,
) : GradleCacheAccess {
private val artifactCachesProvider = gradle.service<ArtifactCachesProvider>() private val artifactCachesProvider = gradle.service<ArtifactCachesProvider>()
override fun useCache(block: () -> Unit) { override fun useCache(block: () -> Unit) {

View File

@@ -1,7 +1,8 @@
package org.nixos.gradle2nix package org.nixos.gradle2nix
abstract class Gradle2NixPlugin : AbstractGradle2NixPlugin( abstract class Gradle2NixPlugin :
AbstractGradle2NixPlugin(
GradleCacheAccessFactoryG81, GradleCacheAccessFactoryG81,
DependencyExtractorApplierG8, DependencyExtractorApplierG8,
ResolveAllArtifactsApplierG8, ResolveAllArtifactsApplierG8,
) )

View File

@@ -4,12 +4,12 @@ import org.gradle.api.internal.artifacts.ivyservice.ArtifactCachesProvider
import org.gradle.api.invocation.Gradle import org.gradle.api.invocation.Gradle
object GradleCacheAccessFactoryG81 : GradleCacheAccessFactory { object GradleCacheAccessFactoryG81 : GradleCacheAccessFactory {
override fun create(gradle: Gradle): GradleCacheAccess { override fun create(gradle: Gradle): GradleCacheAccess = GradleCacheAccessG81(gradle)
return GradleCacheAccessG81(gradle)
}
} }
class GradleCacheAccessG81(gradle: Gradle) : GradleCacheAccess { class GradleCacheAccessG81(
gradle: Gradle,
) : GradleCacheAccess {
private val artifactCachesProvider = gradle.service<ArtifactCachesProvider>() private val artifactCachesProvider = gradle.service<ArtifactCachesProvider>()
override fun useCache(block: () -> Unit) { override fun useCache(block: () -> Unit) {