-
Notifications
You must be signed in to change notification settings - Fork 39
/
default.nix
253 lines (220 loc) · 8.86 KB
/
default.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
{ writeShellScriptBin, writeText, runCommand, writeScriptBin, stdenv, lib
, fetchurl, fetchgit, makeWrapper, nodejs, yarn, jq, gnutar }:
with lib;
let
inherit (builtins) fromJSON toJSON split removeAttrs replaceStrings toFile;
nodeSources = runCommand "node-sources" {} ''
tar --no-same-owner --no-same-permissions -xf ${nodejs.src}
mv node-* $out
'';
depsToFetches = deps: concatMap depToFetch (attrValues deps);
makeTarball = name: path: stdenv.mkDerivation {
inherit name;
buildInputs = [ gnutar ];
phases = [ "packPhase" ];
packPhase = ''
cp -R "${path}" package
chmod -R +w package
tar --format=posix -caf $out -C package .
'';
};
depFetchOwn = { resolved, integrity, name ? null, rev ? null, version ? null, ... }:
let
bname = baseNameOf resolved;
fname = if hasSuffix ".tgz" bname || hasSuffix ".tar.gz" bname then
# If name is @author/lib, then tarball is expected to be @author-bname.tar.gz
if ! isNull name && substring 0 1 name == "@"
then elemAt (splitString "/" name) 0 + "-" + bname
# If name doesn't start with @, then the tarball is expected to be bname.tar.gz
else bname
else
# If bname isn't a string that has '*.tar.gz' format
# (which is true for direct git dependencies or dependencies from github),
# a yarn offline cache archive for the corresponding dependency is expected to be tarball with 'bname' name
# without extension >:(
# Note that such dependencies also don't have integrity and it has to be provided manually
# in 'integreties' attribute of 'buildYarnPackage'
replaceStrings ["#"] ["-"] bname;
# 'rev' is set only for direct git dependencies
path = if ! isNull rev
then let
gitURI = replaceStrings ["git+"] [""] resolved;
protocol = elemAt (splitString "://" gitURI) 0;
path = replaceStrings [":"] ["/"] (elemAt (splitString "://" gitURI) 1);
in
makeTarball fname (builtins.fetchGit {
url = protocol + "://" + path;
rev = rev;
})
else fetchurl {
name = fname;
url = resolved;
hash = integrity;
};
in nameValuePair resolved {
inherit name fname integrity path;
};
depToFetch = args@{ resolved ? null, dependencies ? { }, ... }:
(optional (resolved != null) (depFetchOwn args))
++ (depsToFetches dependencies);
cacheInput = oFile: iFile:
writeText oFile (toJSON (depToFetch iFile));
patchShebangs = writeShellScriptBin "patchShebangs.sh" ''
set -e
source ${stdenv}/setup
patchShebangs "$@"
'';
npmInfo = src: rec {
info = fromJSON (readFile (src + "/package.json"));
pname = info.name or "unknown-node-package";
version = info.version or "unknown";
};
npmModules = "${nodejs}/lib/node_modules/npm/node_modules";
shellWrap = writeShellScriptBin "npm-shell-wrap.sh" ''
set -e
pushd ''${PWD%%node_modules*}/node_modules
${patchShebangs}/bin/patchShebangs.sh .
popd
exec bash "$@"
'';
commonEnv = {
XDG_CONFIG_DIRS = ".";
NO_UPDATE_NOTIFIER = true;
installJavascript = true;
};
commonBuildInputs = [ nodejs makeWrapper ]; # TODO: git?
unScope = replaceStrings [ "@" "/" ] [ "" "-" ];
# unpack the .tgz into output directory and add npm wrapper
# TODO: "cd $out" vs NIX_NPM_BUILDPACKAGE_OUT=$out?
untarAndWrap = name: cmds: ''
shopt -s nullglob
mkdir -p $out/bin
tar xzvf ${unScope name}.tgz -C $out --strip-components=1
if [ "$installJavascript" = "1" ]; then
cp -R node_modules $out/
patchShebangs $out/bin
for i in $out/bin/*.js; do
makeWrapper ${nodejs}/bin/node $out/bin/$(basename $i .js) \
--add-flags $i --run "cd $out"
done
${
concatStringsSep ";" (map (cmd:
''
makeWrapper ${cmd} $out/bin/${
baseNameOf cmd
} --run "cd $out" --prefix PATH : ${stdenv.shellPackage}/bin'')
cmds)
}
fi
'';
in rec {
mkNodeModules = { src, extraEnvVars ? { }, pname, version, buildInputs ? [] }:
let
packageJson = src + "/package.json";
packageLockJson = src + "/package-lock.json";
info = fromJSON (readFile packageJson);
lock = fromJSON (readFile packageLockJson);
in
assert asserts.assertMsg (versionAtLeast nodejs.version "10.20.0") "nix-npm-buildPackages requires at least npm v6.13.5";
# TODO: this *could* work with some more debugging
assert asserts.assertMsg (versionAtLeast nodejs.version "16" -> lock.lockfileVersion >= 2) "node v16 requires lockfile v2 (run npm once)";
# TODO: lock file version 3
assert asserts.assertMsg (lock.lockfileVersion <= 2) "nix-npm-buildPackage doesn't support this lock file version";
stdenv.mkDerivation ({
name = "${pname}-${version}-node-modules";
buildInputs = [ nodejs jq ] ++ buildInputs;
npm_config_cache = "./npm-cache";
npm_config_nodejs = "${nodejs}";
npm_config_offline = true;
npm_config_script_shell = "${shellWrap}/bin/npm-shell-wrap.sh";
npm_config_update_notifier = false;
buildCommand = ''
# Inside nix-build sandbox $HOME points to a non-existing
# directory, but npm may try to create this directory (e.g.
# when you run `npm install` or `npm prune`) and will succeed
# if you have a single-user nix installation (because / is
# writable in this case), causing different behavior for
# single-user and multi-user nix. Set $HOME to a read-only
# directory to fix it
export HOME=$(mktemp -d)
chmod a-w "$HOME"
# do not run the toplevel lifecycle scripts, we only do dependencies
cp ${toFile "package.json" (builtins.toJSON (info // { scripts = { }; }))} ./package.json
cp ${toFile "package-lock.json" (builtins.toJSON lock)} ./package-lock.json
echo 'building npm cache'
chmod u+w ./package-lock.json
addToSearchPath NODE_PATH ${npmModules} # ssri
node ${./mknpmcache.js} ${cacheInput "npm-cache-input.json" lock}
echo 'building node_modules'
npm --nodedir=${nodeSources} ci
echo 'patching shebangs'
patchShebangs ./node_modules/
mkdir $out
mv ./node_modules $out/
# add npm-cache because npm prune wants to change some pkgs
mv ./npm-cache $out/
'';
} // extraEnvVars);
buildNpmPackage = args@{ src, npmBuild ? ''
# this is what npm runs by default, only run when it exists
${jq}/bin/jq -e '.scripts.prepublish' package.json >/dev/null && npm run prepublish
${jq}/bin/jq -e '.scripts.prepare' package.json >/dev/null && npm run prepare
'', buildInputs ? [ ], extraEnvVars ? { }
, extraNodeModulesArgs ? {}
, # environment variables passed through to `npm ci`
... }:
let
inherit (npmInfo src) pname version;
nodeModules = mkNodeModules ({
inherit src extraEnvVars pname version;
} // extraNodeModulesArgs);
in
assert asserts.assertMsg (!(args ? packageOverrides)) "buildNpmPackage-packageOverrides is no longer supported";
stdenv.mkDerivation ({
inherit pname version;
configurePhase = ''
runHook preConfigure
export HOME=$(mktemp -d)
chmod a-w "$HOME"
# npm prune actually installs some packages sometimes
cp -RL --no-preserve=mode "${nodeModules}/npm-cache" "$PWD/npm-cache"
export npm_config_cache="$PWD/npm-cache"
if [[ -e ./node_modules ]]; then
echo 'WARNING: node_modules directory already exists, removing it'
rm -rf ./node_modules
fi
patchShebangs .
cp --reflink=auto -r ${nodeModules}/node_modules ./node_modules
chmod -R u+w ./node_modules
runHook postConfigure
'';
buildPhase = ''
runHook preBuild
${npmBuild}
runHook postBuild
'';
dontNpmPrune = false;
installPhase = ''
runHook preInstall
if [ -z "''${dontNpmPrune-}" ]; then
echo "running npm prune --production"
npm prune --production
fi
npm pack --ignore-scripts
${untarAndWrap "${pname}-${version}" [ "${nodejs}/bin/npm" ]}
runHook postInstall
'';
npm_config_offline = true;
npm_config_update_notifier = false;
passthru = { inherit nodeModules; };
} // commonEnv // extraEnvVars
// removeAttrs args [ "extraEnvVars" "extraNodeModulesArgs" ] // {
buildInputs = commonBuildInputs ++ buildInputs;
passthru = { inherit nodeModules; } // (args.passthru or {});
});
buildYarnPackage = import ./buildYarnPackage.nix {
inherit lib npmInfo runCommand fetchurl npmModules writeScriptBin nodejs
yarn patchShebangs writeText stdenv untarAndWrap depToFetch commonEnv
makeWrapper writeShellScriptBin unScope;
};
}