fix(nix): replace manual hash management with bun2nix (#1021)
* fix(nix): replace manual hash management with bun2nix Eliminate the recurring problem of Nix build hashes breaking whenever bun dependencies change or the bun version in nixpkgs updates. Changes: - Add bun2nix flake input (pinned to v2.0.1) for per-package fetchurl expressions from bun.lock, replacing the single FOD hash approach - Use allowBuiltinFetchGit for cargo git dependencies, removing manual outputHashes that required updates on every git dep change - Add scripts/check-nix-deps.ts (cross-platform, runs via bun) that auto-regenerates .nix/bun.nix when bun.lock changes, triggered by the postinstall hook in package.json - Update CI workflow to verify bun.nix is in sync and evaluate flake - Remove scripts/update-nix-hashes.sh (no longer needed) * style: format check-nix-deps.ts with prettier * ci(nix): make nix-check job a required check * fix(nix): address review feedback - Fix bun2nix input pinning: use path syntax (github:owner/repo/tag) instead of ?tag= query parameter so flake.lock records the ref and `nix flake update` respects the pin - Make check-nix-deps.ts exit with 0 on bun2nix failure so that `bun install` is not blocked for non-Nix developers (CI validates bun.nix independently) - Fix stale reference to check-nix-deps.sh in flake.nix comment * ci(nix): add full nix build step to CI workflow Add `nix build .#handy -L --show-trace` after flake evaluation to catch runtime build errors (broken dependencies, sandbox issues, compilation failures) that flake eval alone cannot detect. * fix(nix): update bun2nix pin from 2.0.1 to 2.0.8 bun2nix 2.0.1 has a bug in cache-entry-creator that causes "ln: failed to create symbolic link '/p': Permission denied" during the build. Version 2.0.8 fixes this.
This commit is contained in:
parent
01b8b05bdc
commit
7056edce28
8 changed files with 1695 additions and 265 deletions
63
.github/workflows/nix-check.yml
vendored
63
.github/workflows/nix-check.yml
vendored
|
|
@ -1,18 +1,62 @@
|
|||
# Verify that Nix dependency files (.nix/bun.nix) are in sync with lockfiles
|
||||
# and that the flake evaluates successfully.
|
||||
#
|
||||
# This catches cases where a developer updated bun.lock but forgot to
|
||||
# regenerate .nix/bun.nix (e.g., committed without running bun install,
|
||||
# which triggers the postinstall hook).
|
||||
|
||||
name: "nix build check"
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
nix-build:
|
||||
runs-on: ubuntu-24.04
|
||||
continue-on-error: true
|
||||
continue-on-error: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
|
||||
- uses: cachix/install-nix-action@v30
|
||||
with:
|
||||
nix_path: nixpkgs=channel:nixos-unstable
|
||||
|
||||
# Regenerate .nix/bun.nix from bun.lock and check if it matches
|
||||
# what's committed. A diff means the developer forgot to run
|
||||
# bun scripts/check-nix-deps.ts or bun install (which triggers it).
|
||||
- name: Check bun.nix is up to date
|
||||
id: bun-check
|
||||
run: |
|
||||
bunx bun2nix -o .nix/bun.nix
|
||||
if ! git diff --quiet .nix/bun.nix; then
|
||||
echo "outdated=true" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Hint on outdated bun.nix
|
||||
if: steps.bun-check.outputs.outdated == 'true'
|
||||
run: |
|
||||
echo ""
|
||||
echo "::warning::.nix/bun.nix is out of sync with bun.lock"
|
||||
echo ""
|
||||
echo "┌──────────────────────────────────────────────────────────────┐"
|
||||
echo "│ .nix/bun.nix is outdated. To fix, run: │"
|
||||
echo "│ │"
|
||||
echo "│ bun scripts/check-nix-deps.ts │"
|
||||
echo "│ │"
|
||||
echo "│ Or simply run 'bun install' — the postinstall hook will │"
|
||||
echo "│ regenerate it automatically. Commit the resulting changes. │"
|
||||
echo "└──────────────────────────────────────────────────────────────┘"
|
||||
echo ""
|
||||
echo "Diff:"
|
||||
git diff .nix/bun.nix
|
||||
exit 1
|
||||
|
||||
# Evaluate the flake to catch issues with cargo git dependency hashes,
|
||||
# missing inputs, or other Nix expression errors.
|
||||
# Skip if bun.nix is already outdated — nix eval would fail with a
|
||||
# cryptic error, and the bun-check step already printed a clear message.
|
||||
- name: Check flake evaluation
|
||||
if: steps.bun-check.outputs.outdated != 'true'
|
||||
id: eval
|
||||
run: |
|
||||
if ! nix eval .#packages.x86_64-linux.handy.drvPath 2>eval_err.log; then
|
||||
|
|
@ -24,16 +68,13 @@ jobs:
|
|||
if: steps.eval.outputs.failed == 'true'
|
||||
run: |
|
||||
echo ""
|
||||
echo "::warning::flake.nix evaluation failed — likely outdated outputHashes or bunDeps hash."
|
||||
echo ""
|
||||
echo "┌─────────────────────────────────────────────────────────────────┐"
|
||||
echo "│ To fix, run on NixOS, Ubuntu/Debian, macOS with nix installed: │"
|
||||
echo "│ │"
|
||||
echo "│ ./scripts/update-nix-hashes.sh │"
|
||||
echo "│ │"
|
||||
echo "│ The script will update version keys and hashes in flake.nix │"
|
||||
echo "│ automatically. Commit the resulting changes. │"
|
||||
echo "└─────────────────────────────────────────────────────────────────┘"
|
||||
echo "::warning::flake.nix evaluation failed"
|
||||
echo ""
|
||||
cat eval_err.log
|
||||
exit 1
|
||||
|
||||
# Full build — catches runtime build errors (broken dependencies,
|
||||
# sandbox issues, compilation failures) that flake eval alone misses.
|
||||
- name: Build handy
|
||||
if: steps.bun-check.outputs.outdated != 'true' && steps.eval.outputs.failed != 'true'
|
||||
run: nix build .#handy -L --show-trace
|
||||
|
|
|
|||
1
.nix/bun-lock-hash
Normal file
1
.nix/bun-lock-hash
Normal file
|
|
@ -0,0 +1 @@
|
|||
e00b12c719a762004194cec01f2ad0b78ae483c41452bcca8537179d28e704b1
|
||||
1400
.nix/bun.nix
Normal file
1400
.nix/bun.nix
Normal file
File diff suppressed because it is too large
Load diff
110
flake.lock
110
flake.lock
|
|
@ -1,5 +1,63 @@
|
|||
{
|
||||
"nodes": {
|
||||
"bun2nix": {
|
||||
"inputs": {
|
||||
"flake-parts": "flake-parts",
|
||||
"import-tree": "import-tree",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"systems": "systems",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770895533,
|
||||
"narHash": "sha256-v3QaK9ugy9bN9RXDnjw0i2OifKmz2NnKM82agtqm/UY=",
|
||||
"owner": "nix-community",
|
||||
"repo": "bun2nix",
|
||||
"rev": "c843f477b15f51151f8c6bcc886954699440a6e1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"ref": "2.0.8",
|
||||
"repo": "bun2nix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1769996383,
|
||||
"narHash": "sha256-AnYjnFWgS49RlqX7LrC4uA+sCCDBj0Ry/WOJ5XWAsa0=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "57928607ea566b5db3ad13af0e57e921e6b12381",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"import-tree": {
|
||||
"locked": {
|
||||
"lastModified": 1763762820,
|
||||
"narHash": "sha256-ZvYKbFib3AEwiNMLsejb/CWs/OL/srFQ8AogkebEPF0=",
|
||||
"owner": "vic",
|
||||
"repo": "import-tree",
|
||||
"rev": "3c23749d8013ec6daa1d7255057590e9ca726646",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "vic",
|
||||
"repo": "import-tree",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1770562336,
|
||||
|
|
@ -16,10 +74,62 @@
|
|||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1769909678,
|
||||
"narHash": "sha256-cBEymOf4/o3FD5AZnzC3J9hLbiZ+QDT/KDuyHXVJOpM=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "72716169fe93074c333e8d0173151350670b824c",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"bun2nix": "bun2nix",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"bun2nix",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1770228511,
|
||||
"narHash": "sha256-wQ6NJSuFqAEmIg2VMnLdCnUc0b7vslUohqqGGD+Fyxk=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "337a4fe074be1042a35086f15481d763b8ddc0e7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
|
|
|
|||
99
flake.nix
99
flake.nix
|
|
@ -3,12 +3,22 @@
|
|||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
|
||||
# bun2nix: generates per-package Nix fetchurl expressions from bun.lock,
|
||||
# replacing the old FOD approach where a single hash covered the entire
|
||||
# node_modules directory (that hash would break on bun version changes).
|
||||
# See: https://github.com/nix-community/bun2nix
|
||||
bun2nix = {
|
||||
url = "github:nix-community/bun2nix/2.0.8";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{
|
||||
self,
|
||||
nixpkgs,
|
||||
bun2nix,
|
||||
}:
|
||||
let
|
||||
supportedSystems = [
|
||||
|
|
@ -24,35 +34,11 @@
|
|||
packages = forAllSystems (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
lib = pkgs.lib;
|
||||
|
||||
bunDeps = pkgs.stdenv.mkDerivation {
|
||||
pname = "handy-bun-deps";
|
||||
inherit version;
|
||||
src = self;
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.bun
|
||||
pkgs.cacert
|
||||
];
|
||||
|
||||
dontFixup = true;
|
||||
|
||||
buildPhase = ''
|
||||
export HOME=$TMPDIR
|
||||
bun install --frozen-lockfile --no-progress
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out
|
||||
cp -r node_modules $out/
|
||||
'';
|
||||
|
||||
outputHashAlgo = "sha256";
|
||||
outputHashMode = "recursive";
|
||||
outputHash = "sha256-84Aw9E2+fEZT+lIb9k1bodessoex+YFr0im2GMVAPnw=";
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
overlays = [ bun2nix.overlays.default ];
|
||||
};
|
||||
lib = pkgs.lib;
|
||||
in
|
||||
{
|
||||
handy = pkgs.rustPlatform.buildRustPackage {
|
||||
|
|
@ -64,13 +50,11 @@
|
|||
|
||||
cargoLock = {
|
||||
lockFile = ./src-tauri/Cargo.lock;
|
||||
outputHashes = {
|
||||
"rdev-0.5.0-2" = "sha256-0F7EaPF8Oa1nnSCAjzEAkitWVpMldL3nCp3c5DVFMe0=";
|
||||
"rodio-0.20.1" = "sha256-wq72awTvN4fXZ9qZc5KLYS9oMxtNDZ4YGxfqz8msofs=";
|
||||
"tauri-nspanel-2.1.0" = "sha256-gotQQ1DOhavdXU8lTEux0vdY880LLetk7VLvSm6/8TI=";
|
||||
"tauri-runtime-2.10.0" = "sha256-s1IBM9hOY+HRdl/E5r7BsRTE7aLaFCCMK/DdS+bvZRc=";
|
||||
"vad-rs-0.1.5" = "sha256-Q9Dxq31npyUPY9wwi6OxqSJrEvFvG8/n0dbyT7XNcyI=";
|
||||
};
|
||||
# Automatically fetch git dependencies using builtins.fetchGit.
|
||||
# This eliminates the need for manual outputHashes that had to be
|
||||
# updated every time a git dependency changed in Cargo.lock.
|
||||
# Safe for standalone flakes (not allowed in nixpkgs, it is needed something like crate2nix).
|
||||
allowBuiltinFetchGit = true;
|
||||
};
|
||||
|
||||
postPatch = ''
|
||||
|
|
@ -78,6 +62,12 @@
|
|||
src-tauri/tauri.conf.json > $TMPDIR/tauri.conf.json
|
||||
cp $TMPDIR/tauri.conf.json src-tauri/tauri.conf.json
|
||||
|
||||
# Strip postinstall hook — it runs check-nix-deps.ts which is only
|
||||
# needed during local development, not inside the Nix sandbox.
|
||||
${pkgs.jq}/bin/jq 'del(.scripts.postinstall)' \
|
||||
package.json > $TMPDIR/package.json
|
||||
cp $TMPDIR/package.json package.json
|
||||
|
||||
# Point libappindicator-sys to the Nix store path
|
||||
substituteInPlace \
|
||||
$cargoDepsCopy/libappindicator-sys-*/src/lib.rs \
|
||||
|
|
@ -93,11 +83,31 @@
|
|||
--replace-fail '.write_to_file("opencc.h");' '// skipped'
|
||||
'';
|
||||
|
||||
# Bun dependencies: fetched per-package using hashes from .nix/bun.nix.
|
||||
# This file is auto-generated by `bunx bun2nix -o .nix/bun.nix` and
|
||||
# kept in sync via the postinstall hook in package.json.
|
||||
# To regenerate manually: bun scripts/check-nix-deps.ts
|
||||
bunDeps = pkgs.bun2nix.fetchBunDeps {
|
||||
bunNix = ./.nix/bun.nix;
|
||||
};
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cargo-tauri.hook
|
||||
pkg-config
|
||||
wrapGAppsHook4
|
||||
bun
|
||||
# pkgs.bun2nix (from overlay), not the flake input — `with pkgs;`
|
||||
# doesn't shadow function arguments in Nix.
|
||||
pkgs.bun2nix.hook # Sets up node_modules from pre-fetched bun cache
|
||||
jq
|
||||
cmake
|
||||
llvmPackages.libclang
|
||||
shaderc
|
||||
];
|
||||
|
||||
preBuild = ''
|
||||
cp -r ${bunDeps}/node_modules node_modules
|
||||
chmod -R +w node_modules
|
||||
substituteInPlace node_modules/.bin/{tsc,vite} \
|
||||
--replace-fail "/usr/bin/env node" "${lib.getExe pkgs.bun}"
|
||||
# bun2nix.hook has already set up node_modules from pre-fetched cache.
|
||||
# Build the frontend with bun (tsc + vite).
|
||||
export HOME=$TMPDIR
|
||||
bun run build
|
||||
'';
|
||||
|
|
@ -116,17 +126,6 @@
|
|||
runHook postInstall
|
||||
'';
|
||||
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cargo-tauri.hook
|
||||
pkg-config
|
||||
wrapGAppsHook4
|
||||
bun
|
||||
jq
|
||||
cmake
|
||||
llvmPackages.libclang
|
||||
shaderc
|
||||
];
|
||||
|
||||
buildInputs = with pkgs; [
|
||||
webkitgtk_4_1
|
||||
gtk3
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@
|
|||
"format:backend": "cd src-tauri && cargo fmt",
|
||||
"test:playwright": "playwright test",
|
||||
"test:playwright:ui": "playwright test --ui",
|
||||
"check:translations": "bun scripts/check-translations.ts"
|
||||
"check:translations": "bun scripts/check-translations.ts",
|
||||
"postinstall": "bun scripts/check-nix-deps.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.1.16",
|
||||
|
|
|
|||
81
scripts/check-nix-deps.ts
Normal file
81
scripts/check-nix-deps.ts
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// scripts/check-nix-deps.ts — Keep .nix/bun.nix in sync with bun.lock
|
||||
//
|
||||
// Handy uses bun2nix to generate per-package Nix fetchurl expressions from
|
||||
// bun.lock. This replaces the old FOD (Fixed-Output Derivation) approach
|
||||
// where a single hash covered the entire node_modules — that hash would
|
||||
// break whenever the bun version in nixpkgs changed, even without any
|
||||
// dependency updates.
|
||||
//
|
||||
// How it works:
|
||||
// 1. Computes sha256 of bun.lock
|
||||
// 2. Compares with stored hash in .nix/bun-lock-hash
|
||||
// 3. If they match — nothing to do (~2ms)
|
||||
// 4. If they differ — runs `bunx bun2nix` to regenerate .nix/bun.nix
|
||||
//
|
||||
// When it runs:
|
||||
// - Automatically via "postinstall" in package.json — triggers after every
|
||||
// bun install / bun add / bun remove / bun update
|
||||
// - Can also be run manually: bun scripts/check-nix-deps.ts
|
||||
//
|
||||
// What to commit:
|
||||
// If the script regenerated .nix/bun.nix, commit it together with bun.lock:
|
||||
// git add bun.lock .nix/bun.nix .nix/bun-lock-hash
|
||||
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||
import { join, resolve } from "path";
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const nixDir = join(root, ".nix");
|
||||
const lockFile = join(root, "bun.lock");
|
||||
const hashFile = join(nixDir, "bun-lock-hash");
|
||||
const nixFile = join(nixDir, "bun.nix");
|
||||
|
||||
// No bun.lock — nothing to do
|
||||
if (!existsSync(lockFile)) process.exit(0);
|
||||
|
||||
// Ensure .nix directory exists
|
||||
mkdirSync(nixDir, { recursive: true });
|
||||
|
||||
// Compute sha256 of the current bun.lock
|
||||
const currentHash = new Bun.CryptoHasher("sha256")
|
||||
.update(readFileSync(lockFile))
|
||||
.digest("hex");
|
||||
|
||||
// Read the previously stored hash (empty if first run)
|
||||
const storedHash = existsSync(hashFile)
|
||||
? readFileSync(hashFile, "utf-8").trim()
|
||||
: "";
|
||||
|
||||
// If hashes match, bun.nix is up to date — nothing to do
|
||||
if (currentHash === storedHash) process.exit(0);
|
||||
|
||||
// bun.lock has changed — regenerate the Nix dependency file
|
||||
console.log(
|
||||
`[check-nix-deps] bun.lock has changed, regenerating ${nixFile}...`,
|
||||
);
|
||||
|
||||
const result = Bun.spawnSync(["bunx", "bun2nix", "-o", nixFile], {
|
||||
cwd: root,
|
||||
stdio: ["inherit", "inherit", "inherit"],
|
||||
});
|
||||
|
||||
if (result.exitCode !== 0) {
|
||||
console.warn(
|
||||
"[check-nix-deps] Warning: bunx bun2nix failed. .nix/bun.nix may be outdated.",
|
||||
);
|
||||
console.warn(
|
||||
"[check-nix-deps] Nix users: run `bunx bun2nix -o .nix/bun.nix` manually.",
|
||||
);
|
||||
console.warn(
|
||||
"[check-nix-deps] Non-Nix users: this is safe to ignore, CI will catch it.",
|
||||
);
|
||||
// Exit 0 so that `bun install` is not blocked for non-Nix developers.
|
||||
// CI validates bun.nix independently.
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
writeFileSync(hashFile, currentHash + "\n");
|
||||
console.log(`[check-nix-deps] Updated ${nixFile}`);
|
||||
console.log(
|
||||
"[check-nix-deps] Don't forget to commit: .nix/bun.nix .nix/bun-lock-hash",
|
||||
);
|
||||
|
|
@ -1,203 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Updates outputHashes and bunDeps hash in flake.nix
|
||||
# when Cargo or JS dependencies change.
|
||||
#
|
||||
# Usage: ./scripts/update-nix-hashes.sh
|
||||
#
|
||||
# Handles:
|
||||
# - Version changes in git dependencies (Cargo.lock → outputHashes)
|
||||
# - bun.lock changes (→ bunDeps outputHash)
|
||||
#
|
||||
# Requires: nix, awk, sed
|
||||
# Works on: NixOS, Ubuntu/Debian, macOS
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
FLAKE_NIX="$PROJECT_DIR/flake.nix"
|
||||
CARGO_LOCK="$PROJECT_DIR/src-tauri/Cargo.lock"
|
||||
|
||||
FAKE_HASH="sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
|
||||
# Portable sed -i (macOS requires -i '', GNU sed requires just -i)
|
||||
sedi() {
|
||||
if sed --version >/dev/null 2>&1; then
|
||||
sed -i "$@"
|
||||
else
|
||||
sed -i '' "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
if ! command -v nix >/dev/null 2>&1; then
|
||||
echo "error: nix is not installed. Install it from https://nixos.org/download/" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$FLAKE_NIX" ]; then
|
||||
echo "error: flake.nix not found at $FLAKE_NIX" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [ ! -f "$CARGO_LOCK" ]; then
|
||||
echo "error: Cargo.lock not found at $CARGO_LOCK" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 1: Extract git dependency representative keys from Cargo.lock
|
||||
#
|
||||
# Cargo.lock format (consecutive lines per package):
|
||||
# [[package]]
|
||||
# name = "foo"
|
||||
# version = "1.2.3"
|
||||
# source = "git+https://...#commit"
|
||||
#
|
||||
# Multiple packages from the same git URL share one outputHash entry keyed
|
||||
# by the alphabetically first "name-version" from that URL.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
extract_cargo_git_keys() {
|
||||
awk '
|
||||
/^name = / { name = substr($3, 2, length($3) - 2) }
|
||||
/^version = / { version = substr($3, 2, length($3) - 2) }
|
||||
/^source = "git\+/ {
|
||||
src = $3
|
||||
gsub(/^"git\+/, "", src)
|
||||
sub(/#.*/, "", src)
|
||||
key = name "-" version
|
||||
if (!(src in best) || key < best[src])
|
||||
best[src] = key
|
||||
}
|
||||
END { for (s in best) print best[s] }
|
||||
' "$CARGO_LOCK" | sort
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 2: Extract current outputHashes keys from flake.nix
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
extract_flake_keys() {
|
||||
# Portable: no grep -P, use awk instead
|
||||
sed -n '/outputHashes/,/};/p' "$FLAKE_NIX" \
|
||||
| awk -F'"' '/sha256-/ { print $2 }' \
|
||||
| sort
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 3: Compare keys and update flake.nix where needed
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
update_output_hash_keys() {
|
||||
local cargo_keys flake_keys
|
||||
cargo_keys=$(extract_cargo_git_keys)
|
||||
flake_keys=$(extract_flake_keys)
|
||||
|
||||
local changed=0
|
||||
|
||||
# For each flake key, check if it still matches a Cargo.lock git dep.
|
||||
# If the package name matches but version differs -> update.
|
||||
echo "$flake_keys" | while IFS= read -r fk; do
|
||||
[ -z "$fk" ] && continue
|
||||
|
||||
# Extract the package name prefix (everything before the version)
|
||||
fname=$(echo "$fk" | sed 's/-[0-9][0-9.]*[-0-9]*$//')
|
||||
|
||||
if echo "$cargo_keys" | grep -qxF "$fk"; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Key not found in Cargo.lock — look for a replacement with the same name
|
||||
replacement=$(echo "$cargo_keys" | while IFS= read -r ck; do
|
||||
cname=$(echo "$ck" | sed 's/-[0-9][0-9.]*[-0-9]*$//')
|
||||
if [ "$cname" = "$fname" ]; then
|
||||
echo "$ck"
|
||||
break
|
||||
fi
|
||||
done)
|
||||
|
||||
if [ -n "$replacement" ]; then
|
||||
echo "outputHashes: $fk -> $replacement"
|
||||
sedi "s|\"$fk\" = \"sha256-[^\"]*\"|\"$replacement\" = \"$FAKE_HASH\"|" "$FLAKE_NIX"
|
||||
changed=1
|
||||
else
|
||||
echo "warning: $fk not found in Cargo.lock git deps and no replacement detected" >&2
|
||||
echo " This entry may need to be removed or added manually." >&2
|
||||
fi
|
||||
done
|
||||
|
||||
# Check for new git deps not yet in flake.nix
|
||||
echo "$cargo_keys" | while IFS= read -r ck; do
|
||||
[ -z "$ck" ] && continue
|
||||
if ! echo "$flake_keys" | grep -qxF "$ck" && ! grep -q "\"$ck\"" "$FLAKE_NIX"; then
|
||||
echo "warning: git dep $ck exists in Cargo.lock but not in flake.nix outputHashes" >&2
|
||||
echo " You may need to add it manually." >&2
|
||||
fi
|
||||
done
|
||||
|
||||
return $changed
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Step 4: Iteratively fix hashes by running nix build and parsing errors
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
fix_hashes() {
|
||||
local max_attempts=10
|
||||
local attempt=0
|
||||
|
||||
while [ "$attempt" -lt "$max_attempts" ]; do
|
||||
attempt=$((attempt + 1))
|
||||
echo ""
|
||||
echo "=== nix build attempt $attempt/$max_attempts ==="
|
||||
|
||||
local output
|
||||
if output=$(nix build .#handy 2>&1); then
|
||||
echo "Build successful!"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check for hash mismatch
|
||||
if echo "$output" | grep -q "hash mismatch in fixed-output derivation"; then
|
||||
local specified got
|
||||
specified=$(echo "$output" | grep "specified:" | awk '{print $2}')
|
||||
got=$(echo "$output" | grep "got:" | awk '{print $2}')
|
||||
|
||||
if [ -n "$specified" ] && [ -n "$got" ]; then
|
||||
echo "Hash mismatch: $specified -> $got"
|
||||
sedi "s|$specified|$got|" "$FLAKE_NIX"
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
# If we can't parse the error, show it and bail out
|
||||
echo ""
|
||||
echo "Build failed with an error that cannot be fixed automatically:" >&2
|
||||
echo "$output" | tail -20 >&2
|
||||
return 1
|
||||
done
|
||||
|
||||
echo "error: exceeded max attempts ($max_attempts)" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Main
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
cd "$PROJECT_DIR"
|
||||
|
||||
echo "=== Nix flake hash updater ==="
|
||||
echo ""
|
||||
echo "Checking outputHashes keys against Cargo.lock..."
|
||||
|
||||
if update_output_hash_keys; then
|
||||
echo "All outputHashes keys are up to date."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "Running nix build to verify/fix hashes..."
|
||||
fix_hashes
|
||||
|
||||
echo ""
|
||||
echo "Done. Changes in flake.nix:"
|
||||
git diff --stat -- flake.nix 2>/dev/null || true
|
||||
Loading…
Reference in a new issue