Files
gradle2nix/README.org
2024-05-29 17:51:45 -07:00

228 lines
7.9 KiB
Org Mode

#+STARTUP: inlineimages
[[./assets/gradle2nix.png]]
Generate [[https://nixos.org/nix/][Nix]] expressions which build [[https://gradle.org/][Gradle]]-based projects.
** Table of contents
#+BEGIN_QUOTE
- [[#why][Why?]]
- [[#installation][Installation]]
- [[#flake][Flake]]
- [[#usage][Usage]]
- [[#for-packagers][For packagers]]
- [[#specifying-the-gradle-installation][Specifying the Gradle installation]]
- [[#contributing][Contributing]]
- [[#license][License]]
#+END_QUOTE
** 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 [[https://reproducible-builds.org/][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 [[https://nixos.org/nixpkgs/][Nix Package Collection]].
** Installation
A [[./gradle.nix][Nix expression]] (generated by =gradle2nix= itself) is provided for
convenience. The following expression will fetch and build the latest
version of this package:
#+begin_src nix
import (fetchTarball "https://github.com/tadfisher/gradle2nix/archive/master.tar.gz") {}
#+end_src
If this expression is in, say, =gradle2nix.nix=, =gradle2nix= can be
built and placed in =./result= with the following:
#+begin_example
nix build -f gradle2nix.nix
#+end_example
You can also use the following one-liners to build or install
=gradle2nix= in your user profile:
#+begin_example
# 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"
#+end_example
=gradle2nix= is not yet packaged in =nixpkgs= itself, but work is
[[https://github.com/NixOS/nixpkgs/pull/77422][in progress]].
The [[./gradle.nix][buildGradlePackage]] function is provided via the
=gradle2nix.passthru.buildGradlePackage= attribute.
#+begin_src nix
{ 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;
gradleFlags = [ "installDist" ];
# ...
}
#+end_src
*** Flake
A [[./flake.nix][flake.nix]] is provided for those using [[https://nixos.wiki/wiki/Flakes][Nix flakes]]. For example, the
following will build and run =gradle2nix= with the arguments provided
after =--=:
#+begin_example
nix run github:tadfisher/gradle2nix -- --help
#+end_example
The [[./gradle.nix][buildGradlePackage]] function is provided via the
=builders= output.
#+begin_src nix
{
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;
gradleFlags = [ "installDist" ];
# ...
};
};
}
#+end_src
** Usage
#+begin_example
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
-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
#+end_example
Simply running =gradle2nix= in the root directory of a project should
be enough for most projects. This will produce two files, by default
called =gradle.lock= and =gradle.nix=, which contain the
pinned dependencies for the project and a standard build expression
which can be imported or called by other Nix expressions. An example
of such an expression can be found in this project's [[./gradle2nix.nix][gradle2nix.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:
#+begin_example
gradle2nix -t :app:installDist
#+end_example
/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:
#+begin_example
# 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
#+end_example
** Contributing
Bug reports and feature requests are encouraged.
[[https://github.com/tadfisher/gradle2nix/issues/new][Create an issue]]
Code contributions are also encouraged. Please review the test cases
in the [[./fixtures][fixtures]] directory and create a new one to reproduce any fixes
or test new features. See the [[./app/src/test/kotlin/org/nixos/gradle2nix/GoldenTest.kt][existing tests]]
for examples of testing with these fixtures.
** License
=gradle2nix= is licensed under the [[./COPYING][MIT License]].