buildGradleApplication
is a Nix builder function for packaging Gradle applications.
buildGradleApplication
drastically simplifies the integration of Gradle projects in the Nix ecosystem by defining a set of rules/constraints that a Gradle project must follow.
- For now, the focus is on packaging Gradle applications, not libraries.
- Using the builder function should feel idiomatic to Nix. It should provide the same experience as
buildPythonPackage
orbuildPerlPackage
but with fewer options. - The rules imposed on the Gradle build should be idiomatic to Gradle and ideally promote Gradle best practices.
- Support automatic updates with tools such as renovate.
- All dependencies (jars) should be packaged into discrete derivations (and linked in the final result) to facilitate efficient deployments and layered OIC images.
- This project should be small and simple.
buildGradleApplication
is not a general purpose solution for building arbitrary Gradle projects. If you want to do that, check out gradle2nix instead.- Do not try to replicate Gradle's behaviour, e.g. to construct a runtime classpath. Instead, use the Gradle built-ins to produce these results.
- Android. But if you have experience in Android, talk to me! It might not be that hard to support android instead (by breaking/adopting Rule #5).
Using Gradles built-in Mechanism for dependency verification is not only a security best practice, but also allows buildGradleApplication
to fetch an fixed version (as a fixed-output derivations) of a dependency and its metadata.
While it should be straight forward to generate a verification-metadata.xml
file by following the documentation, take extra care that Gradle version and JDK version align! This should not be a problem when using Nix for your development environment.
Once such a verification-metadata.xml
file exists, Gradle will refuse to download anything not mentioned in it. This can lead to issues when using an IDE such as IntelliJ IDEA which might attempt to download javadoc and source artifacts not listed in the verification-metadata.xml
file. You can automatically trust all javadocs/sources as follows:
<!-- gradle/verification-metadata.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<verification-metadata xmlns="https://schema.gradle.org/dependency-verification" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://schema.gradle.org/dependency-verification https://schema.gradle.org/dependency-verification/dependency-verification-1.2.xsd">
<configuration>
<verify-metadata>true</verify-metadata>
<verify-signatures>false</verify-signatures>
<trusted-artifacts>
<!-- See https://youtrack.jetbrains.com/issue/IDEA-258328 -->
<trust file=".*-javadoc[.]jar" regex="true"/>
<trust file=".*-sources[.]jar" regex="true"/>
<trust file="gradle-[0-9.]+-src.zip" regex="true"/>
<trust file="groovy-[a-z]*-?[0-9.]+.pom" regex="true"/>
</trusted-artifacts>
</configuration>
<components>
<!-- ... -->
</components>
</verification-metadata>
Here is an example command to let Gradle add all dependency artifacts to your verification-metadata.xml
:
gradle --refresh-dependencies --write-verification-metadata sha256 --write-locks dependencies
Gradle not remove any artefacts from the verification-metadata.xml
even if they are not used anymore. This can lead to . The updateVerificationMetadata
package from this flake can be used to re-generate the file while keeping the <configuration>
section. You must ensure that the Gradle version and JDK version align.
update-verification-metadata
Note: Renovate can and will re-generate this file when updating dependencies - Yay 🎉
buildGradleApplication
only supports Maven repositories to fetch dependencies. Ivy is not supported.
Nix uses a sandbox which prevents internet access during build time (for a good reason). All other (implicit) build dependencies must be provided via Nix instead. buildGradleApplication
takes care of downloading and providing the Maven dependencies. Everything else is specific to your build and must be handled by you.
Let's take the gradle-node
plugin as an example. It can be configured to download and install a specific version of Node.js. This will fail for the reason given above. Instead, provide Node.js as nativeBuildInput
instead:
buildGradleApplication {
# ...
nativeBuildInputs = [pkgs.nodejs];
}
Because Nix uses a sandbox which prevents internet access during build time, buildGradleApplication
needs to pre fetch all required artifacts. These are then made available to the offline build using a local maven repository. The location of this repository depends on it's contents and is provided to the Gradle build via the MAVEN_SOURCE_REPOSITORY
Environment Variable.
It's a Gradle best practice to centralize repositories declarations.
buildGradleApplication
assumes that all repository declarations are located in your settings.gradle(.kts)
files. It will then replace these declarations during build time with the location of the offline repository (using a Gradle init script)
Here is an example of how your Gradle build should declare it's repositories:
// settings.gradle.kts
pluginManagement {
repositories {
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
mavenCentral()
}
// Highly recommended, see https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:centralized-repository-declaration
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
}
Note that repository declarations must be defined for each included build as well.
Also Note that buildGradleApplication
is (currently) unable to extract the declared repositories from your Gradle build. If you use different or additional repositories, you must provide it to buildGradleApplication
using the repositories
parameter:
buildGradleApplication {
# ...
repositories = ["https://plugins.gradle.org/m2/" "https://repo1.maven.org/maven2/" "https://example.com/maven2/"];
}
Currently, the focus of this tool is to package Gradle applications. In order to launch a java application, we need both an main class as an entry point and a runtime classpath. The latter must contain both third-party dependencies fetched from a maven repository and project local libraries generated from other projects within the same build.
Gradle provides exactly that (a so called Distribution) with the built in application
plugin. The required configuration is quite reasonable:
plugins {
application
// ...
}
application {
mainClass.set("org.gradle.sample.Main")
}
// ...
Checkout the application
plugin documentation for any further details.
Gradle has a few switches to make builds more reproducible. These must be set to ensure proper reproducibility of the genereated .jar
files.
// Important: This configuration is probably not complete for your project!
tasks.withType<AbstractArchiveTask>().configureEach {
isPreserveFileTimestamps = false
isReproducibleFileOrder = true
dirPermissions { unix("755") }
filePermissions { unix("644") }
}
Alternatively, you might use the Reproducible Builds plugin to achive the same.
Gradle's dependency resolution can be unstable in the following cases:
- dynamic dependency versions are used (version ranges, latest.release, 1.+, ...)
- Changing versions (SNAPSHOTs, fixed version with changing contents, ...)
The recommended way to use buildGradleApplication
is to prevent the use of non reproducible dependencies:
configurations.all {
resolutionStrategy {
failOnNonReproducibleResolution()
}
}
If you must use these features (please, don't!), use dependency locking.
For more details, see the "Making sure resolution is reproducible" section in the Gradle Docs.
{
inputs = {
build-gradle-application.url = "github:raphiz/buildGradleApplication";
# ...
};
# ...
outputs = {
nixpkgs,
build-gradle-application,
...
}: {
# ...
pkgs = import nixpkgs {
inherit system;
overlays = [build-gradle-application.overlays.default];
};
# ...
};
}
The usage of buildGradleApplication
should be straight forward once your build follows the outlined rules above. Here is a very minimal example:
# package.nix
{
lib,
version,
buildGradleApplication,
}:
buildGradleApplication {
pname = "hello-world";
version = version;
src = ./.;
meta = with lib; {
description = "Hello World Application";
};
}
For further examples, checkout the example repository
All available parameters of buildGradleApplication
are documented in the source code
The recommended way to execute any Gradle build is with the help of the Gradle Wrapper. It's main motivations are to have a standardised version per project and to make it easy to deploy in different execution environments. When using Nix, these motivations are largely obsolete.
There may still be reasons to use the wrapper even when using Nix. In these cases, it's inconvenient to keep both versions (nix and wrapper) in sync.
To simplify this case, you can use the url and checksum from the gradle-wrapper.properties
file to build exactly the same gradle package with the gradleFromWrapper
builder function:
gradle = pkgs.gradleFromWrapper {
wrapperPropertiesPath = ./gradle/wrapper/gradle-wrapper.properties;
};
NOTE: This utility only works with nixpkgs 24.11 and above, since it is based on changes made to gradleGen
in this PR.
Sadly, many Maven repositories contain the same artifacts but with different metadata. One such example is the Kotlin JVM Gradle Plugin (Maven Central vs. gradle plugin portal). To work around this, buildGradleApplication
uses a special fetchArtifact
builder instead of the classic fetchurl
fetchers. fetchArtifact
will try to download a given artifact with a given hash from all provided urls. If the checksum of the downloaded artifact differs from the expected one, it is quietly ignored and the next url is tried instead.
The first build with buildGradleApplication
might be very slow. The reason for this is, that each maven artifact is a dedicated derivation and derivations are not built in parallel by default.
You can speed up the first build by enabling concurrent builds, for example:
nix build -j 15
Feel free to create an issue or submit a pull request.
Feedback is also very welcome!
buildGradleApplication
is licensed under the MIT License.