nixpkgs-bootstrap/mkNixpkgs: rework to more reliably support updateScripts

This commit is contained in:
2025-05-09 05:50:35 +00:00
parent 15de5234ef
commit fbde0005d8

View File

@@ -2,6 +2,24 @@
# this means it has to be evaluatable using only builtins,
# though i'm free to include optional functionality (e.g. update scripts) so long as i gate it behind availability checks.
#
# some considerations in order to keep this file compatible with updateScripts (e.g. `scripts/update sane.nixpkgs-bootstrap.master`):
# - <repo:nixos/nixpkgs:pkgs/common-updater/scripts/update-source-version>, used by `{git,unstableGit}updater`,
# updates the src attributes (rev, url, etc), then attempts to `nix-build` the package, discovers the new
# source hash by scraping the nix error, "hash mismatch in fixed-output derivation ...", then patches in the new hash.
# - this logic only works for nixpkgs fetchers -- NOT (necessarily) the nix builtin fetcher.
# - this process requires that attrs like `sane.nixpkgs-bootstrap.master.src` be _evaluatable_, even if
# e.g. the `patches` i intend to apply don't cleanly apply to the new rev.
#
# this leads to the following updateScript-aware implementation:
# 1. impure.nix evaluates (approximately) to `(import nixpkgs-bootstrap/master.nix { ... }).pkgs.extend (overlays)`.
# - that's `(mkNixpkgs { branch = "master", rev = ... }).pkgs.extend (overlays)`
# 2. we can't guarantee that `sane.nixpkgs-bootstrap.master.src` is actually evaluatable, when updating the bootstrap,
# however we _can_ detect that the bootstrap is being updated during eval of `mkNixpkgs`(*),
# and if so force a failure which the update script will parse identically as if the actual `sane.nixpkgs-bootstrap.master.src` had failed to build.
# (*) `mkNixpkgs` can detect that an updateScript for `foo.src` is running by testing `foo.src.hash`
# against the sentinel value which `update-source-version` assigns during updates.
#
#
# branch workflow:
# - daily:
# - nixos-unstable cut from master after enough packages have been built in caches.
@@ -18,51 +36,75 @@
# - staging-next: if testing stuff that's been PR'd into staging, i.e. base library updates.
# - staging: maybe if no staging-next -> master PR has been cut yet?
{
#VVV these may or may not be available when called VVV
#VVV these may or may not be available when called. VVV
applyPatches ? null,
fetchpatch2 ? null,
fetchzip ? null,
stdenv ? null,
unstableGitUpdater ? null,
}:
let
inBootstrap = fetchzip == null;
fetchzip' = if inBootstrap then builtins.fetchTarball else fetchzip;
optionalAttrs = cond: attrs: if cond then attrs else {};
# nixpkgs' update-source-version (updateScript) calculates the new hash for a `src` by specifying this hardcoded bogus hash and then attempting to realize it.
sentinelSha256 = "sha256-AzH1rZFqEH8sovZZfJykvsEmCedEZWigQFHWHl6/PdE=";
fetchBootstrap = { url, pname, version, ... }@args: {
# N.B. `outPath` is a special attr name which nix consults when coercing an attrset to a string.
outPath = builtins.fetchTarball ({
inherit url;
name = if version != "" then "${pname}-${version}" else pname;
} // (
builtins.removeAttrs args [ "url" "pname" "version" ]
));
inherit pname version;
};
mkNixpkgs = {
branch,
rev ? null,
sha256 ? null,
version ? null,
rev ? "",
sha256 ? "",
version ? "",
#VVV config
localSystem ? if stdenv != null then stdenv.buildPlatform.system else builtins.currentSystem, #< not available in pure mode
system ? if stdenv != null then stdenv.hostPlatform.system else localSystem,
}@args: let
src' = fetchzip' ({
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
# nixpkgs' update-source-version (updateScript) finds the new hash by specifying this hardcoded bogus hash
# and then checking the error messages.
# but that doesn't work for builtins; instead we leave the builtin hash unspec'd,
# and let eval progress to where pkgs.fetchzip exists and errors.
} // optionalAttrs (sha256 != "sha256-AzH1rZFqEH8sovZZfJykvsEmCedEZWigQFHWHl6/PdE=") {
inherit sha256;
} // optionalAttrs (!inBootstrap) {
pname = "nixpkgs";
version = rev;
inherit sha256; #< if out of the bootstrap, then *always* pin with hashes!
});
url = "https://github.com/NixOS/nixpkgs/archive/${rev}.tar.gz";
pname = "nixpkgs-${branch}";
commonNixpkgsArgs = {
inherit localSystem;
# reset impurities
config = {};
overlays = [];
};
# first, fetch the unpatched nixpkgs src:
src' = if fetchzip != null then
fetchzip { inherit url pname version sha256; }
else if sha256 != "" && sha256 != sentinelSha256 then
# fetch nixpkgs using `builtins.fetchTarball`.
# a proper bootstrap would be to return `(import (fetchBootstrap src) {}).fetchzip src`.
# this would be more immune to bugs like <https://github.com/NixOS/nixpkgs/pull/404702#issuecomment-2861798821>,
# however it would force an extra eval of nixpkgs for _every_ user invocation of `nix`.
fetchBootstrap { inherit url pname version sha256; }
else
# we don't know the hash for our nixpkgs.
# likely that an updateScript is running, and attempting to discover the new hash.
# then, use an impure bootstrap to fetch nixpkgs, and then invoke that nixpkgs' `fetchzip`.
# this is expected to fail, however by failing inside a nixpkgs' fetcher, we allow the updateScript to complete.
let
impureNixpkgsSrc = fetchBootstrap { inherit url pname version; };
impureNixpkgs = import impureNixpkgsSrc commonNixpkgsArgs;
in
impureNixpkgs.fetchzip { inherit url pname version sha256; }
;
unpatchedNixpkgs = import src' commonNixpkgsArgs;
patchedSrc = unpatchedNixpkgs.applyPatches {
applyPatches' = if applyPatches != null then applyPatches else unpatchedNixpkgs.applyPatches;
fetchpatch2' = if fetchpatch2 != null then fetchpatch2 else unpatchedNixpkgs.fetchpatch2;
patchedSrc = applyPatches' {
src = src';
name = "nixpkgs-patched-uninsane";
inherit version;
patches = unpatchedNixpkgs.callPackage ./patches.nix { };
patches = import ./patches.nix { fetchpatch2 = fetchpatch2'; };
# skip applied patches
prePatch = ''
realpatch=$(command -v patch)
@@ -82,9 +124,11 @@ let
# so avoid specifying hostPlatform.system on non-cross builds, so i can use upstream caches.
crossSystem = system;
} else {});
nixpkgs = import "${patchedSrc}" nixpkgsArgs;
nixpkgs = import patchedSrc nixpkgsArgs;
in
if rev == null && sha256 == null && version == null then
if rev == "" && sha256 == "" && version == "" then
# allow `impure.nix` to invoke this with only `branch`, and we figure out the details for it.
# TODO: remove this code path.
import ./${branch}.nix {
mkNixpkgs = args': mkNixpkgs ({
inherit localSystem system;
@@ -92,11 +136,13 @@ let
}
else
patchedSrc.overrideAttrs (base: {
# attributes needed for update scripts
# attributes needed for update scripts.
# TODO: should be possible to pass these straight through `applyPatches` instead of using `overrideAttrs`.
inherit version;
pname = "nixpkgs";
passthru = (base.passthru or {}) // {
# override is used to configure hostPlatform higher up.
# TODO: refactor to avoid overriding.
override = overrideArgs: mkNixpkgs (args // overrideArgs);
pkgs = nixpkgs;