From 4d1f72a3243b24e0924b2896e82078fc9450e3ec Mon Sep 17 00:00:00 2001 From: Jens Petersen Date: Fri, 24 Oct 2025 01:24:04 +0800 Subject: [PATCH 01/18] libexpr needs boost-1.87+ for try_emplace_and_cvisit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 2.32, nix now needs boost 1.87 or later to build, due to using unordered::concurrent_flat_map try_emplace_and_cvisit ../src/libexpr/eval.cc: In member function ‘void nix::EvalState::evalFile(const nix::SourcePath&, nix::Value&, bool)’: ../src/libexpr/eval.cc:1096:20: error: ‘class boost::unordered::concurrent_flat_map, std::equal_to, traceable_allocator > >’ has no member named ‘try_emplace_and_cvisit’; did you mean ‘try_emplace_or_cvisit’? 1096 | fileEvalCache->try_emplace_and_cvisit( | ^~~~~~~~~~~~~~~~~~~~~~ | try_emplace_or_cvisit See https://github.com/boostorg/unordered/commit/834580b53948eec553c232dda40beefc68b3e8f9 (cherry picked from commit f594a8e11e06b9ed6b2d62efbf74e964e9c1848c) --- src/libutil/meson.build | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libutil/meson.build b/src/libutil/meson.build index 8c9e1f1eb..6c76659dd 100644 --- a/src/libutil/meson.build +++ b/src/libutil/meson.build @@ -64,7 +64,7 @@ boost = dependency( 'url', ], include_type : 'system', - version : '>=1.82.0', + version : '>=1.87.0', ) # boost is a public dependency, but not a pkg-config dependency unfortunately, so we # put in `deps_other`. From b36f8043d2dc1edc81efab533d51bf6128f73bdc Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Mon, 27 Oct 2025 16:14:19 +0300 Subject: [PATCH 02/18] libexpr: Speed up BindingsBuilder::finishSizeIfNecessary Instead of iterating over the newly built bindings we can do a cheaper set_intersection to count duplicates or fall back to a per-element binary search over the "base" bindings. This speeds up `hello` evaluation by around 10ms (0.196s -> 0.187s) and `nixos.closures.ec2.x86_64-linux` by 140ms (2.744s -> 2.609s). This addresses a somewhat steep performance regression from 82315c3807f90be8f4728b32c343e6a2f31969e3 that reduced memory requirements of attribute set merges. With this patch we get back around to 2.31 level of eval performance while keeping the memory usage optimization. Also document the optimization a bit more. (cherry picked from commit ec2fd2dc23671463fa06222e60a383123380295b) --- src/libexpr/include/nix/expr/attr-set.hh | 45 +++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/libexpr/include/nix/expr/attr-set.hh b/src/libexpr/include/nix/expr/attr-set.hh index 46eecd9bd..f57302c42 100644 --- a/src/libexpr/include/nix/expr/attr-set.hh +++ b/src/libexpr/include/nix/expr/attr-set.hh @@ -5,6 +5,7 @@ #include "nix/expr/symbol-table.hh" #include +#include #include #include @@ -463,12 +464,48 @@ private: return bindings->baseLayer; } + /** + * If the bindings gets "layered" on top of another we need to recalculate + * the number of unique attributes in the chain. + * + * This is done by either iterating over the base "layer" and the newly added + * attributes and counting duplicates. If the base "layer" is big this approach + * is inefficient and we fall back to doing per-element binary search in the base + * "layer". + */ void finishSizeIfNecessary() { - if (hasBaseLayer()) - /* NOTE: Do not use std::ranges::distance, since Bindings is a sized - range, but we are calculating this size here. */ - bindings->numAttrsInChain = std::distance(bindings->begin(), bindings->end()); + if (!hasBaseLayer()) + return; + + auto & base = *bindings->baseLayer; + auto attrs = std::span(bindings->attrs, bindings->numAttrs); + + Bindings::size_type duplicates = 0; + + /* If the base bindings is smaller than the newly added attributes + iterate using std::set_intersection to run in O(|base| + |attrs|) = + O(|attrs|). Otherwise use an O(|attrs| * log(|base|)) per-attr binary + search to check for duplicates. Note that if we are in this code path then + |attrs| <= bindingsUpdateLayerRhsSizeThreshold, which 16 by default. We are + optimizing for the case when a small attribute set gets "layered" on top of + a much larger one. When attrsets are already small it's fine to do a linear + scan, but we should avoid expensive iterations over large "base" attrsets. */ + if (attrs.size() > base.size()) { + std::set_intersection( + base.begin(), + base.end(), + attrs.begin(), + attrs.end(), + boost::make_function_output_iterator([&]([[maybe_unused]] auto && _) { ++duplicates; })); + } else { + for (const auto & attr : attrs) { + if (base.get(attr.name)) + ++duplicates; + } + } + + bindings->numAttrsInChain = base.numAttrsInChain + attrs.size() - duplicates; } public: From 19441dd317862edcbcafeddd7215a201e64a63d3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 27 Oct 2025 16:36:00 +0100 Subject: [PATCH 03/18] Bump version --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 544fe5d43..5506598e0 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.32.2 +2.32.3 From 3a92f83e7523479134ff6decceb5236d53a98450 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 27 Oct 2025 00:05:39 +0100 Subject: [PATCH 04/18] diff-closures: print sizes with dynamic unit (cherry picked from commit 9d4d10954a06a9c37bb88fb702e084e4634ebd3c) --- src/libutil-tests/util.cc | 1 + src/libutil/include/nix/util/util.hh | 2 +- src/libutil/util.cc | 9 +++++---- src/nix/diff-closures.cc | 3 +-- src/nix/path-info.cc | 2 +- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/libutil-tests/util.cc b/src/libutil-tests/util.cc index c48b97e8e..32114d9da 100644 --- a/src/libutil-tests/util.cc +++ b/src/libutil-tests/util.cc @@ -158,6 +158,7 @@ TEST(renderSize, misc) ASSERT_EQ(renderSize(972, true), " 0.9 KiB"); ASSERT_EQ(renderSize(973, true), " 1.0 KiB"); // FIXME: should round down ASSERT_EQ(renderSize(1024, true), " 1.0 KiB"); + ASSERT_EQ(renderSize(-1024, true), " -1.0 KiB"); ASSERT_EQ(renderSize(1024 * 1024, true), "1024.0 KiB"); ASSERT_EQ(renderSize(1100 * 1024, true), " 1.1 MiB"); ASSERT_EQ(renderSize(2ULL * 1024 * 1024 * 1024, true), " 2.0 GiB"); diff --git a/src/libutil/include/nix/util/util.hh b/src/libutil/include/nix/util/util.hh index 26f03938a..cb1c9694d 100644 --- a/src/libutil/include/nix/util/util.hh +++ b/src/libutil/include/nix/util/util.hh @@ -104,7 +104,7 @@ N string2IntWithUnitPrefix(std::string_view s) * GiB`. If `align` is set, the number will be right-justified by * padding with spaces on the left. */ -std::string renderSize(uint64_t value, bool align = false); +std::string renderSize(int64_t value, bool align = false); /** * Parse a string into a float. diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 383a904ad..69826070c 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -132,15 +132,16 @@ std::optional string2Float(const std::string_view s) template std::optional string2Float(const std::string_view s); template std::optional string2Float(const std::string_view s); -std::string renderSize(uint64_t value, bool align) +std::string renderSize(int64_t value, bool align) { static const std::array prefixes{{'K', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}}; size_t power = 0; - double res = value; - while (res > 1024 && power < prefixes.size()) { + double abs_value = std::abs(value); + while (abs_value > 1024 && power < prefixes.size()) { ++power; - res /= 1024; + abs_value /= 1024; } + double res = (double) value / std::pow(1024.0, power); return fmt(align ? "%6.1f %ciB" : "%.1f %ciB", power == 0 ? res / 1024 : res, prefixes.at(power)); } diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc index cbf842e5c..d36a21d74 100644 --- a/src/nix/diff-closures.cc +++ b/src/nix/diff-closures.cc @@ -107,8 +107,7 @@ void printClosureDiff( if (!removed.empty() || !added.empty()) items.push_back(fmt("%s → %s", showVersions(removed), showVersions(added))); if (showDelta) - items.push_back( - fmt("%s%+.1f KiB" ANSI_NORMAL, sizeDelta > 0 ? ANSI_RED : ANSI_GREEN, sizeDelta / 1024.0)); + items.push_back(fmt("%s%s" ANSI_NORMAL, sizeDelta > 0 ? ANSI_RED : ANSI_GREEN, renderSize(sizeDelta))); logger->cout("%s%s: %s", indent, name, concatStringsSep(", ", items)); } } diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc index fef3ae120..146b775e5 100644 --- a/src/nix/path-info.cc +++ b/src/nix/path-info.cc @@ -141,7 +141,7 @@ struct CmdPathInfo : StorePathsCommand, MixJSON void printSize(std::ostream & str, uint64_t value) { if (humanReadable) - str << fmt("\t%s", renderSize(value, true)); + str << fmt("\t%s", renderSize((int64_t) value, true)); else str << fmt("\t%11d", value); } From 7d7ca3fe9636a1aff70a912235775e6ddc82c30a Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 27 Oct 2025 00:55:40 +0100 Subject: [PATCH 05/18] refactor(libutil): remove `showBytes()` in favor of `renderSize()` The `showBytes()` function was redundant with `renderSize()` as the latter automatically selects the appropriate unit (KiB, MiB, GiB, etc.) based on the value, whereas `showBytes()` always formatted as MiB regardless of size. Co-authored-by: Bernardo Meurer Costa (cherry picked from commit f234633e2707db752a25f5319d5b63389dd162bd) --- src/libmain/include/nix/main/shared.hh | 2 -- src/libmain/shared.cc | 2 +- src/libstore/optimise-store.cc | 2 +- src/libutil/include/nix/util/util.hh | 2 -- src/libutil/util.cc | 5 ----- 5 files changed, 2 insertions(+), 11 deletions(-) diff --git a/src/libmain/include/nix/main/shared.hh b/src/libmain/include/nix/main/shared.hh index 47d08a050..43069ba82 100644 --- a/src/libmain/include/nix/main/shared.hh +++ b/src/libmain/include/nix/main/shared.hh @@ -89,8 +89,6 @@ extern volatile ::sig_atomic_t blockInt; /* GC helpers. */ -std::string showBytes(uint64_t bytes); - struct GCResults; struct PrintFreed diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 4b36ec98e..3b88ea0c9 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -406,7 +406,7 @@ RunPager::~RunPager() PrintFreed::~PrintFreed() { if (show) - std::cout << fmt("%d store paths deleted, %s freed\n", results.paths.size(), showBytes(results.bytesFreed)); + std::cout << fmt("%d store paths deleted, %s freed\n", results.paths.size(), renderSize(results.bytesFreed)); } } // namespace nix diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc index 8f2878136..3e02fa812 100644 --- a/src/libstore/optimise-store.cc +++ b/src/libstore/optimise-store.cc @@ -312,7 +312,7 @@ void LocalStore::optimiseStore() optimiseStore(stats); - printInfo("%s freed by hard-linking %d files", showBytes(stats.bytesFreed), stats.filesLinked); + printInfo("%s freed by hard-linking %d files", renderSize(stats.bytesFreed), stats.filesLinked); } void LocalStore::optimisePath(const Path & path, RepairFlag repair) diff --git a/src/libutil/include/nix/util/util.hh b/src/libutil/include/nix/util/util.hh index cb1c9694d..1234937b4 100644 --- a/src/libutil/include/nix/util/util.hh +++ b/src/libutil/include/nix/util/util.hh @@ -333,8 +333,6 @@ struct overloaded : Ts... template overloaded(Ts...) -> overloaded; -std::string showBytes(uint64_t bytes); - /** * Provide an addition operator between strings and string_views * inexplicably omitted from the standard library. diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 69826070c..f14bc63ac 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -257,9 +257,4 @@ std::pair getLine(std::string_view s) } } -std::string showBytes(uint64_t bytes) -{ - return fmt("%.2f MiB", bytes / (1024.0 * 1024.0)); -} - } // namespace nix From ebadea07349c0f84d102485a79c519073190e7e9 Mon Sep 17 00:00:00 2001 From: Marcel Date: Mon, 27 Oct 2025 01:21:02 +0100 Subject: [PATCH 06/18] treewide: replace manual MiB calculations with renderSize (cherry picked from commit 584a8e8a0085e3597f79d1ac9cf9970f575de32a) --- src/libfetchers/git-utils.cc | 5 +++-- src/libmain/shared.cc | 14 ++++++++------ src/libstore/gc.cc | 5 ++--- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 215418522..65587b43a 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -9,6 +9,7 @@ #include "nix/util/users.hh" #include "nix/util/fs-sink.hh" #include "nix/util/sync.hh" +#include "nix/util/util.hh" #include #include @@ -530,12 +531,12 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this auto act = (Activity *) payload; act->result( resFetchStatus, - fmt("%d/%d objects received, %d/%d deltas indexed, %.1f MiB", + fmt("%d/%d objects received, %d/%d deltas indexed, %s", stats->received_objects, stats->total_objects, stats->indexed_deltas, stats->total_deltas, - stats->received_bytes / (1024.0 * 1024.0))); + renderSize(stats->received_bytes))); return getInterrupted() ? -1 : 0; } diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 3b88ea0c9..19733fb3e 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -6,6 +6,7 @@ #include "nix/main/loggers.hh" #include "nix/main/progress-bar.hh" #include "nix/util/signals.hh" +#include "nix/util/util.hh" #include #include @@ -64,18 +65,19 @@ void printMissing(ref store, const MissingPaths & missing, Verbosity lvl) } if (!missing.willSubstitute.empty()) { - const float downloadSizeMiB = missing.downloadSize / (1024.f * 1024.f); - const float narSizeMiB = missing.narSize / (1024.f * 1024.f); if (missing.willSubstitute.size() == 1) { printMsg( - lvl, "this path will be fetched (%.2f MiB download, %.2f MiB unpacked):", downloadSizeMiB, narSizeMiB); + lvl, + "this path will be fetched (%s download, %s unpacked):", + renderSize(missing.downloadSize), + renderSize(missing.narSize)); } else { printMsg( lvl, - "these %d paths will be fetched (%.2f MiB download, %.2f MiB unpacked):", + "these %d paths will be fetched (%s download, %s unpacked):", missing.willSubstitute.size(), - downloadSizeMiB, - narSizeMiB); + renderSize(missing.downloadSize), + renderSize(missing.narSize)); } std::vector willSubstituteSorted = {}; std::for_each(missing.willSubstitute.begin(), missing.willSubstitute.end(), [&](const StorePath & p) { diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 47f40ab8e..193247aa2 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -5,6 +5,7 @@ #include "nix/util/finally.hh" #include "nix/util/unix-domain-socket.hh" #include "nix/util/signals.hh" +#include "nix/util/util.hh" #include "nix/store/posix-fs-canonicalise.hh" #include "store-config-private.hh" @@ -906,9 +907,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) #endif ; - printInfo( - "note: currently hard linking saves %.2f MiB", - ((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0))); + printInfo("note: currently hard linking saves %s", renderSize(unsharedSize - actualSize - overhead)); } /* While we're at it, vacuum the database. */ From 328a3bbbd0c8ec4a51b85a813f09a6d2064f993a Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 17 Oct 2025 12:03:18 -0400 Subject: [PATCH 07/18] Regression test for issue #14287 This will currently fail, until the bug is fixed. Co-Authored-By: Sergei Zimmerman (cherry picked from commit 246dbe1c0554deda6eda2fd89855237294dcca5e) --- tests/functional/build-hook-list-paths.sh | 12 ++++++++++++ tests/functional/post-hook.sh | 12 ++++++++++++ 2 files changed, 24 insertions(+) create mode 100755 tests/functional/build-hook-list-paths.sh diff --git a/tests/functional/build-hook-list-paths.sh b/tests/functional/build-hook-list-paths.sh new file mode 100755 index 000000000..03691c2d2 --- /dev/null +++ b/tests/functional/build-hook-list-paths.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +set -x +set -e + +[ -n "$OUT_PATHS" ] +[ -n "$DRV_PATH" ] +[ -n "$HOOK_DEST" ] + +for o in $OUT_PATHS; do + echo "$o" >> "$HOOK_DEST" +done diff --git a/tests/functional/post-hook.sh b/tests/functional/post-hook.sh index 67bb46377..b16d8ab84 100755 --- a/tests/functional/post-hook.sh +++ b/tests/functional/post-hook.sh @@ -29,6 +29,18 @@ nix-build -o "$TEST_ROOT"/result dependencies.nix --post-build-hook "$pushToStor export BUILD_HOOK_ONLY_OUT_PATHS=$([ ! "$NIX_TESTS_CA_BY_DEFAULT" ]) nix-build -o "$TEST_ROOT"/result-mult multiple-outputs.nix -A a.first --post-build-hook "$pushToStore" +if isDaemonNewer "2.33.0pre20251029"; then + # Regression test for issue #14287: `--check` should re-run post build + # hook, even though nothing is getting newly registered. + export HOOK_DEST=$TEST_ROOT/listing + # Needed so the hook will get the above environment variable. + restartDaemon + nix-build -o "$TEST_ROOT"/result-mult multiple-outputs.nix --check -A a.first --post-build-hook "$PWD/build-hook-list-paths.sh" + grepQuiet a-first "$HOOK_DEST" + grepQuiet a-second "$HOOK_DEST" + unset HOOK_DEST +fi + clearStore # Ensure that the remote store contains both the runtime and build-time From 9e4177bc674733ed5cec32c6ffdd12ee3ca16ece Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 17 Oct 2025 13:07:41 -0400 Subject: [PATCH 08/18] Fix issue #14287 The test added in the previous commit now passes. Co-authored-by: Eelco Dolstra (cherry picked from commit de192794c9f5a4dbd4fb6735277140f2161d9d79) --- src/libstore/build/derivation-building-goal.cc | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/libstore/build/derivation-building-goal.cc b/src/libstore/build/derivation-building-goal.cc index 001816ca0..310b2bf84 100644 --- a/src/libstore/build/derivation-building-goal.cc +++ b/src/libstore/build/derivation-building-goal.cc @@ -860,7 +860,15 @@ Goal::Co DerivationBuildingGoal::tryToBuild() { builder.reset(); StorePathSet outputPaths; - for (auto & [_, output] : builtOutputs) { + /* In the check case we install no store objects, and so + `builtOutputs` is empty. However, per issue #14287, there is + an expectation that the post-build hook is still executed. + (This is useful for e.g. logging successful deterministic rebuilds.) + + In order to make that work, in the check case just load the + (preexisting) infos from scratch, rather than relying on what + `DerivationBuilder` returned to us. */ + for (auto & [_, output] : buildMode == bmCheck ? checkPathValidity(initialOutputs).second : builtOutputs) { // for sake of `bmRepair` worker.markContentsGood(output.outPath); outputPaths.insert(output.outPath); From 939f81c2e6fb998ca5a45540abac145e1aa5bdac Mon Sep 17 00:00:00 2001 From: bryango Date: Wed, 29 Oct 2025 18:09:42 +0800 Subject: [PATCH 09/18] zsh/completion: put compdef on first line Some zsh setups (including mine) do not load the completion if `#compdef` is not on the first line. So we move the `# shellcheck` comment to the second line to avoid this issue. (cherry picked from commit 956fffdd6f196fc5b61057e19d43a8b275369649) --- misc/zsh/completion.zsh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/zsh/completion.zsh b/misc/zsh/completion.zsh index eb26a16cb..6146455fe 100644 --- a/misc/zsh/completion.zsh +++ b/misc/zsh/completion.zsh @@ -1,5 +1,5 @@ -# shellcheck disable=all #compdef nix +# shellcheck disable=all function _nix() { local ifs_bk="$IFS" From f566957dc4a827ec5b5ee24f5f37aaa5183a95d7 Mon Sep 17 00:00:00 2001 From: Bernardo Meurer Costa Date: Wed, 1 Oct 2025 21:20:33 +0000 Subject: [PATCH 10/18] fix(libstore/build/derivation-goal): don't assert on partially valid outputs Fixes: #14130 (cherry picked from commit 9eecee3d4e28634ef11d0044bb2e84bd8b13f2c7) --- src/libstore/build/derivation-goal.cc | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 2e57c1708..cd4a2df6f 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -182,7 +182,19 @@ Goal::Co DerivationGoal::haveDerivation() } } - assert(success.builtOutputs.count(wantedOutput) > 0); + /* If the wanted output is not in builtOutputs (e.g., because it + was already valid and therefore not re-registered), we need to + add it ourselves to ensure we return the correct information. */ + if (success.builtOutputs.count(wantedOutput) == 0) { + debug( + "BUG! wanted output '%s' not in builtOutputs, working around by adding it manually", wantedOutput); + auto realisation = assertPathValidity(); + realisation.id = DrvOutput{ + .drvHash = outputHash, + .outputName = wantedOutput, + }; + success.builtOutputs.emplace(wantedOutput, std::move(realisation)); + } } } From a24df3d4e5ba03570294abfb49660b5258337484 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Fri, 31 Oct 2025 23:12:54 +0300 Subject: [PATCH 11/18] meson: Also split version string at '+' for Darwin (cherry picked from commit 1ca6e9ef54bf4bb09583355d34e79d0e9eb0caf6) --- nix-meson-build-support/common/meson.build | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nix-meson-build-support/common/meson.build b/nix-meson-build-support/common/meson.build index 2944a733b..595503f61 100644 --- a/nix-meson-build-support/common/meson.build +++ b/nix-meson-build-support/common/meson.build @@ -42,7 +42,7 @@ if cxx.get_id() == 'clang' add_project_arguments('-fpch-instantiate-templates', language : 'cpp') endif -# Darwin ld doesn't like "X.Y.Zpre" -nix_soversion = meson.project_version().split('pre')[0] +# Darwin ld doesn't like "X.Y.ZpreABCD+W" +nix_soversion = meson.project_version().split('+')[0].split('pre')[0] subdir('assert-fail') From ed09f1b4d986d3588b12eea56f392c0a6d79c422 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Fri, 31 Oct 2025 22:42:43 +0300 Subject: [PATCH 12/18] libfetchers: Restore plain git inputs recognition Accidentally broken in dbc235cc62b0f26d459c1df7b4659ff15abdc718. Adds a bit of tests for this, even though this protocol is mostly deprecated everywhere. (cherry picked from commit ade3d5d746894e5a8bce8ce2d8d9042526036904) --- src/libfetchers/git.cc | 3 +-- src/libflake-tests/flakeref.cc | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 9334dc1cb..c8311c17f 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -164,8 +164,7 @@ struct GitInputScheme : InputScheme { std::optional inputFromURL(const Settings & settings, const ParsedURL & url, bool requireTree) const override { - auto parsedScheme = parseUrlScheme(url.scheme); - if (parsedScheme.application != "git") + if (url.scheme != "git" && parseUrlScheme(url.scheme).application != "git") return {}; auto url2(url); diff --git a/src/libflake-tests/flakeref.cc b/src/libflake-tests/flakeref.cc index e2cb91bb8..3879a5ba1 100644 --- a/src/libflake-tests/flakeref.cc +++ b/src/libflake-tests/flakeref.cc @@ -199,6 +199,28 @@ INSTANTIATE_TEST_SUITE_P( .description = "flake_id_ref_branch_ignore_empty_segments_ref_rev", .expectedUrl = "flake:nixpkgs/branch/2aae6c35c94fcfb415dbe95f408b9ce91ee846ed", }, + InputFromURLTestCase{ + .url = "git://somewhere/repo?ref=branch", + .attrs = + { + {"type", Attr("git")}, + {"ref", Attr("branch")}, + {"url", Attr("git://somewhere/repo")}, + }, + .description = "plain_git_with_ref", + .expectedUrl = "git://somewhere/repo?ref=branch", + }, + InputFromURLTestCase{ + .url = "git+https://somewhere.aaaaaaa/repo?ref=branch", + .attrs = + { + {"type", Attr("git")}, + {"ref", Attr("branch")}, + {"url", Attr("https://somewhere.aaaaaaa/repo")}, + }, + .description = "git_https_with_ref", + .expectedUrl = "git+https://somewhere.aaaaaaa/repo?ref=branch", + }, InputFromURLTestCase{ // Note that this is different from above because the "flake id" shorthand // doesn't allow this. From 7b41563055a9c4bd446bcb61bed584eb3c27c075 Mon Sep 17 00:00:00 2001 From: Sergei Zimmerman Date: Sat, 1 Nov 2025 00:36:15 +0300 Subject: [PATCH 13/18] libstore: Improve store-reference back-compat with IPv6 ZoneId literals This restores the pre-2.31 handling of ZoneID identifiers in store references. It's the only place we reasonably care about this back-compat. (cherry picked from commit 8dbc2475f710982a7a6b3f89ae686c586e6e2a42) --- .../ssh_unbracketed_ipv6_4.txt | 1 + .../ssh_unbracketed_ipv6_5.txt | 1 + .../ssh_unbracketed_ipv6_6.txt | 1 + .../ssh_unbracketed_ipv6_7.txt | 1 + .../ssh_unbracketed_ipv6_8.txt | 1 + .../ssh_unbracketed_ipv6_9.txt | 1 + src/libstore-tests/store-reference.cc | 60 +++++++++++++++++++ src/libstore/store-reference.cc | 28 ++++++++- 8 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_4.txt create mode 100644 src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_5.txt create mode 100644 src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_6.txt create mode 100644 src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_7.txt create mode 100644 src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_8.txt create mode 100644 src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_9.txt diff --git a/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_4.txt b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_4.txt new file mode 100644 index 000000000..e093c3f30 --- /dev/null +++ b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_4.txt @@ -0,0 +1 @@ +ssh://userinfo@[fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%eth0]?a=b&c=d \ No newline at end of file diff --git a/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_5.txt b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_5.txt new file mode 100644 index 000000000..8375d3c6d --- /dev/null +++ b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_5.txt @@ -0,0 +1 @@ +ssh://userinfo@[fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%25eth0]?a=b&c=d \ No newline at end of file diff --git a/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_6.txt b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_6.txt new file mode 100644 index 000000000..f5a09c2f7 --- /dev/null +++ b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_6.txt @@ -0,0 +1 @@ +ssh://userinfo@fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%25?a=b&c=d \ No newline at end of file diff --git a/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_7.txt b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_7.txt new file mode 100644 index 000000000..3bef5e73f --- /dev/null +++ b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_7.txt @@ -0,0 +1 @@ +ssh://userinfo@fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%eth0?a=b&c=d \ No newline at end of file diff --git a/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_8.txt b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_8.txt new file mode 100644 index 000000000..3db9f9910 --- /dev/null +++ b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_8.txt @@ -0,0 +1 @@ +ssh://fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%eth0?a=b&c=d \ No newline at end of file diff --git a/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_9.txt b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_9.txt new file mode 100644 index 000000000..ad199cfde --- /dev/null +++ b/src/libstore-tests/data/store-reference/ssh_unbracketed_ipv6_9.txt @@ -0,0 +1 @@ +ssh://fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%eth0 \ No newline at end of file diff --git a/src/libstore-tests/store-reference.cc b/src/libstore-tests/store-reference.cc index 7b42b45a2..a52b92b78 100644 --- a/src/libstore-tests/store-reference.cc +++ b/src/libstore-tests/store-reference.cc @@ -183,4 +183,64 @@ static StoreReference sshIPv6AuthorityWithUserinfoAndParams{ URI_TEST_READ(ssh_unbracketed_ipv6_3, sshIPv6AuthorityWithUserinfoAndParams) +static const StoreReference sshIPv6AuthorityWithUserinfoAndParamsAndZoneId{ + .variant = + StoreReference::Specified{ + .scheme = "ssh", + .authority = "userinfo@[fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%25eth0]", + }, + .params = + { + {"a", "b"}, + {"c", "d"}, + }, +}; + +URI_TEST_READ(ssh_unbracketed_ipv6_4, sshIPv6AuthorityWithUserinfoAndParamsAndZoneId) +URI_TEST_READ(ssh_unbracketed_ipv6_5, sshIPv6AuthorityWithUserinfoAndParamsAndZoneId) + +static const StoreReference sshIPv6AuthorityWithUserinfoAndParamsAndZoneIdTricky{ + .variant = + StoreReference::Specified{ + .scheme = "ssh", + .authority = "userinfo@[fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%2525]", + }, + .params = + { + {"a", "b"}, + {"c", "d"}, + }, +}; + +// Non-standard syntax where the IPv6 literal appears without brackets. In +// this case don't considering %25 to be a pct-encoded % and just take it as a +// literal value. 25 is a perfectly legal ZoneId value in theory. +URI_TEST_READ(ssh_unbracketed_ipv6_6, sshIPv6AuthorityWithUserinfoAndParamsAndZoneIdTricky) +URI_TEST_READ(ssh_unbracketed_ipv6_7, sshIPv6AuthorityWithUserinfoAndParamsAndZoneId) + +static const StoreReference sshIPv6AuthorityWithParamsAndZoneId{ + .variant = + StoreReference::Specified{ + .scheme = "ssh", + .authority = "[fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%25eth0]", + }, + .params = + { + {"a", "b"}, + {"c", "d"}, + }, +}; + +URI_TEST_READ(ssh_unbracketed_ipv6_8, sshIPv6AuthorityWithParamsAndZoneId) + +static const StoreReference sshIPv6AuthorityWithZoneId{ + .variant = + StoreReference::Specified{ + .scheme = "ssh", + .authority = "[fea5:23e1:3916:fc24:cb52:2837:2ecb:ea8e%25eth0]", + }, +}; + +URI_TEST_READ(ssh_unbracketed_ipv6_9, sshIPv6AuthorityWithZoneId) + } // namespace nix diff --git a/src/libstore/store-reference.cc b/src/libstore/store-reference.cc index 96ee829d0..01e197be7 100644 --- a/src/libstore/store-reference.cc +++ b/src/libstore/store-reference.cc @@ -121,7 +121,27 @@ StoreReference StoreReference::parse(const std::string & uri, const StoreReferen * greedily assumed to be the part of the host address. */ auto authorityString = schemeAndAuthority->authority; auto userinfo = splitPrefixTo(authorityString, '@'); - auto maybeIpv6 = boost::urls::parse_ipv6_address(authorityString); + /* Back-compat shim for ZoneId specifiers. Technically this isn't + * standard, but the expectation is this works with the old syntax + * for ZoneID specifiers. For the full story behind the fiasco that + * is ZoneID in URLs look at [^]. + * [^]: https://datatracker.ietf.org/doc/html/draft-schinazi-httpbis-link-local-uri-bcp-03 + */ + + /* Fish out the internals from inside square brackets. It might be that the pct-sign is unencoded and that's + * why we failed to parse it previously. */ + if (authorityString.starts_with('[') && authorityString.ends_with(']')) { + authorityString.remove_prefix(1); + authorityString.remove_suffix(1); + } + + auto maybeBeforePct = splitPrefixTo(authorityString, '%'); + bool hasZoneId = maybeBeforePct.has_value(); + auto maybeZoneId = hasZoneId ? std::optional{authorityString} : std::nullopt; + + std::string_view maybeIpv6S = maybeBeforePct.value_or(authorityString); + auto maybeIpv6 = boost::urls::parse_ipv6_address(maybeIpv6S); + if (maybeIpv6) { std::string fixedAuthority; if (userinfo) { @@ -129,7 +149,11 @@ StoreReference StoreReference::parse(const std::string & uri, const StoreReferen fixedAuthority += '@'; } fixedAuthority += '['; - fixedAuthority += authorityString; + fixedAuthority += maybeIpv6S; + if (maybeZoneId) { + fixedAuthority += "%25"; // pct-encoded percent character + fixedAuthority += *maybeZoneId; + } fixedAuthority += ']'; return { .variant = From ec122cbfdac4e0168c0e815337f4b720c9270c1d Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sun, 2 Nov 2025 14:10:12 +0100 Subject: [PATCH 14/18] flake: Update, nixos-25.05-small -> nixos-25.05 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/d98ce345cdab58477ca61855540999c86577d19d?narHash=sha256-O2CIn7HjZwEGqBrwu9EU76zlmA5dbmna7jL1XUmAId8%3D' (2025-08-26) → 'github:NixOS/nixpkgs/daf6dc47aa4b44791372d6139ab7b25269184d55?narHash=sha256-wxX7u6D2rpkJLWkZ2E932SIvDJW8%2BON/0Yy8%2Ba5vsDU%3D' (2025-10-27) (cherry picked from commit 233bd250d175719896ef4985acb4a41613cb34c9) --- flake.lock | 8 ++++---- flake.nix | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/flake.lock b/flake.lock index cc2b2f27e..63290ef86 100644 --- a/flake.lock +++ b/flake.lock @@ -63,16 +63,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1756178832, - "narHash": "sha256-O2CIn7HjZwEGqBrwu9EU76zlmA5dbmna7jL1XUmAId8=", + "lastModified": 1761597516, + "narHash": "sha256-wxX7u6D2rpkJLWkZ2E932SIvDJW8+ON/0Yy8+a5vsDU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "d98ce345cdab58477ca61855540999c86577d19d", + "rev": "daf6dc47aa4b44791372d6139ab7b25269184d55", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-25.05-small", + "ref": "nixos-25.05", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index a2bdeb0e5..93502d71e 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "The purely functional package manager"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05-small"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.05"; inputs.nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2"; inputs.nixpkgs-23-11.url = "github:NixOS/nixpkgs/a62e6edd6d5e1fa0329b8653c801147986f8d446"; From 828bf74cd09c10f5467fb6bc72d94fcd156c45a1 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 3 Nov 2025 12:01:55 +0100 Subject: [PATCH 15/18] Apply updated nixfmt (cherry picked from commit 81a2809a526e4fcc887d3178c8b48646320a25e8) --- doc/manual/generate-store-types.nix | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/generate-store-types.nix b/doc/manual/generate-store-types.nix index a03d3d621..4e06c7f60 100644 --- a/doc/manual/generate-store-types.nix +++ b/doc/manual/generate-store-types.nix @@ -24,9 +24,9 @@ let in concatStringsSep "\n" (map showEntry storesList); - "index.md" = - replaceStrings [ "@store-types@" ] [ index ] - (readFile ./source/store/types/index.md.in); + "index.md" = replaceStrings [ "@store-types@" ] [ index ] ( + readFile ./source/store/types/index.md.in + ); tableOfContents = let From e6d823e46d38c974af73db2a31551477772a8326 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 5 Nov 2025 15:19:01 +0100 Subject: [PATCH 16/18] nix flake check: Remove incorrect assertion The assumption that no unknown paths can be returned is incorrect. It can happen if a derivation has outputs that are substitutable, but that have references that cannot be substituted (i.e. an incomplete closure in the binary cache). This can easily happen with magic-nix-cache. (cherry picked from commit a828cf777aa3497879aeca407af97b652d1edee7) --- src/nix/flake.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 18be64bba..f4850fa92 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -798,8 +798,6 @@ struct CmdFlakeCheck : FlakeCommand // via substitution, as `nix flake check` only needs to verify buildability, // not actually produce the outputs. auto missing = store->queryMissing(drvPaths); - // Only occurs if `drvPaths` contains a `DerivedPath::Opaque`, which should never happen - assert(missing.unknown.empty()); std::vector toBuild; for (auto & path : missing.willBuild) { From 47ba37528501079892ef549c419bf55008a82c91 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 6 Nov 2025 13:06:08 +0100 Subject: [PATCH 17/18] Don't crash on flakerefs containing newlines Fixes #14311. (cherry picked from commit c1317017e902f34631f2f4598710114d36e84ee4) --- src/libflake/flakeref.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libflake/flakeref.cc b/src/libflake/flakeref.cc index 38979783d..2474c2cc4 100644 --- a/src/libflake/flakeref.cc +++ b/src/libflake/flakeref.cc @@ -80,7 +80,8 @@ std::pair parsePathFlakeRefWithFragment( std::smatch match; auto succeeds = std::regex_match(url, match, pathFlakeRegex); - assert(succeeds); + if (!succeeds) + throw Error("invalid flakeref '%s'", url); auto path = match[1].str(); auto query = decodeQuery(match[3].str(), /*lenient=*/true); auto fragment = percentDecode(match[5].str()); From aa657c16790853a2243c6e431c300f9503c78ac0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 6 Nov 2025 22:10:21 +0100 Subject: [PATCH 18/18] Revert "Merge pull request #14382 from NixOS/backport-14364-to-2.32-maintenance" This reverts commit 5c9481de191230baa4a2faf15bbccfc7877a6751, reversing changes made to 291e8ab6bdeb03a38f3f78c0a6e598675f7bbabb. This is a behaviour change that should be avoided on maintenance branches. --- src/libfetchers/git-utils.cc | 5 ++--- src/libmain/include/nix/main/shared.hh | 2 ++ src/libmain/shared.cc | 16 +++++++--------- src/libstore/gc.cc | 5 +++-- src/libstore/optimise-store.cc | 2 +- src/libutil-tests/util.cc | 1 - src/libutil/include/nix/util/util.hh | 4 +++- src/libutil/util.cc | 14 +++++++++----- src/nix/diff-closures.cc | 3 ++- src/nix/path-info.cc | 2 +- 10 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/libfetchers/git-utils.cc b/src/libfetchers/git-utils.cc index 65587b43a..215418522 100644 --- a/src/libfetchers/git-utils.cc +++ b/src/libfetchers/git-utils.cc @@ -9,7 +9,6 @@ #include "nix/util/users.hh" #include "nix/util/fs-sink.hh" #include "nix/util/sync.hh" -#include "nix/util/util.hh" #include #include @@ -531,12 +530,12 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this auto act = (Activity *) payload; act->result( resFetchStatus, - fmt("%d/%d objects received, %d/%d deltas indexed, %s", + fmt("%d/%d objects received, %d/%d deltas indexed, %.1f MiB", stats->received_objects, stats->total_objects, stats->indexed_deltas, stats->total_deltas, - renderSize(stats->received_bytes))); + stats->received_bytes / (1024.0 * 1024.0))); return getInterrupted() ? -1 : 0; } diff --git a/src/libmain/include/nix/main/shared.hh b/src/libmain/include/nix/main/shared.hh index 43069ba82..47d08a050 100644 --- a/src/libmain/include/nix/main/shared.hh +++ b/src/libmain/include/nix/main/shared.hh @@ -89,6 +89,8 @@ extern volatile ::sig_atomic_t blockInt; /* GC helpers. */ +std::string showBytes(uint64_t bytes); + struct GCResults; struct PrintFreed diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index 19733fb3e..4b36ec98e 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -6,7 +6,6 @@ #include "nix/main/loggers.hh" #include "nix/main/progress-bar.hh" #include "nix/util/signals.hh" -#include "nix/util/util.hh" #include #include @@ -65,19 +64,18 @@ void printMissing(ref store, const MissingPaths & missing, Verbosity lvl) } if (!missing.willSubstitute.empty()) { + const float downloadSizeMiB = missing.downloadSize / (1024.f * 1024.f); + const float narSizeMiB = missing.narSize / (1024.f * 1024.f); if (missing.willSubstitute.size() == 1) { printMsg( - lvl, - "this path will be fetched (%s download, %s unpacked):", - renderSize(missing.downloadSize), - renderSize(missing.narSize)); + lvl, "this path will be fetched (%.2f MiB download, %.2f MiB unpacked):", downloadSizeMiB, narSizeMiB); } else { printMsg( lvl, - "these %d paths will be fetched (%s download, %s unpacked):", + "these %d paths will be fetched (%.2f MiB download, %.2f MiB unpacked):", missing.willSubstitute.size(), - renderSize(missing.downloadSize), - renderSize(missing.narSize)); + downloadSizeMiB, + narSizeMiB); } std::vector willSubstituteSorted = {}; std::for_each(missing.willSubstitute.begin(), missing.willSubstitute.end(), [&](const StorePath & p) { @@ -408,7 +406,7 @@ RunPager::~RunPager() PrintFreed::~PrintFreed() { if (show) - std::cout << fmt("%d store paths deleted, %s freed\n", results.paths.size(), renderSize(results.bytesFreed)); + std::cout << fmt("%d store paths deleted, %s freed\n", results.paths.size(), showBytes(results.bytesFreed)); } } // namespace nix diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 193247aa2..47f40ab8e 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -5,7 +5,6 @@ #include "nix/util/finally.hh" #include "nix/util/unix-domain-socket.hh" #include "nix/util/signals.hh" -#include "nix/util/util.hh" #include "nix/store/posix-fs-canonicalise.hh" #include "store-config-private.hh" @@ -907,7 +906,9 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) #endif ; - printInfo("note: currently hard linking saves %s", renderSize(unsharedSize - actualSize - overhead)); + printInfo( + "note: currently hard linking saves %.2f MiB", + ((unsharedSize - actualSize - overhead) / (1024.0 * 1024.0))); } /* While we're at it, vacuum the database. */ diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc index 3e02fa812..8f2878136 100644 --- a/src/libstore/optimise-store.cc +++ b/src/libstore/optimise-store.cc @@ -312,7 +312,7 @@ void LocalStore::optimiseStore() optimiseStore(stats); - printInfo("%s freed by hard-linking %d files", renderSize(stats.bytesFreed), stats.filesLinked); + printInfo("%s freed by hard-linking %d files", showBytes(stats.bytesFreed), stats.filesLinked); } void LocalStore::optimisePath(const Path & path, RepairFlag repair) diff --git a/src/libutil-tests/util.cc b/src/libutil-tests/util.cc index 32114d9da..c48b97e8e 100644 --- a/src/libutil-tests/util.cc +++ b/src/libutil-tests/util.cc @@ -158,7 +158,6 @@ TEST(renderSize, misc) ASSERT_EQ(renderSize(972, true), " 0.9 KiB"); ASSERT_EQ(renderSize(973, true), " 1.0 KiB"); // FIXME: should round down ASSERT_EQ(renderSize(1024, true), " 1.0 KiB"); - ASSERT_EQ(renderSize(-1024, true), " -1.0 KiB"); ASSERT_EQ(renderSize(1024 * 1024, true), "1024.0 KiB"); ASSERT_EQ(renderSize(1100 * 1024, true), " 1.1 MiB"); ASSERT_EQ(renderSize(2ULL * 1024 * 1024 * 1024, true), " 2.0 GiB"); diff --git a/src/libutil/include/nix/util/util.hh b/src/libutil/include/nix/util/util.hh index 1234937b4..26f03938a 100644 --- a/src/libutil/include/nix/util/util.hh +++ b/src/libutil/include/nix/util/util.hh @@ -104,7 +104,7 @@ N string2IntWithUnitPrefix(std::string_view s) * GiB`. If `align` is set, the number will be right-justified by * padding with spaces on the left. */ -std::string renderSize(int64_t value, bool align = false); +std::string renderSize(uint64_t value, bool align = false); /** * Parse a string into a float. @@ -333,6 +333,8 @@ struct overloaded : Ts... template overloaded(Ts...) -> overloaded; +std::string showBytes(uint64_t bytes); + /** * Provide an addition operator between strings and string_views * inexplicably omitted from the standard library. diff --git a/src/libutil/util.cc b/src/libutil/util.cc index f14bc63ac..383a904ad 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -132,16 +132,15 @@ std::optional string2Float(const std::string_view s) template std::optional string2Float(const std::string_view s); template std::optional string2Float(const std::string_view s); -std::string renderSize(int64_t value, bool align) +std::string renderSize(uint64_t value, bool align) { static const std::array prefixes{{'K', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'}}; size_t power = 0; - double abs_value = std::abs(value); - while (abs_value > 1024 && power < prefixes.size()) { + double res = value; + while (res > 1024 && power < prefixes.size()) { ++power; - abs_value /= 1024; + res /= 1024; } - double res = (double) value / std::pow(1024.0, power); return fmt(align ? "%6.1f %ciB" : "%.1f %ciB", power == 0 ? res / 1024 : res, prefixes.at(power)); } @@ -257,4 +256,9 @@ std::pair getLine(std::string_view s) } } +std::string showBytes(uint64_t bytes) +{ + return fmt("%.2f MiB", bytes / (1024.0 * 1024.0)); +} + } // namespace nix diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc index d36a21d74..cbf842e5c 100644 --- a/src/nix/diff-closures.cc +++ b/src/nix/diff-closures.cc @@ -107,7 +107,8 @@ void printClosureDiff( if (!removed.empty() || !added.empty()) items.push_back(fmt("%s → %s", showVersions(removed), showVersions(added))); if (showDelta) - items.push_back(fmt("%s%s" ANSI_NORMAL, sizeDelta > 0 ? ANSI_RED : ANSI_GREEN, renderSize(sizeDelta))); + items.push_back( + fmt("%s%+.1f KiB" ANSI_NORMAL, sizeDelta > 0 ? ANSI_RED : ANSI_GREEN, sizeDelta / 1024.0)); logger->cout("%s%s: %s", indent, name, concatStringsSep(", ", items)); } } diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc index 146b775e5..fef3ae120 100644 --- a/src/nix/path-info.cc +++ b/src/nix/path-info.cc @@ -141,7 +141,7 @@ struct CmdPathInfo : StorePathsCommand, MixJSON void printSize(std::ostream & str, uint64_t value) { if (humanReadable) - str << fmt("\t%s", renderSize((int64_t) value, true)); + str << fmt("\t%s", renderSize(value, true)); else str << fmt("\t%11d", value); }