Generate Nix expressions which build Gradle-based projects.
Table of contents
Why?
Nix is an OS-agnostic package manager, a language-agnostic build system, and a bespoke programming language. One of its unique features is that it is purely functional; a "package" is a function which accepts inputs (source code, configuration, etc) and produces an output (binaries, a Java JAR, documentation, really anything).
One benefit of a functional build system is reproducibility. If you specify your inputs precisely, and take care not to introduce impurities—such as files retrieved over a network without tracking their content—you will receive, byte-for-byte, the exact output as someone else running the same function over the same inputs.
Gradle is not a functional build system. Most Gradle-based projects will produce highly variable outputs depending on a host of impure inputs, including:
- The JVM hosting the build
- The Gradle installation running the build
- Any usage of dynamic version constraints for dependencies
- SNAPSHOT dependencies
- Environment variables and command-line options
- Artifacts cached on the system hosting the build
gradle2nix helps to solve this problem by leveraging Nix to
control the most common inputs to a Gradle build. When run on a
project, it will record all dependencies for both the build
environment (including plugins and buildscript blocks) and the
project, and provide a Nix expression to run the build given these
dependencies. The build itself is then run in a sandbox, where only
content-tracked network requests are allowed to fetch dependencies,
and a local Maven repository is created on-the-fly to host the
dependency artifacts somewhere Gradle can resolve them without a
network.
This tool is useful for both development and packaging. You can use
gradle2nix to:
- Create isolated and reproducible development environments that work anywhere Nix itself can run.
- Reduce or eliminate flakiness and maintenance headaches from CI/CD pipelines.
- Distribute a recipe which can reliably build a Gradle project in repositories such as the Nix Package Collection.
Installation
A Nix expression (generated by gradle2nix
itself) is provided for convenience. The following expression will
fetch and build the latest version of this package:
import (fetchTarball "https://github.com/tadfisher/gradle2nix/archive/master.tar.gz") {}
If this expression is in, say, gradle2nix.nix, gradle2nix can be
built and placed in ./result with the following:
nix build -f gradle2nix.nix
You can also use the following one-liners to build or install
gradle2nix in your user profile:
# Build and place in ./result/ nix build -f "https://github.com/tadfisher/gradle2nix/archive/master.tar.gz" # Build and install in the user profile nix-env -if "https://github.com/tadfisher/gradle2nix/archive/master.tar.gz"
gradle2nix is not yet packaged in nixpkgs itself, but work is
in progress.
The buildGradlePackage function
is provided via the gradle2nix.passthru.buildGradlePackage
attribute.
{ pkgs ? import <nixpkgs> {} }:
let
gradle2nix = import (fetchTarball "https://github.com/tadfisher/gradle2nix/archive/master.tar.gz") {}
in
gradle2nix.buildGradlePackage {
pname = "my-package";
version = "1.0";
lockFile = ./gradle.lock;
gradleInstallFlags = [ "installDist" ];
# ...
}
Flake
A flake.nix is provided for those using
Nix flakes. For example, the
following will build and run gradle2nix with the arguments
provided after --:
nix run github:tadfisher/gradle2nix -- --help
The buildGradlePackage function
is provided via the builders output.
{
inputs.gradle2nix.url = "github:tadfisher/gradle2nix";
outputs = { self, gradle2nix }: {
packages.x86_64-linux.default = gradle2nix.builders.x86_64-linux.buildGradlePackage {
pname = "my-package";
version = "1.0";
lockFile = ./gradle.lock;
gradleInstallFlags = [ "installDist" ];
# ...
};
};
}
Usage
Usage: gradle2nix [<options>] [<args>]...
Gradle installation:
Where to find Gradle. By default, use the project's wrapper.
--gradle-dist=<uri> Gradle distribution URI
--gradle-home=<dir> Gradle home path (e.g. `nix eval --raw nixpkgs#gradle.outPath`/lib/gradle)
--gradle-wrapper=<value> Gradle wrapper version
Options:
-t, --task=<task> Gradle tasks to run (default: [resolveAllArtifacts])
-a, --artifacts=<artifacts> Comma-separated list of artifacts to download (artifacts:
doxygen,javadoc,samples,sources,usermanual) (default: [])
-p, --project=<path> Path to the project root (default: Current directory)
-o, --out-dir=<dir> Path to write generated files (default: <project>)
-l, --lock-file=<filename> Name of the generated lock file (default: gradle.lock)
-j, --gradle-jdk=<dir> JDK home to use for launching Gradle (e.g. `nix eval --raw
nixpkgs#openjdk.home`)
--log=(debug|info|warn|error) Print messages with this priority or higher (default: info)
--dump-events Dump Gradle event logs to the output directory
--stacktrace Print a stack trace on error
-h, --help Show this message and exit
Arguments:
<args> Extra arguments to pass to Gradle
Simply running gradle2nix in the root directory of a project
should be enough for most projects. This will produce a lock file,
by default called gradle.lock, which contains the pinned
dependencies for the project. An example of a build expression using
this lock file can be found in this project's
default.nix.
For packagers
If you're creating a Nix package for an existing Gradle project, you
can reduce the number of pinned dependencies by passing one or more
--task arguments. This will only pin the dependencies that were
resolved as part of the build, instead of the default behavior where
all possible dependencies are pinned.
For example, if the package produces its build output via the
:app:installDist task, use the following:
gradle2nix -t :app:installDist
Note: This may be required if the build resolves configurations at execution time.
Specifying the Gradle installation
By default, if the project has configured the Gradle wrapper, it will be used; otherwise, the version of Gradle used to build gradle2nix will be used. You can override this to use any of the following:
# Gradle distribution URL: gradle2nix --gradle-dist='https://services.gradle.org/distributions/gradle-8.7-bin.zip' # Path to a local Gradle installation: gradle2nix --gradle-home=`nix eval nixpkgs#gradle.outPath`/lib/gradle # A specific wrapper version: gradle2nix --gradle-wrapper=8.7
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:
- Applies gradleSetupHook, overriding the
required
gradlepackage if specified. - Builds the offline Maven repository with buildMavenRepo.
- Sets the JDK used to run Gradle if specified.
- Applies the offline repo to the Gradle build using an initialization script.
- Source: 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). -
gradle - 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 detailed documentation below.
-
overrides - Override artifacts in the offline Maven repository. See detailed documentation below.
In addition, this function accepts:
- All arguments to
stdenv.mkDerivation. - Arguments specific to
gradleSetupHook(see 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: 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 detailed documentation below.
-
overrides - Override artifacts in the offline Maven repository. See detailed documentation below.
gradleSetupHook
A 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: setup-hook.sh
-
Location:
- Nix
gradle2nix.passthru.gradleSetupHook- Flake
packages.${system}.gradleSetupHook
Variables controlling gradleSetupHook
-
gradleInitScript - Path to an
initialization
script used by
gradleduring all phases. -
gradleFlags - Controls the arguments passed to
gradleduring all phases. -
gradleBuildFlags - Controls the arguments passed to
gradleduring the build phase. The build phase is skipped if this is unset. -
gradleCheckFlags - Controls the arguments passed to
gradleduring the check phase. The check phase is skipped if this is unset. -
gradleInstallFlags - Controls the arguments passed to
gradleduring 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 ofgradleInitScript. -
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.
enableParallelBuildingenableParallelCheckingenableParallelInstalling
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:
{
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";
};
};
}
overrides
This is an attrset 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.
-
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" }; } -
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 ''; }; }
Contributing
Bug reports and feature requests are encouraged.
Code contributions are also encouraged. Please review the test cases in the fixtures directory and create a new one to reproduce any fixes or test new features. See the existing tests for examples of testing with these fixtures.
License
gradle2nix is licensed under the Apache License 2.0.
