Compare commits

..

478 Commits

Author SHA1 Message Date
internal-nix-ci[bot]
ac3532d0f2 Merge pull request #14354 from NixOS/backport-14343-to-2.32-maintenance
[Backport 2.32-maintenance] Revert "libmain: Catch logger exceptions in `handleExceptions`"
2025-10-25 00:00:15 +00:00
Sergei Zimmerman
84dbf182d4 Revert "libmain: Catch logger exceptions in handleExceptions"
This reverts commit 90d1ff4805.

The initial issue with EPIPE was solved in 9f680874c5.
Now this patch does move bad than good by eating up boost::io::format_error that are
bugs.

(cherry picked from commit 4f5af471fb)
2025-10-24 23:28:53 +00:00
internal-nix-ci[bot]
4a27d70132 Merge pull request #14280 from NixOS/backport-14276-to-2.32-maintenance
[Backport 2.32-maintenance] libstore/registerOutputs: Don't try to optimize a non-existent actual…
2025-10-16 22:17:53 +00:00
Sergei Zimmerman
dadb5b01b7 libstore/registerOutputs: Don't try to optimize a non-existent actualPath
Since 3c610df550 this resulted in `getting status of`
errors on paths inside the chroot if a path was already valid. Careful inspection
of the logic shows that if buildMode != bmCheck actualPath gets reassigned to
store.toRealPath(finalDestPath). The only branch that cares about actualPath is
the buildMode == bmCheck case, which doesn't lead to optimisePath anyway.

(cherry picked from commit 4cbcaad435)
2025-10-16 21:46:08 +00:00
internal-nix-ci[bot]
3c39583e55 Merge pull request #14267 from NixOS/backport-14263-to-2.32-maintenance
[Backport 2.32-maintenance] Restore `ServeProto::Command::ImportPaths`
2025-10-16 00:07:32 +00:00
Sergei Zimmerman
a038c92d38 Restore ServeProto::Command::ImportPaths
This partially reverts commit 5e46df973f,
partially reversing changes made to
8c789db05b.

We do this because Hydra, while using the newer version of the protocol,
still uses this command, even though Nix (as a client) doesn't use it.
On that basis, we don't want to remove it (or consider it only part of
the older versions of the protocol) until Hydra no longer uses the
Legacy SSH Protocol.

(cherry picked from commit 0deb492b3d)
2025-10-15 23:38:26 +00:00
internal-nix-ci[bot]
cf6ad228ae Merge pull request #14259 from NixOS/backport-14253-to-2.32-maintenance
[Backport 2.32-maintenance] libfetchers/git-utils: Be more correct about validating refnames
2025-10-15 20:33:35 +00:00
Sergei Zimmerman
44701007b4 libfetchers/git-utils: Be more correct about validating refnames
Turns out there's a much better API for this that doesn't have the
footguns of the previous method.

isLegalRefName is somewhat of a misnomer, since it's mainly used to
validate user inputs that can be either references, branch names,
psedorefs or tags.

(cherry picked from commit 5d1178b817)
2025-10-15 20:08:43 +00:00
internal-nix-ci[bot]
ff1f145992 Merge pull request #14256 from NixOS/backport-14205-to-2.32-maintenance
[Backport 2.32-maintenance] Improved backwards compatibility hack for git URLs using dir=...
2025-10-15 16:31:21 +00:00
Graham Dennis
3519ad2ca6 Improve comment
(cherry picked from commit 8d9e9bc400)
2025-10-15 15:58:18 +00:00
Graham Dennis
16af6a8ed1 Improved backwards compatibility hack for git URLs using dir=... attribute
(cherry picked from commit 43b01b6790)
2025-10-15 15:58:18 +00:00
Eelco Dolstra
549a2e8272 Bump version 2025-10-14 11:26:39 +02:00
internal-nix-ci[bot]
2531dcad75 Merge pull request #14238 from NixOS/backport-14237-to-2.32-maintenance
[Backport 2.32-maintenance] Remove validation of URLs passed to FileTransferRequest verbatim
2025-10-13 21:23:05 +00:00
Sergei Zimmerman
11f9c59140 Remove validation of URLs passed to FileTransferRequest verbatim
CURL is not very strict about validation of URLs passed to it. We
should reflect this in our handling of URLs that we get from the user
in <nix/fetchurl.nix> or builtins.fetchurl. ValidURL was an attempt to
rectify this, but it turned out to be too strict. The only good way to
resolve this is to pass (in some cases) the user-provided string verbatim
to CURL. Other usages in libfetchers still benefit from using structured
ParsedURL and validation though.

nix store prefetch-file --name foo 'https://cdn.skypack.dev/big.js@^5.2.2'
error: 'https://cdn.skypack.dev/big.js@^5.2.2' is not a valid URL: leftover

(cherry picked from commit 47f427a172)
2025-10-13 20:48:15 +00:00
internal-nix-ci[bot]
a25a219e79 Merge pull request #14213 from NixOS/backport-14194-to-2.32-maintenance
[Backport 2.32-maintenance] libutil: Print stack trace on assertion failure
2025-10-11 00:40:50 +00:00
Sergei Zimmerman
f07486b205 libutil: Print stack trace on assertion failure
This change overrides __assert_fail on glibc/musl
to instead call std::terminate that we have a custom
handler for. This ensures that we have more context
to diagnose issues encountered by users in the wild.

(cherry picked from commit 46382ade74)
2025-10-11 00:08:36 +00:00
internal-nix-ci[bot]
9ec98f7844 Merge pull request #14212 from NixOS/backport-14210-to-2.32-maintenance
[Backport 2.32-maintenance] libstore: Fix double-quoting of paths in logs
2025-10-10 23:26:18 +00:00
Sergei Zimmerman
634e1d3b65 libstore: Fix double-quoting of paths in logs
std::filesystem::path is already quoted by boost::format with double quotes (").
(cherry picked from commit f30cb8667b)
2025-10-10 22:54:22 +00:00
internal-nix-ci[bot]
70655061e3 Merge pull request #14202 from NixOS/backport-14199-to-2.32-maintenance
[Backport 2.32-maintenance] packaging: only override `toml11` when necessary
2025-10-09 18:04:05 +00:00
Seth Flynn
da328e6004 packaging: only override toml11 when necessary
v4.4.0 hit Nixpkgs in https://github.com/NixOS/nixpkgs/pull/442682.
Ideally we'd just use that, but this keeps the fallback behavior until
it's more widespread

(cherry picked from commit 0f016f9bf5)
2025-10-09 17:56:10 +00:00
internal-nix-ci[bot]
6b16af8c0e Merge pull request #14197 from NixOS/backport-14191-to-2.32-maintenance
[Backport 2.32-maintenance] libutil: Fix renderAuthorityAndPath unreachable for path:/ URLs
2025-10-09 00:00:28 +00:00
internal-nix-ci[bot]
010b78e0cf Merge pull request #14196 from NixOS/backport-14189-to-2.32-maintenance
[Backport 2.32-maintenance] exportReferencesGraph: Handle heterogeneous arrays
2025-10-08 23:43:51 +00:00
Sergei Zimmerman
98b7654390 libutil: Fix renderAuthorityAndPath unreachable for path:/ URLs
This was mistakenly triggered by path:/ URL, since the `//` would
correspond to 3 empty segments.

(cherry picked from commit 1d8dd77e1d)
2025-10-08 23:24:01 +00:00
Eelco Dolstra
c5799aa62c exportReferencesGraph: Handle heterogeneous arrays
This barfed with

   error: [json.exception.type_error.302] type must be string, but is array

on `nix build github:malt3/bazel-env#bazel-env` because it has a `exportReferencesGraph` with a value like `["string",...["string"]]`.

(cherry picked from commit 94f410b628)
2025-10-08 23:13:09 +00:00
Eelco Dolstra
72e3dd396c Bump version 2025-10-07 17:14:49 +02:00
Eelco Dolstra
d069633b3d Mark official release 2025-10-07 13:30:16 +02:00
Eelco Dolstra
6c21c810b9 Merge pull request #14164 from NixOS/release-notes
Release notes for Nix 2.32
2025-10-07 12:48:53 +02:00
John Ericson
eea6d75783 Merge pull request #14168 from xokdvium/nar-require-contents
libutil: Throw if `str("contents")` not found
2025-10-06 19:39:26 -04:00
Samuel Connelly
242f362567 libutil: Throw if str("contents") not found
This was broken in 7aa3e7e3a5 (since 2.25).
2025-10-07 01:04:49 +03:00
Sergei Zimmerman
0068ee6ca7 Release note for attrset optimization 2025-10-06 22:16:21 +03:00
Eelco Dolstra
1e709554d5 Merge pull request #14050 from NixOS/fix-fetch-to-store-caching
Fix fetchToStore caching
2025-10-06 19:39:41 +02:00
Eelco Dolstra
8f71ef7ede Update doc/manual/source/release-notes/rl-2.32.md
Co-authored-by: Taeer Bar-Yam <Radvendii@users.noreply.github.com>
2025-10-06 19:27:30 +02:00
Eelco Dolstra
8142989e67 Merge pull request #14166 from lovesegfault/nix-rl-notes
docs(release-notes): note fix for fetchTarball/fetchurl substitution
2025-10-06 19:11:18 +02:00
Bernardo Meurer Costa
776038f842 docs(release-notes): note fix for fetchTarball/fetchurl substitution 2025-10-06 17:09:34 +00:00
Eelco Dolstra
f4e44040d4 Release note for external derivation builders 2025-10-06 16:26:29 +02:00
Eelco Dolstra
0376112a51 Organize release notes 2025-10-06 16:11:24 +02:00
Eelco Dolstra
c1761b867b Contributors 2025-10-06 16:11:15 +02:00
Eelco Dolstra
9f6ed70429 release notes: 2.32.0 2025-10-06 16:04:58 +02:00
Eelco Dolstra
35b3557fe4 Merge pull request #14162 from NixOS/fix-windows-build
Don't build getPtsName() on Windows
2025-10-06 14:55:39 +02:00
Eelco Dolstra
8aa0acb9e8 Don't build getPtsName() on Windows
It's not needed.

https://hydra.nixos.org/build/309215536
2025-10-06 13:26:36 +02:00
Eelco Dolstra
5e65584edf Merge pull request #14145 from NixOS/external-derivation-builder
External derivation builders
2025-10-06 12:13:01 +02:00
Eelco Dolstra
e7e2ac97f8 Merge remote-tracking branch 'origin/master' into external-derivation-builder 2025-10-06 11:40:56 +02:00
Eelco Dolstra
e9c5d721d8 ExternalDerivationBuilder: Emit a version field 2025-10-06 11:36:26 +02:00
Eelco Dolstra
68bd2e40f4 ExternalDerivationBuilder: Pass the (scratch) outputs 2025-10-06 11:33:29 +02:00
Eelco Dolstra
6c0d67769d ExternalDerivationBuilder: Pass inputPaths 2025-10-06 11:29:15 +02:00
Eelco Dolstra
e5ae81c21a Merge pull request #14158 from fzakaria/fzakaria/small-clang-tidy-fix
clang-tidy fix for src/libstore/build/derivation-check.cc
2025-10-06 11:11:48 +02:00
Sergei Zimmerman
d5fa131cfb Merge pull request #14161 from Radvendii/exprophasattr-alloc
libexpr: fixup ExprOpHasAttr() to take allocator reference
2025-10-05 22:49:17 +00:00
Taeer Bar-Yam
14b119c948 libexpr: fixup ExprOpHasAttr() to take allocator reference 2025-10-05 18:28:47 -04:00
Sergei Zimmerman
29f3da1305 Merge branch 'master' into fzakaria/small-clang-tidy-fix 2025-10-05 21:27:39 +00:00
John Ericson
34bca9212a Merge pull request #14156 from NixOS/fix-ub
treewide: Squash some user-after-free bugs
2025-10-05 16:49:22 -04:00
John Ericson
cd9c208baf Merge pull request #14159 from NixOS/revert-resolution-goal
Revert #14097, #14022
2025-10-05 16:28:33 -04:00
Sergei Zimmerman
ce749454dc Revert "Merge pull request #14022 from obsidiansystems/derivation-resolution-goal"
This reverts commit d02dca099f, reversing
changes made to 9bd09155ac.
2025-10-05 21:54:59 +03:00
Sergei Zimmerman
7e39ab4dc7 Revert "Merge pull request #14097 from obsidiansystems/light-realisation-improvements"
This reverts commit dc8c1461da, reversing
changes made to 28adcfda32.
2025-10-05 21:54:32 +03:00
Farid Zakaria
06a82da6f5 clang-tidy fix for src/libstore/build/derivation-check.cc 2025-10-05 11:18:30 -07:00
Sergei Zimmerman
be1ade7373 libexpr: Use use-after-move in SampleStack::saveProfile() 2025-10-05 16:57:13 +03:00
Sergei Zimmerman
452ec09fe0 libstore: Fix use-after-move in DerivationGoal::repairClosure 2025-10-05 16:55:41 +03:00
Jörg Thalheim
7ba3ef21a6 Merge pull request #14154 from NixOS/fix-segfault-toView
treewide: Remove toView() because it leads to segfaults when compiled…
2025-10-05 13:38:40 +02:00
Sergei Zimmerman
dce1a893d0 treewide: Remove toView() because it leads to segfaults when compiled with newer nixpkgs
Firstly, this is now available on darwin where the default in llvm 19.
Secondly, this leads to very weird segfaults when building with newer nixpkgs for some reason.
(It's UB after all).

This appears when building with the following:

mesonComponentOverrides = finalAttrs: prevAttrs: {
mesonBuildType = "debugoptimized";
dontStrip = true;
doCheck = false;
separateDebugInfo = false;
preConfigure = (prevAttrs.preConfigure or "") + ''
  case "$mesonBuildType" in
  release|minsize|debugoptimized) appendToVar mesonFlags "-Db_lto=true"  ;;
  *)                              appendToVar mesonFlags "-Db_lto=false" ;;
  esac
'';
};

And with the following nixpkgs input:

nix build ".#nix-cli" -L --override-input nixpkgs "https://releases.nixos.org/nixos/unstable/nixos-25.11pre870157.7df7ff7d8e00/nixexprs.tar.xz"

Stacktrace:

 #0  0x00000000006afdc0 in ?? ()
 #1  0x00007ffff71cebb6 in _Unwind_ForcedUnwind_Phase2 () from /nix/store/41ym1jm1b7j3rhglk82gwg9jml26z1km-gcc-14.3.0-lib/lib/libgcc_s.so.1
 #2  0x00007ffff71cf5b5 in _Unwind_Resume () from /nix/store/41ym1jm1b7j3rhglk82gwg9jml26z1km-gcc-14.3.0-lib/lib/libgcc_s.so.1
 #3  0x00007ffff7eac7d8 in std::basic_ios<char, std::char_traits<char> >::~basic_ios (this=<optimized out>, this=<optimized out>)
     at /nix/store/82kmz7r96navanrc2fgckh2bamiqrgsw-gcc-14.3.0/include/c++/14.3.0/bits/basic_ios.h:286
 #4  std::__cxx11::basic_ostringstream<char, std::char_traits<char>, std::allocator<char> >::basic_ostringstream (this=<optimized out>, this=<optimized out>)
     at /nix/store/82kmz7r96navanrc2fgckh2bamiqrgsw-gcc-14.3.0/include/c++/14.3.0/sstream:806
 #5  nix::SimpleLogger::logEI (this=<optimized out>, ei=...) at ../logging.cc:121
 #6  0x00007ffff7515794 in nix::Logger::logEI (this=0x675450, lvl=nix::lvlError, ei=...) at /nix/store/bkshji3nnxmrmgwa4n2kaxadajkwvn65-nix-util-2.32.0pre-dev/include/nix/util/logging.hh:144
 #7  nix::handleExceptions (programName=..., fun=...) at ../shared.cc:336
 #8  0x000000000047b76b in main (argc=<optimized out>, argv=<optimized out>) at /nix/store/82kmz7r96navanrc2fgckh2bamiqrgsw-gcc-14.3.0/include/c++/14.3.0/bits/new_allocator.h:88
2025-10-05 02:30:21 +03:00
Sergei Zimmerman
35d7719f46 Merge pull request #14149 from Radvendii/exprophasattr-alloc
libexpr: allocate ExprOpHasAttr's AttrPath in Exprs::alloc
2025-10-03 23:50:46 +03:00
Taeer Bar-Yam
39109c05be libexpr: allocate ExprOpHasAttr's AttrPath in Exprs::alloc 2025-10-03 23:26:41 +03:00
Sergei Zimmerman
75826824d0 Merge pull request #14124 from Radvendii/exprselect-alloc
libexpr: allocate ExprSelect's AttrPath in Expr::alloc
2025-10-03 23:25:02 +03:00
Taeer Bar-Yam
76a92985d7 libexpr: allocate ExprSelect's AttrName vector in Expr::alloc 2025-10-03 22:51:23 +03:00
Sergei Zimmerman
862c816498 Merge pull request #14150 from cole-h/fixup-fakessh-check
libstore: fixup fakeSSH check
2025-10-03 22:46:40 +03:00
Cole Helbling
7ec1427fc3 libstore: fixup fakeSSH check
This broke invocations like:

    NIX_SSHOPTS='-p2222 -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no' nix copy /nix/store/......-foo --to ssh-ng://root@localhost

In Nix 2.30.2, fakeSSH was enabled when the "thing I want to connect to"
was plain old "localhost". Previously, this check was written as:

         , fakeSSH(host == "localhost")

Given the above invocation, `host` would have been `root@localhost`, and
thus `fakeSSH` would be `false` because `root@localhost` != `localhost`.

However, since 49ba06175e, `authority.host`
returned _just_ the host (`localhost`, no user) and erroneously enabled
`fakeSSH` in this case, causing `NIX_SSHOPTS` to be ignored (since,
when `fakeSSH` is `true`, `SSHMaster::startCommand` doesn't call
`addCommonSSHOpts`).

`authority.to_string()` accurately returns the expected `root@localhost`
format (given the above invocation), fixing this.
2025-10-03 12:17:17 -07:00
Eelco Dolstra
73e4c40e64 Add test for external-builders 2025-10-03 16:32:32 +02:00
Eelco Dolstra
d5d7ca01b3 Merge pull request #14138 from lovesegfault/nix-fix-4313
fix(libfetchers): substitute fetchTarball and fetchurl
2025-10-03 15:13:06 +02:00
Eelco Dolstra
584ef0ffd3 Add external builders
These are helper programs that execute derivations for specified
system types (e.g. using QEMU to emulate another system type).

To use, set `external-builders`:

  external-builders = [{"systems": ["aarch64-linux"], "program": "/path/to/external-builder.py"}]

The external builder gets one command line argument, the path to a JSON file containing all necessary information about the derivation:

  {
    "args": [...],
    "builder": "/nix/store/kwcyvgdg98n98hqapaz8sw92pc2s78x6-bash-5.2p37/bin/bash",
    "env": {
      "HOME": "/homeless-shelter",
      ...
    },
    "realStoreDir": "/tmp/nix/nix/store",
    "storeDir": "/nix/store",
    "tmpDir": "/tmp/nix-shell.dzQ2hE/nix-build-patchelf-0.14.3.drv-46/build",
    "tmpDirInSandbox": "/build"
  }

Co-authored-by: Cole Helbling <cole.helbling@determinate.systems>
2025-10-03 14:34:13 +02:00
Jörg Thalheim
76ac3758d7 Merge pull request #14144 from lovesegfault/curl-based-s3-pieces
build(libstore): add NIX_WITH_CURL_S3 build option
2025-10-03 09:25:22 +02:00
Bernardo Meurer Costa
27f6417128 build(libstore): add NIX_WITH_CURL_S3 build option
Introduce a new build option 'curl-s3-store' for the curl-based S3
implementation, separate from the existing AWS SDK-based 's3-store'.
The two options are mutually exclusive to avoid conflicts.

Users can enable the new implementation with:
  -Dcurl-s3-store=enabled -Ds3-store=disabled
2025-10-03 03:34:57 +00:00
Sergei Zimmerman
8a8a0c2a4b Merge pull request #14135 from lovesegfault/curl-based-s3-pieces
feat(libstore): add AWS CRT-based credential infrastructure
2025-10-02 22:58:36 +00:00
Sergei Zimmerman
eb67b0df5a Merge pull request #14142 from NixOS/move-settings-http-store
libstore: Move {narinfo,ls,log}-compression settings from BinaryCache…
2025-10-02 21:04:22 +00:00
Sergei Zimmerman
ea14933915 Merge pull request #14139 from osbm/master
docs: Update documentation regarding the flake outputs
2025-10-02 20:17:42 +00:00
Sergei Zimmerman
d2017e0e1a libstore: Move {narinfo,ls,log}-compression settings from BinaryCacheStoreConfig to HttpBinaryCacheStoreConfig
These settings are only implemented for the http store and should not be
there for the file:// stores.
2025-10-02 23:11:16 +03:00
Bernardo Meurer Costa
1e92b61750 fix(libfetchers): substitute fetchTarball and fetchurl
Fixes #4313 by enabling builtins.fetchurl, builtins.fetchTarball to use
binary cache substituters before attempting to download from the
original URL.
2025-10-02 19:33:02 +00:00
osbm
7f3f0f2a0b docs: Update documentation regarding the flake outputs 2025-10-02 10:44:30 +03:00
Bernardo Meurer Costa
a4e792cba7 feat(libstore): add AWS CRT-based credential infrastructure
Add lightweight AWS credential resolution using AWS CRT (Common Runtime)
instead of the full AWS SDK. This provides credential management for the
upcoming curl-based S3 implementation.
2025-10-01 21:53:55 +00:00
John Ericson
dc8c1461da Merge pull request #14097 from obsidiansystems/light-realisation-improvements
Realisation improvements, dummy store support, unit testing
2025-10-01 17:28:26 -04:00
John Ericson
5592bb717b Implement realisation operations on dummy store 2025-10-01 17:05:06 -04:00
John Ericson
e06968ec25 Split out UnkeyedRealisation from Realisation
Realisations are conceptually key-value pairs, mapping `DrvOutputs` (the
key) to information about that derivation output.

This separate the value type, which will be useful in maps, etc., where
we don't want to denormalize by including the key twice.

This matches similar changes for existing types:

| keyed              | unkeyed                |
|--------------------|------------------------|
| `ValidPathInfo`    | `UnkeyedValidPathInfo` |
| `KeyedBuildResult` | `BuildResult`          |
| `Realisation`      | `UnkeyedRealisation`   |
2025-10-01 17:01:26 -04:00
Sergei Zimmerman
28adcfda32 Merge pull request #14119 from NixOS/hide-derivation-internal
libexpr: Move derivation-internal.nix from corepkgsFS to internalFS
2025-10-01 20:58:17 +00:00
Jörg Thalheim
d02dca099f Merge pull request #14022 from obsidiansystems/derivation-resolution-goal
Introduce `DerivationResolutionGoal`, fix substituting a single CA drv output
2025-10-01 22:53:58 +02:00
Jörg Thalheim
9bd09155ac Merge pull request #14136 from Mic92/jitsi
link to jitsi meeting in the PR docs
2025-10-01 22:39:13 +02:00
Sergei Zimmerman
2774e67c60 Merge pull request #14128 from obsidiansystems/expose-dummy-store-for-tests-somewhat
Expose some core implementation details and write a basic unit test for the dummy store
2025-10-01 20:27:37 +00:00
Jörg Thalheim
85d6c8af4d link to jitsi meeting in the PR docs 2025-10-01 22:23:31 +02:00
Sergei Zimmerman
2a0fddc7d5 libexpr: Move derivation-internal.nix from corepkgsFS to internalFS
Best I can tell this was never supposed to be exposed to the user
and has been this way since 2.19.

2.18 did not expose this file to the user:

nix run nix/2.18-maintenance -- eval --expr "import <nix/derivation-internal.nix>"

error: getting status of '/__corepkgs__/derivation-internal.nix': No such file or directory
2025-10-01 23:13:11 +03:00
Sergei Zimmerman
d0c017def5 Merge pull request #14134 from JamiKettunen/fix-libc++-build
libstore: Include missing header to fix compile with libc++ 20
2025-10-01 20:07:03 +00:00
Sergei Zimmerman
30a6cbe90b Merge pull request #14131 from lovesegfault/curl-based-s3-pieces
refactor(libstore): extract S3 URL parsing into separate files
2025-10-01 19:39:46 +00:00
Jami Kettunen
140b08ae3e libstore: Include missing header to fix compile with libc++ 20
https://en.cppreference.com/w/cpp/thread.html

src/libstore/gc.cc:121:39: error: no member named 'sleep_for' in namespace 'std::this_thread'
  121 |                     std::this_thread::sleep_for(std::chrono::milliseconds(100));
      |                     ~~~~~~~~~~~~~~~~~~^
2025-10-01 22:19:08 +03:00
Bernardo Meurer Costa
b72898b2aa refactor(libstore): extract S3 URL parsing into separate files
Move ParsedS3URL from s3.cc/.hh into dedicated s3-url.cc/.hh files.
This separates URL parsing utilities (which are protocol-agnostic) from
the AWS SDK-specific S3Helper implementation, making the code cleaner
and enabling reuse by future curl-based S3 implementation.
2025-10-01 16:11:38 +00:00
John Ericson
251479bdda Merge pull request #14127 from obsidiansystems/registerDrvOutput-no-blanket-unsupported
`Store::registerDrvOutput` make pure virtual
2025-10-01 08:51:18 -04:00
John Ericson
772a38069e Merge pull request #14129 from fzakaria/fzakaria/shellcheck-multiple-6
Remove all shellcheck exclusions
2025-09-30 23:54:53 -04:00
Farid Zakaria
015b639cea shellcheck fix: tests/functional/why-depends.sh 2025-09-30 20:27:51 -07:00
Farid Zakaria
c8ef6dfa5a shellcheck fix: tests/functional/user-envs.sh 2025-09-30 20:27:50 -07:00
Farid Zakaria
13eac5295d shellcheck fix: tests/functional/user-envs.builder.sh 2025-09-30 20:23:02 -07:00
Farid Zakaria
049c4c7546 shellcheck fix: tests/functional/user-envs-test-case.sh 2025-09-30 20:22:11 -07:00
Farid Zakaria
359e73a6db shellcheck fix: tests/functional/user-envs-migration.sh 2025-09-30 20:20:13 -07:00
Farid Zakaria
799cd62ec8 shellcheck fix: tests/functional/toString-path.sh 2025-09-30 20:19:47 -07:00
Farid Zakaria
b349783830 shellcheck fix: tests/functional/supplementary-groups.sh 2025-09-30 20:19:27 -07:00
Farid Zakaria
8c9bfb6e12 shellcheck fix: tests/functional/simple.builder.sh 2025-09-30 20:18:48 -07:00
Farid Zakaria
7266a51412 shellcheck fix: tests/functional/selfref-gc.sh 2025-09-30 20:17:55 -07:00
Farid Zakaria
b8f1a8a0c1 shellcheck fix: tests/functional/selfref-gc.sh 2025-09-30 20:17:55 -07:00
Farid Zakaria
1a5ccbeafc shellcheck fix: tests/functional/secure-drv-outputs.sh 2025-09-30 20:17:55 -07:00
Farid Zakaria
64d828b8c4 shellcheck fix: tests/functional/search.sh 2025-09-30 20:17:55 -07:00
Farid Zakaria
b42ed6a74d shellcheck fix: tests/functional/restricted.sh 2025-09-30 20:17:55 -07:00
Farid Zakaria
d35d86da89 shellcheck fix: tests/functional/repair.sh 2025-09-30 20:17:54 -07:00
Farid Zakaria
06f21e101f shellcheck fix: tests/functional/remote-store.sh 2025-09-30 20:15:34 -07:00
Farid Zakaria
7ed4011990 shellcheck fix: tests/functional/referrers.sh 2025-09-30 20:15:34 -07:00
Farid Zakaria
5d1333bf4b shellcheck fix: tests/functional/recursive.sh 2025-09-30 20:15:34 -07:00
Farid Zakaria
8a36cf4422 shellcheck fix: tests/functional/readfile-context.sh 2025-09-30 20:15:34 -07:00
Farid Zakaria
c8a7719614 shellcheck fix: tests/functional/read-only-store.sh 2025-09-30 20:15:34 -07:00
Farid Zakaria
1492c1bc5d shellcheck fix: tests/functional/push-to-store.sh 2025-09-30 20:15:34 -07:00
Farid Zakaria
a11195d6ce shellcheck fix: tests/functional/push-to-store-old.sh 2025-09-30 20:15:34 -07:00
Farid Zakaria
b951e6e1ed shellcheck fix: tests/functional/pure-eval.sh 2025-09-30 20:15:34 -07:00
Farid Zakaria
bcd8311ec6 shellcheck fix: tests/functional/post-hook.sh 2025-09-30 20:15:33 -07:00
Farid Zakaria
1aaa3dafee shellcheck fix: tests/functional/placeholders.sh 2025-09-30 19:54:29 -07:00
Farid Zakaria
c82aa04a3d shellcheck fix: tests/functional/path-info.sh 2025-09-30 19:53:54 -07:00
Farid Zakaria
112c9d8f54 shellcheck fix: tests/functional/path-from-hash-part.sh 2025-09-30 19:53:33 -07:00
Farid Zakaria
32cbf5f55a shellcheck fix: tests/functional/pass-as-file.sh 2025-09-30 19:52:44 -07:00
John Ericson
9ac306c4df Expose some core implementation details and write a basic unit test for the dummy store
This test currently doesn't use the new-exposed functionality, but with
future changes the tests will be expanded and they will be used.
2025-09-30 14:52:32 -04:00
John Ericson
88bd0c25f2 Store::registerDrvOutput make pure virtual
It should be the responsibility of implementations that don't implement
it to say so.

See also PR #9799, and issue #5729
2025-09-30 14:13:04 -04:00
John Ericson
c97b050a6c Fix ca/eval-store.sh test
The refactor in the last commit fixed the bug it was supposed to fix,
but introduced a new bug in that sometimes we tried to write a resolved
derivation to a store before all its `inputSrcs` were in that store.

The solution is to defer writing the derivation until inside
`DerivationBuildingGoal`, just before we do an actual build. At this
point, we are sure that all inputs in are the store.

This does have the side effect of meaning we don't write down the
resolved derivation in the substituting case, only the building case,
but I think that is actually fine. The store that actually does the
building should make a record of what it built by storing the resolved
derivation. Other stores that just substitute from that store don't
necessary want that derivation however. They can trust the substituter
to keep the record around, or baring that, they can attempt to re
resolve everything, if they need to be audited.
2025-09-30 11:29:21 -04:00
John Ericson
39f6fd9b46 Fix #13247
Resolve the derivation before creating a building goal, in a context
where we know what output(s) we want. That way we have a chance just to
download the outputs we want.

Fix #13247
2025-09-30 11:29:19 -04:00
John Ericson
8f4a739d0f Split out DerivationResolutionGoal
This prepares the way for fixing a few issues.
2025-09-30 11:25:52 -04:00
John Ericson
d76dc2406f Merge pull request #14060 from obsidiansystems/build-result-variant
Use `std::variant` to enforce `BuildResult` invariants
2025-09-30 11:02:13 -04:00
Jörg Thalheim
bc66e131f8 Merge pull request #14120 from lovesegfault/http-binary-cache-compression
feat(libstore/http-binary-cache-store): narinfo/ls/log compression
2025-09-30 12:50:10 +02:00
Jörg Thalheim
6e6f88ac45 add changelog for http binary cache compression 2025-09-30 11:05:20 +02:00
Jörg Thalheim
3fcd33079c add http binary cache test for compression options 2025-09-30 10:35:46 +02:00
Jörg Thalheim
a5facbd2d1 Merge pull request #14121 from obsidiansystems/file-transfer-quit
Some Curl file transfer cleanups
2025-09-30 09:12:08 +02:00
Jörg Thalheim
a5b35ec129 Merge pull request #14106 from Radvendii/exprpath-alloc
libexpr: allocate ExprPath strings in the allocator
2025-09-30 09:04:24 +02:00
John Ericson
e52e801421 Merge pull request #14123 from NixOS/path-tests-pure-eval
libexpr-tests: Add unit tests for broken readDir /. for pure eval
2025-09-30 00:33:51 -04:00
Sergei Zimmerman
a8670e8a7d libexpr-tests: Add unit tests for broken readDir /. for pure eval
A very unfortunate interaction of current filtering with pure eval is
that the following actually leads to `lib.a = {}`. This just adds a unit
test for this broken behavior. This is really good to be done as a unit test
via the in-memory store.

{
  outputs =
    { ... }:
    {
      lib.a = builtins.readDir /.;
    };
}
2025-09-30 03:16:35 +03:00
John Ericson
86fb5b24a9 curlFileTransfer::workerThreadEntry Only call quit if we need to. 2025-09-29 18:10:34 -04:00
John Ericson
1f65b08d94 curlFileTransfer::State:quit emptys the queue
Whoever first calls `quit` now empties the queue, instead of waiting for
the worker thread to do it.

(Note that in the unwinding case, the worker thread is still the first
to call `quit`, though.)
2025-09-29 18:10:34 -04:00
John Ericson
d5402b8527 Encapsulate curlFileTransfer::State:quit
It is allowed to read it, and to set it to `false`, but not to set it
to `true`.
2025-09-29 18:10:34 -04:00
Bernardo Meurer Costa
689fa81dc9 feat(libstore/http-binary-cache-store): narinfo/ls/log compression 2025-09-29 21:53:40 +00:00
Sergei Zimmerman
823c0d1140 Merge pull request #14118 from xokdvium/fix-make-empty-source-accessor
libutil: Create empty directory at the root for makeEmptySourceAccessor
2025-09-29 21:19:02 +00:00
Taeer Bar-Yam
f70b0b599c libexpr: allocate ExprPath strings in the allocator 2025-09-29 17:02:05 -04:00
Sergei Zimmerman
1830f5f967 libutil: Create empty directory at the root for makeEmptySourceAccessor
This is my SNAFU. Accidentally broken in 02c9ac445f.

There's very dubious behavior for 'builtins.readDir /.':

{
  outputs =
    { ... }:
    {
      lib.a = builtins.readDir /.;
    };
}

nix eval /tmp/test-flake#lib.a

Starting from 2.27 this now returns an empty set. This really isn't supposed
to happen, but this change in the semantics of makeEmptySourceAccessor accidentally
changed the behavior of this.
2025-09-29 23:16:28 +03:00
Jörg Thalheim
13a236ba29 Merge pull request #14114 from fzakaria/fzakaria/shellcheck-multiple-4
shellcheck fixes continued
2025-09-29 21:32:19 +02:00
Farid Zakaria
ef17baf50d shellcheck fix: tests/functional/parallel.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
4dc5dbaba2 shellcheck fix: tests/functional/parallel.builder.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
c09cf33a3a shellcheck fix: tests/functional/output-normalization.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
32818483a5 shellcheck fix: tests/functional/optimise-store.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
375529c7e5 shellcheck fix: tests/functional/nix_path.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
fe4e476d13 shellcheck fix: tests/functional/nix-shell.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
78833ca8d0 shellcheck fix: tests/functional/nix-profile.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
cf206ef61e shellcheck fix: tests/functional/nix-daemon-untrusting.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
8c2664ed15 shellcheck fix: tests/functional/nix-copy-ssh.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
ca7414cd18 shellcheck fix: tests/functional/nix-copy-ssh-ng.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
c9fd721be9 shellcheck fix: tests/functional/nix-copy-ssh-common.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
83e203fe45 shellcheck fix: tests/functional/nix-collect-garbage-d.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
2b1a0963f9 shellcheck fix: tests/functional/nix-channel.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
2bfc9019fa shellcheck fix: tests/functional/nix-build.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
794723142b shellcheck fix: tests/functional/nested-sandboxing/command.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
1a71c1ef9f shellcheck fix: tests/functional/nested-sandboxing.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
e26b0c66b0 shellcheck fix: tests/functional/multiple-outputs.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
f2eef5b0a4 shellcheck fix: tests/functional/misc.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
5a13f9fc91 shellcheck fix: tests/functional/logging.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
c4da98c8f4 shellcheck fix: tests/functional/linux-sandbox.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
5341d82428 shellcheck fix: tests/functional/legacy-ssh-store.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
f702101224 shellcheck fix: tests/functional/install-darwin.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
78d9a8d92b shellcheck fix: tests/functional/impure-eval.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
1cd96f22c0 shellcheck fix: tests/functional/impure-derivations.sh 2025-09-29 10:52:17 -07:00
Farid Zakaria
f3a2876c3a shellcheck fix: tests/functional/hash-convert.sh 2025-09-29 10:52:16 -07:00
Robert Hensing
8a968c599d Merge pull request #14116 from fzakaria/fzakaria/shellcheck-multiple-5
shellcheck fixes CA functional tests
2025-09-29 19:48:05 +02:00
Farid Zakaria
4232cb045a Remaining functional/ca tests for shellcheck 2025-09-29 10:25:49 -07:00
Farid Zakaria
5846d9d4dc shellcheck fix: tests/functional/ca/build-dry.sh 2025-09-29 10:12:04 -07:00
Farid Zakaria
745d1f9519 shellcheck fix: tests/functional/ca/build-delete.sh 2025-09-29 10:11:29 -07:00
Jörg Thalheim
fca6d8f1cc Merge pull request #14112 from EphraimSiegfried/make-content-addressed-doc-fix
docs: fix build command in make-content-addressed.md
2025-09-29 18:45:14 +02:00
Farid Zakaria
52b9fb38e0 shellcheck fix: tests/functional/gc-non-blocking.sh 2025-09-29 09:23:41 -07:00
Farid Zakaria
2e5952fb6a shellcheck fix: tests/functional/gc-concurrent2.builder.sh 2025-09-29 09:22:45 -07:00
Farid Zakaria
75df03204b shellcheck fix: tests/functional/gc-concurrent.sh 2025-09-29 09:21:47 -07:00
Farid Zakaria
613bd67574 shellcheck fix: tests/functional/gc-concurrent.builder.sh 2025-09-29 09:20:02 -07:00
Farid Zakaria
4192ca9131 shellcheck fix: tests/functional/gc-auto.sh 2025-09-29 09:18:50 -07:00
Farid Zakaria
08a82f4682 shellcheck fix: tests/functional/formatter.simple.sh 2025-09-29 09:17:24 -07:00
Farid Zakaria
f596c9b8c3 shellcheck fix: tests/functional/flakes/show.sh 2025-09-29 09:16:29 -07:00
Farid Zakaria
cb22518754 shellcheck fix: tests/functional/flakes/run.sh 2025-09-29 09:15:11 -07:00
Farid Zakaria
020f67a653 shellcheck fix: tests/functional/flakes/prefetch.sh 2025-09-29 09:14:41 -07:00
Ephraim Siegfried
121dda0f1f docs: fix build command in make-content-addressed.md 2025-09-29 14:07:26 +02:00
Jörg Thalheim
b6f4788a8f Merge pull request #14110 from Mic92/ptsname
Fix thread-safety issue with ptsname() usage
2025-09-29 13:49:58 +02:00
Jörg Thalheim
f816b9bcb8 Merge pull request #14111 from Mic92/symlinks
Prevent infinite symlink loop in followLinksToStore()
2025-09-29 13:49:19 +02:00
Jörg Thalheim
5ec9138179 Prevent infinite symlink loop in followLinksToStore()
The followLinksToStore() function could hang indefinitely when encountering
symlink cycles outside the Nix store, causing 100% CPU usage and blocking
any operations that use this function.

This affects multiple commands including nix-store --query, --delete,
--verify, nix-env, and nix-copy-closure when given paths with symlink cycles.

The fix adds a maximum limit of 1024 symlink follows (matching the limit
used by canonPath) and throws an error when exceeded, preventing the
infinite loop while preserving the original semantics of stopping at
the first path inside the store.
2025-09-29 12:22:43 +02:00
Jörg Thalheim
a9ffa42dda Fix thread-safety issue with ptsname() usage
Replace non-thread-safe ptsname() calls with a new getPtsName() helper
function that:
- Uses thread-safe ptsname_r() on Linux/BSD platforms
- Uses mutex-protected ptsname() on macOS (which lacks ptsname_r())
2025-09-29 12:01:49 +02:00
Eelco Dolstra
b0431a76f5 Merge pull request #14058 from DeterminateSystems/upstream-RossComputerGuy/feat/expose-logfmt
C API: add log format and verbosity functions
2025-09-29 11:21:43 +02:00
Jörg Thalheim
5e65fa7069 Merge pull request #14109 from Mic92/mingw
fix mingw build
2025-09-29 10:57:43 +02:00
Jörg Thalheim
69eae7770a fix mingw build 2025-09-29 10:29:37 +02:00
Jörg Thalheim
738d141dd9 Merge pull request #14108 from fzakaria/fzakaria/shellcheck-multiple-3
shellcheck fixes for tests/functional/local-overlay-store
2025-09-29 09:58:07 +02:00
Jörg Thalheim
7cbc0f97e7 Merge pull request #14090 from Radvendii/expr-slim
nixexpr: introduce arena to hold ExprString strings
2025-09-29 08:33:10 +02:00
Jörg Thalheim
c5b3567512 Merge pull request #14105 from xokdvium/cpp-bison
libexpr: Switch parser.y to %skeleton lalr1.cc
2025-09-29 08:23:03 +02:00
Farid Zakaria
f394390492 shellcheck fix: tests/functional/local-overlay-store/add-lower-inner.sh 2025-09-28 20:40:08 -07:00
Farid Zakaria
8f0d9412ba shellcheck fix: tests/functional/local-overlay-store/bad-uris.sh 2025-09-28 20:39:40 -07:00
Farid Zakaria
8f14301533 shellcheck fix: tests/functional/local-overlay-store/build-inner.sh 2025-09-28 20:38:46 -07:00
Farid Zakaria
6cae8da29d shellcheck fix: tests/functional/local-overlay-store/check-post-init.sh 2025-09-28 20:37:23 -07:00
Farid Zakaria
bb97f4b07a shellcheck fix: tests/functional/local-overlay-store/common.sh 2025-09-28 20:35:47 -07:00
Farid Zakaria
20665e1c3d shellcheck fix: tests/functional/local-overlay-store/delete-duplicate-inner.sh 2025-09-28 20:32:49 -07:00
Farid Zakaria
326d626ad7 shellcheck fix: tests/functional/local-overlay-store/delete-refs-inner.sh 2025-09-28 20:32:12 -07:00
Farid Zakaria
62b36eba11 shellcheck fix: tests/functional/local-overlay-store/gc-inner.sh 2025-09-28 20:31:09 -07:00
Farid Zakaria
675179a510 shellcheck fix: tests/functional/local-overlay-store/gc.sh 2025-09-28 20:30:02 -07:00
Farid Zakaria
283a9c4c5a shellcheck fix: tests/functional/local-overlay-store/optimise-inner.sh 2025-09-28 20:29:35 -07:00
Farid Zakaria
dbb53de9d3 shellcheck fix: tests/functional/local-overlay-store/redundant-add.sh 2025-09-28 20:28:18 -07:00
Farid Zakaria
0c50d5b25a shellcheck fix: tests/functional/local-overlay-store/redundant-add-inner.sh 2025-09-28 20:27:21 -07:00
Farid Zakaria
1bee4d0988 shellcheck fix: tests/functional/local-overlay-store/redundant-add.sh 2025-09-28 20:25:42 -07:00
Farid Zakaria
4ef4e96788 shellcheck fix: tests/functional/local-overlay-store/remount.sh 2025-09-28 20:25:09 -07:00
Farid Zakaria
c4c95f3d39 shellcheck fix: tests/functional/local-overlay-store/stale-file-handle-inner.sh 2025-09-28 20:24:38 -07:00
Farid Zakaria
e896bf1cb1 shellcheck fix: tests/functional/local-overlay-store/stale-file-handle.sh 2025-09-28 20:24:09 -07:00
Farid Zakaria
3a1ba8e41e shellcheck fix: tests/functional/local-overlay-store/verify-inner.sh 2025-09-28 20:23:19 -07:00
Farid Zakaria
76c9d3885c shellcheck fix: tests/functional/local-overlay-store/verify.sh 2025-09-28 20:22:55 -07:00
John Ericson
676e885f8d Merge pull request #14084 from obsidiansystems/issue-13247-test
Create test for Issue 13247
2025-09-28 19:12:44 -04:00
Sergei Zimmerman
c1f805b856 packaging: Build without symbolic interposition on GCC
This turns out to be a big problem for performance of Bison
generated code, that for whatever reason cannot be made internal
to the shared library. This causes GCC to make a bunch of function
calls go through PLT. Ideally these hot functions (like move/copy ctor) could become
inline in upstream Bison. That will make sure that GCC can do interprocedular
optimizations without -fno-semantic-interposition [^]. Considering that
LLVM already does inlining and whatnot is a good motivation for this change.
I don't know of any case where Nix relies on LD_PRELOAD tricks for the shared
libraries in production use-cases.

[^]: https://maskray.me/blog/2021-05-09-fno-semantic-interposition
2025-09-29 01:46:40 +03:00
Sergei Zimmerman
a8715a2d6e libexpr: Switch parser.y to %skeleton lalr1.cc
Since the parser is now LALR we can easily switch
over to the less ugly sketelon than the default C one.
This would allow us to switch from %union to %define api.value.type variant
in the future to avoid the need for triviall POD types.
2025-09-29 00:58:41 +03:00
John Ericson
241c7cd1f9 Merge pull request #14104 from xokdvium/dead-code-location
libexpr: Remove unused members from ParserLocation
2025-09-28 16:46:50 -04:00
Sergei Zimmerman
0f08feaa58 libexpr: Remove unused members from ParserLocation 2025-09-28 22:57:11 +03:00
Taeer Bar-Yam
eab467ecfb libexpr: introduce arena to hold ExprString strings
1. Saves 24-32 bytes per string (size of std::string)
2. Saves additional bytes by not over-allocating strings (in total we
save ~1% memory)
3. Sets us up to perform a similar transformation on the other Expr
subclasses
4. Makes ExprString trivially moveable (before the string data might
move, causing the Value's pointer to become invalid). This is important
so we can put ExprStrings in an std::vector and refer to them by index

We have introduced a string copy in ParserState::stripIndentation().
This could be removed by pre-allocating the right sized string in the
arena, but this adds complexity and doesn't seem to improve performance,
so for now we've left the copy in.
2025-09-28 14:23:13 -04:00
Sergei Zimmerman
c43ea09b9b Merge pull request #14100 from obsidiansystems/add-missing-pragma-once
Add `#pragma once` to `dummy-store.hh`
2025-09-28 16:41:28 +00:00
John Ericson
582d3ee611 Add #pragma once to dummy-store.hh
We should have a lint for this.

In later (yet to be merged at this time) commits, this started causing
problems that only the sanitzer caught.
2025-09-28 12:12:24 -04:00
John Ericson
f7d35dc1dc Merge pull request #14099 from xokdvium/fix-assert-failure
libstore: Call canonPath for constructing LocalFSStoreConfig::rootDir
2025-09-28 12:06:02 -04:00
Sergei Zimmerman
0866ba0b4a libstore: Deduplicate LocalFSStoreConfig::rootDir initializers
Co-authored-by: John Ericson <John.Ericson@Obsidian.Systems>
2025-09-28 18:38:57 +03:00
John Ericson
a01d52e57b Merge pull request #14063 from obsidiansystems/test-less-macro
Minimize the use of C Macros for characterization tests
2025-09-28 11:14:29 -04:00
John Ericson
87fc579bb7 Merge pull request #14098 from xokdvium/store-references-fixes
libstore: Make all StoreConfig::getReference implementations return s…
2025-09-28 11:11:49 -04:00
Sergei Zimmerman
3a64d3c0da libstore: Call canonPath for constructing LocalFSStoreConfig::rootDir
This mirrors what OptionalPathSetting does. Otherwise we run into
an assertion failure for relative paths specified as the authority + path:

nix build nixpkgs#hello --store "local://a/b"
nix: ../posix-source-accessor.cc:13: nix::PosixSourceAccessor::PosixSourceAccessor(std::filesystem::__cxx11::path&&): Assertion `root.empty() || root.is_absolute()' failed.

This is now diagnosed properly:

error: not an absolute path: 'a/b'

Just as you'd specify the root via a query parameter:

nix build nixpkgs#hello --store "local?root=a/b"
2025-09-28 17:42:19 +03:00
John Ericson
01b2037bc0 Minimize the use of C Macros for characterization tests
Fewer macros is better!

Introduce a new `JsonChacterizationTest` mixin class to help with this.

Also, avoid some needless copies with `GetParam`.

Part of my effort shoring up the JSON formats with #13570.
2025-09-28 09:54:46 -04:00
Sergei Zimmerman
426a72c9cf libstore: Make all StoreConfig::getReference implementations return store parameters
These stragglers have been accidentally left out when implementing the StoreConfig::getReference.
Also HttpBinaryCacheStore::getReference now returns the actual store parameters, not the cacheUri
parameters.
2025-09-28 16:29:12 +03:00
John Ericson
e35abb1102 Create test for issue 13247
This test ends up being skipped, since the bug has not yet been fixed. A
future commit will fix the bug.

Progress on #13247, naturally.
2025-09-27 21:53:14 -04:00
John Ericson
e731c43eae Use std::variant to enforce BuildResult invariants
There is now a clean separation between successful and failing build
results.
2025-09-27 15:56:06 -04:00
John Ericson
43550e8edb Lock down BuildResult::Status enum values
This allows refactoring without changing wire protocol by mistake.
2025-09-27 15:15:20 -04:00
John Ericson
3c610df550 Delete scratch data for CA derivation that produced already-extant output
In the case where the store object doesn't exist, we do correctly move
(rather than copy) the scratch data into place. In this case, the
destination store object already exists, but we still want to clean up
after ourselves.
2025-09-27 15:14:33 -04:00
John Ericson
3bf1268ac6 Merge pull request #14096 from Mic92/concurrency-bugs-2
document thread-unsafe mutation in PosixSourceAccessor
2025-09-27 11:18:56 -04:00
Sergei Zimmerman
b9571b6e52 Merge pull request #13508 from CertainLach/push-oyyysvytlnpr
fix: wait on incomplete assignment in REPL
2025-09-27 10:43:41 +00:00
Yaroslav Bolyukin
0a3eb22360 fix: wait on incomplete assignment in REPL
Fixes: https://github.com/NixOS/nix/issues/13507
2025-09-27 13:25:27 +03:00
Jörg Thalheim
866c9179a0 document thread-unsafe mutation in PosixSourceAccessor 2025-09-26 23:31:56 +02:00
Jörg Thalheim
7817239644 Merge pull request #14092 from rszyma/fix-devshell-attrpath-in-docs
doc: Fix invalid devshell attrpath
2025-09-26 21:01:02 +02:00
Jörg Thalheim
6721ef5feb Merge branch 'master' into fix-fetch-to-store-caching 2025-09-26 20:44:39 +02:00
Jörg Thalheim
be92b18add Merge pull request #14083 from fzakaria/fzakaria/shellcheck-multiple-2
shellcheck fixes
2025-09-26 20:42:43 +02:00
rszyma
7bd67cd8dc doc: Fix invalid devshell attrpath
`native-clangStdenvPackages` devshell attrpath was being mentioned in
development docs, but doesn't work anymore (since 69fde530).
2025-09-26 19:49:36 +02:00
Jörg Thalheim
b5f765b7eb Merge pull request #14047 from Radvendii/eval-memory
libexpr: move eval memory allocation to own struct
2025-09-26 19:24:30 +02:00
Farid Zakaria
2a6724299a shellcheck fix: tests/functional/flakes/follow-paths.sh 2025-09-26 08:41:30 -07:00
Farid Zakaria
6fc8f04ecb shellcheck fix: tests/functional/flakes/flakes.sh 2025-09-26 08:41:30 -07:00
Farid Zakaria
ac5615dd91 shellcheck fix: tests/functional/flakes/config.sh 2025-09-26 08:41:30 -07:00
Farid Zakaria
26a10453c3 shellcheck fix: tests/functional/flakes/check.sh 2025-09-26 08:41:30 -07:00
Farid Zakaria
9bf8e7b730 shellcheck fix: tests/functional/flakes/absolute-paths.sh 2025-09-26 08:41:30 -07:00
Farid Zakaria
8839bab84d shellcheck fix: completion files 2025-09-26 08:41:30 -07:00
Farid Zakaria
f8e351cd94 shellcheck fix: tests/functional/fixed 2025-09-26 08:41:30 -07:00
Farid Zakaria
4cec876319 shellcheck fix: tests/functional/fetchMercurial.sh 2025-09-26 08:41:30 -07:00
Farid Zakaria
c4c3524318 shellcheck fix: tests/functional/fetchGitVerification.sh 2025-09-26 08:41:29 -07:00
Tristan Ross
bb6a4dccdf libutil-c: add nix_set_verbosity function 2025-09-26 08:31:23 -07:00
Eelco Dolstra
8aa4669328 Merge pull request #14086 from getchoo-contrib/getchoo/help-pure-eval
nix-cli: use pure/restricted eval for help pages
2025-09-26 11:32:14 +02:00
Seth Flynn
ff82de86da nix-cli: use pure/restricted eval for help pages
This avoids any complications that can arise from the environment
affecting evaluation of the help pages (which don't need to be calling
out to anything external anyways)

A recent example of one of these problems is
https://github.com/NixOS/nix/issues/14085, which would break help pages
by causing them to make invalid calls to the dummy store they're
evaluated with

Fixes: https://github.com/NixOS/nix/issues/14062
Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
2025-09-26 02:05:58 -04:00
Taeer Bar-Yam
7b3c193bd3 libexpr: move eval memory allocation to own struct
Co-authored-by: eldritch horrors <pennae@lix.systems>
Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>

See original commit on lix:
f5754dc90a
2025-09-26 00:40:43 +03:00
Farid Zakaria
53ad2433b4 shellcheck fix: tests/functional/fetchGitSubmodules.sh 2025-09-25 13:09:36 -07:00
Farid Zakaria
ea035ae165 shellcheck fix: tests/tests/functional/dump-db.sh 2025-09-25 13:07:41 -07:00
John Ericson
89141f1d67 Merge pull request #14082 from fzakaria/fzakaria/shellcheck-multiple
shellcheck: multiple file fixes
2025-09-25 15:59:43 -04:00
Farid Zakaria
1619409bf2 shellcheck fix: tests/functional/fetchGitRefs.sh 2025-09-25 12:45:43 -07:00
Farid Zakaria
32e1b5209b shellcheck fix: tests/functional/fetchGit.sh 2025-09-25 12:45:43 -07:00
Farid Zakaria
d07dd92db3 shellcheck fix: tests/functional/fetchClosure.sh 2025-09-25 12:45:43 -07:00
Farid Zakaria
230da1cbe7 shellcheck fix: tests/functional/export.sh 2025-09-25 12:45:40 -07:00
Tristan Ross
cf595b81d5 libmain-c: add nix_set_log_format function 2025-09-25 10:57:48 -07:00
Farid Zakaria
9e3c502521 shellcheck fix: tests/functional/extra-sandbox-profile.sh 2025-09-25 10:35:26 -07:00
Farid Zakaria
a209748ec0 shellcheck fix: tests/functional/export-graph.sh 2025-09-25 10:35:01 -07:00
Farid Zakaria
b8c24cdaef shellcheck fix: tests/functional/eval-store.sh 2025-09-25 10:33:40 -07:00
Jörg Thalheim
e3d62f35ea Merge pull request #14075 from fzakaria/fzakaria/shellcheck-functional-dump-db
shellcheck fix functional/dump-db.sh
2025-09-25 19:31:49 +02:00
Farid Zakaria
dc69e2e520 shellcheck fix: tests/functional/dyn-drv/recursive-mod-json.sh 2025-09-25 10:31:06 -07:00
Farid Zakaria
119489f253 shellcheck fix: tests/functional/dyn-drv/old-daemon-error-hack.sh 2025-09-25 10:30:03 -07:00
Farid Zakaria
412e51215f shellcheck fix: functional/dyn-drv/eval-outputOf.sh 2025-09-25 10:29:27 -07:00
Eelco Dolstra
4b9735b761 Test against uncacheable paths
This is to test the non-functional property that most paths should be
cacheable. We've had frequent cases where caching broken but we didn't
notice.
2025-09-25 11:30:11 -04:00
Eelco Dolstra
1d130492d7 Mount inputs on storeFS to restore fetchToStore() caching
fetchToStore() caching was broken because it uses the fingerprint of
the accessor, but now that the accessor (typically storeFS) is a
composite (like MountedSourceAccessor or AllowListSourceAccessor),
there was no fingerprint anymore. So fetchToStore now uses the new
getFingerprint() method to get the specific fingerprint for the
subpath.
2025-09-25 11:30:11 -04:00
Eelco Dolstra
ec6d5c7de3 Path fetcher: Simplify fingerprint computation 2025-09-25 11:21:14 -04:00
Eelco Dolstra
3450a72ba0 Git fetcher: Make dirty repos with no commits cacheable 2025-09-25 11:20:00 -04:00
Eelco Dolstra
28d11c5bcc Add SourceAccessor::getFingerprint()
This returns the fingerprint for a specific subpath. This is intended
for "composite" accessors like MountedSourceAccessor, where different
subdirectories can have different fingerprints.
2025-09-25 11:20:00 -04:00
Eelco Dolstra
55c7ef9d40 SourceAccessor: Make lstat() virtual
With FilteringSourceAccessor, lstat() needs to throw a different
exception if the path is inaccessible than if it doesn't exist.
2025-09-25 11:20:00 -04:00
John Ericson
46095284f1 Merge pull request #14080 from NixOS/storeFS-prep
Some `storeFS` and similar cleanup
2025-09-25 10:50:25 -04:00
Jörg Thalheim
099a74e9f4 Merge pull request #14041 from getchoo-contrib/getchoo/cache-substituted-inputs
libfetchers: avoid re-copying substituted inputs
2025-09-25 13:27:02 +02:00
Seth Flynn
74305d5260 libfetchers: avoid re-copying substituted inputs
Previously, Nix would not create a cache entry for substituted/cached
inputs

This led to severe slowdowns in some scenarios where a large input (like
Nixpkgs) had already been unpacked to the store but didn't exist in a
users cache, as described in https://github.com/NixOS/nix/issues/11228

Using the same method as https://github.com/NixOS/nix/pull/12911, we can
create a cache entry for the fingerprint of substituted/cached inputs
and avoid this problem entirely
2025-09-25 04:04:57 -04:00
Jörg Thalheim
534b29068a Merge pull request #14071 from fzakaria/fzakaria/shellcheck-functional-db-migration
shellcheck fix functional/db-migration.sh
2025-09-25 09:44:09 +02:00
Jörg Thalheim
697c704f28 Merge branch 'master' into fzakaria/shellcheck-functional-db-migration 2025-09-25 09:26:32 +02:00
Farid Zakaria
6e2c11e296 shellcheck fix functional/dump-db.sh
Add back the path variable
2025-09-25 09:24:39 +02:00
Jörg Thalheim
bdd3eb400a Merge pull request #14030 from roberth/c-api-item-access
C API: Various improvements to attribute/item access
2025-09-25 09:06:11 +02:00
John Ericson
ae896bebdf Merge pull request #14067 from fzakaria/fzakaria/shellcheck-systemd-multi-user
shellcheck fix scipts/install-systemd-multi-user.sh
2025-09-25 02:50:40 -04:00
Jörg Thalheim
eb04a6d3e3 Merge pull request #14079 from obsidiansystems/clean-up-why-depends
Clean up `nix why-depends` store accessor usage, and put back store dir in output
2025-09-25 08:49:12 +02:00
Jörg Thalheim
a08ae1d024 doc: Add release notes for C API lazy accessors 2025-09-25 08:45:32 +02:00
Jörg Thalheim
1877c477fc Merge branch 'master' into fzakaria/shellcheck-functional-db-migration 2025-09-25 08:35:45 +02:00
Jörg Thalheim
6dcfce0450 Merge branch 'master' into fzakaria/shellcheck-systemd-multi-user 2025-09-25 08:35:10 +02:00
Jörg Thalheim
ed3f847225 Merge pull request #14051 from NixOS/atomic-counters
Atomic statistics counters
2025-09-25 08:24:37 +02:00
John Ericson
2898dbe2d9 Merge pull request #14073 from fzakaria/fzakaria/shellcheck-functional-dependencies.builder0
shellcheck fix functional/dependencies.builder0.sh
2025-09-25 02:10:14 -04:00
Eelco Dolstra
e8f951289f EvalState: Don't maintain stats by default
These counters are extremely expensive in a multi-threaded
program. For instance, disabling them speeds up evaluation of the
NixOS/nix/2.21.2 from 32.6s to 17.8s.
2025-09-25 08:03:24 +02:00
Eelco Dolstra
8d257f5510 EvalState: Make the counters atomic 2025-09-25 08:03:24 +02:00
Jörg Thalheim
a6fcca3888 Merge branch 'master' into fzakaria/shellcheck-functional-db-migration 2025-09-25 07:54:43 +02:00
Farid Zakaria
e15c44d46b shellcheck fix functional/db-migration.sh 2025-09-25 07:53:29 +02:00
Jörg Thalheim
f3416913d4 Merge pull request #14069 from fzakaria/fzakaria/shellcheck-compute-levels
shellcheck fix for functional/compute-levels.sh
2025-09-25 07:52:56 +02:00
John Ericson
7f9867b548 Merge pull request #14076 from fzakaria/fzakaria/shellcheck-functional-dyn-drv-build-built-drv
shellcheck fix functional/dyn-drv/build-built-drv.sh
2025-09-25 01:51:21 -04:00
Jörg Thalheim
8ff5aeb367 Merge pull request #14065 from obsidiansystems/realisation-json-unit-test
Add JSON tests for `Realisation`
2025-09-25 07:42:15 +02:00
Jörg Thalheim
3076cc9e84 Merge branch 'master' into fzakaria/shellcheck-systemd-multi-user 2025-09-25 07:40:27 +02:00
Jörg Thalheim
31695f3690 Merge branch 'master' into fzakaria/shellcheck-compute-levels 2025-09-25 07:39:25 +02:00
Jörg Thalheim
a534a27571 Merge branch 'master' into fzakaria/shellcheck-functional-dependencies.builder0 2025-09-25 07:37:30 +02:00
Jörg Thalheim
5d6af1243a Merge branch 'master' into fzakaria/shellcheck-functional-dyn-drv-build-built-drv 2025-09-25 07:35:10 +02:00
John Ericson
9b2f282af5 Simplify the definition of rootFS
It was getting very hard to follow.
2025-09-25 00:14:14 -04:00
Eelco Dolstra
35189c0ae0 Expose the fact that storeFS is a MountedSourceAccessor
This will become useful.
2025-09-25 00:14:14 -04:00
John Ericson
8ef70ef522 Rename one overload to allowPathLegacy
Makes it easier to tell when it is isued.
2025-09-25 00:14:14 -04:00
Eelco Dolstra
339338e166 MountedSourceAccessor: Move into a separate header, add mount method 2025-09-25 00:14:14 -04:00
Farid Zakaria
bc13130497 shellcheck fix tests/functional/dyn-drv/dep-built-drv.sh (#14078) 2025-09-25 03:28:16 +00:00
John Ericson
d26dee20b2 Clean up nix why-depends store accessor usage, and put back store dir in output
With this change, the store-wide `getFSAccessor` has only one usage left
--- the evaluator. If we get rid of that (as is planned), we can then
remove that method altogether, simplifying `Store`. Hurray!

I removed the store dir by mistake from the pretty-printed (for humans)
output in eb643d034f. That change was not
supposed to change output.
2025-09-24 23:06:03 -04:00
John Ericson
d1958e1b2c Merge pull request #14066 from fzakaria/fzakaria/shellcheck-install-multi-user
shellcheck fix scripts/install-multi-user.sh
2025-09-24 23:00:16 -04:00
John Ericson
d0e2babee1 Merge pull request #14072 from fzakaria/fzakaria/shellcheck-functional-debugger
shellcheck fix: functional/debugger.sh
2025-09-24 22:58:32 -04:00
John Ericson
d2595130c6 Merge pull request #14074 from fzakaria/fzakaria/shellcheck-functional-dependencies
shellcheck fix functional/dependencies.sh
2025-09-24 22:57:09 -04:00
John Ericson
db25195bb2 Merge pull request #14077 from fzakaria/fzakaria/shellcheck-dyn-drv-common
shellcheck fix functional/dyn-drv/common.sh
2025-09-24 22:51:55 -04:00
John Ericson
5842aa2cac Merge pull request #14070 from fzakaria/fzakaria/shellcheck-functional-config.sh
shellcheck fix functional/config.sh
2025-09-24 22:47:21 -04:00
John Ericson
1e1c6d1042 Merge pull request #14068 from fzakaria/fzakaria/shellcheck-functional-completions.sh
shellcheck fix tests/functional/completions.sh
2025-09-24 22:44:32 -04:00
Farid Zakaria
614ef6cfb1 shellcheck fix functional/dyn-drv/common.sh 2025-09-24 19:18:30 -07:00
Farid Zakaria
59791082fa shellcheck fix functional/dyn-drv/build-built-drv.sh 2025-09-24 19:17:12 -07:00
Farid Zakaria
98f716f78c Revert change for SC2059 for nix_user_for_core 2025-09-24 19:13:46 -07:00
Farid Zakaria
c7c74fec67 shellcheck fix functional/dependencies.sh 2025-09-24 19:11:00 -07:00
Farid Zakaria
121a8ab3ec shellcheck fix functional/dependencies.builder0.sh 2025-09-24 19:09:21 -07:00
Farid Zakaria
67d43f3b12 shellcheck fix: functional/debugger.sh 2025-09-24 19:06:23 -07:00
Farid Zakaria
832100f543 shellcheck fix functional/config.sh 2025-09-24 18:59:41 -07:00
Farid Zakaria
2732812524 Enable shellcheck for functional/compute-levels.sh 2025-09-24 18:57:30 -07:00
Farid Zakaria
92f8f87dd1 shellcheck fix tests/functional/completions.sh 2025-09-24 18:56:00 -07:00
Farid Zakaria
76b9565414 shellcheck fix scipts/install-systemd-multi-user.sh 2025-09-24 18:52:46 -07:00
Farid Zakaria
c77b15a178 shellcheck fix scripts/install-multi-user.sh 2025-09-24 18:49:53 -07:00
John Ericson
30691c38c2 Add JSON tests for Realisation 2025-09-24 18:09:24 -04:00
John Ericson
9f26c20ebd Merge pull request #14057 from obsidiansystems/derived-path-json
Modernize and test derived path JSON
2025-09-24 18:08:06 -04:00
John Ericson
f2cd6235a7 Merge pull request #14056 from obsidiansystems/better-output-spec-json-tests
Convert `{Extended,}OutputsSpec` JSON tests to characterization tests
2025-09-24 17:41:25 -04:00
Sergei Zimmerman
05279f2ba0 Merge pull request #14061 from xokdvium/k-way-update
libexpr: Preparation for more k-way updates of attribute sets (NFC)
2025-09-25 00:29:54 +03:00
John Ericson
19fa132a8c Merge pull request #14049 from NixOS/per-object-fs-accessor
Create a second `Store::getFSAccessor` for a single store object
2025-09-24 17:29:10 -04:00
John Ericson
a97d6d89d8 Create a second Store::getFSAccessor for a single store object
This is sometimes easier / more performant to implement, and
independently it is also a more convenient interface for many callers.

The existing store-wide `getFSAccessor` is only used for

 - `nix why-depends`
 - the evaluator

I hope we can get rid of it for those, too, and then we have the option
of getting rid of the store-wide method.

Co-authored-by: Sergei Zimmerman <sergei@zimmerman.foo>
2025-09-24 15:49:14 -04:00
Sergei Zimmerman
97ce7759d0 libexpr: Use same naive iterative merging but with evalForUpdate 2025-09-24 21:47:59 +03:00
Jörg Thalheim
0175f7e836 Merge pull request #14059 from xokdvium/formatting-ci
ci: Split formatting check into a separate job, gate other jobs
2025-09-24 13:29:22 +02:00
Jörg Thalheim
00775ad83c Apply suggestion from @getchoo
Co-authored-by: Seth Flynn <getchoo@tuta.io>
2025-09-24 13:14:00 +02:00
Sergei Zimmerman
9789019a50 libexpr: Move *StackReservation constants to gc-small-vector.hh
There are other places where it's useful to use these constants
(notably in eval.hh).
2025-09-24 01:05:18 +03:00
Sergei Zimmerman
b7c6cf900f libexpr: Explicitly define ExprOpUpdate 2025-09-24 01:04:26 +03:00
Sergei Zimmerman
e282175f48 libexpr: Split out MakeBinOpMembers from MakeBinOp 2025-09-24 01:04:23 +03:00
Sergei Zimmerman
35d8ffe01d ci: Split formatting check into a separate job, gate other jobs
This makes the CI fail fast and more explicitly in case the formatting
is incorrect and provides a better error messages. This also ensures
that we don't burn CI on useless checks for code that wouldn't pass lints
anyway.
2025-09-24 00:34:35 +03:00
John Ericson
d23e59bb6b Modernize and test derived path JSON
Old code is now just used for `nix build` --- there is no CLI breaking
change.

Test the new format, too.

The new format is not currently used, but will be used going forward,
for example in the C API.

Progress on #13570
2025-09-23 15:05:56 -04:00
Eelco Dolstra
73d3ab05b6 Merge pull request #14054 from obsidiansystems/to-json-no-copy
Fix `JSON_IMPL` macro to avoid extraneous copies
2025-09-23 20:58:12 +02:00
John Ericson
f24e00710e Convert {Extended,}OutputsSpec JSON tests to characterization tests
This brings them in line with the other tests, and furthers my goals of
separating unit test data from code.

Doing this cleanup as part of my #13570 effort, but strictly-speaking,
this is separate as these data types' JSON never contained and store
paths or store dirs, just simple output name strings.
2025-09-23 14:47:00 -04:00
Eelco Dolstra
03440946cd Merge pull request #14055 from obsidiansystems/rm-pointless-std-visit
Remove some pointless `std::visit`
2025-09-23 20:14:33 +02:00
John Ericson
1c71cb4005 Remove some pointless std::visit
These are not needed, because the `toJSON` methods are already
implemented for the variant wrapper too.
2025-09-23 13:59:27 -04:00
John Ericson
af71a9dbd9 Fix JSON_IMPL macro to avoid extraneous copies
Should take the thing we're serializing by reference.
2025-09-23 13:05:12 -04:00
John Ericson
d9de675357 Merge pull request #14027 from obsidiansystems/more-ca-tests
More floating content-addressing derivation tests
2025-09-22 17:09:39 -04:00
John Ericson
7ea31c6e56 Run multiple outputs and build-delete test for CA drvs also 2025-09-22 16:54:30 -04:00
Jörg Thalheim
af82c847a7 Merge pull request #14048 from roberth/shellcheck
Shellcheck
2025-09-22 21:57:42 +02:00
Robert Hensing
8a9d9bb0e9 pre-commit: Remove exclusion for removed file 2025-09-22 21:06:26 +02:00
Robert Hensing
4183308ee2 tests/func*/characterisation-test-infra: Fix shellcheck 2025-09-22 21:06:26 +02:00
Robert Hensing
993ea14f52 pre-commit: Remove exclude that passes 2025-09-22 21:06:26 +02:00
Robert Hensing
926287d813 tests/func*/ca/common: Fix shellcheck 2025-09-22 21:06:26 +02:00
Robert Hensing
8c31e07cce tests/func*/ca/build-with-garbage-path: Fix shellcheck 2025-09-22 21:06:26 +02:00
John Ericson
5292b0e49e Merge pull request #14038 from NixOS/thread-safe-dummy
libstore: Make writable dummy store thread-safe
2025-09-22 14:26:35 -04:00
Robert Hensing
01357d0808 Merge pull request #14012 from obsidiansystems/drv-realisation-issue-13570
Convert Realisation JSON logic to standard style
2025-09-22 20:25:06 +02:00
Robert Hensing
8b97d14c08 pre-commit: Give reason for ca test wrappers exclusion 2025-09-22 19:57:28 +02:00
Robert Hensing
5af644492b nix develop: Apply shellcheck 2025-09-22 19:31:22 +02:00
Sergei Zimmerman
5915fe3190 Revert "Use shared pointers in the memory source accessor"
This is no longer necessary.

This reverts commit 4df60e639b.
2025-09-22 20:22:17 +03:00
Sergei Zimmerman
c4c92c4c61 libstore: Make writable dummy store thread-safe
Tested by building with b_sanitize=thread and running:

nix flake prefetch-inputs --store "dummy://?read-only=false"

It might make sense to move this utility class out of dummy-store.cc,
but it seems fine for now.
2025-09-22 20:22:16 +03:00
Robert Hensing
43ec36cddf pre-commit: Remove exclude that passes 2025-09-22 19:21:06 +02:00
Robert Hensing
033f13fb1a pre-commit: Remove exclude that passes 2025-09-22 19:19:39 +02:00
Robert Hensing
34e9caaf9b pre-commit: Move zsh exclude 2025-09-22 19:18:52 +02:00
Robert Hensing
6195dfff3a pre-commit: Move fish exclude 2025-09-22 19:17:58 +02:00
Sergei Zimmerman
c71f80b6eb libstore: Implement boost::hash for StorePath 2025-09-22 20:16:11 +03:00
Robert Hensing
1878e788ce misc/bash/completion.sh: Fix shellcheck 2025-09-22 19:15:44 +02:00
Robert Hensing
c12187b15a pre-commit: Drop exclude config/install-sh
This file was part of the make-based build, which has been removed.
2025-09-22 19:12:33 +02:00
Robert Hensing
df23f2b3c1 packaging/dev-shell: Add shellcheck
It was already in the closure for the pre-commit hook installation
script.
2025-09-22 19:09:35 +02:00
John Ericson
91593a237f Convert Realisation JSON logic to standard style
No behavior is changed, just:

- Declare a canonical `nlohmnan::json::adl_serializer`

- Use `json-utils.hh` to shorten code without getting worse error
  messages.

Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
2025-09-22 12:59:37 -04:00
John Ericson
6389f65d63 Rework derivation format release note slightly 2025-09-22 12:59:37 -04:00
Robert Hensing
169a368459 Merge pull request #14040 from NixOS/import-thunk
Ensure that files are parsed/evaluated only once (2nd attempt)
2025-09-22 18:33:00 +02:00
Eelco Dolstra
a0103fc302 Merge pull request #13852 from lovesegfault/warn-no-kvm
feat(libstore): warn when kvm is enabled but /dev/kvm isn't available
2025-09-22 18:31:57 +02:00
Jörg Thalheim
dcaf001d52 Merge pull request #14021 from jfroche/fix/installer-shell-path
installer: prepend nix paths to shell config files instead of appending
2025-09-22 18:27:03 +02:00
John Ericson
74b31d10db Merge pull request #14042 from corngood/cygwin-cross
fix cross build for cygwin
2025-09-22 11:55:40 -04:00
David McFarland
32d4ea8140 fix cross-build for cygwin 2025-09-22 12:27:04 -03:00
Eelco Dolstra
d32d77f4d4 Allocate ExprParseFile on the heap for now
https://github.com/NixOS/nix/pull/14013#issuecomment-3308085755
2025-09-22 12:06:51 +02:00
Robert Hensing
28c0089268 Merge pull request #14039 from NixOS/document-expr-methods
libexpr: Document {eval,maybeThunk} methods
2025-09-22 11:59:55 +02:00
Eelco Dolstra
5f60602875 Reapply "Merge pull request #13938 from NixOS/import-thunk"
This reverts commit fd034814dc.
2025-09-22 11:48:58 +02:00
Robert Hensing
71b27774f0 libexpr: Document {eval,maybeThunk} methods 2025-09-22 01:41:02 +03:00
Robert Hensing
c121c65640 C API: Clarify valid use of bindings ordering 2025-09-21 21:16:46 +02:00
Robert Hensing
ab7feb3898 Merge pull request #14023 from NixOS/dummy-store-path-info
Make dummy store also store path info
2025-09-21 16:58:18 +02:00
Sergei Zimmerman
a453a49043 tests: Tests for writeable dummy in-memory store 2025-09-21 13:36:31 +03:00
Sergei Zimmerman
b66c357b58 libstore: Implement DummyStore::narFromPath 2025-09-21 13:19:50 +03:00
Sergei Zimmerman
3a4c618483 libstore: Fix typo in description of dummy store 2025-09-21 13:19:49 +03:00
Sergei Zimmerman
ed9b377928 libstore: Disable path info cache for dummy store 2025-09-21 13:19:48 +03:00
Sergei Zimmerman
341878ce0f libstore: Make dummy store also store path info 2025-09-21 13:19:47 +03:00
Sergei Zimmerman
02c9ac445f libutil: Improve handling of non-directory root in MemorySourceAccessor 2025-09-21 13:19:45 +03:00
John Ericson
4df60e639b Use shared pointers in the memory source accessor
This allows aliasing, like hard links.
2025-09-21 13:19:44 +03:00
John Ericson
f66b56ad3f Merge pull request #14035 from NixOS/github-fetcher-accessor
libfetchers/github: Use getFSAccessor for downloadFile result
2025-09-20 21:42:54 -04:00
Sergei Zimmerman
e04381edbd libfetchers/github: Use getFSAccessor for downloadFile result
We should use proper abstractions for reading files from the store.
E.g. this caused errors when trying to download github flakes into
an in-memory store in #14023.
2025-09-21 01:12:42 +03:00
Robert Hensing
d8d1fb0e34 Merge pull request #14029 from roberth/enable-libexpr-tests
libexpr-tests: Enable when test setup for building succeeds
2025-09-20 00:57:15 +02:00
Robert Hensing
d0b1caf53a C API: Document and verify NIX_ERR_KEY behavior 2025-09-20 00:13:50 +02:00
Robert Hensing
2d1b412e5b libexpr-tests: Enable when test setup for building succeeds
Accidentally disabled by 9bc218ca3f
2025-09-19 23:39:00 +02:00
Robert Hensing
3d777eb37f C API: Add lazy attribute value and list item accessors 2025-09-19 23:39:00 +02:00
Robert Hensing
7c553a30a9 C API: Improve nix_get_attr_name_byidx() doc 2025-09-19 23:39:00 +02:00
Robert Hensing
0e74b25f62 C API: Fix bounds checking in _byidx functions
The docs weren't 100% clear about bounds checking, but suggested that
errors would be caught.
The bounds checks are cheap compared to the function calls they're in,
so we have no reason to omit them.
2025-09-19 23:39:00 +02:00
John Ericson
773dd61d1c Merge pull request #14026 from lovesegfault/s3-url-tests
test(libstore): additional ParsedS3Url tests
2025-09-19 17:08:36 -04:00
Bernardo Meurer Costa
b63d9fbc87 test(libstore): additional ParsedS3Url tests
Extracted from the work in #13752
2025-09-19 20:52:44 +00:00
Jean-François Roche
a408bc3e30 installer: prepend nix paths to shell config files instead of appending
Some distribution will stop evaluating the shell config file if
they are not running in an interactive shell. As we append the nix
paths to the end of the file, they will not be evaluated.

We better prepend the nix paths to the shell config files to be sure
that once nix is installed, nix path will be available in any shell.

Note that this is already the case for the detsys installer script for
a while: https://github.com/DeterminateSystems/nix-installer/pull/148

Possibly related errors:

https://github.com/NixOS/nix/issues/8061
https://github.com/NixOS/nix/pull/6628
https://github.com/NixOS/nix/issues/2587
2025-09-19 14:06:06 +02:00
Jörg Thalheim
07b96c1d14 Merge pull request #14018 from NixOS/fix-soname-for-real
meson: Fix SONAME for unstable versions
2025-09-19 13:01:04 +02:00
John Ericson
936334638d Merge pull request #14020 from xokdvium/dummy-store-fixes
libstore: Set display prefix for dummy store
2025-09-19 00:00:00 -04:00
John Ericson
8719c2e6b7 Merge pull request #14016 from xokdvium/asan
treewide: Support builds with ASAN, enable in CI
2025-09-18 23:48:23 -04:00
Sergei Zimmerman
72e2b0efea libstore: Set display prefix for dummy store
Otherwise the prefix is «unknown».
2025-09-19 02:28:59 +03:00
Sergei Zimmerman
d5e84383d1 doc: Document building with sanitizers 2025-09-19 01:37:15 +03:00
Sergei Zimmerman
94d37e62fc treewide: Support builds with ASAN, enable in CI
Enables builds with ASAN to catch memory corruption
bugs faster and in CI. This is an incredibly valuable
instrument that must be used as much as possible.

Somewhat based on jade's work from Lix, though there's a lot that
we have to do differently:

19ae87e5ce

Co-authored-by: Jade Lovelace <lix@jade.fyi>
2025-09-19 01:33:57 +03:00
Sergei Zimmerman
7dbfe9f576 meson: Fix SONAME for unstable versions
Replacing the string is not enough [^] for e.g. nixpkgs precise versions:

`2.31pre20250712_b1245123`

[^]: https://github.com/NixOS/nixpkgs/pull/444089
2025-09-18 23:34:27 +03:00
Jörg Thalheim
3a687eeb4f Merge pull request #14015 from eclairevoyant/doc-global-builtins
doc: document global builtins
2025-09-18 08:11:18 +02:00
Jörg Thalheim
4808b0e24f Merge pull request #14014 from xokdvium/fish delete
libexpr-c: Fix mismatched new/delete
2025-09-18 08:08:39 +02:00
éclairevoyant
40b47b9395 doc: document global builtins 2025-09-18 00:04:31 -04:00
Sergei Zimmerman
309d55807c libexpr-c: Fix mismatched new/delete
This leads to ASAN errors:

==1137785==ERROR: AddressSanitizer: new-delete-type-mismatch on 0x523000001d00 in thread T0:
  object passed to delete has wrong type:
  size of the allocated type:   5968 bytes;
  size of the deallocated type: 5968 bytes.
  alignment of the allocated type:   8 bytes;
  alignment of the deallocated type: default-aligned.
2025-09-18 02:58:32 +03:00
Jörg Thalheim
9d8c6a6646 Merge pull request #14013 from xokdvium/revert-13938
Revert "Merge pull request #13938 from NixOS/import-thunk"
2025-09-18 01:23:14 +02:00
Sergei Zimmerman
fd034814dc Revert "Merge pull request #13938 from NixOS/import-thunk"
This has multiple dangling pointer issues that lead to segfaults in e.g.:

nix eval --expr '(builtins.getFlake "github:nixos/nixpkgs/25.05")' --impure

This reverts commit ad175727e4, reversing
changes made to d314750174.
2025-09-18 01:52:46 +03:00
Robert Hensing
b4fcb64276 Merge pull request #13980 from obsidiansystems/drv-json-issue-13570
Make the JSON format for derivation use basename store paths
2025-09-17 23:31:41 +02:00
Robert Hensing
82315c3807 Merge pull request #13987 from xokdvium/bindings-bananza
libexpr: Structural sharing of attrsets
2025-09-17 23:15:05 +02:00
Sergei Zimmerman
0ccb00bb81 libexpr: Add release note for c-api-byidx change 2025-09-17 23:54:46 +03:00
Sergei Zimmerman
6138bc3de3 libexpr: Structural sharing of attrsets
This changes the implementation of Bindings to allow
for a more space-efficient implementation of attribute
set merges. This is accomplished by "layering" over the "base" Bindings.
The top "layer" is naturally the right-hand-side of the update operator //.

Such an implementation leads to significantly better memory usage on
something like nixpkgs:

nix-env --query --available --out-path --file ../nixpkgs --eval-system x86_64-linux > /dev/null

Comparison against 2b0fd88324 for x86_64-linux on nixpkgs f06c7c3b6f5074dbffcf02542fb86af3a5526afa:

| metric                 | mean_before     | mean_after      | mean_diff       | mean_%_change | p_value | t_stat  |
| -                      | -               | -               | -               | -             | -       | -       |
| cpuTime                | 21.1520         | 21.3414         | 0.1894          | 0.7784        | 0.3190  | 1.0219  |
| envs.bytes             | 461451951.6190  | 461451951.6190  | -               | -             | -       | -       |
| envs.elements          | 34344544.8571   | 34344544.8571   | -               | -             | -       | -       |
| envs.number            | 23336949.0952   | 23336949.0952   | -               | -             | -       | -       |
| gc.cycles              | 7.5238          | 7.2857          | -0.2381         | -4.6825       | 0.0565  | -2.0244 |
| gc.heapSize            | 1777848124.9524 | 1252162023.6190 | -525686101.3333 | -29.9472      | 0.0000  | -8.7041 |
| gc.totalBytes          | 3102787383.6190 | 2498431578.6667 | -604355804.9524 | -19.7704      | 0.0000  | -9.3502 |
| list.bytes             | 59928225.9048   | 59928225.9048   | -               | -             | -       | -       |
| list.concats           | 1240028.2857    | 1240028.2857    | -               | -             | -       | -       |
| list.elements          | 7491028.2381    | 7491028.2381    | -               | -             | -       | -       |
| nrAvoided              | 28165342.2381   | 28165342.2381   | -               | -             | -       | -       |
| nrExprs                | 1577412.9524    | 1577412.9524    | -               | -             | -       | -       |
| nrFunctionCalls        | 20970743.4286   | 20970743.4286   | -               | -             | -       | -       |
| nrLookups              | 10867306.0952   | 10867306.0952   | -               | -             | -       | -       |
| nrOpUpdateValuesCopied | 61206062.0000   | 25748169.5238   | -35457892.4762  | -58.8145      | 0.0000  | -8.9189 |
| nrOpUpdates            | 2167097.4286    | 2167097.4286    | -               | -             | -       | -       |
| nrPrimOpCalls          | 12337423.4286   | 12337423.4286   | -               | -             | -       | -       |
| nrThunks               | 29361806.7619   | 29361806.7619   | -               | -             | -       | -       |
| sets.bytes             | 1393822818.6667 | 897587655.2381  | -496235163.4286 | -36.7168      | 0.0000  | -9.1115 |
| sets.elements          | 84504465.3333   | 48270845.9524   | -36233619.3810  | -43.8698      | 0.0000  | -8.9181 |
| sets.number            | 5218921.6667    | 5218921.6667    | -               | -             | -       | -       |
| sizes.Attr             | 16.0000         | 16.0000         | -               | -             | -       | -       |
| sizes.Bindings         | 8.0000          | 24.0000         | 16.0000         | 200.0000      | -       | inf     |
| sizes.Env              | 8.0000          | 8.0000          | -               | -             | -       | -       |
| sizes.Value            | 16.0000         | 16.0000         | -               | -             | -       | -       |
| symbols.bytes          | 1368494.0952    | 1368494.0952    | -               | -             | -       | -       |
| symbols.number         | 109147.1905     | 109147.1905     | -               | -             | -       | -       |
| time.cpu               | 21.1520         | 21.3414         | 0.1894          | 0.7784        | 0.3190  | 1.0219  |
| time.gc                | 1.6011          | 0.8508          | -0.7503         | -37.1507      | 0.0017  | -3.6328 |
| time.gcFraction        | 0.0849          | 0.0399          | -0.0450         | -37.4504      | 0.0035  | -3.3116 |
| values.bytes           | 615968144.7619  | 615968144.7619  | -               | -             | -       | -       |
| values.number          | 38498009.0476   | 38498009.0476   | -               | -             | -       | -       |

Overall this does slow down the evaluator slightly (no more than ~10% in most cases),
but this seems like a very decent tradeoff for shaving off 33% of memory usage.
2025-09-17 23:54:45 +03:00
John Ericson
9d7229a2a4 Make the JSON format for derivation use basename store paths
See #13570 for details --- the idea is that included the store dir in
store paths makes systematic JSON parting with e.g. Serde, Aeson,
nlohmann, or similiar harder.

After talking to Eelco, we are changing the `Derivation` format right
away because not only is `nix derivation` technically experimental, we think it is
also less widely used in practice than, say, `nix path-info`.

Progress on #13570
2025-09-17 16:38:17 -04:00
Robert Hensing
3eb223f4bb Merge pull request #10915 from NixOS/dummy-memory
Use `MemorySourceAccessor` in `DummyStore` so writes work
2025-09-17 22:33:17 +02:00
John Ericson
d5ce8c3caa Use MemorySourceAccessor in DummyStore
Add `read-only` setting to `dummy://` store for back compat.

Test by changing an existing test to use this instead, fixing a TODO.

Co-Authored-By: HaeNoe <git@haenoe.party>
Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
2025-09-17 16:09:53 -04:00
Eelco Dolstra
7ff61576f2 Merge pull request #14009 from xokdvium/fix-authorization
Revert "tests/nixos: Fix daemon store reference in authorization test"
2025-09-17 21:58:44 +02:00
John Ericson
168c24b605 Declare DummyStoreConfig in a header
This will useful for unit tests.
2025-09-17 15:54:30 -04:00
John Ericson
613de9d9cc Add missing #pragma once 2025-09-17 15:21:21 -04:00
Sergei Zimmerman
86ad8d49f9 Revert "tests/nixos: Fix daemon store reference in authorization test"
This reverts commit 695f3bc7e3.
2025-09-17 22:05:26 +03:00
Jörg Thalheim
187520ce88 Merge pull request #14008 from juhp/update-COPYING
COPYING: update to latest lgpl-2.1.txt
2025-09-17 20:15:01 +02:00
Jens Petersen
a66ba324d7 COPYING: update to latest lgpl-2.1.txt (fixes #13758)
$ curl -I https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
:
Last-Modified: Wed, 18 Sep 2024 14:34:04 GMT
ETag: "6733-62265b29fd1ee"
:
2025-09-18 00:45:01 +08:00
Jörg Thalheim
7822bd5692 Merge pull request #14005 from juhp/nix_soversion
nix-meson-build-support/common nix_soversion: fixup removal of 'pre'
2025-09-17 12:19:57 +02:00
Robert Hensing
0b19468368 Merge pull request #13907 from obsidiansystems/store-building-unit-test
C API for building
2025-09-16 19:45:51 +02:00
John Ericson
9bc218ca3f Add new C API for working with derivations
Also test the APIs we just added.
2025-09-16 13:25:36 -04:00
Jens Petersen
ca23c819e0 nix-meson-build-support/common nix_soversion: fixup removal of 'pre'
.strip() removes individual chars whereas .replace() affects whole substring

Thanks @keszybz
2025-09-16 18:31:40 +08:00
Jörg Thalheim
1a69fc6ab5 Merge pull request #14001 from juhp/common-soversion
meson: refactor nix_soversion into nix-meson-build-support/common
2025-09-16 08:50:05 +02:00
Jens Petersen
86bb7c958a meson: refactor nix_soversion into nix-meson-build-support/common
This is a follow-on to #13995 which added soversion to the libraries
2025-09-16 12:54:30 +08:00
Jörg Thalheim
5e17a3f81c Merge pull request #13998 from Mic92/fast-flake-check
nix flake check: Skip substitutable derivations
2025-09-15 21:44:11 +02:00
Seth Flynn
ecdda5798c nix flake check: Skip substitutable derivations
Since `nix flake check` doesn't produce a `result` symlink, it doesn't
actually need to build/substitute derivations that are already known
to have succeeded, i.e. that are substitutable.

This can speed up CI jobs in cases where the derivations have already
been built by other jobs. For instance, a command like

  nix flake check github:NixOS/hydra/aa62c7f7db31753f0cde690f8654dd1907fc0ce2

should no longer build anything because the outputs are already in
cache.nixos.org.

Based-on: https://github.com/DeterminateSystems/nix-src/pull/134
Based-on: https://gerrit.lix.systems/c/lix/+/3841
Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
2025-09-15 21:31:03 +02:00
Jörg Thalheim
0b401e2199 Merge pull request #13995 from juhp/soversion
meson: add soversion with nix version to give SONAME to libs
2025-09-15 20:36:31 +02:00
Eelco Dolstra
ad175727e4 Merge pull request #13938 from NixOS/import-thunk
Ensure that files are parsed/evaluated only once
2025-09-15 19:03:18 +02:00
John Ericson
d314750174 Merge pull request #13999 from obsidiansystems/more-get-improvements
More `get` / `getOr` improvements
2025-09-15 12:55:40 -04:00
John Ericson
f3e3f75838 More get / getOr improvements
- Use `const K`, not `K`, otherwise we don't get auto referencing of
  rvalues.

- Generalized the deleted overloads, because we don't care what the key
  type is --- we want to get rid of anything that has an rvalue map
  type.
2025-09-15 12:25:37 -04:00
Jens Petersen
dd1a554aba meson: add soversion with nix version to give SONAME to libs (#13960, #13979)
remove 'pre' version suffix for non-releases (chokes Darwin ld)
2025-09-15 22:08:26 +08:00
Sergei Zimmerman
2b0fd88324 Merge pull request #13991 from xokdvium/bindings-remove-find
libexpr: Remove Bindings::find
2025-09-14 21:32:31 +00:00
John Ericson
ffc14ac91b Merge pull request #13983 from xokdvium/bindings-fixes
libexpr: Make Bindings::iterator a proper strong type instead of pointer
2025-09-14 17:31:48 -04:00
Sergei Zimmerman
d830840433 libexpr: Remove Bindings::find
A follow-up optimization will make it impossible to make a find function
that returns an iterator in an efficient manner. All consumer code can
easily use the `get` variant.
2025-09-14 23:29:44 +03:00
Sergei Zimmerman
ddabd94f82 libexpr: Make Bindings::iterator a proper strong type instead of pointer
As evident from the number of tests that were holding this API completely
wrong (the end() iterator returned from find() is NEVER nullptr) we should
not have this footgun. A proper strong type guarantees that this confusion
will not happen again.

Also this will be helpful down the road when Bindings becomes something
smarter than an array of Attr.
2025-09-14 22:52:37 +03:00
Sergei Zimmerman
b974b7dc1e Merge pull request #13985 from dramforever/master 2025-09-14 11:19:10 +00:00
dramforever
7295034362 libstore: Raise default connect-timeout to 15 secs
This allows the weird network or DNS server fallback mechanism inside
glibc to work, and prevents a "Resolving timed out after 5000
milliseconds" error. Read on for details.

The DNS request stuff (dns-hosts) in glibc uses this fallback procedure
to minimize network RTT in the ideal case while dealing with
ill-behaving networks and DNS servers gracefully (see resolv.conf(5)):

- Use sendmmsg() to send UDP DNS requests for IPv4 and IPv6 in parallel
- If that times out (meaning that none or only one of the responses have
  been received), send the requests one by one, waiting for the response
  before sending the next request ("single-request")
- If that still times out, try to use a different socket (hence
  different address) for each request ("single-request-reopen")

The default timeout inside glibc is 5 seconds. Therefore, setting
connect-timeout, and therefore CURLOPT_CONNECTTIMEOUT to 5 seconds
prevents the single-request fallback, and setting it to even 10 seconds
prevents the single-request-reopen fallback as well.

The fallback decision is saved by glibc, but only thread-locally, and
libcurl starts a new thread for getaddrinfo() for each connection.
Therefore for every connection the fallback starts from sendmmsg() all
over again. And since these are considered to have timed out by libcurl,
even though getaddrinfo() might return a successful result, it is not
cached in libcurl.

While a user could tweak these with resolv.conf(5) options (e.g. using
networking.resolvconf.extraOptions in NixOS), and indeed that is
probably needed to avoid annoying delays, it still means that the
default connect-timeout of 5 is too low. Raise it to give fallback a
chance.
2025-09-14 08:48:29 +08:00
John Ericson
5bc96798b1 Merge pull request #13982 from obsidiansystems/path-info-static-function
`ValidPathInfo`, `NarInfo`, turn funky constructor into static method
2025-09-13 20:10:45 -04:00
Sergei Zimmerman
e75501da3e libexpr: Remove non-const iterators of Bindings 2025-09-13 23:21:24 +03:00
John Ericson
74be28820c ValidPathInfo, NarInfo, turn funky constructor into static method
This is more flexible, and needed for me to be able to reshuffle the
inheritance bureaucracy to make the JSON instances more precise.
2025-09-13 13:17:14 -04:00
Jörg Thalheim
465d627f7f Merge pull request #13978 from xokdvium/fix-warn
libutil: Fix missing return warning
2025-09-13 10:30:32 +02:00
Sergei Zimmerman
298ea97c12 libutil: Fix missing return warning
../hash.cc: In function 'nix::{anonymous}::DecodeNamePair nix::baseExplicit(HashFormat)':
../hash.cc:114:1: warning: control reaches end of non-void function [-Wreturn-type]
  114 | }
      | ^
2025-09-13 09:19:07 +03:00
John Ericson
1907a3300f Merge pull request #13811 from hgl/patch-2
doc: Rephrase store-object.md
2025-09-12 23:34:52 -04:00
John Ericson
1710fd09f3 Merge pull request #13975 from obsidiansystems/prep-json-0
Misc prep changes for #13942
2025-09-12 23:32:20 -04:00
Glen Huang
a0b633dd2b doc: Rephrase store-object.md 2025-09-13 10:17:12 +08:00
John Ericson
f78062d2fb Merge pull request #13976 from xokdvium/darwin-packaging
packaging: Drop legacy apple sdk pattern
2025-09-12 18:37:36 -04:00
Sergei Zimmerman
20b532eab0 packaging: Drop legacy apple sdk pattern
This has been dropped on unstable an nix no longer
compiled with overridden nixpkgs input. On 25.05 these
overrides already do nothing.

Tested with:

nix build .#packages.x86_64-darwin.nix-cli -L --override-input nixpkgs https://releases.nixos.org/nixos/unstable/nixos-25.11pre859555.ab0f3607a6c7/nixexprs.tar.xz

Default deployment target on 25.05 is 11.3, so 10.13
sdk override doesn't have to be updated at all as evident
from the fact that we didn't observe any issues with it.
2025-09-13 01:07:42 +03:00
John Ericson
095ac66d4c Introduce Hash::parseExplicitFormatUnprefixed 2025-09-12 18:04:29 -04:00
John Ericson
c6d06ce486 Fix hash error message
Wrong number of arguments was causing a format assertion.
2025-09-12 18:04:29 -04:00
John Ericson
c242706319 Move json_avoids_null to its own header
This is because we need it in declarations where we should not be
including the full `nlohmann/json.hpp`.

Already can clean up by moving the experimental feature "instance".

Also, make the `std::map` instance better by allowing for other
comparison functions.
2025-09-12 18:04:29 -04:00
Philip Wilk
aef431fbd1 bugfix/3514: do not throw on substituter errors if other substituters are still enabled (#13301)
## Motivation

Nix currently hard fails if a substituter is inaccessible, even when they are other substituters available, unless `fallback = true`. 
This breaks nix build, run, shell et al entirely. 
This would modify the default behaviour so that nix would actually use the other available substituters and not hard error.

Here is an example before vs after when using dotenv where I have manually stopped my own cache to trigger this issue, before and after the patch. The initial error is really frustrating because there is other caches available.
![image](https://github.com/user-attachments/assets/b4aec474-52d1-497d-b4e8-6f5737d6acc7)
![image](https://github.com/user-attachments/assets/ee91fcd4-4a1a-4c33-bf88-3aee67ad3cc9)

## Context

https://github.com/NixOS/nix/issues/3514#issuecomment-2905056198 is the earliest issue I could find, but there are many duplicates.

There is an initial PR at https://github.com/NixOS/nix/pull/7188, but this appears to have been abandoned - over 2 years with no activity, then a no comment review in jan. There was a subsequent PR at https://github.com/NixOS/nix/pull/8983 but this was closed without merge - over a year without activity.
<!-- Non-trivial change: Briefly outline the implementation strategy. -->
I have visualised the current and proposed flows. I believe my logic flows line up with what is suggested in https://github.com/NixOS/nix/pull/7188#issuecomment-1375652870 but correct me if I am wrong.
Current behaviour:
![current](https://github.com/user-attachments/assets/d9501b34-274c-4eb3-88c3-9021a482e364)
Proposed behaviour:
![proposed](https://github.com/user-attachments/assets/8236e4f4-21ef-45d7-87e1-6c8d416e8c1c)

[Charts in lucid](https://lucid.app/lucidchart/1b51b08d-6c4f-40e0-bf54-480df322cccf/view)
<!-- Invasive change: Discuss alternative designs or approaches you considered. -->

Possible issues to think about:
- I could not figure out where the curl error is created... I can't figure out how to swallow it and turn it into a warn or better yet, a debug log.
- Unfortunately, in contrast with the previous point, I'm not sure how verbose we want to warns/traces to be - personally I think that the warn that a substituter has been disabled (when it happens) is sufficient, and that the next one is being used, but this is personal preference.
2025-09-12 17:29:34 -04:00
Sergei Zimmerman
92df96543c Merge pull request #13972 from xokdvium/no-string-values-in-evalstate 2025-09-12 21:19:09 +00:00
Jörg Thalheim
5772c207d8 Merge pull request #13970 from xokdvium/no-soname
Revert "meson: add soversion to libraries (#13960)"
2025-09-12 23:17:17 +02:00
Sergei Zimmerman
f4c38278ca libexpr: Remove vString* Values from EvalState
EvalState is too big and cluttered. These strings
can be private constant statics.
2025-09-12 23:44:52 +03:00
Sergei Zimmerman
0db2b8c8fe Revert "meson: add soversion to libraries (#13960)"
This reverts commit bdbc739d6e.

Such a change needs more thought put into it. By versioning
shared libraries we'd make a false impression that libraries
themselves are actually versioned and have some sort of stable
ABI, which is not the case.

This will be useful when C bindings become stable, but as long
as they are experimental it does not make sense to set SONAME.

Also this change should not have been backported, since it's
severely breaking.
2025-09-12 20:43:34 +03:00
Eelco Dolstra
8d8f49cb5a Use concurrent_flat_map_fwd.hpp 2025-09-12 17:49:15 +02:00
Eelco Dolstra
4b63ff63a4 Remove some unnecessary hash template arguments 2025-09-12 17:26:29 +02:00
Eelco Dolstra
ad6eb22368 Ensure that files are parsed/evaluated only once
When doing multithreaded evaluation, we want to ensure that any Nix
file is parsed and evaluated only once. The easiest way to do this is
to rely on thunks, since those ensure locking in the multithreaded
evaluator. `fileEvalCache` is now a mapping from `SourcePath` to a
`Value *`. The value is initially a thunk (pointing to a
`ExprParseFile` helper object) that can be forced to parse and
evaluate the file. So a subsequent thread requesting the same file
will see a thunk that is possibly locked and wait for it.

The parser cache is gone since it's no longer needed. However, there
is a new `importResolutionCache` that maps `SourcePath`s to
`SourcePath`s (e.g. `/foo` to `/foo/default.nix`). Previously we put
multiple entries in `fileEvalCache`, which was ugly and could result
in work duplication.
2025-09-12 17:05:21 +02:00
Eelco Dolstra
47c16fc4bd SourcePath: Implement boost::hash 2025-09-12 17:05:21 +02:00
Eelco Dolstra
8fbf4b9427 CanonPath: Implement boost::hash 2025-09-12 17:05:21 +02:00
Eelco Dolstra
7f9b5226af Add getConcurrent helper function 2025-09-12 16:49:25 +02:00
Jörg Thalheim
377b60ee9b Merge pull request #13926 from NaN-git/opt_boost-unordered
replace all occurences of std::unordered_* by equivalents from boost
2025-09-12 11:46:42 +02:00
Jörg Thalheim
429812cdb8 Merge pull request #13966 from juhp/patch-1
meson: add soversion to libraries (#13960)
2025-09-12 08:25:12 +02:00
Jörg Thalheim
f6802a8ccf Merge pull request #13963 from xokdvium/fix-no-gc
libexpr: Fix build without Boehm
2025-09-12 08:23:19 +02:00
Jens Petersen
bdbc739d6e meson: add soversion to libraries (#13960) 2025-09-12 14:06:39 +08:00
Sergei Zimmerman
c0b35c71cd libexpr: Fix build without Boehm
This should have been placed under the ifdef.
2025-09-12 04:02:07 +03:00
Sergei Zimmerman
ef5fedbc0d Merge pull request #13936 from xokdvium/empty-list-bindings
libexpr: Make constant Values global constants, move out of EvalState
2025-09-10 23:12:20 +00:00
Sergei Zimmerman
5db4b0699c libexpr: Make constant Values global constants, move out of EvalState
These constant Values have no business being in the EvalState in the
first place. The ultimate goal is to get rid of the ugly `getBuiltins`
and its relience (in `createBaseEnv`) on these global constants is getting in the way.

Same idea as in f017f9ddd3.

Co-authored-by: eldritch horrors <pennae@lix.systems>
2025-09-11 01:53:41 +03:00
Sergei Zimmerman
462b9ac49c libexpr: Make Value::isa and Value::getStorage private methods
This was always intended to be the case, but accidentally left
in the public interface.
2025-09-11 01:52:17 +03:00
Sergei Zimmerman
4df1a3ca76 libexpr: Make emptyBindings a global constant
This object is always constant and will never get modified.
Having it as a global (constant) static is much easier and
unclutters the EvalState.

Same idea as in f017f9ddd3.

Co-authored-by: eldritch horrors <pennae@lix.systems>
2025-09-11 01:51:48 +03:00
Philipp Otterbein
9dbc2cae4f hashmaps with string keys: add transparent lookups 2025-09-10 23:04:44 +02:00
Philipp Otterbein
9f2b6a1b94 replace more std::unordered_* types by faster boost hash maps 2025-09-10 23:04:44 +02:00
Philipp Otterbein
4f8c50fb77 libexpr: replace std::unordered_* types by faster boost hash maps 2025-09-10 23:04:44 +02:00
John Ericson
c0fd9146d6 Move exportPaths() / importPaths() out of the Store class (#13959) 2025-09-10 15:39:20 -04:00
Eelco Dolstra
3898a7343a Merge pull request #13961 from NyCodeGHG/push-prlsssvmxwvl
meson: link to libatomic on powerpc-linux
2025-09-10 19:22:54 +02:00
Marie Ramlow
37eec84bc1 meson: link to libatomic on powerpc-linux
Like 32-bit Arm, 32-bit PowerPC also needs linking against libatomic
because it doesn't support some atomic instructions in hardware.
2025-09-10 18:50:35 +02:00
Eelco Dolstra
fe5b669534 Move exportPaths() / importPaths() out of the Store class 2025-09-10 14:22:46 +02:00
Jörg Thalheim
5e46df973f Merge pull request #13957 from NixOS/drop-old-serve-protocol
Remove support for serve protocol version < 5
2025-09-10 13:50:12 +02:00
Eelco Dolstra
9df99e0658 Remove ServeProto::Command::ExportPaths
This seems to have been unused since the build-remote.pl removal in February 2017 (27dc76c1a5).
2025-09-10 10:57:15 +02:00
Eelco Dolstra
fa048e4383 Remove support for serve protocol < 5
This was introduced in August 2018 (2825e05d21).
2025-09-10 10:57:15 +02:00
Bernardo Meurer Costa
f193bca595 feat(libstore): warn when kvm is enabled but /dev/kvm isn't available 2025-08-28 18:44:28 +00:00
484 changed files with 8667 additions and 4045 deletions

View File

@@ -15,6 +15,10 @@ so you understand the process and the expectations.
- volunteering contributions effectively
- how to get help and our review process.
PR stuck in review? We have two Nix team meetings per week online that are open for everyone in a jitsi conference:
- https://calendar.google.com/calendar/u/0/embed?src=b9o52fobqjak8oq8lfkhg3t0qg@group.calendar.google.com
-->
## Motivation

View File

@@ -29,7 +29,32 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
- run: nix flake show --all-systems --json
pre-commit-checks:
name: pre-commit checks
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: ./.github/actions/install-nix-action
with:
dogfood: ${{ github.event_name == 'workflow_dispatch' && inputs.dogfood || github.event_name != 'workflow_dispatch' }}
extra_nix_config: experimental-features = nix-command flakes
github_token: ${{ secrets.GITHUB_TOKEN }}
- uses: DeterminateSystems/magic-nix-cache-action@main
- run: ./ci/gha/tests/pre-commit-checks
basic-checks:
name: aggregate basic checks
if: ${{ always() }}
runs-on: ubuntu-24.04
needs: [pre-commit-checks, eval]
steps:
- name: Exit with any errors
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
run: |
exit 1
tests:
needs: basic-checks
strategy:
fail-fast: false
matrix:
@@ -214,6 +239,7 @@ jobs:
docker push $IMAGE_ID:master
vm_tests:
needs: basic-checks
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5

View File

@@ -1 +1 @@
2.32.0
2.32.2

25
COPYING
View File

@@ -1,8 +1,8 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
GNU LESSER GENERAL PUBLIC LICENSE
Version 2.1, February 1999
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
<https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -10,7 +10,7 @@
as the successor of the GNU Library Public License, version 2, hence
the version number 2.1.]
Preamble
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -112,7 +112,7 @@ modification follow. Pay close attention to the difference between a
former contains code derived from the library, whereas the latter must
be combined with the library in order to run.
GNU LESSER GENERAL PUBLIC LICENSE
GNU LESSER GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License Agreement applies to any software library or other
@@ -146,7 +146,7 @@ such a program is covered only if its contents constitute a work based
on the Library (independent of the use of the Library in a tool for
writing it). Whether that is true depends on what the Library does
and what the program that uses the Library does.
1. You may copy and distribute verbatim copies of the Library's
complete source code as you receive it, in any medium, provided that
you conspicuously and appropriately publish on each copy an
@@ -432,7 +432,7 @@ decision will be guided by the two goals of preserving the free status
of all derivatives of our free software and of promoting the sharing
and reuse of software generally.
NO WARRANTY
NO WARRANTY
15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
@@ -455,7 +455,7 @@ FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
END OF TERMS AND CONDITIONS
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Libraries
@@ -484,8 +484,7 @@ convey the exclusion of warranty; and each file should have at least the
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
License along with this library; if not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
@@ -496,9 +495,7 @@ necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
<signature of Ty Coon>, 1 April 1990
Ty Coon, President of Vice
<signature of Moe Ghoul>, 1 April 1990
Moe Ghoul, President of Vice
That's all there is to it!

View File

@@ -24,16 +24,7 @@ let
enableSanitizersLayer = finalAttrs: prevAttrs: {
mesonFlags =
(prevAttrs.mesonFlags or [ ])
++ [
# Run all tests with UBSAN enabled. Running both with ubsan and
# without doesn't seem to have much immediate benefit for doubling
# the GHA CI workaround.
#
# TODO: Work toward enabling "address,undefined" if it seems feasible.
# This would maybe require dropping Boost coroutines and ignoring intentional
# memory leaks with detect_leaks=0.
(lib.mesonOption "b_sanitize" "undefined")
]
++ [ (lib.mesonOption "b_sanitize" "address,undefined") ]
++ (lib.optionals stdenv.cc.isClang [
# https://www.github.com/mesonbuild/meson/issues/764
(lib.mesonBool "b_lundef" false)
@@ -71,8 +62,12 @@ rec {
nixComponentsInstrumented = nixComponents.overrideScope (
final: prev: {
nix-store-tests = prev.nix-store-tests.override { withBenchmarks = true; };
# Boehm is incompatible with ASAN.
nix-expr = prev.nix-expr.override { enableGC = !withSanitizers; };
mesonComponentOverrides = lib.composeManyExtensions componentOverrides;
# Unclear how to make Perl bindings work with a dynamically linked ASAN.
nix-perl-bindings = if withSanitizers then null else prev.nix-perl-bindings;
}
);

24
ci/gha/tests/pre-commit-checks Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
system=$(nix eval --raw --impure --expr builtins.currentSystem)
echo "::group::Running pre-commit checks"
if nix build ".#checks.$system.pre-commit" -L; then
echo "::endgroup::"
exit 0
fi
echo "::error ::Changes do not pass pre-commit checks"
cat <<EOF
The code isn't formatted or doesn't pass lints. You can run pre-commit locally with:
nix develop -c ./maintainers/format.sh
EOF
echo "::endgroup::"
exit 1

View File

@@ -15,6 +15,7 @@ pymod = import('python')
python = pymod.find_installation('python3')
nix_env_for_docs = {
'ASAN_OPTIONS' : 'abort_on_error=1:print_summary=1:detect_leaks=0',
'HOME' : '/dummy',
'NIX_CONF_DIR' : '/dummy',
'NIX_SSL_CERT_FILE' : '/dummy/no-ca-bundle.crt',

View File

@@ -1,23 +0,0 @@
---
synopsis: "C API: Errors returned from your primops are not treated as recoverable by default"
prs: [13930]
---
Nix 2.32 by default remembers the error in the thunk that triggered it.
Previously the following sequence of events worked:
1. Have a thunk that invokes a primop that's defined through the C API
2. The primop returns an error
3. Force the thunk again
4. The primop returns a value
5. The thunk evaluated successfully
**Resolution**
C API consumers that rely on this must change their recoverable error calls:
```diff
-nix_set_err_msg(context, NIX_ERR_*, msg);
+nix_set_err_msg(context, NIX_ERR_RECOVERABLE, msg);
```

View File

@@ -1,6 +0,0 @@
---
synopsis: "Removed support for daemons and clients older than Nix 2.0"
prs: [13951]
---
We have dropped support in the daemon worker protocol for daemons and clients that don't speak at least version 18 of the protocol. This first Nix release that supports this version is Nix 2.0, released in February 2018.

View File

@@ -1,6 +0,0 @@
---
synopsis: "Temporary build directories no longer include derivation names"
prs: [13839]
---
Temporary build directories created during derivation builds no longer include the derivation name in their path to avoid build failures when the derivation name is too long. This change ensures predictable prefix lengths for build directories under `/nix/var/nix/builds`.

View File

@@ -138,6 +138,7 @@
- [Contributing](development/contributing.md)
- [Releases](release-notes/index.md)
{{#include ./SUMMARY-rl-next.md}}
- [Release 2.32 (2025-10-06)](release-notes/rl-2.32.md)
- [Release 2.31 (2025-08-21)](release-notes/rl-2.31.md)
- [Release 2.30 (2025-07-07)](release-notes/rl-2.30.md)
- [Release 2.29 (2025-05-14)](release-notes/rl-2.29.md)

View File

@@ -2,6 +2,7 @@ xp_features_json = custom_target(
command : [ nix, '__dump-xp-features' ],
capture : true,
output : 'xp-features.json',
env : nix_env_for_docs,
)
experimental_features_shortlist_md = custom_target(

View File

@@ -23,7 +23,7 @@ $ nix-shell
To get a shell with one of the other [supported compilation environments](#compilation-environments):
```console
$ nix-shell --attr devShells.x86_64-linux.native-clangStdenvPackages
$ nix-shell --attr devShells.x86_64-linux.native-clangStdenv
```
> **Note**

View File

@@ -24,6 +24,19 @@ It is also possible to build without debugging for faster build:
(The first line is needed because `fortify` hardening requires at least some optimization.)
## Building Nix with sanitizers
Nix can be built with [Address](https://clang.llvm.org/docs/AddressSanitizer.html) and
[UB](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) sanitizers using LLVM
or GCC. This is useful when debugging memory corruption issues.
```console
[nix-shell]$ export mesonBuildType=debugoptimized
[nix-shell]$ appendToVar mesonFlags "-Dlibexpr:gc=disabled" # Disable Boehm
[nix-shell]$ appendToVar mesonFlags "-Dbindings=false" # Disable nix-perl
[nix-shell]$ appendToVar mesonFlags "-Db_sanitize=address,undefined"
```
## Debugging the Nix Binary
Obtain your preferred debugger within the development shell:

View File

@@ -7,5 +7,6 @@ experimental_feature_descriptions_md = custom_target(
xp_features_json,
],
capture : true,
env : nix_env_for_docs,
output : 'experimental-feature-descriptions.md',
)

View File

@@ -5,12 +5,28 @@ All built-ins are available through the global [`builtins`](#builtins-builtins)
Some built-ins are also exposed directly in the global scope:
<!-- TODO(@rhendric, #10970): this list is incomplete -->
- [`derivation`](#builtins-derivation)
- [`import`](#builtins-import)
- `derivationStrict`
- [`abort`](#builtins-abort)
- [`baseNameOf`](#builtins-baseNameOf)
- [`break`](#builtins-break)
- [`dirOf`](#builtins-dirOf)
- [`false`](#builtins-false)
- [`fetchGit`](#builtins-fetchGit)
- `fetchMercurial`
- [`fetchTarball`](#builtins-fetchTarball)
- [`fetchTree`](#builtins-fetchTree)
- [`fromTOML`](#builtins-fromTOML)
- [`import`](#builtins-import)
- [`isNull`](#builtins-isNull)
- [`map`](#builtins-map)
- [`null`](#builtins-null)
- [`placeholder`](#builtins-placeholder)
- [`removeAttrs`](#builtins-removeAttrs)
- `scopedImport`
- [`throw`](#builtins-throw)
- [`toString`](#builtins-toString)
- [`true`](#builtins-true)
<dl>
<dt id="builtins-derivation"><a href="#builtins-derivation"><code>derivation <var>attrs</var></code></a></dt>

View File

@@ -14,6 +14,21 @@ is a JSON object with the following fields:
The name of the derivation.
This is used when calculating the store paths of the derivation's outputs.
* `version`:
Must be `3`.
This is a guard that allows us to continue evolving this format.
The choice of `3` is fairly arbitrary, but corresponds to this informal version:
- Version 0: A-Term format
- Version 1: Original JSON format, with ugly `"r:sha256"` inherited from A-Term format.
- Version 2: Separate `method` and `hashAlgo` fields in output specs
- Verison 3: Drop store dir from store paths, just include base name.
Note that while this format is experimental, the maintenance of versions is best-effort, and not promised to identify every change.
* `outputs`:
Information about the output paths of the derivation.
This is a JSON object with one member per output, where the key is the output name and the value is a JSON object with these fields:
@@ -52,7 +67,6 @@ is a JSON object with the following fields:
> ```json
> "outputs": {
> "out": {
> "path": "/nix/store/2543j7c6jn75blc3drf4g5vhb1rhdq29-source",
> "method": "nar",
> "hashAlgo": "sha256",
> "hash": "6fc80dcc62179dbc12fc0b5881275898f93444833d21b89dfe5f7fbcbb1d0d62"
@@ -63,6 +77,15 @@ is a JSON object with the following fields:
* `inputSrcs`:
A list of store paths on which this derivation depends.
> **Example**
>
> ```json
> "inputSrcs": [
> "47y241wqdhac3jm5l7nv0x4975mb1975-separate-debug-info.sh",
> "56d0w71pjj9bdr363ym3wj1zkwyqq97j-fix-pop-var-context-error.patch"
> ]
> ```
* `inputDrvs`:
A JSON object specifying the derivations on which this derivation depends, and what outputs of those derivations.
@@ -70,8 +93,8 @@ is a JSON object with the following fields:
>
> ```json
> "inputDrvs": {
> "/nix/store/6lkh5yi7nlb7l6dr8fljlli5zfd9hq58-curl-7.73.0.drv": ["dev"],
> "/nix/store/fn3kgnfzl5dzym26j8g907gq3kbm8bfh-unzip-6.0.drv": ["out"]
> "6lkh5yi7nlb7l6dr8fljlli5zfd9hq58-curl-7.73.0.drv": ["dev"],
> "fn3kgnfzl5dzym26j8g907gq3kbm8bfh-unzip-6.0.drv": ["out"]
> }
> ```

View File

@@ -0,0 +1,130 @@
# Release 2.32.0 (2025-10-06)
## Incompatible changes
- Removed support for daemons and clients older than Nix 2.0 [#13951](https://github.com/NixOS/nix/pull/13951)
We have dropped support in the daemon worker protocol for daemons and clients that don't speak at least version 18 of the protocol. This first Nix release that supports this version is Nix 2.0, released in February 2018.
- Derivation JSON format now uses store path basenames only [#13570](https://github.com/NixOS/nix/issues/13570) [#13980](https://github.com/NixOS/nix/pull/13980)
Experience with many JSON frameworks (e.g. nlohmann/json in C++, Serde in Rust, and Aeson in Haskell) has shown that the use of the store directory in JSON formats is an impediment to systematic JSON formats, because it requires the serializer/deserializer to take an extra paramater (the store directory).
We ultimately want to rectify this issue with all JSON formats to the extent allowed by our stability promises. To start with, we are changing the JSON format for derivations because the `nix derivation` commands are — in addition to being formally unstable — less widely used than other unstable commands.
See the documentation on the [JSON format for derivations](@docroot@/protocols/json/derivation.md) for further details.
- C API: `nix_get_attr_name_byidx`, `nix_get_attr_byidx` take a `nix_value *` instead of `const nix_value *` [#13987](https://github.com/NixOS/nix/pull/13987)
In order to accommodate a more optimized internal representation of attribute set merges these functions require
a mutable `nix_value *` that might be modified on access. This does *not* break the ABI of these functions.
## New features
- C API: Add lazy attribute and list item accessors [#14030](https://github.com/NixOS/nix/pull/14030)
The C API now includes lazy accessor functions for retrieving values from lists and attribute sets without forcing evaluation:
- `nix_get_list_byidx_lazy()` - Get a list element without forcing its evaluation
- `nix_get_attr_byname_lazy()` - Get an attribute value by name without forcing evaluation
- `nix_get_attr_byidx_lazy()` - Get an attribute by index without forcing evaluation
These functions are useful when forwarding unevaluated sub-values to other lists, attribute sets, or function calls. They allow more efficient handling of Nix values by deferring evaluation until actually needed.
Additionally, bounds checking has been improved for all `_byidx` functions to properly validate indices before access, preventing potential out-of-bounds errors.
The documentation for `NIX_ERR_KEY` error handling has also been clarified to specify when this error code is returned.
- HTTP binary caches now support transparent compression for metadata
HTTP binary cache stores can now compress `.narinfo`, `.ls`, and build log files before uploading them,
reducing bandwidth usage and storage requirements. The compression is applied transparently using the
`Content-Encoding` header, allowing compatible clients to automatically decompress the files.
Three new configuration options control this behavior:
- `narinfo-compression`: Compression method for `.narinfo` files
- `ls-compression`: Compression method for `.ls` files
- `log-compression`: Compression method for build logs in `log/` directory
Example usage:
```
nix copy --to 'http://cache.example.com?narinfo-compression=gzip&ls-compression=gzip' /nix/store/...
nix store copy-log --to 'http://cache.example.com?log-compression=br' /nix/store/...
```
- Temporary build directories no longer include derivation names [#13839](https://github.com/NixOS/nix/pull/13839)
Temporary build directories created during derivation builds no longer include the derivation name in their path to avoid build failures when the derivation name is too long. This change ensures predictable prefix lengths for build directories under `/nix/var/nix/builds`.
- External derivation builders [#14145](https://github.com/NixOS/nix/pull/14145)
These are helper programs that Nix calls to perform derivations for specified system types, e.g. by using QEMU to emulate a different type of platform. For more information, see the [`external-builders` setting](../command-ref/conf-file.md#conf-external-builders).
This is currently an experimental feature.
## Performance improvements
- Optimize memory usage of attribute set merges [#13987](https://github.com/NixOS/nix/pull/13987)
[Attribute set update operations](@docroot@/language/operators.md#update) have been optimized to
reduce reallocations in cases when the second operand is small.
For typical evaluations of nixpkgs this optimization leads to ~20% less memory allocated in total
without significantly affecting evaluation performance.
See [eval-attrset-update-layer-rhs-threshold](@docroot@/command-ref/conf-file.md#conf-eval-attrset-update-layer-rhs-threshold)
- Substituted flake inputs are no longer re-copied to the store [#14041](https://github.com/NixOS/nix/pull/14041)
Since 2.25, Nix would fail to store a cache entry for substituted flake inputs, which in turn would cause them to be re-copied to the store on initial evaluation. Caching these inputs results in a near doubling of performance in some cases — especially on I/O-bound machines and when using commands that fetch many inputs, like `nix flake [archive|prefetch-inputs]`.
- `nix flake check` now skips derivations that can be substituted [#13574](https://github.com/NixOS/nix/pull/13574)
Previously, `nix flake check` would evaluate and build/substitute all
derivations. Now, it will skip downloading derivations that can be substituted.
This can drastically decrease the time invocations take in environments where
checks may already be cached (like in CI).
- `fetchTarball` and `fetchurl` now correctly substitute (#14138)
At some point we stopped substituting calls to `fetchTarball` and `fetchurl` with a set `narHash` to avoid incorrectly substituting things in `fetchTree`, even though it would be safe to substitute when calling the legacy `fetch{Tarball,url}`. This fixes that regression where it is safe.
- Started moving AST allocations into a bump allocator [#14088](https://github.com/NixOS/nix/issues/14088)
This leaves smaller, immutable structures in the AST. So far this saves about 2% memory on a NixOS config evaluation.
## Contributors
This release was made possible by the following 32 contributors:
- Farid Zakaria [**(@fzakaria)**](https://github.com/fzakaria)
- dram [**(@dramforever)**](https://github.com/dramforever)
- Ephraim Siegfried [**(@EphraimSiegfried)**](https://github.com/EphraimSiegfried)
- Robert Hensing [**(@roberth)**](https://github.com/roberth)
- Taeer Bar-Yam [**(@Radvendii)**](https://github.com/Radvendii)
- Emily [**(@emilazy)**](https://github.com/emilazy)
- Jens Petersen [**(@juhp)**](https://github.com/juhp)
- Bernardo Meurer [**(@lovesegfault)**](https://github.com/lovesegfault)
- Jörg Thalheim [**(@Mic92)**](https://github.com/Mic92)
- Leandro Emmanuel Reina Kiperman [**(@kip93)**](https://github.com/kip93)
- Marie [**(@NyCodeGHG)**](https://github.com/NyCodeGHG)
- Ethan Evans [**(@ethanavatar)**](https://github.com/ethanavatar)
- Yaroslav Bolyukin [**(@CertainLach)**](https://github.com/CertainLach)
- Matej Urbas [**(@urbas)**](https://github.com/urbas)
- Jami Kettunen [**(@JamiKettunen)**](https://github.com/JamiKettunen)
- Clayton [**(@netadr)**](https://github.com/netadr)
- Grégory Marti [**(@gmarti)**](https://github.com/gmarti)
- Eelco Dolstra [**(@edolstra)**](https://github.com/edolstra)
- rszyma [**(@rszyma)**](https://github.com/rszyma)
- Philip Wilk [**(@philipwilk)**](https://github.com/philipwilk)
- John Ericson [**(@Ericson2314)**](https://github.com/Ericson2314)
- Tom Westerhout [**(@twesterhout)**](https://github.com/twesterhout)
- Tristan Ross [**(@RossComputerGuy)**](https://github.com/RossComputerGuy)
- Sergei Zimmerman [**(@xokdvium)**](https://github.com/xokdvium)
- Jean-François Roche [**(@jfroche)**](https://github.com/jfroche)
- Seth Flynn [**(@getchoo)**](https://github.com/getchoo)
- éclairevoyant [**(@eclairevoyant)**](https://github.com/eclairevoyant)
- Glen Huang [**(@hgl)**](https://github.com/hgl)
- osman - オスマン [**(@osbm)**](https://github.com/osbm)
- David McFarland [**(@corngood)**](https://github.com/corngood)
- Cole Helbling [**(@cole-h)**](https://github.com/cole-h)
- Sinan Mohd [**(@sinanmohd)**](https://github.com/sinanmohd)
- Philipp Otterbein

View File

@@ -20,7 +20,8 @@ The graph of references excluding self-references thus forms a [directed acyclic
[directed acyclic graph]: @docroot@/glossary.md#gloss-directed-acyclic-graph
We can take the [transitive closure] of the references graph, which any pair of store objects have an edge not if there is a single reference from the first to the second, but a path of one or more references from the first to the second.
We can take the [transitive closure] of the references graph, in which any pair of store objects have an edge if a *path* of one or more references exists from the first to the second object.
(A single reference always forms a path which is one reference long, but longer paths may connect objects which have no direct reference between them.)
The *requisites* of a store object are all store objects reachable by paths of references which start with given store object's references.
[transitive closure]: https://en.wikipedia.org/wiki/Transitive_closure

View File

@@ -32,7 +32,7 @@
let
inherit (nixpkgs) lib;
officialRelease = false;
officialRelease = true;
linux32BitSystems = [ "i686-linux" ];
linux64BitSystems = [

View File

@@ -203,5 +203,26 @@
"ConnorBaker01@Gmail.com": "ConnorBaker",
"jsoo1@asu.edu": "jsoo1",
"hsngrmpf+github@gmail.com": "DavHau",
"matthew@floxdev.com": "mkenigs"
"matthew@floxdev.com": "mkenigs",
"taeer@bar-yam.me": "Radvendii",
"beme@anthropic.com": "lovesegfault",
"osbm@osbm.dev": "osbm",
"jami.kettunen@protonmail.com": "JamiKettunen",
"ephraim.siegfried@hotmail.com": "EphraimSiegfried",
"rszyma.dev@gmail.com": "rszyma",
"tristan.ross@determinate.systems": "RossComputerGuy",
"corngood@gmail.com": "corngood",
"jfroche@pyxel.be": "jfroche",
"848000+eclairevoyant@users.noreply.github.com": "eclairevoyant",
"petersen@redhat.com": "juhp",
"dramforever@live.com": "dramforever",
"me@glenhuang.com": "hgl",
"philip.wilk@fivium.co.uk": "philipwilk",
"me@nycode.dev": "NyCodeGHG",
"14264576+twesterhout@users.noreply.github.com": "twesterhout",
"sinan@sinanmohd.com": "sinanmohd",
"42688647+netadr@users.noreply.github.com": "netadr",
"matej.urbas@gmail.com": "urbas",
"ethanalexevans@gmail.com": "ethanavatar",
"greg.marti@gmail.com": "gmarti"
}

View File

@@ -177,5 +177,24 @@
"avnik": "Alexander V. Nikolaev",
"DavHau": null,
"aln730": "AGawas",
"vog": "Volker Diels-Grabsch"
"vog": "Volker Diels-Grabsch",
"corngood": "David McFarland",
"twesterhout": "Tom Westerhout",
"JamiKettunen": "Jami Kettunen",
"dramforever": "dram",
"philipwilk": "Philip Wilk",
"netadr": "Clayton",
"NyCodeGHG": "Marie",
"jfroche": "Jean-Fran\u00e7ois Roche",
"urbas": "Matej Urbas",
"osbm": "osman - \u30aa\u30b9\u30de\u30f3",
"rszyma": null,
"eclairevoyant": "\u00e9clairevoyant",
"Radvendii": "Taeer Bar-Yam",
"sinanmohd": "Sinan Mohd",
"ethanavatar": "Ethan Evans",
"gmarti": "Gr\u00e9gory Marti",
"lovesegfault": "Bernardo Meurer",
"EphraimSiegfried": "Ephraim Siegfried",
"hgl": "Glen Huang"
}

View File

@@ -104,151 +104,6 @@
};
shellcheck = {
enable = true;
excludes = [
# We haven't linted these files yet
''^config/install-sh$''
''^misc/bash/completion\.sh$''
''^misc/fish/completion\.fish$''
''^misc/zsh/completion\.zsh$''
''^scripts/create-darwin-volume\.sh$''
''^scripts/install-darwin-multi-user\.sh$''
''^scripts/install-multi-user\.sh$''
''^scripts/install-systemd-multi-user\.sh$''
''^src/nix/get-env\.sh$''
''^tests/functional/ca/build-dry\.sh$''
''^tests/functional/ca/build-with-garbage-path\.sh$''
''^tests/functional/ca/common\.sh$''
''^tests/functional/ca/concurrent-builds\.sh$''
''^tests/functional/ca/eval-store\.sh$''
''^tests/functional/ca/gc\.sh$''
''^tests/functional/ca/import-from-derivation\.sh$''
''^tests/functional/ca/new-build-cmd\.sh$''
''^tests/functional/ca/nix-shell\.sh$''
''^tests/functional/ca/post-hook\.sh$''
''^tests/functional/ca/recursive\.sh$''
''^tests/functional/ca/repl\.sh$''
''^tests/functional/ca/selfref-gc\.sh$''
''^tests/functional/ca/why-depends\.sh$''
''^tests/functional/characterisation-test-infra\.sh$''
''^tests/functional/common/vars-and-functions\.sh$''
''^tests/functional/completions\.sh$''
''^tests/functional/compute-levels\.sh$''
''^tests/functional/config\.sh$''
''^tests/functional/db-migration\.sh$''
''^tests/functional/debugger\.sh$''
''^tests/functional/dependencies\.builder0\.sh$''
''^tests/functional/dependencies\.sh$''
''^tests/functional/dump-db\.sh$''
''^tests/functional/dyn-drv/build-built-drv\.sh$''
''^tests/functional/dyn-drv/common\.sh$''
''^tests/functional/dyn-drv/dep-built-drv\.sh$''
''^tests/functional/dyn-drv/eval-outputOf\.sh$''
''^tests/functional/dyn-drv/old-daemon-error-hack\.sh$''
''^tests/functional/dyn-drv/recursive-mod-json\.sh$''
''^tests/functional/eval-store\.sh$''
''^tests/functional/export-graph\.sh$''
''^tests/functional/export\.sh$''
''^tests/functional/extra-sandbox-profile\.sh$''
''^tests/functional/fetchClosure\.sh$''
''^tests/functional/fetchGit\.sh$''
''^tests/functional/fetchGitRefs\.sh$''
''^tests/functional/fetchGitSubmodules\.sh$''
''^tests/functional/fetchGitVerification\.sh$''
''^tests/functional/fetchMercurial\.sh$''
''^tests/functional/fixed\.builder1\.sh$''
''^tests/functional/fixed\.builder2\.sh$''
''^tests/functional/fixed\.sh$''
''^tests/functional/flakes/absolute-paths\.sh$''
''^tests/functional/flakes/check\.sh$''
''^tests/functional/flakes/config\.sh$''
''^tests/functional/flakes/flakes\.sh$''
''^tests/functional/flakes/follow-paths\.sh$''
''^tests/functional/flakes/prefetch\.sh$''
''^tests/functional/flakes/run\.sh$''
''^tests/functional/flakes/show\.sh$''
''^tests/functional/formatter\.sh$''
''^tests/functional/formatter\.simple\.sh$''
''^tests/functional/gc-auto\.sh$''
''^tests/functional/gc-concurrent\.builder\.sh$''
''^tests/functional/gc-concurrent\.sh$''
''^tests/functional/gc-concurrent2\.builder\.sh$''
''^tests/functional/gc-non-blocking\.sh$''
''^tests/functional/hash-convert\.sh$''
''^tests/functional/impure-derivations\.sh$''
''^tests/functional/impure-eval\.sh$''
''^tests/functional/install-darwin\.sh$''
''^tests/functional/legacy-ssh-store\.sh$''
''^tests/functional/linux-sandbox\.sh$''
''^tests/functional/local-overlay-store/add-lower-inner\.sh$''
''^tests/functional/local-overlay-store/add-lower\.sh$''
''^tests/functional/local-overlay-store/bad-uris\.sh$''
''^tests/functional/local-overlay-store/build-inner\.sh$''
''^tests/functional/local-overlay-store/build\.sh$''
''^tests/functional/local-overlay-store/check-post-init-inner\.sh$''
''^tests/functional/local-overlay-store/check-post-init\.sh$''
''^tests/functional/local-overlay-store/common\.sh$''
''^tests/functional/local-overlay-store/delete-duplicate-inner\.sh$''
''^tests/functional/local-overlay-store/delete-duplicate\.sh$''
''^tests/functional/local-overlay-store/delete-refs-inner\.sh$''
''^tests/functional/local-overlay-store/delete-refs\.sh$''
''^tests/functional/local-overlay-store/gc-inner\.sh$''
''^tests/functional/local-overlay-store/gc\.sh$''
''^tests/functional/local-overlay-store/optimise-inner\.sh$''
''^tests/functional/local-overlay-store/optimise\.sh$''
''^tests/functional/local-overlay-store/redundant-add-inner\.sh$''
''^tests/functional/local-overlay-store/redundant-add\.sh$''
''^tests/functional/local-overlay-store/remount\.sh$''
''^tests/functional/local-overlay-store/stale-file-handle-inner\.sh$''
''^tests/functional/local-overlay-store/stale-file-handle\.sh$''
''^tests/functional/local-overlay-store/verify-inner\.sh$''
''^tests/functional/local-overlay-store/verify\.sh$''
''^tests/functional/logging\.sh$''
''^tests/functional/misc\.sh$''
''^tests/functional/multiple-outputs\.sh$''
''^tests/functional/nested-sandboxing\.sh$''
''^tests/functional/nested-sandboxing/command\.sh$''
''^tests/functional/nix-build\.sh$''
''^tests/functional/nix-channel\.sh$''
''^tests/functional/nix-collect-garbage-d\.sh$''
''^tests/functional/nix-copy-ssh-common\.sh$''
''^tests/functional/nix-copy-ssh-ng\.sh$''
''^tests/functional/nix-copy-ssh\.sh$''
''^tests/functional/nix-daemon-untrusting\.sh$''
''^tests/functional/nix-profile\.sh$''
''^tests/functional/nix-shell\.sh$''
''^tests/functional/nix_path\.sh$''
''^tests/functional/optimise-store\.sh$''
''^tests/functional/output-normalization\.sh$''
''^tests/functional/parallel\.builder\.sh$''
''^tests/functional/parallel\.sh$''
''^tests/functional/pass-as-file\.sh$''
''^tests/functional/path-from-hash-part\.sh$''
''^tests/functional/path-info\.sh$''
''^tests/functional/placeholders\.sh$''
''^tests/functional/post-hook\.sh$''
''^tests/functional/pure-eval\.sh$''
''^tests/functional/push-to-store-old\.sh$''
''^tests/functional/push-to-store\.sh$''
''^tests/functional/read-only-store\.sh$''
''^tests/functional/readfile-context\.sh$''
''^tests/functional/recursive\.sh$''
''^tests/functional/referrers\.sh$''
''^tests/functional/remote-store\.sh$''
''^tests/functional/repair\.sh$''
''^tests/functional/restricted\.sh$''
''^tests/functional/search\.sh$''
''^tests/functional/secure-drv-outputs\.sh$''
''^tests/functional/selfref-gc\.sh$''
''^tests/functional/shell\.shebang\.sh$''
''^tests/functional/simple\.builder\.sh$''
''^tests/functional/supplementary-groups\.sh$''
''^tests/functional/toString-path\.sh$''
''^tests/functional/user-envs-migration\.sh$''
''^tests/functional/user-envs-test-case\.sh$''
''^tests/functional/user-envs\.builder\.sh$''
''^tests/functional/user-envs\.sh$''
''^tests/functional/why-depends\.sh$''
];
};
};
};

View File

@@ -41,8 +41,10 @@ subproject('libexpr-c')
subproject('libflake-c')
subproject('libmain-c')
asan_enabled = 'address' in get_option('b_sanitize')
# Language Bindings
if get_option('bindings') and not meson.is_cross_build()
if get_option('bindings') and not meson.is_cross_build() and not asan_enabled
subproject('perl')
endif

View File

@@ -1,3 +1,4 @@
# shellcheck shell=bash
function _complete_nix {
local -a words
local cword cur

View File

@@ -1,3 +1,4 @@
# shellcheck disable=all
function _nix_complete
# Get the current command up to a cursor.
# - Behaves correctly even with pipes and nested in commands like env.

View File

@@ -1,3 +1,4 @@
# shellcheck disable=all
#compdef nix
function _nix() {

View File

@@ -0,0 +1,12 @@
asan_test_options_env = {
'ASAN_OPTIONS' : 'abort_on_error=1:print_summary=1:detect_leaks=0',
}
# Clang gets grumpy about missing libasan symbols if -shared-libasan is not
# passed when building shared libs, at least on Linux
if cxx.get_id() == 'clang' and ('address' in get_option('b_sanitize') or 'undefined' in get_option(
'b_sanitize',
))
add_project_link_arguments('-shared-libasan', language : 'cpp')
endif

View File

@@ -0,0 +1,32 @@
can_wrap_assert_fail_test_code = '''
#include <cstdlib>
#include <cassert>
int main()
{
assert(0);
}
extern "C" void * __real___assert_fail(const char *, const char *, unsigned int, const char *);
extern "C" void *
__wrap___assert_fail(const char *, const char *, unsigned int, const char *)
{
return __real___assert_fail(nullptr, nullptr, 0, nullptr);
}
'''
wrap_assert_fail_args = [ '-Wl,--wrap=__assert_fail' ]
can_wrap_assert_fail = cxx.links(
can_wrap_assert_fail_test_code,
args : wrap_assert_fail_args,
name : 'linker can wrap __assert_fail',
)
if can_wrap_assert_fail
deps_other += declare_dependency(
sources : 'wrap-assert-fail.cc',
link_args : wrap_assert_fail_args,
)
endif

View File

@@ -0,0 +1,17 @@
#include "nix/util/error.hh"
#include <cstdio>
#include <cstdlib>
#include <cinttypes>
#include <string_view>
extern "C" [[noreturn]] void __attribute__((weak))
__wrap___assert_fail(const char * assertion, const char * file, unsigned int line, const char * function)
{
char buf[512];
int n =
snprintf(buf, sizeof(buf), "Assertion '%s' failed in %s at %s:%" PRIuLEAST32, assertion, function, file, line);
if (n < 0)
nix::panic("Assertion failed and could not format error message");
nix::panic(std::string_view(buf, std::min(static_cast<int>(sizeof(buf)), n)));
}

View File

@@ -5,6 +5,15 @@ if not (host_machine.system() == 'windows' and cxx.get_id() == 'gcc')
deps_private += dependency('threads')
endif
if host_machine.system() == 'cygwin'
# -std=gnu on cygwin defines 'unix', which conflicts with the namespace
add_project_arguments(
'-D_POSIX_C_SOURCE=200809L',
'-D_GNU_SOURCE',
language : 'cpp',
)
endif
add_project_arguments(
'-Wdeprecated-copy',
'-Werror=suggest-override',
@@ -33,10 +42,7 @@ if cxx.get_id() == 'clang'
add_project_arguments('-fpch-instantiate-templates', language : 'cpp')
endif
# Clang gets grumpy about missing libasan symbols if -shared-libasan is not
# passed when building shared libs, at least on Linux
if cxx.get_id() == 'clang' and ('address' in get_option('b_sanitize') or 'undefined' in get_option(
'b_sanitize',
))
add_project_link_arguments('-shared-libasan', language : 'cpp')
endif
# Darwin ld doesn't like "X.Y.Zpre"
nix_soversion = meson.project_version().split('pre')[0]
subdir('assert-fail')

View File

@@ -3,6 +3,6 @@
# This is needed for std::atomic on some platforms
# We did not manage to test this reliably on all platforms, so we hardcode
# it for now.
if host_machine.cpu_family() == 'arm'
if host_machine.cpu_family() in [ 'arm', 'ppc' ]
deps_other += cxx.find_library('atomic')
endif

View File

@@ -164,6 +164,24 @@ let
};
mesonLibraryLayer = finalAttrs: prevAttrs: {
preConfigure =
let
interpositionFlags = [
"-fno-semantic-interposition"
"-Wl,-Bsymbolic-functions"
];
in
# NOTE: By default GCC disables interprocedular optimizations (in particular inlining) for
# position-independent code and thus shared libraries.
# Since LD_PRELOAD tricks aren't worth losing out on optimizations, we disable it for good.
# This is not the case for Clang, where inlining is done by default even without -fno-semantic-interposition.
# https://reviews.llvm.org/D102453
# https://fedoraproject.org/wiki/Changes/PythonNoSemanticInterpositionSpeedup
prevAttrs.preConfigure or ""
+ lib.optionalString stdenv.cc.isGNU ''
export CFLAGS="''${CFLAGS:-} ${toString interpositionFlags}"
export CXXFLAGS="''${CXXFLAGS:-} ${toString interpositionFlags}"
'';
outputs = prevAttrs.outputs or [ "out" ] ++ [ "dev" ];
};

View File

@@ -10,27 +10,8 @@
stdenv,
}:
let
prevStdenv = stdenv;
in
let
inherit (pkgs) lib;
stdenv = if prevStdenv.isDarwin && prevStdenv.isx86_64 then darwinStdenv else prevStdenv;
# Fix the following error with the default x86_64-darwin SDK:
#
# error: aligned allocation function of type 'void *(std::size_t, std::align_val_t)' is only available on macOS 10.13 or newer
#
# Despite the use of the 10.13 deployment target here, the aligned
# allocation function Clang uses with this setting actually works
# all the way back to 10.6.
# NOTE: this is not just a version constraint, but a request to make Darwin
# provide this version level of support. Removing this minimum version
# request will regress the above error.
darwinStdenv = pkgs.overrideSDK prevStdenv { darwinMinVersion = "10.13"; };
in
scope: {
inherit stdenv;
@@ -76,15 +57,20 @@ scope: {
prevAttrs.postInstall;
});
toml11 = pkgs.toml11.overrideAttrs rec {
version = "4.4.0";
src = pkgs.fetchFromGitHub {
owner = "ToruNiina";
repo = "toml11";
tag = "v${version}";
hash = "sha256-sgWKYxNT22nw376ttGsTdg0AMzOwp8QH3E8mx0BZJTQ=";
};
};
# TODO: Remove this when https://github.com/NixOS/nixpkgs/pull/442682 is included in a stable release
toml11 =
if lib.versionAtLeast pkgs.toml11.version "4.4.0" then
pkgs.toml11
else
pkgs.toml11.overrideAttrs rec {
version = "4.4.0";
src = pkgs.fetchFromGitHub {
owner = "ToruNiina";
repo = "toml11";
tag = "v${version}";
hash = "sha256-sgWKYxNT22nw376ttGsTdg0AMzOwp8QH3E8mx0BZJTQ=";
};
};
# TODO Hack until https://github.com/NixOS/nixpkgs/issues/45462 is fixed.
boost =

View File

@@ -118,6 +118,7 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
modular.pre-commit.settings.package
(pkgs.writeScriptBin "pre-commit-hooks-install" modular.pre-commit.settings.installationScript)
pkgs.buildPackages.nixfmt-rfc-style
pkgs.buildPackages.shellcheck
pkgs.buildPackages.gdb
]
++ lib.optional (stdenv.cc.isClang && stdenv.hostPlatform == stdenv.buildPlatform) (

View File

@@ -55,18 +55,22 @@ readonly NIX_INSTALLED_NIX="@nix@"
readonly NIX_INSTALLED_CACERT="@cacert@"
#readonly NIX_INSTALLED_NIX="/nix/store/j8dbv5w6jl34caywh2ygdy88knx1mdf7-nix-2.3.6"
#readonly NIX_INSTALLED_CACERT="/nix/store/7dxhzymvy330i28ii676fl1pqwcahv2f-nss-cacert-3.49.2"
readonly EXTRACTED_NIX_PATH="$(dirname "$0")"
EXTRACTED_NIX_PATH="$(dirname "$0")"
readonly EXTRACTED_NIX_PATH
# allow to override identity change command
readonly NIX_BECOME=${NIX_BECOME:-sudo}
NIX_BECOME=${NIX_BECOME:-sudo}
readonly NIX_BECOME
readonly ROOT_HOME=~root
ROOT_HOME=~root
readonly ROOT_HOME
if [ -t 0 ] && [ -z "${NIX_INSTALLER_YES:-}" ]; then
readonly IS_HEADLESS='no'
IS_HEADLESS='no'
else
readonly IS_HEADLESS='yes'
IS_HEADLESS='yes'
fi
readonly IS_HEADLESS
headless() {
if [ "$IS_HEADLESS" = "yes" ]; then
@@ -156,6 +160,7 @@ EOF
}
nix_user_for_core() {
# shellcheck disable=SC2059
printf "$NIX_BUILD_USER_NAME_TEMPLATE" "$1"
}
@@ -381,10 +386,12 @@ _sudo() {
# Ensure that $TMPDIR exists if defined.
if [[ -n "${TMPDIR:-}" ]] && [[ ! -d "${TMPDIR:-}" ]]; then
# shellcheck disable=SC2174
mkdir -m 0700 -p "${TMPDIR:-}"
fi
readonly SCRATCH=$(mktemp -d)
SCRATCH=$(mktemp -d)
readonly SCRATCH
finish_cleanup() {
rm -rf "$SCRATCH"
}
@@ -677,7 +684,8 @@ create_directories() {
# hiding behind || true, and the general state
# should be one the user can repair once they
# figure out where chown is...
local get_chr_own="$(PATH="$(getconf PATH 2>/dev/null)" command -vp chown)"
local get_chr_own
get_chr_own="$(PATH="$(getconf PATH 2>/dev/null)" command -vp chown)"
if [[ -z "$get_chr_own" ]]; then
get_chr_own="$(command -v chown)"
fi
@@ -915,9 +923,11 @@ configure_shell_profile() {
fi
if [ -e "$profile_target" ]; then
shell_source_lines \
| _sudo "extend your $profile_target with nix-daemon settings" \
tee -a "$profile_target"
{
shell_source_lines
cat "$profile_target"
} | _sudo "extend your $profile_target with nix-daemon settings" \
tee "$profile_target"
fi
done
@@ -1013,6 +1023,7 @@ main() {
# Set profile targets after OS-specific scripts are loaded
if command -v poly_configure_default_profile_targets > /dev/null 2>&1; then
# shellcheck disable=SC2207
PROFILE_TARGETS=($(poly_configure_default_profile_targets))
else
PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/zshrc" "/etc/bash.bashrc" "/etc/zsh/zshrc")

View File

@@ -39,7 +39,7 @@ create_systemd_proxy_env() {
vars="http_proxy https_proxy ftp_proxy all_proxy no_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY ALL_PROXY NO_PROXY"
for v in $vars; do
if [ "x${!v:-}" != "x" ]; then
echo "Environment=${v}=$(escape_systemd_env ${!v})"
echo "Environment=${v}=$(escape_systemd_env "${!v}")"
fi
done
}

View File

@@ -83,12 +83,22 @@ nlohmann::json SingleBuiltPath::Built::toJSON(const StoreDirConfig & store) cons
nlohmann::json SingleBuiltPath::toJSON(const StoreDirConfig & store) const
{
return std::visit([&](const auto & buildable) { return buildable.toJSON(store); }, raw());
return std::visit(
overloaded{
[&](const SingleBuiltPath::Opaque & o) -> nlohmann::json { return store.printStorePath(o.path); },
[&](const SingleBuiltPath::Built & b) { return b.toJSON(store); },
},
raw());
}
nlohmann::json BuiltPath::toJSON(const StoreDirConfig & store) const
{
return std::visit([&](const auto & buildable) { return buildable.toJSON(store); }, raw());
return std::visit(
overloaded{
[&](const BuiltPath::Opaque & o) -> nlohmann::json { return store.printStorePath(o.path); },
[&](const BuiltPath::Built & b) { return b.toJSON(store); },
},
raw());
}
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const

View File

@@ -604,28 +604,28 @@ std::vector<BuiltPathWithResult> Installable::build(
static void throwBuildErrors(std::vector<KeyedBuildResult> & buildResults, const Store & store)
{
std::vector<KeyedBuildResult> failed;
std::vector<std::pair<const KeyedBuildResult *, const KeyedBuildResult::Failure *>> failed;
for (auto & buildResult : buildResults) {
if (!buildResult.success()) {
failed.push_back(buildResult);
if (auto * failure = buildResult.tryGetFailure()) {
failed.push_back({&buildResult, failure});
}
}
auto failedResult = failed.begin();
if (failedResult != failed.end()) {
if (failed.size() == 1) {
failedResult->rethrow();
failedResult->second->rethrow();
} else {
StringSet failedPaths;
for (; failedResult != failed.end(); failedResult++) {
if (!failedResult->errorMsg.empty()) {
if (!failedResult->second->errorMsg.empty()) {
logError(
ErrorInfo{
.level = lvlError,
.msg = failedResult->errorMsg,
.msg = failedResult->second->errorMsg,
});
}
failedPaths.insert(failedResult->path.to_string(store));
failedPaths.insert(failedResult->first->path.to_string(store));
}
throw Error("build of %s failed", concatStringsSep(", ", quoteStrings(failedPaths)));
}
@@ -695,12 +695,14 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
auto buildResults = store->buildPathsWithResults(pathsToBuild, bMode, evalStore);
throwBuildErrors(buildResults, *store);
for (auto & buildResult : buildResults) {
// If we didn't throw, they must all be sucesses
auto & success = std::get<nix::BuildResult::Success>(buildResult.inner);
for (auto & aux : backmap[buildResult.path]) {
std::visit(
overloaded{
[&](const DerivedPath::Built & bfd) {
std::map<std::string, StorePath> outputs;
for (auto & [outputName, realisation] : buildResult.builtOutputs)
for (auto & [outputName, realisation] : success.builtOutputs)
outputs.emplace(outputName, realisation.outPath);
res.push_back(
{aux.installable,

View File

@@ -67,6 +67,7 @@ config_priv_h = configure_file(
)
subdir('nix-meson-build-support/common')
subdir('nix-meson-build-support/asan-options')
sources = files(
'built-path.cc',
@@ -95,6 +96,7 @@ this_library = library(
'nixcmd',
sources,
config_priv_h,
soversion : nix_soversion,
dependencies : deps_public + deps_private + deps_other,
include_directories : include_dirs,
link_args : linker_export_flags,

View File

@@ -669,7 +669,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
ss << "No documentation found.\n\n";
}
auto markdown = toView(ss);
auto markdown = ss.view();
logger->cout(trim(renderMarkdownToTerminal(markdown)));
} else
@@ -760,7 +760,7 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
void NixRepl::initEnv()
{
env = &state->allocEnv(envSize);
env = &state->mem.allocEnv(envSize);
env->up = &state->baseEnv;
displ = 0;
staticEnv->vars.clear();
@@ -869,14 +869,8 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
Expr * NixRepl::parseString(std::string s)
{
return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv);
}
void NixRepl::evalString(std::string s, Value & v)
{
Expr * e;
try {
e = parseString(s);
return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv);
} catch (ParseError & e) {
if (e.msg().find("unexpected end of file") != std::string::npos)
// For parse errors on incomplete input, we continue waiting for the next line of
@@ -885,6 +879,11 @@ void NixRepl::evalString(std::string s, Value & v)
else
throw;
}
}
void NixRepl::evalString(std::string s, Value & v)
{
Expr * e = parseString(s);
e->eval(*state, *env, v);
state->forceValue(v, v.determinePos(noPos));
}

View File

@@ -28,6 +28,7 @@ deps_public_maybe_subproject = [
subdir('nix-meson-build-support/subprojects')
subdir('nix-meson-build-support/common')
subdir('nix-meson-build-support/asan-options')
sources = files(
'nix_api_expr.cc',
@@ -50,6 +51,7 @@ subdir('nix-meson-build-support/windows-version')
this_library = library(
'nixexprc',
sources,
soversion : nix_soversion,
dependencies : deps_public + deps_private + deps_other,
include_directories : include_dirs,
link_args : linker_export_flags,

View File

@@ -16,7 +16,7 @@
#include "nix_api_util_internal.h"
#if NIX_USE_BOEHMGC
# include <mutex>
# include <boost/unordered/concurrent_flat_map.hpp>
#endif
/**
@@ -137,7 +137,7 @@ nix_eval_state_builder * nix_eval_state_builder_new(nix_c_context * context, Sto
void nix_eval_state_builder_free(nix_eval_state_builder * builder)
{
delete builder;
operator delete(builder, static_cast<std::align_val_t>(alignof(nix_eval_state_builder)));
}
nix_err nix_eval_state_builder_load(nix_c_context * context, nix_eval_state_builder * builder)
@@ -203,32 +203,24 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c
void nix_state_free(EvalState * state)
{
delete state;
operator delete(state, static_cast<std::align_val_t>(alignof(EvalState)));
}
#if NIX_USE_BOEHMGC
std::unordered_map<
boost::concurrent_flat_map<
const void *,
unsigned int,
std::hash<const void *>,
std::equal_to<const void *>,
traceable_allocator<std::pair<const void * const, unsigned int>>>
nix_refcounts;
std::mutex nix_refcount_lock;
nix_refcounts{};
nix_err nix_gc_incref(nix_c_context * context, const void * p)
{
if (context)
context->last_err_code = NIX_OK;
try {
std::scoped_lock lock(nix_refcount_lock);
auto f = nix_refcounts.find(p);
if (f != nix_refcounts.end()) {
f->second++;
} else {
nix_refcounts[p] = 1;
}
nix_refcounts.insert_or_visit({p, 1}, [](auto & kv) { kv.second++; });
}
NIXC_CATCH_ERRS
}
@@ -239,12 +231,12 @@ nix_err nix_gc_decref(nix_c_context * context, const void * p)
if (context)
context->last_err_code = NIX_OK;
try {
std::scoped_lock lock(nix_refcount_lock);
auto f = nix_refcounts.find(p);
if (f != nix_refcounts.end()) {
if (--f->second == 0)
nix_refcounts.erase(f);
} else
bool fail = true;
nix_refcounts.erase_if(p, [&](auto & kv) {
fail = false;
return !--kv.second;
});
if (fail)
throw std::runtime_error("nix_gc_decref: object was not referenced");
}
NIXC_CATCH_ERRS

View File

@@ -1,5 +1,4 @@
#include "nix/expr/attr-set.hh"
#include "nix/expr/eval-error.hh"
#include "nix/util/configuration.hh"
#include "nix/expr/eval.hh"
#include "nix/store/globals.hh"
@@ -90,13 +89,8 @@ static void nix_c_primop_wrapper(
f(userdata, &ctx, (EvalState *) &state, (nix_value **) args, (nix_value *) &vTmp);
if (ctx.last_err_code != NIX_OK) {
if (ctx.last_err_code == NIX_ERR_RECOVERABLE) {
state.error<nix::RecoverableEvalError>("Recoverable error from custom function: %s", *ctx.last_err)
.atPos(pos)
.debugThrow();
} else {
state.error<nix::EvalError>("Error from custom function: %s", *ctx.last_err).atPos(pos).debugThrow();
}
/* TODO: Throw different errors depending on the error code */
state.error<nix::EvalError>("Error from custom function: %s", *ctx.last_err).atPos(pos).debugThrow();
}
if (!vTmp.isValid()) {
@@ -183,8 +177,6 @@ ValueType nix_get_type(nix_c_context * context, const nix_value * value)
switch (v.type()) {
case nThunk:
return NIX_TYPE_THUNK;
case nFailed:
return NIX_TYPE_FAILED;
case nInt:
return NIX_TYPE_INT;
case nFloat:
@@ -334,6 +326,10 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nList);
if (ix >= v.listSize()) {
nix_set_err_msg(context, NIX_ERR_KEY, "list index out of bounds");
return nullptr;
}
auto * p = v.listView()[ix];
nix_gc_incref(nullptr, p);
if (p != nullptr)
@@ -343,6 +339,26 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
NIXC_CATCH_ERRS_NULL
}
nix_value *
nix_get_list_byidx_lazy(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nList);
if (ix >= v.listSize()) {
nix_set_err_msg(context, NIX_ERR_KEY, "list index out of bounds");
return nullptr;
}
auto * p = v.listView()[ix];
nix_gc_incref(nullptr, p);
// Note: intentionally NOT calling forceValue() to keep the element lazy
return as_nix_value_ptr(p);
}
NIXC_CATCH_ERRS_NULL
}
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
{
if (context)
@@ -363,6 +379,27 @@ nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value
NIXC_CATCH_ERRS_NULL
}
nix_value *
nix_get_attr_byname_lazy(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
assert(v.type() == nix::nAttrs);
nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs()->get(s);
if (attr) {
nix_gc_incref(nullptr, attr->value);
// Note: intentionally NOT calling forceValue() to keep the attribute lazy
return as_nix_value_ptr(attr->value);
}
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
return nullptr;
}
NIXC_CATCH_ERRS_NULL
}
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
{
if (context)
@@ -379,13 +416,28 @@ bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalS
NIXC_CATCH_ERRS_RES(false);
}
nix_value * nix_get_attr_byidx(
nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name)
static void collapse_attrset_layer_chain_if_needed(nix::Value & v, EvalState * state)
{
auto & attrs = *v.attrs();
if (attrs.isLayered()) {
auto bindings = state->state.buildBindings(attrs.size());
std::ranges::copy(attrs, std::back_inserter(bindings));
v.mkAttrs(bindings);
}
}
nix_value *
nix_get_attr_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
collapse_attrset_layer_chain_if_needed(v, state);
if (i >= v.attrs()->size()) {
nix_set_err_msg(context, NIX_ERR_KEY, "attribute index out of bounds");
return nullptr;
}
const nix::Attr & a = (*v.attrs())[i];
*name = state->state.symbols[a.name].c_str();
nix_gc_incref(nullptr, a.value);
@@ -395,13 +447,38 @@ nix_value * nix_get_attr_byidx(
NIXC_CATCH_ERRS_NULL
}
const char *
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i)
nix_value * nix_get_attr_byidx_lazy(
nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
collapse_attrset_layer_chain_if_needed(v, state);
if (i >= v.attrs()->size()) {
nix_set_err_msg(context, NIX_ERR_KEY, "attribute index out of bounds (Nix C API contract violation)");
return nullptr;
}
const nix::Attr & a = (*v.attrs())[i];
*name = state->state.symbols[a.name].c_str();
nix_gc_incref(nullptr, a.value);
// Note: intentionally NOT calling forceValue() to keep the attribute lazy
return as_nix_value_ptr(a.value);
}
NIXC_CATCH_ERRS_NULL
}
const char * nix_get_attr_name_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i)
{
if (context)
context->last_err_code = NIX_OK;
try {
auto & v = check_value_in(value);
collapse_attrset_layer_chain_if_needed(v, state);
if (i >= v.attrs()->size()) {
nix_set_err_msg(context, NIX_ERR_KEY, "attribute index out of bounds (Nix C API contract violation)");
return nullptr;
}
const nix::Attr & a = (*v.attrs())[i];
return state->state.symbols[a.name].c_str();
}
@@ -602,7 +679,7 @@ nix_err nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * b
context->last_err_code = NIX_OK;
try {
auto & v = check_value_not_null(value);
nix::Symbol s = bb->builder.state.get().symbols.create(name);
nix::Symbol s = bb->builder.symbols.get().create(name);
bb->builder.insert(s, &v);
}
NIXC_CATCH_ERRS

View File

@@ -32,8 +32,7 @@ typedef enum {
NIX_TYPE_ATTRS,
NIX_TYPE_LIST,
NIX_TYPE_FUNCTION,
NIX_TYPE_EXTERNAL,
NIX_TYPE_FAILED,
NIX_TYPE_EXTERNAL
} ValueType;
// forward declarations
@@ -266,10 +265,25 @@ ExternalValue * nix_get_external(nix_c_context * context, nix_value * value);
*/
nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix);
/** @brief Get an attr by name
/** @brief Get the ix'th element of a list without forcing evaluation of the element
*
* Returns the list element without forcing its evaluation, allowing access to lazy values.
* The list value itself must already be evaluated.
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect (must be an evaluated list)
* @param[in] state nix evaluator state
* @param[in] ix list element to get
* @return value, NULL in case of errors
*/
nix_value *
nix_get_list_byidx_lazy(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix);
/** @brief Get an attr by name
*
* Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
* @param[in] name attribute name
@@ -277,6 +291,21 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
*/
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
/** @brief Get an attribute value by attribute name, without forcing evaluation of the attribute's value
*
* Returns the attribute value without forcing its evaluation, allowing access to lazy values.
* The attribute set value itself must already be evaluated.
*
* Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect (must be an evaluated attribute set)
* @param[in] state nix evaluator state
* @param[in] name attribute name
* @return value, NULL in case of errors
*/
nix_value *
nix_get_attr_byname_lazy(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
/** @brief Check if an attribute name exists on a value
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
@@ -286,11 +315,21 @@ nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value
*/
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
/** @brief Get an attribute by index in the sorted bindings
/** @brief Get an attribute by index
*
* Also gives you the name.
*
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
* Attributes are returned in an unspecified order which is NOT suitable for
* reproducible operations. In Nix's domain, reproducibility is paramount. The caller
* is responsible for sorting the attributes or storing them in an ordered map to
* ensure deterministic behavior in your application.
*
* @note When Nix does sort attributes, which it does for virtually all intermediate
* operations and outputs, it uses byte-wise lexicographic order (equivalent to
* lexicographic order by Unicode scalar value for valid UTF-8). We recommend
* applying this same ordering for consistency.
*
* Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
* @param[in] state nix evaluator state
@@ -298,12 +337,50 @@ bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalS
* @param[out] name will store a pointer to the attribute name
* @return value, NULL in case of errors
*/
nix_value * nix_get_attr_byidx(
nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name);
nix_value *
nix_get_attr_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name);
/** @brief Get an attribute name by index in the sorted bindings
/** @brief Get an attribute by index, without forcing evaluation of the attribute's value
*
* Useful when you want the name but want to avoid evaluation.
* Also gives you the name.
*
* Returns the attribute value without forcing its evaluation, allowing access to lazy values.
* The attribute set value itself must already have been evaluated.
*
* Attributes are returned in an unspecified order which is NOT suitable for
* reproducible operations. In Nix's domain, reproducibility is paramount. The caller
* is responsible for sorting the attributes or storing them in an ordered map to
* ensure deterministic behavior in your application.
*
* @note When Nix does sort attributes, which it does for virtually all intermediate
* operations and outputs, it uses byte-wise lexicographic order (equivalent to
* lexicographic order by Unicode scalar value for valid UTF-8). We recommend
* applying this same ordering for consistency.
*
* Use nix_gc_decref when you're done with the pointer
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect (must be an evaluated attribute set)
* @param[in] state nix evaluator state
* @param[in] i attribute index
* @param[out] name will store a pointer to the attribute name
* @return value, NULL in case of errors
*/
nix_value * nix_get_attr_byidx_lazy(
nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name);
/** @brief Get an attribute name by index
*
* Returns the attribute name without forcing evaluation of the attribute's value.
*
* Attributes are returned in an unspecified order which is NOT suitable for
* reproducible operations. In Nix's domain, reproducibility is paramount. The caller
* is responsible for sorting the attributes or storing them in an ordered map to
* ensure deterministic behavior in your application.
*
* @note When Nix does sort attributes, which it does for virtually all intermediate
* operations and outputs, it uses byte-wise lexicographic order (equivalent to
* lexicographic order by Unicode scalar value for valid UTF-8). We recommend
* applying this same ordering for consistency.
*
* Owned by the nix EvalState
* @param[out] context Optional, stores error information
@@ -312,8 +389,7 @@ nix_value * nix_get_attr_byidx(
* @param[in] i attribute index
* @return name, NULL in case of errors
*/
const char *
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i);
const char * nix_get_attr_name_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i);
/**@}*/
/** @name Initializers

View File

@@ -26,11 +26,20 @@ public:
}
protected:
LibExprTest()
LibExprTest(ref<Store> store, auto && makeEvalSettings)
: LibStoreTest()
, evalSettings(makeEvalSettings(readOnlyMode))
, state({}, store, fetchSettings, evalSettings, nullptr)
{
evalSettings.nixPath = {};
}
LibExprTest()
: LibExprTest(openStore("dummy://"), [](bool & readOnlyMode) {
EvalSettings settings{readOnlyMode};
settings.nixPath = {};
return settings;
})
{
}
Value eval(std::string input, bool forceValue = true)

View File

@@ -31,6 +31,7 @@ rapidcheck = dependency('rapidcheck')
deps_public += rapidcheck
subdir('nix-meson-build-support/common')
subdir('nix-meson-build-support/asan-options')
sources = files(
'tests/value/context.cc',
@@ -44,6 +45,7 @@ subdir('nix-meson-build-support/windows-version')
this_library = library(
'nix-expr-test-support',
sources,
soversion : nix_soversion,
dependencies : deps_public + deps_private + deps_other,
include_directories : include_dirs,
# TODO: Remove `-lrapidcheck` when https://github.com/emil-e/rapidcheck/pull/326

View File

@@ -3,6 +3,7 @@
#include "nix/expr/eval.hh"
#include "nix/expr/tests/libexpr.hh"
#include "nix/util/memory-source-accessor.hh"
namespace nix {
@@ -174,4 +175,41 @@ TEST_F(EvalStateTest, getBuiltin_fail)
ASSERT_THROW(state.getBuiltin("nonexistent"), EvalError);
}
class PureEvalTest : public LibExprTest
{
public:
PureEvalTest()
: LibExprTest(openStore("dummy://", {{"read-only", "false"}}), [](bool & readOnlyMode) {
EvalSettings settings{readOnlyMode};
settings.pureEval = true;
settings.restrictEval = true;
return settings;
})
{
}
};
TEST_F(PureEvalTest, pathExists)
{
ASSERT_THAT(eval("builtins.pathExists /."), IsFalse());
ASSERT_THAT(eval("builtins.pathExists /nix"), IsFalse());
ASSERT_THAT(eval("builtins.pathExists /nix/store"), IsFalse());
{
std::string contents = "Lorem ipsum";
StringSource s{contents};
auto path = state.store->addToStoreFromDump(
s, "source", FileSerialisationMethod::Flat, ContentAddressMethod::Raw::Text, HashAlgorithm::SHA256);
auto printed = store->printStorePath(path);
ASSERT_THROW(eval(fmt("builtins.readFile %s", printed)), RestrictedPathError);
ASSERT_THAT(eval(fmt("builtins.pathExists %s", printed)), IsFalse());
ASSERT_THROW(eval("builtins.readDir /."), RestrictedPathError);
state.allowPath(path); // FIXME: This shouldn't behave this way.
ASSERT_THAT(eval("builtins.readDir /."), IsAttrsOfSize(0));
}
}
} // namespace nix

View File

@@ -1,40 +1,15 @@
#include <gtest/gtest.h>
#include <cstdlib>
#include "nix/store/globals.hh"
#include "nix/util/logging.hh"
#include "nix/store/tests/test-main.hh"
#include "nix/util/config-global.hh"
using namespace nix;
int main(int argc, char ** argv)
{
if (argc > 1 && std::string_view(argv[1]) == "__build-remote") {
printError("test-build-remote: not supported in libexpr unit tests");
return 1;
}
// Disable build hook. We won't be testing remote builds in these unit tests. If we do, fix the above build hook.
settings.buildHook = {};
#ifdef __linux__ // should match the conditional around sandboxBuildDir declaration.
// When building and testing nix within the host's Nix sandbox, our store dir will be located in the host's
// sandboxBuildDir, e.g.: Host
// storeDir = /nix/store
// sandboxBuildDir = /build
// This process
// storeDir = /build/foo/bar/store
// sandboxBuildDir = /build
// However, we have a rule that the store dir must not be inside the storeDir, so we need to pick a different
// sandboxBuildDir.
settings.sandboxBuildDir = "/test-build-dir-instead-of-usual-build-dir";
#endif
#ifdef __APPLE__
// Avoid this error, when already running in a sandbox:
// sandbox-exec: sandbox_apply: Operation not permitted
settings.sandboxMode = smDisabled;
setEnv("_NIX_TEST_NO_SANDBOX", "1");
#endif
auto res = testMainForBuidingPre(argc, argv);
if (res)
return res;
// For pipe operator tests in trivial.cc
experimentalFeatureSettings.set("experimental-features", "pipe-operators");

View File

@@ -45,6 +45,7 @@ config_priv_h = configure_file(
)
subdir('nix-meson-build-support/common')
subdir('nix-meson-build-support/asan-options')
sources = files(
'derived-path.cc',
@@ -82,7 +83,7 @@ this_exe = executable(
test(
meson.project_name(),
this_exe,
env : {
env : asan_test_options_env + {
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
},
protocol : 'gtest',

View File

@@ -423,6 +423,55 @@ TEST_F(nix_api_expr_test, nix_expr_primop_bad_return_thunk)
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("badReturnThunk"));
}
static void primop_with_nix_err_key(
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
{
nix_set_err_msg(context, NIX_ERR_KEY, "Test error from primop");
}
TEST_F(nix_api_expr_test, nix_expr_primop_nix_err_key_conversion)
{
// Test that NIX_ERR_KEY from a custom primop gets converted to a generic EvalError
//
// RATIONALE: NIX_ERR_KEY must not be propagated from custom primops because it would
// create semantic confusion. NIX_ERR_KEY indicates missing keys/indices in C API functions
// (like nix_get_attr_byname, nix_get_list_byidx). If custom primops could return NIX_ERR_KEY,
// an evaluation error would be indistinguishable from an actual missing attribute.
//
// For example, if nix_get_attr_byname returned NIX_ERR_KEY when the attribute is present
// but the value evaluation fails, callers expecting NIX_ERR_KEY to mean "missing attribute"
// would incorrectly handle evaluation failures as missing attributes. In places where
// missing attributes are tolerated (like optional attributes), this would cause the
// program to continue after swallowing the error, leading to silent failures.
PrimOp * primop = nix_alloc_primop(
ctx, primop_with_nix_err_key, 1, "testErrorPrimop", nullptr, "a test primop that sets NIX_ERR_KEY", nullptr);
assert_ctx_ok();
nix_value * primopValue = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_primop(ctx, primopValue, primop);
assert_ctx_ok();
nix_value * arg = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_int(ctx, arg, 42);
assert_ctx_ok();
nix_value * result = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_value_call(ctx, state, primopValue, arg, result);
// Verify that NIX_ERR_KEY gets converted to NIX_ERR_NIX_ERROR (generic evaluation error)
ASSERT_EQ(nix_err_code(ctx), NIX_ERR_NIX_ERROR);
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("Error from custom function"));
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("Test error from primop"));
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("testErrorPrimop"));
// Clean up
nix_gc_decref(ctx, primopValue);
nix_gc_decref(ctx, arg);
nix_gc_decref(ctx, result);
}
TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
{
nix_value * n = nix_alloc_value(ctx, state);
@@ -438,104 +487,30 @@ TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
ASSERT_EQ(3, rInt);
}
// The following is a test case for retryable thunks.
// This is a requirement for the current way in which NixOps4 evaluates its deployment expressions.
// An alternative strategy could be implemented, but unwinding the stack may be a more efficient way to deal with many
// suspensions/resumptions, compared to e.g. using a thread or coroutine stack for each suspended dependency. This test
// models the essential bits of a deployment tool that uses such a strategy.
// State for the retryable primop - simulates deployment resource availability
struct DeploymentResourceState
TEST_F(nix_api_expr_test, nix_expr_attrset_update)
{
bool vm_created = false;
};
nix_expr_eval_from_string(ctx, state, "{ a = 0; b = 2; } // { a = 1; b = 3; } // { a = 2; }", ".", value);
assert_ctx_ok();
static void primop_load_resource_input(
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
{
assert(context);
assert(state);
auto * resource_state = static_cast<DeploymentResourceState *>(user_data);
// Get the resource input name argument
std::string input_name;
if (nix_get_string(context, args[0], OBSERVE_STRING(input_name)) != NIX_OK)
return;
// Only handle "vm_id" input - throw for anything else
if (input_name != "vm_id") {
std::string error_msg = "unknown resource input: " + input_name;
nix_set_err_msg(context, NIX_ERR_NIX_ERROR, error_msg.c_str());
return;
ASSERT_EQ(nix_get_attrs_size(ctx, value), 2);
assert_ctx_ok();
std::array<std::pair<std::string_view, nix_value *>, 2> values;
for (unsigned int i = 0; i < 2; ++i) {
const char * name;
values[i].second = nix_get_attr_byidx(ctx, value, state, i, &name);
assert_ctx_ok();
values[i].first = name;
}
std::sort(values.begin(), values.end(), [](const auto & lhs, const auto & rhs) { return lhs.first < rhs.first; });
if (resource_state->vm_created) {
// VM has been created, return the ID
nix_init_string(context, ret, "vm-12345");
} else {
// VM not created yet, fail with dependency error
nix_set_err_msg(context, NIX_ERR_RECOVERABLE, "VM not yet created");
}
}
TEST_F(nix_api_expr_test, nix_expr_thunk_re_evaluation_after_deployment)
{
// This test demonstrates NixOps4's requirement: a thunk calling a primop should be
// re-evaluable when deployment resources become available that were not available initially.
DeploymentResourceState resource_state;
PrimOp * primop = nix_alloc_primop(
ctx,
primop_load_resource_input,
1,
"loadResourceInput",
nullptr,
"load a deployment resource input",
&resource_state);
nix_value * a = values[0].second;
ASSERT_EQ("a", values[0].first);
ASSERT_EQ(nix_get_int(ctx, a), 2);
assert_ctx_ok();
nix_value * primopValue = nix_alloc_value(ctx, state);
nix_value * b = values[1].second;
ASSERT_EQ("b", values[1].first);
ASSERT_EQ(nix_get_int(ctx, b), 3);
assert_ctx_ok();
nix_init_primop(ctx, primopValue, primop);
assert_ctx_ok();
nix_value * inputName = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_string(ctx, inputName, "vm_id");
assert_ctx_ok();
// Create a single thunk by using nix_init_apply instead of nix_value_call
// This creates a lazy application that can be forced multiple times
nix_value * thunk = nix_alloc_value(ctx, state);
assert_ctx_ok();
nix_init_apply(ctx, thunk, primopValue, inputName);
assert_ctx_ok();
// First force: VM not created yet, should fail
nix_value_force(ctx, state, thunk);
ASSERT_EQ(NIX_ERR_NIX_ERROR, nix_err_code(ctx));
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("VM not yet created"));
// Clear the error context for the next attempt
nix_c_context_free(ctx);
ctx = nix_c_context_create();
// Simulate deployment process: VM gets created
resource_state.vm_created = true;
// Second force of the SAME thunk: this is where the "failed" value issue appears
// With failed value caching, this should fail because the thunk is marked as permanently failed
// Without failed value caching (or with retryable failures), this should succeed
nix_value_force(ctx, state, thunk);
// If we get here without error, the thunk was successfully re-evaluated
assert_ctx_ok();
std::string result;
nix_get_string(ctx, thunk, OBSERVE_STRING(result));
assert_ctx_ok();
ASSERT_STREQ("vm-12345", result.c_str());
}
} // namespace nixC

View File

@@ -162,6 +162,114 @@ TEST_F(nix_api_expr_test, nix_build_and_init_list)
nix_gc_decref(ctx, intValue);
}
TEST_F(nix_api_expr_test, nix_get_list_byidx_large_indices)
{
// Create a small list to test extremely large out-of-bounds access
ListBuilder * builder = nix_make_list_builder(ctx, state, 2);
nix_value * intValue = nix_alloc_value(ctx, state);
nix_init_int(ctx, intValue, 42);
nix_list_builder_insert(ctx, builder, 0, intValue);
nix_list_builder_insert(ctx, builder, 1, intValue);
nix_make_list(ctx, builder, value);
nix_list_builder_free(builder);
// Test extremely large indices that would definitely crash without bounds checking
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, 1000000));
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, UINT_MAX / 2));
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, UINT_MAX / 2 + 1000000));
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
// Clean up
nix_gc_decref(ctx, intValue);
}
TEST_F(nix_api_expr_test, nix_get_list_byidx_lazy)
{
// Create a list with a throwing lazy element, an already-evaluated int, and a lazy function call
// 1. Throwing lazy element - create a function application thunk that will throw when forced
nix_value * throwingFn = nix_alloc_value(ctx, state);
nix_value * throwingValue = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(
ctx,
state,
R"(
_: throw "This should not be evaluated by the lazy accessor"
)",
"<test>",
throwingFn);
assert_ctx_ok();
nix_init_apply(ctx, throwingValue, throwingFn, throwingFn);
assert_ctx_ok();
// 2. Already evaluated int (not lazy)
nix_value * intValue = nix_alloc_value(ctx, state);
nix_init_int(ctx, intValue, 42);
assert_ctx_ok();
// 3. Lazy function application that would compute increment 5 = 6
nix_value * lazyApply = nix_alloc_value(ctx, state);
nix_value * incrementFn = nix_alloc_value(ctx, state);
nix_value * argFive = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(ctx, state, "x: x + 1", "<test>", incrementFn);
assert_ctx_ok();
nix_init_int(ctx, argFive, 5);
// Create a lazy application: (x: x + 1) 5
nix_init_apply(ctx, lazyApply, incrementFn, argFive);
assert_ctx_ok();
ListBuilder * builder = nix_make_list_builder(ctx, state, 3);
nix_list_builder_insert(ctx, builder, 0, throwingValue);
nix_list_builder_insert(ctx, builder, 1, intValue);
nix_list_builder_insert(ctx, builder, 2, lazyApply);
nix_make_list(ctx, builder, value);
nix_list_builder_free(builder);
// Test 1: Lazy accessor should return the throwing element without forcing evaluation
nix_value * lazyThrowingElement = nix_get_list_byidx_lazy(ctx, value, state, 0);
assert_ctx_ok();
ASSERT_NE(nullptr, lazyThrowingElement);
// Verify the element is still lazy by checking that forcing it throws
nix_value_force(ctx, state, lazyThrowingElement);
assert_ctx_err();
ASSERT_THAT(
nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("This should not be evaluated by the lazy accessor"));
// Test 2: Lazy accessor should return the already-evaluated int
nix_value * intElement = nix_get_list_byidx_lazy(ctx, value, state, 1);
assert_ctx_ok();
ASSERT_NE(nullptr, intElement);
ASSERT_EQ(42, nix_get_int(ctx, intElement));
// Test 3: Lazy accessor should return the lazy function application without forcing
nix_value * lazyFunctionElement = nix_get_list_byidx_lazy(ctx, value, state, 2);
assert_ctx_ok();
ASSERT_NE(nullptr, lazyFunctionElement);
// Force the lazy function application - should compute 5 + 1 = 6
nix_value_force(ctx, state, lazyFunctionElement);
assert_ctx_ok();
ASSERT_EQ(6, nix_get_int(ctx, lazyFunctionElement));
// Clean up
nix_gc_decref(ctx, throwingFn);
nix_gc_decref(ctx, throwingValue);
nix_gc_decref(ctx, intValue);
nix_gc_decref(ctx, lazyApply);
nix_gc_decref(ctx, incrementFn);
nix_gc_decref(ctx, argFive);
nix_gc_decref(ctx, lazyThrowingElement);
nix_gc_decref(ctx, intElement);
nix_gc_decref(ctx, lazyFunctionElement);
}
TEST_F(nix_api_expr_test, nix_build_and_init_attr_invalid)
{
ASSERT_EQ(nullptr, nix_get_attr_byname(ctx, nullptr, state, 0));
@@ -244,6 +352,225 @@ TEST_F(nix_api_expr_test, nix_build_and_init_attr)
free(out_name);
}
TEST_F(nix_api_expr_test, nix_get_attr_byidx_large_indices)
{
// Create a small attribute set to test extremely large out-of-bounds access
const char ** out_name = (const char **) malloc(sizeof(char *));
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, 2);
nix_value * intValue = nix_alloc_value(ctx, state);
nix_init_int(ctx, intValue, 42);
nix_bindings_builder_insert(ctx, builder, "test", intValue);
nix_make_attrs(ctx, value, builder);
nix_bindings_builder_free(builder);
// Test extremely large indices that would definitely crash without bounds checking
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, 1000000, out_name));
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, UINT_MAX / 2, out_name));
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, UINT_MAX / 2 + 1000000, out_name));
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
// Test nix_get_attr_name_byidx with large indices too
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, 1000000));
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, UINT_MAX / 2));
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, UINT_MAX / 2 + 1000000));
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
// Clean up
nix_gc_decref(ctx, intValue);
free(out_name);
}
TEST_F(nix_api_expr_test, nix_get_attr_byname_lazy)
{
// Create an attribute set with a throwing lazy attribute, an already-evaluated int, and a lazy function call
// 1. Throwing lazy element - create a function application thunk that will throw when forced
nix_value * throwingFn = nix_alloc_value(ctx, state);
nix_value * throwingValue = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(
ctx,
state,
R"(
_: throw "This should not be evaluated by the lazy accessor"
)",
"<test>",
throwingFn);
assert_ctx_ok();
nix_init_apply(ctx, throwingValue, throwingFn, throwingFn);
assert_ctx_ok();
// 2. Already evaluated int (not lazy)
nix_value * intValue = nix_alloc_value(ctx, state);
nix_init_int(ctx, intValue, 42);
assert_ctx_ok();
// 3. Lazy function application that would compute increment 7 = 8
nix_value * lazyApply = nix_alloc_value(ctx, state);
nix_value * incrementFn = nix_alloc_value(ctx, state);
nix_value * argSeven = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(ctx, state, "x: x + 1", "<test>", incrementFn);
assert_ctx_ok();
nix_init_int(ctx, argSeven, 7);
// Create a lazy application: (x: x + 1) 7
nix_init_apply(ctx, lazyApply, incrementFn, argSeven);
assert_ctx_ok();
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, 3);
nix_bindings_builder_insert(ctx, builder, "throwing", throwingValue);
nix_bindings_builder_insert(ctx, builder, "normal", intValue);
nix_bindings_builder_insert(ctx, builder, "lazy", lazyApply);
nix_make_attrs(ctx, value, builder);
nix_bindings_builder_free(builder);
// Test 1: Lazy accessor should return the throwing attribute without forcing evaluation
nix_value * lazyThrowingAttr = nix_get_attr_byname_lazy(ctx, value, state, "throwing");
assert_ctx_ok();
ASSERT_NE(nullptr, lazyThrowingAttr);
// Verify the attribute is still lazy by checking that forcing it throws
nix_value_force(ctx, state, lazyThrowingAttr);
assert_ctx_err();
ASSERT_THAT(
nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("This should not be evaluated by the lazy accessor"));
// Test 2: Lazy accessor should return the already-evaluated int
nix_value * intAttr = nix_get_attr_byname_lazy(ctx, value, state, "normal");
assert_ctx_ok();
ASSERT_NE(nullptr, intAttr);
ASSERT_EQ(42, nix_get_int(ctx, intAttr));
// Test 3: Lazy accessor should return the lazy function application without forcing
nix_value * lazyFunctionAttr = nix_get_attr_byname_lazy(ctx, value, state, "lazy");
assert_ctx_ok();
ASSERT_NE(nullptr, lazyFunctionAttr);
// Force the lazy function application - should compute 7 + 1 = 8
nix_value_force(ctx, state, lazyFunctionAttr);
assert_ctx_ok();
ASSERT_EQ(8, nix_get_int(ctx, lazyFunctionAttr));
// Test 4: Missing attribute should return NULL with NIX_ERR_KEY
nix_value * missingAttr = nix_get_attr_byname_lazy(ctx, value, state, "nonexistent");
ASSERT_EQ(nullptr, missingAttr);
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
// Clean up
nix_gc_decref(ctx, throwingFn);
nix_gc_decref(ctx, throwingValue);
nix_gc_decref(ctx, intValue);
nix_gc_decref(ctx, lazyApply);
nix_gc_decref(ctx, incrementFn);
nix_gc_decref(ctx, argSeven);
nix_gc_decref(ctx, lazyThrowingAttr);
nix_gc_decref(ctx, intAttr);
nix_gc_decref(ctx, lazyFunctionAttr);
}
TEST_F(nix_api_expr_test, nix_get_attr_byidx_lazy)
{
// Create an attribute set with a throwing lazy attribute, an already-evaluated int, and a lazy function call
// 1. Throwing lazy element - create a function application thunk that will throw when forced
nix_value * throwingFn = nix_alloc_value(ctx, state);
nix_value * throwingValue = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(
ctx,
state,
R"(
_: throw "This should not be evaluated by the lazy accessor"
)",
"<test>",
throwingFn);
assert_ctx_ok();
nix_init_apply(ctx, throwingValue, throwingFn, throwingFn);
assert_ctx_ok();
// 2. Already evaluated int (not lazy)
nix_value * intValue = nix_alloc_value(ctx, state);
nix_init_int(ctx, intValue, 99);
assert_ctx_ok();
// 3. Lazy function application that would compute increment 10 = 11
nix_value * lazyApply = nix_alloc_value(ctx, state);
nix_value * incrementFn = nix_alloc_value(ctx, state);
nix_value * argTen = nix_alloc_value(ctx, state);
nix_expr_eval_from_string(ctx, state, "x: x + 1", "<test>", incrementFn);
assert_ctx_ok();
nix_init_int(ctx, argTen, 10);
// Create a lazy application: (x: x + 1) 10
nix_init_apply(ctx, lazyApply, incrementFn, argTen);
assert_ctx_ok();
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, 3);
nix_bindings_builder_insert(ctx, builder, "a_throwing", throwingValue);
nix_bindings_builder_insert(ctx, builder, "b_normal", intValue);
nix_bindings_builder_insert(ctx, builder, "c_lazy", lazyApply);
nix_make_attrs(ctx, value, builder);
nix_bindings_builder_free(builder);
// Proper usage: first get the size and gather all attributes into a map
unsigned int attrCount = nix_get_attrs_size(ctx, value);
assert_ctx_ok();
ASSERT_EQ(3u, attrCount);
// Gather all attributes into a map (proper contract usage)
std::map<std::string, nix_value *> attrMap;
const char * name;
for (unsigned int i = 0; i < attrCount; i++) {
nix_value * attr = nix_get_attr_byidx_lazy(ctx, value, state, i, &name);
assert_ctx_ok();
ASSERT_NE(nullptr, attr);
attrMap[std::string(name)] = attr;
}
// Now test the gathered attributes
ASSERT_EQ(3u, attrMap.size());
ASSERT_TRUE(attrMap.count("a_throwing"));
ASSERT_TRUE(attrMap.count("b_normal"));
ASSERT_TRUE(attrMap.count("c_lazy"));
// Test 1: Throwing attribute should be lazy
nix_value * throwingAttr = attrMap["a_throwing"];
nix_value_force(ctx, state, throwingAttr);
assert_ctx_err();
ASSERT_THAT(
nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("This should not be evaluated by the lazy accessor"));
// Test 2: Normal attribute should be already evaluated
nix_value * normalAttr = attrMap["b_normal"];
ASSERT_EQ(99, nix_get_int(ctx, normalAttr));
// Test 3: Lazy function should compute when forced
nix_value * lazyAttr = attrMap["c_lazy"];
nix_value_force(ctx, state, lazyAttr);
assert_ctx_ok();
ASSERT_EQ(11, nix_get_int(ctx, lazyAttr));
// Clean up
nix_gc_decref(ctx, throwingFn);
nix_gc_decref(ctx, throwingValue);
nix_gc_decref(ctx, intValue);
nix_gc_decref(ctx, lazyApply);
nix_gc_decref(ctx, incrementFn);
nix_gc_decref(ctx, argTen);
for (auto & pair : attrMap) {
nix_gc_decref(ctx, pair.second);
}
}
TEST_F(nix_api_expr_test, nix_value_init)
{
// Setup

View File

@@ -62,6 +62,7 @@ mkMesonExecutable (finalAttrs: {
mkdir -p "$HOME"
''
+ ''
export ASAN_OPTIONS=abort_on_error=1:print_summary=1:detect_leaks=0
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
touch $out

View File

@@ -195,18 +195,18 @@ TEST_F(PrimOpTest, unsafeGetAttrPos)
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(3));
auto file = v.attrs()->find(createSymbol("file"));
auto file = v.attrs()->get(createSymbol("file"));
ASSERT_NE(file, nullptr);
ASSERT_THAT(*file->value, IsString());
auto s = baseNameOf(file->value->string_view());
ASSERT_EQ(s, "foo.nix");
auto line = v.attrs()->find(createSymbol("line"));
auto line = v.attrs()->get(createSymbol("line"));
ASSERT_NE(line, nullptr);
state.forceValue(*line->value, noPos);
ASSERT_THAT(*line->value, IsIntEq(4));
auto column = v.attrs()->find(createSymbol("column"));
auto column = v.attrs()->get(createSymbol("column"));
ASSERT_NE(column, nullptr);
state.forceValue(*column->value, noPos);
ASSERT_THAT(*column->value, IsIntEq(3));
@@ -246,7 +246,7 @@ TEST_F(PrimOpTest, removeAttrsRetains)
{
auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]");
ASSERT_THAT(v, IsAttrsOfSize(1));
ASSERT_NE(v.attrs()->find(createSymbol("y")), nullptr);
ASSERT_NE(v.attrs()->get(createSymbol("y")), nullptr);
}
TEST_F(PrimOpTest, listToAttrsEmptyList)
@@ -266,7 +266,7 @@ TEST_F(PrimOpTest, listToAttrs)
{
auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto key = v.attrs()->find(createSymbol("key"));
auto key = v.attrs()->get(createSymbol("key"));
ASSERT_NE(key, nullptr);
ASSERT_THAT(*key->value, IsIntEq(123));
}
@@ -275,7 +275,7 @@ TEST_F(PrimOpTest, intersectAttrs)
{
auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs()->find(createSymbol("b"));
auto b = v.attrs()->get(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(3));
}
@@ -293,11 +293,11 @@ TEST_F(PrimOpTest, functionArgs)
auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto x = v.attrs()->find(createSymbol("x"));
auto x = v.attrs()->get(createSymbol("x"));
ASSERT_NE(x, nullptr);
ASSERT_THAT(*x->value, IsFalse());
auto y = v.attrs()->find(createSymbol("y"));
auto y = v.attrs()->get(createSymbol("y"));
ASSERT_NE(y, nullptr);
ASSERT_THAT(*y->value, IsTrue());
}
@@ -307,13 +307,13 @@ TEST_F(PrimOpTest, mapAttrs)
auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto a = v.attrs()->find(createSymbol("a"));
auto a = v.attrs()->get(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsThunk());
state.forceValue(*a->value, noPos);
ASSERT_THAT(*a->value, IsIntEq(10));
auto b = v.attrs()->find(createSymbol("b"));
auto b = v.attrs()->get(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsThunk());
state.forceValue(*b->value, noPos);
@@ -642,7 +642,7 @@ class ToStringPrimOpTest : public PrimOpTest,
TEST_P(ToStringPrimOpTest, toString)
{
const auto [input, output] = GetParam();
const auto & [input, output] = GetParam();
auto v = eval(input);
ASSERT_THAT(v, IsStringEq(output));
}
@@ -798,7 +798,7 @@ class CompareVersionsPrimOpTest : public PrimOpTest,
TEST_P(CompareVersionsPrimOpTest, compareVersions)
{
auto [expression, expectation] = GetParam();
const auto & [expression, expectation] = GetParam();
auto v = eval(expression);
ASSERT_THAT(v, IsIntEq(expectation));
}
@@ -834,16 +834,16 @@ class ParseDrvNamePrimOpTest
TEST_P(ParseDrvNamePrimOpTest, parseDrvName)
{
auto [input, expectedName, expectedVersion] = GetParam();
const auto & [input, expectedName, expectedVersion] = GetParam();
const auto expr = fmt("builtins.parseDrvName \"%1%\"", input);
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(2));
auto name = v.attrs()->find(createSymbol("name"));
auto name = v.attrs()->get(createSymbol("name"));
ASSERT_TRUE(name);
ASSERT_THAT(*name->value, IsStringEq(expectedName));
auto version = v.attrs()->find(createSymbol("version"));
auto version = v.attrs()->get(createSymbol("version"));
ASSERT_TRUE(version);
ASSERT_THAT(*version->value, IsStringEq(expectedVersion));
}

View File

@@ -75,11 +75,11 @@ TEST_F(TrivialExpressionTest, updateAttrs)
{
auto v = eval("{ a = 1; } // { b = 2; a = 3; }");
ASSERT_THAT(v, IsAttrsOfSize(2));
auto a = v.attrs()->find(createSymbol("a"));
auto a = v.attrs()->get(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsIntEq(3));
auto b = v.attrs()->find(createSymbol("b"));
auto b = v.attrs()->get(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(2));
}
@@ -176,7 +176,7 @@ TEST_P(AttrSetMergeTrvialExpressionTest, attrsetMergeLazy)
auto v = eval(expr);
ASSERT_THAT(v, IsAttrsOfSize(1));
auto a = v.attrs()->find(createSymbol("a"));
auto a = v.attrs()->get(createSymbol("a"));
ASSERT_NE(a, nullptr);
ASSERT_THAT(*a->value, IsThunk());
@@ -184,11 +184,11 @@ TEST_P(AttrSetMergeTrvialExpressionTest, attrsetMergeLazy)
ASSERT_THAT(*a->value, IsAttrsOfSize(2));
auto b = a->value->attrs()->find(createSymbol("b"));
auto b = a->value->attrs()->get(createSymbol("b"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(1));
auto c = a->value->attrs()->find(createSymbol("c"));
auto c = a->value->attrs()->get(createSymbol("c"));
ASSERT_NE(c, nullptr);
ASSERT_THAT(*c->value, IsIntEq(2));
}
@@ -330,7 +330,7 @@ TEST_F(TrivialExpressionTest, bindOr)
{
auto v = eval("{ or = 1; }");
ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs()->find(createSymbol("or"));
auto b = v.attrs()->get(createSymbol("or"));
ASSERT_NE(b, nullptr);
ASSERT_THAT(*b->value, IsIntEq(1));
}

View File

@@ -110,8 +110,8 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
{
Value * v2;
try {
auto dummyArgs = state.allocBindings(0);
v2 = findAlongAttrPath(state, "meta.position", *dummyArgs, v).first;
auto & dummyArgs = Bindings::emptyBindings;
v2 = findAlongAttrPath(state, "meta.position", dummyArgs, v).first;
} catch (Error &) {
throw NoPositionInfo("package '%s' has no source location information", what);
}

View File

@@ -5,36 +5,37 @@
namespace nix {
Bindings Bindings::emptyBindings;
/* Allocate a new array of attributes for an attribute set with a specific
capacity. The space is implicitly reserved after the Bindings
structure. */
Bindings * EvalState::allocBindings(size_t capacity)
Bindings * EvalMemory::allocBindings(size_t capacity)
{
if (capacity == 0)
return &emptyBindings;
if (capacity > std::numeric_limits<Bindings::size_t>::max())
return &Bindings::emptyBindings;
if (capacity > std::numeric_limits<Bindings::size_type>::max())
throw Error("attribute set of size %d is too big", capacity);
nrAttrsets++;
nrAttrsInAttrsets += capacity;
stats.nrAttrsets++;
stats.nrAttrsInAttrsets += capacity;
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
}
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
{
auto value = state.get().allocValue();
auto value = mem.get().allocValue();
bindings->push_back(Attr(name, value, pos));
return *value;
}
Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos)
{
return alloc(state.get().symbols.create(name), pos);
return alloc(symbols.get().create(name), pos);
}
void Bindings::sort()
{
if (size_)
std::sort(begin(), end());
std::sort(attrs, attrs + numAttrs);
}
Value & Value::mkAttrs(BindingsBuilder & bindings)

View File

@@ -113,6 +113,5 @@ template class EvalErrorBuilder<MissingArgumentError>;
template class EvalErrorBuilder<InfiniteRecursionError>;
template class EvalErrorBuilder<InvalidPathError>;
template class EvalErrorBuilder<IFDError>;
template class EvalErrorBuilder<RecoverableEvalError>;
} // namespace nix

View File

@@ -24,6 +24,10 @@
#endif
namespace nix {
#if NIX_USE_BOEHMGC
/*
* Ensure that Boehm satisfies our alignment requirements. This is the default configuration [^]
* and this assertion should never break for any platform. Let's assert it just in case.
@@ -35,9 +39,6 @@
*/
static_assert(sizeof(void *) * 2 == GC_GRANULE_BYTES, "Boehm GC must use GC_GRANULE_WORDS = 2");
namespace nix {
#if NIX_USE_BOEHMGC
/* Called when the Boehm GC runs out of memory. */
static void * oomHandler(size_t requested)
{

View File

@@ -324,7 +324,7 @@ void SampleStack::saveProfile()
std::visit([&](auto && info) { info.symbolize(state, os, posCache); }, pos);
}
os << " " << count;
writeLine(profileFd.get(), std::move(os).str());
writeLine(profileFd.get(), os.str());
/* Clear ostringstream. */
os.str("");
os.clear();

View File

@@ -1,5 +1,4 @@
#include "nix/expr/eval.hh"
#include "nix/expr/eval-error.hh"
#include "nix/expr/eval-settings.hh"
#include "nix/expr/primops.hh"
#include "nix/expr/print-options.hh"
@@ -18,6 +17,7 @@
#include "nix/expr/print.hh"
#include "nix/fetchers/filtering-source-accessor.hh"
#include "nix/util/memory-source-accessor.hh"
#include "nix/util/mounted-source-accessor.hh"
#include "nix/expr/gc-small-vector.hh"
#include "nix/util/url.hh"
#include "nix/fetchers/fetch-to-store.hh"
@@ -28,7 +28,6 @@
#include "parser-tab.hh"
#include <algorithm>
#include <exception>
#include <iostream>
#include <sstream>
#include <cstring>
@@ -40,6 +39,7 @@
#include <nlohmann/json.hpp>
#include <boost/container/small_vector.hpp>
#include <boost/unordered/concurrent_flat_map.hpp>
#include "nix/util/strings-inline.hh"
@@ -126,8 +126,6 @@ std::string_view showType(ValueType type, bool withArticle)
return WA("a", "float");
case nThunk:
return WA("a", "thunk");
case nFailed:
return WA("a", "failure");
}
unreachable();
}
@@ -196,6 +194,15 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
static constexpr size_t BASE_ENV_SIZE = 128;
EvalMemory::EvalMemory()
#if NIX_USE_BOEHMGC
: valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
#endif
{
assertGCInitialized();
}
EvalState::EvalState(
const LookupPath & lookupPathFromArguments,
ref<Store> store,
@@ -206,7 +213,6 @@ EvalState::EvalState(
, settings{settings}
, symbols(StaticEvalSymbols::staticSymbolTable())
, repair(NoRepair)
, emptyBindings(Bindings())
, storeFS(makeMountedSourceAccessor({
{CanonPath::root, makeEmptySourceAccessor()},
/* In the pure eval case, we can simply require
@@ -229,22 +235,18 @@ EvalState::EvalState(
*/
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
}))
, rootFS(({
, rootFS([&] {
/* In pure eval mode, we provide a filesystem that only
contains the Nix store.
If we have a chroot store and pure eval is not enabled,
use a union accessor to make the chroot store available
at its logical location while still having the
Otherwise, use a union accessor to make the augmented store
available at its logical location while still having the
underlying directory available. This is necessary for
instance if we're evaluating a file from the physical
/nix/store while using a chroot store. */
auto accessor = getFSSourceAccessor();
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
if (settings.pureEval || store->storeDir != realStoreDir) {
accessor = settings.pureEval ? storeFS : makeUnionSourceAccessor({accessor, storeFS});
}
/nix/store while using a chroot store, and also for lazy
mounted fetchTree. */
auto accessor = settings.pureEval ? storeFS.cast<SourceAccessor>()
: makeUnionSourceAccessor({getFSSourceAccessor(), storeFS});
/* Apply access control if needed. */
if (settings.restrictEval || settings.pureEval)
@@ -255,11 +257,11 @@ EvalState::EvalState(
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
});
accessor;
}))
return accessor;
}())
, corepkgsFS(make_ref<MemorySourceAccessor>())
, internalFS(make_ref<MemorySourceAccessor>())
, derivationInternal{corepkgsFS->addFile(
, derivationInternal{internalFS->addFile(
CanonPath("derivation-internal.nix"),
#include "primops/derivation.nix.gen.hh"
)}
@@ -269,14 +271,15 @@ EvalState::EvalState(
, debugRepl(nullptr)
, debugStop(false)
, trylevel(0)
, srcToStore(make_ref<decltype(srcToStore)::element_type>())
, importResolutionCache(make_ref<decltype(importResolutionCache)::element_type>())
, fileEvalCache(make_ref<decltype(fileEvalCache)::element_type>())
, regexCache(makeRegexCache())
#if NIX_USE_BOEHMGC
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
, baseEnvP(std::allocate_shared<Env *>(traceable_allocator<Env *>(), &allocEnv(BASE_ENV_SIZE)))
, baseEnvP(std::allocate_shared<Env *>(traceable_allocator<Env *>(), &mem.allocEnv(BASE_ENV_SIZE)))
, baseEnv(**baseEnvP)
#else
, baseEnv(allocEnv(BASE_ENV_SIZE))
, baseEnv(mem.allocEnv(BASE_ENV_SIZE))
#endif
, staticBaseEnv{std::make_shared<StaticEnv>(nullptr, nullptr)}
{
@@ -285,18 +288,8 @@ EvalState::EvalState(
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
assertGCInitialized();
static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes");
vEmptyList.mkList(buildList(0));
vNull.mkNull();
vTrue.mkBool(true);
vFalse.mkBool(false);
vStringRegular.mkStringNoCopy("regular");
vStringDirectory.mkStringNoCopy("directory");
vStringSymlink.mkStringNoCopy("symlink");
vStringUnknown.mkStringNoCopy("unknown");
static_assert(sizeof(Counter) == 64, "counters must be 64 bytes");
/* Construct the Nix expression search path. */
assert(lookupPath.elements.empty());
@@ -343,7 +336,7 @@ EvalState::EvalState(
EvalState::~EvalState() {}
void EvalState::allowPath(const Path & path)
void EvalState::allowPathLegacy(const Path & path)
{
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListSourceAccessor>())
rootFS2->allowPrefix(CanonPath(path));
@@ -591,12 +584,12 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
.name = name,
.arity = 0, // FIXME: figure out how deep by syntax only? It's not semantically useful though...
.args = {},
.doc = makeImmutableString(toView(s)), // NOTE: memory leak when compiled without GC
.doc = makeImmutableString(s.view()), // NOTE: memory leak when compiled without GC
};
}
if (isFunctor(v)) {
try {
Value & functor = *v.attrs()->find(s.functor)->value;
Value & functor = *v.attrs()->get(s.functor)->value;
Value * vp[] = {&v};
Value partiallyApplied;
// The first parameter is not user-provided, and may be
@@ -890,19 +883,18 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
}
}
ListBuilder::ListBuilder(EvalState & state, size_t size)
ListBuilder::ListBuilder(size_t size)
: size(size)
, elems(size <= 2 ? inlineElems : (Value **) allocBytes(size * sizeof(Value *)))
{
state.nrListElems += size;
}
Value * EvalState::getBool(bool b)
{
return b ? &vTrue : &vFalse;
return b ? &Value::vTrue : &Value::vFalse;
}
unsigned long nrThunks = 0;
static Counter nrThunks;
static inline void mkThunk(Value & v, Env & env, Expr * expr)
{
@@ -993,10 +985,6 @@ void EvalState::mkSingleDerivedPathString(const SingleDerivedPath & p, Value & v
});
}
/* Create a thunk for the delayed computation of the given expression
in the given environment. But if the expression is a variable,
then look it up right away. This significantly reduces the number
of thunks allocated. */
Value * Expr::maybeThunk(EvalState & state, Env & env)
{
Value * v = state.allocValue();
@@ -1040,61 +1028,90 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
return &v;
}
/**
* A helper `Expr` class to lets us parse and evaluate Nix expressions
* from a thunk, ensuring that every file is parsed/evaluated only
* once (via the thunk stored in `EvalState::fileEvalCache`).
*/
struct ExprParseFile : Expr, gc
{
// FIXME: make this a reference (see below).
SourcePath path;
bool mustBeTrivial;
ExprParseFile(SourcePath & path, bool mustBeTrivial)
: path(path)
, mustBeTrivial(mustBeTrivial)
{
}
void eval(EvalState & state, Env & env, Value & v) override
{
printTalkative("evaluating file '%s'", path);
auto e = state.parseExprFromFile(path);
try {
auto dts =
state.debugRepl
? makeDebugTraceStacker(
state, *e, state.baseEnv, e->getPos(), "while evaluating the file '%s':", path.to_string())
: nullptr;
// Enforce that 'flake.nix' is a direct attrset, not a
// computation.
if (mustBeTrivial && !(dynamic_cast<ExprAttrs *>(e)))
state.error<EvalError>("file '%s' must be an attribute set", path).debugThrow();
state.eval(e, v);
} catch (Error & e) {
state.addErrorTrace(e, "while evaluating the file '%s':", path.to_string());
throw;
}
}
};
void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
{
FileEvalCache::iterator i;
if ((i = fileEvalCache.find(path)) != fileEvalCache.end()) {
v = i->second;
auto resolvedPath = getConcurrent(*importResolutionCache, path);
if (!resolvedPath) {
resolvedPath = resolveExprPath(path);
importResolutionCache->emplace(path, *resolvedPath);
}
if (auto v2 = getConcurrent(*fileEvalCache, *resolvedPath)) {
forceValue(**v2, noPos);
v = **v2;
return;
}
auto resolvedPath = resolveExprPath(path);
if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) {
v = i->second;
return;
}
Value * vExpr;
// FIXME: put ExprParseFile on the stack instead of the heap once
// https://github.com/NixOS/nix/pull/13930 is merged. That will ensure
// the post-condition that `expr` is unreachable after
// `forceValue()` returns.
auto expr = new ExprParseFile{*resolvedPath, mustBeTrivial};
printTalkative("evaluating file '%1%'", resolvedPath);
Expr * e = nullptr;
fileEvalCache->try_emplace_and_cvisit(
*resolvedPath,
nullptr,
[&](auto & i) {
vExpr = allocValue();
vExpr->mkThunk(&baseEnv, expr);
i.second = vExpr;
},
[&](auto & i) { vExpr = i.second; });
auto j = fileParseCache.find(resolvedPath);
if (j != fileParseCache.end())
e = j->second;
forceValue(*vExpr, noPos);
if (!e)
e = parseExprFromFile(resolvedPath);
fileParseCache.emplace(resolvedPath, e);
try {
auto dts = debugRepl ? makeDebugTraceStacker(
*this,
*e,
this->baseEnv,
e->getPos(),
"while evaluating the file '%1%':",
resolvedPath.to_string())
: nullptr;
// Enforce that 'flake.nix' is a direct attrset, not a
// computation.
if (mustBeTrivial && !(dynamic_cast<ExprAttrs *>(e)))
error<EvalError>("file '%s' must be an attribute set", path).debugThrow();
eval(e, v);
} catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string());
throw;
}
fileEvalCache.emplace(resolvedPath, v);
if (path != resolvedPath)
fileEvalCache.emplace(path, v);
v = *vExpr;
}
void EvalState::resetFileCache()
{
fileEvalCache.clear();
fileParseCache.clear();
importResolutionCache->clear();
fileEvalCache->clear();
inputCache->clear();
}
@@ -1163,7 +1180,7 @@ void ExprPath::eval(EvalState & state, Env & env, Value & v)
Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
{
Env & inheritEnv = state.allocEnv(inheritFromExprs->size());
Env & inheritEnv = state.mem.allocEnv(inheritFromExprs->size());
inheritEnv.up = &up;
Displacement displ = 0;
@@ -1182,7 +1199,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
if (recursive) {
/* Create a new environment that contains the attributes in
this `rec'. */
Env & env2(state.allocEnv(attrs.size()));
Env & env2(state.mem.allocEnv(attrs.size()));
env2.up = &env;
dynamicEnv = &env2;
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
@@ -1274,7 +1291,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
{
/* Create a new environment that contains the attributes in this
`let'. */
Env & env2(state.allocEnv(attrs->attrs.size()));
Env & env2(state.mem.allocEnv(attrs->attrs.size()));
env2.up = &env;
Env * inheritEnv = attrs->inheritFromExprs ? attrs->buildInheritFromEnv(state, env2) : nullptr;
@@ -1305,7 +1322,7 @@ void ExprList::eval(EvalState & state, Env & env, Value & v)
Value * ExprList::maybeThunk(EvalState & state, Env & env)
{
if (elems.empty()) {
return &state.vEmptyList;
return &Value::vEmptyList;
}
return Expr::maybeThunk(state, env);
}
@@ -1317,7 +1334,7 @@ void ExprVar::eval(EvalState & state, Env & env, Value & v)
v = *v2;
}
static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & attrPath)
static std::string showAttrPath(EvalState & state, Env & env, std::span<const AttrName> attrPath)
{
std::ostringstream out;
bool first = true;
@@ -1353,10 +1370,10 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
env,
getPos(),
"while evaluating the attribute '%1%'",
showAttrPath(state, env, attrPath))
showAttrPath(state, env, getAttrPath()))
: nullptr;
for (auto & i : attrPath) {
for (auto & i : getAttrPath()) {
state.nrLookups++;
const Attr * j;
auto name = getName(i, state, env);
@@ -1394,7 +1411,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
auto origin = std::get_if<SourcePath>(&pos2r.origin);
if (!(origin && *origin == state.derivationInternal))
state.addErrorTrace(
e, pos2, "while evaluating the attribute '%1%'", showAttrPath(state, env, attrPath));
e, pos2, "while evaluating the attribute '%1%'", showAttrPath(state, env, getAttrPath()));
}
throw;
}
@@ -1405,13 +1422,13 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
Symbol ExprSelect::evalExceptFinalSelect(EvalState & state, Env & env, Value & attrs)
{
Value vTmp;
Symbol name = getName(attrPath[attrPath.size() - 1], state, env);
Symbol name = getName(attrPathStart[nAttrPath - 1], state, env);
if (attrPath.size() == 1) {
if (nAttrPath == 1) {
e->eval(state, env, vTmp);
} else {
ExprSelect init(*this);
init.attrPath.pop_back();
init.nAttrPath--;
init.eval(state, env, vTmp);
}
attrs = vTmp;
@@ -1480,7 +1497,7 @@ void EvalState::callFunction(Value & fun, std::span<Value *> args, Value & vRes,
ExprLambda & lambda(*vCur.lambda().fun);
auto size = (!lambda.arg ? 0 : 1) + (lambda.hasFormals() ? lambda.formals->formals.size() : 0);
Env & env2(allocEnv(size));
Env & env2(mem.allocEnv(size));
env2.up = vCur.lambda().env;
Displacement displ = 0;
@@ -1721,8 +1738,8 @@ void EvalState::autoCallFunction(const Bindings & args, Value & fun, Value & res
forceValue(fun, pos);
if (fun.type() == nAttrs) {
auto found = fun.attrs()->find(s.functor);
if (found != fun.attrs()->end()) {
auto found = fun.attrs()->get(s.functor);
if (found) {
Value * v = allocValue();
callFunction(*found->value, fun, *v, pos);
forceValue(*v, pos);
@@ -1769,7 +1786,7 @@ https://nix.dev/manual/nix/stable/language/syntax.html#functions.)",
void ExprWith::eval(EvalState & state, Env & env, Value & v)
{
Env & env2(state.allocEnv(1));
Env & env2(state.mem.allocEnv(1));
env2.up = &env;
env2.values[0] = attrs->maybeThunk(state, env);
@@ -1787,7 +1804,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
std::ostringstream out;
cond->show(state.symbols, out);
auto exprStr = toView(out);
auto exprStr = out.view();
if (auto eq = dynamic_cast<ExprOpEq *>(cond)) {
try {
@@ -1851,51 +1868,113 @@ void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
|| state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
}
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
void ExprOpUpdate::eval(EvalState & state, Value & v, Value & v1, Value & v2)
{
Value v1, v2;
state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
state.nrOpUpdates++;
if (v1.attrs()->size() == 0) {
const Bindings & bindings1 = *v1.attrs();
if (bindings1.empty()) {
v = v2;
return;
}
if (v2.attrs()->size() == 0) {
const Bindings & bindings2 = *v2.attrs();
if (bindings2.empty()) {
v = v1;
return;
}
auto attrs = state.buildBindings(v1.attrs()->size() + v2.attrs()->size());
/* Simple heuristic for determining whether attrs2 should be "layered" on top of
attrs1 instead of copying to a new Bindings. */
bool shouldLayer = [&]() -> bool {
if (bindings1.isLayerListFull())
return false;
if (bindings2.size() > state.settings.bindingsUpdateLayerRhsSizeThreshold)
return false;
return true;
}();
if (shouldLayer) {
auto attrs = state.buildBindings(bindings2.size());
attrs.layerOnTopOf(bindings1);
std::ranges::copy(bindings2, std::back_inserter(attrs));
v.mkAttrs(attrs.alreadySorted());
state.nrOpUpdateValuesCopied += bindings2.size();
return;
}
auto attrs = state.buildBindings(bindings1.size() + bindings2.size());
/* Merge the sets, preferring values from the second set. Make
sure to keep the resulting vector in sorted order. */
auto i = v1.attrs()->begin();
auto j = v2.attrs()->begin();
auto i = bindings1.begin();
auto j = bindings2.begin();
while (i != v1.attrs()->end() && j != v2.attrs()->end()) {
while (i != bindings1.end() && j != bindings2.end()) {
if (i->name == j->name) {
attrs.insert(*j);
++i;
++j;
} else if (i->name < j->name)
attrs.insert(*i++);
else
attrs.insert(*j++);
} else if (i->name < j->name) {
attrs.insert(*i);
++i;
} else {
attrs.insert(*j);
++j;
}
}
while (i != v1.attrs()->end())
attrs.insert(*i++);
while (j != v2.attrs()->end())
attrs.insert(*j++);
while (i != bindings1.end()) {
attrs.insert(*i);
++i;
}
while (j != bindings2.end()) {
attrs.insert(*j);
++j;
}
v.mkAttrs(attrs.alreadySorted());
state.nrOpUpdateValuesCopied += v.attrs()->size();
}
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
{
UpdateQueue q;
evalForUpdate(state, env, q);
v.mkAttrs(&Bindings::emptyBindings);
for (auto & rhs : std::views::reverse(q)) {
/* Remember that queue is sorted rightmost attrset first. */
eval(state, /*v=*/v, /*v1=*/v, /*v2=*/rhs);
}
}
void Expr::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx)
{
Value v;
state.evalAttrs(env, this, v, getPos(), errorCtx);
q.push_back(v);
}
void ExprOpUpdate::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q)
{
/* Output rightmost attrset first to the merge queue as the one
with the most priority. */
e2->evalForUpdate(state, env, q, "in the right operand of the update (//) operator");
e1->evalForUpdate(state, env, q, "in the left operand of the update (//) operator");
}
void ExprOpUpdate::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx)
{
evalForUpdate(state, env, q);
}
void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
{
Value v1;
@@ -2064,54 +2143,6 @@ void ExprBlackHole::eval(EvalState & state, [[maybe_unused]] Env & env, Value &
// always force this to be separate, otherwise forceValue may inline it and take
// a massive perf hit
[[gnu::noinline]]
void EvalState::handleEvalExceptionForThunk(Env * env, Expr * expr, Value & v, const PosIdx pos)
{
v.mkThunk(env, expr);
tryFixupBlackHolePos(v, pos);
auto e = std::current_exception();
Value * recovery = nullptr;
try {
std::rethrow_exception(e);
} catch (const RecoverableEvalError & e) {
recovery = allocValue();
} catch (...) {
}
if (recovery) {
recovery->mkThunk(env, expr);
}
v.mkFailed(e, recovery);
}
[[gnu::noinline]]
void EvalState::handleEvalExceptionForApp(Value & v)
{
auto e = std::current_exception();
Value * recovery = nullptr;
try {
std::rethrow_exception(e);
} catch (const RecoverableEvalError & e) {
recovery = allocValue();
} catch (...) {
}
if (recovery) {
*recovery = v;
}
v.mkFailed(e, recovery);
}
[[gnu::noinline]]
void EvalState::handleEvalFailed(Value & v, const PosIdx pos)
{
assert(v.isFailed());
if (auto recoveryValue = v.failed()->recoveryValue) {
v = *recoveryValue;
forceValue(v, pos);
} else {
std::rethrow_exception(v.failed()->ex);
}
}
void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos)
{
if (!v.isBlackhole())
@@ -2120,8 +2151,7 @@ void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos)
try {
std::rethrow_exception(e);
} catch (InfiniteRecursionError & e) {
if (!e.hasPos())
e.atPos(positions[pos]);
e.atPos(positions[pos]);
} catch (...) {
}
}
@@ -2221,10 +2251,10 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx
return v.boolean();
}
Bindings::const_iterator EvalState::getAttr(Symbol attrSym, const Bindings * attrSet, std::string_view errorCtx)
const Attr * EvalState::getAttr(Symbol attrSym, const Bindings * attrSet, std::string_view errorCtx)
{
auto value = attrSet->find(attrSym);
if (value == attrSet->end()) {
auto value = attrSet->get(attrSym);
if (!value) {
error<TypeError>("attribute '%s' missing", symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow();
}
return value;
@@ -2232,7 +2262,7 @@ Bindings::const_iterator EvalState::getAttr(Symbol attrSym, const Bindings * att
bool EvalState::isFunctor(const Value & fun) const
{
return fun.type() == nAttrs && fun.attrs()->find(s.functor) != fun.attrs()->end();
return fun.type() == nAttrs && fun.attrs()->get(s.functor);
}
void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx)
@@ -2313,8 +2343,8 @@ bool EvalState::isDerivation(Value & v)
std::optional<std::string>
EvalState::tryAttrsToString(const PosIdx pos, Value & v, NixStringContext & context, bool coerceMore, bool copyToStore)
{
auto i = v.attrs()->find(s.toString);
if (i != v.attrs()->end()) {
auto i = v.attrs()->get(s.toString);
if (i) {
Value v1;
callFunction(*i->value, v, v1, pos);
return coerceToString(
@@ -2359,8 +2389,8 @@ BackedStringView EvalState::coerceToString(
auto maybeString = tryAttrsToString(pos, v, context, coerceMore, copyToStore);
if (maybeString)
return std::move(*maybeString);
auto i = v.attrs()->find(s.outPath);
if (i == v.attrs()->end()) {
auto i = v.attrs()->get(s.outPath);
if (!i) {
error<TypeError>(
"cannot coerce %1% to a string: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions))
.withTrace(pos, errorCtx)
@@ -2428,7 +2458,7 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat
if (nix::isDerivation(path.path.abs()))
error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow();
auto dstPathCached = get(*srcToStore.lock(), path);
auto dstPathCached = getConcurrent(*srcToStore, path);
auto dstPath = dstPathCached ? *dstPathCached : [&]() {
auto dstPath = fetchToStore(
@@ -2441,7 +2471,7 @@ StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePat
nullptr,
repair);
allowPath(dstPath);
srcToStore.lock()->try_emplace(path, dstPath);
srcToStore->try_emplace(path, dstPath);
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath));
return dstPath;
}();
@@ -2466,8 +2496,8 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
/* Similarly, handle __toString where the result may be a path
value. */
if (v.type() == nAttrs) {
auto i = v.attrs()->find(s.toString);
if (i != v.attrs()->end()) {
auto i = v.attrs()->get(s.toString);
if (i) {
Value v1;
callFunction(*i->value, v, v1, pos);
return coerceToPath(pos, v1, context, errorCtx);
@@ -2746,11 +2776,8 @@ void EvalState::assertEqValues(Value & v1, Value & v2, const PosIdx pos, std::st
}
return;
// Cannot be returned by forceValue().
case nThunk:
case nFailed:
unreachable();
case nThunk: // Must not be left by forceValue
assert(false);
default: // Note that we pass compiler flags that should make `default:` unreachable.
// Also note that this probably ran after `eqValues`, which implements
// the same logic more efficiently (without having to unwind stacks),
@@ -2842,11 +2869,8 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
// !!!
return v1.fpoint() == v2.fpoint();
// Cannot be returned by forceValue().
case nThunk:
case nFailed:
unreachable();
case nThunk: // Must not be left by forceValue
assert(false);
default: // Note that we pass compiler flags that should make `default:` unreachable.
error<EvalError>("eqValues: cannot compare %1% with %2%", showType(v1), showType(v2))
.withTrace(pos, errorCtx)
@@ -2869,11 +2893,11 @@ bool EvalState::fullGC()
#endif
}
bool Counter::enabled = getEnv("NIX_SHOW_STATS").value_or("0") != "0";
void EvalState::maybePrintStats()
{
bool showStats = getEnv("NIX_SHOW_STATS").value_or("0") != "0";
if (showStats) {
if (Counter::enabled) {
// Make the final heap size more deterministic.
#if NIX_USE_BOEHMGC
if (!fullGC()) {
@@ -2889,10 +2913,12 @@ void EvalState::printStatistics()
std::chrono::microseconds cpuTimeDuration = getCpuUserTime();
float cpuTime = std::chrono::duration_cast<std::chrono::duration<float>>(cpuTimeDuration).count();
uint64_t bEnvs = nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value *);
uint64_t bLists = nrListElems * sizeof(Value *);
uint64_t bValues = nrValues * sizeof(Value);
uint64_t bAttrsets = nrAttrsets * sizeof(Bindings) + nrAttrsInAttrsets * sizeof(Attr);
auto & memstats = mem.getStats();
uint64_t bEnvs = memstats.nrEnvs * sizeof(Env) + memstats.nrValuesInEnvs * sizeof(Value *);
uint64_t bLists = memstats.nrListElems * sizeof(Value *);
uint64_t bValues = memstats.nrValues * sizeof(Value);
uint64_t bAttrsets = memstats.nrAttrsets * sizeof(Bindings) + memstats.nrAttrsInAttrsets * sizeof(Attr);
#if NIX_USE_BOEHMGC
GC_word heapSize, totalBytes;
@@ -2918,18 +2944,18 @@ void EvalState::printStatistics()
#endif
};
topObj["envs"] = {
{"number", nrEnvs},
{"elements", nrValuesInEnvs},
{"number", memstats.nrEnvs.load()},
{"elements", memstats.nrValuesInEnvs.load()},
{"bytes", bEnvs},
};
topObj["nrExprs"] = Expr::nrExprs;
topObj["nrExprs"] = Expr::nrExprs.load();
topObj["list"] = {
{"elements", nrListElems},
{"elements", memstats.nrListElems.load()},
{"bytes", bLists},
{"concats", nrListConcats},
{"concats", nrListConcats.load()},
};
topObj["values"] = {
{"number", nrValues},
{"number", memstats.nrValues.load()},
{"bytes", bValues},
};
topObj["symbols"] = {
@@ -2937,9 +2963,9 @@ void EvalState::printStatistics()
{"bytes", symbols.totalSize()},
};
topObj["sets"] = {
{"number", nrAttrsets},
{"number", memstats.nrAttrsets.load()},
{"bytes", bAttrsets},
{"elements", nrAttrsInAttrsets},
{"elements", memstats.nrAttrsInAttrsets.load()},
};
topObj["sizes"] = {
{"Env", sizeof(Env)},
@@ -2947,13 +2973,13 @@ void EvalState::printStatistics()
{"Bindings", sizeof(Bindings)},
{"Attr", sizeof(Attr)},
};
topObj["nrOpUpdates"] = nrOpUpdates;
topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied;
topObj["nrThunks"] = nrThunks;
topObj["nrAvoided"] = nrAvoided;
topObj["nrLookups"] = nrLookups;
topObj["nrPrimOpCalls"] = nrPrimOpCalls;
topObj["nrFunctionCalls"] = nrFunctionCalls;
topObj["nrOpUpdates"] = nrOpUpdates.load();
topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied.load();
topObj["nrThunks"] = nrThunks.load();
topObj["nrAvoided"] = nrAvoided.load();
topObj["nrLookups"] = nrLookups.load();
topObj["nrPrimOpCalls"] = nrPrimOpCalls.load();
topObj["nrFunctionCalls"] = nrFunctionCalls.load();
#if NIX_USE_BOEHMGC
topObj["gc"] = {
{"heapSize", heapSize},
@@ -3100,6 +3126,11 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
auto res = (r / CanonPath(suffix)).resolveSymlinks();
if (res.pathExists())
return res;
// Backward compatibility hack: throw an exception if access
// to this path is not allowed.
if (auto accessor = res.accessor.dynamic_pointer_cast<FilteringSourceAccessor>())
accessor->checkAccess(res.path);
}
if (hasPrefix(path, "nix/"))
@@ -3154,7 +3185,7 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
/* Allow access to paths in the search path. */
if (initAccessControl) {
allowPath(path.path.abs());
allowPathLegacy(path.path.abs());
if (store->isInStore(path.path.abs())) {
try {
allowClosure(store->toStorePath(path.path.abs()).first);
@@ -3166,6 +3197,11 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
if (path.resolveSymlinks().pathExists())
return finish(std::move(path));
else {
// Backward compatibility hack: throw an exception if access
// to this path is not allowed.
if (auto accessor = path.accessor.dynamic_pointer_cast<FilteringSourceAccessor>())
accessor->checkAccess(path.path);
logWarning({.msg = HintFmt("Nix search path entry '%1%' does not exist, ignoring", value)});
}
}
@@ -3184,7 +3220,8 @@ Expr * EvalState::parse(
docComments = &it->second;
}
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, settings, positions, *docComments, rootFS);
auto result = parseExprFromBuf(
text, length, origin, basePath, mem.exprs.alloc, symbols, settings, positions, *docComments, rootFS);
result->bindVars(*this, staticEnv);

View File

@@ -45,8 +45,8 @@ PackageInfo::PackageInfo(EvalState & state, ref<Store> store, const std::string
std::string PackageInfo::queryName() const
{
if (name == "" && attrs) {
auto i = attrs->find(state->s.name);
if (i == attrs->end())
auto i = attrs->get(state->s.name);
if (!i)
state->error<TypeError>("derivation name missing").debugThrow();
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
}
@@ -56,11 +56,10 @@ std::string PackageInfo::queryName() const
std::string PackageInfo::querySystem() const
{
if (system == "" && attrs) {
auto i = attrs->find(state->s.system);
auto i = attrs->get(state->s.system);
system =
i == attrs->end()
? "unknown"
: state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
!i ? "unknown"
: state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
}
return system;
}
@@ -95,9 +94,9 @@ StorePath PackageInfo::requireDrvPath() const
StorePath PackageInfo::queryOutPath() const
{
if (!outPath && attrs) {
auto i = attrs->find(state->s.outPath);
auto i = attrs->get(state->s.outPath);
NixStringContext context;
if (i != attrs->end())
if (i)
outPath = state->coerceToStorePath(
i->pos, *i->value, context, "while evaluating the output path of a derivation");
}

View File

@@ -4,12 +4,16 @@
#include "nix/expr/nixexpr.hh"
#include "nix/expr/symbol-table.hh"
#include <boost/container/static_vector.hpp>
#include <algorithm>
#include <functional>
#include <ranges>
#include <optional>
namespace nix {
class EvalState;
class EvalMemory;
struct Value;
/**
@@ -47,15 +51,53 @@ static_assert(
* by its size and its capacity, the capacity being the number of Attr
* elements allocated after this structure, while the size corresponds to
* the number of elements already inserted in this structure.
*
* Bindings can be efficiently `//`-composed into an intrusive linked list of "layers"
* that saves on copies and allocations. Each lookup (@see Bindings::get) traverses
* this linked list until a matching attribute is found (thus overlays earlier in
* the list take precedence). For iteration over the whole Bindings, an on-the-fly
* k-way merge is performed by Bindings::iterator class.
*/
class Bindings
{
public:
typedef uint32_t size_t;
using size_type = uint32_t;
PosIdx pos;
/**
* An instance of bindings objects with 0 attributes.
* This object must never be modified.
*/
static Bindings emptyBindings;
private:
size_t size_ = 0;
/**
* Number of attributes in the attrs FAM (Flexible Array Member).
*/
size_type numAttrs = 0;
/**
* Number of attributes with unique names in the layer chain.
*
* This is the *real* user-facing size of bindings, whereas @ref numAttrs is
* an implementation detail of the data structure.
*/
size_type numAttrsInChain = 0;
/**
* Length of the layers list.
*/
uint32_t numLayers = 1;
/**
* Bindings that this attrset is "layered" on top of.
*/
const Bindings * baseLayer = nullptr;
/**
* Flexible array member of attributes.
*/
Attr attrs[0];
Bindings() = default;
@@ -64,71 +106,306 @@ private:
Bindings & operator=(const Bindings &) = delete;
Bindings & operator=(Bindings &&) = delete;
friend class BindingsBuilder;
/**
* Maximum length of the Bindings layer chains.
*/
static constexpr unsigned maxLayers = 8;
public:
size_t size() const
size_type size() const
{
return size_;
return numAttrsInChain;
}
bool empty() const
{
return !size_;
return size() == 0;
}
typedef Attr * iterator;
class iterator
{
public:
using value_type = Attr;
using pointer = const value_type *;
using reference = const value_type &;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
typedef const Attr * const_iterator;
friend class Bindings;
private:
struct BindingsCursor
{
/**
* Attr that the cursor currently points to.
*/
pointer current;
/**
* One past the end pointer to the contiguous buffer of Attrs.
*/
pointer end;
/**
* Priority of the value. Lesser values have more priority (i.e. they override
* attributes that appear later in the linked list of Bindings).
*/
uint32_t priority;
pointer operator->() const noexcept
{
return current;
}
reference get() const noexcept
{
return *current;
}
bool empty() const noexcept
{
return current == end;
}
void increment() noexcept
{
++current;
}
void consume(Symbol name) noexcept
{
while (!empty() && current->name <= name)
++current;
}
GENERATE_CMP(BindingsCursor, me->current->name, me->priority)
};
using QueueStorageType = boost::container::static_vector<BindingsCursor, maxLayers>;
/**
* Comparator implementing the override priority / name ordering
* for BindingsCursor.
*/
static constexpr auto comp = std::greater<BindingsCursor>();
/**
* A priority queue used to implement an on-the-fly k-way merge.
*/
QueueStorageType cursorHeap;
/**
* The attribute the iterator currently points to.
*/
pointer current = nullptr;
/**
* Whether iterating over a single attribute and not a merge chain.
*/
bool doMerge = true;
void push(BindingsCursor cursor) noexcept
{
cursorHeap.push_back(cursor);
std::ranges::make_heap(cursorHeap, comp);
}
[[nodiscard]] BindingsCursor pop() noexcept
{
std::ranges::pop_heap(cursorHeap, comp);
auto cursor = cursorHeap.back();
cursorHeap.pop_back();
return cursor;
}
iterator & finished() noexcept
{
current = nullptr;
return *this;
}
void next(BindingsCursor cursor) noexcept
{
current = &cursor.get();
cursor.increment();
if (!cursor.empty())
push(cursor);
}
std::optional<BindingsCursor> consumeAllUntilCurrentName() noexcept
{
auto cursor = pop();
Symbol lastHandledName = current->name;
while (cursor->name <= lastHandledName) {
cursor.consume(lastHandledName);
if (!cursor.empty())
push(cursor);
if (cursorHeap.empty())
return std::nullopt;
cursor = pop();
}
return cursor;
}
explicit iterator(const Bindings & attrs) noexcept
: doMerge(attrs.baseLayer)
{
auto pushBindings = [this, priority = unsigned{0}](const Bindings & layer) mutable {
auto first = layer.attrs;
push(
BindingsCursor{
.current = first,
.end = first + layer.numAttrs,
.priority = priority++,
});
};
if (!doMerge) {
if (attrs.empty())
return;
current = attrs.attrs;
pushBindings(attrs);
return;
}
const Bindings * layer = &attrs;
while (layer) {
if (layer->numAttrs != 0)
pushBindings(*layer);
layer = layer->baseLayer;
}
if (cursorHeap.empty())
return;
next(pop());
}
public:
iterator() = default;
reference operator*() const noexcept
{
return *current;
}
pointer operator->() const noexcept
{
return current;
}
iterator & operator++() noexcept
{
if (!doMerge) {
++current;
if (current == cursorHeap.front().end)
return finished();
return *this;
}
if (cursorHeap.empty())
return finished();
auto cursor = consumeAllUntilCurrentName();
if (!cursor)
return finished();
next(*cursor);
return *this;
}
iterator operator++(int) noexcept
{
iterator tmp = *this;
++*this;
return tmp;
}
bool operator==(const iterator & rhs) const noexcept
{
return current == rhs.current;
}
};
using const_iterator = iterator;
void push_back(const Attr & attr)
{
attrs[size_++] = attr;
attrs[numAttrs++] = attr;
numAttrsInChain = numAttrs;
}
const_iterator find(Symbol name) const
/**
* Get attribute by name or nullptr if no such attribute exists.
*/
const Attr * get(Symbol name) const noexcept
{
Attr key(name, 0);
const_iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name)
return i;
return end();
}
auto getInChunk = [key = Attr{name, nullptr}](const Bindings & chunk) -> const Attr * {
auto first = chunk.attrs;
auto last = first + chunk.numAttrs;
const Attr * i = std::lower_bound(first, last, key);
if (i != last && i->name == key.name)
return i;
return nullptr;
};
const Bindings * currentChunk = this;
while (currentChunk) {
const Attr * maybeAttr = getInChunk(*currentChunk);
if (maybeAttr)
return maybeAttr;
currentChunk = currentChunk->baseLayer;
}
const Attr * get(Symbol name) const
{
Attr key(name, 0);
const_iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name)
return &*i;
return nullptr;
}
iterator begin()
/**
* Check if the layer chain is full.
*/
bool isLayerListFull() const noexcept
{
return &attrs[0];
return numLayers == Bindings::maxLayers;
}
iterator end()
/**
* Test if the length of the linked list of layers is greater than 1.
*/
bool isLayered() const noexcept
{
return &attrs[size_];
return numLayers > 1;
}
const_iterator begin() const
{
return &attrs[0];
return const_iterator(*this);
}
const_iterator end() const
{
return &attrs[size_];
return const_iterator();
}
Attr & operator[](size_t pos)
Attr & operator[](size_type pos)
{
if (isLayered()) [[unlikely]]
unreachable();
return attrs[pos];
}
const Attr & operator[](size_t pos) const
const Attr & operator[](size_type pos) const
{
if (isLayered()) [[unlikely]]
unreachable();
return attrs[pos];
}
@@ -140,19 +417,21 @@ public:
std::vector<const Attr *> lexicographicOrder(const SymbolTable & symbols) const
{
std::vector<const Attr *> res;
res.reserve(size_);
for (size_t n = 0; n < size_; n++)
res.emplace_back(&attrs[n]);
std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) {
res.reserve(size());
std::ranges::transform(*this, std::back_inserter(res), [](const Attr & a) { return &a; });
std::ranges::sort(res, [&](const Attr * a, const Attr * b) {
std::string_view sa = symbols[a->name], sb = symbols[b->name];
return sa < sb;
});
return res;
}
friend class EvalState;
friend class EvalMemory;
};
static_assert(std::forward_iterator<Bindings::iterator>);
static_assert(std::ranges::forward_range<Bindings>);
/**
* A wrapper around Bindings that ensures that its always in sorted
* order at the end. The only way to consume a BindingsBuilder is to
@@ -163,23 +442,38 @@ class BindingsBuilder final
public:
// needed by std::back_inserter
using value_type = Attr;
using size_type = Bindings::size_t;
using size_type = Bindings::size_type;
private:
Bindings * bindings;
Bindings::size_t capacity_;
Bindings::size_type capacity_;
friend class EvalState;
friend class EvalMemory;
BindingsBuilder(EvalState & state, Bindings * bindings, size_type capacity)
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings, size_type capacity)
: bindings(bindings)
, capacity_(capacity)
, state(state)
, mem(mem)
, symbols(symbols)
{
}
bool hasBaseLayer() const noexcept
{
return bindings->baseLayer;
}
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());
}
public:
std::reference_wrapper<EvalState> state;
std::reference_wrapper<EvalMemory> mem;
std::reference_wrapper<SymbolTable> symbols;
void insert(Symbol name, Value * value, PosIdx pos = noPos)
{
@@ -193,10 +487,26 @@ public:
void push_back(const Attr & attr)
{
assert(bindings->size() < capacity_);
assert(bindings->numAttrs < capacity_);
bindings->push_back(attr);
}
/**
* "Layer" the newly constructured Bindings on top of another attribute set.
*
* This effectively performs an attribute set merge, while giving preference
* to attributes from the newly constructed Bindings in case of duplicate attribute
* names.
*
* This operation amortizes the need to copy over all attributes and allows
* for efficient implementation of attribute set merges (ExprOpUpdate::eval).
*/
void layerOnTopOf(const Bindings & base) noexcept
{
bindings->baseLayer = &base;
bindings->numLayers = base.numLayers + 1;
}
Value & alloc(Symbol name, PosIdx pos = noPos);
Value & alloc(std::string_view name, PosIdx pos = noPos);
@@ -204,11 +514,13 @@ public:
Bindings * finish()
{
bindings->sort();
finishSizeIfNecessary();
return bindings;
}
Bindings * alreadySorted()
{
finishSizeIfNecessary();
return bindings;
}

View File

@@ -0,0 +1,70 @@
#pragma once
#include <atomic>
#include <cstdint>
namespace nix {
/**
* An atomic counter aligned on a cache line to prevent false sharing.
* The counter is only enabled when the `NIX_SHOW_STATS` environment
* variable is set. This is to prevent contention on these counters
* when multi-threaded evaluation is enabled.
*/
struct alignas(64) Counter
{
using value_type = uint64_t;
std::atomic<value_type> inner{0};
static bool enabled;
Counter() {}
operator value_type() const noexcept
{
return inner;
}
void operator=(value_type n) noexcept
{
inner = n;
}
value_type load() const noexcept
{
return inner;
}
value_type operator++() noexcept
{
return enabled ? ++inner : 0;
}
value_type operator++(int) noexcept
{
return enabled ? inner++ : 0;
}
value_type operator--() noexcept
{
return enabled ? --inner : 0;
}
value_type operator--(int) noexcept
{
return enabled ? inner-- : 0;
}
value_type operator+=(value_type n) noexcept
{
return enabled ? inner += n : 0;
}
value_type operator-=(value_type n) noexcept
{
return enabled ? inner -= n : 0;
}
};
} // namespace nix

View File

@@ -56,14 +56,6 @@ MakeError(MissingArgumentError, EvalError);
MakeError(InfiniteRecursionError, EvalError);
MakeError(IFDError, EvalBaseError);
/**
* An evaluation error which should be retried instead of rethrown
*
* A RecoverableEvalError is not an EvalError, because we shouldn't cache it in the eval cache, as it should be retried
* anyway.
*/
MakeError(RecoverableEvalError, EvalBaseError);
struct InvalidPathError : public EvalError
{
public:

View File

@@ -5,7 +5,6 @@
#include "nix/expr/eval.hh"
#include "nix/expr/eval-error.hh"
#include "nix/expr/eval-settings.hh"
#include <exception>
namespace nix {
@@ -27,7 +26,7 @@ inline void * allocBytes(size_t n)
}
[[gnu::always_inline]]
Value * EvalState::allocValue()
Value * EvalMemory::allocValue()
{
#if NIX_USE_BOEHMGC
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
@@ -49,15 +48,15 @@ Value * EvalState::allocValue()
void * p = allocBytes(sizeof(Value));
#endif
nrValues++;
stats.nrValues++;
return (Value *) p;
}
[[gnu::always_inline]]
Env & EvalState::allocEnv(size_t size)
Env & EvalMemory::allocEnv(size_t size)
{
nrEnvs++;
nrValuesInEnvs += size;
stats.nrEnvs++;
stats.nrValuesInEnvs += size;
Env * env;
@@ -98,19 +97,12 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
else
ExprBlackHole::throwInfiniteRecursionError(*this, v);
} catch (...) {
handleEvalExceptionForThunk(env, expr, v, pos);
v.mkThunk(env, expr);
tryFixupBlackHolePos(v, pos);
throw;
}
} else if (v.isApp()) {
try {
callFunction(*v.app().left, *v.app().right, v, pos);
} catch (...) {
handleEvalExceptionForApp(v);
throw;
}
} else if (v.isFailed()) {
handleEvalFailed(v, pos);
}
} else if (v.isApp())
callFunction(*v.app().left, *v.app().right, v, pos);
}
[[gnu::always_inline]]

View File

@@ -342,6 +342,25 @@ struct EvalSettings : Config
This is useful for improving code readability and making path literals
more explicit.
)"};
Setting<unsigned> bindingsUpdateLayerRhsSizeThreshold{
this,
sizeof(void *) == 4 ? 8192 : 16,
"eval-attrset-update-layer-rhs-threshold",
R"(
Tunes the maximum size of an attribute set that, when used
as a right operand in an [attribute set update expression](@docroot@/language/operators.md#update),
uses a more space-efficient linked-list representation of attribute sets.
Setting this to larger values generally leads to less memory allocations,
but may lead to worse evaluation performance.
A value of `0` disables this optimization completely.
This is an advanced performance tuning option and typically should not be changed.
The default value is chosen to balance performance and memory usage. On 32 bit systems
where memory is scarce, the default is a large value to reduce the amount of allocations.
)"};
};
/**

View File

@@ -16,10 +16,14 @@
#include "nix/expr/search-path.hh"
#include "nix/expr/repl-exit-status.hh"
#include "nix/util/ref.hh"
#include "nix/expr/counter.hh"
// For `NIX_USE_BOEHMGC`, and if that's set, `GC_THREADS`
#include "nix/expr/config.hh"
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/unordered/concurrent_flat_map_fwd.hpp>
#include <map>
#include <optional>
#include <functional>
@@ -38,6 +42,7 @@ class Store;
namespace fetchers {
struct Settings;
struct InputCache;
struct Input;
} // namespace fetchers
struct EvalSettings;
class EvalState;
@@ -45,6 +50,7 @@ class StorePath;
struct SingleDerivedPath;
enum RepairFlag : bool;
struct MemorySourceAccessor;
struct MountedSourceAccessor;
namespace eval_cache {
class EvalCache;
@@ -162,7 +168,7 @@ typedef std::
map<std::string, Value *, std::less<std::string>, traceable_allocator<std::pair<const std::string, Value *>>>
ValMap;
typedef std::unordered_map<PosIdx, DocComment> DocCommentMap;
typedef boost::unordered_flat_map<PosIdx, DocComment, std::hash<PosIdx>> DocCommentMap;
struct Env
{
@@ -297,6 +303,68 @@ struct StaticEvalSymbols
}
};
class EvalMemory
{
#if NIX_USE_BOEHMGC
/**
* Allocation cache for GC'd Value objects.
*/
std::shared_ptr<void *> valueAllocCache;
/**
* Allocation cache for size-1 Env objects.
*/
std::shared_ptr<void *> env1AllocCache;
#endif
public:
struct Statistics
{
Counter nrEnvs;
Counter nrValuesInEnvs;
Counter nrValues;
Counter nrAttrsets;
Counter nrAttrsInAttrsets;
Counter nrListElems;
};
EvalMemory();
EvalMemory(const EvalMemory &) = delete;
EvalMemory(EvalMemory &&) = delete;
EvalMemory & operator=(const EvalMemory &) = delete;
EvalMemory & operator=(EvalMemory &&) = delete;
inline Value * allocValue();
inline Env & allocEnv(size_t size);
Bindings * allocBindings(size_t capacity);
BindingsBuilder buildBindings(SymbolTable & symbols, size_t capacity)
{
return BindingsBuilder(*this, symbols, allocBindings(capacity), capacity);
}
ListBuilder buildList(size_t size)
{
stats.nrListElems += size;
return ListBuilder(size);
}
const Statistics & getStats() const &
{
return stats;
}
/**
* Storage for the AST nodes
*/
Exprs exprs;
private:
Statistics stats;
};
class EvalState : public std::enable_shared_from_this<EvalState>
{
public:
@@ -307,53 +375,18 @@ public:
SymbolTable symbols;
PosTable positions;
EvalMemory mem;
/**
* If set, force copying files to the Nix store even if they
* already exist there.
*/
RepairFlag repair;
Bindings emptyBindings;
/**
* Empty list constant.
*/
Value vEmptyList;
/**
* `null` constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
Value vNull;
/**
* `true` constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
Value vTrue;
/**
* `true` constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
Value vFalse;
/** `"regular"` */
Value vStringRegular;
/** `"directory"` */
Value vStringDirectory;
/** `"symlink"` */
Value vStringSymlink;
/** `"unknown"` */
Value vStringUnknown;
/**
* The accessor corresponding to `store`.
*/
const ref<SourceAccessor> storeFS;
const ref<MountedSourceAccessor> storeFS;
/**
* The accessor for the root filesystem.
@@ -395,7 +428,7 @@ public:
bool inDebugger = false;
int trylevel;
std::list<DebugTrace> debugTraces;
std::map<const Expr *, const std::shared_ptr<const StaticEnv>> exprEnvs;
boost::unordered_flat_map<const Expr *, const std::shared_ptr<const StaticEnv>> exprEnvs;
const std::shared_ptr<const StaticEnv> getStaticEnv(const Expr & expr) const
{
@@ -438,59 +471,41 @@ private:
/* Cache for calls to addToStore(); maps source paths to the store
paths. */
Sync<std::unordered_map<SourcePath, StorePath>> srcToStore;
ref<boost::concurrent_flat_map<SourcePath, StorePath>> srcToStore;
/**
* A cache from path names to parse trees.
* A cache that maps paths to "resolved" paths for importing Nix
* expressions, i.e. `/foo` to `/foo/default.nix`.
*/
typedef std::unordered_map<
SourcePath,
Expr *,
std::hash<SourcePath>,
std::equal_to<SourcePath>,
traceable_allocator<std::pair<const SourcePath, Expr *>>>
FileParseCache;
FileParseCache fileParseCache;
ref<boost::concurrent_flat_map<SourcePath, SourcePath>> importResolutionCache;
/**
* A cache from path names to values.
* A cache from resolved paths to values.
*/
typedef std::unordered_map<
ref<boost::concurrent_flat_map<
SourcePath,
Value,
Value *,
std::hash<SourcePath>,
std::equal_to<SourcePath>,
traceable_allocator<std::pair<const SourcePath, Value>>>
FileEvalCache;
FileEvalCache fileEvalCache;
traceable_allocator<std::pair<const SourcePath, Value *>>>>
fileEvalCache;
/**
* Associate source positions of certain AST nodes with their preceding doc comment, if they have one.
* Grouped by file.
*/
std::unordered_map<SourcePath, DocCommentMap> positionToDocComment;
boost::unordered_flat_map<SourcePath, DocCommentMap> positionToDocComment;
LookupPath lookupPath;
std::map<std::string, std::optional<SourcePath>> lookupPathResolved;
boost::unordered_flat_map<std::string, std::optional<SourcePath>, StringViewHash, std::equal_to<>>
lookupPathResolved;
/**
* Cache used by prim_match().
*/
std::shared_ptr<RegexCache> regexCache;
#if NIX_USE_BOEHMGC
/**
* Allocation cache for GC'd Value objects.
*/
std::shared_ptr<void *> valueAllocCache;
/**
* Allocation cache for size-1 Env objects.
*/
std::shared_ptr<void *> env1AllocCache;
#endif
public:
EvalState(
@@ -501,6 +516,15 @@ public:
std::shared_ptr<Store> buildStore = nullptr);
~EvalState();
/**
* A wrapper around EvalMemory::allocValue() to avoid code churn when it
* was introduced.
*/
inline Value * allocValue()
{
return mem.allocValue();
}
LookupPath getLookupPath()
{
return lookupPath;
@@ -528,8 +552,11 @@ public:
/**
* Allow access to a path.
*
* Only for restrict eval: pure eval just whitelist store paths,
* never arbitrary paths.
*/
void allowPath(const Path & path);
void allowPathLegacy(const Path & path);
/**
* Allow access to a store path. Note that this gets remapped to
@@ -549,6 +576,11 @@ public:
void checkURI(const std::string & uri);
/**
* Mount an input on the Nix store.
*/
StorePath mountInput(fetchers::Input & input, const fetchers::Input & originalInput, ref<SourceAccessor> accessor);
/**
* Parse a Nix expression from the specified file.
*/
@@ -610,28 +642,8 @@ public:
*/
inline void forceValue(Value & v, const PosIdx pos);
private:
/**
* Internal support function for forceValue
*
* This code is factored out so that it's not in the heavily inlined hot path.
*/
void handleEvalExceptionForThunk(Env * env, Expr * expr, Value & v, const PosIdx pos);
/**
* Internal support function for forceValue
*
* This code is factored out so that it's not in the heavily inlined hot path.
*/
void handleEvalExceptionForApp(Value & v);
void handleEvalFailed(Value & v, PosIdx pos);
void tryFixupBlackHolePos(Value & v, PosIdx pos);
public:
/**
* Force a value, then recursively force list elements and
* attributes.
@@ -667,7 +679,7 @@ public:
/**
* Get attribute from an attribute set and throw an error if it doesn't exist.
*/
Bindings::const_iterator getAttr(Symbol attrSym, const Bindings * attrSet, std::string_view errorCtx);
const Attr * getAttr(Symbol attrSym, const Bindings * attrSet, std::string_view errorCtx);
template<typename... Args>
[[gnu::noinline]]
@@ -766,11 +778,11 @@ public:
/**
* Internal primops not exposed to the user.
*/
std::unordered_map<
boost::unordered_flat_map<
std::string,
Value *,
std::hash<std::string>,
std::equal_to<std::string>,
StringViewHash,
std::equal_to<>,
traceable_allocator<std::pair<const std::string, Value *>>>
internalPrimOps;
@@ -889,22 +901,14 @@ public:
*/
void autoCallFunction(const Bindings & args, Value & fun, Value & res);
/**
* Allocation primitives.
*/
inline Value * allocValue();
inline Env & allocEnv(size_t size);
Bindings * allocBindings(size_t capacity);
BindingsBuilder buildBindings(size_t capacity)
{
return BindingsBuilder(*this, allocBindings(capacity), capacity);
return mem.buildBindings(symbols, capacity);
}
ListBuilder buildList(size_t size)
{
return ListBuilder(*this, size);
return mem.buildList(size);
}
/**
@@ -1021,26 +1025,20 @@ private:
*/
std::string mkSingleDerivedPathStringRaw(const SingleDerivedPath & p);
unsigned long nrEnvs = 0;
unsigned long nrValuesInEnvs = 0;
unsigned long nrValues = 0;
unsigned long nrListElems = 0;
unsigned long nrLookups = 0;
unsigned long nrAttrsets = 0;
unsigned long nrAttrsInAttrsets = 0;
unsigned long nrAvoided = 0;
unsigned long nrOpUpdates = 0;
unsigned long nrOpUpdateValuesCopied = 0;
unsigned long nrListConcats = 0;
unsigned long nrPrimOpCalls = 0;
unsigned long nrFunctionCalls = 0;
Counter nrLookups;
Counter nrAvoided;
Counter nrOpUpdates;
Counter nrOpUpdateValuesCopied;
Counter nrListConcats;
Counter nrPrimOpCalls;
Counter nrFunctionCalls;
bool countCalls;
typedef std::map<std::string, size_t> PrimOpCalls;
typedef boost::unordered_flat_map<std::string, size_t, StringViewHash, std::equal_to<>> PrimOpCalls;
PrimOpCalls primOpCalls;
typedef std::map<ExprLambda *, size_t> FunctionCalls;
typedef boost::unordered_flat_map<ExprLambda *, size_t> FunctionCalls;
FunctionCalls functionCalls;
/** Evaluation/call profiler. */
@@ -1048,7 +1046,7 @@ private:
void incrFunctionCall(ExprLambda * fun);
typedef std::map<PosIdx, size_t> AttrSelects;
typedef boost::unordered_flat_map<PosIdx, size_t, std::hash<PosIdx>> AttrSelects;
AttrSelects attrSelects;
friend struct ExprOpUpdate;

View File

@@ -26,4 +26,20 @@ using SmallValueVector = SmallVector<Value *, nItems>;
template<size_t nItems>
using SmallTemporaryValueVector = SmallVector<Value, nItems>;
/**
* For functions where we do not expect deep recursion, we can use a sizable
* part of the stack a free allocation space.
*
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
*/
constexpr size_t nonRecursiveStackReservation = 128;
/**
* Functions that maybe applied to self-similar inputs, such as concatMap on a
* tree, should reserve a smaller part of the stack for allocation.
*
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
*/
constexpr size_t conservativeStackReservation = 16;
} // namespace nix

View File

@@ -10,6 +10,7 @@ config_pub_h = configure_file(
headers = [ config_pub_h ] + files(
'attr-path.hh',
'attr-set.hh',
'counter.hh',
'eval-cache.hh',
'eval-error.hh',
'eval-gc.hh',

View File

@@ -2,12 +2,17 @@
///@file
#include <map>
#include <span>
#include <vector>
#include <memory_resource>
#include <algorithm>
#include "nix/expr/gc-small-vector.hh"
#include "nix/expr/value.hh"
#include "nix/expr/symbol-table.hh"
#include "nix/expr/eval-error.hh"
#include "nix/util/pos-idx.hh"
#include "nix/expr/counter.hh"
namespace nix {
@@ -76,9 +81,20 @@ struct AttrName
: expr(e) {};
};
static_assert(std::is_trivially_copy_constructible_v<AttrName>);
typedef std::vector<AttrName> AttrPath;
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath);
std::string showAttrPath(const SymbolTable & symbols, std::span<const AttrName> attrPath);
using UpdateQueue = SmallTemporaryValueVector<conservativeStackReservation>;
class Exprs
{
std::pmr::monotonic_buffer_resource buffer;
public:
std::pmr::polymorphic_allocator<char> alloc{&buffer};
};
/* Abstract syntax of Nix expressions. */
@@ -89,7 +105,7 @@ struct Expr
Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body;
};
static unsigned long nrExprs;
static Counter nrExprs;
Expr()
{
@@ -99,8 +115,25 @@ struct Expr
virtual ~Expr() {};
virtual void show(const SymbolTable & symbols, std::ostream & str) const;
virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
/** Normal evaluation, implemented directly by all subclasses. */
virtual void eval(EvalState & state, Env & env, Value & v);
/**
* Create a thunk for the delayed computation of the given expression
* in the given environment. But if the expression is a variable,
* then look it up right away. This significantly reduces the number
* of thunks allocated.
*/
virtual Value * maybeThunk(EvalState & state, Env & env);
/**
* Only called when performing an attrset update: `//` or similar.
* Instead of writing to a Value &, this function writes to an UpdateQueue.
* This allows the expression to perform multiple updates in a delayed manner, gathering up all the updates before
* applying them.
*/
virtual void evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx);
virtual void setName(Symbol name);
virtual void setDocComment(DocComment docComment) {};
@@ -152,13 +185,28 @@ struct ExprFloat : Expr
struct ExprString : Expr
{
std::string s;
Value v;
ExprString(std::string && s)
: s(std::move(s))
/**
* This is only for strings already allocated in our polymorphic allocator,
* or that live at least that long (e.g. c++ string literals)
*/
ExprString(const char * s)
{
v.mkStringNoCopy(this->s.data());
v.mkStringNoCopy(s);
};
ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv)
{
auto len = sv.length();
if (len == 0) {
v.mkStringNoCopy("");
return;
}
char * s = alloc.allocate(len + 1);
sv.copy(s, len);
s[len] = '\0';
v.mkStringNoCopy(s);
};
Value * maybeThunk(EvalState & state, Env & env) override;
@@ -168,14 +216,16 @@ struct ExprString : Expr
struct ExprPath : Expr
{
ref<SourceAccessor> accessor;
std::string s;
Value v;
ExprPath(ref<SourceAccessor> accessor, std::string s)
ExprPath(std::pmr::polymorphic_allocator<char> & alloc, ref<SourceAccessor> accessor, std::string_view sv)
: accessor(accessor)
, s(std::move(s))
{
v.mkPath(&*accessor, this->s.c_str());
auto len = sv.length();
char * s = alloc.allocate(len + 1);
sv.copy(s, len);
s[len] = '\0';
v.mkPath(&*accessor, s);
}
Value * maybeThunk(EvalState & state, Env & env) override;
@@ -242,20 +292,33 @@ struct ExprInheritFrom : ExprVar
struct ExprSelect : Expr
{
PosIdx pos;
uint32_t nAttrPath;
Expr *e, *def;
AttrPath attrPath;
ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def)
AttrName * attrPathStart;
ExprSelect(
std::pmr::polymorphic_allocator<char> & alloc,
const PosIdx & pos,
Expr * e,
std::span<const AttrName> attrPath,
Expr * def)
: pos(pos)
, nAttrPath(attrPath.size())
, e(e)
, def(def)
, attrPath(std::move(attrPath)) {};
, attrPathStart(alloc.allocate_object<AttrName>(nAttrPath))
{
std::ranges::copy(attrPath, attrPathStart);
};
ExprSelect(const PosIdx & pos, Expr * e, Symbol name)
ExprSelect(std::pmr::polymorphic_allocator<char> & alloc, const PosIdx & pos, Expr * e, Symbol name)
: pos(pos)
, nAttrPath(1)
, e(e)
, def(0)
, attrPathStart((alloc.allocate_object<AttrName>()))
{
attrPath.push_back(AttrName(name));
*attrPathStart = AttrName(name);
};
PosIdx getPos() const override
@@ -263,6 +326,11 @@ struct ExprSelect : Expr
return pos;
}
std::span<const AttrName> getAttrPath() const
{
return {attrPathStart, nAttrPath};
}
/**
* Evaluate the `a.b.c` part of `a.b.c.d`. This exists mostly for the purpose of :doc in the repl.
*
@@ -280,10 +348,14 @@ struct ExprSelect : Expr
struct ExprOpHasAttr : Expr
{
Expr * e;
AttrPath attrPath;
ExprOpHasAttr(Expr * e, AttrPath attrPath)
std::span<AttrName> attrPath;
ExprOpHasAttr(std::pmr::polymorphic_allocator<char> & alloc, Expr * e, std::vector<AttrName> attrPath)
: e(e)
, attrPath(std::move(attrPath)) {};
, attrPath({alloc.allocate_object<AttrName>(attrPath.size()), attrPath.size()})
{
std::ranges::copy(attrPath, this->attrPath.begin());
};
PosIdx getPos() const override
{
@@ -565,36 +637,39 @@ struct ExprOpNot : Expr
COMMON_METHODS
};
#define MakeBinOp(name, s) \
struct name : Expr \
{ \
PosIdx pos; \
Expr *e1, *e2; \
name(Expr * e1, Expr * e2) \
: e1(e1) \
, e2(e2) {}; \
name(const PosIdx & pos, Expr * e1, Expr * e2) \
: pos(pos) \
, e1(e1) \
, e2(e2) {}; \
void show(const SymbolTable & symbols, std::ostream & str) const override \
{ \
str << "("; \
e1->show(symbols, str); \
str << " " s " "; \
e2->show(symbols, str); \
str << ")"; \
} \
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override \
{ \
e1->bindVars(es, env); \
e2->bindVars(es, env); \
} \
void eval(EvalState & state, Env & env, Value & v) override; \
PosIdx getPos() const override \
{ \
return pos; \
} \
#define MakeBinOpMembers(name, s) \
PosIdx pos; \
Expr *e1, *e2; \
name(Expr * e1, Expr * e2) \
: e1(e1) \
, e2(e2){}; \
name(const PosIdx & pos, Expr * e1, Expr * e2) \
: pos(pos) \
, e1(e1) \
, e2(e2){}; \
void show(const SymbolTable & symbols, std::ostream & str) const override \
{ \
str << "("; \
e1->show(symbols, str); \
str << " " s " "; \
e2->show(symbols, str); \
str << ")"; \
} \
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override \
{ \
e1->bindVars(es, env); \
e2->bindVars(es, env); \
} \
void eval(EvalState & state, Env & env, Value & v) override; \
PosIdx getPos() const override \
{ \
return pos; \
}
#define MakeBinOp(name, s) \
struct name : Expr \
{ \
MakeBinOpMembers(name, s) \
}
MakeBinOp(ExprOpEq, "==");
@@ -602,9 +677,20 @@ MakeBinOp(ExprOpNEq, "!=");
MakeBinOp(ExprOpAnd, "&&");
MakeBinOp(ExprOpOr, "||");
MakeBinOp(ExprOpImpl, "->");
MakeBinOp(ExprOpUpdate, "//");
MakeBinOp(ExprOpConcatLists, "++");
struct ExprOpUpdate : Expr
{
private:
/** Special case for merging of two attrsets. */
void eval(EvalState & state, Value & v, Value & v1, Value & v2);
void evalForUpdate(EvalState & state, Env & env, UpdateQueue & q);
public:
MakeBinOpMembers(ExprOpUpdate, "//");
virtual void evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx) override;
};
struct ExprConcatStrings : Expr
{
PosIdx pos;

View File

@@ -24,7 +24,6 @@ struct StringToken
}
};
// This type must be trivially copyable; see YYLTYPE_IS_TRIVIAL in parser.y.
struct ParserLocation
{
int beginOffset;
@@ -44,9 +43,6 @@ struct ParserLocation
beginOffset = stashedBeginOffset;
endOffset = stashedEndOffset;
}
/** Latest doc comment position, or 0. */
int doc_comment_first_column, doc_comment_last_column;
};
struct LexerState
@@ -71,7 +67,7 @@ struct LexerState
/**
* @brief Maps some positions to a DocComment, where the comment is relevant to the location.
*/
std::unordered_map<PosIdx, DocComment> & positionToDocComment;
DocCommentMap & positionToDocComment;
PosTable & positions;
PosTable::Origin origin;
@@ -82,6 +78,7 @@ struct LexerState
struct ParserState
{
const LexerState & lexerState;
std::pmr::polymorphic_allocator<char> & alloc;
SymbolTable & symbols;
PosTable & positions;
Expr * result;
@@ -327,7 +324,7 @@ ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, st
// Ignore empty strings for a minor optimisation and AST simplification
if (s2 != "") {
es2->emplace_back(i->first, new ExprString(std::move(s2)));
es2->emplace_back(i->first, new ExprString(alloc, s2));
}
};
for (; i != es.end(); ++i, --n) {

View File

@@ -8,22 +8,6 @@
namespace nix {
/**
* For functions where we do not expect deep recursion, we can use a sizable
* part of the stack a free allocation space.
*
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
*/
constexpr size_t nonRecursiveStackReservation = 128;
/**
* Functions that maybe applied to self-similar inputs, such as concatMap on a
* tree, should reserve a smaller part of the stack for allocation.
*
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
*/
constexpr size_t conservativeStackReservation = 16;
struct RegisterPrimOp
{
typedef std::vector<PrimOp> PrimOps;

View File

@@ -2,7 +2,6 @@
///@file
#include <cassert>
#include <exception>
#include <span>
#include <type_traits>
#include <concepts>
@@ -13,6 +12,7 @@
#include "nix/expr/print-options.hh"
#include "nix/util/checked-arithmetic.hh"
#include <boost/unordered/unordered_flat_map_fwd.hpp>
#include <nlohmann/json_fwd.hpp>
namespace nix {
@@ -36,7 +36,6 @@ typedef enum {
tBool,
tNull,
tFloat,
tFailed,
tExternal,
tPrimOp,
tAttrs,
@@ -59,7 +58,6 @@ typedef enum {
*/
typedef enum {
nThunk,
nFailed,
nInt,
nFloat,
nBool,
@@ -157,7 +155,7 @@ class ListBuilder
Value * inlineElems[2] = {nullptr, nullptr};
public:
Value ** elems;
ListBuilder(EvalState & state, size_t size);
ListBuilder(size_t size);
// NOTE: Can be noexcept because we are just copying integral values and
// raw pointers.
@@ -268,30 +266,6 @@ struct ValueBase
size_t size;
Value * const * elems;
};
/**
Representation of an evaluation that previously failed.
`Value` references `Failed` by packed pointer, and its `new` is GC managed.
@see gc_cleanup std::exception_ptr
*/
class Failed : public gc_cleanup
{
public:
std::exception_ptr ex;
/**
* Optional value for recovering `RecoverableEvalError`
* Must be set iff `ex` is an instance of `RecoverableEvalError`.
*/
Value * recoveryValue;
Failed(std::exception_ptr ex, Value * recoveryValue)
: ex(ex)
, recoveryValue(recoveryValue)
{
}
};
};
template<typename T>
@@ -318,7 +292,6 @@ struct PayloadTypeToInternalType
MACRO(PrimOp *, primOp, tPrimOp) \
MACRO(ValueBase::PrimOpApplicationThunk, primOpApp, tPrimOpApp) \
MACRO(ExternalValueBase *, external, tExternal) \
MACRO(ValueBase::Failed *, failed, tFailed) \
MACRO(NixFloat, fpoint, tFloat)
#define NIX_VALUE_PAYLOAD_TYPE(T, FIELD_NAME, DISCRIMINATOR) \
@@ -623,11 +596,6 @@ protected:
path.path = std::bit_cast<const char *>(payload[1]);
}
void getStorage(Failed *& failed) const noexcept
{
failed = std::bit_cast<Failed *>(payload[1]);
}
void setStorage(NixInt integer) noexcept
{
setSingleDWordPayload<tInt>(integer.value);
@@ -677,11 +645,6 @@ protected:
{
setUntaggablePayload<pdPath>(path.accessor, path.path);
}
void setStorage(Failed * failed) noexcept
{
setSingleDWordPayload<tFailed>(std::bit_cast<PackedPointer>(failed));
}
};
/**
@@ -871,6 +834,35 @@ struct Value : public ValueStorage<sizeof(void *)>
{
friend std::string showType(const Value & v);
/**
* Empty list constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
static Value vEmptyList;
/**
* `null` constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
static Value vNull;
/**
* `true` constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
static Value vTrue;
/**
* `true` constant.
*
* This is _not_ a singleton. Pointer equality is _not_ sufficient.
*/
static Value vFalse;
private:
template<InternalType... discriminator>
bool isa() const noexcept
{
@@ -904,12 +896,12 @@ public:
inline bool isThunk() const
{
return isa<tThunk>();
}
};
inline bool isApp() const
{
return isa<tApp>();
}
};
inline bool isBlackhole() const;
@@ -917,22 +909,17 @@ public:
inline bool isLambda() const
{
return isa<tLambda>();
}
};
inline bool isPrimOp() const
{
return isa<tPrimOp>();
}
};
inline bool isPrimOpApp() const
{
return isa<tPrimOpApp>();
}
inline bool isFailed() const
{
return isa<tFailed>();
}
};
/**
* Returns the normal type of a Value. This only returns nThunk if
@@ -969,8 +956,6 @@ public:
return nExternal;
case tFloat:
return nFloat;
case tFailed:
return nFailed;
case tThunk:
case tApp:
return nThunk;
@@ -1093,11 +1078,6 @@ public:
setStorage(n);
}
inline void mkFailed(std::exception_ptr e, Value * recovery) noexcept
{
setStorage(new Value::Failed(e, recovery));
}
bool isList() const noexcept
{
return isa<tListSmall, tListN>();
@@ -1201,11 +1181,6 @@ public:
{
return getStorage<Path>().accessor;
}
Failed * failed() const noexcept
{
return getStorage<Failed *>();
}
};
extern ExprBlackHole eBlackHole;
@@ -1221,7 +1196,7 @@ void Value::mkBlackhole()
}
typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector;
typedef std::unordered_map<
typedef boost::unordered_flat_map<
Symbol,
Value *,
std::hash<Symbol>,

View File

@@ -1,11 +1,11 @@
#include "lexer-helpers.hh"
void nix::lexer::internal::initLoc(YYLTYPE * loc)
void nix::lexer::internal::initLoc(Parser::location_type * loc)
{
loc->beginOffset = loc->endOffset = 0;
}
void nix::lexer::internal::adjustLoc(yyscan_t yyscanner, YYLTYPE * loc, const char * s, size_t len)
void nix::lexer::internal::adjustLoc(yyscan_t yyscanner, Parser::location_type * loc, const char * s, size_t len)
{
loc->stash();

View File

@@ -2,16 +2,12 @@
#include <cstddef>
// including the generated headers twice leads to errors
#ifndef BISON_HEADER
# include "lexer-tab.hh"
# include "parser-tab.hh"
#endif
#include "parser-scanner-decls.hh"
namespace nix::lexer::internal {
void initLoc(YYLTYPE * loc);
void initLoc(Parser::location_type * loc);
void adjustLoc(yyscan_t yyscanner, YYLTYPE * loc, const char * s, size_t len);
void adjustLoc(yyscan_t yyscanner, Parser::location_type * loc, const char * s, size_t len);
} // namespace nix::lexer::internal

View File

@@ -82,6 +82,10 @@ static void requireExperimentalFeature(const ExperimentalFeature & feature, cons
}
using enum nix::Parser::token::token_kind_type;
using YYSTYPE = nix::Parser::value_type;
using YYLTYPE = nix::Parser::location_type;
// yacc generates code that uses unannotated fallthrough.
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"

View File

@@ -53,7 +53,12 @@ deps_other += boost
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json
bdw_gc = dependency('bdw-gc', required : get_option('gc'))
bdw_gc_required = get_option('gc').disable_if(
'address' in get_option('b_sanitize'),
error_message : 'Building with Boehm GC and ASAN is not supported',
)
bdw_gc = dependency('bdw-gc', required : bdw_gc_required)
if bdw_gc.found()
deps_public += bdw_gc
foreach funcspec : [
@@ -64,6 +69,10 @@ if bdw_gc.found()
define_value = cxx.has_function(funcspec).to_int()
configdata_priv.set(define_name, define_value)
endforeach
if host_machine.system() == 'cygwin'
# undefined reference to `__wrap__Znwm'
configdata_pub.set('GC_NO_INLINE_STD_NEW', 1)
endif
endif
# Used in public header. Affects ABI!
configdata_pub.set('NIX_USE_BOEHMGC', bdw_gc.found().to_int())
@@ -88,6 +97,7 @@ config_priv_h = configure_file(
)
subdir('nix-meson-build-support/common')
subdir('nix-meson-build-support/asan-options')
parser_tab = custom_target(
input : 'parser.y',
@@ -163,6 +173,7 @@ sources = files(
'search-path.cc',
'value-to-json.cc',
'value-to-xml.cc',
'value.cc',
'value/context.cc',
)
@@ -180,6 +191,7 @@ this_library = library(
parser_tab,
lexer_tab,
generated_headers,
soversion : nix_soversion,
dependencies : deps_public + deps_private + deps_other,
include_directories : include_dirs,
link_args : linker_export_flags,

View File

@@ -11,7 +11,7 @@
namespace nix {
unsigned long Expr::nrExprs = 0;
Counter Expr::nrExprs;
ExprBlackHole eBlackHole;
@@ -40,12 +40,12 @@ void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
{
printLiteralString(str, s);
printLiteralString(str, v.string_view());
}
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
{
str << s;
str << v.pathStr();
}
void ExprVar::show(const SymbolTable & symbols, std::ostream & str) const
@@ -57,7 +57,7 @@ void ExprSelect::show(const SymbolTable & symbols, std::ostream & str) const
{
str << "(";
e->show(symbols, str);
str << ")." << showAttrPath(symbols, attrPath);
str << ")." << showAttrPath(symbols, getAttrPath());
if (def) {
str << " or (";
def->show(symbols, str);
@@ -261,7 +261,7 @@ void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const
str << "__curPos";
}
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
std::string showAttrPath(const SymbolTable & symbols, std::span<const AttrName> attrPath)
{
std::ostringstream out;
bool first = true;
@@ -362,7 +362,7 @@ void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
e->bindVars(es, env);
if (def)
def->bindVars(es, env);
for (auto & i : attrPath)
for (auto & i : getAttrPath())
if (!i.symbol)
i.expr->bindVars(es, env);
}

View File

@@ -0,0 +1,17 @@
#pragma once
#ifndef BISON_HEADER
# include "parser-tab.hh"
using YYSTYPE = nix::parser::BisonParser::value_type;
using YYLTYPE = nix::parser::BisonParser::location_type;
# include "lexer-tab.hh" // IWYU pragma: export
#endif
namespace nix {
class Parser : public parser::BisonParser
{
using BisonParser::BisonParser;
};
} // namespace nix

View File

@@ -1,5 +1,7 @@
%skeleton "lalr1.cc"
%define api.location.type { ::nix::ParserLocation }
%define api.pure
%define api.namespace { ::nix::parser }
%define api.parser.class { BisonParser }
%locations
%define parse.error verbose
%defines
@@ -26,19 +28,12 @@
#include "nix/expr/eval-settings.hh"
#include "nix/expr/parser-state.hh"
// Bison seems to have difficulty growing the parser stack when using C++ with
// a custom location type. This undocumented macro tells Bison that our
// location type is "trivially copyable" in C++-ese, so it is safe to use the
// same memcpy macro it uses to grow the stack that it uses with its own
// default location type. Without this, we get "error: memory exhausted" when
// parsing some large Nix files. Our other options are to increase the initial
// stack size (200 by default) to be as large as we ever want to support (so
// that growing the stack is unnecessary), or redefine the stack-relocation
// macro ourselves (which is also undocumented).
#define YYLTYPE_IS_TRIVIAL 1
#define YY_DECL int yylex \
(YYSTYPE * yylval_param, YYLTYPE * yylloc_param, yyscan_t yyscanner, nix::ParserState * state)
#define YY_DECL \
int yylex( \
nix::Parser::value_type * yylval_param, \
nix::Parser::location_type * yylloc_param, \
yyscan_t yyscanner, \
nix::ParserState * state)
// For efficiency, we only track offsets; not line,column coordinates
# define YYLLOC_DEFAULT(Current, Rhs, N) \
@@ -57,13 +52,14 @@
namespace nix {
typedef std::unordered_map<PosIdx, DocComment> DocCommentMap;
typedef boost::unordered_flat_map<PosIdx, DocComment, std::hash<PosIdx>> DocCommentMap;
Expr * parseExprFromBuf(
char * text,
size_t length,
Pos::Origin origin,
const SourcePath & basePath,
std::pmr::polymorphic_allocator<char> & alloc,
SymbolTable & symbols,
const EvalSettings & settings,
PosTable & positions,
@@ -78,24 +74,30 @@ Expr * parseExprFromBuf(
%{
#include "parser-tab.hh"
#include "lexer-tab.hh"
/* The parser is very performance sensitive and loses out on a lot
of performance even with basic stdlib assertions. Since those don't
affect ABI we can disable those just for this file. */
#if defined(_GLIBCXX_ASSERTIONS) && !defined(_GLIBCXX_DEBUG)
#undef _GLIBCXX_ASSERTIONS
#endif
#include "parser-scanner-decls.hh"
YY_DECL;
using namespace nix;
#define CUR_POS state->at(yyloc)
#define CUR_POS state->at(yylhs.location)
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char * error)
void parser::BisonParser::error(const location_type &loc_, const std::string &error)
{
auto loc = loc_;
if (std::string_view(error).starts_with("syntax error, unexpected end of file")) {
loc->beginOffset = loc->endOffset;
loc.beginOffset = loc.endOffset;
}
throw ParseError({
.msg = HintFmt(error),
.pos = state->positions[state->at(*loc)]
.pos = state->positions[state->at(loc)]
});
}
@@ -134,6 +136,7 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
std::vector<nix::AttrName> * attrNames;
std::vector<std::pair<nix::AttrName, nix::PosIdx>> * inheritAttrs;
std::vector<std::pair<nix::PosIdx, nix::Expr *>> * string_parts;
std::variant<nix::Expr *, std::string_view> * to_be_string;
std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, nix::StringToken>>> * ind_string_parts;
}
@@ -148,7 +151,8 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
%type <inheritAttrs> attrs
%type <string_parts> string_parts_interpolated
%type <ind_string_parts> ind_string_parts
%type <e> path_start string_parts string_attr
%type <e> path_start
%type <to_be_string> string_parts string_attr
%type <id> attr
%token <id> ID
%token <str> STR IND_STR
@@ -182,7 +186,7 @@ start: expr {
state->result = $1;
// This parser does not use yynerrs; suppress the warning.
(void) yynerrs;
(void) yynerrs_;
};
expr: expr_function;
@@ -257,7 +261,7 @@ expr_op
| expr_op OR expr_op { $$ = new ExprOpOr(state->at(@2), $1, $3); }
| expr_op IMPL expr_op { $$ = new ExprOpImpl(state->at(@2), $1, $3); }
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(state->at(@2), $1, $3); }
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, std::move(*$3)); delete $3; }
| expr_op '?' attrpath { $$ = new ExprOpHasAttr(state->alloc, $1, std::move(*$3)); delete $3; }
| expr_op '+' expr_op
{ $$ = new ExprConcatStrings(state->at(@2), false, new std::vector<std::pair<PosIdx, Expr *> >({{state->at(@1), $1}, {state->at(@3), $3}})); }
| expr_op '-' expr_op { $$ = new ExprCall(state->at(@2), new ExprVar(state->s.sub), {$1, $3}); }
@@ -278,9 +282,9 @@ expr_app
expr_select
: expr_simple '.' attrpath
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), nullptr); delete $3; }
{ $$ = new ExprSelect(state->alloc, CUR_POS, $1, std::move(*$3), nullptr); delete $3; }
| expr_simple '.' attrpath OR_KW expr_select
{ $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; $5->warnIfCursedOr(state->symbols, state->positions); }
{ $$ = new ExprSelect(state->alloc, CUR_POS, $1, std::move(*$3), $5); delete $3; $5->warnIfCursedOr(state->symbols, state->positions); }
| /* Backwards compatibility: because Nixpkgs has a function named or,
allow stuff like map or [...]. This production is problematic (see
https://github.com/NixOS/nix/issues/11118) and will be refactored in the
@@ -303,7 +307,13 @@ expr_simple
}
| INT_LIT { $$ = new ExprInt($1); }
| FLOAT_LIT { $$ = new ExprFloat($1); }
| '"' string_parts '"' { $$ = $2; }
| '"' string_parts '"' {
std::visit(overloaded{
[&](std::string_view str) { $$ = new ExprString(state->alloc, str); },
[&](Expr * expr) { $$ = expr; }},
*$2);
delete $2;
}
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
$$ = state->stripIndentation(CUR_POS, std::move(*$2));
delete $2;
@@ -314,11 +324,11 @@ expr_simple
$$ = new ExprConcatStrings(CUR_POS, false, $2);
}
| SPATH {
std::string path($1.p + 1, $1.l - 2);
std::string_view path($1.p + 1, $1.l - 2);
$$ = new ExprCall(CUR_POS,
new ExprVar(state->s.findFile),
{new ExprVar(state->s.nixPath),
new ExprString(std::move(path))});
new ExprString(state->alloc, path)});
}
| URI {
static bool noURLLiterals = experimentalFeatureSettings.isEnabled(Xp::NoUrlLiterals);
@@ -327,13 +337,13 @@ expr_simple
.msg = HintFmt("URL literals are disabled"),
.pos = state->positions[CUR_POS]
});
$$ = new ExprString(std::string($1));
$$ = new ExprString(state->alloc, $1);
}
| '(' expr ')' { $$ = $2; }
/* Let expressions `let {..., body = ...}' are just desugared
into `(rec {..., body = ...}).body'. */
| LET '{' binds '}'
{ $3->recursive = true; $3->pos = CUR_POS; $$ = new ExprSelect(noPos, $3, state->s.body); }
{ $3->recursive = true; $3->pos = CUR_POS; $$ = new ExprSelect(state->alloc, noPos, $3, state->s.body); }
| REC '{' binds '}'
{ $3->recursive = true; $3->pos = CUR_POS; $$ = $3; }
| '{' binds1 '}'
@@ -344,19 +354,19 @@ expr_simple
;
string_parts
: STR { $$ = new ExprString(std::string($1)); }
| string_parts_interpolated { $$ = new ExprConcatStrings(CUR_POS, true, $1); }
| { $$ = new ExprString(""); }
: STR { $$ = new std::variant<Expr *, std::string_view>($1); }
| string_parts_interpolated { $$ = new std::variant<Expr *, std::string_view>(new ExprConcatStrings(CUR_POS, true, $1)); }
| { $$ = new std::variant<Expr *, std::string_view>(std::string_view()); }
;
string_parts_interpolated
: string_parts_interpolated STR
{ $$ = $1; $1->emplace_back(state->at(@2), new ExprString(std::string($2))); }
{ $$ = $1; $1->emplace_back(state->at(@2), new ExprString(state->alloc, $2)); }
| string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(state->at(@2), $3); }
| DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *>>; $$->emplace_back(state->at(@1), $2); }
| STR DOLLAR_CURLY expr '}' {
$$ = new std::vector<std::pair<PosIdx, Expr *>>;
$$->emplace_back(state->at(@1), new ExprString(std::string($1)));
$$->emplace_back(state->at(@1), new ExprString(state->alloc, $1));
$$->emplace_back(state->at(@2), $3);
}
;
@@ -382,8 +392,8 @@ path_start
root filesystem accessor, rather than the accessor of the
current Nix expression. */
literal.front() == '/'
? new ExprPath(state->rootFS, std::move(path))
: new ExprPath(state->basePath.accessor, std::move(path));
? new ExprPath(state->alloc, state->rootFS, path)
: new ExprPath(state->alloc, state->basePath.accessor, path);
}
| HPATH {
if (state->settings.pureEval) {
@@ -393,7 +403,7 @@ path_start
);
}
Path path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = new ExprPath(ref<SourceAccessor>(state->rootFS), std::move(path));
$$ = new ExprPath(state->alloc, ref<SourceAccessor>(state->rootFS), path);
}
;
@@ -437,7 +447,7 @@ binds1
$accum->attrs.emplace(
i.symbol,
ExprAttrs::AttrDef(
new ExprSelect(iPos, from, i.symbol),
new ExprSelect(state->alloc, iPos, from, i.symbol),
iPos,
ExprAttrs::AttrDef::Kind::InheritedFrom));
}
@@ -454,15 +464,16 @@ attrs
: attrs attr { $$ = $1; $1->emplace_back(AttrName(state->symbols.create($2)), state->at(@2)); }
| attrs string_attr
{ $$ = $1;
ExprString * str = dynamic_cast<ExprString *>($2);
if (str) {
$$->emplace_back(AttrName(state->symbols.create(str->s)), state->at(@2));
delete str;
} else
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in inherit"),
.pos = state->positions[state->at(@2)]
});
std::visit(overloaded {
[&](std::string_view str) { $$->emplace_back(AttrName(state->symbols.create(str)), state->at(@2)); },
[&](Expr * expr) {
throw ParseError({
.msg = HintFmt("dynamic attributes not allowed in inherit"),
.pos = state->positions[state->at(@2)]
});
}
}, *$2);
delete $2;
}
| { $$ = new std::vector<std::pair<AttrName, PosIdx>>; }
;
@@ -471,22 +482,20 @@ attrpath
: attrpath '.' attr { $$ = $1; $1->push_back(AttrName(state->symbols.create($3))); }
| attrpath '.' string_attr
{ $$ = $1;
ExprString * str = dynamic_cast<ExprString *>($3);
if (str) {
$$->push_back(AttrName(state->symbols.create(str->s)));
delete str;
} else
$$->push_back(AttrName($3));
std::visit(overloaded {
[&](std::string_view str) { $$->push_back(AttrName(state->symbols.create(str))); },
[&](Expr * expr) { $$->push_back(AttrName(expr)); }
}, *$3);
delete $3;
}
| attr { $$ = new std::vector<AttrName>; $$->push_back(AttrName(state->symbols.create($1))); }
| string_attr
{ $$ = new std::vector<AttrName>;
ExprString *str = dynamic_cast<ExprString *>($1);
if (str) {
$$->push_back(AttrName(state->symbols.create(str->s)));
delete str;
} else
$$->push_back(AttrName($1));
std::visit(overloaded {
[&](std::string_view str) { $$->push_back(AttrName(state->symbols.create(str))); },
[&](Expr * expr) { $$->push_back(AttrName(expr)); }
}, *$1);
delete $1;
}
;
@@ -497,7 +506,7 @@ attr
string_attr
: '"' string_parts '"' { $$ = $2; }
| DOLLAR_CURLY expr '}' { $$ = $2; }
| DOLLAR_CURLY expr '}' { $$ = new std::variant<Expr *, std::string_view>($2); }
;
expr_list
@@ -537,6 +546,7 @@ Expr * parseExprFromBuf(
size_t length,
Pos::Origin origin,
const SourcePath & basePath,
std::pmr::polymorphic_allocator<char> & alloc,
SymbolTable & symbols,
const EvalSettings & settings,
PosTable & positions,
@@ -551,6 +561,7 @@ Expr * parseExprFromBuf(
};
ParserState state {
.lexerState = lexerState,
.alloc = alloc,
.symbols = symbols,
.positions = positions,
.basePath = basePath,
@@ -563,7 +574,8 @@ Expr * parseExprFromBuf(
Finally _destroy([&] { yylex_destroy(scanner); });
yy_scan_buffer(text, length, scanner);
yyparse(scanner, &state);
Parser parser(scanner, &state);
parser.parse();
return state.result;
}

View File

@@ -1,5 +1,7 @@
#include "nix/store/store-api.hh"
#include "nix/expr/eval.hh"
#include "nix/util/mounted-source-accessor.hh"
#include "nix/fetchers/fetch-to-store.hh"
namespace nix {
@@ -18,4 +20,27 @@ SourcePath EvalState::storePath(const StorePath & path)
return {rootFS, CanonPath{store->printStorePath(path)}};
}
StorePath
EvalState::mountInput(fetchers::Input & input, const fetchers::Input & originalInput, ref<SourceAccessor> accessor)
{
auto storePath = fetchToStore(fetchSettings, *store, accessor, FetchMode::Copy, input.getName());
allowPath(storePath); // FIXME: should just whitelist the entire virtual store
storeFS->mount(CanonPath(store->printStorePath(storePath)), accessor);
auto narHash = store->queryPathInfo(storePath)->narHash;
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
if (originalInput.getNarHash() && narHash != *originalInput.getNarHash())
throw Error(
(unsigned int) 102,
"NAR hash mismatch in input '%s', expected '%s' but got '%s'",
originalInput.to_string(),
narHash.to_string(HashFormat::SRI, true),
originalInput.getNarHash()->to_string(HashFormat::SRI, true));
return storePath;
}
} // namespace nix

View File

@@ -18,6 +18,8 @@
#include "nix/util/sort.hh"
#include <boost/container/small_vector.hpp>
#include <boost/unordered/concurrent_flat_map.hpp>
#include <boost/unordered/unordered_flat_map.hpp>
#include <nlohmann/json.hpp>
#include <sys/types.h>
@@ -260,7 +262,7 @@ static void scopedImport(EvalState & state, const PosIdx pos, SourcePath & path,
{
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
Env * env = &state.allocEnv(vScope->attrs()->size());
Env * env = &state.mem.allocEnv(vScope->attrs()->size());
env->up = &state.baseEnv;
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv, vScope->attrs()->size());
@@ -515,7 +517,6 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
v.mkStringNoCopy("float");
break;
case nThunk:
case nFailed:
unreachable();
}
}
@@ -1076,11 +1077,11 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value ** args, Val
try {
state.forceValue(*args[0], pos);
attrs.insert(state.s.value, args[0]);
attrs.insert(state.symbols.create("success"), &state.vTrue);
attrs.insert(state.symbols.create("success"), &Value::vTrue);
} catch (AssertionError & e) {
// `value = false;` is unfortunate but removing it is a breaking change.
attrs.insert(state.s.value, &state.vFalse);
attrs.insert(state.symbols.create("success"), &state.vFalse);
attrs.insert(state.s.value, &Value::vFalse);
attrs.insert(state.symbols.create("success"), &Value::vFalse);
}
// restore the debugRepl pointer if we saved it earlier.
@@ -1366,8 +1367,8 @@ static void derivationStrictInternal(EvalState & state, std::string_view drvName
using nlohmann::json;
std::optional<StructuredAttrs> jsonObject;
auto pos = v.determinePos(noPos);
auto attr = attrs->find(state.s.structuredAttrs);
if (attr != attrs->end()
auto attr = attrs->get(state.s.structuredAttrs);
if (attr
&& state.forceBool(
*attr->value,
pos,
@@ -1377,8 +1378,8 @@ static void derivationStrictInternal(EvalState & state, std::string_view drvName
/* Check whether null attributes should be ignored. */
bool ignoreNulls = false;
attr = attrs->find(state.s.ignoreNulls);
if (attr != attrs->end())
attr = attrs->get(state.s.ignoreNulls);
if (attr)
ignoreNulls = state.forceBool(
*attr->value,
pos,
@@ -1751,7 +1752,7 @@ static void derivationStrictInternal(EvalState & state, std::string_view drvName
read them later. */
{
auto h = hashDerivationModulo(*state.store, drv, false);
drvHashes.lock()->insert_or_assign(drvPath, h);
drvHashes.insert_or_assign(drvPath, std::move(h));
}
auto result = state.buildBindings(1 + drv.outputs.size());
@@ -2039,8 +2040,8 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value ** args, Va
state.forceAttrs(*v2, pos, "while evaluating an element of the list passed to builtins.findFile");
std::string prefix;
auto i = v2->attrs()->find(state.s.prefix);
if (i != v2->attrs()->end())
auto i = v2->attrs()->get(state.s.prefix);
if (i)
prefix = state.forceStringNoCtx(
*i->value,
pos,
@@ -2242,19 +2243,45 @@ static RegisterPrimOp primop_hashFile({
.fun = prim_hashFile,
});
static Value * fileTypeToString(EvalState & state, SourceAccessor::Type type)
static const Value & fileTypeToString(EvalState & state, SourceAccessor::Type type)
{
return type == SourceAccessor::Type::tRegular ? &state.vStringRegular
: type == SourceAccessor::Type::tDirectory ? &state.vStringDirectory
: type == SourceAccessor::Type::tSymlink ? &state.vStringSymlink
: &state.vStringUnknown;
struct Constants
{
Value regular;
Value directory;
Value symlink;
Value unknown;
};
static const Constants stringValues = []() {
Constants res;
res.regular.mkStringNoCopy("regular");
res.directory.mkStringNoCopy("directory");
res.symlink.mkStringNoCopy("symlink");
res.unknown.mkStringNoCopy("unknown");
return res;
}();
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
using enum SourceAccessor::Type;
switch (type) {
case tRegular:
return stringValues.regular;
case tDirectory:
return stringValues.directory;
case tSymlink:
return stringValues.symlink;
default:
return stringValues.unknown;
}
}
static void prim_readFileType(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
auto path = realisePath(state, pos, *args[0], std::nullopt);
/* Retrieve the directory entry type and stringize it. */
v = *fileTypeToString(state, path.lstat().type);
v = fileTypeToString(state, path.lstat().type);
}
static RegisterPrimOp primop_readFileType({
@@ -2298,7 +2325,9 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value ** args, Val
} else {
// This branch of the conditional is much more likely.
// Here we just stringize the directory entry type.
attrs.insert(state.symbols.create(name), fileTypeToString(state, *type));
// N.B. const_cast here is ok, because these values will never be modified, since
// only thunks are mutable - other types do not change once constructed.
attrs.insert(state.symbols.create(name), const_cast<Value *>(&fileTypeToString(state, *type)));
}
}
@@ -2383,7 +2412,7 @@ static void prim_toXML(EvalState & state, const PosIdx pos, Value ** args, Value
std::ostringstream out;
NixStringContext context;
printValueAsXML(state, true, false, *args[0], out, context, pos);
v.mkString(toView(out), context);
v.mkString(out.view(), context);
}
static RegisterPrimOp primop_toXML({
@@ -2491,7 +2520,7 @@ static void prim_toJSON(EvalState & state, const PosIdx pos, Value ** args, Valu
std::ostringstream out;
NixStringContext context;
printValueAsJSON(state, true, *args[0], pos, out, context);
v.mkString(toView(out), context);
v.mkString(out.view(), context);
}
static RegisterPrimOp primop_toJSON({
@@ -2673,7 +2702,7 @@ bool EvalState::callPathFilter(Value * filterFun, const SourcePath & path, PosId
arg1.mkString(path.path.abs());
// assert that type is not "unknown"
Value * args[]{&arg1, fileTypeToString(*this, st.type)};
Value * args[]{&arg1, const_cast<Value *>(&fileTypeToString(*this, st.type))};
Value res;
callFunction(*filterFun, args, res, pos);
@@ -2979,8 +3008,8 @@ static void prim_unsafeGetAttrPos(EvalState & state, const PosIdx pos, Value **
auto attr = state.forceStringNoCtx(
*args[0], pos, "while evaluating the first argument passed to builtins.unsafeGetAttrPos");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.unsafeGetAttrPos");
auto i = args[1]->attrs()->find(state.symbols.create(attr));
if (i == args[1]->attrs()->end())
auto i = args[1]->attrs()->get(state.symbols.create(attr));
if (!i)
v.mkNull();
else
state.mkPos(v, i->pos);
@@ -3047,7 +3076,7 @@ static void prim_hasAttr(EvalState & state, const PosIdx pos, Value ** args, Val
{
auto attr = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hasAttr");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.hasAttr");
v.mkBool(args[1]->attrs()->find(state.symbols.create(attr)) != args[1]->attrs()->end());
v.mkBool(args[1]->attrs()->get(state.symbols.create(attr)));
}
static RegisterPrimOp primop_hasAttr({
@@ -3132,7 +3161,7 @@ static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value ** args,
// Step 1. Sort the name-value attrsets in place using the memory we allocate for the result
auto listView = args[0]->listView();
size_t listSize = listView.size();
auto & bindings = *state.allocBindings(listSize);
auto & bindings = *state.mem.allocBindings(listSize);
using ElemPtr = decltype(&bindings[0].value);
for (const auto & [n, v2] : enumerate(listView)) {
@@ -3257,14 +3286,14 @@ static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value ** ar
if (left.size() < right.size()) {
for (auto & l : left) {
auto r = right.find(l.name);
if (r != right.end())
auto r = right.get(l.name);
if (r)
attrs.insert(*r);
}
} else {
for (auto & r : right) {
auto l = left.find(r.name);
if (l != left.end())
auto l = left.get(r.name);
if (l)
attrs.insert(r);
}
}
@@ -3327,14 +3356,14 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value ** args
{
state.forceValue(*args[0], pos);
if (args[0]->isPrimOpApp() || args[0]->isPrimOp()) {
v.mkAttrs(&state.emptyBindings);
v.mkAttrs(&Bindings::emptyBindings);
return;
}
if (!args[0]->isLambda())
state.error<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow();
if (!args[0]->lambda().fun->hasFormals()) {
v.mkAttrs(&state.emptyBindings);
v.mkAttrs(&Bindings::emptyBindings);
return;
}
@@ -4028,7 +4057,7 @@ static void prim_groupBy(EvalState & state, const PosIdx pos, Value ** args, Val
auto name = state.forceStringNoCtx(
res, pos, "while evaluating the return value of the grouping function passed to builtins.groupBy");
auto sym = state.symbols.create(name);
auto vector = attrs.try_emplace(sym, ValueVector()).first;
auto vector = attrs.try_emplace<ValueVector>(sym, {}).first;
vector->second.push_back(vElem);
}
@@ -4563,27 +4592,21 @@ static RegisterPrimOp primop_convertHash({
struct RegexCache
{
struct State
{
std::unordered_map<std::string, std::regex, StringViewHash, std::equal_to<>> cache;
};
Sync<State> state_;
boost::concurrent_flat_map<std::string, std::regex, StringViewHash, std::equal_to<>> cache;
std::regex get(std::string_view re)
{
auto state(state_.lock());
auto it = state->cache.find(re);
if (it != state->cache.end())
return it->second;
std::regex regex;
/* No std::regex constructor overload from std::string_view, but can be constructed
from a pointer + size or an iterator range. */
return state->cache
.emplace(
std::piecewise_construct,
std::forward_as_tuple(re),
std::forward_as_tuple(/*s=*/re.data(), /*count=*/re.size(), std::regex::extended))
.first->second;
cache.try_emplace_and_cvisit(
re,
/*s=*/re.data(),
/*count=*/re.size(),
std::regex::extended,
[&regex](const auto & kv) { regex = kv.second; },
[&regex](const auto & kv) { regex = kv.second; });
return regex;
}
};
@@ -4614,7 +4637,7 @@ void prim_match(EvalState & state, const PosIdx pos, Value ** args, Value & v)
auto list = state.buildList(match.size() - 1);
for (const auto & [i, v2] : enumerate(list))
if (!match[i + 1].matched)
v2 = &state.vNull;
v2 = &Value::vNull;
else
v2 = mkString(state, match[i + 1]);
v.mkList(list);
@@ -4706,7 +4729,7 @@ void prim_split(EvalState & state, const PosIdx pos, Value ** args, Value & v)
auto list2 = state.buildList(slen);
for (const auto & [si, v2] : enumerate(list2)) {
if (!match[si + 1].matched)
v2 = &state.vNull;
v2 = &Value::vNull;
else
v2 = mkString(state, match[si + 1]);
}
@@ -4827,7 +4850,7 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value ** ar
from.emplace_back(state.forceString(
*elem, pos, "while evaluating one of the strings to replace passed to builtins.replaceStrings"));
std::unordered_map<size_t, std::string_view> cache;
boost::unordered_flat_map<size_t, std::string_view> cache;
auto to = args[1]->listView();
NixStringContext context;
@@ -5060,7 +5083,7 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
addConstant(
"null",
&vNull,
&Value::vNull,
{
.type = nNull,
.doc = R"(

View File

@@ -10,6 +10,7 @@
#include "nix/util/url.hh"
#include "nix/expr/value-to-json.hh"
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/fetchers/input-cache.hh"
#include <nlohmann/json.hpp>
@@ -218,11 +219,11 @@ static void fetchTree(
throw Error("input '%s' is not allowed to use the '__final' attribute", input.to_string());
}
auto [storePath, input2] = input.fetchToStore(state.store);
auto cachedInput = state.inputCache->getAccessor(state.store, input, fetchers::UseRegistries::No);
state.allowPath(storePath);
auto storePath = state.mountInput(cachedInput.lockedInput, input, cachedInput.accessor);
emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false);
emitTreeAttrs(state, storePath, cachedInput.lockedInput, v, params.emptyRevFallback, false);
}
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value ** args, Value & v)
@@ -561,14 +562,22 @@ static void fetch(
.hash = *expectedHash,
.references = {}});
if (state.store->isValidPath(expectedPath)) {
// Try to get the path from the local store or substituters
try {
state.store->ensurePath(expectedPath);
debug("using substituted/cached path '%s' for '%s'", state.store->printStorePath(expectedPath), *url);
state.allowAndSetStorePathString(expectedPath, v);
return;
} catch (Error & e) {
debug(
"substitution of '%s' failed, will try to download: %s",
state.store->printStorePath(expectedPath),
e.what());
// Fall through to download
}
}
// TODO: fetching may fail, yet the path may be substitutable.
// https://github.com/NixOS/nix/issues/4313
// Download the file/tarball if substitution failed or no hash was provided
auto storePath = unpack ? fetchToStore(
state.fetchSettings,
*state.store,

View File

@@ -139,7 +139,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
attrs.alloc("_type").mkStringNoCopy("timestamp");
std::ostringstream s;
s << t;
auto str = toView(s);
auto str = s.view();
forceNoNullByte(str);
attrs.alloc("value").mkString(str);
v.mkAttrs(attrs);

View File

@@ -75,9 +75,6 @@ void printAmbiguous(
str << "«potential infinite recursion»";
}
break;
case nFailed:
str << "«failed»";
break;
case nFunction:
if (v.isLambda()) {
str << "<LAMBDA>";

View File

@@ -1,5 +1,4 @@
#include <limits>
#include <unordered_set>
#include <sstream>
#include "nix/expr/print.hh"
@@ -10,6 +9,8 @@
#include "nix/util/english.hh"
#include "nix/expr/eval.hh"
#include <boost/unordered/unordered_flat_set.hpp>
namespace nix {
void printElided(
@@ -81,7 +82,7 @@ std::ostream & printLiteralBool(std::ostream & str, bool boolean)
// For example `or' doesn't need to be quoted.
bool isReservedKeyword(const std::string_view str)
{
static const std::unordered_set<std::string_view> reservedKeywords = {
static const boost::unordered_flat_set<std::string_view> reservedKeywords = {
"if", "then", "else", "assert", "with", "let", "in", "rec", "inherit"};
return reservedKeywords.contains(str);
}
@@ -460,7 +461,7 @@ private:
std::ostringstream s;
s << state.positions[v.lambda().fun->pos];
output << " @ " << filterANSIEscapes(toView(s));
output << " @ " << filterANSIEscapes(s.view());
}
} else if (v.isPrimOp()) {
if (v.primOp())
@@ -508,11 +509,6 @@ private:
}
}
void printFailed(Value & v)
{
output << "«failed»";
}
void printExternal(Value & v)
{
v.external()->print(output);
@@ -588,10 +584,6 @@ private:
printThunk(v);
break;
case nFailed:
printFailed(v);
break;
case nExternal:
printExternal(v);
break;

View File

@@ -96,7 +96,6 @@ json printValueAsJSON(
break;
case nThunk:
case nFailed:
case nFunction:
state.error<TypeError>("cannot convert %1% to JSON", showType(v)).atPos(v.determinePos(pos)).debugThrow();
}

View File

@@ -170,11 +170,6 @@ static void printValueAsXML(
case nThunk:
doc.writeEmptyElement("unevaluated");
break;
case nFailed:
doc.writeEmptyElement("failed");
break;
}
}

29
src/libexpr/value.cc Normal file
View File

@@ -0,0 +1,29 @@
#include "nix/expr/value.hh"
namespace nix {
Value Value::vEmptyList = []() {
Value res;
res.setStorage(List{.size = 0, .elems = nullptr});
return res;
}();
Value Value::vNull = []() {
Value res;
res.mkNull();
return res;
}();
Value Value::vTrue = []() {
Value res;
res.mkBool(true);
return res;
}();
Value Value::vFalse = []() {
Value res;
res.mkBool(false);
return res;
}();
} // namespace nix

View File

@@ -32,6 +32,7 @@ add_project_arguments(
)
subdir('nix-meson-build-support/common')
subdir('nix-meson-build-support/asan-options')
sources = files(
'nix_api_fetchers.cc',
@@ -53,6 +54,7 @@ subdir('nix-meson-build-support/windows-version')
this_library = library(
'nixfetchersc',
sources,
soversion : nix_soversion,
dependencies : deps_public + deps_private + deps_other,
include_directories : include_dirs,
link_args : linker_export_flags,

View File

@@ -175,6 +175,12 @@ TEST_F(GitUtilsTest, peel_reference)
TEST(GitUtils, isLegalRefName)
{
ASSERT_TRUE(isLegalRefName("A/b"));
ASSERT_TRUE(isLegalRefName("AaA/b"));
ASSERT_TRUE(isLegalRefName("FOO/BAR/BAZ"));
ASSERT_TRUE(isLegalRefName("HEAD"));
ASSERT_TRUE(isLegalRefName("refs/tags/1.2.3"));
ASSERT_TRUE(isLegalRefName("refs/heads/master"));
ASSERT_TRUE(isLegalRefName("foox"));
ASSERT_TRUE(isLegalRefName("1337"));
ASSERT_TRUE(isLegalRefName("foo.baz"));

View File

@@ -1,5 +1,6 @@
#include "nix/store/store-open.hh"
#include "nix/store/globals.hh"
#include "nix/store/dummy-store.hh"
#include "nix/fetchers/fetch-settings.hh"
#include "nix/fetchers/fetchers.hh"
#include "nix/fetchers/git-utils.hh"
@@ -179,10 +180,11 @@ TEST_F(GitTest, submodulePeriodSupport)
// 6) Commit the addition in super
commitAll(super.get(), "Add submodule with branch='.'");
// TODO: Use dummy:// store with MemorySourceAccessor.
Path storeTmpDir = createTempDir();
auto storeTmpDirAutoDelete = AutoDelete(storeTmpDir, true);
ref<Store> store = openStore(storeTmpDir);
auto store = [] {
auto cfg = make_ref<DummyStoreConfig>(StoreReference::Params{});
cfg->readOnly = false;
return cfg->openStore();
}();
auto settings = fetchers::Settings{};
auto input = fetchers::Input::fromAttrs(

View File

@@ -37,6 +37,7 @@ libgit2 = dependency('libgit2')
deps_private += libgit2
subdir('nix-meson-build-support/common')
subdir('nix-meson-build-support/asan-options')
sources = files(
'access-tokens.cc',
@@ -63,7 +64,7 @@ this_exe = executable(
test(
meson.project_name(),
this_exe,
env : {
env : asan_test_options_env + {
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
},
protocol : 'gtest',

View File

@@ -61,6 +61,7 @@ mkMesonExecutable (finalAttrs: {
buildInputs = [ writableTmpDirAsHomeHook ];
}
''
export ASAN_OPTIONS=abort_on_error=1:print_summary=1:detect_leaks=0
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
touch $out

View File

@@ -1,14 +1,14 @@
#include <gtest/gtest.h>
#include "nix/fetchers/fetchers.hh"
#include "nix/util/json-utils.hh"
#include <nlohmann/json.hpp>
#include "nix/util/tests/characterization.hh"
#include "nix/util/tests/json-characterization.hh"
namespace nix {
using nlohmann::json;
class PublicKeyTest : public CharacterizationTest
class PublicKeyTest : public JsonCharacterizationTest<fetchers::PublicKey>,
public ::testing::WithParamInterface<std::pair<std::string_view, fetchers::PublicKey>>
{
std::filesystem::path unitTestData = getUnitTestData() / "public-key";
@@ -19,30 +19,35 @@ public:
}
};
#define TEST_JSON(FIXTURE, NAME, VAL) \
TEST_F(FIXTURE, PublicKey_##NAME##_from_json) \
{ \
readTest(#NAME ".json", [&](const auto & encoded_) { \
fetchers::PublicKey expected{VAL}; \
fetchers::PublicKey got = nlohmann::json::parse(encoded_); \
ASSERT_EQ(got, expected); \
}); \
} \
\
TEST_F(FIXTURE, PublicKey_##NAME##_to_json) \
{ \
writeTest( \
#NAME ".json", \
[&]() -> json { return nlohmann::json(fetchers::PublicKey{VAL}); }, \
[](const auto & file) { return json::parse(readFile(file)); }, \
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
}
TEST_P(PublicKeyTest, from_json)
{
const auto & [name, expected] = GetParam();
readJsonTest(name, expected);
}
TEST_JSON(PublicKeyTest, simple, (fetchers::PublicKey{.type = "ssh-rsa", .key = "ABCDE"}))
TEST_P(PublicKeyTest, to_json)
{
const auto & [name, value] = GetParam();
writeJsonTest(name, value);
}
TEST_JSON(PublicKeyTest, defaultType, fetchers::PublicKey{.key = "ABCDE"})
#undef TEST_JSON
INSTANTIATE_TEST_SUITE_P(
PublicKeyJSON,
PublicKeyTest,
::testing::Values(
std::pair{
"simple",
fetchers::PublicKey{
.type = "ssh-rsa",
.key = "ABCDE",
},
},
std::pair{
"defaultType",
fetchers::PublicKey{
.key = "ABCDE",
},
}));
TEST_F(PublicKeyTest, PublicKey_noRoundTrip_from_json)
{

View File

@@ -1,6 +1,7 @@
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/fetchers/fetchers.hh"
#include "nix/fetchers/fetch-settings.hh"
#include "nix/util/environment-variables.hh"
namespace nix {
@@ -27,14 +28,22 @@ StorePath fetchToStore(
std::optional<fetchers::Cache::Key> cacheKey;
if (!filter && path.accessor->fingerprint) {
cacheKey = makeFetchToStoreCacheKey(std::string{name}, *path.accessor->fingerprint, method, path.path.abs());
auto [subpath, fingerprint] = filter ? std::pair<CanonPath, std::optional<std::string>>{path.path, std::nullopt}
: path.accessor->getFingerprint(path.path);
if (fingerprint) {
cacheKey = makeFetchToStoreCacheKey(std::string{name}, *fingerprint, method, subpath.abs());
if (auto res = settings.getCache()->lookupStorePath(*cacheKey, store)) {
debug("store path cache hit for '%s'", path);
return res->storePath;
}
} else
} else {
static auto barf = getEnv("_NIX_TEST_BARF_ON_UNCACHEABLE").value_or("") == "1";
if (barf && !filter)
throw Error("source path '%s' is uncacheable (filter=%d)", path, (bool) filter);
// FIXME: could still provide in-memory caching keyed on `SourcePath`.
debug("source path '%s' is uncacheable", path);
}
Activity act(
*logger,

View File

@@ -3,8 +3,8 @@
#include "nix/util/source-path.hh"
#include "nix/fetchers/fetch-to-store.hh"
#include "nix/util/json-utils.hh"
#include "nix/fetchers/store-path-accessor.hh"
#include "nix/fetchers/fetch-settings.hh"
#include "nix/fetchers/fetch-to-store.hh"
#include <nlohmann/json.hpp>
@@ -332,10 +332,20 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
debug("using substituted/cached input '%s' in '%s'", to_string(), store->printStorePath(storePath));
auto accessor = makeStorePathAccessor(store, storePath);
// We just ensured the store object was there
auto accessor = ref{store->getFSAccessor(storePath)};
accessor->fingerprint = getFingerprint(store);
// Store a cache entry for the substituted tree so later fetches
// can reuse the existing nar instead of copying the unpacked
// input back into the store on every evaluation.
if (accessor->fingerprint) {
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive;
auto cacheKey = makeFetchToStoreCacheKey(getName(), *accessor->fingerprint, method, "/");
settings->getCache()->upsert(cacheKey, *store, {}, storePath);
}
accessor->setPathDisplay("«" + to_string() + "»");
return {accessor, *this};
@@ -346,8 +356,10 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
auto [accessor, result] = scheme->getAccessor(store, *this);
assert(!accessor->fingerprint);
accessor->fingerprint = result.getFingerprint(store);
if (!accessor->fingerprint)
accessor->fingerprint = result.getFingerprint(store);
else
result.cachedFingerprint = accessor->fingerprint;
return {accessor, std::move(result)};
}
@@ -509,7 +521,7 @@ fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json &
return res;
}
void adl_serializer<fetchers::PublicKey>::to_json(json & json, fetchers::PublicKey p)
void adl_serializer<fetchers::PublicKey>::to_json(json & json, const fetchers::PublicKey & p)
{
json["type"] = p.type;
json["key"] = p.key;

View File

@@ -1,5 +1,7 @@
#include "nix/fetchers/filtering-source-accessor.hh"
#include <boost/unordered/unordered_flat_set.hpp>
namespace nix {
std::optional<std::filesystem::path> FilteringSourceAccessor::getPhysicalPath(const CanonPath & path)
@@ -14,15 +16,26 @@ std::string FilteringSourceAccessor::readFile(const CanonPath & path)
return next->readFile(prefix / path);
}
void FilteringSourceAccessor::readFile(const CanonPath & path, Sink & sink, std::function<void(uint64_t)> sizeCallback)
{
checkAccess(path);
return next->readFile(prefix / path, sink, sizeCallback);
}
bool FilteringSourceAccessor::pathExists(const CanonPath & path)
{
return isAllowed(path) && next->pathExists(prefix / path);
}
std::optional<SourceAccessor::Stat> FilteringSourceAccessor::maybeLstat(const CanonPath & path)
{
return isAllowed(path) ? next->maybeLstat(prefix / path) : std::nullopt;
}
SourceAccessor::Stat FilteringSourceAccessor::lstat(const CanonPath & path)
{
checkAccess(path);
return next->maybeLstat(prefix / path);
return next->lstat(prefix / path);
}
SourceAccessor::DirEntries FilteringSourceAccessor::readDirectory(const CanonPath & path)
@@ -47,6 +60,13 @@ std::string FilteringSourceAccessor::showPath(const CanonPath & path)
return displayPrefix + next->showPath(prefix / path) + displaySuffix;
}
std::pair<CanonPath, std::optional<std::string>> FilteringSourceAccessor::getFingerprint(const CanonPath & path)
{
if (fingerprint)
return {path, fingerprint};
return next->getFingerprint(prefix / path);
}
void FilteringSourceAccessor::checkAccess(const CanonPath & path)
{
if (!isAllowed(path))
@@ -57,12 +77,12 @@ void FilteringSourceAccessor::checkAccess(const CanonPath & path)
struct AllowListSourceAccessorImpl : AllowListSourceAccessor
{
std::set<CanonPath> allowedPrefixes;
std::unordered_set<CanonPath> allowedPaths;
boost::unordered_flat_set<CanonPath> allowedPaths;
AllowListSourceAccessorImpl(
ref<SourceAccessor> next,
std::set<CanonPath> && allowedPrefixes,
std::unordered_set<CanonPath> && allowedPaths,
boost::unordered_flat_set<CanonPath> && allowedPaths,
MakeNotAllowedError && makeNotAllowedError)
: AllowListSourceAccessor(SourcePath(next), std::move(makeNotAllowedError))
, allowedPrefixes(std::move(allowedPrefixes))
@@ -84,7 +104,7 @@ struct AllowListSourceAccessorImpl : AllowListSourceAccessor
ref<AllowListSourceAccessor> AllowListSourceAccessor::create(
ref<SourceAccessor> next,
std::set<CanonPath> && allowedPrefixes,
std::unordered_set<CanonPath> && allowedPaths,
boost::unordered_flat_set<CanonPath> && allowedPaths,
MakeNotAllowedError && makeNotAllowedError)
{
return make_ref<AllowListSourceAccessorImpl>(

View File

@@ -12,6 +12,7 @@
#include <git2/attr.h>
#include <git2/blob.h>
#include <git2/branch.h>
#include <git2/commit.h>
#include <git2/config.h>
#include <git2/describe.h>
@@ -28,10 +29,12 @@
#include <git2/submodule.h>
#include <git2/sys/odb_backend.h>
#include <git2/sys/mempack.h>
#include <git2/tag.h>
#include <git2/tree.h>
#include <boost/unordered/unordered_flat_map.hpp>
#include <boost/unordered/unordered_flat_set.hpp>
#include <iostream>
#include <unordered_set>
#include <queue>
#include <regex>
#include <span>
@@ -315,7 +318,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
uint64_t getRevCount(const Hash & rev) override
{
std::unordered_set<git_oid> done;
boost::unordered_flat_set<git_oid, std::hash<git_oid>> done;
std::queue<Commit> todo;
todo.push(peelObject<Commit>(lookupObject(*this, hashToOID(rev)).get(), GIT_OBJECT_COMMIT));
@@ -569,7 +572,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
void verifyCommit(const Hash & rev, const std::vector<fetchers::PublicKey> & publicKeys) override
{
// Map of SSH key types to their internal OpenSSH representations
static const std::unordered_map<std::string_view, std::string_view> keyTypeMap = {
static const boost::unordered_flat_map<std::string_view, std::string_view> keyTypeMap = {
{"ssh-dsa", "ssh-dsa"},
{"ssh-ecdsa", "ssh-ecdsa"},
{"ssh-ecdsa-sk", "sk-ecdsa-sha2-nistp256@openssh.com"},
@@ -816,7 +819,7 @@ struct GitSourceAccessor : SourceAccessor
return toHash(*git_tree_entry_id(entry));
}
std::unordered_map<CanonPath, TreeEntry> lookupCache;
boost::unordered_flat_map<CanonPath, TreeEntry> lookupCache;
/* Recursively look up 'path' relative to the root. */
git_tree_entry * lookup(State & state, const CanonPath & path)
@@ -1253,7 +1256,7 @@ GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllow
makeFSSourceAccessor(path),
std::set<CanonPath>{wd.files},
// Always allow access to the root, but not its children.
std::unordered_set<CanonPath>{CanonPath::root},
boost::unordered_flat_set<CanonPath>{CanonPath::root},
std::move(makeNotAllowedError))
.cast<SourceAccessor>();
if (exportIgnore)
@@ -1322,63 +1325,33 @@ GitRepo::WorkdirInfo GitRepo::getCachedWorkdirInfo(const std::filesystem::path &
return workdirInfo;
}
/**
* Checks that the git reference is valid and normalizes slash '/' sequences.
*
* Accepts shorthand references (one-level refnames are allowed).
*/
bool isValidRefNameAllowNormalizations(const std::string & refName)
{
/* Unfortunately libgit2 doesn't expose the limit in headers, but its internal
limit is also 1024. */
std::array<char, 1024> normalizedRefBuffer;
/* It would be nice to have a better API like git_reference_name_is_valid, but
* with GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND flag. libgit2 uses it internally
* but doesn't expose it in public headers [1].
* [1]:
* https://github.com/libgit2/libgit2/blob/9d5f1bacc23594c2ba324c8f0d41b88bf0e9ef04/src/libgit2/refs.c#L1362-L1365
*/
auto res = git_reference_normalize_name(
normalizedRefBuffer.data(),
normalizedRefBuffer.size(),
refName.c_str(),
GIT_REFERENCE_FORMAT_ALLOW_ONELEVEL | GIT_REFERENCE_FORMAT_REFSPEC_SHORTHAND);
return res == 0;
}
bool isLegalRefName(const std::string & refName)
{
initLibGit2();
/* Since `git_reference_normalize_name` is the best API libgit2 has for verifying
* reference names with shorthands (see comment in normalizeRefName), we need to
* ensure that exceptions to the validity checks imposed by normalization [1] are checked
* explicitly.
* [1]: https://git-scm.com/docs/git-check-ref-format#Documentation/git-check-ref-format.txt---normalize
*/
/* Check for cases that don't get rejected by libgit2.
* FIXME: libgit2 should reject this. */
if (refName == "@")
return false;
/* Leading slashes and consecutive slashes are stripped during normalizatiton. */
if (refName.starts_with('/') || refName.find("//") != refName.npos)
return false;
/* Refer to libgit2. */
if (!isValidRefNameAllowNormalizations(refName))
return false;
/* libgit2 doesn't barf on DEL symbol.
* FIXME: libgit2 should reject this. */
if (refName.find('\177') != refName.npos)
return false;
return true;
for (auto * func : {
git_reference_name_is_valid,
git_branch_name_is_valid,
git_tag_name_is_valid,
}) {
int valid = 0;
if (func(&valid, refName.c_str()))
throw Error("checking git reference '%s': %s", refName, git_error_last()->message);
if (valid)
return true;
}
return false;
}
} // namespace nix

Some files were not shown because too many files have changed in this diff Show More