diff --git a/pkgs/development/web/nodejs/build-node-package.nix b/pkgs/development/web/nodejs/build-node-package.nix index 7d166a87e6a..048b53f0005 100644 --- a/pkgs/development/web/nodejs/build-node-package.nix +++ b/pkgs/development/web/nodejs/build-node-package.nix @@ -1,22 +1,30 @@ { stdenv, runCommand, nodejs, neededNatives}: { - name, src, + name, version ? "", src, # Node package name - pkgName ? (builtins.parseDrvName name).name, + pkgName ? + if version != "" then stdenv.lib.removeSuffix "-${version}" name else + (builtins.parseDrvName name).name, # List or attribute set of dependencies deps ? {}, # List or attribute set of peer depencies - peerDependencies ? [], + peerDependencies ? {}, + + # List or attribute set of optional dependencies + optionalDependencies ? {}, + + # List of optional dependencies to skip + skipOptionalDependencies ? [], # Whether package is binary or library - bin ? null, + bin ? false, - # Flags passed to npm install - flags ? [], + # Additional flags passed to npm install + flags ? "", # Command to be run before shell hook preShellHook ? "", @@ -24,6 +32,12 @@ # Command to be run after shell hook postShellHook ? "", + # Same as https://docs.npmjs.com/files/package.json#os + os ? [], + + # Same as https://docs.npmjs.com/files/package.json#cpu + cpu ? [], + # Attribute set of already resolved deps (internal), # for avoiding infinite recursion resolvedDeps ? {}, @@ -34,42 +48,67 @@ with stdenv.lib; let - npmFlags = concatStringsSep " " (map (v: "--${v}") flags); - - sources = runCommand "node-sources" {} '' - tar --no-same-owner --no-same-permissions -xf ${nodejs.src} - mv $(find . -type d -mindepth 1 -maxdepth 1) $out - ''; - - # Convert deps to attribute set - attrDeps = if isAttrs deps then deps else - (listToAttrs (map (dep: nameValuePair dep.name dep) deps)); - - # All required node modules, without already resolved dependencies - requiredDeps = removeAttrs attrDeps (attrNames resolvedDeps); - - # Recursive dependencies that we want to avoid with shim creation - recursiveDeps = removeAttrs attrDeps (attrNames requiredDeps); - - peerDeps = filter (dep: dep.pkgName != pkgName) peerDependencies; - self = let - # Pass resolved dependencies to dependencies of this package - deps = map ( - dep: dep.override { - resolvedDeps = resolvedDeps // { "${name}" = self; }; - } - ) (attrValues requiredDeps); + sources = runCommand "node-sources" {} '' + tar --no-same-owner --no-same-permissions -xf ${nodejs.src} + mv $(find . -type d -mindepth 1 -maxdepth 1) $out + ''; + + platforms = fold (entry: platforms: + let + filterPlatforms = attrByPath [(removePrefix "!" entry)] [] stdenv.lib.platforms; + in + if hasPrefix "!" entry then + filter (p: any (f: p != f) filterPlatforms) platforms + else + filter (p: any (f: p == f) filterPlatforms) platforms + ) nodejs.meta.platforms os; + + mapDependencies = deps: f: rec { + # Convert deps to attribute set + attrDeps = if isAttrs deps then deps else + (listToAttrs (map (dep: nameValuePair dep.name dep) deps)); + + # All required node modules, without already resolved dependencies + # Also override with already resolved dependencies + requiredDeps = mapAttrs (name: dep: + dep.override { + resolvedDeps = resolvedDeps // { "${name}" = self; }; + } + ) (filterAttrs f (removeAttrs attrDeps (attrNames resolvedDeps))); + + # Recursive dependencies that we want to avoid with shim creation + recursiveDeps = filterAttrs f (removeAttrs attrDeps (attrNames requiredDeps)); + }; + + _dependencies = mapDependencies deps (name: dep: + dep.pkgName != pkgName); + _optionalDependencies = mapDependencies optionalDependencies (name: dep: + any (platform: stdenv.system == platform) dep.meta.platforms && + all (d: d != dep.pkgName) skipOptionalDependencies + ); + _peerDependencies = mapDependencies peerDependencies (name: dep: + dep.pkgName != pkgName); + + requiredDependencies = + _dependencies.requiredDeps // + _optionalDependencies.requiredDeps // + _peerDependencies.requiredDeps; + + recursiveDependencies = + _dependencies.recursiveDeps // + _optionalDependencies.recursiveDeps // + _peerDependencies.recursiveDeps; patchShebangs = dir: '' - node=`type -p node` - coffee=`type -p coffee || true` - find -L ${dir} -type f -print0 | xargs -0 grep -Il . | \ - xargs sed --follow-symlinks -i \ - -e 's@#!/usr/bin/env node@#!'"$node"'@' \ - -e 's@#!/usr/bin/env coffee@#!'"$coffee"'@' \ - -e 's@#!/.*/node@#!'"$node"'@' \ - -e 's@#!/.*/coffee@#!'"$coffee"'@' || true + node=`type -p node` + coffee=`type -p coffee || true` + find -L ${dir} -type f -print0 | xargs -0 grep -Il . | \ + xargs sed --follow-symlinks -i \ + -e 's@#!/usr/bin/env node@#!'"$node"'@' \ + -e 's@#!/usr/bin/env coffee@#!'"$coffee"'@' \ + -e 's@#!/.*/node@#!'"$node"'@' \ + -e 's@#!/.*/coffee@#!'"$coffee"'@' || true ''; in stdenv.mkDerivation ({ @@ -147,9 +186,9 @@ let # We do not handle shrinkwraps yet rm npm-shrinkwrap.json 2>/dev/null || true - mkdir build-dir + mkdir ../build-dir ( - cd build-dir + cd ../build-dir mkdir node_modules # Symlink or copy dependencies for node modules @@ -161,12 +200,7 @@ let '' else '' cp -R ${dep}/lib/node_modules/${dep.pkgName} node_modules/ '' - ) deps} - - # Symlink peer dependencies - ${concatMapStrings (dep: '' - ln -sv ${dep}/lib/node_modules/${dep.pkgName} node_modules/ - '') peerDeps} + ) (attrValues requiredDependencies)} # Create shims for recursive dependenceies ${concatMapStrings (dep: '' @@ -177,10 +211,10 @@ let "version": "${getVersion dep}" } EOF - '') (attrValues recursiveDeps)} + '') (attrValues recursiveDependencies)} ) - export HOME=$PWD/build-dir + export HOME=$PWD/../build-dir runHook postConfigure ''; @@ -189,14 +223,14 @@ let # If source was a file, repackage it, so npm pre/post publish hooks are not triggered, if [[ -f $src ]]; then - tar --exclude='build-dir' -czf build-dir/package.tgz ./ + GZIP=-1 tar -czf ../build-dir/package.tgz ./ export src=$HOME/package.tgz else export src=$PWD fi # Install package - (cd $HOME && npm --registry http://www.example.com --nodedir=${sources} install $src ${npmFlags}) + (cd $HOME && npm --registry http://www.example.com --nodedir=${sources} install $src --fetch-retries 0 ${flags}) runHook postBuild ''; @@ -211,7 +245,7 @@ let ${concatMapStrings (dep: '' rm node_modules/${dep.pkgName}/package.json rmdir node_modules/${dep.pkgName} - '') (attrValues recursiveDeps)} + '') (attrValues recursiveDependencies)} mkdir -p $out/lib/node_modules @@ -230,10 +264,10 @@ let done fi - # Symlink dependencies + # Move peer dependencies to node_modules ${concatMapStrings (dep: '' mv node_modules/${dep.pkgName} $out/lib/node_modules - '') peerDeps} + '') (attrValues _peerDependencies.requiredDeps)} # Install binaries and patch shebangs mv node_modules/.bin $out/lib/node_modules 2>/dev/null || true @@ -256,23 +290,36 @@ let mkdir -p node_modules ${concatMapStrings (dep: '' ln -sfv ${dep}/lib/node_modules/${dep.pkgName} node_modules/ - '') deps} + '') (attrValues requiredDependencies)} ${postShellHook} ''; + # Stipping does not make a lot of sense in node packages + dontStrip = true; + + meta = { + platforms = platforms; + maintainers = [ stdenv.lib.maintainers.offline ]; + }; + passthru.pkgName = pkgName; - } // (filterAttrs (n: v: n != "deps" && n != "resolvedDeps") args) // { + } // (filterAttrs (n: v: all (k: n != k) ["deps" "resolvedDeps" "optionalDependencies"]) args) // { name = "${nodejs.interpreterName}-${name}"; # Run the node setup hook when this package is a build input propagatedNativeBuildInputs = (args.propagatedNativeBuildInputs or []) ++ [ nodejs ]; - # Make buildNodePackage useful with --run-env - nativeBuildInputs = (args.nativeBuildInputs or []) ++ deps ++ peerDependencies ++ neededNatives; + nativeBuildInputs = + (args.nativeBuildInputs or []) ++ neededNatives ++ + (attrValues requiredDependencies); # Expose list of recursive dependencies upstream, up to the package that # caused recursive dependency - recursiveDeps = (flatten (map (d: remove name d.recursiveDeps) deps)) ++ (attrNames recursiveDeps); + recursiveDeps = + (flatten ( + map (dep: remove name dep.recursiveDeps) (attrValues requiredDependencies) + )) ++ + (attrNames recursiveDependencies); }); in self