From d34f5de76cf666e2f94ce602fcbd8188c3c1e15b Mon Sep 17 00:00:00 2001 From: "Timothy J. Aveni" Date: Sun, 24 May 2026 15:02:28 -0700 Subject: [PATCH] Initial commit germanium cutoff --- qx-package-helpers.nix | 497 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 497 insertions(+) create mode 100644 qx-package-helpers.nix diff --git a/qx-package-helpers.nix b/qx-package-helpers.nix new file mode 100644 index 0000000..ee8fe6d --- /dev/null +++ b/qx-package-helpers.nix @@ -0,0 +1,497 @@ +let + mkSchemaSupport = + { + pkgs, + inputs, + system, + self ? null, + flakeRoot, + schemaDir, + schemaPrefix ? "packageSchema_", + schemaBuildCommand ? "yarn build", + schemaTypecheckCommand ? "yarn typecheck", + nodejs ? pkgs.nodejs_24, + git ? pkgs.git, + }: + let + schemaPackageJson = builtins.fromJSON (builtins.readFile "${schemaDir}/package.json"); + schemaMainPath = schemaPackageJson.main or "dist/qx-package-schema.js"; + schemaMainPathDefault = "dist/qx-package-schema.js"; + schemaPackageName = schemaPackageJson.name or "schema"; + selfSchemaName = + if pkgs.lib.hasInfix "/" schemaPackageName + then pkgs.lib.last (pkgs.lib.splitString "/" schemaPackageName) + else schemaPackageName; + + flakeText = + builtins.replaceStrings + [ "\n" "\r" "\t" ] + [ " " " " " " ] + (builtins.readFile "${flakeRoot}/flake.nix"); + + getInputUrl = inputName: + let + pattern1 = ".*" + inputName + "[[:space:]]*\\.url[[:space:]]*=[[:space:]]*\"([^\"]+)\".*"; + pattern2 = ".*" + inputName + "[[:space:]]*=[[:space:]]*\\{[^}]*url[[:space:]]*=[[:space:]]*\"([^\"]+)\".*"; + match1 = builtins.match pattern1 flakeText; + match2 = if match1 != null then match1 else builtins.match pattern2 flakeText; + in if match2 == null then null else builtins.elemAt match2 0; + + schemaBase = + (pkgs.callPackage "${schemaDir}/yarn-project.nix" { + inherit nodejs git; + }) + { + # avoid pulling .git, etc. + src = pkgs.lib.cleanSource schemaDir; + }; + + schema = schemaBase.overrideAttrs (old: { + buildPhase = (old.buildPhase or "") + '' + runHook preBuild + ${schemaBuildCommand} + runHook postBuild + ''; + + }); + + schemaCheck = schema.overrideAttrs (old: { + doCheck = true; + + checkPhase = (old.checkPhase or "") + '' + runHook preCheck + ${schemaTypecheckCommand} + runHook postCheck + ''; + }); + + schemaInputs = + pkgs.lib.filterAttrs (name: _: pkgs.lib.hasPrefix schemaPrefix name) inputs; + + decodeSchemaInputName = encodedName: + builtins.replaceStrings [ "__" ] [ "." ] encodedName; + + schemaPackagesFromInputs = + pkgs.lib.mapAttrs' + (name: flake: + let + schemaName = + decodeSchemaInputName (pkgs.lib.removePrefix schemaPrefix name); + in + { + name = schemaName; + value = flake.packages.${system}.schema; + }) + schemaInputs; + + schemaPackages = schemaPackagesFromInputs // { "${selfSchemaName}" = schema; }; + + schemaMetaByName = + pkgs.lib.mapAttrs' + (name: flake: + let + schemaName = + decodeSchemaInputName (pkgs.lib.removePrefix schemaPrefix name); + sourceInfo = if flake ? sourceInfo then flake.sourceInfo else { }; + sourceRev = sourceInfo.rev or null; + inputUrl = getInputUrl name; + urlBase = + if inputUrl == null + then null + else builtins.head (pkgs.lib.splitString "?" inputUrl); + isDisallowed = + inputUrl == null + || pkgs.lib.hasPrefix "path:" inputUrl + || pkgs.lib.hasPrefix "file:" inputUrl + || pkgs.lib.hasPrefix "git+file:" inputUrl; + flakeRef = + if isDisallowed || sourceRev == null || urlBase == null + then null + else urlBase + "?rev=" + sourceRev; + meta = + { + name = "@qx-package-schemas/${schemaName}"; + type = sourceInfo.type or null; + url = inputUrl; + rev = sourceRev; + flakeRef = flakeRef; + }; + in + if flakeRef == null + then throw "Schema input ${schemaName} must be a git flake with url+rev" + else { + name = schemaName; + value = meta; + }) + schemaInputs; + + schemaMetaJsonByName = + pkgs.lib.mapAttrs (_: meta: builtins.toJSON meta) schemaMetaByName; + + schemaExtensionsBlock = pkgs.lib.concatStringsSep "\n" ( + [ + "packageExtensions:" + " \"qx-package@*\":" + " dependencies:" + ] + ++ pkgs.lib.mapAttrsToList ( + name: _: " \"@qx-package-schemas/${name}\": \"portal:./qx-package-schemas/${name}\"" + ) schemaPackages + ); + + schemaExtensionsBlockEscaped = pkgs.lib.escapeShellArg schemaExtensionsBlock; + + schemaLinkCommands = pkgs.lib.concatStringsSep "\n" ( + pkgs.lib.mapAttrsToList (name: drv: '' + schema_pkg="$(ls -d ${drv}/libexec/* 2>/dev/null | head -n1 || true)" + if [ -n "$schema_pkg" ]; then + ln -sfn "$schema_pkg" qx-package-schemas/${name} + fi + '') schemaPackages + ); + + schemaInstallCommands = pkgs.lib.concatStringsSep "\n" ( + pkgs.lib.mapAttrsToList (name: drv: + let + metaJson = + if pkgs.lib.hasAttr name schemaMetaJsonByName + then schemaMetaJsonByName.${name} + else null; + in + '' + schema_pkg="$(ls -d ${drv}/libexec/* | head -n1)" + dest="qx-package-schemas/${name}" + rm -rf "$dest" + cp -R --no-preserve=mode,ownership "$schema_pkg" "$dest" + chmod -R u+w "$dest" + '' + + ( + if metaJson != null + then '' + schema_main="${schemaMainPathDefault}" + if [ -f "$dest/$schema_main" ]; then + block_tmp="$(mktemp -p "$dest")" + tmp_out="$(mktemp -p "$dest")" + cat > "$block_tmp" <<'EOF' +packageSchema.__qx = ${metaJson}; +EOF + chmod u+w "$dest/$schema_main" + if grep -q '^export default ' "$dest/$schema_main"; then + awk -v blockFile="$block_tmp" ' + BEGIN { + while ((getline line < blockFile) > 0) { + block = block line "\n" + } + } + !inserted && /^export default / { printf "%s", block; inserted=1 } + { print } + ' "$dest/$schema_main" > "$tmp_out" + cat "$tmp_out" > "$dest/$schema_main" + elif grep -q '^//# sourceMappingURL=' "$dest/$schema_main"; then + awk -v blockFile="$block_tmp" ' + BEGIN { + while ((getline line < blockFile) > 0) { + block = block line "\n" + } + } + /^\/\/# sourceMappingURL=/ { printf "%s", block; print; next } + { print } + ' "$dest/$schema_main" > "$tmp_out" + cat "$tmp_out" > "$dest/$schema_main" + else + printf '\n' >> "$dest/$schema_main" + cat "$block_tmp" >> "$dest/$schema_main" + fi + rm -f "$block_tmp" "$tmp_out" + fi + '' + else "" + ) + ) schemaPackages + ); + + updateYarnrcScript = '' + qx_package_schemas_update_yarnrc() { + local yarnrc=".yarnrc.yml" + local tmp="$(mktemp)" + local block_tmp="$(mktemp)" + printf '%s\n' ${schemaExtensionsBlockEscaped} > "$block_tmp" + awk -v blockFile="$block_tmp" ' + BEGIN { + while ((getline line < blockFile) > 0) { + block = block line "\n" + } + } + /^# BEGIN packageExtensions/ {print; printf "%s", block; skip=1; next} + /^# END packageExtensions/ {skip=0; print; next} + !skip {print} + ' "$yarnrc" > "$tmp" + mv "$tmp" "$yarnrc" + rm -f "$block_tmp" + } + ''; + in + { + inherit + schema + schemaCheck + schemaPackages + schemaLinkCommands + schemaInstallCommands + updateYarnrcScript + schemaExtensionsBlockEscaped + selfSchemaName; + }; + + mkTsPackageServer = + { + srcDir, + serverBin ? "server", + nodejs ? null, + git ? null, + templatePreparePackages ? null, + buildCommand ? null, + typecheckCommand ? "yarn typecheck", + serverOverrides ? (old: { }), + }: + { + pkgs, + schemaSupport, + extraBuildInputs ? [ ], + extraNativeBuildInputs ? [ ], + extraRuntimePackages ? [ ], + }: + let + nodejs' = if nodejs != null then nodejs else pkgs.nodejs_24; + git' = if git != null then git else pkgs.git; + templatePreparePackages' = + if templatePreparePackages != null + then templatePreparePackages + else [ + pkgs.findutils + git' + pkgs.gnused + nodejs' + pkgs.yarn-berry_4 + ]; + + packageJson = builtins.fromJSON (builtins.readFile "${srcDir}/package.json"); + packageName = packageJson.name or "qx-package"; + + base = + (pkgs.callPackage "${srcDir}/yarn-project.nix" { + nodejs = nodejs'; + git = git'; + }) + { + # avoid pulling .git, etc. + src = pkgs.lib.cleanSource srcDir; + }; + + runtimeBinPath = pkgs.lib.makeBinPath extraRuntimePackages; + runtimeLibPath = pkgs.lib.makeLibraryPath extraRuntimePackages; + runtimeWrap = + if extraRuntimePackages == [ ] + then "" + else '' + wrapProgram "$out/bin/${serverBin}" \ + --prefix PATH : ${runtimeBinPath} \ + --prefix LD_LIBRARY_PATH : ${runtimeLibPath} + ''; + + server = base.overrideAttrs (old: + let + extraAttrs = serverOverrides old; + in + { + buildInputs = (old.buildInputs or [ ]) ++ extraBuildInputs; + + nativeBuildInputs = (old.nativeBuildInputs or [ ]) ++ [ + pkgs.python3 + pkgs.gnumake + pkgs.gcc + pkgs.pkg-config + pkgs.makeWrapper + ] ++ extraNativeBuildInputs; + + # node-gyp will look at these + PYTHON = "${pkgs.python3}/bin/python3"; + npm_config_python = "${pkgs.python3}/bin/python3"; + + preConfigure = (old.preConfigure or "") + '' + mkdir -p qx-package-schemas + ${schemaSupport.schemaLinkCommands} + ''; + + buildPhase = + (old.buildPhase or "") + + ( + if buildCommand != null + then '' + runHook preBuildQx + ${buildCommand} + runHook postBuildQx + '' + else "" + ); + + postFixup = (old.postFixup or "") + runtimeWrap; + + postInstall = (old.postInstall or "") + '' + pkg_root="$out/libexec/${old.name}" + mkdir -p "$pkg_root/qx-package-schemas" + (cd "$pkg_root" && ${schemaSupport.schemaInstallCommands}) + ''; + } // extraAttrs + ); + + check = server.overrideAttrs (old: { + doCheck = true; + + checkPhase = (old.checkPhase or "") + '' + runHook preCheck + ${typecheckCommand} + runHook postCheck + ''; + }); + + prepareShell = pkgs.mkShell { + packages = templatePreparePackages'; + }; + in + { + inherit server check packageName serverBin; + + devShells = { + "qx-prepare" = prepareShell; + }; + }; + + mkQxPackageFlake = + { + self ? null, + inputs, + nixpkgs, + flake-utils, + flakeRoot, + schemaDir, + schemaPrefix ? "packageSchema_", + serverBuilder, + packageName ? null, + extraBuildInputs ? (pkgs: [ ]), + extraNativeBuildInputs ? (pkgs: [ ]), + extraRuntimePackages ? (pkgs: [ ]), + extraDevShellPackages ? (pkgs: [ ]), + enableYarnrcHook ? true, + extraDevShellHook ? "", + appProgram ? null, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { inherit system; }; + + schemaSupport = mkSchemaSupport { + inherit pkgs inputs system schemaDir schemaPrefix self flakeRoot; + }; + + normalizeList = value: + if builtins.isFunction value + then value pkgs + else value; + + serverResult = serverBuilder { + inherit pkgs schemaSupport; + extraBuildInputs = normalizeList extraBuildInputs; + extraNativeBuildInputs = normalizeList extraNativeBuildInputs; + extraRuntimePackages = normalizeList extraRuntimePackages; + }; + + combinedName = pkgs.lib.strings.sanitizeDerivationName ( + if packageName != null + then packageName + else if serverResult ? packageName + then serverResult.packageName + else "qx-package" + ); + + packageServer = serverResult.server; + packageServerCheck = if serverResult ? check then serverResult.check else null; + + appProgramFinal = + if appProgram != null + then appProgram + else if serverResult ? appProgram + then serverResult.appProgram + else "${packageServer}/bin/${serverResult.serverBin or "server"}"; + + devShellHookBase = + if enableYarnrcHook + then '' + find_schema_root() { + dir="$PWD" + while [ "$dir" != "/" ]; do + if [ -f "$dir/src/.yarnrc.yml" ] && [ -d "$dir/schema" ]; then + printf "%s\n" "$dir" + return 0 + fi + dir="$(dirname "$dir")" + done + return 1 + } + + ${schemaSupport.updateYarnrcScript} + + link_schemas() { + mkdir -p qx-package-schemas + ${schemaSupport.schemaLinkCommands} + } + + base="$(find_schema_root || true)" + if [ -n "$base" ]; then + (cd "$base/src" && link_schemas && qx_package_schemas_update_yarnrc) + if [ -d "$base/schema" ]; then + ln -sfn "$base/schema" "$base/src/qx-package-schemas/${schemaSupport.selfSchemaName}" + fi + fi + '' + else ""; + + devShellHook = devShellHookBase + extraDevShellHook; + + devShellPackages = normalizeList extraDevShellPackages; + + checks = + { schema = schemaSupport.schemaCheck; } + // (if packageServerCheck != null then { default = packageServerCheck; } else { }); + extraDevShells = if serverResult ? devShells then serverResult.devShells else { }; + in + { + packages.default = pkgs.symlinkJoin { + name = combinedName; + paths = [ packageServer schemaSupport.schema ]; + }; + packages.server = packageServer; + packages.schema = schemaSupport.schema; + + apps.default = { + type = "app"; + program = appProgramFinal; + meta.description = "Quixos package server"; + }; + + checks = checks; + + devShells = extraDevShells // { + default = pkgs.mkShell { + inputsFrom = [ packageServer ] ++ builtins.attrValues schemaSupport.schemaPackages; + packages = devShellPackages; + shellHook = devShellHook; + }; + }; + } + ); +in +{ + inherit mkQxPackageFlake mkTsPackageServer mkSchemaSupport; +}