211 lines
8.6 KiB
Nix
211 lines
8.6 KiB
Nix
/*
|
|
A set of tools for generating node packages, such as to be imported by
|
|
default.nix files generated by nixfromnpm.
|
|
*/
|
|
|
|
{
|
|
# Self-reference so that we can pass through to downstream libraries
|
|
self,
|
|
# Base set of packages, i.e. nixpkgs.
|
|
pkgs,
|
|
# Version of nodejs.
|
|
nodejsVersion ? "4.1",
|
|
# Whether to use npm3 (requires a prebuilt tarball of npm3).
|
|
npm3 ? true
|
|
}:
|
|
|
|
let
|
|
# Function to replace dots with something
|
|
replaceDots = c: replaceChars ["."] [c];
|
|
inherit (builtins) readDir removeAttrs length getEnv elemAt hasAttr;
|
|
inherit (pkgs.lib) attrNames attrValues filterAttrs flip foldl
|
|
hasSuffix hasPrefix removeSuffix replaceChars
|
|
optional optionals stringToCharacters
|
|
concatStrings tail splitString;
|
|
inherit (pkgs.stdenv) isLinux;
|
|
|
|
# Function to remove the first character of a string.
|
|
dropFirstChar = str: concatStrings (tail (stringToCharacters str));
|
|
|
|
# Like a for loop.
|
|
for = flip map;
|
|
|
|
# Concatenate a list of sets.
|
|
joinSets = foldl (a: b: a // b) {};
|
|
|
|
# Extracts a tarball containing a bootstrapped version of npm 3.
|
|
# This tarball must have been previously generated by an invocation
|
|
# of nixfromnpm, but one of these should be included in the
|
|
# nixfromnpm distribution (if not, run the `gen_npm3` script).
|
|
npm3-src = pkgs.runCommand "npm3" {src=./npm3.tar.gz;} ''
|
|
mkdir -p $out && cd $out && tar -xf $src
|
|
'';
|
|
|
|
# Builds the extracted nix file. Since of course it can't use npm3,
|
|
# being that it hasn't been built yet, we disable npm3 for this.
|
|
_npm3 = import npm3-src {
|
|
inherit pkgs nodejsVersion;
|
|
npm3 = false;
|
|
};
|
|
|
|
# Parse the `NPM_AUTH_TOKENS` environment variable to discover
|
|
# namespace-token associations and turn them into an attribute set
|
|
# which we can use as an input to the fetchPrivateNpm function.
|
|
# Split the variable on ':', then turn each k=v element in
|
|
# the list into an attribute set and join all of those sets.
|
|
namespaceTokens = joinSets (
|
|
for (splitString ":" (getEnv "NPM_AUTH_TOKENS")) (kvPair:
|
|
let kv = splitString "=" kvPair; in
|
|
if length kv != 2 then {}
|
|
else {"${elemAt kv 0}" = elemAt kv 1;}));
|
|
|
|
# A function similar to fetchUrl but allows setting of custom headers.
|
|
fetchUrlWithHeaders = pkgs.callPackage ./fetchUrlWithHeaders.nix {};
|
|
|
|
# Uses the parsed namespace tokens to create a function that can
|
|
# fetch a private package from an npm repo.
|
|
fetchPrivateNpm = {namespace, headers ? {}, ...}@args:
|
|
if !(hasAttr namespace namespaceTokens)
|
|
then throw "NPM_AUTH_TOKENS does not contain namespace ${namespace}"
|
|
else let
|
|
Authorization = "Bearer ${namespaceTokens.${namespace}}";
|
|
headers = {inherit Authorization;} // headers;
|
|
in
|
|
fetchUrlWithHeaders (removeAttrs args ["namespace"] // {inherit headers;});
|
|
in
|
|
|
|
rec {
|
|
nodejs = pkgs."nodejs-${replaceDots "_" nodejsVersion}" or (
|
|
throw "The given nodejs version ${nodejsVersion} has not been defined."
|
|
);
|
|
buildNodePackage = import ./buildNodePackage.nix ({
|
|
inherit (pkgs) stdenv runCommand;
|
|
inherit nodejs buildNodePackage;
|
|
neededNatives = [pkgs.python] ++ optionals isLinux [pkgs.utillinux];
|
|
} // (if npm3 then {npm = _npm3;} else {}));
|
|
# A generic package that will fail to build. This is used to indicate
|
|
# packages that are broken, without failing the entire generation of
|
|
# a package expression.
|
|
brokenPackage = {name, reason}:
|
|
let
|
|
deriv = pkgs.stdenv.mkDerivation {
|
|
name = "BROKEN-${name}";
|
|
buildCommand = ''
|
|
echo "Package ${name} is broken: ${reason}"
|
|
exit 1
|
|
'';
|
|
passthru.withoutTests = deriv;
|
|
passthru.pkgName = name;
|
|
passthru.basicName = "BROKEN";
|
|
passthru.namespace = null;
|
|
passthru.version = "BROKEN";
|
|
passthru.override = _: deriv;
|
|
passthru.recursiveDeps = [];
|
|
passthru.peerDependencies = {};
|
|
};
|
|
in
|
|
deriv;
|
|
|
|
# List a directory after filtering the files.
|
|
lsFilter = pred: dir: attrNames (filterAttrs pred (readDir dir));
|
|
|
|
# Checks the name and type of a listing to grab non-dotfile dirs.
|
|
isRegDir = name: type: type == "directory" && !(hasPrefix "." name);
|
|
|
|
# Discover all of the node packages in a folder and turn them into a set
|
|
# mapping `<name>_<version>` to the expression to build that package.
|
|
discoverPackages = {callPackage, rootPath}:
|
|
# if true then throw "huh? ${rootPath}" else
|
|
let
|
|
# Names of NPM packages defined in this directory. Don't take
|
|
# files that start with '@'.
|
|
nodeDirs = lsFilter (n: t: isRegDir n t && !(hasPrefix "@" n))
|
|
(/. + rootPath);
|
|
# Generate the package expression from a package name and .nix path.
|
|
toPackage = name: filepath: let
|
|
versionRaw = removeSuffix ".nix" filepath; # Raw version, i.e. "1.2.4"
|
|
# Join with package name to make the variable name.
|
|
varName = "${replaceDots "-" name}_${replaceDots "-" versionRaw}";
|
|
in
|
|
# Return the singleton set which maps that name to the actual expression.
|
|
{"${varName}" = callPackage (/. + rootPath + "/${name}/${filepath}") {};};
|
|
in
|
|
# For each directory, and each .nix file in it, create a package from that.
|
|
joinSets (for nodeDirs (pkgName: let
|
|
pkgDir = /. + rootPath + "/${pkgName}";
|
|
# List of .nix files in the directory (excluding symlinks).
|
|
versionFiles = lsFilter (name: type: type == "regular" &&
|
|
hasSuffix ".nix" name)
|
|
pkgDir;
|
|
# Check if there is a `latest.nix` file
|
|
hasLatest = lsFilter (n: _: n == "latest.nix") pkgDir != [];
|
|
in
|
|
joinSets (
|
|
# Find all of the versions listed in the folder.
|
|
map (toPackage pkgName) versionFiles ++
|
|
# If the folder has a `latest.nix` file, link the bare name of
|
|
# the package to that file.
|
|
optional hasLatest {
|
|
"${replaceDots "-" pkgName}" = callPackage
|
|
(/. + rootPath + "/${pkgName}/latest.nix") {};
|
|
})));
|
|
|
|
# Same as above, except that we take all of the namespaced packages;
|
|
# these packages are in folders prefaced with `@`, and contain
|
|
# packages in that folder. So, for example the path `@foo/bar` is
|
|
# the path to all of the versions of the `bar` package under the
|
|
# namespace `foo`.
|
|
discoverNamespacePackages = {callPackage, rootPath}: let
|
|
isNsDir = name: type: type == "directory" && hasPrefix "@" name;
|
|
# Names of NPM packages defined in this directory.
|
|
namespaceDirs = lsFilter isNsDir (/. + rootPath);
|
|
in
|
|
# For each namespace directory, each package folder in it, and
|
|
# each .nix file in that, create a package from that and then
|
|
# create a namespace out of that.
|
|
joinSets (for namespaceDirs (nsDirName: {
|
|
"${dropFirstChar nsDirName}" = discoverPackages {
|
|
inherit callPackage;
|
|
rootPath = /. + rootPath + "/${nsDirName}";
|
|
};
|
|
}));
|
|
|
|
# The function that a default.nix can call into which will scan its
|
|
# directory for all of the package files and generate a big attribute set
|
|
# for all of them. Re-exports the `callPackage` function and all of the
|
|
# attribute sets, as well as the nodeLib.
|
|
generatePackages = {rootPath, extensions ? []}:
|
|
let
|
|
callPackageWith = pkgSet: path: overridingArgs: let
|
|
inherit (builtins) intersectAttrs functionArgs;
|
|
inherit (pkgs.lib) filterAttrs;
|
|
# The path must be a function; import it here.
|
|
func = import path;
|
|
# Get the arguments to the function; e.g. "{a=false; b=true;}", where
|
|
# a false value is an argument that has no default.
|
|
funcArgs = functionArgs func;
|
|
# Take only the arguments that don't have a default.
|
|
noDefaults = filterAttrs (_: v: v == false) funcArgs;
|
|
# Intersect this set with the package set to create the arguments to
|
|
# the function.
|
|
satisfyingArgs = intersectAttrs noDefaults pkgSet;
|
|
# Override these arguments with whatever's passed in.
|
|
actualArgs = satisfyingArgs // overridingArgs;
|
|
# Call the function with these args to get a derivation.
|
|
deriv = func actualArgs;
|
|
in deriv;
|
|
|
|
callPackage = callPackageWith {
|
|
inherit fetchUrlWithHeaders namespaces namespaceTokens;
|
|
inherit pkgs nodePackages buildNodePackage brokenPackage;
|
|
};
|
|
nodePackages = joinSets (map (e: e.nodePackages) extensions) //
|
|
discoverPackages {inherit callPackage rootPath;};
|
|
namespaces = joinSets (map (e: e.namespaces) extensions) //
|
|
discoverNamespacePackages {inherit callPackage rootPath;};
|
|
in {
|
|
inherit nodePackages callPackage namespaces namespaceTokens pkgs;
|
|
nodeLib = self;
|
|
};
|
|
}
|