yarn2nix-0.10.1: nix-lib/default.nix
{ lib, pkgs
# TODO: temporary, to make overwriting yarn2nix easy
# TODO: remove static building once RPATHs are fixed
, yarn2nix ? pkgs.haskell.lib.justStaticExecutables
pkgs.haskellPackages.yarn2nix
}:
let
# Build an attrset of node dependencies suitable for the `nodeBuildInputs`
# argument of `buildNodePackage`. The input is an overlay
# of node packages that call `_buildNodePackage`, like in the
# files generated by `yarn2nix`.
# It is possible to just call it with a generated file, like so:
# `buildNodeDeps (pkgs.callPackage ./npm-deps.nix {})`
# You can also use `lib.composeExtensions` to override packages
# in the set:
# ```
# buildNodeDeps (lib.composeExtensions
# (pkgs.callPackage ./npm-deps.nix {})
# (self: super: { pkg = super.pkg.override {…}; }))
# ```
# TODO: should _buildNodePackage be fixed in here?
buildNodeDeps = nodeDeps: lib.fix
(lib.extends
nodeDeps
(self: {
# The actual function building our packages.
# type: { key: String | { scope: String, name: String }
# , <other arguments of ./buildNodePackage.nix> }
# Wraps the invocation in the fix point, to construct the
# list of { key, drv } needed by buildNodePackage
# from the templates.
# It is basically a manual paramorphism, carrying parts of the
# information of the previous layer (the original package key).
# TODO: move that function out of the package set
# and get nice self/super scoping right
_buildNodePackage = { key, ... }@args:
# To keep the generated files shorter, we allow keys to
# be represented as strings if they have no scopes.
# This is the only place where this is accepted,
# but hacky nonetheless. Probably fix with above TODO.
let key' = if builtins.isString key
then { scope = ""; name = key; }
else key;
in { key = key';
drv = buildNodePackage (args // { key = key'; }); };
}));
# Build a package template generated by the `yarn2nix --template`
# utility from a yarn package. The first input is the path to the
# template nix file, the second input is all node dependencies
# needed by the template, in the form generated by `buildNodeDeps`.
callTemplate = yarn2nixTemplate: allDeps:
pkgs.callPackage yarn2nixTemplate {} allDeps;
buildNodePackage = import ./buildNodePackage.nix {
inherit linkNodeDeps yarn2nix;
inherit (pkgs) stdenv lib nodejs;
};
# Link together a `node_modules` folder that can be used
# by npm’s module system to call dependencies.
# Also link executables of all dependencies into `.bin`.
# TODO: copy manpages & docs as well
# type: { name: String
# , dependencies: ListOf { key: { scope: String, name: String }
# , drv : Drv } }
# -> Drv
linkNodeDeps = {name, dependencies}:
pkgs.runCommand ("${name}-node_modules") {
# This just creates a simple link farm, which should be pretty fast,
# saving us from additional hydra requests for potentially hundreds
# of packages.
allowSubstitutes = false;
# Also tell Hydra it’s not worth copying to a builder.
preferLocalBuild = true;
} ''
mkdir -p $out/.bin
${lib.concatMapStringsSep "\n"
(dep:
let
hasScope = dep.key.scope != "";
# scoped packages get another subdirectory for their scope (`@scope/`)
parentfolder = if hasScope
then "$out/@${dep.key.scope}"
else "$out";
subfolder = "${parentfolder}/${dep.key.name}";
in ''
echo "linking node dependency ${formatKey dep.key}"
${ # we need to create the scope folder, otherwise ln fails
lib.optionalString hasScope ''mkdir -p "${parentfolder}"'' }
ln -sT ${dep.drv} "${subfolder}"
${yarn2nix}/bin/node-package-tool \
link-bin \
--to=$out/.bin \
--package=${subfolder}
'')
dependencies}
'';
# Filter out files/directories with one of the given prefix names
# from the given path.
# type: ListOf File -> Path -> Drv
removePrefixes = prfxs: path:
let
hasPrefix = file: prfx: lib.hasPrefix ((builtins.toPath path) + "/" + prfx) file;
hasAnyPrefix = file: lib.any (hasPrefix file) prfxs;
in
builtins.filterSource (file: _: ! (hasAnyPrefix file)) path;
# `callYarnLock` calls `yarn2nix` to generate a nix representation of
# a `yarn.lock` file and directly imports it. It uses `yarn2nix`'s
# offline mode, so its resolving capabilities are limited, i. e. git
# dependencies are not possible.
#
# Example usage:
#
# ```
# buildNodeDeps (callYarnLock ./yarn.lock {})
# ```
callYarnLock = yarnLock: { name ? "yarn.lock.nix" }:
pkgs.callPackage (pkgs.runCommand name {
# faster to build locally, see also note at linkNodeDeps
allowSubstitutes = false;
preferLocalBuild = true;
} ''
${yarn2nix}/bin/yarn2nix --offline ${yarnLock} > $out
'') { };
# `callPackageJson` calls `yarn2nix --template` to generate a
# nix representation of a `package.json` and directly imports
# it. It returns a function which expects a dependency attrset
# like `callYarnLock` generates.
#
# Example usage:
#
# ```
# let template = callPackageJson ./package.json {};
# in buildNodePackage ({ src = ./.; } //
# template (callYarnLock ./yarn.lock {}))
# ```
callPackageJson = packageJson: { name ? "package.json.nix" }:
pkgs.callPackage (pkgs.runCommand name {
# faster to build locally, see also note at linkNodeDeps
allowSubstitutes = false;
preferLocalBuild = true;
} ''
${yarn2nix}/bin/yarn2nix --template ${packageJson} > $out
'') {};
# format a package key of { scope: String, name: String }
formatKey = { scope, name }:
if scope == ""
then name
else "@${scope}/${name}";
in {
inherit buildNodeDeps linkNodeDeps buildNodePackage
callTemplate removePrefixes callYarnLock
callPackageJson;
}