raphiz / buildGradleApplication

A Nix builder function for packaging Gradle applications.
MIT License
22 stars 2 forks source link
gradle nix nix-flake

buildGradleApplication

License

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.

Goals

Non-Goals

Rules

Rule #1: Requires Checksum Verification (verification-metadata.xml)

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 🎉

Rule #2: Maven Repositories Only

buildGradleApplication only supports Maven repositories to fetch dependencies. Ivy is not supported.

Rule #3: No Downloads

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];
}

Rule #4: Centralized Repository

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/"];
}

Rule #5: Using the application Plugin

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.

Rule #6: Tell gradle to be more reproducible

Gradle has a few switches to make builds more reproducible. These must be set to ensure proper reproducibility of the genereated .jar files.

tasks.withType<AbstractArchiveTask>().configureEach {
    isPreserveFileTimestamps = false
    isReproducibleFileOrder = true
}

Installation (via flakes)

{
  inputs = {
    build-gradle-application.url = "github:raphiz/buildGradleApplication";
    # ...
  };

  # ...
  outputs = {
    nixpkgs,
    build-gradle-application,
    ...
    }: {
        # ...
        pkgs = import nixpkgs {
          inherit system;
          overlays = [build-gradle-application.overlays.default];
        };
        # ...
    };
}

Usage

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

Additional Information

Maven Repositories are not Mirrors

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.

Very slow first build

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

Contributing

Feel free to create an issue or submit a pull request.

Feedback is also very welcome!

License

buildGradleApplication is licensed under the MIT License.