add: descriptive comments, remove: some unused bindings

This commit is contained in:
hsjobeki 2022-12-30 22:21:37 +01:00
parent 96e8a0e297
commit ca80e29283
4 changed files with 216 additions and 180 deletions

View File

@ -112,12 +112,13 @@
dependencyGraph;
packageVersions =
l.zipAttrsWith
(name: versions: l.flatten versions)
[
allDependants
allDependencies
];
l.mapAttrs (name: versions: l.unique versions)
(l.zipAttrsWith
(name: versions: l.flatten versions)
[
allDependants
allDependencies
]);
cyclicDependencies = lock.cyclicDependencies;

View File

@ -34,11 +34,10 @@
}: let
l = lib // builtins;
b = builtins;
inherit (pkgs) stdenv python3 python310Packages makeWrapper jq;
nodejsVersion = subsystemAttrs.nodejsVersion;
defaultNodejsVersion = "14";
defaultNodejsVersion = l.versions.major pkgs.nodejs.version;
isMainPackage = name: version:
(packages."${name}" or null) == version;
@ -50,6 +49,11 @@
pkgs."nodejs-${nodejsVersion}_x"
or (throw "Could not find nodejs version '${nodejsVersion}' in pkgs");
nodeSources = pkgs.runCommandLocal "node-sources" {} ''
tar --no-same-owner --no-same-permissions -xf ${nodejs.src}
mv node-* $out
'';
# e.g.
# {
# "@babel/core": ["1.0.0","2.0.0"]
@ -73,66 +77,81 @@
)
packageVersions;
# our builder, written in python. We have huge complexity with how npm builds node_modules
nodejsBuilder = python310Packages.buildPythonApplication {
pname = "builder";
version = "0.1.0";
# our builder, written in python. Better handles the complexity with how npm builds node_modules
nodejsBuilder = pkgs.python310Packages.buildPythonApplication {
name = "builder";
src = ./nodejs_builder;
format = "pyproject";
nativeBuildInputs = with python310Packages; [poetry mypy flake8 black semantic-version];
propagatedBuildInputs = with python310Packages; [node-semver];
nativeBuildInputs = with pkgs.python310Packages; [poetry mypy flake8 black];
doCheck = false;
meta = {
description = "Custom builder";
};
};
# type: resolveChildren :: { name :: String, version :: String, rootVersions :: { ${String} :: {String} }} -> { ${String} :: { version :: String, dependencies :: Self } }
# function that resolves local vs global dependencies.
# we copy dependencies into the global node_modules scope, if they dont have conflicts there.
# otherwise we need to declare the package as 'private'
# returns the recursive structure:
# {
# "pname": {
# "version": "1.0.0",
# "dependencies": {...},
# }
# }
resolveChildren = {
name, #a
version, #1.1.2
rootVersions,
# {
# "packageNameA": "1.0.0",
# "packageNameB": "2.0.0"
# }
}: let
directDeps = getDependencies name version;
installLocally = name: version: !(rootVersions ? ${name}) || (rootVersions.${name} != version);
locallyRequiredDeps = b.filter (d: installLocally d.name d.version) directDeps;
localDepsAttrs = b.listToAttrs (l.map (dep: l.nameValuePair dep.name dep.version) locallyRequiredDeps);
newRootVersions = rootVersions // localDepsAttrs;
localDeps =
l.mapAttrs
(
name: version: {
inherit version;
dependencies = resolveChildren {
inherit name version;
rootVersions = newRootVersions;
};
}
)
localDepsAttrs;
in
localDeps;
# function that 'builds' a package.
# executes
# type: mkNodeModule :: String -> String -> Derivation
mkNodeModule = name: version: let
pname = lib.replaceStrings ["@" "/"] ["__at__" "__slash__"] (name + "@" + version);
# all direct dependencies of current package
deps = getDependencies name version;
resolveChildren = {
name, #a
version, #1.1.2
rootVersions,
# {
# "packageNameA": "1.0.0",
# "packageNameB": "2.0.0"
# }
}: let
directDeps = getDependencies name version;
installLocally = name: version: !(rootVersions ? ${name}) || (rootVersions.${name} != version);
locallyRequiredDeps = b.filter (d: installLocally d.name d.version) directDeps;
localDepsAttrs = b.listToAttrs (l.map (dep: l.nameValuePair dep.name dep.version) locallyRequiredDeps);
newRootVersions = rootVersions // localDepsAttrs;
localDeps =
l.mapAttrs
(
name: version: {
inherit version;
dependencies = resolveChildren {
inherit name version;
rootVersions = newRootVersions;
};
}
)
localDepsAttrs;
in
localDeps;
# in case of a conflict pick the highest semantic version as root. All other version must then be private if used.
# TODO: pick the version that minimizes the tree
pickVersion = name: versions: directDepsAttrs.${name} or (l.head (l.sort (a: b: l.compareVersions a b == 1) versions));
rootPackages = l.mapAttrs (name: versions: pickVersion name versions) packageVersions;
packageVersions' = l.mapAttrs (n: v: l.unique v) packageVersions;
rootPackages = l.mapAttrs (name: versions: pickVersion name versions) packageVersions';
# direct dependencies are all direct dependencies parsed from the lockfile at root level.
directDeps = getDependencies name version;
# type: { ${String} :: String } # e.g { "prettier" = "1.2.3"; }
directDepsAttrs = l.listToAttrs (b.map (dep: l.nameValuePair dep.name dep.version) directDeps);
# build the node_modules tree from all known rootPackages
# type: { ${String} :: { version :: String, dependencies :: Self } }
nodeModulesTree =
l.mapAttrs (
name: version: let
@ -148,22 +167,40 @@
nmTreeJSON = b.toJSON nodeModulesTree;
# type: makeDepAttrs :: {
# deps :: { ${String} :: { ${String} :: { deps :: Self, derivation :: Derivation } } },
# dep :: { name :: String, version :: String }
# attributes :: {
# derivation :: Derivation,
# deps :: { ${String} :: { ${String} :: { deps :: Self, derivation :: Derivation } } } }
# }
# -> { ${String} :: { ${String} :: { deps :: Self, derivation :: Derivation } } }
makeDepAttrs = {
deps,
dep,
attributes,
}:
deps
// {
${dep.name} =
(deps.${dep.name} or {})
// {
${dep.version} =
(deps.${dep.name}.${dep.version} or {})
// attributes;
};
};
depsTree = let
getDeps = deps: (b.foldl'
(
deps: dep:
deps
// {
${dep.name} =
(deps.${dep.name} or {})
// {
${dep.version} =
(deps.${dep.name}.${dep.version} or {})
// {
deps = getDeps (getDependencies dep.name dep.version);
derivation = allPackages.${dep.name}.${dep.version}.lib;
};
};
makeDepAttrs {
inherit deps dep;
attributes = {
deps = getDeps (getDependencies dep.name dep.version);
derivation = allPackages.${dep.name}.${dep.version}.lib;
};
}
)
{}
@ -172,139 +209,139 @@
depsTreeJSON = b.toJSON depsTree;
# Type: src :: Derivation
src = getSource name version;
# produceDerivation makes the mkDerivation overridable by the dream2nix users
pkg = produceDerivation name (
stdenv.mkDerivation
{
inherit nmTreeJSON depsTreeJSON;
passAsFile = ["nmTreeJSON" "depsTreeJSON"];
with pkgs;
stdenv.mkDerivation
{
inherit pname version src nodeSources;
inherit nmTreeJSON depsTreeJSON;
passAsFile = ["nmTreeJSON" "depsTreeJSON"];
inherit pname version src;
# needed for some current overrides
nativeBuildInputs = [makeWrapper];
nativeBuildInputs = [makeWrapper];
buildInputs = [jq nodejs python3];
outputs = ["out" "lib" "deps"];
buildInputs = [jq nodejs python3];
outputs = ["out" "lib" "deps"];
inherit (pkgs) system;
packageName = pname;
name = pname;
packageName = pname;
name = pname;
installMethod =
if isMainPackage name version
then "copy"
else "symlink";
installMethod =
if isMainPackage name version
then "copy"
else "symlink";
passthru.devShell = import ./devShell.nix {
inherit nodejs pkg pkgs;
};
passthru.devShell = import ./devShell.nix {
inherit nodejs pkg pkgs;
};
unpackCmd =
if lib.hasSuffix ".tgz" src
then "tar --delay-directory-restore -xf $src"
else null;
unpackCmd =
if lib.hasSuffix ".tgz" src
then "tar --delay-directory-restore -xf $src"
else null;
preConfigurePhases = ["d2nPatchPhase" "d2nCheckPhase"];
preConfigurePhases = ["d2nPatchPhase" "d2nCheckPhase"];
unpackPhase = import ./unpackPhase.nix {};
unpackPhase = import ./unpackPhase.nix {};
# nodejs expects HOME to be set
d2nPatchPhase = ''
export HOME=$TMPDIR
'';
# nodejs expects HOME to be set
d2nPatchPhase = ''
export HOME=$TMPDIR
'';
# pre-checks:
# - platform compatibility (os + arch must match)
d2nCheckPhase = ''
# exit code 3 -> the package is incompatible to the current platform
# -> Let the build succeed, but don't create node_modules
${nodejsBuilder}/bin/d2nCheck \
|| \
if [ "$?" == "3" ]; then
mkdir -p $out
mkdir -p $lib
mkdir -p $deps
echo "Not compatible with system $system" > $lib/error
exit 0
else
exit 1
fi
'';
# create the node_modules folder
# - uses symlinks as default
# - symlink the .bin
# - add PATH to .bin
configurePhase = ''
runHook preConfigure
${nodejsBuilder}/bin/d2nNodeModules
export PATH="$PATH:node_modules/.bin"
runHook postConfigure
'';
# only build the main package
# deps only get unpacked, installed, patched, etc
dontBuild = ! (isMainPackage name version);
isMain = isMainPackage name version;
# Build:
# npm run build
# custom build commands for:
# - electron apps
# fallback to npm lifecycle hooks, if no build script is present
buildPhase = ''
runHook preBuild
if [ "$(jq '.scripts.build' ./package.json)" != "null" ];
then
echo "running npm run build...."
npm run build
fi
runHook postBuild
'';
# copy node_modules
# - symlink .bin
# - symlink manual pages
# - dream2nix copies node_modules folder if it is the top-level package
installPhase = ''
runHook preInstall
if [ ! -n "$isMain" ];
then
if [ "$(jq '.scripts.preinstall' ./package.json)" != "null" ]; then
npm --production --offline --nodedir=$nodeSources run preinstall
# pre-checks:
# - platform compatibility (os + arch must match)
d2nCheckPhase = ''
# exit code 3 -> the package is incompatible to the current platform
# -> Let the build succeed, but don't create node_modules
${nodejsBuilder}/bin/d2nCheck \
|| \
if [ "$?" == "3" ]; then
mkdir -p $out
mkdir -p $lib
mkdir -p $deps
echo "Not compatible with system $system" > $lib/error
exit 0
else
exit 1
fi
if [ "$(jq '.scripts.install' ./package.json)" != "null" ]; then
npm --production --offline --nodedir=$nodeSources run install
'';
# create the node_modules folder
# - uses symlinks as default
# - symlink the .bin
# - add PATH to .bin
configurePhase = ''
runHook preConfigure
${nodejsBuilder}/bin/d2nNodeModules
export PATH="$PATH:node_modules/.bin"
runHook postConfigure
'';
# only build the main package
# deps only get unpacked, installed, patched, etc
dontBuild = ! (isMainPackage name version);
isMain = isMainPackage name version;
# Build:
# npm run build
# custom build commands for:
# - electron apps
# fallback to npm lifecycle hooks, if no build script is present
buildPhase = ''
runHook preBuild
if [ "$(jq '.scripts.build' ./package.json)" != "null" ];
then
echo "running npm run build...."
npm run build
fi
if [ "$(jq '.scripts.postinstall' ./package.json)" != "null" ]; then
npm --production --offline --nodedir=$nodeSources run postinstall
runHook postBuild
'';
# copy node_modules
# - symlink .bin
# - symlink manual pages
# - dream2nix copies node_modules folder if it is the top-level package
installPhase = ''
runHook preInstall
if [ ! -n "$isMain" ];
then
if [ "$(jq '.scripts.preinstall' ./package.json)" != "null" ]; then
npm --production --offline --nodedir=$nodeSources run preinstall
fi
if [ "$(jq '.scripts.install' ./package.json)" != "null" ]; then
npm --production --offline --nodedir=$nodeSources run install
fi
if [ "$(jq '.scripts.postinstall' ./package.json)" != "null" ]; then
npm --production --offline --nodedir=$nodeSources run postinstall
fi
fi
fi
# $out
# - $out/lib/... -> $lib ...(extracted tgz)
# - $out/lib/node_modules -> $deps
# - $out/bin
# $out
# - $out/lib/... -> $lib ...(extracted tgz)
# - $out/lib/node_modules -> $deps
# - $out/bin
# $deps
# - $deps/node_modules
# $deps
# - $deps/node_modules
# $lib
# - ... (extracted + install scripts runned)
${nodejsBuilder}/bin/d2nMakeOutputs
# $lib
# - ... (extracted + install scripts runned)
${nodejsBuilder}/bin/d2nMakeOutputs
runHook postInstall
'';
}
runHook postInstall
'';
}
);
in
pkg;

View File

@ -1,6 +1,6 @@
import os
from enum import Enum
from typing import Any, Optional, TypedDict
from typing import Any, Optional
from pathlib import Path
from .logger import logger
from dataclasses import dataclass

View File

@ -11,8 +11,6 @@ d2nMakeOutputs = 'nodejs_builder.main:makeOutputs'
[tool.poetry.dependencies]
python = "^3.10"
semantic-version = "^2.9.0"
node-semver = "^0.8.1"
[tool.poetry.dev-dependencies]
mypy = "^0.991"