{ 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 } # , } # 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; }