I tried to summarize all the approaches to package applications which during their normal build process interact with online repos (Maven, NPM, Cargo, …) to compute the list of dependencies.
To package them with Nix many utilities have been created (yarn2nix
, crate2nix
, … referred as lang2nix
hereinnafter) which generate either Nix code or a bundle of dependencies using the source trees of those apps as input.
It seems that every of the known approaches receives good portion of critics and we still have no simple and convenient idea on how a deterministic builder like Nix should work with mutable online repositories.
(The text below supposes to be an initial version of a wiki page, but i’d like to receive some feedback; from the authors of the proposals and from people who may use other approaches in their private forks)
A. All deps are downloaded into a huge bundle, whose output is secured with additional hash cargoSha256
, mavenSha256
, … (widely used)
[-] inconvenient (and easy to forget) to update hash, especially when src
points to a local dir or fetchGitLocal
or peeks HEAD
of a development branch
[-] the bundles are typically big (100s megabytes)
[-] difficult to replace some of the downloaded deps with locally built ones (important for jars with executables (or .so) inside which do not run on NixOS)
[-] hashes drift over time, because Maven/Cargo/etc are mutable
[-] @edolstra had more arguments against “abusing fixed-output derivation” (Restrict fixed-output derivations · Issue #2270 · NixOS/nix · GitHub)
B. Keeping lang2nix
-generated .nix-file in <nixpkgs>
next to the derivation code (widely used)
[+] huge bundle is spitted to smaller artifacts, which can be shared between different projects and substituted with NixOS-aware versions
[-] inconvenient (and easy to forget) to manually run lang2nix
, especially when src
points to a local directory or fetchGitLocal
or peeks HEAD
of a development branch
C. Import from derivation, … (in use by HerculesCI and seen in some people’s public repositories)
stdenv.mkDerivation {
src = ...
buildInputs = import
(stdenv.mkDerivation {
inherit src;
# it produces nix file with content line `[ (fetchurl ...) (fetchgit ...) ]`
buildCommand = "lang2nix $src > $out";
});
}
[+] runs lang2nix
automatically when src
(or lang2nix
itself) is changed
[-] the inner derivation (with lang2nix
) must have network access, at least to Maven/Cargo/etc (or be fixed-output, but then it is the case A
), but allowing network access results in non-determinism because Maven/Cargo/etc are mutable and we can get different result on the next run.
[-] Import from derivation has some problem with distributed build (AFAIK, solved in HerculesCI)
D. recursive nix (nix-build
in nix-build
)
stdenv.mkDerivation {
src = ...
preBuild = ''
lang2nix $src > deps.nix
DEPS=$(nix-build deps.nix)
... try do adopt $DEPS
'';
}
[+] replaces IFD with a more distributed build-friendly approach
[-] still, lang2nix
must have network access, at least to Maven/Cargo/etc
E. @Ericson2314’s variant (nix-instantiate
in nix-build
[RFC 0092] Computed derivations by Ericson2314 · Pull Request #92 · NixOS/rfcs · GitHub)
Will it perform better than D
?
At first glance, no, but I might miss something, I did not grok CA yet.
F. C
, D
and E
would be more pure if we maintain (or convince upstream to do) immutable snapshots of online packages repositories and allow network access only to particular immutable snapshots from within non-fixed-output derivations.
There is already
- GitHub - DavHau/pypi-deps-db: Dependency DB for python packages on pypi and
- https://github.com/DavHau/conda-channels/tree/master/channels
snapshoting metadata of Python repositories on GitHub every 12 hours
H. Simply trying to minimize the drawbacks of the above methods, we can get something like lang2nix
-generated file next to derivation code, but generated automatically (have not seen yet, just an idea)
# pseudo-code, not tested
let
src = ...
depNix = stdenv.mkDerivation {
inherit src;
# it produces nix file with content line `[ (fetchurl ...) (fetchgit ...) ]`
buildCommand = "lang2nix $src > $out";
}
depFile =
let
# or <nixpkgs/.cache/> + "deps-${depNix.drvHash}.nix"
localFile = ./. + "deps-${depNix.drvHash}.nix";
in
if builtins.fileExists localFile then
# if generated file exists and matches `src`, it is exactly `B`
# there is no IFD nor `builtins.exec`
localFile
else
# if the file does not exist (or `src` changed)
# `lang2nix` will be run once at eval time under the eval user
# and added to `git`, just like the manual run of `lang2nix` in `B`
builtins.exec ''
src=${depNix.src} out=${toString localFile} ${depNix.buildCommand}
git add ${toString localFile}
echo '"${toString localFile}"'
'';
in
stdenv.mkDerivation {
inherit src;
buildInputs = import depFile;
}