Skip to content

Commit

Permalink
rework dependency fetcher; add installEnv and noDevDependencies options
Browse files Browse the repository at this point in the history
  • Loading branch information
nzbr committed Jan 31, 2024
1 parent b88c960 commit c08830c
Show file tree
Hide file tree
Showing 3 changed files with 177 additions and 128 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ In addition to all arguments accepted by `stdenv.mkDerivation`, the `mkPnpmPacka
| `script` | The npm script that is executed | `build` |
| `distDir` | The directory that should be copied to the output | `dist` |
| `installInPlace` | Run `pnpm install` in the source directory instead of a separate derivation | `false` |
| `installEnv` | Environment variables that should be present during `pnpm install` | `{}` |
| `noDevDependencies` | Only download and install `dependencies`, not `devDependencies` | `false` |
| `extraNodeModuleSources` | Additional files that should be available during `pnpm install` | `[]` |
| `copyPnpmStore` | Copy the pnpm store into the build directory instead of linking it | `true` |
| `copyNodeModules` | Copy the `node_modules` into the build directory instead of linking it | `false` |
| `extraNodeModuleSources` | Additional files that should be available during `pnpm install` | `[]` |
| `extraBuildInputs` | Additional entries for `nativeBuildInputs` | `[]` |
128 changes: 69 additions & 59 deletions derivation.nix
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
, stdenv
, nodejs
, pkg-config
, yq
, callPackage
, writeShellScriptBin
, writeText
, runCommand
, ...
Expand All @@ -27,9 +25,11 @@ in
, script ? "build"
, distDir ? "dist"
, installInPlace ? false
, installEnv ? { }
, noDevDependencies ? false
, extraNodeModuleSources ? [ ]
, copyPnpmStore ? true
, copyNodeModules ? false
, extraNodeModuleSources ? [ ]
, extraBuildInputs ? [ ]
, nodejs ? nodePkg
, pnpm ? nodejs.pkgs.pnpm
Expand Down Expand Up @@ -78,76 +78,86 @@ in
installPhase = ''
runHook preInstall
mv ${distDir} $out
${if distDir == "." then "cp -r" else "mv"} ${distDir} $out
runHook postInstall
'';

passthru = {
inherit attrs;

patchedLockfile = patchLockfile pnpmLockYaml;
patchedLockfileYaml = writeText "pnpm-lock.yaml" (toJSON passthru.patchedLockfile);

pnpmStore = runCommand "${name}-pnpm-store"
{
nativeBuildInputs = [ nodejs pnpm ];
} ''
mkdir -p $out
store=$(pnpm store path)
mkdir -p $(dirname $store)
ln -s $out $(pnpm store path)
pnpm store add ${concatStringsSep " " (unique (dependencyTarballs { inherit registry; lockfile = pnpmLockYaml; }))}
'';

nodeModules = stdenv.mkDerivation {
name = "${name}-node-modules";
passthru =
let
processResult = processLockfile { inherit registry noDevDependencies; lockfile = pnpmLockYaml; };
in
{
inherit attrs;

inherit nativeBuildInputs;
patchedLockfile = processResult.patchedLockfile;
patchedLockfileYaml = writeText "pnpm-lock.yaml" (toJSON passthru.patchedLockfile);

unpackPhase = concatStringsSep "\n"
(
map
(v:
let
nv = if isAttrs v then v else { name = "."; value = v; };
in
"cp -vr ${nv.value} ${nv.name}"
)
([
{ name = "package.json"; value = packageJSON; }
{ name = "pnpm-lock.yaml"; value = passthru.patchedLockfileYaml; }
] ++ extraNodeModuleSources)
);

buildPhase = ''
export HOME=$NIX_BUILD_TOP # Some packages need a writable HOME
pnpmStore = runCommand "${name}-pnpm-store"
{
nativeBuildInputs = [ nodejs pnpm ];
} ''
mkdir -p $out
store=$(pnpm store path)
mkdir -p $(dirname $store)
ln -s $out $(pnpm store path)
cp -f ${passthru.patchedLockfileYaml} pnpm-lock.yaml
# solve pnpm: EACCES: permission denied, copyfile '/build/.pnpm-store
${if !copyPnpmStore
then "ln -s"
else "cp -RL"
} ${passthru.pnpmStore} $(pnpm store path)
${lib.optionalString copyPnpmStore "chmod -R +w $(pnpm store path)"}
pnpm install --frozen-lockfile --offline
pnpm store add ${concatStringsSep " " (unique processResult.dependencyTarballs)}
'';

installPhase = ''
cp -r node_modules/. $out
'';
nodeModules = stdenv.mkDerivation {
name = "${name}-node-modules";

inherit nativeBuildInputs;

unpackPhase = concatStringsSep "\n"
(
map
(v:
let
nv = if isAttrs v then v else { name = "."; value = v; };
in
"cp -vr ${nv.value} ${nv.name}"
)
([
{ name = "package.json"; value = packageJSON; }
{ name = "pnpm-lock.yaml"; value = passthru.patchedLockfileYaml; }
] ++ extraNodeModuleSources)
);

buildPhase = ''
export HOME=$NIX_BUILD_TOP # Some packages need a writable HOME
store=$(pnpm store path)
mkdir -p $(dirname $store)
cp -f ${passthru.patchedLockfileYaml} pnpm-lock.yaml
# solve pnpm: EACCES: permission denied, copyfile '/build/.pnpm-store
${if !copyPnpmStore
then "ln -s"
else "cp -RL"
} ${passthru.pnpmStore} $(pnpm store path)
${lib.optionalString copyPnpmStore "chmod -R +w $(pnpm store path)"}
${concatStringsSep "\n" (
mapAttrsToList
(n: v: ''export ${n}="${v}"'')
installEnv
)}
pnpm install ${optionalString noDevDependencies "--prod "}--frozen-lockfile --offline
'';

installPhase = ''
cp -r node_modules/. $out
'';
};
};
};

})
(attrs // { extraNodeModuleSources = null; })
(attrs // { extraNodeModuleSources = null; installEnv = null; })
);
}
173 changes: 105 additions & 68 deletions lockfile.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,81 +6,118 @@
}:

with lib;
let
splitVersion = name: splitString "@" (head (splitString "(" name));
getVersion = name: last (splitVersion name);
withoutVersion = name: concatStringsSep "@" (init (splitVersion name));
gitTarball = n: v:
let
repo =
if ((v.resolution.type or "") == "git")
then
fetchGit
{
url = v.resolution.repo;
rev = v.resolution.commit;
shallow = true;
}
else
let
split = splitString "/" v.id;
in
fetchGit {
url = "https://${concatStringsSep "/" (init split)}.git";
rev = (last split);
shallow = true;
};
in
# runCommand (last (init (traceValSeq (splitString "/" (traceValSeq (withoutVersion (traceValSeq n))))))) { } ''
runCommand "${last (init (splitString "/" (head (splitString "(" n))))}.tgz" { } ''
tar -czf $out -C ${repo} .
'';
in
rec {

parseLockfile = lockfile: builtins.fromJSON (readFile (runCommand "toJSON" { } "${remarshal}/bin/yaml2json ${lockfile} $out"));

dependencyTarballs = { registry, lockfile }:
unique (
mapAttrsToList
(n: v:
if hasPrefix "/" n then
let
name = withoutVersion n;
baseName = last (splitString "/" (withoutVersion n));
version = getVersion n;
in
fetchurl (
{
url = v.resolution.tarball or "${registry}/${name}/-/${baseName}-${version}.tgz";
} // (
if hasPrefix "sha1-" v.resolution.integrity then
{ sha1 = v.resolution.integrity; }
else
{ sha512 = v.resolution.integrity; }
)
)
else
gitTarball n v
)
(parseLockfile lockfile).packages
);

patchLockfile = lockfile:
processLockfile = { registry, lockfile, noDevDependencies }:
let
orig = parseLockfile lockfile;
in
orig // {
packages = mapAttrs
(n: v:
if hasPrefix "/" n
then v
else v // {
resolution.tarball = "file:${gitTarball n v}";
splitVersion = name: splitString "@" (head (splitString "(" name));
getVersion = name: last (splitVersion name);
withoutVersion = name: concatStringsSep "@" (init (splitVersion name));
switch = options:
if ((length options) == 0)
then throw "No matching case found!"
else
if ((head options).case or true)
then (head options).result
else switch (tail options);
mkTarball = pkg: contents:
runCommand "${last (init (splitString "/" (head (splitString "(" pkg))))}.tgz" { } ''
tar -czf $out -C ${contents} .
'';
findTarball = n: v:
switch [
{
case = (v.resolution.type or "") == "git";
result =
mkTarball n (
fetchGit {
url = v.resolution.repo;
rev = v.resolution.commit;
shallow = true;
}
);
}
{
case = hasAttrByPath [ "resolution" "tarball" ] v && hasAttrByPath [ "resolution" "integrity" ] v;
result = fetchurl {
url = v.resolution.tarball;
${head (splitString "-" v.resolution.integrity)} = v.resolution.integrity;
};
}
)
orig.packages;
{
case = hasPrefix "https://codeload.github.com" (v.resolution.tarball or "");
result =
let
m = strings.match "https://codeload.github.com/([^/]+)/([^/]+)/tar\\.gz/([a-f0-9]+)" v.resolution.tarball;
in
mkTarball n (
fetchGit {
url = "https://github.com/${elemAt m 0}/${elemAt m 1}";
rev = (elemAt m 2);
shallow = true;
}
);
}
{
case = (v ? id);
result =
let
split = splitString "/" v.id;
in
mkTarball n (
fetchGit {
url = "https://${concatStringsSep "/" (init split)}.git";
rev = (last split);
shallow = true;
}
);
}
{
case = hasPrefix "/" n;
result =
let
name = withoutVersion n;
baseName = last (splitString "/" (withoutVersion n));
version = getVersion n;
in
fetchurl {
url = "${registry}/${name}/-/${baseName}-${version}.tgz";
${head (splitString "-" v.resolution.integrity)} = v.resolution.integrity;
};
}
];
in
{
dependencyTarballs =
unique (
mapAttrsToList
findTarball
(filterAttrs
(n: v: !noDevDependencies || !(v.dev or false))
(parseLockfile lockfile).packages
)
);

patchedLockfile =
let
orig = parseLockfile lockfile;
in
orig // {
packages = mapAttrs
(n: v:
v // (
if noDevDependencies && (v.dev or false)
then { resolution = { }; }
else {
resolution.tarball = "file:${findTarball n v}";
}
)
)
orig.packages;

};
};

}

0 comments on commit c08830c

Please sign in to comment.