From ff920956a9334dc5f39cae28a667356a38bfddf2 Mon Sep 17 00:00:00 2001 From: ash Date: Fri, 3 Apr 2026 22:07:54 +0100 Subject: [PATCH] libstore/build: fix URL guesswork for failing structuredAttrs FODs Fixes #1175 Change-Id: I6a074f489620f6de0615858f4d0d872c6a6a6964 --- doc/manual/change-authors.yml | 3 ++ .../rl-next/structuredattrs-url-guesswork.md | 11 +++++++ lix/libstore/build/local-derivation-goal.cc | 17 ++++++++++ .../assets/test_build/fod-failing.nix | 33 +++++++++++++++++++ .../commands/test_build/test_build_fod.py | 22 +++++++++++-- 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 doc/manual/rl-next/structuredattrs-url-guesswork.md diff --git a/doc/manual/change-authors.yml b/doc/manual/change-authors.yml index b9d5fc50d..04a764a6d 100644 --- a/doc/manual/change-authors.yml +++ b/doc/manual/change-authors.yml @@ -150,6 +150,9 @@ kasimeka: forgejo: janw4ld github: kasimeka +keysmashes: + github: keysmashes + kfears: display_name: KFears forgejo: kfearsoff diff --git a/doc/manual/rl-next/structuredattrs-url-guesswork.md b/doc/manual/rl-next/structuredattrs-url-guesswork.md new file mode 100644 index 000000000..24b519c64 --- /dev/null +++ b/doc/manual/rl-next/structuredattrs-url-guesswork.md @@ -0,0 +1,11 @@ +--- +synopsis: "Hash mismatch diagnostics now work with `structuredAttrs`" +issues: [fj#1175] +cls: [5441] +category: Fixes +credits: [keysmashes] +--- + +Nixpkgs fetchers like `fetchurl` now use `structuredAttrs`, which broke the +hash mismatch diagnostics added in Lix 2.91. This has been fixed and the likely +URL is now shown again. diff --git a/lix/libstore/build/local-derivation-goal.cc b/lix/libstore/build/local-derivation-goal.cc index e1031aa9b..436122c51 100644 --- a/lix/libstore/build/local-derivation-goal.cc +++ b/lix/libstore/build/local-derivation-goal.cc @@ -1711,6 +1711,23 @@ try { anyHashMismatchSeen = true; // XXX: shameless layering violation hack that makes the hash mismatch error at least not utterly worthless auto guessedUrl = getOr(drv->env, "urls", getOr(drv->env, "url", "(unknown)")); + if (guessedUrl == "(unknown)") { + if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) { + auto json = *structuredAttrs; + if (json.contains("urls") && json["urls"].is_array() && !json["urls"].empty()) { + guessedUrl = ""; + for (auto it = json["urls"].begin(); it != json["urls"].end(); ++it) { + if (!it->is_string()) continue; + if (guessedUrl != "") { + guessedUrl += " or "; + } + guessedUrl += it->get(); + } + } else if (json.contains("url") && json["url"].is_string()) { + guessedUrl = json["url"].get(); + } + } + } delayedException = std::make_exception_ptr(BuildError( "hash mismatch in fixed-output derivation '%s':\n likely URL: %s\n " "specified: %s\n got: %s\n expected path: %s\n got path: %s", diff --git a/tests/functional2/commands/test_build/assets/test_build/fod-failing.nix b/tests/functional2/commands/test_build/assets/test_build/fod-failing.nix index b63fbd900..4283635e6 100644 --- a/tests/functional2/commands/test_build/assets/test_build/fod-failing.nix +++ b/tests/functional2/commands/test_build/assets/test_build/fod-failing.nix @@ -38,4 +38,37 @@ rec { exit 1 ''; }; + x5 = mkDerivation { + name = "x5"; + __structuredAttrs = true; + buildCommand = "echo $name > \${outputs[out]}"; + url = "https://avian.example/owls.tar.gz"; + outputHashMode = "recursive"; + outputHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }; + x6 = mkDerivation { + name = "x6"; + __structuredAttrs = true; + buildCommand = "echo $name > \${outputs[out]}"; + urls = [ "https://avian.example/geese.tar.gz" ]; + outputHashMode = "recursive"; + outputHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }; + x7 = mkDerivation { + name = "x7"; + __structuredAttrs = true; + buildCommand = "echo $name > \${outputs[out]}"; + url = 42; + urls = 0; + outputHashMode = "recursive"; + outputHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }; + x8 = mkDerivation { + name = "x8"; + __structuredAttrs = true; + buildCommand = "echo $name > \${outputs[out]}"; + urls = [ "https://avian.example/swan1.tar.gz" "https://avian.example/swan2.tar.gz" ]; + outputHashMode = "recursive"; + outputHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="; + }; } diff --git a/tests/functional2/commands/test_build/test_build_fod.py b/tests/functional2/commands/test_build/test_build_fod.py index 602f222fc..a2934ebbb 100644 --- a/tests/functional2/commands/test_build/test_build_fod.py +++ b/tests/functional2/commands/test_build/test_build_fod.py @@ -1,5 +1,7 @@ import re +import pytest + from testlib.fixtures.file_helper import with_files, CopyFile from testlib.fixtures.nix import Nix from testlib.utils import get_global_asset @@ -27,8 +29,7 @@ def test_url_keep_going_1(nix: Nix): assert re.findall(r"hash mismatch in fixed-output derivation '.*-x.\.drv'", err) assert "likely URL: " in err assert re.findall( - r"error: build of '.*-x[1-4]\.drv\^out', '.*-x[1-4]\.drv\^out', '.*-x[1-4]\.drv\^out', '.*-x[1-4]\.drv\^out' failed", - err, + r"error: build of '.*-x[1-8]\.drv\^out'(, '.*-x[1-7]\.drv\^out'){6} failed", err ) @@ -67,6 +68,23 @@ def test_missing_dependency_keep_going(nix: Nix): assert re.findall(rf"hash mismatch in fixed-output derivation '.*-{f}\.drv'", err) +@with_files(_fod_files) +@pytest.mark.parametrize( + ("attr", "url"), + [ + ("x5", "https://avian.example/owls.tar.gz"), + ("x6", "https://avian.example/geese.tar.gz"), + ("x7", "(unknown)"), + ("x8", "https://avian.example/swan1.tar.gz or https://avian.example/swan2.tar.gz"), + ], +) +def test_structured_attrs_url(nix: Nix, attr: str, url: str): + res = nix.nix([*_build_args, attr], flake=True).run().expect(1) + err = res.stderr_plain + assert err.count("error: ") == 1 + assert f"likely URL: {url}" in err + + @with_files({"config.nix": get_global_asset("config.nix")}) def test_build_inaccessible_build_dir(nix: Nix): env = nix.env