Compare commits

...

223 Commits

Author SHA1 Message Date
Théophane Hufschmitt
bb9b33ad6f Reword the experimental feature description.
Co-Authored-By: Robert Hensing <robert@roberthensing.nl>
2024-02-27 07:00:50 +01:00
Théophane Hufschmitt
7d3328e4cc find-roots: Fix regex to match store paths
Has been made a bit more lenient upstream, so match that
2024-02-27 06:58:42 +01:00
Théophane Hufschmitt
79a2997b9e find-roots: Remove the using std directives
We got rid of them in the rest of the code, so let's do the same here.
2024-02-27 06:53:55 +01:00
Théophane Hufschmitt
86ae77ba22 Don't run the tracing daemon from the Nix store
That would defeat the whole purpose of the thing as it would provide a
nice escalation path from a Nix vulnerability to root access
2024-02-27 06:49:51 +01:00
Théophane Hufschmitt
e68ee65329 Document the installation process with a non-root daemon
Not supported by the installer because there are many moving parts, but
a rough installation guide can help be used for people to get a custom
installation for their needs
2024-02-27 06:37:11 +01:00
Théophane Hufschmitt
1dbba94244 Make sure that pdiGcDaemon is the right pid
Move its definition before a loop that might change `$!`
2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
e859565a20 tests: Pipe the nix-find-roots output to /dev/null
Otherwise the tests get stuck when using the daemon (I didn't understand
why though, so this is a dirty patch more than a proper fix, but at
least it works now)
2024-02-23 10:00:10 +01:00
John Ericson
7f643277e9 pname + version 2024-02-23 10:00:10 +01:00
John Ericson
9e79d2d1b9 Restore some things lost in the rebase 2024-02-23 10:00:10 +01:00
John Ericson
fb7251915b Make rootless daemon NixOS test setup more declarative
I misunderstand what was going on and https://github.com/NixOS/nixpkgs/issues/263248 is a non-issue. That means we can improve the code right away.

Thank you @eclairevoyant for tipping me off that I was mistaken!
2024-02-23 10:00:10 +01:00
John Ericson
24318d8055 Create a NixOS integration test for the rootless daemon
The test plan is taken from
https://github.com/thufschmitt/rootless-nix-daemon-test. That
intentionally used non-NixOS to get around the ambient Nix daemon, but
with newer NixOS we can in fact disable the ambient Nix daemon an run
our own!

A few things which are needed to make this nicer in the future

- https://github.com/NixOS/nixpkgs/issues/3702

  A now-fixed issue, but won't be available until 23.05

- https://github.com/NixOS/nixpkgs/issues/263248
  https://github.com/NixOS/nixpkgs/issues/263250

  Newly opened issues inspired by the process of writing this test.
2024-02-23 10:00:10 +01:00
John Ericson
c53498b0bf nix-find-roots: Don't explicitly link c++fs on Darwin
It is no longer needed. See f4a8426098
which did the same thing in the rest of Nix.
2024-02-23 10:00:10 +01:00
John Ericson
d170ab4d8c tests: Fix config file name 2024-02-23 10:00:10 +01:00
John Ericson
07e6ee93f3 Mark non-header functions static, API docs in header 2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
8dc4ff661c nix-find-roots: Cleanup
Based on an offline review by @mopleen (thanks!)
2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
a97c309198 nix-find-roots: Don't assume that argv[0] exists 2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
bc445dc2b6 Remove the NIX_GC_SOCKET_PATH environment variable
Not really needed since it's configurable from the config (and people
can always use `$NIX_CONFIG` if they really need to configure it from
the CLI)
2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
21efcb7b61 Split the gc-external-daemon test
Improve parallelism a bit
2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
d68268706e Allow the gc roots daemon to use a long socket path
`chdir` to the directory of the socket and only use a relative path to
it to bypass the socket path length limit (like it's done in
`nix::bind`, except that there's no need to fork here since we can
afford changing the directory of the process)
2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
9b32b05956 Use the standalone gc lib in the default gc 2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
6d2631f514 Add some tests for the external gc daemon 2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
db23f5cf2d gc: Use the trace helper 2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
9bbf398d71 Add an external executable to trace the gc roots back to the store 2024-02-23 10:00:10 +01:00
Théophane Hufschmitt
6a5210f48e Merge pull request #9815 from NixOS/nix-hash-path
`nix hash path`, text hashing for `nix store add`, and preparatory refactors
2024-02-22 17:15:34 +01:00
Eelco Dolstra
f183eef4a0 Merge pull request #10060 from bobvanderlinden/pr-simple-profile-remove-suggestion
profile: suggest removal using profile entry name
2024-02-22 11:02:12 +01:00
Bob van der Linden
4ae5091716 nix profile: suggest removal using entry name
When a file conflict arises during a package install a suggestion is
made to remove the old entry. This was previously done using the
installable URLs of the old entry. These URLs are quite verbose and
often do not equal the URL of the existing entry.

This change uses the recently introduced profile entry name for the
suggestion, resulting in a simpler output.

The improvement is easily seen in the change to the functional test.
2024-02-21 21:58:36 +01:00
John Ericson
2080d89b87 Merge pull request #10038 from edolstra/tarball-git-cache
Use the Git cache for tarball flakes
2024-02-21 15:47:02 -05:00
Eelco Dolstra
e391fc2101 Add comments 2024-02-21 21:15:28 +01:00
Théophane Hufschmitt
774e7ca584 Merge pull request #9914 from 9999years/debugger-on-trace
Enter debugger on `builtins.trace` with an option
2024-02-21 18:32:56 +01:00
Théophane Hufschmitt
36fa8b1bcc Merge pull request #9943 from 9999years/release-notes
Add release notes
2024-02-21 18:17:50 +01:00
John Ericson
efd36b49e8 nix hash path, and preperatory refactors
- `nix store add` supports text hashing

  With functional test ensuring it matches `builtins.toFile`.

- Factored-out flags for both commands

- Move all common reusable flags to `libcmd`

  - They are not part of the *definition* of the CLI infra, just a usag
    of it.

  - The `libstore` flag couldn't go in `args.hh` in libutil anyways,
    would be awkward for it to live alone

- Shuffle around `Cmd*` hierarchy so flags for deprecated commands don't
  end up on the new ones
2024-02-21 12:11:25 -05:00
Rebecca Turner
b111fba8cd Add documentation, rename to debugger-on-trace 2024-02-21 09:07:39 -08:00
Eelco Dolstra
3f5d7afe46 Merge pull request #10024 from edolstra/remove-locked-flag
Input: Replace 'locked' bool by isLocked() method
2024-02-21 16:19:15 +01:00
Eelco Dolstra
9ae665b9e1 Merge pull request #10054 from syvb/channel-unpack-count
Say how many channels were unpacked in nix-channel
2024-02-21 12:23:57 +01:00
Eelco Dolstra
09d76e512a GitArchiveInputScheme: Require a NAR hash 2024-02-21 12:08:18 +01:00
Eelco Dolstra
ff4fa4dbd3 Merge pull request #10055 from GrahamDennis/gdennis/faster-flake-lock-parsing
Faster flake.lock parsing
2024-02-21 11:57:44 +01:00
Graham Dennis
7fd0de38c6 Faster flake.lock parsing
This PR reduces the creation of short-lived basic_json objects while
parsing flake.lock files. For large flake.lock files (~1.5MB) I was
observing ~60s being spent for trivial nix build operations while
after this change it is now taking ~1.6s.
2024-02-21 18:40:34 +11:00
syvb
60eeacc24a Say how many channels were unpacked 2024-02-20 19:17:18 -05:00
John Ericson
a1b1070dbe Merge pull request #9916 from 9999years/quit-whole-program-from-debugger
`:quit` in the debugger should quit the whole program
2024-02-20 18:12:05 -05:00
Rebecca Turner
8e71883e3f Rename ProcessLineResult variants 2024-02-20 14:52:16 -08:00
Rebecca Turner
2a8fe9a938 :quit in the debugger should quit the whole program 2024-02-20 10:01:13 -08:00
Eelco Dolstra
071dd2b3a4 Input: Replace 'locked' bool by isLocked() method
It's better to just check whether the input has all the attributes
needed to consider itself locked (e.g. whether a Git input has an
'rev' attribute).

Also, the 'locked' field was actually incorrect for Git inputs: it
would be set to true even for dirty worktrees. As a result, we got
away with using fetchTree() internally even though fetchTree()
requires a locked input in pure mode. In particular, this allowed
'--override-input' to work by accident.

The fix is to pass a set of "overrides" to call-flake.nix for all the
unlocked inputs (i.e. the top-level flake and any --override-inputs).
2024-02-20 16:59:15 +01:00
John Ericson
78e7c98b02 Merge pull request #10049 from edolstra/dont-send-experimental-features
Don't send settings that depend on disabled experimental features to the daemon
2024-02-20 09:53:08 -05:00
Eelco Dolstra
0acd783190 Don't send settings that depend on disabled experimental features to the daemon
This fixes warnings like

   warning: Ignoring setting 'auto-allocate-uids' because experimental feature 'auto-allocate-uids' is not enabled
   warning: Ignoring setting 'impure-env' because experimental feature 'configurable-impure-env' is not enabled

when using the daemon and the user didn't actually set those settings.

Note: this also hides those settings from `nix config show`, but that
seems a good thing.
2024-02-20 15:28:42 +01:00
Théophane Hufschmitt
d2c6a93bd5 Merge pull request #10044 from edolstra/empty-git-repos
Handle empty Git repositories / workdirs
2024-02-20 14:01:23 +01:00
Eelco Dolstra
cabee98152 Tarball fetcher: Use the content-addressed Git cache
Backported from the lazy-trees branch.
2024-02-20 12:57:36 +01:00
Eelco Dolstra
930b9c8269 PosixSourceAccessor: Support roots that are not directories
We have to support this for `fetchTree { type = "file" }` (and
probably other types of trees that can have a non-directory at the
root, like NARs).
2024-02-20 12:51:15 +01:00
Eelco Dolstra
b00f412f81 Remove bad.tar.xz check, since libarchive doesn't care 2024-02-20 12:51:15 +01:00
Eelco Dolstra
db012d1e63 tests/functional/tarball.sh: Fix invalid file:// URLs 2024-02-20 12:51:15 +01:00
Théophane Hufschmitt
6f4bb1b584 Merge pull request #10043 from edolstra/fix-readonly-fetchToStore
fetchToStore(): Don't always respect settings.readOnlyMode
2024-02-20 12:07:48 +01:00
Eelco Dolstra
7cb4d0c5b7 fetchToStore(): Don't always respect settings.readOnlyMode
It's now up to the caller whether readOnlyMode should be applied. In
some contexts (like InputScheme::fetch()), we always need to fetch.
2024-02-20 11:46:49 +01:00
Eelco Dolstra
9e762454cf Support empty Git repositories / workdirs
Fixes #10039.
2024-02-20 11:40:02 +01:00
Eelco Dolstra
d52d91fe7a AllowListInputAccessor: Clarify that the "allowed paths" are actually allowed prefixes
E.g. adding "/" will allow access to the root and *everything below it*.
2024-02-20 11:23:26 +01:00
Eelco Dolstra
6162105675 Don't say "copying X to the store" in read-only mode 2024-02-20 10:47:30 +01:00
John Ericson
60936f28e5 Merge pull request #9881 from obsidiansystems/purify-canon-path
Purify `CanonPath`
2024-02-16 10:11:11 -05:00
John Ericson
d17e1d9737 Purify CanonPath
The core `CanonPath` constructors were using `absPath`, but `absPath` in
some situations does IO which is not appropriate. It turns out that
these constructors avoided those situations, and thus were pure, but it
was far from obvious this was the case.

To remedy the situation, abstract the core algorithm from `canonPath` to
use separately in `CanonPath` without any IO. No we know by-construction
that those constructors are pure.

That leaves `CanonPath::fromCWD` as the only operation which uses IO /
is impure. Add docs on it, and `CanonPath` as a whole, explaining the
situation.

This is also necessary to support Windows paths on windows without
messing up `CanonPath`. But, I think it is good even without that.

Co-authored-by: Eelco Dolstra <edolstra@gmail.com>
Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
2024-02-16 09:47:25 -05:00
John Ericson
d53c8901ef Merge pull request #9985 from alois31/symlink-resolution
Restore `builtins.pathExists` behavior on broken symlinks
2024-02-16 09:24:03 -05:00
John Ericson
e27b7e04bf Add note about this being a temp solution 2024-02-16 08:45:15 -05:00
Eelco Dolstra
06be819b89 Merge pull request #9485 from edolstra/tarball-cache
Add a Git-based content-addressed tarball cache
2024-02-15 22:37:57 +01:00
Eelco Dolstra
78b8db72b5 Remove tarball-cache.{hh,cc}
TarballInfo is only used in github.cc, and getTarballCache() is a bit
too trivial to have its own file.
2024-02-15 21:58:08 +01:00
Eelco Dolstra
9b04ce16fc Merge branch 'tarball-cache' of github.com:Ericson2314/nix into tarball-cache 2024-02-15 21:48:10 +01:00
Eelco Dolstra
4b40fc5b46 Merge remote-tracking branch 'origin/master' into tarball-cache 2024-02-15 21:46:36 +01:00
Eelco Dolstra
27b8d5d920 Merge pull request #10013 from edolstra/libgit2-lookup
GitInputAccessor: Speed up lookup()
2024-02-15 17:30:22 +01:00
John Ericson
ed24baaec4 Finish separating concerns with tarball cache
There is no longer an `importTarball` method. Instead, there is a
`unpackTarfileToSink` function (back in libutil). The caller can use
thisw with the `getParseSink` method we added in the last commit easily
enough.

In addition, tarball cache functionality is separated from `git-utils`
and moved into `tarball-cache`. This ensures we are separating mechanism
and policy.
2024-02-15 10:34:01 -05:00
John Ericson
ba6a5f06ee Split GitRepoImpl::importTarball
There is now a separation of:

1. A `FileSystemObjectSink` for writing to git repos

2. Adapting libarchive to use that parse sink.

The prepares a proper separation of concerns.
2024-02-15 10:27:54 -05:00
Théophane Hufschmitt
bc9471d032 Merge pull request #9966 from NixOS/mention-OPTIMIZE-in-hacking-guide
Mention `OPTIMIZE=0` more prominently in the hacking guide
2024-02-15 09:33:15 +01:00
Eelco Dolstra
67a6d34448 GitInputAccessor: Speed up lookup()
A command like

  rm -rf ~/.cache/nix/tarball-cache/ ~/.cache/nix/fetcher-cache-v1.sqlite*; nix flake metadata 'git+file:///home/eelco/Dev/nixpkgs?rev=9463103069725474698139ab10f17a9d125da859'

was spending about 84% of its runtime in lookup(), specifically in
git_tree_entry_bypath(). (The reading of blobs is less than 3%.)

It appears libgit2 doesn't do a lot of caching of trees, so we now
make sure that when we look up a path, we add all its parents, and all
the immediate children of the parents (since we have them in memory
anyway), to our own cache.

This speed up the command above from 17.2s to 7.8s on my machine.

Fixes (or at least should improve a lot) #9684.
2024-02-14 19:25:23 +01:00
Rebecca Turner
6d2b446e2b Add release notes for "Pretty print values in nix repl" 2024-02-14 08:58:54 -08:00
Eelco Dolstra
54354eaecf Merge remote-tracking branch 'origin/master' into tarball-cache 2024-02-14 14:45:19 +01:00
Théophane Hufschmitt
d857914e1a Merge pull request #9931 from 9999years/pretty-printer
Pretty-print values in the REPL
2024-02-14 13:32:58 +01:00
John Ericson
ee9f6d022f Merge pull request #10001 from abathur/fix_macos_daemon_perms
install-darwin: fix symbolic perms for install cmd
2024-02-13 17:39:11 -05:00
John Ericson
ef0b82a267 Merge pull request #9986 from nbelakovski/patch-1
Add a note about lists values.md
2024-02-13 17:10:32 -05:00
Eelco Dolstra
2b946aad8e Merge pull request #10006 from edolstra/fix-impure-fetchurl
<nix/fetchurl.nix>: Restore support for "impure = true"
2024-02-13 22:50:55 +01:00
John Ericson
5b26c66a8c Merge pull request #10009 from obsidiansystems/ca-type-names
Proper `parse` and `render` functions for `FileIngestionMethod` and `ContentAddressMethod`
2024-02-13 13:28:52 -05:00
John Ericson
9d64613dca Update src/libutil/file-content-address.cc 2024-02-13 12:50:10 -05:00
Alois Wohlschlager
89e21ab4bd Restore builtins.pathExists behavior on broken symlinks
Commit 83c067c0fa changed `builtins.pathExists`
to resolve symlinks before checking for existence. Consequently, if the path
refers to a symlink itself, existence of the target of the symlink (instead of
the symlink itself) was checked. Restore the previous behavior by skipping
symlink resolution in the last component.
2024-02-13 18:09:55 +01:00
John Ericson
db41a0616a Use ContentAddressMethod::render in one more place
Good to deduplicate the code.
2024-02-13 11:14:49 -05:00
Eelco Dolstra
4254cdabf2 Merge pull request #10008 from edolstra/config-status-dep
*.in files: Depend on config.status
2024-02-13 16:49:41 +01:00
John Ericson
f49efb32fb Merge pull request #10005 from fricklerhandwerk/local-store-glossary
update glossary entry on store types
2024-02-13 10:45:36 -05:00
John Ericson
41dd9857c7 Proper parse and render functions for FileIngestionMethod and ContentAddressMethod
No outward facing behavior is changed.

Older methods with same names that operate on on method + algo pair (for
old-style `<method>:algo`) are renamed to `*WithAlgo`.)

The functions are unit-tested in the same way the names for the hash
algorithms are tested.
2024-02-13 10:30:16 -05:00
Valentin Gagarin
f27205f743 redirect local and chroot store to main page 2024-02-13 16:25:07 +01:00
John Ericson
fb5a438dca Merge pull request #10007 from NixOS/split-test
Split `hash.sh` test in two
2024-02-13 10:22:12 -05:00
Eelco Dolstra
bb21892351 *.in files: Depend on config.status 2024-02-13 16:10:56 +01:00
Valentin Gagarin
5b69409f6b only refer to the local store page 2024-02-13 16:08:44 +01:00
John Ericson
8bebf9607c Split hash.sh test in two
Converting hashes and hashing files are pretty separate tasks, and more
test parallelism is better.
2024-02-13 09:44:14 -05:00
Eelco Dolstra
b439b1fc66 Merge pull request #9993 from edolstra/builtin-paths
builtin:{unpack-channel,buildenv}: Get output path from the derivation
2024-02-13 14:57:13 +01:00
Valentin Gagarin
39c353f6fa reword description of the cores setting (#9522)
* reword description of the `cores` setting

- be precise about the `builder` executable
- clearly distinguish between `builder` and job parallelism
- clarify the role of `mkDerivation` in the example
- remove prose for the default, it's shown programmatically
- mention relation to `max-jobs`
2024-02-13 13:52:51 +00:00
Eelco Dolstra
bb63bd50e6 <nix/fetchurl.nix>: Restore support for "impure = true" 2024-02-13 14:14:20 +01:00
Valentin Gagarin
fd82ba0985 extract reference documentation on remote builds (#9526)
- move all reference documentation to the `builders` configuration setting
- reword documentation on machine specification, add examples
- disable showing the default value, as it rendered as `@/dummy/machines`, which is wrong
- highlight the examples
- link to the configuration docs for distributed builds
- builder -> build machine

Co-authored-by: Janik H <janik@aq0.de>
2024-02-13 14:13:56 +01:00
Valentin Gagarin
e37d502895 add instructions to wipe the substituter lookup cache (#9498)
* add instructions to wipe the substituter lookup cache
2024-02-13 13:34:51 +01:00
Valentin Gagarin
ce19338f9f update glossary entry on store types
the interesting information is on the proper pages, and is now presented
a bit more prominently.

the paragraph was a bit confusing to read, also because an anchor link
to an inline definition was in the middle of the sentence. "local store"
now has its own glossary entry.
2024-02-13 13:30:17 +01:00
Théophane Hufschmitt
9d87f25673 Merge pull request #9997 from pg83/patch-1
Update nar-info-disk-cache.cc
2024-02-13 08:55:57 +01:00
Travis A. Everett
64a076fe06 install-darwin: fix symbolic perms for install cmd
The symbolic form in use here doesn't seem to have an effect
in either the BSD or coreutils install commands, leaving the
daemon plist with empty permissions. This seems to cause its
own problems.

I think I've got the right symbolic syntax now :)
2024-02-13 01:18:08 -06:00
Anton Samokhvalov
64cbd4c05a Update nar-info-disk-cache.cc
fix case when asserts are no-op, like in release build
2024-02-12 23:37:40 +03:00
Robert Hensing
bdb6f56c90 Merge pull request #9295 from NixOS/store-path-complete-construction
Include store path exact spec in the docs
2024-02-12 19:02:03 +01:00
John Ericson
f29d2a9d11 Small EBNF fix 2024-02-12 12:30:28 -05:00
John Ericson
898fd1e48d Update doc/manual/src/protocols/store-path.md 2024-02-12 12:04:37 -05:00
John Ericson
5169f5f4d9 Apply suggestions from code review
Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
2024-02-12 12:01:54 -05:00
John Ericson
c873a140d7 Apply suggestions from code review
Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
2024-02-12 11:34:54 -05:00
John Ericson
4c3e4d6d71 Sections, EBNF tweaks 2024-02-12 11:33:06 -05:00
John Ericson
0862d7ce57 Move around non-terminals 2024-02-12 11:26:08 -05:00
John Ericson
eb76b35efa Merge remote-tracking branch 'upstream/master' into store-path-complete-construction 2024-02-12 11:22:54 -05:00
John Ericson
30f6b0f9c5 pre -> fingerprint in store path grammar
As suggested by @roberth in
https://github.com/NixOS/nix/pull/9295#discussion_r1486402040.

Thanks!
2024-02-12 11:22:08 -05:00
John Ericson
95190e68ed Mention the use of Extended Backus–Naur form 2024-02-12 11:21:54 -05:00
John Ericson
ac1301ddfd Convert store path "grammar" to EBNF 2024-02-12 11:16:53 -05:00
John Ericson
91557df4a7 Apply suggestions from code review
Co-authored-by: Robert Hensing <roberth@users.noreply.github.com>
2024-02-12 11:16:12 -05:00
Eelco Dolstra
a9b69b2fff builtin:{unpack-channel,buildenv}: Get output path from the derivation
Similar to 1ee42c5b88, get the "out"
path from the derivation (and complain if it doesn't exist), rather
than getting it from the environment.
2024-02-12 16:34:59 +01:00
Eelco Dolstra
c4ebb82da4 Merge pull request #9992 from edolstra/fix-warning
Fix "may be used uninitialized" warning
2024-02-12 16:02:21 +01:00
Eelco Dolstra
619ca631d0 Fix "may be used uninitialized" warning 2024-02-12 15:29:48 +01:00
Eelco Dolstra
d3c1997127 Merge pull request #9976 from alois31/restore-manual-pages
Restore manual pages
2024-02-12 15:21:47 +01:00
Théophane Hufschmitt
e352c66161 Merge pull request #9983 from BOHverkill/fix-string-interpolation-link
Fix link to derivation in string interpolation doc
2024-02-12 14:26:03 +01:00
Eelco Dolstra
ec6ca6e42c Merge pull request #9948 from obsidiansystems/no-canon-path-from-cwd
Get rid of `CanonPath::fromCwd`
2024-02-12 14:04:01 +01:00
nbelakovski
4496a4537b Update values.md
Link to elemAt
2024-02-11 22:52:49 -08:00
John Ericson
9d182e71fb Merge pull request #9984 from BOHverkill/fix-manual-link
Fix link to manual in CONTRIBUTING.md
2024-02-11 16:13:06 -05:00
nbelakovski
f298159a2b Add a note about lists values.md
There's probably more that can be said, but I thought it might be helpful to put something here about how to access elements of a list for folks coming from more or less any other programming language. If this is rarely used, it might be nice to add to the documentation something about why it's rarely used.
2024-02-10 18:52:39 -08:00
BOHverkill
fae8c15737 Fix link to manual in CONTRIBUTING.md 2024-02-10 17:44:33 +01:00
BOHverkill
53eecae525 Fix link to derivation in string interpolation doc
The reference link definition for it pointing to the glossary was
removed, so it is currently not displayed as a link.
2024-02-10 17:27:16 +01:00
Alois Wohlschlager
8f3253c6f4 Restore manual pages
Commit d536c57e87 inadvertedly broke build and
installation of all non-autogenerated manual pages (in particular, all the ones
documenting the stable CLI), by moving the definition of the man-pages variable
in doc/manual/local.mk after its usage in mk/lib.mk. Move including the former
earlier so that the correct order is restored.
2024-02-09 19:07:59 +01:00
Théophane Hufschmitt
28a988bdde Merge pull request #9974 from edolstra/less-chatty-post-build-hook
runPostBuildHook(): Be less chatty
2024-02-09 16:39:02 +01:00
Eelco Dolstra
fb5a792280 runPostBuildHook(): Be less chatty
Don't spam the user with "running post-build-hook" messages. It's up
to the post-build hook if it has something interesting to say.
2024-02-09 15:55:24 +01:00
Eelco Dolstra
4487ebca66 Merge pull request #9973 from fricklerhandwerk/add-links-gc-confusion
add clickable anchor links
2024-02-09 13:41:18 +01:00
Eelco Dolstra
57ebcadb2f Merge pull request #9972 from NixOS/allow-unoptimized-builds
Don't hardcode the `-O2` compiler flag
2024-02-09 13:25:51 +01:00
Valentin Gagarin
60045f9c96 add clickable anchor links
how the different invocations relate to each other seems be
confusing, which is relatable because one has to wire it up in your head
while reading. an explicit reference should make it unambiguous and
easier to notice due to links being highlighted.
2024-02-09 10:41:03 +01:00
Théophane Hufschmitt
1fe7b01669 Don't hardcode the -O2 compiler flag
autoconf authors apparently decided that setting `-O2` by default was a good idea. I disagree, and Nix has its own way of deciding that (with `OPTIMIZE={0,1}`). Explicitly set `CFLAGS` and `CXXFLAGS` in the configure script to disable that behaviour.

Fix #9965
2024-02-09 06:27:24 +01:00
Théophane Hufschmitt
47a1dbb4b8 Merge pull request #9925 from 9999years/fmt-cleanup
Cleanup `fmt.hh`
2024-02-09 06:01:03 +01:00
Rebecca Turner
953eb0cba2 Fix tests 2024-02-08 15:55:20 -08:00
Rebecca Turner
4440eb54e7 Add release note 2024-02-08 12:07:38 -08:00
Rebecca Turner
8b7eb7400b Enter debugger on builtins.trace with an option 2024-02-08 12:07:37 -08:00
Rebecca Turner
c0e7f50c1a Rename hintfmt to HintFmt 2024-02-08 11:58:25 -08:00
Rebecca Turner
149bd63afb Cleanup fmt.hh
When I started contributing to Nix, I found the mix of definitions and
names in `fmt.hh` to be rather confusing, especially the small
difference between `hintfmt` and `hintformat`. I've renamed many classes
and added documentation to most definitions.

- `formatHelper` is no longer exported.
- `fmt`'s documentation is now with `fmt` rather than (misleadingly)
  above `formatHelper`.
- `yellowtxt` is renamed to `Magenta`.

  `yellowtxt` wraps its value with `ANSI_WARNING`, but `ANSI_WARNING`
  has been equal to `ANSI_MAGENTA` for a long time. Now the name is
  updated.
- `normaltxt` is renamed to `Uncolored`.
- `hintfmt` has been merged into `hintformat` as extra constructor
  functions.
- `hintformat` has been renamed to `hintfmt`.
- The single-argument `hintformat(std::string)` constructor has been
  renamed to a static member `hintformat::interpolate` to avoid pitfalls
  with using user-generated strings as format strings.
2024-02-08 11:51:03 -08:00
Théophane Hufschmitt
1ba9780cf5 Merge pull request #9834 from 9999years/structured-errors
Towards structured error classes
2024-02-08 20:00:25 +01:00
Rebecca Turner
403c90ddf5 Extract printSpace helper 2024-02-08 10:18:12 -08:00
Rebecca Turner
1c5f5d4291 prettyPrint -> shouldPrettyPrint 2024-02-08 10:18:10 -08:00
Rebecca Turner
a27651908f Add assertion for decreasing the indent
Co-authored-by: Théophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com>
2024-02-08 10:11:45 -08:00
Rebecca Turner
0f1269243b Revert "Add release notes for "Stack traces are more compact""
This reverts commit b2868acbdc.
2024-02-08 10:09:47 -08:00
Rebecca Turner
b2868acbdc Add release notes for "Stack traces are more compact" 2024-02-08 10:08:48 -08:00
Rebecca Turner
7f8960d0f2 Add release notes for "Stack size is increased on macOS" 2024-02-08 10:08:48 -08:00
Rebecca Turner
837c350bcd Add release notes for "Cycle detection in nix repl is simpler and more reliable" 2024-02-08 10:08:48 -08:00
Rebecca Turner
4f0d43a397 Add release notes for "nix repl now respects Ctrl-C while printing values" 2024-02-08 10:08:48 -08:00
Rebecca Turner
24cdb81bb0 Add release notes for "Nix commands respect Ctrl-C" 2024-02-08 10:08:48 -08:00
Rebecca Turner
abb5fef355 Add release notes for "Functions are printed with more detail" 2024-02-08 10:08:48 -08:00
Rebecca Turner
9a5d52262f Add release notes for "Nix no longer attempts to git add files that are .gitignored" 2024-02-08 10:08:48 -08:00
Rebecca Turner
754c35abfb Add release notes for "Debugger prints source position information" 2024-02-08 10:08:48 -08:00
John Ericson
4687beecef Get rid of CanonPath::fromCwd
As discussed in the last Nix team meeting (2024-02-95), this method
doesn't belong because `CanonPath` is a virtual/ideal absolute path
format, not used in file systems beyond the native OS format for which a
"current working directory" is defined.

Progress towards #9205
2024-02-08 11:01:41 -05:00
Eelco Dolstra
6563a58294 Merge pull request #9964 from fricklerhandwerk/merge-format-sections
move JSON section into Formats and Protocols
2024-02-08 16:53:59 +01:00
Théophane Hufschmitt
a8050d9b83 Merge pull request #9928 from 9999years/error-messages-in-nix-repl
Improve error printing in `nix repl`
2024-02-08 16:21:13 +01:00
Théophane Hufschmitt
96686a2a29 Merge pull request #9963 from fricklerhandwerk/fold-sidebar
manual: fold sidebar sections
2024-02-08 16:18:27 +01:00
John Ericson
f2f54cf087 Merge pull request #9863 from obsidiansystems/perl-open-other-store
Support arbitrary stores in Perl bindings
2024-02-08 09:30:18 -05:00
Théophane Hufschmitt
46a0625a40 Merge pull request #9929 from 9999years/dont-print-values-in-magenta
Don't print the first bracket in values in magenta in error messages
2024-02-08 10:56:18 +01:00
Théophane Hufschmitt
9b8b486091 Merge pull request #9933 from pennae/debugger-fix
fix debugger crashing while printing envs
2024-02-08 10:48:02 +01:00
Théophane Hufschmitt
acef4f17a2 Merge pull request #9918 from 9999years/debugger-locals-for-let-expressions
Expose locals from `let` expressions to the debugger
2024-02-08 10:17:55 +01:00
Théophane Hufschmitt
c4ed92fa6f Merge pull request #9917 from 9999years/enter-debugger-more-reliably
Enter debugger more reliably in `let` expressions and function calls
2024-02-08 10:09:54 +01:00
Théophane Hufschmitt
fb78a99e04 Merge pull request #9924 from 9999years/rename-yellowtxt
Rename `yellowtxt` -> `magentatxt`
2024-02-08 10:01:30 +01:00
Théophane Hufschmitt
0dea16e686 Mention OPTIMIZE=0 more prominently in the hacking guide
This is a game-changer when developing, it shouldn't just be hidden amongst a list of more advanced variables.
2024-02-08 09:50:12 +01:00
Théophane Hufschmitt
f388a6148d Merge pull request #9919 from 9999years/reduce-debugger-clutter
Reduce visual clutter in the debugger
2024-02-08 09:42:38 +01:00
Théophane Hufschmitt
d24c8aa491 Simplify a conditional in the repl initialisation 2024-02-08 09:22:30 +01:00
Valentin Gagarin
e486b76eef move JSON section into Formats and Protocols 2024-02-08 09:13:58 +01:00
Valentin Gagarin
140de3b278 manual: fold sidebar sections
the table of contents is very long now, and folded sections allow
for a better overview.
2024-02-08 09:00:00 +01:00
John Ericson
bc08502249 Support arbitrary stores in Perl bindings
Fix #9859

It's a breaking change but that's fine; we can just update Hydra to use
the new bindings.
2024-02-07 22:31:58 -05:00
Eelco Dolstra
09a6e8e703 Merge pull request #9949 from fricklerhandwerk/fix-redirects-file
fix location of `_redirects` file
2024-02-07 11:34:35 +01:00
Rebecca Turner
9723f533d8 Add comment 2024-02-06 16:50:47 -08:00
Rebecca Turner
474fc4078a Add comments 2024-02-06 16:49:28 -08:00
Valentin Gagarin
2d74b56aee fix location of _redirects file
the Netlify `_redirects` file must be in the root directory [0] of the
files to serve, and mdBook copies all the files in `src` that aren't
`.md` to the output directory [1].

[0]: https://docs.netlify.com/routing/redirects/
[1]: https://rust-lang.github.io/mdBook/guide/creating.html#source-files
2024-02-06 23:22:34 +01:00
Rebecca Turner
c0a15fb7d0 Pretty-print values in the REPL
Pretty-print values in the REPL by printing each item in a list or
attrset on a separate line. When possible, single-item lists and
attrsets are printed on one line, as long as they don't contain a nested
list, attrset, or thunk.

Before:
```
{ attrs = { a = { b = { c = { }; }; }; }; list = [ 1 ]; list' = [ 1 2 3 ]; }
```

After:
```
{
  attrs = {
    a = {
      b = {
        c = { };
      };
    };
  };
  list = [ 1 ];
  list' = [
    1
    2
    3
  ];
}
```
2024-02-05 13:23:38 -08:00
Rebecca Turner
657a607812 Add release note 2024-02-05 13:21:08 -08:00
Rebecca Turner
155bc761f6 Add release note 2024-02-05 13:16:39 -08:00
Rebecca Turner
b63a8d7c46 Add release note 2024-02-05 13:15:29 -08:00
Rebecca Turner
601fc7d159 Add release note 2024-02-05 13:13:26 -08:00
Robert Hensing
a31f2cb0cd Merge pull request #9939 from edolstra/slash-operator
CanonPath, SourcePath: Change operator + to /
2024-02-05 15:55:27 +01:00
Eelco Dolstra
a6737b7e17 CanonPath, SourcePath: Change operator + to /
This is less confusing and makes it more similar to std::filesystem::path.
2024-02-05 15:17:39 +01:00
Eelco Dolstra
c291d2d8dd Merge pull request #9927 from 9999years/catch-error-in-value-printer
Catch `Error`, not `BaseError` in `ValuePrinter`
2024-02-05 15:01:21 +01:00
Eelco Dolstra
6ec08b85f6 Merge pull request #9934 from nmeum/absPath-out-of-bounds
absPath: Explicitly check if path is empty before accessing it
2024-02-05 14:44:02 +01:00
Eelco Dolstra
e190c20c33 Merge pull request #9926 from 9999years/fix-cycle-detection-in-nix-repl
Fix cycle detection in `nix repl`
2024-02-05 14:24:28 +01:00
Valentin Gagarin
8d4890c3f8 catch multiple use of link reference (#9937) 2024-02-04 21:45:10 +00:00
Valentin Gagarin
8b873edcca fix anchor link; less weird link texts (#9936) 2024-02-04 21:15:20 +00:00
Valentin Gagarin
721fddac2f use the right heading level (#9935) 2024-02-04 21:03:13 +00:00
Théophane Hufschmitt
25385a408e Merge pull request #9930 from rvl/print-dev-env-unbound-variables
print-dev-env: Avoid using unbound shellHook variable
2024-02-04 20:55:17 +01:00
pennae
5ccb06ee1b fix debugger crashing while printing envs
fixes #9932
2024-02-04 17:12:04 +01:00
Rodney Lorrimar
e1131b5927 print-dev-env: Avoid using unbound shellHook variable
Some tools which consume the "nix print-dev-env" rc script (such as
"nix-direnv") are sensitive to the use of unbound variables. They use
"set -u".

The "nix print-dev-env" rc script initially unsets "shellHook", then
loads variables from the derivation, and then evaluates "shellHook".
However, most derivations don't have a "shellHook" attribute.

So users get the error "shellHook: unbound variable". This can be
demonstrated with the command:

    nix print-dev-env nixpkgs#hello | bash -u

This commit changes the rc script to provide an empty fallback value
for the "shellHook" variable.

Closes: #7951 #8253
2024-02-04 13:57:13 +08:00
Rebecca Turner
770d2bc779 Key repeated values on attribute binding pointers, not value pointers
Closes #8672
2024-02-03 21:23:06 -08:00
Rebecca Turner
9646d62b0c Don't print values in magenta
This fixes the opening bracket of lists/attrsets being printed in
magenta, unlike the closing bracket.

https://github.com/NixOS/nix/pull/9753#issuecomment-1904616088
2024-02-03 21:17:22 -08:00
Rebecca Turner
c5d525cd84 Print error messages but not traces
This makes output of values that include errors much cleaner.

Before:
```
nix-repl> { err = builtins.throw "uh oh!"; }
{ err = «error:
       … while calling the 'throw' builtin
         at «string»:1:9:
            1| { err = builtins.throw "uh oh!"; }
             |         ^

       error: uh oh!»; }
```

After:
```
nix-repl> { err = builtins.throw "uh oh!"; }
{ err = «error: uh oh!»; }
```

But if the whole expression throws an error, source locations and (if
applicable) a stack trace are printed, like you'd expect:

```
nix-repl> builtins.throw "uh oh!"
error:
       … while calling the 'throw' builtin
         at «string»:1:1:
            1| builtins.throw "uh oh!"
             | ^

       error: uh oh!
```
2024-02-03 20:50:16 -08:00
Rebecca Turner
a7927abdc1 Catch Error, not BaseError in ValuePrinter
`BaseError` includes `Interrupt`. We probably don't want the value
printer to tell you you pressed Ctrl-C while it was working.
2024-02-03 19:29:07 -08:00
Rebecca Turner
a7939a6c2a Rename yellowtxt -> magentatxt
`yellowtxt` wraps its value with `ANSI_WARNING`, but `ANSI_WARNING` has
been equal to `ANSI_MAGENTA` for a long time. Now the name is updated.
2024-02-03 19:28:11 -08:00
Sören Tempel
ec5cc1026d absPath: Explicitly check if path is empty before accessing it
It is entirely possible for the path to be an empty string and many
unit tests actually pass it as an empty string (e.g. both_roundrip or
turnsEmptyPathIntoCWD). In this case, without this patch, absPath will
perform a one-byte out-of-bounds access.

This was discovered while enabling the nix test suite on Alpine where
we compile all software with `-D_GLIBCXX_ASSERTIONS=1`, thus resulting
in a test failure on Alpine.
2024-02-04 00:47:47 +01:00
Eelco Dolstra
49cf090cb2 Merge pull request #9913 from 9999years/debugger-positions
Print positions in `--debugger`, instead of pointers
2024-02-03 09:27:09 +01:00
Rebecca Turner
6414cd259e Reduce visual clutter in the debugger 2024-02-02 19:58:35 -08:00
Rebecca Turner
36dfac7560 Expose locals from let expressions to the debugger 2024-02-02 19:31:18 -08:00
Rebecca Turner
0127d54d5e Enter debugger more reliably in let expressions and calls 2024-02-02 19:14:22 -08:00
Rebecca Turner
7d7483cafc Print positions in --debugger, instead of pointers 2024-02-02 17:38:46 -08:00
Eelco Dolstra
081dc5daa1 Merge pull request #9902 from NixOS/require-fixed-output-fetchurl
builtin:fetchurl: Ensure a fixed-output derivation
2024-02-02 14:00:34 +01:00
Eelco Dolstra
e67458e5b8 Better test fix 2024-02-02 13:34:56 +01:00
Eelco Dolstra
05535be03a Fix test 2024-02-02 13:07:08 +01:00
Rebecca Turner
faaccecbc8 Remove EXCEPTION_NEEDS_THROW_SPEC
We're on C++ 20 now, we don't need this
2024-02-01 16:39:38 -08:00
Rebecca Turner
87dc4bc7d1 Attach positions to errors in derivationStrict 2024-02-01 16:39:38 -08:00
Rebecca Turner
c6a89c1a16 libexpr: Support structured error classes
While preparing PRs like #9753, I've had to change error messages in
dozens of code paths. It would be nice if instead of

    EvalError("expected 'boolean' but found '%1%'", showType(v))

we could write

    TypeError(v, "boolean")

or similar. Then, changing the error message could be a mechanical
refactor with the compiler pointing out places the constructor needs to
be changed, rather than the error-prone process of grepping through the
codebase. Structured errors would also help prevent the "same" error
from having multiple slightly different messages, and could be a first
step towards error codes / an error index.

This PR reworks the exception infrastructure in `libexpr` to
support exception types with different constructor signatures than
`BaseError`. Actually refactoring the exceptions to use structured data
will come in a future PR (this one is big enough already, as it has to
touch every exception in `libexpr`).

The core design is in `eval-error.hh`. Generally, errors like this:

    state.error("'%s' is not a string", getAttrPathStr())
      .debugThrow<TypeError>()

are transformed like this:

    state.error<TypeError>("'%s' is not a string", getAttrPathStr())
      .debugThrow()

The type annotation has moved from `ErrorBuilder::debugThrow` to
`EvalState::error`.
2024-02-01 16:39:38 -08:00
Rebecca Turner
c62c21e29a Move PodIdx to pos-idx.hh and PosTable to pos-table.hh 2024-02-01 13:12:59 -08:00
Eelco Dolstra
b8b739e484 builtin:fetchurl: Get output hash info from the drv 2024-02-01 22:01:02 +01:00
Eelco Dolstra
1ee42c5b88 builtin:fetchurl: Ensure a fixed-output derivation
Previously we didn't check that the derivation was fixed-output, so
you could use builtin:fetchurl to impurely fetch a file.
2024-02-01 21:46:01 +01:00
Eelco Dolstra
ef6d055ace Merge pull request #9896 from hercules-ci/tidy-tidy
Disable a slightly annoying clang-tidy check
2024-02-01 09:02:46 +01:00
Robert Hensing
58c26dd0f0 Add .clang-tidy 2024-02-01 01:01:39 +01:00
Robert Hensing
0f2e9e6bd2 Typo 2024-02-01 01:01:04 +01:00
Robert Hensing
4072a8fea0 Merge pull request #9867 from hercules-ci/issue-912
#912 allow leading period
2024-01-31 19:10:59 +01:00
Robert Hensing
7c9ed1b1a3 Merge pull request #9890 from obsidiansystems/remove-unneeded-toRealPath
Change an `allowPath` call to take a store path again
2024-01-31 18:45:19 +01:00
Robert Hensing
f1b4663805 Disallow store path names that are . or .. (plus opt. -)
As discussed in the maintainer meeting on 2024-01-29.

Mainly this is to avoid a situation where the name is parsed and
treated as a file name, mostly to protect users.
.-* and ..-* are also considered invalid because they might strip
on that separator to remove versions. Doesn't really work, but that's
what we decided, and I won't argue with it, because .-* probably
doesn't seem to have a real world application anyway.
We do still permit a 1-character name that's just "-", which still
poses a similar risk in such a situation. We can't start disallowing
trailing -, because a non-zero number of users will need it and we've
seen how annoying and painful such a change is.

What matters most is preventing a situation where . or .. can be
injected, and to just get this done.
2024-01-31 18:35:19 +01:00
Robert Hensing
8406da2877 test: Generate distinct hashes
Gen::just is the constant generator. Don't just return that!
2024-01-31 18:35:19 +01:00
Robert Hensing
69bbd5852a test: Generate distinct path names
Gen::just is the constant generator. Don't just return that!
2024-01-31 18:35:19 +01:00
Robert Hensing
b13e6a76b4 parseStorePath: Support leading period 2024-01-31 18:35:19 +01:00
John Ericson
caea7dcb7e Change an allowPath call to take a store path again
This looks like a revert of #5844, but is not.

That one was needed because
d90f9d4b99 (diff-0f59bb6f197822ef9f19ceae9624989499d170c84dfdc1f486a8959bb4588cafR85)
changed the type of the argument to `allowPath` from a `StorePath` to a
`Path`. But since
caabc4f648 (diff-0f59bb6f197822ef9f19ceae9624989499d170c84dfdc1f486a8959bb4588cafL100-R92),
it is a `StorePath` again.

I think this is worth changing because we want to be very careful about
`toRealPath` and the evaluator --- ideally the choice of real path does
not affect evaluation at all. So using it fewer times is better.
2024-01-31 11:44:06 -05:00
John Ericson
75ebb90a70 Merge pull request #9884 from edolstra/resolve-symlinks
Resolve symlinks in a few more places
2024-01-30 10:42:54 -05:00
Eelco Dolstra
d661a89398 Merge pull request #9880 from NixOS/dependabot/github_actions/zeebe-io/backport-action-2.4.1
build(deps): bump zeebe-io/backport-action from 2.4.0 to 2.4.1
2024-01-30 15:37:25 +01:00
Eelco Dolstra
b36ff47e7c Resolve symlinks in a few more places
Fixes #9882.
2024-01-30 15:35:31 +01:00
Eelco Dolstra
a7db7b6e44 Merge pull request #9879 from NixOS/bump-2.21.0
Bump version
2024-01-30 10:13:42 +01:00
dependabot[bot]
a3aae7beef build(deps): bump zeebe-io/backport-action from 2.4.0 to 2.4.1
Bumps [zeebe-io/backport-action](https://github.com/zeebe-io/backport-action) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/zeebe-io/backport-action/releases)
- [Commits](https://github.com/zeebe-io/backport-action/compare/v2.4.0...v2.4.1)

---
updated-dependencies:
- dependency-name: zeebe-io/backport-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-01-29 22:14:10 +00:00
Eelco Dolstra
2f3fb6c12e Bump version 2024-01-29 22:57:25 +01:00
Robert Hensing
9ddd0f2af8 Revert "StorePath: reject names starting with '.'"
This reverts commit 24bda0c7b3.
2024-01-27 11:18:03 +01:00
John Ericson
a34ec0bd12 Include store path exact spec in the docs
This is niche, but deserves to be in the manual because it is describing
behavior visible to the outside world, not mere implementation details.
2024-01-18 22:19:14 -05:00
Eelco Dolstra
cf02b3335c Merge remote-tracking branch 'origin/master' into tarball-cache 2023-12-22 16:45:01 +01:00
John Ericson
e7d95a1d80 Merge remote-tracking branch 'upstream/master' into tarball-cache 2023-12-15 09:37:13 -05:00
Eelco Dolstra
06e106beff Disable GitHub tree hash mismatch warning 2023-12-14 13:38:10 +01:00
Eelco Dolstra
1fce12ec32 Merge remote-tracking branch 'origin/master' into tarball-cache 2023-12-14 13:31:29 +01:00
Eelco Dolstra
043413bb59 boost::span -> std::span 2023-11-29 12:38:46 +01:00
Eelco Dolstra
b36857ac8d Add a Git-based content-addressed tarball cache
GitArchiveInputScheme now streams tarballs into a Git repository. This
deduplicates data a lot, e.g. when you're fetching different revisions
of the Nixpkgs repo. It also warns if the tree hash returned by GitHub
doesn't match the tree hash of the imported tarball.
2023-11-29 12:37:03 +01:00
251 changed files with 5729 additions and 2822 deletions

3
.clang-tidy Normal file
View File

@@ -0,0 +1,3 @@
# We use pointers to aggregates in a couple of places, intentionally.
# void * would look weird.
Checks: '-bugprone-sizeof-expression'

View File

@@ -21,7 +21,7 @@ jobs:
fetch-depth: 0
- name: Create backport PRs
# should be kept in sync with `version`
uses: zeebe-io/backport-action@v2.4.0
uses: zeebe-io/backport-action@v2.4.1
with:
# Config README: https://github.com/zeebe-io/backport-action#backport-action
github_token: ${{ secrets.GITHUB_TOKEN }}

4
.gitignore vendored
View File

@@ -94,6 +94,7 @@ perl/Makefile.config
/tests/functional/ca/config.nix
/tests/functional/dyn-drv/config.nix
/tests/functional/repl-result-out
/tests/functional/debugger-test-out
/tests/functional/test-libstoreconsumer/test-libstoreconsumer
# /tests/functional/lang/
@@ -107,6 +108,9 @@ perl/Makefile.config
/misc/systemd/nix-daemon.service
/misc/systemd/nix-daemon.socket
/misc/systemd/nix-gc-trace.service
/misc/systemd/nix-gc-trace.socket
/misc/systemd/nix-daemon.conf
/misc/upstart/nix-daemon.conf

View File

@@ -1 +1 @@
2.20.0
2.21.0

View File

@@ -63,7 +63,7 @@ Check out the [security policy](https://github.com/NixOS/nix/security/policy).
- Functional tests [`tests/functional/**.sh`](./tests/functional)
- Unit tests [`src/*/tests`](./src/)
- Integration tests [`tests/nixos/*`](./tests/nixos)
- [ ] User documentation in the [manual](..doc/manual/src)
- [ ] User documentation in the [manual](./doc/manual/src)
- [ ] API documentation in header files
- [ ] Code and comments are self-explanatory
- [ ] Commit message explains **why** the change was made

View File

@@ -12,6 +12,7 @@ makefiles = \
mk/precompiled-headers.mk \
local.mk \
src/libutil/local.mk \
src/nix-find-roots/local.mk \
src/libstore/local.mk \
src/libfetchers/local.mk \
src/libmain/local.mk \
@@ -41,12 +42,24 @@ endif
ifeq ($(ENABLE_FUNCTIONAL_TESTS), yes)
makefiles += \
tests/functional/local.mk \
tests/functional/gc-external-daemon/local.mk \
tests/functional/ca/local.mk \
tests/functional/dyn-drv/local.mk \
tests/functional/test-libstoreconsumer/local.mk \
tests/functional/plugins/local.mk
endif
# Some makefiles require access to built programs and must be included late.
makefiles-late =
ifeq ($(ENABLE_DOC_GEN), yes)
makefiles-late += doc/manual/local.mk
endif
ifeq ($(ENABLE_INTERNAL_API_DOCS), yes)
makefiles-late += doc/internal-api/local.mk
endif
# Miscellaneous global Flags
OPTIMIZE = 1
@@ -95,24 +108,16 @@ installcheck:
@exit 1
endif
# Documentation or else fallback stub rules.
#
# The documentation makefiles be included after `mk/lib.mk` so rules
# refer to variables defined by `mk/lib.mk`. Rules are not "lazy" like
# variables, unfortunately.
# Documentation fallback stub rules.
ifeq ($(ENABLE_DOC_GEN), yes)
$(eval $(call include-sub-makefile, doc/manual/local.mk))
else
ifneq ($(ENABLE_DOC_GEN), yes)
.PHONY: manual-html manpages
manual-html manpages:
@echo "Generated docs are disabled. Configure without '--disable-doc-gen', or avoid calling 'make manpages' and 'make manual-html'."
@exit 1
endif
ifeq ($(ENABLE_INTERNAL_API_DOCS), yes)
$(eval $(call include-sub-makefile, doc/internal-api/local.mk))
else
ifneq ($(ENABLE_INTERNAL_API_DOCS), yes)
.PHONY: internal-api-html
internal-api-html:
@echo "Internal API docs are disabled. Configure with '--enable-internal-api-docs', or avoid calling 'make internal-api-html'."

View File

@@ -47,6 +47,10 @@ AC_DEFINE_UNQUOTED(SYSTEM, ["$system"], [platform identifier ('cpu-os')])
# State should be stored in /nix/var, unless the user overrides it explicitly.
test "$localstatedir" = '${prefix}/var' && localstatedir=/nix/var
# Assign a default value to C{,XX}FLAGS as the default configure script sets them
# to -O2 otherwise, which we don't want to have hardcoded
CFLAGS=${CFLAGS-""}
CXXFLAGS=${CXXFLAGS-""}
AC_PROG_CC
AC_PROG_CXX

View File

@@ -6,6 +6,8 @@ additional-css = ["custom.css"]
additional-js = ["redirects.js"]
edit-url-template = "https://github.com/NixOS/nix/tree/master/doc/manual/{path}"
git-repository-url = "https://github.com/NixOS/nix"
fold.enable = true
fold.level = 1
[preprocessor.anchors]
renderers = ["html"]

View File

@@ -18,7 +18,7 @@ const redirects = {
"chap-tuning-cores-and-jobs": "advanced-topics/cores-vs-jobs.html",
"chap-diff-hook": "advanced-topics/diff-hook.html",
"check-dirs-are-unregistered": "advanced-topics/diff-hook.html#check-dirs-are-unregistered",
"chap-distributed-builds": "advanced-topics/distributed-builds.html",
"chap-distributed-builds": "command-ref/conf-file.html#conf-builders",
"chap-post-build-hook": "advanced-topics/post-build-hook.html",
"chap-post-build-hook-caveats": "advanced-topics/post-build-hook.html#implementation-caveats",
"chap-writing-nix-expressions": "language/index.html",
@@ -358,7 +358,11 @@ const redirects = {
"one-time-setup": "testing.html#one-time-setup",
"using-the-ci-generated-installer-for-manual-testing": "testing.html#using-the-ci-generated-installer-for-manual-testing",
"characterization-testing": "#characterisation-testing-unit",
}
},
"glossary.html": {
"gloss-local-store": "store/types/local-store.html",
"gloss-chroot-store": "store/types/local-store.html",
},
};
// the following code matches the current page's URL against the set of redirects.

View File

@@ -0,0 +1,40 @@
---
synopsis: Concise error printing in `nix repl`
prs: 9928
---
Previously, if an element of a list or attribute set threw an error while
evaluating, `nix repl` would print the entire error (including source location
information) inline. This output was clumsy and difficult to parse:
```
nix-repl> { err = builtins.throw "uh oh!"; }
{ err = «error:
… while calling the 'throw' builtin
at «string»:1:9:
1| { err = builtins.throw "uh oh!"; }
| ^
error: uh oh!»; }
```
Now, only the error message is displayed, making the output much more readable.
```
nix-repl> { err = builtins.throw "uh oh!"; }
{ err = «error: uh oh!»; }
```
However, if the whole expression being evaluated throws an error, source
locations and (if applicable) a stack trace are printed, just like you'd expect:
```
nix-repl> builtins.throw "uh oh!"
error:
… while calling the 'throw' builtin
at «string»:1:1:
1| builtins.throw "uh oh!"
| ^
error: uh oh!
```

View File

@@ -0,0 +1,9 @@
---
synopsis: "`--debugger` can now access bindings from `let` expressions"
prs: 9918
issues: 8827.
---
Breakpoints and errors in the bindings of a `let` expression can now access
those bindings in the debugger. Previously, only the body of `let` expressions
could access those bindings.

View File

@@ -0,0 +1,9 @@
---
synopsis: Enter the `--debugger` when `builtins.trace` is called if `debugger-on-trace` is set
prs: 9914
---
If the `debugger-on-trace` option is set and `--debugger` is given,
`builtins.trace` calls will behave similarly to `builtins.break` and will enter
the debug REPL. This is useful for determining where warnings are being emitted
from.

View File

@@ -0,0 +1,25 @@
---
synopsis: Debugger prints source position information
prs: 9913
---
The `--debugger` now prints source location information, instead of the
pointers of source location information. Before:
```
nix-repl> :bt
0: while evaluating the attribute 'python311.pythonForBuild.pkgs'
0x600001522598
```
After:
```
0: while evaluating the attribute 'python311.pythonForBuild.pkgs'
/nix/store/hg65h51xnp74ikahns9hyf3py5mlbbqq-source/overrides/default.nix:132:27
131|
132| bootstrappingBase = pkgs.${self.python.pythonAttr}.pythonForBuild.pkgs;
| ^
133| in
```

View File

@@ -0,0 +1,25 @@
---
synopsis: The `--debugger` will start more reliably in `let` expressions and function calls
prs: 9917
issues: 6649
---
Previously, if you attempted to evaluate this file with the debugger:
```nix
let
a = builtins.trace "before inner break" (
builtins.break "hello"
);
b = builtins.trace "before outer break" (
builtins.break a
);
in
b
```
Nix would correctly enter the debugger at `builtins.break a`, but if you asked
it to `:continue`, it would skip over the `builtins.break "hello"` expression
entirely.
Now, Nix will correctly enter the debugger at both breakpoints.

View File

@@ -0,0 +1,50 @@
---
synopsis: Functions are printed with more detail
prs: 9606
issues: 7145
---
Functions and `builtins` are printed with more detail in `nix repl`, `nix
eval`, `builtins.trace`, and most other places values are printed.
Before:
```
$ nix repl nixpkgs
nix-repl> builtins.map
«primop»
nix-repl> builtins.map lib.id
«primop-app»
nix-repl> builtins.trace lib.id "my-value"
trace: <LAMBDA>
"my-value"
$ nix eval --file functions.nix
{ id = <LAMBDA>; primop = <PRIMOP>; primop-app = <PRIMOP-APP>; }
```
After:
```
$ nix repl nixpkgs
nix-repl> builtins.map
«primop map»
nix-repl> builtins.map lib.id
«partially applied primop map»
nix-repl> builtins.trace lib.id "my-value"
trace: «lambda id @ /nix/store/8rrzq23h2zq7sv5l2vhw44kls5w0f654-source/lib/trivial.nix:26:5»
"my-value"
$ nix eval --file functions.nix
{ id = «lambda id @ /Users/wiggles/nix/functions.nix:2:8»; primop = «primop map»; primop-app = «partially applied primop map»; }
```
This was actually released in Nix 2.20, but wasn't added to the release notes
so we're announcing it here. The historical release notes have been updated as well.
[type-error]: https://github.com/NixOS/nix/pull/9753
[coercion-error]: https://github.com/NixOS/nix/pull/9754

View File

@@ -0,0 +1,10 @@
---
synopsis: Store paths are allowed to start with `.`
issues: 912
prs: 9867 9091 9095 9120 9121 9122 9130 9219 9224
---
Leading periods were allowed by accident in Nix 2.4. The Nix team has considered this to be a bug, but this behavior has since been relied on by users, leading to unnecessary difficulties.
From now on, leading periods are officially, definitively supported. The names `.` and `..` are disallowed, as well as those starting with `.-` or `..-`.
Nix versions that denied leading periods are documented [in the issue](https://github.com/NixOS/nix/issues/912#issuecomment-1919583286).

View File

@@ -0,0 +1,13 @@
---
synopsis: Nix commands respect Ctrl-C
prs: 9687 6995
issues: 7245
---
Previously, many Nix commands would hang indefinitely if Ctrl-C was pressed
while performing various operations (including `nix develop`, `nix flake
update`, and so on). With several fixes to Nix's signal handlers, Nix commands
will now exit quickly after Ctrl-C is pressed.
This was actually released in Nix 2.20, but wasn't added to the release notes
so we're announcing it here. The historical release notes have been updated as well.

View File

@@ -0,0 +1,24 @@
---
synopsis: "`nix repl` pretty-prints values"
prs: 9931
---
`nix repl` will now pretty-print values:
```
{
attrs = {
a = {
b = {
c = { };
};
};
};
list = [ 1 ];
list' = [
1
2
3
];
}
```

View File

@@ -0,0 +1,37 @@
---
synopsis: "Visual clutter in `--debugger` is reduced"
prs: 9919
---
Before:
```
info: breakpoint reached
Starting REPL to allow you to inspect the current state of the evaluator.
Welcome to Nix 2.20.0pre20231222_dirty. Type :? for help.
nix-repl> :continue
error: uh oh
Starting REPL to allow you to inspect the current state of the evaluator.
Welcome to Nix 2.20.0pre20231222_dirty. Type :? for help.
nix-repl>
```
After:
```
info: breakpoint reached
Nix 2.20.0pre20231222_dirty debugger
Type :? for help.
nix-repl> :continue
error: uh oh
nix-repl>
```

View File

@@ -0,0 +1,8 @@
---
synopsis: "`nix repl` now respects Ctrl-C while printing values"
prs: 9927
---
`nix repl` will now halt immediately when Ctrl-C is pressed while it's printing
a value. This is useful if you got curious about what would happen if you
printed all of Nixpkgs.

View File

@@ -0,0 +1,22 @@
---
synopsis: Cycle detection in `nix repl` is simpler and more reliable
prs: 9926
issues: 8672
---
The cycle detection in `nix repl`, `nix eval`, `builtins.trace`, and everywhere
else values are printed is now simpler and matches the cycle detection in
`nix-instantiate --eval` output.
Before:
```
nix eval --expr 'let self = { inherit self; }; in self'
{ self = { self = «repeated»; }; }
```
After:
```
{ self = «repeated»; }
```

View File

@@ -0,0 +1,9 @@
---
synopsis: Stack size is increased on macOS
prs: 9860
---
Previously, Nix would set the stack size to 64MiB on Linux, but would leave the
stack size set to the default (approximately 8KiB) on macOS. Now, the stack
size is correctly set to 64MiB on macOS as well, which should reduce stack
overflow segfaults in deeply-recursive Nix expressions.

View File

@@ -104,11 +104,12 @@
- [Channels](command-ref/files/channels.md)
- [Default Nix expression](command-ref/files/default-nix-expression.md)
- [Architecture and Design](architecture/architecture.md)
- [JSON Formats](json/index.md)
- [Store Object Info](json/store-object-info.md)
- [Derivation](json/derivation.md)
- [Protocols](protocols/index.md)
- [Formats and Protocols](protocols/index.md)
- [JSON Formats](protocols/json/index.md)
- [Store Object Info](protocols/json/store-object-info.md)
- [Derivation](protocols/json/derivation.md)
- [Serving Tarball Flakes](protocols/tarball-fetcher.md)
- [Store Path Specification](protocols/store-path.md)
- [Derivation "ATerm" file format](protocols/derivation-aterm.md)
- [Glossary](glossary.md)
- [Contributing](contributing/index.md)

View File

@@ -36,5 +36,6 @@
/package-management/s3-substituter /store/types/s3-binary-cache-store 301!
/protocols/protocols /protocols 301!
/json/* /protocols/json/:splat 301!
/release-notes/release-notes /release-notes 301!

View File

@@ -36,16 +36,8 @@ error: cannot connect to 'mac'
then you need to ensure that the `PATH` of non-interactive login shells
contains Nix.
> **Warning**
>
> If you are building via the Nix daemon, it is the Nix daemon user account (that is, `root`) that should have SSH access to a user (not necessarily `root`) on the remote machine.
>
> If you cant or dont want to configure `root` to be able to access the remote machine, you can use a private Nix store instead by passing e.g. `--store ~/my-nix` when running a Nix command from the local machine.
The list of remote machines can be specified on the command line or in
the Nix configuration file. The former is convenient for testing. For
example, the following command allows you to build a derivation for
`x86_64-darwin` on a Linux machine:
The [list of remote build machines](@docroot@/command-ref/conf-file.md#conf-builders) can be specified on the command line or in the Nix configuration file.
For example, the following command allows you to build a derivation for `x86_64-darwin` on a Linux machine:
```console
$ uname
@@ -60,97 +52,20 @@ $ cat ./result
Darwin
```
It is possible to specify multiple builders separated by a semicolon or
a newline, e.g.
It is possible to specify multiple build machines separated by a semicolon or a newline, e.g.
```console
--builders 'ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd'
```
Each machine specification consists of the following elements, separated
by spaces. Only the first element is required. To leave a field at its
default, set it to `-`.
1. The URI of the remote store in the format
`ssh://[username@]hostname`, e.g. `ssh://nix@mac` or `ssh://mac`.
For backward compatibility, `ssh://` may be omitted. The hostname
may be an alias defined in your `~/.ssh/config`.
2. A comma-separated list of Nix platform type identifiers, such as
`x86_64-darwin`. It is possible for a machine to support multiple
platform types, e.g., `i686-linux,x86_64-linux`. If omitted, this
defaults to the local platform type.
3. The SSH identity file to be used to log in to the remote machine. If
omitted, SSH will use its regular identities.
4. The maximum number of builds that Nix will execute in parallel on
the machine. Typically this should be equal to the number of CPU
cores. For instance, the machine `itchy` in the example will execute
up to 8 builds in parallel.
5. The “speed factor”, indicating the relative speed of the machine. If
there are multiple machines of the right type, Nix will prefer the
fastest, taking load into account.
6. A comma-separated list of *supported features*. If a derivation has
the `requiredSystemFeatures` attribute, then Nix will only perform
the derivation on a machine that has the specified features. For
instance, the attribute
```nix
requiredSystemFeatures = [ "kvm" ];
```
will cause the build to be performed on a machine that has the `kvm`
feature.
7. A comma-separated list of *mandatory features*. A machine will only
be used to build a derivation if all of the machines mandatory
features appear in the derivations `requiredSystemFeatures`
attribute.
8. The (base64-encoded) public host key of the remote machine. If omitted, SSH
will use its regular known-hosts file. Specifically, the field is calculated
via `base64 -w0 /etc/ssh/ssh_host_ed25519_key.pub`.
For example, the machine specification
nix@scratchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 1 kvm
nix@itchy.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 8 2
nix@poochie.labs.cs.uu.nl i686-linux /home/nix/.ssh/id_scratchy_auto 1 2 kvm benchmark
specifies several machines that can perform `i686-linux` builds.
However, `poochie` will only do builds that have the attribute
```nix
requiredSystemFeatures = [ "benchmark" ];
```
or
```nix
requiredSystemFeatures = [ "benchmark" "kvm" ];
```
`itchy` cannot do builds that require `kvm`, but `scratchy` does support
such builds. For regular builds, `itchy` will be preferred over
`scratchy` because it has a higher speed factor.
Remote builders can also be configured in `nix.conf`, e.g.
Remote build machines can also be configured in [`nix.conf`](@docroot@/command-ref/conf-file.md), e.g.
builders = ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd
Finally, remote builders can be configured in a separate configuration
file included in `builders` via the syntax `@file`. For example,
Finally, remote build machines can be configured in a separate configuration
file included in `builders` via the syntax `@/path/to/file`. For example,
builders = @/etc/nix/machines
causes the list of machines in `/etc/nix/machines` to be included. (This
is the default.)
If you want the builders to use caches, you likely want to set the
option `builders-use-substitutes` in your local `nix.conf`.
To build only on remote builders and disable building on the local
machine, you can use the option `--max-jobs 0`.
causes the list of machines in `/etc/nix/machines` to be included.
(This is the default.)

View File

@@ -51,7 +51,7 @@ These options are for deleting old [profiles] prior to deleting unreachable [sto
- <span id="opt-delete-old">[`--delete-old`](#opt-delete-old)</span> / `-d`\
Delete all old generations of profiles.
This is the equivalent of invoking `nix-env --delete-generations old` on each found profile.
This is the equivalent of invoking [`nix-env --delete-generations old`](@docroot@/command-ref/nix-env/delete-generations.md#generations-old) on each found profile.
- <span id="opt-delete-older-than">[`--delete-older-than`](#opt-delete-older-than)</span> *period*\
Delete all generations of profiles older than the specified amount (except for the generations that were active at that point in time).

View File

@@ -12,13 +12,13 @@ This operation deletes the specified generations of the current profile.
*generations* can be a one of the following:
- <span id="generations-list">`<number>...`</span>:\
- <span id="generations-list">[`<number>...`](#generations-list)</span>:\
A list of generation numbers, each one a separate command-line argument.
Delete exactly the profile generations given by their generation number.
Deleting the current generation is not allowed.
- The special value <span id="generations-old">`old`</span>
- <span id="generations-old">[The special value `old`](#generations-old)</span>
Delete all generations except the current one.
@@ -30,7 +30,7 @@ This operation deletes the specified generations of the current profile.
> Because one can roll back to a previous generation, it is possible to have generations newer than the current one.
> They will also be deleted.
- <span id="generations-time">`<number>d`</span>:\
- <span id="generations-time">[`<number>d`](#generations-time)</span>:\
The last *number* days
*Example*: `30d`
@@ -38,7 +38,7 @@ This operation deletes the specified generations of the current profile.
Delete all generations created more than *number* days ago, except the most recent one of them.
This allows rolling back to generations that were available within the specified period.
- <span id="generations-count">`+<number>`</span>:\
- <span id="generations-count">[`+<number>`](#generations-count)</span>:\
The last *number* generations up to the present
*Example*: `+5`

View File

@@ -44,13 +44,13 @@ To build Nix itself in this shell:
```console
[nix-shell]$ autoreconfPhase
[nix-shell]$ configurePhase
[nix-shell]$ make -j $NIX_BUILD_CORES
[nix-shell]$ make -j $NIX_BUILD_CORES OPTIMIZE=0
```
To install it in `$(pwd)/outputs` and test it:
```console
[nix-shell]$ make install
[nix-shell]$ make install OPTIMIZE=0
[nix-shell]$ make installcheck check -j $NIX_BUILD_CORES
[nix-shell]$ nix --version
nix (Nix) 2.12
@@ -147,10 +147,10 @@ Nix can be built for various platforms, as specified in [`flake.nix`]:
In order to build Nix for a different platform than the one you're currently
on, you need a way for your current Nix installation to build code for that
platform. Common solutions include [remote builders] and [binary format emulation]
platform. Common solutions include [remote build machines] and [binary format emulation]
(only supported on NixOS).
[remote builders]: ../advanced-topics/distributed-builds.md
[remote builders]: @docroot@/language/derivations.md#attr-builder
[binary format emulation]: https://nixos.org/manual/nixos/stable/options.html#opt-boot.binfmt.emulatedSystems
Given such a setup, executing the build only requires selecting the respective attribute.

View File

@@ -37,7 +37,7 @@
This can be achieved by:
- Fetching a pre-built [store object] from a [substituter]
- Running the [`builder`](@docroot@/language/derivations.md#attr-builder) executable as specified in the corresponding [derivation]
- Delegating to a [remote builder](@docroot@/advanced-topics/distributed-builds.html) and retrieving the outputs
- Delegating to a [remote machine](@docroot@/command-ref/conf-file.md#conf-builders) and retrieving the outputs
<!-- TODO: link [running] to build process page, #8888 -->
See [`nix-store --realise`](@docroot@/command-ref/nix-store/realise.md) for a detailed description of the algorithm.
@@ -59,23 +59,12 @@
- [store]{#gloss-store}
A collection of store objects, with operations to manipulate that collection.
See [Nix store](./store/index.md) for details.
A collection of [store objects][store object], with operations to manipulate that collection.
See [Nix Store](./store/index.md) for details.
There are many types of stores.
See [`nix help-stores`](@docroot@/command-ref/new-cli/nix3-help-stores.md) for a complete list.
From the perspective of the location where Nix is invoked, the Nix store can be referred to _local_ or _remote_.
Only a [local store]{#gloss-local-store} exposes a location in the file system of the machine where Nix is invoked that allows access to store objects, typically `/nix/store`.
Local stores can be used for building [derivations](#gloss-derivation).
See [Local Store](@docroot@/command-ref/new-cli/nix3-help-stores.md#local-store) for details.
There are many types of stores, see [Store Types](./store/types/index.md) for details.
[store]: #gloss-store
[local store]: #gloss-local-store
- [chroot store]{#gloss-chroot-store}
A [local store] whose canonical path is anything other than `/nix/store`.
- [binary cache]{#gloss-binary-cache}
@@ -87,7 +76,7 @@
- [store path]{#gloss-store-path}
The location of a [store object](@docroot@/store/index.md#store-object) in the file system, i.e., an immediate child of the Nix store directory.
The location of a [store object] in the file system, i.e., an immediate child of the Nix store directory.
> **Example**
>
@@ -243,6 +232,7 @@
- All paths in the store path's [closure] are valid.
[validity]: #gloss-validity
[local store]: @docroot@/store/types/local-store.md
- [user environment]{#gloss-user-env}

View File

@@ -0,0 +1,138 @@
# Using Nix in multi-user mode with a non-root daemon
> Experimental blurb
It is experimentally possible to run Nix in multi-user mode without running the whole daemon as root.
This is done by delegating the only part that requires root access to a separate daemon, with a much smaller attack surface.
Because of the need for a second daemon, this makes the setup a bit more complex and isn't yet supported by the installer. It is however possible to set this up manually:
1. Create a new user and group for the daemon:
```sh
sudo groupadd nix-daemon
sudo useradd --gid nix-daemon --system -c "Nix daemon user" nix-daemon
```
2. Create `/nix` owned by that user:
```sh
sudo mkdir -m 0755 /nix
sudo chown nix-daemon:nix-daemon /nix
```
3. Download a statically-compiled Nix version for bootstrapping
```sh
curl -L https://hydra.nixos.org/job/nix/master/buildStatic.x86_64-linux/latest/download-by-type/file/binary-dist -o /tmp/nix-env
chmod +x /tmp/nix-env
```
4. Install a proper Nix and the tracing daemon in the store
```sh
export DAEMON_HOME=$(sudo -u nix-daemon mktemp -d)
sudo -u nix-daemon HOME="$DAEMON_HOME" \
/tmp/nix-env \
-f https://github.com/nixos/nix/archive/rootless-daemon.tar.gz \
-iA default packages.x86_64-linux.nix-find-roots \
--option extra-substituters https://nixos-nix-install-tests.cachix.org \
--option extra-trusted-public-keys nixos-nix-install-tests.cachix.org-1:Le57vOUJjOcdzLlbwmZVBuLGoDC+Xg2rQDtmIzALgFU= \
--store / \
--profile /nix/var/nix/profiles/default
sudo -u nix-daemon mkdir -p /nix/var/nix/gc-socket
sudo -u nix-daemon rm -rf "$DAEMON_HOME"
```
5. Move the tracing daemon executable out of the store (as we don't want Nix
to own it)
```sh
sudo cp /nix/var/nix/profiles/default/bin/nix-find-roots /usr/bin/
```
6. Install the systemd services for the daemon:
```sh
cat <<EOF | sudo tee /etc/systemd/system/nix-daemon.service
[Unit]
Description=Nix Daemon
Documentation=man:nix-daemon https://nixos.org/manual
RequiresMountsFor=/nix/store
RequiresMountsFor=/nix/var
RequiresMountsFor=/nix/var/nix/db
ConditionPathIsReadWrite=/nix/var/nix/daemon-socket
[Service]
ExecStart=@/nix/var/nix/profiles/default/bin/nix-daemon nix-daemon --daemon
KillMode=process
LimitNOFILE=1048576
TasksMax=1048576
User=nix-daemon
Group=nix-daemon
[Install]
WantedBy=multi-user.target
EOF
```
```sh
cat <<EOF | sudo tee /etc/systemd/system/nix-daemon.socket
[Unit]
Description=Nix Daemon Socket
Before=multi-user.target
RequiresMountsFor=/nix/store
ConditionPathIsReadWrite=/nix/var/nix/daemon-socket
[Socket]
ListenStream=/nix/var/nix/daemon-socket/socket
SocketUser=nix-daemon
[Install]
WantedBy=sockets.target
EOF
```
7. Install the systemd services for the tracing daemon:
```sh
cat <<EOF | sudo tee /etc/systemd/system/nix-find-roots.service
[Unit]
Description=Nix GC tracer daemon
RequiresMountsFor=/nix/store
RequiresMountsFor=/nix/var
ConditionPathIsReadWrite=/nix/var/nix/gc-socket
ProcSubset=pid
[Service]
ExecStart=@/usr/bin/nix-find-roots nix-find-roots
Type=simple
StandardError=journal
ProtectSystem=full
ReadWritePaths=/nix/var/nix/gc-socket
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
PrivateNetwork=true
PrivateDevices=true
ProtectKernelTunables=true
[Install]
WantedBy=multi-user.target
EOF
```
```sh
cat <<EOF | sudo tee /etc/systemd/system/nix-find-roots.socket
[Unit]
Description=Nix Daemon Socket
Before=multi-user.target
RequiresMountsFor=/nix/store
ConditionPathIsReadWrite=/nix/var/nix/gc-socket
[Socket]
ListenStream=/nix/var/nix/gc-socket/socket
Accept=false
[Install]
WantedBy=sockets.target
EOF
```
8. Enable the required experimental Nix feature and basic configuration:
```sh
sudo mkdir /etc/nix
cat <<EOF | sudo tee /etc/nix/nix.conf
experimental-features = external-gc-daemon
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=
substituters = https://cache.nixos.org/
EOF
```
9. Start the systemd sockets:
```sh
sudo systemctl start nix-daemon.socket
sudo systemctl start nix-find-roots.socket
```
10. Profit

View File

@@ -16,7 +16,7 @@ nix (Nix) 2.18.1
> Writing to the [local store](@docroot@/store/types/local-store.md) with a newer version of Nix, for example by building derivations with [`nix-build`](@docroot@/command-ref/nix-build.md) or [`nix-store --realise`](@docroot@/command-ref/nix-store/realise.md), may change the database schema!
> Reverting to an older version of Nix may therefore require purging the store database before it can be used.
### Linux multi-user
## Linux multi-user
```console
$ sudo su

View File

@@ -257,7 +257,7 @@ Derivations can declare some infrequently used optional attributes.
of the environment (typically, a few hundred kilobyte).
- [`preferLocalBuild`]{#adv-attr-preferLocalBuild}\
If this attribute is set to `true` and [distributed building is enabled](../advanced-topics/distributed-builds.md), then, if possible, the derivation will be built locally instead of being forwarded to a remote machine.
If this attribute is set to `true` and [distributed building is enabled](@docroot@/command-ref/conf-file.md#conf-builders), then, if possible, the derivation will be built locally instead of being forwarded to a remote machine.
This is useful for derivations that are cheapest to build locally.
- [`allowSubstitutes`]{#adv-attr-allowSubstitutes}\

View File

@@ -36,7 +36,7 @@ It outputs an attribute set, and produces a [store derivation] as a side effect
The system type on which the [`builder`](#attr-builder) executable is meant to be run.
A necessary condition for Nix to build derivations locally is that the `system` attribute matches the current [`system` configuration option].
It can automatically [build on other platforms](../advanced-topics/distributed-builds.md) by forwarding build requests to other machines.
It can automatically [build on other platforms](@docroot@/language/derivations.md#attr-builder) by forwarding build requests to other machines.
[`system` configuration option]: @docroot@/command-ref/conf-file.md#conf-system

View File

@@ -1,6 +1,8 @@
# Import From Derivation
The value of a Nix expression can depend on the contents of a [store object](@docroot@/glossary.md#gloss-store-object).
The value of a Nix expression can depend on the contents of a [store object].
[store object]: @docroot@/glossary.md#gloss-store-object
Passing an expression `expr` that evaluates to a [store path](@docroot@/glossary.md#gloss-store-path) to any built-in function which reads from the filesystem constitutes Import From Derivation (IFD):

View File

@@ -84,7 +84,7 @@ The `+` operator is overloaded to also work on strings and paths.
>
> *string* `+` *string*
Concatenate two [string]s and merge their string contexts.
Concatenate two [strings][string] and merge their string contexts.
[String concatenation]: #string-concatenation
@@ -94,7 +94,7 @@ Concatenate two [string]s and merge their string contexts.
>
> *path* `+` *path*
Concatenate two [path]s.
Concatenate two [paths][path].
The result is a path.
[Path concatenation]: #path-concatenation
@@ -150,9 +150,9 @@ If an attribute name is present in both, the attribute value from the latter is
Comparison is
- [arithmetic] for [number]s
- lexicographic for [string]s and [path]s
- item-wise lexicographic for [list]s:
- [arithmetic] for [numbers][number]
- lexicographic for [strings][string] and [paths][path]
- item-wise lexicographic for [lists][list]:
elements at the same index in both lists are compared according to their type and skipped if they are equal.
All comparison operators are implemented in terms of `<`, and the following equivalencies hold:
@@ -163,12 +163,12 @@ All comparison operators are implemented in terms of `<`, and the following equi
| *a* `>` *b* | *b* `<` *a* |
| *a* `>=` *b* | `! (` *a* `<` *b* `)` |
[Comparison]: #comparison-operators
[Comparison]: #comparison
## Equality
- [Attribute sets][attribute set] and [list]s are compared recursively, and therefore are fully evaluated.
- Comparison of [function]s always returns `false`.
- [Attribute sets][attribute set] and [lists][list] are compared recursively, and therefore are fully evaluated.
- Comparison of [functions][function] always returns `false`.
- Numbers are type-compatible, see [arithmetic] operators.
- Floating point numbers only differ up to a limited precision.

View File

@@ -20,6 +20,8 @@ Rather than writing
(where `freetype` is a [derivation]), you can instead write
[derivation]: ../glossary.md#gloss-derivation
```nix
"--with-freetype2-library=${freetype}/lib"
```

View File

@@ -156,6 +156,8 @@ function and the fifth being a set.
Note that lists are only lazy in values, and they are strict in length.
Elements in a list can be accessed using [`builtins.elemAt`](./builtins.md#builtins-elemAt).
## Attribute Set
An attribute set is a collection of name-value-pairs (called *attributes*) enclosed in curly brackets (`{ }`).

View File

@@ -14,11 +14,11 @@ Info about a [store object].
* `narHash`:
Hash of the [file system object] part of the store object when serialized as a [Nix Archive](#gloss-nar).
Hash of the [file system object] part of the store object when serialized as a [Nix Archive].
* `narSize`:
Size of the [file system object] part of the store object when serialized as a [Nix Archive](#gloss-nar).
Size of the [file system object] part of the store object when serialized as a [Nix Archive].
* `references`:
@@ -30,6 +30,7 @@ Info about a [store object].
[store path]: @docroot@/glossary.md#gloss-store-path
[file system object]: @docroot@/store/file-system-object.md
[Nix Archive]: @docroot@/glossary.md#gloss-nar
## Impure fields

View File

@@ -0,0 +1,126 @@
# Complete Store Path Calculation
This is the complete specification for how store paths are calculated.
The format of this specification is close to [Extended BackusNaur form](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form), but must deviate for a few things such as hash functions which we treat as bidirectional for specification purposes.
Regular users do *not* need to know this information --- store paths can be treated as black boxes computed from the properties of the store objects they refer to.
But for those interested in exactly how Nix works, e.g. if they are reimplementing it, this information can be useful.
## Store path proper
```ebnf
store-path = store-dir "/" digest "-" name
```
where
- `name` = the name of the store object.
- `store-dir` = the [store directory](@docroot@/store/store-path.md#store-directory)
- `digest` = base-32 representation of the first 160 bits of a [SHA-256] hash of `fingerprint`
This the hash part of the store name
## Fingerprint
- ```ebnf
fingerprint = type ":" sha256 ":" inner-digest ":" store ":" name
```
Note that it includes the location of the store as well as the name to make sure that changes to either of those are reflected in the hash
(e.g. you won't get `/nix/store/<digest>-name1` and `/nix/store/<digest>-name2`, or `/gnu/store/<digest>-name1`, with equal hash parts).
- `type` = one of:
- ```ebnf
| "text" ( ":" store-path )*
```
for encoded derivations written to the store.
The optional trailing store paths are the references of the store object.
- ```ebnf
| "source" ( ":" store-path )*
```
For paths copied to the store and hashed via a [Nix Archive (NAR)] and [SHA-256][sha-256].
Just like in the text case, we can have the store objects referenced by their paths.
Additionally, we can have an optional `:self` label to denote self reference.
- ```ebnf
| "output:" id
```
For either the outputs built from derivations,
paths copied to the store hashed that area single file hashed directly, or the via a hash algorithm other than [SHA-256][sha-256].
(in that case "source" is used; this is only necessary for compatibility).
`id` is the name of the output (usually, "out").
For content-addressed store objects, `id`, is always "out".
- `inner-digest` = base-16 representation of a SHA-256 hash of `inner-fingerprint`
## Inner fingerprint
- `inner-fingerprint` = one of the following based on `type`:
- if `type` = `"text:" ...`:
the string written to the resulting store path.
- if `type` = `"source:" ...`:
the the hash of the [Nix Archive (NAR)] serialization of the [file system object](@docroot@/store/file-system-object.md) of the store object.
- if `type` = `"output:" id`:
- For input-addressed derivation outputs:
the [ATerm](@docroot@/protocols/derivation-aterm.md) serialization of the derivation modulo fixed output derivations.
- For content-addressed store paths:
```ebnf
"fixed:out:" rec algo ":" hash ":"
```
where
- `rec` = one of:
- ```ebnf
| "r:"
```
hashes of the for [Nix Archive (NAR)] (arbitrary file system object) serialization
- ```ebnf
| ""
```
(empty string) for hashes of the flat (single file) serialization
- ```ebnf
algo = "md5" | "sha1" | "sha256"
```
- `hash` = base-16 representation of the path or flat hash of the contents of the path (or expected contents of the path for fixed-output derivations).
Note that `id` = `"out"`, regardless of the name part of the store path.
Also note that NAR + SHA-256 must not use this case, and instead must use the `type` = `"source:" ...` case.
[Nix Archive (NAR)]: @docroot@/glossary.md#gloss-NAR
[sha-256]: https://en.m.wikipedia.org/wiki/SHA-256
### Historical Note
The `type` = `"source:" ...` and `type` = `"output:out"` grammars technically overlap in purpose,
in that both can represent data hashed by its SHA-256 NAR serialization.
The original reason for this way of computing names was to prevent name collisions (for security).
For instance, the thinking was that it shouldn't be feasible to come up with a derivation whose output path collides with the path for a copied source.
The former would have an `inner-fingerprint` starting with `output:out:`, while the latter would have an `inner-fingerprint` starting with `source:`.
Since `64519cfd657d024ae6e2bb74cb21ad21b886fd2a` (2008), however, it was decided that separating derivation-produced vs manually-hashed content-addressed data like this was not useful.
Now, data that is content-addressed with SHA-256 + NAR-serialization always uses the `source:...` construction, regardless of how it was produced (manually or by derivation).
This allows freely switching between using [fixed-output derivations](@docroot@/glossary.md#gloss-fixed-output-derivation) for fetching, and fetching out-of-band and then manually adding.
It also removes the ambiguity from the grammar.

View File

@@ -167,3 +167,36 @@
error: expected a set but found an integer
```
- Functions are printed with more detail [#7145](https://github.com/NixOS/nix/issues/7145) [#9606](https://github.com/NixOS/nix/pull/9606)
`nix repl`, `nix eval`, `builtins.trace`, and most other places values are
printed will now include function names and source location information:
```
$ nix repl nixpkgs
nix-repl> builtins.map
«primop map»
nix-repl> builtins.map lib.id
«partially applied primop map»
nix-repl> builtins.trace lib.id "my-value"
trace: «lambda id @ /nix/store/8rrzq23h2zq7sv5l2vhw44kls5w0f654-source/lib/trivial.nix:26:5»
"my-value"
```
- Flake operations like `nix develop` will no longer fail when run in a Git
repository where the `flake.lock` file is `.gitignore`d
[#8854](https://github.com/NixOS/nix/issues/8854)
[#9324](https://github.com/NixOS/nix/pull/9324)
- Nix commands will now respect Ctrl-C
[#7145](https://github.com/NixOS/nix/issues/7145)
[#6995](https://github.com/NixOS/nix/pull/6995)
[#9687](https://github.com/NixOS/nix/pull/9687)
Previously, many Nix commands would hang indefinitely if Ctrl-C was pressed
while performing various operations (including `nix develop`, `nix flake
update`, and so on). With several fixes to Nix's signal handlers, Nix
commands will now exit quickly after Ctrl-C is pressed.

View File

@@ -152,6 +152,30 @@
'';
};
nix-find-roots = prev.stdenv.mkDerivation {
pname = "nix-find-roots";
inherit version;
src = fileset.toSource {
root = ./src/nix-find-roots;
fileset = /*fileset.intersect baseFiles (*/fileset.unions [
./src/nix-find-roots/main.cc
./src/nix-find-roots/lib
]/*)*/;
};
CXXFLAGS = prev.lib.optionalString prev.stdenv.hostPlatform.isStatic "-static";
buildPhase = ''
$CXX $CXXFLAGS -std=c++17 *.cc **/*.cc -I lib -o nix-find-roots
'';
installPhase = ''
mkdir -p $out/bin
cp nix-find-roots $out/bin/
'';
};
libgit2-nix = final.libgit2.overrideAttrs (attrs: {
src = libgit2;
version = libgit2.lastModifiedDate;
@@ -366,6 +390,9 @@
default = nix;
} // (lib.optionalAttrs (builtins.elem system linux64BitSystems) {
nix-static = nixpkgsFor.${system}.static.nix;
inherit (nixpkgsFor.${system}.static) nix-find-roots;
dockerImage =
let
pkgs = nixpkgsFor.${system}.native;

View File

@@ -1,6 +1,8 @@
ifdef HOST_LINUX
$(foreach n, nix-daemon.socket nix-daemon.service, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644)))
$(foreach n,\
nix-daemon.socket nix-daemon.service nix-gc-trace.socket nix-gc-trace.service,\
$(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644)))
$(foreach n, nix-daemon.conf, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/tmpfiles.d, 0644)))
clean-files += $(d)/nix-daemon.socket $(d)/nix-daemon.service $(d)/nix-daemon.conf

View File

@@ -0,0 +1,21 @@
[Unit]
Description=Nix GC Tracer Daemon
RequiresMountsFor=@storedir@
RequiresMountsFor=@localstatedir@
ConditionPathIsReadWrite=@localstatedir@/nix/gc-socket
ProcSubset=pid
[Service]
ExecStart=@@libexecdir@/nix/nix-find-roots nix-find-roots
Type=simple
StandardError=journal
ProtectSystem=full
ReadWritePaths=@localstatedir@/nix/gc-socket
SystemCallFilter=@system-service
SystemCallErrorNumber=EPERM
PrivateNetwork=true
PrivateDevices=true
ProtectKernelTunables=true
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,12 @@
[Unit]
Description=Nix Daemon Socket
Before=multi-user.target
RequiresMountsFor=@storedir@
ConditionPathIsReadWrite=@localstatedir@/nix/gc-socket
[Socket]
ListenStream=@localstatedir@/nix/gc-socket/socket
Accept=false
[Install]
WantedBy=sockets.target

View File

@@ -97,6 +97,10 @@ $(foreach test-group, $(install-tests-groups), \
$(eval $(call run-test,$(test),$(install_test_init))) \
$(eval $(test-group).test-group: $(test).test)))
# Include makefiles requiring built programs.
$(foreach mf, $(makefiles-late), $(eval $(call include-sub-makefile,$(mf))))
$(foreach file, $(man-pages), $(eval $(call install-data-in, $(file), $(mandir)/man$(patsubst .%,%,$(suffix $(file))))))

View File

@@ -10,10 +10,10 @@ endef
ifneq ($(MAKECMDGOALS), clean)
$(buildprefix)%.h: %.h.in
$(buildprefix)%.h: %.h.in $(buildprefix)config.status
$(trace-gen) rm -f $@ && cd $(buildprefixrel) && ./config.status --quiet --header=$(@:$(buildprefix)%=%)
$(buildprefix)%: %.in
$(buildprefix)%: %.in $(buildprefix)config.status
$(trace-gen) rm -f $@ && cd $(buildprefixrel) && ./config.status --quiet --file=$(@:$(buildprefix)%=%)
endif

2
perl/.yath.rc Normal file
View File

@@ -0,0 +1,2 @@
[test]
-I=rel(lib/Nix)

View File

@@ -5,12 +5,12 @@
, nix, curl, bzip2, xz, boost, libsodium, darwin
}:
perl.pkgs.toPerlModule (stdenv.mkDerivation {
perl.pkgs.toPerlModule (stdenv.mkDerivation (finalAttrs: {
name = "nix-perl-${nix.version}";
src = fileset.toSource {
root = ../.;
fileset = fileset.unions [
fileset = fileset.unions ([
../.version
../m4
../mk
@@ -20,7 +20,10 @@ perl.pkgs.toPerlModule (stdenv.mkDerivation {
./configure.ac
./lib
./local.mk
];
] ++ lib.optionals finalAttrs.doCheck [
./.yath.rc
./t
]);
};
nativeBuildInputs =
@@ -40,6 +43,13 @@ perl.pkgs.toPerlModule (stdenv.mkDerivation {
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
++ lib.optional stdenv.isDarwin darwin.apple_sdk.frameworks.Security;
# `perlPackages.Test2Harness` is marked broken for Darwin
doCheck = !stdenv.isDarwin;
nativeCheckInputs = [
perlPackages.Test2Harness
];
configureFlags = [
"--with-dbi=${perlPackages.DBI}/${perl.libPrefix}"
"--with-dbd-sqlite=${perlPackages.DBDSQLite}/${perl.libPrefix}"
@@ -48,4 +58,4 @@ perl.pkgs.toPerlModule (stdenv.mkDerivation {
enableParallelBuilding = true;
postUnpack = "sourceRoot=$sourceRoot/perl";
})
}))

View File

@@ -12,17 +12,20 @@ our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
our @EXPORT = qw(
setVerbosity
isValidPath queryReferences queryPathInfo queryDeriver queryPathHash
queryPathFromHashPart
topoSortPaths computeFSClosure followLinksToStorePath exportPaths importPaths
StoreWrapper
StoreWrapper::new
StoreWrapper::isValidPath StoreWrapper::queryReferences StoreWrapper::queryPathInfo StoreWrapper::queryDeriver StoreWrapper::queryPathHash
StoreWrapper::queryPathFromHashPart
StoreWrapper::topoSortPaths StoreWrapper::computeFSClosure followLinksToStorePath StoreWrapper::exportPaths StoreWrapper::importPaths
StoreWrapper::addToStore StoreWrapper::makeFixedOutputPath
StoreWrapper::derivationFromPath
StoreWrapper::addTempRoot
StoreWrapper::queryRawRealisation
hashPath hashFile hashString convertHash
signString checkSignature
addToStore makeFixedOutputPath
derivationFromPath
addTempRoot
getBinDir getStoreDir
queryRawRealisation
setVerbosity
);
our $VERSION = '0.15';

View File

@@ -17,47 +17,61 @@
#include <sodium.h>
#include <nlohmann/json.hpp>
using namespace nix;
static bool libStoreInitialized = false;
static ref<Store> store()
{
static std::shared_ptr<Store> _store;
if (!_store) {
try {
initLibStore();
_store = openStore();
} catch (Error & e) {
croak("%s", e.what());
}
}
return ref<Store>(_store);
}
struct StoreWrapper {
ref<Store> store;
};
MODULE = Nix::Store PACKAGE = Nix::Store
PROTOTYPES: ENABLE
TYPEMAP: <<HERE
StoreWrapper * O_OBJECT
OUTPUT
O_OBJECT
sv_setref_pv( $arg, CLASS, (void*)$var );
INPUT
O_OBJECT
if ( sv_isobject($arg) && (SvTYPE(SvRV($arg)) == SVt_PVMG) ) {
$var = ($type)SvIV((SV*)SvRV( $arg ));
}
else {
warn( \"${Package}::$func_name() -- \"
\"$var not a blessed SV reference\");
XSRETURN_UNDEF;
}
HERE
#undef dNOOP // Hack to work around "error: declaration of 'Perl___notused' has a different language linkage" error message on clang.
#define dNOOP
void
StoreWrapper::DESTROY()
void init()
CODE:
store();
void setVerbosity(int level)
CODE:
verbosity = (Verbosity) level;
int isValidPath(char * path)
StoreWrapper *
StoreWrapper::new(char * s = nullptr)
CODE:
static std::shared_ptr<Store> _store;
try {
RETVAL = store()->isValidPath(store()->parseStorePath(path));
if (!libStoreInitialized) {
initLibStore();
libStoreInitialized = true;
}
if (items == 1) {
_store = openStore();
RETVAL = new StoreWrapper {
.store = ref<Store>{_store}
};
} else {
RETVAL = new StoreWrapper {
.store = openStore(s)
};
}
} catch (Error & e) {
croak("%s", e.what());
}
@@ -65,52 +79,81 @@ int isValidPath(char * path)
RETVAL
SV * queryReferences(char * path)
void init()
CODE:
if (!libStoreInitialized) {
initLibStore();
libStoreInitialized = true;
}
void setVerbosity(int level)
CODE:
verbosity = (Verbosity) level;
int
StoreWrapper::isValidPath(char * path)
CODE:
try {
RETVAL = THIS->store->isValidPath(THIS->store->parseStorePath(path));
} catch (Error & e) {
croak("%s", e.what());
}
OUTPUT:
RETVAL
SV *
StoreWrapper::queryReferences(char * path)
PPCODE:
try {
for (auto & i : store()->queryPathInfo(store()->parseStorePath(path))->references)
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(i).c_str(), 0)));
for (auto & i : THIS->store->queryPathInfo(THIS->store->parseStorePath(path))->references)
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * queryPathHash(char * path)
SV *
StoreWrapper::queryPathHash(char * path)
PPCODE:
try {
auto s = store()->queryPathInfo(store()->parseStorePath(path))->narHash.to_string(HashFormat::Nix32, true);
auto s = THIS->store->queryPathInfo(THIS->store->parseStorePath(path))->narHash.to_string(HashFormat::Nix32, true);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * queryDeriver(char * path)
SV *
StoreWrapper::queryDeriver(char * path)
PPCODE:
try {
auto info = store()->queryPathInfo(store()->parseStorePath(path));
auto info = THIS->store->queryPathInfo(THIS->store->parseStorePath(path));
if (!info->deriver) XSRETURN_UNDEF;
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(*info->deriver).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(*info->deriver).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * queryPathInfo(char * path, int base32)
SV *
StoreWrapper::queryPathInfo(char * path, int base32)
PPCODE:
try {
auto info = store()->queryPathInfo(store()->parseStorePath(path));
auto info = THIS->store->queryPathInfo(THIS->store->parseStorePath(path));
if (!info->deriver)
XPUSHs(&PL_sv_undef);
else
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(*info->deriver).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(*info->deriver).c_str(), 0)));
auto s = info->narHash.to_string(base32 ? HashFormat::Nix32 : HashFormat::Base16, true);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
mXPUSHi(info->registrationTime);
mXPUSHi(info->narSize);
AV * refs = newAV();
for (auto & i : info->references)
av_push(refs, newSVpv(store()->printStorePath(i).c_str(), 0));
av_push(refs, newSVpv(THIS->store->printStorePath(i).c_str(), 0));
XPUSHs(sv_2mortal(newRV((SV *) refs)));
AV * sigs = newAV();
for (auto & i : info->sigs)
@@ -120,10 +163,11 @@ SV * queryPathInfo(char * path, int base32)
croak("%s", e.what());
}
SV * queryRawRealisation(char * outputId)
SV *
StoreWrapper::queryRawRealisation(char * outputId)
PPCODE:
try {
auto realisation = store()->queryRealisation(DrvOutput::parse(outputId));
auto realisation = THIS->store->queryRealisation(DrvOutput::parse(outputId));
if (realisation)
XPUSHs(sv_2mortal(newSVpv(realisation->toJSON().dump().c_str(), 0)));
else
@@ -133,46 +177,50 @@ SV * queryRawRealisation(char * outputId)
}
SV * queryPathFromHashPart(char * hashPart)
SV *
StoreWrapper::queryPathFromHashPart(char * hashPart)
PPCODE:
try {
auto path = store()->queryPathFromHashPart(hashPart);
XPUSHs(sv_2mortal(newSVpv(path ? store()->printStorePath(*path).c_str() : "", 0)));
auto path = THIS->store->queryPathFromHashPart(hashPart);
XPUSHs(sv_2mortal(newSVpv(path ? THIS->store->printStorePath(*path).c_str() : "", 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * computeFSClosure(int flipDirection, int includeOutputs, ...)
SV *
StoreWrapper::computeFSClosure(int flipDirection, int includeOutputs, ...)
PPCODE:
try {
StorePathSet paths;
for (int n = 2; n < items; ++n)
store()->computeFSClosure(store()->parseStorePath(SvPV_nolen(ST(n))), paths, flipDirection, includeOutputs);
THIS->store->computeFSClosure(THIS->store->parseStorePath(SvPV_nolen(ST(n))), paths, flipDirection, includeOutputs);
for (auto & i : paths)
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(i).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * topoSortPaths(...)
SV *
StoreWrapper::topoSortPaths(...)
PPCODE:
try {
StorePathSet paths;
for (int n = 0; n < items; ++n) paths.insert(store()->parseStorePath(SvPV_nolen(ST(n))));
auto sorted = store()->topoSortPaths(paths);
for (int n = 0; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
auto sorted = THIS->store->topoSortPaths(paths);
for (auto & i : sorted)
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(i).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * followLinksToStorePath(char * path)
SV *
StoreWrapper::followLinksToStorePath(char * path)
CODE:
try {
RETVAL = newSVpv(store()->printStorePath(store()->followLinksToStorePath(path)).c_str(), 0);
RETVAL = newSVpv(THIS->store->printStorePath(THIS->store->followLinksToStorePath(path)).c_str(), 0);
} catch (Error & e) {
croak("%s", e.what());
}
@@ -180,34 +228,37 @@ SV * followLinksToStorePath(char * path)
RETVAL
void exportPaths(int fd, ...)
void
StoreWrapper::exportPaths(int fd, ...)
PPCODE:
try {
StorePathSet paths;
for (int n = 1; n < items; ++n) paths.insert(store()->parseStorePath(SvPV_nolen(ST(n))));
for (int n = 1; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
FdSink sink(fd);
store()->exportPaths(paths, sink);
THIS->store->exportPaths(paths, sink);
} catch (Error & e) {
croak("%s", e.what());
}
void importPaths(int fd, int dontCheckSigs)
void
StoreWrapper::importPaths(int fd, int dontCheckSigs)
PPCODE:
try {
FdSource source(fd);
store()->importPaths(source, dontCheckSigs ? NoCheckSigs : CheckSigs);
THIS->store->importPaths(source, dontCheckSigs ? NoCheckSigs : CheckSigs);
} catch (Error & e) {
croak("%s", e.what());
}
SV * hashPath(char * algo, int base32, char * path)
SV *
hashPath(char * algo, int base32, char * path)
PPCODE:
try {
PosixSourceAccessor accessor;
auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(path);
Hash h = hashPath(
accessor, CanonPath::fromCwd(path),
accessor, canonPath,
FileIngestionMethod::Recursive, parseHashAlgo(algo)).first;
auto s = h.to_string(base32 ? HashFormat::Nix32 : HashFormat::Base16, false);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
@@ -280,64 +331,67 @@ int checkSignature(SV * publicKey_, SV * sig_, char * msg)
RETVAL
SV * addToStore(char * srcPath, int recursive, char * algo)
SV *
StoreWrapper::addToStore(char * srcPath, int recursive, char * algo)
PPCODE:
try {
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
PosixSourceAccessor accessor;
auto path = store()->addToStore(
auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(srcPath);
auto path = THIS->store->addToStore(
std::string(baseNameOf(srcPath)),
accessor, CanonPath::fromCwd(srcPath),
accessor, canonPath,
method, parseHashAlgo(algo));
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(path).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * makeFixedOutputPath(int recursive, char * algo, char * hash, char * name)
SV *
StoreWrapper::makeFixedOutputPath(int recursive, char * algo, char * hash, char * name)
PPCODE:
try {
auto h = Hash::parseAny(hash, parseHashAlgo(algo));
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
auto path = store()->makeFixedOutputPath(name, FixedOutputInfo {
auto path = THIS->store->makeFixedOutputPath(name, FixedOutputInfo {
.method = method,
.hash = h,
.references = {},
});
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(path).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV * derivationFromPath(char * drvPath)
SV *
StoreWrapper::derivationFromPath(char * drvPath)
PREINIT:
HV *hash;
CODE:
try {
Derivation drv = store()->derivationFromPath(store()->parseStorePath(drvPath));
Derivation drv = THIS->store->derivationFromPath(THIS->store->parseStorePath(drvPath));
hash = newHV();
HV * outputs = newHV();
for (auto & i : drv.outputsAndOptPaths(*store())) {
for (auto & i : drv.outputsAndOptPaths(*THIS->store)) {
hv_store(
outputs, i.first.c_str(), i.first.size(),
!i.second.second
? newSV(0) /* null value */
: newSVpv(store()->printStorePath(*i.second.second).c_str(), 0),
: newSVpv(THIS->store->printStorePath(*i.second.second).c_str(), 0),
0);
}
hv_stores(hash, "outputs", newRV((SV *) outputs));
AV * inputDrvs = newAV();
for (auto & i : drv.inputDrvs.map)
av_push(inputDrvs, newSVpv(store()->printStorePath(i.first).c_str(), 0)); // !!! ignores i->second
av_push(inputDrvs, newSVpv(THIS->store->printStorePath(i.first).c_str(), 0)); // !!! ignores i->second
hv_stores(hash, "inputDrvs", newRV((SV *) inputDrvs));
AV * inputSrcs = newAV();
for (auto & i : drv.inputSrcs)
av_push(inputSrcs, newSVpv(store()->printStorePath(i).c_str(), 0));
av_push(inputSrcs, newSVpv(THIS->store->printStorePath(i).c_str(), 0));
hv_stores(hash, "inputSrcs", newRV((SV *) inputSrcs));
hv_stores(hash, "platform", newSVpv(drv.platform.c_str(), 0));
@@ -361,10 +415,11 @@ SV * derivationFromPath(char * drvPath)
RETVAL
void addTempRoot(char * storePath)
void
StoreWrapper::addTempRoot(char * storePath)
PPCODE:
try {
store()->addTempRoot(store()->parseStorePath(storePath));
THIS->store->addTempRoot(THIS->store->parseStorePath(storePath));
} catch (Error & e) {
croak("%s", e.what());
}

View File

@@ -41,3 +41,6 @@ Store_FORCE_INSTALL = 1
Store_INSTALL_DIR = $(perllibdir)/auto/Nix/Store
clean-files += lib/Nix/Config.pm lib/Nix/Store.cc Makefile.config
check: all
yath test

13
perl/t/init.t Normal file
View File

@@ -0,0 +1,13 @@
use strict;
use warnings;
use Test2::V0;
use Nix::Store;
my $s = new Nix::Store("dummy://");
my $res = $s->isValidPath("/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-bar");
ok(!$res, "should not have path");
done_testing;

View File

@@ -102,7 +102,7 @@ poly_extra_try_me_commands() {
poly_configure_nix_daemon_service() {
task "Setting up the nix-daemon LaunchDaemon"
_sudo "to set up the nix-daemon as a LaunchDaemon" \
/usr/bin/install -m -rw-r--r-- "/nix/var/nix/profiles/default$NIX_DAEMON_DEST" "$NIX_DAEMON_DEST"
/usr/bin/install -m "u=rw,go=r" "/nix/var/nix/profiles/default$NIX_DAEMON_DEST" "$NIX_DAEMON_DEST"
_sudo "to load the LaunchDaemon plist for nix-daemon" \
launchctl load /Library/LaunchDaemons/org.nixos.nix-daemon.plist

View File

@@ -202,7 +202,7 @@ static int main_build_remote(int argc, char * * argv)
else
drvstr = "<unknown>";
auto error = hintformat(errorText);
auto error = HintFmt(errorText);
error
% drvstr
% neededSystem

View File

@@ -9,6 +9,7 @@
#include "store-api.hh"
#include "command.hh"
#include "tarball.hh"
#include "fetch-to-store.hh"
namespace nix {
@@ -156,7 +157,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
for (auto & i : autoArgs) {
auto v = state.allocValue();
if (i.second[0] == 'E')
state.mkThunk_(*v, state.parseExprFromString(i.second.substr(1), state.rootPath(CanonPath::fromCwd())));
state.mkThunk_(*v, state.parseExprFromString(i.second.substr(1), state.rootPath(".")));
else
v->mkString(((std::string_view) i.second).substr(1));
res.insert(state.symbols.create(i.first), v);
@@ -164,11 +165,12 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
return res.finish();
}
SourcePath lookupFileArg(EvalState & state, std::string_view s, CanonPath baseDir)
SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * baseDir)
{
if (EvalSettings::isPseudoUrl(s)) {
auto storePath = fetchers::downloadTarball(
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).storePath;
auto accessor = fetchers::downloadTarball(
EvalSettings::resolvePseudoUrl(s)).accessor;
auto storePath = fetchToStore(*state.store, SourcePath(accessor), FetchMode::Copy);
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
}
@@ -185,7 +187,7 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, CanonPath baseDi
}
else
return state.rootPath(CanonPath(s, baseDir));
return state.rootPath(baseDir ? absPath(s, *baseDir) : absPath(s));
}
}

View File

@@ -29,6 +29,6 @@ private:
std::map<std::string, std::string> autoArgs;
};
SourcePath lookupFileArg(EvalState & state, std::string_view s, CanonPath baseDir = CanonPath::fromCwd());
SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * baseDir = nullptr);
}

View File

@@ -17,7 +17,7 @@ Strings editorFor(const SourcePath & file, uint32_t line)
editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", line));
args.push_back(path->abs());
args.push_back(path->string());
return args;
}

View File

@@ -45,7 +45,7 @@ ref<InstallableValue> InstallableValue::require(ref<Installable> installable)
std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx)
{
if (v.type() == nPath) {
auto storePath = fetchToStore(*state->store, v.path());
auto storePath = fetchToStore(*state->store, v.path(), FetchMode::Copy);
return {{
.path = DerivedPath::Opaque {
.path = std::move(storePath),

View File

@@ -487,10 +487,11 @@ Installables SourceExprCommand::parseInstallables(
state->eval(e, *vFile);
}
else if (file) {
state->evalFile(lookupFileArg(*state, *file, CanonPath::fromCwd(getCommandBaseDir())), *vFile);
auto dir = absPath(getCommandBaseDir());
state->evalFile(lookupFileArg(*state, *file, &dir), *vFile);
}
else {
CanonPath dir(CanonPath::fromCwd(getCommandBaseDir()));
Path dir = absPath(getCommandBaseDir());
auto e = state->parseExprFromString(*expr, state->rootPath(dir));
state->eval(e, *vFile);
}

View File

@@ -0,0 +1,121 @@
#include "misc-store-flags.hh"
namespace nix::flag
{
static void hashFormatCompleter(AddCompletions & completions, size_t index, std::string_view prefix)
{
for (auto & format : hashFormats) {
if (hasPrefix(format, prefix)) {
completions.add(format);
}
}
}
Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf)
{
assert(*hf == nix::HashFormat::SRI);
return Args::Flag {
.longName = std::move(longName),
.description = "Hash format (`base16`, `nix32`, `base64`, `sri`). Default: `sri`.",
.labels = {"hash-format"},
.handler = {[hf](std::string s) {
*hf = parseHashFormat(s);
}},
.completer = hashFormatCompleter,
};
}
Args::Flag hashFormatOpt(std::string && longName, std::optional<HashFormat> * ohf)
{
return Args::Flag {
.longName = std::move(longName),
.description = "Hash format (`base16`, `nix32`, `base64`, `sri`).",
.labels = {"hash-format"},
.handler = {[ohf](std::string s) {
*ohf = std::optional<HashFormat>{parseHashFormat(s)};
}},
.completer = hashFormatCompleter,
};
}
static void hashAlgoCompleter(AddCompletions & completions, size_t index, std::string_view prefix)
{
for (auto & algo : hashAlgorithms)
if (hasPrefix(algo, prefix))
completions.add(algo);
}
Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha)
{
return Args::Flag {
.longName = std::move(longName),
.description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`).",
.labels = {"hash-algo"},
.handler = {[ha](std::string s) {
*ha = parseHashAlgo(s);
}},
.completer = hashAlgoCompleter,
};
}
Args::Flag hashAlgoOpt(std::string && longName, std::optional<HashAlgorithm> * oha)
{
return Args::Flag {
.longName = std::move(longName),
.description = "Hash algorithm (`md5`, `sha1`, `sha256`, or `sha512`). Can be omitted for SRI hashes.",
.labels = {"hash-algo"},
.handler = {[oha](std::string s) {
*oha = std::optional<HashAlgorithm>{parseHashAlgo(s)};
}},
.completer = hashAlgoCompleter,
};
}
Args::Flag fileIngestionMethod(FileIngestionMethod * method)
{
return Args::Flag {
.longName = "mode",
// FIXME indentation carefully made for context, this is messed up.
.description = R"(
How to compute the hash of the input.
One of:
- `nar` (the default): Serialises the input as an archive (following the [_Nix Archive Format_](https://edolstra.github.io/pubs/phd-thesis.pdf#page=101)) and passes that to the hash function.
- `flat`: Assumes that the input is a single file and directly passes it to the hash function;
)",
.labels = {"file-ingestion-method"},
.handler = {[method](std::string s) {
*method = parseFileIngestionMethod(s);
}},
};
}
Args::Flag contentAddressMethod(ContentAddressMethod * method)
{
return Args::Flag {
.longName = "mode",
// FIXME indentation carefully made for context, this is messed up.
.description = R"(
How to compute the content-address of the store object.
One of:
- `nar` (the default): Serialises the input as an archive (following the [_Nix Archive Format_](https://edolstra.github.io/pubs/phd-thesis.pdf#page=101)) and passes that to the hash function.
- `flat`: Assumes that the input is a single file and directly passes it to the hash function;
- `text`: Like `flat`, but used for
[derivations](@docroot@/glossary.md#store-derivation) serialized in store object and
[`builtins.toFile`](@docroot@/language/builtins.html#builtins-toFile).
For advanced use-cases only;
for regular usage prefer `nar` and `flat.
)",
.labels = {"content-address-method"},
.handler = {[method](std::string s) {
*method = ContentAddressMethod::parse(s);
}},
};
}
}

View File

@@ -0,0 +1,21 @@
#include "args.hh"
#include "content-address.hh"
namespace nix::flag {
Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha);
static inline Args::Flag hashAlgo(HashAlgorithm * ha)
{
return hashAlgo("hash-algo", ha);
}
Args::Flag hashAlgoOpt(std::string && longName, std::optional<HashAlgorithm> * oha);
Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf);
Args::Flag hashFormatOpt(std::string && longName, std::optional<HashFormat> * ohf);
static inline Args::Flag hashAlgoOpt(std::optional<HashAlgorithm> * oha)
{
return hashAlgoOpt("hash-algo", oha);
}
Args::Flag fileIngestionMethod(FileIngestionMethod * method);
Args::Flag contentAddressMethod(ContentAddressMethod * method);
}

View File

@@ -52,6 +52,27 @@ extern "C" {
namespace nix {
/**
* Returned by `NixRepl::processLine`.
*/
enum class ProcessLineResult {
/**
* The user exited with `:quit`. The REPL should exit. The surrounding
* program or evaluation (e.g., if the REPL was acting as the debugger)
* should also exit.
*/
Quit,
/**
* The user exited with `:continue`. The REPL should exit, but the program
* should continue running.
*/
Continue,
/**
* The user did not exit. The REPL should request another line of input.
*/
PromptAgain,
};
struct NixRepl
: AbstractNixRepl
#if HAVE_BOEHMGC
@@ -75,13 +96,13 @@ struct NixRepl
std::function<AnnotatedValues()> getValues);
virtual ~NixRepl();
void mainLoop() override;
ReplExitStatus mainLoop() override;
void initEnv() override;
StringSet completePrefix(const std::string & prefix);
bool getLine(std::string & input, const std::string & prompt);
StorePath getDerivationPath(Value & v);
bool processLine(std::string line);
ProcessLineResult processLine(std::string line);
void loadFile(const Path & path);
void loadFlake(const std::string & flakeRef);
@@ -101,7 +122,8 @@ struct NixRepl
.ansiColors = true,
.force = true,
.derivationPaths = true,
.maxDepth = maxDepth
.maxDepth = maxDepth,
.prettyIndent = 2
});
}
};
@@ -232,7 +254,7 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi
: positions[dt.expr.getPos() ? dt.expr.getPos() : noPos];
if (pos) {
out << pos;
out << *pos;
if (auto loc = pos->getCodeLines()) {
out << "\n";
printCodeLines(out, "", *pos, *loc);
@@ -243,10 +265,19 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi
return out;
}
void NixRepl::mainLoop()
static bool isFirstRepl = true;
ReplExitStatus NixRepl::mainLoop()
{
std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
if (isFirstRepl) {
std::string_view debuggerNotice = "";
if (state->debugRepl) {
debuggerNotice = " debugger";
}
notice("Nix %1%%2%\nType :? for help.", nixVersion, debuggerNotice);
}
isFirstRepl = false;
loadFiles();
@@ -277,15 +308,25 @@ void NixRepl::mainLoop()
// When continuing input from previous lines, don't print a prompt, just align to the same
// number of chars as the prompt.
if (!getLine(input, input.empty() ? "nix-repl> " : " ")) {
// ctrl-D should exit the debugger.
// Ctrl-D should exit the debugger.
state->debugStop = false;
state->debugQuit = true;
logger->cout("");
break;
// TODO: Should Ctrl-D exit just the current debugger session or
// the entire program?
return ReplExitStatus::QuitAll;
}
logger->resume();
try {
if (!removeWhitespace(input).empty() && !processLine(input)) return;
switch (processLine(input)) {
case ProcessLineResult::Quit:
return ReplExitStatus::QuitAll;
case ProcessLineResult::Continue:
return ReplExitStatus::Continue;
case ProcessLineResult::PromptAgain:
break;
default:
abort();
}
} 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
@@ -422,8 +463,6 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
// Quietly ignore parse errors.
} catch (EvalError & e) {
// Quietly ignore evaluation errors.
} catch (UndefinedVarError & e) {
// Quietly ignore undefined variable errors.
} catch (BadURL & e) {
// Quietly ignore BadURL flake-related errors.
}
@@ -475,10 +514,11 @@ void NixRepl::loadDebugTraceEnv(DebugTrace & dt)
}
}
bool NixRepl::processLine(std::string line)
ProcessLineResult NixRepl::processLine(std::string line)
{
line = trim(line);
if (line == "") return true;
if (line.empty())
return ProcessLineResult::PromptAgain;
_isInterrupted = false;
@@ -573,13 +613,13 @@ bool NixRepl::processLine(std::string line)
else if (state->debugRepl && (command == ":s" || command == ":step")) {
// set flag to stop at next DebugTrace; exit repl.
state->debugStop = true;
return false;
return ProcessLineResult::Continue;
}
else if (state->debugRepl && (command == ":c" || command == ":continue")) {
// set flag to run to next breakpoint or end of program; exit repl.
state->debugStop = false;
return false;
return ProcessLineResult::Continue;
}
else if (command == ":a" || command == ":add") {
@@ -722,8 +762,7 @@ bool NixRepl::processLine(std::string line)
else if (command == ":q" || command == ":quit") {
state->debugStop = false;
state->debugQuit = true;
return false;
return ProcessLineResult::Quit;
}
else if (command == ":doc") {
@@ -784,7 +823,7 @@ bool NixRepl::processLine(std::string line)
}
}
return true;
return ProcessLineResult::PromptAgain;
}
void NixRepl::loadFile(const Path & path)
@@ -890,7 +929,7 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
Expr * NixRepl::parseString(std::string s)
{
return state->parseExprFromString(std::move(s), state->rootPath(CanonPath::fromCwd()), staticEnv);
return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv);
}
@@ -915,7 +954,7 @@ std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
}
void AbstractNixRepl::runSimple(
ReplExitStatus AbstractNixRepl::runSimple(
ref<EvalState> evalState,
const ValMap & extraEnv)
{
@@ -937,7 +976,7 @@ void AbstractNixRepl::runSimple(
for (auto & [name, value] : extraEnv)
repl->addVarToScope(repl->state->symbols.create(name), *value);
repl->mainLoop();
return repl->mainLoop();
}
}

View File

@@ -28,13 +28,13 @@ struct AbstractNixRepl
const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<AnnotatedValues()> getValues);
static void runSimple(
static ReplExitStatus runSimple(
ref<EvalState> evalState,
const ValMap & extraEnv);
virtual void initEnv() = 0;
virtual void mainLoop() = 0;
virtual ReplExitStatus mainLoop() = 0;
};
}

View File

@@ -65,10 +65,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
if (!attrIndex) {
if (v->type() != nAttrs)
throw TypeError(
state.error<TypeError>(
"the expression selected by the selection path '%1%' should be a set but is %2%",
attrPath,
showType(*v));
showType(*v)).debugThrow();
if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath);
@@ -88,10 +88,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
else {
if (!v->isList())
throw TypeError(
state.error<TypeError>(
"the expression selected by the selection path '%1%' should be a list but is %2%",
attrPath,
showType(*v));
showType(*v)).debugThrow();
if (*attrIndex >= v->listSize())
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath);

View File

@@ -491,7 +491,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
if (forceErrors)
debug("reevaluating failed cached attribute '%s'", getAttrPathStr(name));
else
throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(name));
throw CachedEvalError(root->state, "cached failure of attribute '%s'", getAttrPathStr(name));
} else
return std::make_shared<AttrCursor>(root,
std::make_pair(shared_from_this(), name), nullptr, std::move(attr));
@@ -500,7 +500,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
// evaluate to see whether 'name' exists
} else
return nullptr;
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
}
}
@@ -508,7 +508,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
if (v.type() != nAttrs)
return nullptr;
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
auto attr = v.attrs->get(name);
@@ -574,14 +574,14 @@ std::string AttrCursor::getString()
debug("using cached string attribute '%s'", getAttrPathStr());
return s->first;
} else
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
root->state.error<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
}
}
auto & v = forceValue();
if (v.type() != nString && v.type() != nPath)
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
return v.type() == nString ? v.c_str() : v.path().to_string();
}
@@ -616,7 +616,7 @@ string_t AttrCursor::getStringWithContext()
return *s;
}
} else
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
root->state.error<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
}
}
@@ -630,7 +630,7 @@ string_t AttrCursor::getStringWithContext()
else if (v.type() == nPath)
return {v.path().to_string(), {}};
else
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
}
bool AttrCursor::getBool()
@@ -643,14 +643,14 @@ bool AttrCursor::getBool()
debug("using cached Boolean attribute '%s'", getAttrPathStr());
return *b;
} else
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
}
}
auto & v = forceValue();
if (v.type() != nBool)
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
return v.boolean;
}
@@ -665,14 +665,14 @@ NixInt AttrCursor::getInt()
debug("using cached integer attribute '%s'", getAttrPathStr());
return i->x;
} else
throw TypeError("'%s' is not an integer", getAttrPathStr());
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
}
}
auto & v = forceValue();
if (v.type() != nInt)
throw TypeError("'%s' is not an integer", getAttrPathStr());
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
return v.integer;
}
@@ -687,7 +687,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
debug("using cached list of strings attribute '%s'", getAttrPathStr());
return *l;
} else
throw TypeError("'%s' is not a list of strings", getAttrPathStr());
root->state.error<TypeError>("'%s' is not a list of strings", getAttrPathStr()).debugThrow();
}
}
@@ -697,7 +697,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
root->state.forceValue(v, noPos);
if (v.type() != nList)
throw TypeError("'%s' is not a list", getAttrPathStr());
root->state.error<TypeError>("'%s' is not a list", getAttrPathStr()).debugThrow();
std::vector<std::string> res;
@@ -720,14 +720,14 @@ std::vector<Symbol> AttrCursor::getAttrs()
debug("using cached attrset attribute '%s'", getAttrPathStr());
return *attrs;
} else
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
}
}
auto & v = forceValue();
if (v.type() != nAttrs)
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs)

113
src/libexpr/eval-error.cc Normal file
View File

@@ -0,0 +1,113 @@
#include "eval-error.hh"
#include "eval.hh"
#include "value.hh"
namespace nix {
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withExitStatus(unsigned int exitStatus)
{
error.withExitStatus(exitStatus);
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::atPos(PosIdx pos)
{
error.err.pos = error.state.positions[pos];
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::atPos(Value & value, PosIdx fallback)
{
return atPos(value.determinePos(fallback));
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withTrace(PosIdx pos, const std::string_view text)
{
error.err.traces.push_front(
Trace{.pos = error.state.positions[pos], .hint = HintFmt(std::string(text)), .frame = false});
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withFrameTrace(PosIdx pos, const std::string_view text)
{
error.err.traces.push_front(
Trace{.pos = error.state.positions[pos], .hint = HintFmt(std::string(text)), .frame = true});
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withSuggestions(Suggestions & s)
{
error.err.suggestions = s;
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::withFrame(const Env & env, const Expr & expr)
{
// NOTE: This is abusing side-effects.
// TODO: check compatibility with nested debugger calls.
// TODO: What side-effects??
error.state.debugTraces.push_front(DebugTrace{
.pos = error.state.positions[expr.getPos()],
.expr = expr,
.env = env,
.hint = HintFmt("Fake frame for debugging purposes"),
.isError = true});
return *this;
}
template<class T>
EvalErrorBuilder<T> & EvalErrorBuilder<T>::addTrace(PosIdx pos, HintFmt hint, bool frame)
{
error.addTrace(error.state.positions[pos], hint, frame);
return *this;
}
template<class T>
template<typename... Args>
EvalErrorBuilder<T> &
EvalErrorBuilder<T>::addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs)
{
addTrace(error.state.positions[pos], HintFmt(std::string(formatString), formatArgs...));
return *this;
}
template<class T>
void EvalErrorBuilder<T>::debugThrow()
{
if (error.state.debugRepl && !error.state.debugTraces.empty()) {
const DebugTrace & last = error.state.debugTraces.front();
const Env * env = &last.env;
const Expr * expr = &last.expr;
error.state.runDebugRepl(&error, *env, *expr);
}
// `EvalState` is the only class that can construct an `EvalErrorBuilder`,
// and it does so in dynamic storage. This is the final method called on
// any such instance and must delete itself before throwing the underlying
// error.
auto error = std::move(this->error);
delete this;
throw error;
}
template class EvalErrorBuilder<EvalError>;
template class EvalErrorBuilder<AssertionError>;
template class EvalErrorBuilder<ThrownError>;
template class EvalErrorBuilder<Abort>;
template class EvalErrorBuilder<TypeError>;
template class EvalErrorBuilder<UndefinedVarError>;
template class EvalErrorBuilder<MissingArgumentError>;
template class EvalErrorBuilder<InfiniteRecursionError>;
template class EvalErrorBuilder<CachedEvalError>;
template class EvalErrorBuilder<InvalidPathError>;
}

104
src/libexpr/eval-error.hh Normal file
View File

@@ -0,0 +1,104 @@
#pragma once
#include <algorithm>
#include "error.hh"
#include "pos-idx.hh"
namespace nix {
struct Env;
struct Expr;
struct Value;
class EvalState;
template<class T>
class EvalErrorBuilder;
class EvalError : public Error
{
template<class T>
friend class EvalErrorBuilder;
public:
EvalState & state;
EvalError(EvalState & state, ErrorInfo && errorInfo)
: Error(errorInfo)
, state(state)
{
}
template<typename... Args>
explicit EvalError(EvalState & state, const std::string & formatString, const Args &... formatArgs)
: Error(formatString, formatArgs...)
, state(state)
{
}
};
MakeError(ParseError, Error);
MakeError(AssertionError, EvalError);
MakeError(ThrownError, AssertionError);
MakeError(Abort, EvalError);
MakeError(TypeError, EvalError);
MakeError(UndefinedVarError, EvalError);
MakeError(MissingArgumentError, EvalError);
MakeError(CachedEvalError, EvalError);
MakeError(InfiniteRecursionError, EvalError);
struct InvalidPathError : public EvalError
{
public:
Path path;
InvalidPathError(EvalState & state, const Path & path)
: EvalError(state, "path '%s' is not valid", path)
{
}
};
/**
* `EvalErrorBuilder`s may only be constructed by `EvalState`. The `debugThrow`
* method must be the final method in any such `EvalErrorBuilder` usage, and it
* handles deleting the object.
*/
template<class T>
class EvalErrorBuilder final
{
friend class EvalState;
template<typename... Args>
explicit EvalErrorBuilder(EvalState & state, const Args &... args)
: error(T(state, args...))
{
}
public:
T error;
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withExitStatus(unsigned int exitStatus);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & atPos(PosIdx pos);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & atPos(Value & value, PosIdx fallback = noPos);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withFrameTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withSuggestions(Suggestions & s);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & withFrame(const Env & e, const Expr & ex);
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> & addTrace(PosIdx pos, HintFmt hint, bool frame = false);
template<typename... Args>
[[nodiscard, gnu::noinline]] EvalErrorBuilder<T> &
addTrace(PosIdx pos, std::string_view formatString, const Args &... formatArgs);
/**
* Delete the `EvalErrorBuilder` and throw the underlying exception.
*/
[[gnu::noinline, gnu::noreturn]] void debugThrow();
};
}

View File

@@ -3,6 +3,7 @@
#include "print.hh"
#include "eval.hh"
#include "eval-error.hh"
namespace nix {
@@ -115,10 +116,11 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e
PosIdx pos = getPos();
forceValue(v, pos);
if (v.type() != nAttrs) {
error("expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withTrace(pos, errorCtx).debugThrow<TypeError>();
error<TypeError>(
"expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).withTrace(pos, errorCtx).debugThrow();
}
}
@@ -128,10 +130,11 @@ inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view e
{
forceValue(v, pos);
if (!v.isList()) {
error("expected a list but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withTrace(pos, errorCtx).debugThrow<TypeError>();
error<TypeError>(
"expected a list but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).withTrace(pos, errorCtx).debugThrow();
}
}

View File

@@ -127,6 +127,16 @@ struct EvalSettings : Config
Setting<unsigned int> maxCallDepth{this, 10000, "max-call-depth",
"The maximum function call depth to allow before erroring."};
Setting<bool> builtinsTraceDebugger{this, false, "debugger-on-trace",
R"(
If set to true and the `--debugger` flag is given,
[`builtins.trace`](@docroot@/language/builtins.md#builtins-trace) will
enter the debugger like
[`builtins.break`](@docroot@/language/builtins.md#builtins-break).
This is useful for debugging warnings in third-party Nix code.
)"};
};
extern EvalSettings evalSettings;

View File

@@ -3,6 +3,7 @@
#include "hash.hh"
#include "primops.hh"
#include "print-options.hh"
#include "shared.hh"
#include "types.hh"
#include "util.hh"
#include "store-api.hh"
@@ -339,46 +340,6 @@ void initGC()
gcInitialised = true;
}
ErrorBuilder & ErrorBuilder::atPos(PosIdx pos)
{
info.errPos = state.positions[pos];
return *this;
}
ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text)
{
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false });
return *this;
}
ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text)
{
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true });
return *this;
}
ErrorBuilder & ErrorBuilder::withSuggestions(Suggestions & s)
{
info.suggestions = s;
return *this;
}
ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr)
{
// NOTE: This is abusing side-effects.
// TODO: check compatibility with nested debugger calls.
state.debugTraces.push_front(DebugTrace {
.pos = nullptr,
.expr = expr,
.env = env,
.hint = hintformat("Fake frame for debugging purposes"),
.isError = true
});
return *this;
}
EvalState::EvalState(
const SearchPath & _searchPath,
ref<Store> store,
@@ -434,14 +395,14 @@ EvalState::EvalState(
, emptyBindings(0)
, rootFS(
evalSettings.restrictEval || evalSettings.pureEval
? ref<InputAccessor>(AllowListInputAccessor::create(makeFSInputAccessor(CanonPath::root), {},
? ref<InputAccessor>(AllowListInputAccessor::create(makeFSInputAccessor(), {},
[](const CanonPath & path) -> RestrictedPathError {
auto modeInformation = evalSettings.pureEval
? "in pure evaluation mode (use '--impure' to override)"
: "in restricted mode";
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
}))
: makeFSInputAccessor(CanonPath::root))
: makeFSInputAccessor())
, corepkgsFS(makeMemoryInputAccessor())
, internalFS(makeMemoryInputAccessor())
, derivationInternal{corepkgsFS->addFile(
@@ -456,7 +417,6 @@ EvalState::EvalState(
, buildStore(buildStore ? buildStore : store)
, debugRepl(nullptr)
, debugStop(false)
, debugQuit(false)
, trylevel(0)
, regexCache(makeRegexCache())
#if HAVE_BOEHMGC
@@ -507,13 +467,13 @@ EvalState::~EvalState()
void EvalState::allowPath(const Path & path)
{
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListInputAccessor>())
rootFS2->allowPath(CanonPath(path));
rootFS2->allowPrefix(CanonPath(path));
}
void EvalState::allowPath(const StorePath & storePath)
{
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListInputAccessor>())
rootFS2->allowPath(CanonPath(store->toRealPath(storePath)));
rootFS2->allowPrefix(CanonPath(store->toRealPath(storePath)));
}
void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value & v)
@@ -744,7 +704,8 @@ void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env &
if (se.up && env.up) {
std::cout << "static: ";
printStaticEnvBindings(st, se);
printWithBindings(st, env);
if (se.isWith)
printWithBindings(st, env);
std::cout << std::endl;
printEnvBindings(st, *se.up, *env.up, ++lvl);
} else {
@@ -756,7 +717,8 @@ void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env &
std::cout << st[i.first] << " ";
std::cout << ANSI_NORMAL;
std::cout << std::endl;
printWithBindings(st, env); // probably nothing there for the top level.
if (se.isWith)
printWithBindings(st, env); // probably nothing there for the top level.
std::cout << std::endl;
}
@@ -778,7 +740,7 @@ void mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const En
if (env.up && se.up) {
mapStaticEnvBindings(st, *se.up, *env.up, vm);
if (!env.values[0]->isThunk()) {
if (se.isWith && !env.values[0]->isThunk()) {
// add 'with' bindings.
Bindings::iterator j = env.values[0]->attrs->begin();
while (j != env.values[0]->attrs->end()) {
@@ -811,7 +773,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
? std::make_unique<DebugTraceStacker>(
*this,
DebugTrace {
.pos = error->info().errPos ? error->info().errPos : positions[expr.getPos()],
.pos = error->info().pos ? error->info().pos : positions[expr.getPos()],
.expr = expr,
.env = env,
.hint = error->info().msg,
@@ -821,18 +783,26 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
if (error)
{
printError("%s\n\n", error->what());
printError("%s\n", error->what());
if (trylevel > 0 && error->info().level != lvlInfo)
printError("This exception occurred in a 'tryEval' call. Use " ANSI_GREEN "--ignore-try" ANSI_NORMAL " to skip these.\n");
printError(ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL);
}
auto se = getStaticEnv(expr);
if (se) {
auto vm = mapStaticEnvBindings(symbols, *se.get(), env);
(debugRepl)(ref<EvalState>(shared_from_this()), *vm);
auto exitStatus = (debugRepl)(ref<EvalState>(shared_from_this()), *vm);
switch (exitStatus) {
case ReplExitStatus::QuitAll:
if (error)
throw *error;
throw Exit(0);
case ReplExitStatus::Continue:
break;
default:
abort();
}
}
}
@@ -843,23 +813,23 @@ void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2)
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame) const
{
e.addTrace(positions[pos], hintfmt(s, s2), frame);
e.addTrace(positions[pos], HintFmt(s, s2), frame);
}
template<typename... Args>
static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
EvalState & state,
Expr & expr,
Env & env,
std::shared_ptr<Pos> && pos,
const char * s,
const std::string & s2)
const Args & ... formatArgs)
{
return std::make_unique<DebugTraceStacker>(state,
DebugTrace {
.pos = std::move(pos),
.expr = expr,
.env = env,
.hint = hintfmt(s, s2),
.hint = HintFmt(formatArgs...),
.isError = false
});
}
@@ -930,7 +900,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
return j->value;
}
if (!fromWith->parentWith)
error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow<UndefinedVarError>();
error<UndefinedVarError>("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow();
for (size_t l = fromWith->prevWith; l; --l, env = env->up) ;
fromWith = fromWith->parentWith;
}
@@ -1136,7 +1106,7 @@ void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
// computation.
if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e)))
error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
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());
@@ -1167,10 +1137,11 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::stri
Value v;
e->eval(*this, env, v);
if (v.type() != nBool)
error("expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withFrame(env, *e).debugThrow<TypeError>();
error<TypeError>(
"expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).withFrame(env, *e).debugThrow();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
@@ -1184,10 +1155,11 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po
try {
e->eval(*this, env, v);
if (v.type() != nAttrs)
error("expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withFrame(env, *e).debugThrow<TypeError>();
error<TypeError>(
"expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).withFrame(env, *e).debugThrow();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
@@ -1296,7 +1268,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
auto nameSym = state.symbols.create(nameVal.string_view());
Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end())
state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>();
state.error<EvalError>("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow();
i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */
@@ -1322,6 +1294,19 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
for (auto & i : attrs->attrs)
env2.values[displ++] = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
auto dts = state.debugRepl
? makeDebugTraceStacker(
state,
*this,
env2,
getPos()
? std::make_shared<Pos>(state.positions[getPos()])
: nullptr,
"while evaluating a '%1%' expression",
"let"
)
: nullptr;
body->eval(state, env2, v);
}
@@ -1408,8 +1393,8 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
for (auto & attr : *vAttrs->attrs)
allAttrNames.insert(state.symbols[attr.name]);
auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
state.error("attribute '%1%' missing", state.symbols[name])
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow<EvalError>();
state.error<EvalError>("attribute '%1%' missing", state.symbols[name])
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow();
}
}
vAttrs = j->value;
@@ -1482,7 +1467,7 @@ public:
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
{
if (callDepth > evalSettings.maxCallDepth)
error("stack overflow; max-call-depth exceeded").atPos(pos).template debugThrow<EvalError>();
error<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
CallDepth _level(callDepth);
auto trace = evalSettings.traceFunctionCalls
@@ -1540,13 +1525,13 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
auto j = args[0]->attrs->get(i.name);
if (!j) {
if (!i.def) {
error("function '%1%' called without required argument '%2%'",
error<TypeError>("function '%1%' called without required argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name])
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withFrame(*fun.lambda.env, lambda)
.debugThrow<TypeError>();
.debugThrow();
}
env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else {
@@ -1566,14 +1551,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (auto & formal : lambda.formals->formals)
formalNames.insert(symbols[formal.name]);
auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
error("function '%1%' called with unexpected argument '%2%'",
error<TypeError>("function '%1%' called with unexpected argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name])
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withSuggestions(suggestions)
.withFrame(*fun.lambda.env, lambda)
.debugThrow<TypeError>();
.debugThrow();
}
abort(); // can't happen
}
@@ -1705,11 +1690,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
}
else
error("attempt to call something which is not a function but %1%: %2%",
error<TypeError>(
"attempt to call something which is not a function but %1%: %2%",
showType(vCur),
ValuePrinter(*this, vCur, errorPrintOptions))
.atPos(pos)
.debugThrow<TypeError>();
.debugThrow();
}
vRes = vCur;
@@ -1718,6 +1704,18 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
void ExprCall::eval(EvalState & state, Env & env, Value & v)
{
auto dts = state.debugRepl
? makeDebugTraceStacker(
state,
*this,
env,
getPos()
? std::make_shared<Pos>(state.positions[getPos()])
: nullptr,
"while calling a function"
)
: nullptr;
Value vFun;
fun->eval(state, env, vFun);
@@ -1779,12 +1777,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
if (j != args.end()) {
attrs.insert(*j);
} else if (!i.def) {
error(R"(cannot evaluate a function that has an argument without a value ('%1%')
error<MissingArgumentError>(R"(cannot evaluate a function that has an argument without a value ('%1%')
Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name])
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow<MissingArgumentError>();
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow();
}
}
}
@@ -1815,7 +1813,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);
state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow<AssertionError>();
state.error<AssertionError>("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow();
}
body->eval(state, env, v);
}
@@ -1993,14 +1991,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n;
nf += vTmp.fpoint;
} else
state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
state.error<EvalError>("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
} else if (firstType == nFloat) {
if (vTmp.type() == nInt) {
nf += vTmp.integer;
} else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint;
} else
state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
state.error<EvalError>("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
} else {
if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not
@@ -2022,7 +2020,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
v.mkFloat(nf);
else if (firstType == nPath) {
if (!context.empty())
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
state.error<EvalError>("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow();
v.mkPath(state.rootPath(CanonPath(canonPath(str()))));
} else
v.mkStringMove(c_str(), context);
@@ -2037,8 +2035,9 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v)
void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
{
state.error("infinite recursion encountered")
.debugThrow<InfiniteRecursionError>();
state.error<InfiniteRecursionError>("infinite recursion encountered")
.atPos(v.determinePos(noPos))
.debugThrow();
}
// always force this to be separate, otherwise forceValue may inline it and take
@@ -2052,7 +2051,7 @@ void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos)
try {
std::rethrow_exception(e);
} catch (InfiniteRecursionError & e) {
e.err.errPos = positions[pos];
e.atPos(positions[pos]);
} catch (...) {
}
}
@@ -2100,15 +2099,18 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt
try {
forceValue(v, pos);
if (v.type() != nInt)
error("expected an integer but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
error<TypeError>(
"expected an integer but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.integer;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
return v.integer;
}
@@ -2119,10 +2121,11 @@ NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view err
if (v.type() == nInt)
return v.integer;
else if (v.type() != nFloat)
error("expected a float but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
error<TypeError>(
"expected a float but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.fpoint;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
@@ -2136,15 +2139,18 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx
try {
forceValue(v, pos);
if (v.type() != nBool)
error("expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
error<TypeError>(
"expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
return v.boolean;
}
@@ -2159,10 +2165,11 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro
try {
forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v))
error("expected a function but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
error<TypeError>(
"expected a function but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
@@ -2175,10 +2182,11 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
try {
forceValue(v, pos);
if (v.type() != nString)
error("expected a string but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
error<TypeError>(
"expected a string but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
return v.string_view();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
@@ -2207,7 +2215,7 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s
{
auto s = forceString(v, pos, errorCtx);
if (v.context()) {
error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow<EvalError>();
error<EvalError>("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string_view(), v.context()[0]).withTrace(pos, errorCtx).debugThrow();
}
return s;
}
@@ -2272,11 +2280,13 @@ BackedStringView EvalState::coerceToString(
return std::move(*maybeString);
auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end()) {
error("cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
error<TypeError>(
"cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
)
.withTrace(pos, errorCtx)
.debugThrow<TypeError>();
.debugThrow();
}
return coerceToString(pos, *i->value, context, errorCtx,
coerceMore, copyToStore, canonicalizePath);
@@ -2284,7 +2294,7 @@ BackedStringView EvalState::coerceToString(
if (v.type() == nExternal) {
try {
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
return v.external->coerceToString(*this, pos, context, coerceMore, copyToStore);
} catch (Error & e) {
e.addTrace(nullptr, errorCtx);
throw;
@@ -2320,25 +2330,33 @@ BackedStringView EvalState::coerceToString(
}
}
error("cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
error<TypeError>("cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
)
.withTrace(pos, errorCtx)
.debugThrow<TypeError>();
.debugThrow();
}
StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path)
{
if (nix::isDerivation(path.path.abs()))
error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow();
auto i = srcToStore.find(path);
auto dstPath = i != srcToStore.end()
? i->second
: [&]() {
auto dstPath = fetchToStore(*store, path, path.baseName(), FileIngestionMethod::Recursive, nullptr, repair);
auto dstPath = fetchToStore(
*store,
path.resolveSymlinks(),
settings.readOnlyMode ? FetchMode::DryRun : FetchMode::Copy,
path.baseName(),
FileIngestionMethod::Recursive,
nullptr,
repair);
allowPath(dstPath);
srcToStore.insert_or_assign(path, dstPath);
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath));
@@ -2380,7 +2398,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext
relative to the root filesystem. */
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (path == "" || path[0] != '/')
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
error<EvalError>("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow();
return rootPath(CanonPath(path));
}
@@ -2390,7 +2408,7 @@ StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringCon
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
if (auto storePath = store->maybeParseStorePath(path))
return *storePath;
error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
error<EvalError>("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow();
}
@@ -2400,18 +2418,18 @@ std::pair<SingleDerivedPath, std::string_view> EvalState::coerceToSingleDerivedP
auto s = forceString(v, context, pos, errorCtx);
auto csize = context.size();
if (csize != 1)
error(
error<EvalError>(
"string '%s' has %d entries in its context. It should only have exactly one entry",
s, csize)
.withTrace(pos, errorCtx).debugThrow<EvalError>();
.withTrace(pos, errorCtx).debugThrow();
auto derivedPath = std::visit(overloaded {
[&](NixStringContextElem::Opaque && o) -> SingleDerivedPath {
return std::move(o);
},
[&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath {
error(
error<EvalError>(
"string '%s' has a context which refers to a complete source and binary closure. This is not supported at this time",
s).withTrace(pos, errorCtx).debugThrow<EvalError>();
s).withTrace(pos, errorCtx).debugThrow();
},
[&](NixStringContextElem::Built && b) -> SingleDerivedPath {
return std::move(b);
@@ -2434,16 +2452,16 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value &
error message. */
std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) {
error(
error<EvalError>(
"path string '%s' has context with the different path '%s'",
s, sExpected)
.withTrace(pos, errorCtx).debugThrow<EvalError>();
.withTrace(pos, errorCtx).debugThrow();
},
[&](const SingleDerivedPath::Built & b) {
error(
error<EvalError>(
"string '%s' has context with the output '%s' from derivation '%s', but the string is not the right placeholder for this derivation output. It should be '%s'",
s, b.output, b.drvPath->to_string(*store), sExpected)
.withTrace(pos, errorCtx).debugThrow<EvalError>();
.withTrace(pos, errorCtx).debugThrow();
}
}, derivedPath.raw());
}
@@ -2528,7 +2546,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
case nThunk: // Must not be left by forceValue
default:
error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>();
error<EvalError>("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow();
}
}
@@ -2689,14 +2707,14 @@ SourcePath resolveExprPath(SourcePath path)
// Basic cycle/depth limit to avoid infinite loops.
if (++followCount >= maxFollow)
throw Error("too many symbolic links encountered while traversing the path '%s'", path);
auto p = path.parent().resolveSymlinks() + path.baseName();
auto p = path.parent().resolveSymlinks() / path.baseName();
if (p.lstat().type != InputAccessor::tSymlink) break;
path = {path.accessor, CanonPath(p.readLink(), path.path.parent().value_or(CanonPath::root))};
}
/* If `path' refers to a directory, append `/default.nix'. */
if (path.resolveSymlinks().lstat().type == InputAccessor::tDirectory)
return path + "default.nix";
return path / "default.nix";
return path;
}
@@ -2738,7 +2756,7 @@ Expr * EvalState::parseStdin()
// drainFD should have left some extra space for terminators
buffer.append("\0\0", 2);
auto s = make_ref<std::string>(std::move(buffer));
return parse(s->data(), s->size(), Pos::Stdin{.source = s}, rootPath(CanonPath::fromCwd()), staticBaseEnv);
return parse(s->data(), s->size(), Pos::Stdin{.source = s}, rootPath("."), staticBaseEnv);
}
@@ -2767,13 +2785,12 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_
if (hasPrefix(path, "nix/"))
return {corepkgsFS, CanonPath(path.substr(3))};
debugThrow(ThrownError({
.msg = hintfmt(evalSettings.pureEval
error<ThrownError>(
evalSettings.pureEval
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
path),
.errPos = positions[pos]
}), 0, 0);
path
).atPos(pos).debugThrow();
}
@@ -2787,12 +2804,13 @@ std::optional<std::string> EvalState::resolveSearchPathPath(const SearchPath::Pa
if (EvalSettings::isPseudoUrl(value)) {
try {
auto storePath = fetchers::downloadTarball(
store, EvalSettings::resolvePseudoUrl(value), "source", false).storePath;
auto accessor = fetchers::downloadTarball(
EvalSettings::resolvePseudoUrl(value)).accessor;
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
res = { store->toRealPath(storePath) };
} catch (FileTransferError & e) {
} catch (Error & e) {
logWarning({
.msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
.msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
});
}
}
@@ -2825,7 +2843,7 @@ std::optional<std::string> EvalState::resolveSearchPathPath(const SearchPath::Pa
res = { path };
else {
logWarning({
.msg = hintfmt("Nix search path entry '%1%' does not exist, ignoring", value)
.msg = HintFmt("Nix search path entry '%1%' does not exist, ignoring", value)
});
res = std::nullopt;
}
@@ -2856,11 +2874,11 @@ Expr * EvalState::parse(
}
std::string ExternalValueBase::coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
{
throw TypeError({
.msg = hintfmt("cannot coerce %1% to a string: %2%", showType(), *this)
});
state.error<TypeError>(
"cannot coerce %1% to a string: %2%", showType(), *this
).atPos(pos).debugThrow();
}

View File

@@ -2,6 +2,7 @@
///@file
#include "attr-set.hh"
#include "eval-error.hh"
#include "types.hh"
#include "value.hh"
#include "nixexpr.hh"
@@ -10,6 +11,7 @@
#include "experimental-features.hh"
#include "input-accessor.hh"
#include "search-path.hh"
#include "repl-exit-status.hh"
#include <map>
#include <optional>
@@ -147,49 +149,10 @@ struct DebugTrace {
std::shared_ptr<Pos> pos;
const Expr & expr;
const Env & env;
hintformat hint;
HintFmt hint;
bool isError;
};
void debugError(Error * e, Env & env, Expr & expr);
class ErrorBuilder
{
private:
EvalState & state;
ErrorInfo info;
ErrorBuilder(EvalState & s, ErrorInfo && i): state(s), info(i) { }
public:
template<typename... Args>
[[nodiscard, gnu::noinline]]
static ErrorBuilder * create(EvalState & s, const Args & ... args)
{
return new ErrorBuilder(s, ErrorInfo { .msg = hintfmt(args...) });
}
[[nodiscard, gnu::noinline]]
ErrorBuilder & atPos(PosIdx pos);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withSuggestions(Suggestions & s);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withFrame(const Env & e, const Expr & ex);
template<class ErrorType>
[[gnu::noinline, gnu::noreturn]]
void debugThrow();
};
class EvalState : public std::enable_shared_from_this<EvalState>
{
public:
@@ -257,9 +220,8 @@ public:
/**
* Debugger
*/
void (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv);
ReplExitStatus (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv);
bool debugStop;
bool debugQuit;
int trylevel;
std::list<DebugTrace> debugTraces;
std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs;
@@ -274,39 +236,11 @@ public:
void runDebugRepl(const Error * error, const Env & env, const Expr & expr);
template<class E>
[[gnu::noinline, gnu::noreturn]]
void debugThrowLastTrace(E && error)
{
debugThrow(error, nullptr, nullptr);
}
template<class E>
[[gnu::noinline, gnu::noreturn]]
void debugThrow(E && error, const Env * env, const Expr * expr)
{
if (debugRepl && ((env && expr) || !debugTraces.empty())) {
if (!env || !expr) {
const DebugTrace & last = debugTraces.front();
env = &last.env;
expr = &last.expr;
}
runDebugRepl(&error, *env, *expr);
}
throw std::move(error);
}
// This is dangerous, but gets in line with the idea that error creation and
// throwing should not allocate on the stack of hot functions.
// as long as errors are immediately thrown, it works.
ErrorBuilder * errorBuilder;
template<typename... Args>
template<class T, typename... Args>
[[nodiscard, gnu::noinline]]
ErrorBuilder & error(const Args & ... args) {
errorBuilder = ErrorBuilder::create(*this, args...);
return *errorBuilder;
EvalErrorBuilder<T> & error(const Args & ... args) {
// `EvalErrorBuilder::debugThrow` performs the corresponding `delete`.
return *new EvalErrorBuilder<T>(*this, args...);
}
private:
@@ -372,6 +306,11 @@ public:
*/
SourcePath rootPath(CanonPath path);
/**
* Variant which accepts relative paths too.
*/
SourcePath rootPath(PathView path);
/**
* Allow access to a path.
*/
@@ -819,7 +758,6 @@ struct DebugTraceStacker {
DebugTraceStacker(EvalState & evalState, DebugTrace t);
~DebugTraceStacker()
{
// assert(evalState.debugTraces.front() == trace);
evalState.debugTraces.pop_front();
}
EvalState & evalState;
@@ -845,22 +783,6 @@ SourcePath resolveExprPath(SourcePath path);
*/
bool isAllowedURI(std::string_view uri, const Strings & allowedPaths);
struct InvalidPathError : EvalError
{
Path path;
InvalidPathError(const Path & path);
#ifdef EXCEPTION_NEEDS_THROW_SPEC
~InvalidPathError() throw () { };
#endif
};
template<class ErrorType>
void ErrorBuilder::debugThrow()
{
// NOTE: We always use the -LastTrace version as we push the new trace in withFrame()
state.debugThrowLastTrace(ErrorType(info));
}
}
#include "eval-inline.hh"

View File

@@ -1,20 +1,52 @@
lockFileStr: rootSrc: rootSubdir:
# This is a helper to callFlake() to lazily fetch flake inputs.
# The contents of the lock file, in JSON format.
lockFileStr:
# A mapping of lock file node IDs to { sourceInfo, subdir } attrsets,
# with sourceInfo.outPath providing an InputAccessor to a previously
# fetched tree. This is necessary for possibly unlocked inputs, in
# particular the root input, but also --override-inputs pointing to
# unlocked trees.
overrides:
let
lockFile = builtins.fromJSON lockFileStr;
# Resolve a input spec into a node name. An input spec is
# either a node name, or a 'follows' path from the root
# node.
resolveInput = inputSpec:
if builtins.isList inputSpec
then getInputByPath lockFile.root inputSpec
else inputSpec;
# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
# root node, returning the final node.
getInputByPath = nodeName: path:
if path == []
then nodeName
else
getInputByPath
# Since this could be a 'follows' input, call resolveInput.
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
(builtins.tail path);
allNodes =
builtins.mapAttrs
(key: node:
let
sourceInfo =
if key == lockFile.root
then rootSrc
else fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
if overrides ? ${key}
then
overrides.${key}.sourceInfo
else
# FIXME: remove obsolete node.info.
fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
subdir = if key == lockFile.root then rootSubdir else node.locked.dir or "";
subdir = overrides.${key}.dir or node.locked.dir or "";
outPath = sourceInfo + ((if subdir == "" then "" else "/") + subdir);
@@ -24,25 +56,6 @@ let
(inputName: inputSpec: allNodes.${resolveInput inputSpec})
(node.inputs or {});
# Resolve a input spec into a node name. An input spec is
# either a node name, or a 'follows' path from the root
# node.
resolveInput = inputSpec:
if builtins.isList inputSpec
then getInputByPath lockFile.root inputSpec
else inputSpec;
# Follow an input path (e.g. ["dwarffs" "nixpkgs"]) from the
# root node, returning the final node.
getInputByPath = nodeName: path:
if path == []
then nodeName
else
getInputByPath
# Since this could be a 'follows' input, call resolveInput.
(resolveInput lockFile.nodes.${nodeName}.inputs.${builtins.head path})
(builtins.tail path);
outputs = flake.outputs (inputs // { self = result; });
result =

View File

@@ -147,15 +147,15 @@ static FlakeInput parseFlakeInput(EvalState & state,
NixStringContext emptyContext = {};
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, emptyContext).dump());
} else
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.symbols[attr.name], showType(*attr.value));
state.error<TypeError>("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.symbols[attr.name], showType(*attr.value)).debugThrow();
}
#pragma GCC diagnostic pop
}
} catch (Error & e) {
e.addTrace(
state.positions[attr.pos],
hintfmt("while evaluating flake attribute '%s'", state.symbols[attr.name]));
HintFmt("while evaluating flake attribute '%s'", state.symbols[attr.name]));
throw;
}
}
@@ -164,7 +164,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
try {
input.ref = FlakeRef::fromAttrs(attrs);
} catch (Error & e) {
e.addTrace(state.positions[pos], hintfmt("while evaluating flake input"));
e.addTrace(state.positions[pos], HintFmt("while evaluating flake input"));
throw;
}
else {
@@ -295,15 +295,15 @@ static Flake getFlake(
std::vector<std::string> ss;
for (auto elem : setting.value->listItems()) {
if (elem->type() != nString)
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
state.symbols[setting.name], showType(*setting.value));
state.error<TypeError>("list element in flake configuration setting '%s' is %s while a string is expected",
state.symbols[setting.name], showType(*setting.value)).debugThrow();
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
}
flake.config.settings.emplace(state.symbols[setting.name], ss);
}
else
throw TypeError("flake configuration setting '%s' is %s",
state.symbols[setting.name], showType(*setting.value));
state.error<TypeError>("flake configuration setting '%s' is %s",
state.symbols[setting.name], showType(*setting.value)).debugThrow();
}
}
@@ -365,6 +365,7 @@ LockedFlake lockFlake(
std::map<InputPath, FlakeInput> overrides;
std::set<InputPath> explicitCliOverrides;
std::set<InputPath> overridesUsed, updatesUsed;
std::map<ref<Node>, StorePath> nodePaths;
for (auto & i : lockFlags.inputOverrides) {
overrides.insert_or_assign(i.first, FlakeInput { .ref = i.second });
@@ -535,11 +536,13 @@ LockedFlake lockFlake(
}
}
computeLocks(
mustRefetch
? getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath).inputs
: fakeInputs,
childNode, inputPath, oldLock, lockRootPath, parentPath, !mustRefetch);
if (mustRefetch) {
auto inputFlake = getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath);
nodePaths.emplace(childNode, inputFlake.storePath);
computeLocks(inputFlake.inputs, childNode, inputPath, oldLock, lockRootPath, parentPath, false);
} else {
computeLocks(fakeInputs, childNode, inputPath, oldLock, lockRootPath, parentPath, true);
}
} else {
/* We need to create a new lock file entry. So fetch
@@ -584,6 +587,7 @@ LockedFlake lockFlake(
flake. Also, unless we already have this flake
in the top-level lock file, use this flake's
own lock file. */
nodePaths.emplace(childNode, inputFlake.storePath);
computeLocks(
inputFlake.inputs, childNode, inputPath,
oldLock
@@ -596,11 +600,13 @@ LockedFlake lockFlake(
}
else {
auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree(
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
state, *input.ref, useRegistries, flakeCache);
auto childNode = make_ref<LockedNode>(lockedRef, ref, false);
nodePaths.emplace(childNode, storePath);
node->inputs.insert_or_assign(id, childNode);
}
}
@@ -615,6 +621,8 @@ LockedFlake lockFlake(
// Bring in the current ref for relative path resolution if we have it
auto parentPath = canonPath(state.store->toRealPath(flake.storePath) + "/" + flake.lockedRef.subdir, true);
nodePaths.emplace(newLockFile.root, flake.storePath);
computeLocks(
flake.inputs,
newLockFile.root,
@@ -707,14 +715,6 @@ LockedFlake lockFlake(
flake.lockedRef.input.getRev() &&
prevLockedRef.input.getRev() != flake.lockedRef.input.getRev())
warn("committed new revision '%s'", flake.lockedRef.input.getRev()->gitRev());
/* Make sure that we picked up the change,
i.e. the tree should usually be dirty
now. Corner case: we could have reverted from a
dirty to a clean tree! */
if (flake.lockedRef.input == prevLockedRef.input
&& !flake.lockedRef.input.isLocked())
throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
}
} else
throw Error("cannot write modified lock file of flake '%s' (use '--no-write-lock-file' to ignore)", topRef);
@@ -724,7 +724,11 @@ LockedFlake lockFlake(
}
}
return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
return LockedFlake {
.flake = std::move(flake),
.lockFile = std::move(newLockFile),
.nodePaths = std::move(nodePaths)
};
} catch (Error & e) {
e.addTrace({}, "while updating the lock file of flake '%s'", flake.lockedRef.to_string());
@@ -736,30 +740,48 @@ void callFlake(EvalState & state,
const LockedFlake & lockedFlake,
Value & vRes)
{
auto vLocks = state.allocValue();
auto vRootSrc = state.allocValue();
auto vRootSubdir = state.allocValue();
auto vTmp1 = state.allocValue();
auto vTmp2 = state.allocValue();
experimentalFeatureSettings.require(Xp::Flakes);
vLocks->mkString(lockedFlake.lockFile.to_string());
auto [lockFileStr, keyMap] = lockedFlake.lockFile.to_string();
emitTreeAttrs(
state,
lockedFlake.flake.storePath,
lockedFlake.flake.lockedRef.input,
*vRootSrc,
false,
lockedFlake.flake.forceDirty);
auto overrides = state.buildBindings(lockedFlake.nodePaths.size());
vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);
for (auto & [node, storePath] : lockedFlake.nodePaths) {
auto override = state.buildBindings(2);
auto & vSourceInfo = override.alloc(state.symbols.create("sourceInfo"));
auto lockedNode = node.dynamic_pointer_cast<const LockedNode>();
emitTreeAttrs(
state,
storePath,
lockedNode ? lockedNode->lockedRef.input : lockedFlake.flake.lockedRef.input,
vSourceInfo,
false,
!lockedNode && lockedFlake.flake.forceDirty);
auto key = keyMap.find(node);
assert(key != keyMap.end());
override
.alloc(state.symbols.create("dir"))
.mkString(lockedNode ? lockedNode->lockedRef.subdir : lockedFlake.flake.lockedRef.subdir);
overrides.alloc(state.symbols.create(key->second)).mkAttrs(override);
}
auto & vOverrides = state.allocValue()->mkAttrs(overrides);
auto vCallFlake = state.allocValue();
state.evalFile(state.callFlakeInternal, *vCallFlake);
auto vTmp1 = state.allocValue();
auto vLocks = state.allocValue();
vLocks->mkString(lockFileStr);
state.callFunction(*vCallFlake, *vLocks, *vTmp1, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
state.callFunction(*vTmp1, vOverrides, vRes, noPos);
}
static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
@@ -865,11 +887,11 @@ static void prim_flakeRefToString(
attrs.emplace(state.symbols[attr.name],
std::string(attr.value->string_view()));
} else {
state.error(
state.error<EvalError>(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.symbols[attr.name],
showType(*attr.value)).debugThrow<EvalError>();
showType(*attr.value)).debugThrow();
}
}
auto flakeRef = FlakeRef::fromAttrs(attrs);

View File

@@ -103,6 +103,13 @@ struct LockedFlake
Flake flake;
LockFile lockFile;
/**
* Store paths of nodes that have been fetched in
* lockFlake(); in particular, the root node and the overriden
* inputs.
*/
std::map<ref<Node>, StorePath> nodePaths;
Fingerprint getFingerprint() const;
};

View File

@@ -38,7 +38,7 @@ LockedNode::LockedNode(const nlohmann::json & json)
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{
if (!lockedRef.input.isLocked())
throw Error("lock file contains mutable lock '%s'",
throw Error("lock file contains unlocked input '%s'",
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
}
@@ -107,7 +107,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
std::string inputKey = i.value();
auto k = nodeMap.find(inputKey);
if (k == nodeMap.end()) {
auto nodes = json["nodes"];
auto & nodes = json["nodes"];
auto jsonNode2 = nodes.find(inputKey);
if (jsonNode2 == nodes.end())
throw Error("lock file references missing node '%s'", inputKey);
@@ -134,10 +134,10 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
// a bit since we don't need to worry about cycles.
}
nlohmann::json LockFile::toJSON() const
std::pair<nlohmann::json, LockFile::KeyMap> LockFile::toJSON() const
{
nlohmann::json nodes;
std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys;
KeyMap nodeKeys;
std::unordered_set<std::string> keys;
std::function<std::string(const std::string & key, ref<const Node> node)> dumpNode;
@@ -194,12 +194,13 @@ nlohmann::json LockFile::toJSON() const
json["root"] = dumpNode("root", root);
json["nodes"] = std::move(nodes);
return json;
return {json, std::move(nodeKeys)};
}
std::string LockFile::to_string() const
std::pair<std::string, LockFile::KeyMap> LockFile::to_string() const
{
return toJSON().dump(2);
auto [json, nodeKeys] = toJSON();
return {json.dump(2), std::move(nodeKeys)};
}
LockFile LockFile::read(const Path & path)
@@ -210,7 +211,7 @@ LockFile LockFile::read(const Path & path)
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
{
stream << lockFile.toJSON().dump(2);
stream << lockFile.toJSON().first.dump(2);
return stream;
}
@@ -243,7 +244,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const
bool LockFile::operator ==(const LockFile & other) const
{
// FIXME: slow
return toJSON() == other.toJSON();
return toJSON().first == other.toJSON().first;
}
bool LockFile::operator !=(const LockFile & other) const

View File

@@ -59,14 +59,15 @@ struct LockFile
typedef std::map<ref<const Node>, std::string> KeyMap;
nlohmann::json toJSON() const;
std::pair<nlohmann::json, KeyMap> toJSON() const;
std::string to_string() const;
std::pair<std::string, KeyMap> to_string() const;
static LockFile read(const Path & path);
/**
* Check whether this lock file has any unlocked inputs.
* Check whether this lock file has any unlocked inputs. If so,
* return one.
*/
std::optional<FlakeRef> isUnlocked() const;

View File

@@ -49,7 +49,7 @@ std::string PackageInfo::queryName() const
{
if (name == "" && attrs) {
auto i = attrs->find(state->sName);
if (i == attrs->end()) throw TypeError("derivation name missing");
if (i == attrs->end()) state->error<TypeError>("derivation name missing").debugThrow();
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
}
return name;
@@ -396,7 +396,8 @@ static void getDerivations(EvalState & state, Value & vIn,
}
}
else throw TypeError("expression does not evaluate to a derivation (or a set or list of those)");
else
state.error<TypeError>("expression does not evaluate to a derivation (or a set or list of those)").debugThrow();
}

View File

@@ -1,4 +1,6 @@
#include "json-to-value.hh"
#include "value.hh"
#include "eval.hh"
#include <variant>
#include <nlohmann/json.hpp>
@@ -159,7 +161,7 @@ public:
}
bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) {
throw JSONParseError(ex.what());
throw JSONParseError("%s", ex.what());
}
};

View File

@@ -1,13 +1,16 @@
#pragma once
///@file
#include "eval.hh"
#include "error.hh"
#include <string>
namespace nix {
MakeError(JSONParseError, EvalError);
class EvalState;
struct Value;
MakeError(JSONParseError, Error);
void parseJSON(EvalState & state, const std::string_view & s, Value & v);

View File

@@ -146,9 +146,9 @@ or { return OR_KW; }
try {
yylval->n = boost::lexical_cast<int64_t>(yytext);
} catch (const boost::bad_lexical_cast &) {
throw ParseError({
.msg = hintfmt("invalid integer '%1%'", yytext),
.errPos = state->positions[CUR_POS],
throw ParseError(ErrorInfo{
.msg = HintFmt("invalid integer '%1%'", yytext),
.pos = state->positions[CUR_POS],
});
}
return INT_LIT;
@@ -156,9 +156,9 @@ or { return OR_KW; }
{FLOAT} { errno = 0;
yylval->nf = strtod(yytext, 0);
if (errno != 0)
throw ParseError({
.msg = hintfmt("invalid float '%1%'", yytext),
.errPos = state->positions[CUR_POS],
throw ParseError(ErrorInfo{
.msg = HintFmt("invalid float '%1%'", yytext),
.pos = state->positions[CUR_POS],
});
return FLOAT_LIT;
}
@@ -285,9 +285,9 @@ or { return OR_KW; }
<INPATH_SLASH>{ANY} |
<INPATH_SLASH><<EOF>> {
throw ParseError({
.msg = hintfmt("path has a trailing slash"),
.errPos = state->positions[CUR_POS],
throw ParseError(ErrorInfo{
.msg = HintFmt("path has a trailing slash"),
.pos = state->positions[CUR_POS],
});
}

View File

@@ -296,10 +296,10 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
enclosing `with'. If there is no `with', then we can issue an
"undefined variable" error now. */
if (withLevel == -1)
throw UndefinedVarError({
.msg = hintfmt("undefined variable '%1%'", es.symbols[name]),
.errPos = es.positions[pos]
});
es.error<UndefinedVarError>(
"undefined variable '%1%'",
es.symbols[name]
).atPos(pos).debugThrow();
for (auto * e = env.get(); e && !fromWith; e = e->up)
fromWith = e->isWith;
this->level = withLevel;
@@ -409,9 +409,6 @@ void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
auto newEnv = std::make_shared<StaticEnv>(nullptr, env.get(), attrs->attrs.size());
Displacement displ = 0;
@@ -423,6 +420,9 @@ void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
for (auto & i : attrs->attrs)
i.second.e->bindVars(es, i.second.inherited ? env : newEnv);
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, newEnv));
body->bindVars(es, newEnv);
}
@@ -447,9 +447,6 @@ void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
break;
}
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
attrs->bindVars(es, env);
auto newEnv = std::make_shared<StaticEnv>(this, env.get());
body->bindVars(es, newEnv);

View File

@@ -9,110 +9,13 @@
#include "error.hh"
#include "chunked-vector.hh"
#include "position.hh"
#include "eval-error.hh"
#include "pos-idx.hh"
#include "pos-table.hh"
namespace nix {
MakeError(EvalError, Error);
MakeError(ParseError, Error);
MakeError(AssertionError, EvalError);
MakeError(ThrownError, AssertionError);
MakeError(Abort, EvalError);
MakeError(TypeError, EvalError);
MakeError(UndefinedVarError, Error);
MakeError(MissingArgumentError, EvalError);
class InfiniteRecursionError : public EvalError
{
friend class EvalState;
public:
using EvalError::EvalError;
};
class PosIdx {
friend class PosTable;
private:
uint32_t id;
explicit PosIdx(uint32_t id): id(id) {}
public:
PosIdx() : id(0) {}
explicit operator bool() const { return id > 0; }
bool operator <(const PosIdx other) const { return id < other.id; }
bool operator ==(const PosIdx other) const { return id == other.id; }
bool operator !=(const PosIdx other) const { return id != other.id; }
};
class PosTable
{
public:
class Origin {
friend PosTable;
private:
// must always be invalid by default, add() replaces this with the actual value.
// subsequent add() calls use this index as a token to quickly check whether the
// current origins.back() can be reused or not.
mutable uint32_t idx = std::numeric_limits<uint32_t>::max();
// Used for searching in PosTable::[].
explicit Origin(uint32_t idx): idx(idx), origin{std::monostate()} {}
public:
const Pos::Origin origin;
Origin(Pos::Origin origin): origin(origin) {}
};
struct Offset {
uint32_t line, column;
};
private:
std::vector<Origin> origins;
ChunkedVector<Offset, 8192> offsets;
public:
PosTable(): offsets(1024)
{
origins.reserve(1024);
}
PosIdx add(const Origin & origin, uint32_t line, uint32_t column)
{
const auto idx = offsets.add({line, column}).second;
if (origins.empty() || origins.back().idx != origin.idx) {
origin.idx = idx;
origins.push_back(origin);
}
return PosIdx(idx + 1);
}
Pos operator[](PosIdx p) const
{
if (p.id == 0 || p.id > offsets.size())
return {};
const auto idx = p.id - 1;
/* we want the last key <= idx, so we'll take prev(first key > idx).
this is guaranteed to never rewind origin.begin because the first
key is always 0. */
const auto pastOrigin = std::upper_bound(
origins.begin(), origins.end(), Origin(idx),
[] (const auto & a, const auto & b) { return a.idx < b.idx; });
const auto origin = *std::prev(pastOrigin);
const auto offset = offsets[idx];
return {offset.line, offset.column, origin.origin};
}
};
inline PosIdx noPos = {};
struct Env;
struct Value;
class EvalState;

View File

@@ -64,17 +64,17 @@ struct ParserState
inline void ParserState::dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos)
{
throw ParseError({
.msg = hintfmt("attribute '%1%' already defined at %2%",
.msg = HintFmt("attribute '%1%' already defined at %2%",
showAttrPath(symbols, attrPath), positions[prevPos]),
.errPos = positions[pos]
.pos = positions[pos]
});
}
inline void ParserState::dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos)
{
throw ParseError({
.msg = hintfmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]),
.errPos = positions[pos]
.msg = HintFmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]),
.pos = positions[pos]
});
}
@@ -154,14 +154,14 @@ inline Formals * ParserState::validateFormals(Formals * formals, PosIdx pos, Sym
}
if (duplicate)
throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[duplicate->first]),
.errPos = positions[duplicate->second]
.msg = HintFmt("duplicate formal function argument '%1%'", symbols[duplicate->first]),
.pos = positions[duplicate->second]
});
if (arg && formals->has(arg))
throw ParseError({
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[arg]),
.errPos = positions[pos]
.msg = HintFmt("duplicate formal function argument '%1%'", symbols[arg]),
.pos = positions[pos]
});
return formals;

View File

@@ -65,8 +65,8 @@ using namespace nix;
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char * error)
{
throw ParseError({
.msg = hintfmt(error),
.errPos = state->positions[state->at(*loc)]
.msg = HintFmt(error),
.pos = state->positions[state->at(*loc)]
});
}
@@ -154,8 +154,8 @@ expr_function
| LET binds IN_KW expr_function
{ if (!$2->dynamicAttrs.empty())
throw ParseError({
.msg = hintfmt("dynamic attributes not allowed in let"),
.errPos = state->positions[CUR_POS]
.msg = HintFmt("dynamic attributes not allowed in let"),
.pos = state->positions[CUR_POS]
});
$$ = new ExprLet($2, $4);
}
@@ -244,8 +244,8 @@ expr_simple
static bool noURLLiterals = experimentalFeatureSettings.isEnabled(Xp::NoUrlLiterals);
if (noURLLiterals)
throw ParseError({
.msg = hintfmt("URL literals are disabled"),
.errPos = state->positions[CUR_POS]
.msg = HintFmt("URL literals are disabled"),
.pos = state->positions[CUR_POS]
});
$$ = new ExprString(std::string($1));
}
@@ -340,8 +340,8 @@ attrs
delete str;
} else
throw ParseError({
.msg = hintfmt("dynamic attributes not allowed in inherit"),
.errPos = state->positions[state->at(@2)]
.msg = HintFmt("dynamic attributes not allowed in inherit"),
.pos = state->positions[state->at(@2)]
});
}
| { $$ = new AttrPath; }

View File

@@ -1,5 +1,4 @@
#include "eval.hh"
#include "fs-input-accessor.hh"
namespace nix {
@@ -8,4 +7,9 @@ SourcePath EvalState::rootPath(CanonPath path)
return {rootFS, std::move(path)};
}
SourcePath EvalState::rootPath(PathView path)
{
return {rootFS, CanonPath(absPath(path))};
}
}

48
src/libexpr/pos-idx.hh Normal file
View File

@@ -0,0 +1,48 @@
#pragma once
#include <cinttypes>
namespace nix {
class PosIdx
{
friend class PosTable;
private:
uint32_t id;
explicit PosIdx(uint32_t id)
: id(id)
{
}
public:
PosIdx()
: id(0)
{
}
explicit operator bool() const
{
return id > 0;
}
bool operator<(const PosIdx other) const
{
return id < other.id;
}
bool operator==(const PosIdx other) const
{
return id == other.id;
}
bool operator!=(const PosIdx other) const
{
return id != other.id;
}
};
inline PosIdx noPos = {};
}

83
src/libexpr/pos-table.hh Normal file
View File

@@ -0,0 +1,83 @@
#pragma once
#include <cinttypes>
#include <numeric>
#include <vector>
#include "chunked-vector.hh"
#include "pos-idx.hh"
#include "position.hh"
namespace nix {
class PosTable
{
public:
class Origin
{
friend PosTable;
private:
// must always be invalid by default, add() replaces this with the actual value.
// subsequent add() calls use this index as a token to quickly check whether the
// current origins.back() can be reused or not.
mutable uint32_t idx = std::numeric_limits<uint32_t>::max();
// Used for searching in PosTable::[].
explicit Origin(uint32_t idx)
: idx(idx)
, origin{std::monostate()}
{
}
public:
const Pos::Origin origin;
Origin(Pos::Origin origin)
: origin(origin)
{
}
};
struct Offset
{
uint32_t line, column;
};
private:
std::vector<Origin> origins;
ChunkedVector<Offset, 8192> offsets;
public:
PosTable()
: offsets(1024)
{
origins.reserve(1024);
}
PosIdx add(const Origin & origin, uint32_t line, uint32_t column)
{
const auto idx = offsets.add({line, column}).second;
if (origins.empty() || origins.back().idx != origin.idx) {
origin.idx = idx;
origins.push_back(origin);
}
return PosIdx(idx + 1);
}
Pos operator[](PosIdx p) const
{
if (p.id == 0 || p.id > offsets.size())
return {};
const auto idx = p.id - 1;
/* we want the last key <= idx, so we'll take prev(first key > idx).
this is guaranteed to never rewind origin.begin because the first
key is always 0. */
const auto pastOrigin = std::upper_bound(
origins.begin(), origins.end(), Origin(idx), [](const auto & a, const auto & b) { return a.idx < b.idx; });
const auto origin = *std::prev(pastOrigin);
const auto offset = offsets[idx];
return {offset.line, offset.column, origin.origin};
}
};
}

View File

@@ -39,10 +39,6 @@ namespace nix {
* Miscellaneous
*************************************************************/
InvalidPathError::InvalidPathError(const Path & path) :
EvalError("path '%s' is not valid", path), path(path) {}
StringMap EvalState::realiseContext(const NixStringContext & context)
{
std::vector<DerivedPath::Built> drvs;
@@ -51,7 +47,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
for (auto & c : context) {
auto ensureValid = [&](const StorePath & p) {
if (!store->isValidPath(p))
debugThrowLastTrace(InvalidPathError(store->printStorePath(p)));
error<InvalidPathError>(store->printStorePath(p)).debugThrow();
};
std::visit(overloaded {
[&](const NixStringContextElem::Built & b) {
@@ -78,9 +74,10 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
if (drvs.empty()) return {};
if (!evalSettings.enableImportFromDerivation)
debugThrowLastTrace(Error(
error<EvalError>(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
drvs.begin()->to_string(*store)));
drvs.begin()->to_string(*store)
).debugThrow();
/* Build/substitute the context. */
std::vector<DerivedPath> buildReqs;
@@ -112,13 +109,13 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
for (auto & outputPath : outputsToCopyAndAllow) {
/* Add the output of this derivations to the allowed
paths. */
allowPath(store->toRealPath(outputPath));
allowPath(outputPath);
}
return res;
}
static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, bool resolveSymlinks = true)
static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, std::optional<SymlinkResolution> resolveSymlinks = SymlinkResolution::Full)
{
NixStringContext context;
@@ -130,7 +127,7 @@ static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, bo
auto realPath = state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context);
path = {path.accessor, CanonPath(realPath)};
}
return resolveSymlinks ? path.resolveSymlinks() : path;
return resolveSymlinks ? path.resolveSymlinks(*resolveSymlinks) : path;
} catch (Error & e) {
e.addTrace(state.positions[pos], "while realising the context of path '%s'", path);
throw;
@@ -170,7 +167,7 @@ static void mkOutputString(
argument. */
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
{
auto path = realisePath(state, pos, vPath, false);
auto path = realisePath(state, pos, vPath, std::nullopt);
auto path2 = path.path.abs();
// FIXME
@@ -340,16 +337,16 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle)
state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
state.error<EvalError>("could not open '%1%': %2%", path, dlerror()).debugThrow();
dlerror();
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
if(!func) {
char *message = dlerror();
if (message)
state.debugThrowLastTrace(EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message));
state.error<EvalError>("could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow();
else
state.debugThrowLastTrace(EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path));
state.error<EvalError>("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow();
}
(func)(state, v);
@@ -365,7 +362,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto elems = args[0]->listElems();
auto count = args[0]->listSize();
if (count == 0)
state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
state.error<EvalError>("at least one argument to 'exec' required").atPos(pos).debugThrow();
NixStringContext context;
auto program = state.coerceToString(pos, *elems[0], context,
"while evaluating the first element of the argument passed to builtins.exec",
@@ -380,7 +377,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
try {
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
} catch (InvalidPathError & e) {
state.error("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow<EvalError>();
state.error<EvalError>("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow();
}
auto output = runProgram(program, true, commandArgs);
@@ -582,7 +579,7 @@ struct CompareValues
if (v1->type() == nInt && v2->type() == nFloat)
return v1->integer < v2->fpoint;
if (v1->type() != v2->type())
state.error("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow<EvalError>();
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
// Allow selecting a subset of enum values
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
@@ -610,7 +607,7 @@ struct CompareValues
}
}
default:
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
state.error<EvalError>("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow();
#pragma GCC diagnostic pop
}
} catch (Error & e) {
@@ -637,7 +634,7 @@ static Bindings::iterator getAttr(
{
Bindings::iterator value = attrSet->find(attrSym);
if (value == attrSet->end()) {
state.error("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow<TypeError>();
state.error<TypeError>("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow();
}
return value;
}
@@ -757,21 +754,12 @@ static RegisterPrimOp primop_break({
if (state.debugRepl && !state.debugTraces.empty()) {
auto error = Error(ErrorInfo {
.level = lvlInfo,
.msg = hintfmt("breakpoint reached"),
.errPos = state.positions[pos],
.msg = HintFmt("breakpoint reached"),
.pos = state.positions[pos],
});
auto & dt = state.debugTraces.front();
state.runDebugRepl(&error, dt.env, dt.expr);
if (state.debugQuit) {
// If the user elects to quit the repl, throw an exception.
throw Error(ErrorInfo{
.level = lvlInfo,
.msg = hintfmt("quit the debugger"),
.errPos = nullptr,
});
}
}
// Return the value we were passed.
@@ -790,7 +778,7 @@ static RegisterPrimOp primop_abort({
NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtins.abort").toOwned();
state.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s));
state.error<Abort>("evaluation aborted with the following error message: '%1%'", s).debugThrow();
}
});
@@ -809,7 +797,7 @@ static RegisterPrimOp primop_throw({
NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtin.throw").toOwned();
state.debugThrowLastTrace(ThrownError(s));
state.error<ThrownError>(s).debugThrow();
}
});
@@ -823,7 +811,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
auto message = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtins.addErrorContext",
false, false).toOwned();
e.addTrace(nullptr, hintfmt(message), true);
e.addTrace(nullptr, HintFmt(message), true);
throw;
}
}
@@ -882,7 +870,7 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va
/* increment state.trylevel, and decrement it when this function returns. */
MaintainCount trylevel(state.trylevel);
void (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
ReplExitStatus (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry)
{
/* to prevent starting the repl from exceptions withing a tryEval, null it. */
@@ -998,6 +986,10 @@ static void prim_trace(EvalState & state, const PosIdx pos, Value * * args, Valu
printError("trace: %1%", args[0]->string_view());
else
printError("trace: %1%", ValuePrinter(state, *args[0]));
if (evalSettings.builtinsTraceDebugger && state.debugRepl && !state.debugTraces.empty()) {
const DebugTrace & last = state.debugTraces.front();
state.runDebugRepl(nullptr, last.env, last.expr);
}
state.forceValue(*args[1], pos);
v = *args[1];
}
@@ -1009,6 +1001,12 @@ static RegisterPrimOp primop_trace({
Evaluate *e1* and print its abstract syntax representation on
standard error. Then return *e2*. This function is useful for
debugging.
If the
[`debugger-on-trace`](@docroot@/command-ref/conf-file.md#conf-debugger-on-trace)
option is set to `true` and the `--debugger` flag is given, the
interactive debugger will be started when `trace` is called (like
[`break`](@docroot@/language/builtins.md#builtins-break)).
)",
.fun = prim_trace,
});
@@ -1074,7 +1072,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
* often results from the composition of several functions
* (derivationStrict, derivation, mkDerivation, mkPythonModule, etc.)
*/
e.addTrace(nullptr, hintfmt(
e.addTrace(nullptr, HintFmt(
"while evaluating derivation '%s'\n"
" whose name attribute is located at %s",
drvName, pos), true);
@@ -1088,9 +1086,10 @@ drvName, Bindings * attrs, Value & v)
/* Check whether attributes should be passed as a JSON file. */
using nlohmann::json;
std::optional<json> jsonObject;
auto pos = v.determinePos(noPos);
auto attr = attrs->find(state.sStructuredAttrs);
if (attr != attrs->end() &&
state.forceBool(*attr->value, noPos,
state.forceBool(*attr->value, pos,
"while evaluating the `__structuredAttrs` "
"attribute passed to builtins.derivationStrict"))
jsonObject = json::object();
@@ -1099,7 +1098,7 @@ drvName, Bindings * attrs, Value & v)
bool ignoreNulls = false;
attr = attrs->find(state.sIgnoreNulls);
if (attr != attrs->end())
ignoreNulls = state.forceBool(*attr->value, noPos, "while evaluating the `__ignoreNulls` attribute " "passed to builtins.derivationStrict");
ignoreNulls = state.forceBool(*attr->value, pos, "while evaluating the `__ignoreNulls` attribute " "passed to builtins.derivationStrict");
/* Build the derivation expression by processing the attributes. */
Derivation drv;
@@ -1128,37 +1127,33 @@ drvName, Bindings * attrs, Value & v)
experimentalFeatureSettings.require(Xp::DynamicDerivations);
ingestionMethod = TextIngestionMethod {};
} else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
.errPos = state.positions[noPos]
}));
state.error<EvalError>(
"invalid value '%s' for 'outputHashMode' attribute", s
).atPos(v).debugThrow();
};
auto handleOutputs = [&](const Strings & ss) {
outputs.clear();
for (auto & j : ss) {
if (outputs.find(j) != outputs.end())
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("duplicate derivation output '%1%'", j),
.errPos = state.positions[noPos]
}));
state.error<EvalError>("duplicate derivation output '%1%'", j)
.atPos(v)
.debugThrow();
/* !!! Check whether j is a valid attribute
name. */
/* Derivations cannot be named drv, because
then we'd have an attribute drvPath in
the resulting set. */
if (j == "drv")
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid derivation output name 'drv'" ),
.errPos = state.positions[noPos]
}));
state.error<EvalError>("invalid derivation output name 'drv'")
.atPos(v)
.debugThrow();
outputs.insert(j);
}
if (outputs.empty())
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation cannot have an empty set of outputs"),
.errPos = state.positions[noPos]
}));
state.error<EvalError>("derivation cannot have an empty set of outputs")
.atPos(v)
.debugThrow();
};
try {
@@ -1167,16 +1162,16 @@ drvName, Bindings * attrs, Value & v)
const std::string_view context_below("");
if (ignoreNulls) {
state.forceValue(*i->value, noPos);
state.forceValue(*i->value, pos);
if (i->value->type() == nNull) continue;
}
if (i->name == state.sContentAddressed && state.forceBool(*i->value, noPos, context_below)) {
if (i->name == state.sContentAddressed && state.forceBool(*i->value, pos, context_below)) {
contentAddressed = true;
experimentalFeatureSettings.require(Xp::CaDerivations);
}
else if (i->name == state.sImpure && state.forceBool(*i->value, noPos, context_below)) {
else if (i->name == state.sImpure && state.forceBool(*i->value, pos, context_below)) {
isImpure = true;
experimentalFeatureSettings.require(Xp::ImpureDerivations);
}
@@ -1184,9 +1179,9 @@ drvName, Bindings * attrs, Value & v)
/* The `args' attribute is special: it supplies the
command-line arguments to the builder. */
else if (i->name == state.sArgs) {
state.forceList(*i->value, noPos, context_below);
state.forceList(*i->value, pos, context_below);
for (auto elem : i->value->listItems()) {
auto s = state.coerceToString(noPos, *elem, context,
auto s = state.coerceToString(pos, *elem, context,
"while evaluating an element of the argument list",
true).toOwned();
drv.args.push_back(s);
@@ -1201,29 +1196,29 @@ drvName, Bindings * attrs, Value & v)
if (i->name == state.sStructuredAttrs) continue;
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, noPos, context);
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context);
if (i->name == state.sBuilder)
drv.builder = state.forceString(*i->value, context, noPos, context_below);
drv.builder = state.forceString(*i->value, context, pos, context_below);
else if (i->name == state.sSystem)
drv.platform = state.forceStringNoCtx(*i->value, noPos, context_below);
drv.platform = state.forceStringNoCtx(*i->value, pos, context_below);
else if (i->name == state.sOutputHash)
outputHash = state.forceStringNoCtx(*i->value, noPos, context_below);
outputHash = state.forceStringNoCtx(*i->value, pos, context_below);
else if (i->name == state.sOutputHashAlgo)
outputHashAlgo = state.forceStringNoCtx(*i->value, noPos, context_below);
outputHashAlgo = state.forceStringNoCtx(*i->value, pos, context_below);
else if (i->name == state.sOutputHashMode)
handleHashMode(state.forceStringNoCtx(*i->value, noPos, context_below));
handleHashMode(state.forceStringNoCtx(*i->value, pos, context_below));
else if (i->name == state.sOutputs) {
/* Require outputs to be a list of strings. */
state.forceList(*i->value, noPos, context_below);
state.forceList(*i->value, pos, context_below);
Strings ss;
for (auto elem : i->value->listItems())
ss.emplace_back(state.forceStringNoCtx(*elem, noPos, context_below));
ss.emplace_back(state.forceStringNoCtx(*elem, pos, context_below));
handleOutputs(ss);
}
} else {
auto s = state.coerceToString(noPos, *i->value, context, context_below, true).toOwned();
auto s = state.coerceToString(pos, *i->value, context, context_below, true).toOwned();
drv.env.emplace(key, s);
if (i->name == state.sBuilder) drv.builder = std::move(s);
else if (i->name == state.sSystem) drv.platform = std::move(s);
@@ -1238,7 +1233,7 @@ drvName, Bindings * attrs, Value & v)
} catch (Error & e) {
e.addTrace(state.positions[i->pos],
hintfmt("while evaluating attribute '%1%' of derivation '%2%'", key, drvName),
HintFmt("while evaluating attribute '%1%' of derivation '%2%'", key, drvName),
true);
throw;
}
@@ -1281,16 +1276,14 @@ drvName, Bindings * attrs, Value & v)
/* Do we have all required attributes? */
if (drv.builder == "")
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'builder' missing"),
.errPos = state.positions[noPos]
}));
state.error<EvalError>("required attribute 'builder' missing")
.atPos(v)
.debugThrow();
if (drv.platform == "")
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'system' missing"),
.errPos = state.positions[noPos]
}));
state.error<EvalError>("required attribute 'system' missing")
.atPos(v)
.debugThrow();
/* Check whether the derivation name is valid. */
if (isDerivation(drvName) &&
@@ -1298,10 +1291,10 @@ drvName, Bindings * attrs, Value & v)
outputs.size() == 1 &&
*(outputs.begin()) == "out"))
{
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation names are allowed to end in '%s' only if they produce a single derivation file", drvExtension),
.errPos = state.positions[noPos]
}));
state.error<EvalError>(
"derivation names are allowed to end in '%s' only if they produce a single derivation file",
drvExtension
).atPos(v).debugThrow();
}
if (outputHash) {
@@ -1310,10 +1303,9 @@ drvName, Bindings * attrs, Value & v)
Ignore `__contentAddressed` because fixed output derivations are
already content addressed. */
if (outputs.size() != 1 || *(outputs.begin()) != "out")
state.debugThrowLastTrace(Error({
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
.errPos = state.positions[noPos]
}));
state.error<EvalError>(
"multiple outputs are not supported in fixed-output derivations"
).atPos(v).debugThrow();
auto h = newHashAllowEmpty(*outputHash, parseHashAlgoOpt(outputHashAlgo));
@@ -1332,10 +1324,8 @@ drvName, Bindings * attrs, Value & v)
else if (contentAddressed || isImpure) {
if (contentAddressed && isImpure)
throw EvalError({
.msg = hintfmt("derivation cannot be both content-addressed and impure"),
.errPos = state.positions[noPos]
});
state.error<EvalError>("derivation cannot be both content-addressed and impure")
.atPos(v).debugThrow();
auto ha = parseHashAlgoOpt(outputHashAlgo).value_or(HashAlgorithm::SHA256);
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
@@ -1376,10 +1366,10 @@ drvName, Bindings * attrs, Value & v)
for (auto & i : outputs) {
auto h = get(hashModulo.hashes, i);
if (!h)
throw AssertionError({
.msg = hintfmt("derivation produced no hash for output '%s'", i),
.errPos = state.positions[noPos],
});
state.error<AssertionError>(
"derivation produced no hash for output '%s'",
i
).atPos(v).debugThrow();
auto outPath = state.store->makeOutputPath(i, *h, drvName);
drv.env[i] = state.store->printStorePath(outPath);
drv.outputs.insert_or_assign(
@@ -1485,10 +1475,10 @@ static RegisterPrimOp primop_toPath({
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
if (evalSettings.pureEval)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"'%s' is not allowed in pure evaluation mode",
"builtins.storePath"
).atPos(pos).debugThrow();
NixStringContext context;
auto path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to 'builtins.storePath'").path;
@@ -1498,10 +1488,8 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
if (!state.store->isStorePath(path.abs()))
path = CanonPath(canonPath(path.abs(), true));
if (!state.store->isInStore(path.abs()))
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = state.positions[pos]
}));
state.error<EvalError>("path '%1%' is not in the Nix store", path)
.atPos(pos).debugThrow();
auto path2 = state.store->toStorePath(path.abs()).first;
if (!settings.readOnlyMode)
state.store->ensurePath(path2);
@@ -1534,13 +1522,16 @@ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args,
try {
auto & arg = *args[0];
auto path = realisePath(state, pos, arg);
/* SourcePath doesn't know about trailing slash. */
state.forceValue(arg, pos);
auto mustBeDir = arg.type() == nString
&& (arg.string_view().ends_with("/")
|| arg.string_view().ends_with("/."));
auto symlinkResolution =
mustBeDir ? SymlinkResolution::Full : SymlinkResolution::Ancestors;
auto path = realisePath(state, pos, arg, symlinkResolution);
auto st = path.maybeLstat();
auto exists = st && (!mustBeDir || st->type == SourceAccessor::tDirectory);
v.mkBool(exists);
@@ -1616,7 +1607,10 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V
auto path = realisePath(state, pos, *args[0]);
auto s = path.readFile();
if (s.find((char) 0) != std::string::npos)
state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
state.error<EvalError>(
"the contents of the file '%1%' cannot be represented as a Nix string",
path
).atPos(pos).debugThrow();
StorePathSet refs;
if (state.store->isInStore(path.path.abs())) {
try {
@@ -1673,10 +1667,11 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
auto rewrites = state.realiseContext(context);
path = rewriteStrings(path, rewrites);
} catch (InvalidPathError & e) {
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"cannot find '%1%', since path '%2%' is not valid",
path,
e.path
).atPos(pos).debugThrow();
}
searchPath.elements.emplace_back(SearchPath::Elem {
@@ -1745,10 +1740,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
auto algo = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashFile");
std::optional<HashAlgorithm> ha = parseHashAlgo(algo);
if (!ha)
state.debugThrowLastTrace(Error({
.msg = hintfmt("unknown hash algo '%1%'", algo),
.errPos = state.positions[pos]
}));
state.error<EvalError>("unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow();
auto path = realisePath(state, pos, *args[1]);
@@ -1777,7 +1769,7 @@ static std::string_view fileTypeToString(InputAccessor::Type type)
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0], false);
auto path = realisePath(state, pos, *args[0], std::nullopt);
/* Retrieve the directory entry type and stringize it. */
v.mkString(fileTypeToString(path.lstat().type));
}
@@ -1816,7 +1808,7 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
// detailed node info quickly in this case we produce a thunk to
// query the file type lazily.
auto epath = state.allocValue();
epath->mkPath(path + name);
epath->mkPath(path / name);
if (!readFileType)
readFileType = &state.getBuiltin("readFileType");
attr.mkApp(readFileType, epath);
@@ -2068,13 +2060,12 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c.raw))
refs.insert(p->path);
else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt(
"in 'toFile': the file named '%1%' must not contain a reference "
"to a derivation but contains (%2%)",
name, c.to_string()),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"files created by %1% may not reference derivations, but %2% references %3%",
"builtins.toFile",
name,
c.to_string()
).atPos(pos).debugThrow();
}
auto storePath = settings.readOnlyMode
@@ -2241,9 +2232,19 @@ static void addPath(
});
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
auto dstPath = fetchToStore(*state.store, path, name, method, filter.get(), state.repair);
auto dstPath = fetchToStore(
*state.store,
path.resolveSymlinks(),
settings.readOnlyMode ? FetchMode::DryRun : FetchMode::Copy,
name,
method,
filter.get(),
state.repair);
if (expectedHash && expectedStorePath != dstPath)
state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
state.error<EvalError>(
"store path mismatch in (possibly filtered) path added from '%s'",
path
).atPos(pos).debugThrow();
state.allowAndSetStorePathString(dstPath, v);
} else
state.allowAndSetStorePathString(*expectedStorePath, v);
@@ -2343,16 +2344,15 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashAlgorithm::SHA256);
else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]),
.errPos = state.positions[attr.pos]
}));
state.error<EvalError>(
"unsupported argument '%1%' to 'addPath'",
state.symbols[attr.name]
).atPos(attr.pos).debugThrow();
}
if (!path)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"missing required 'path' attribute in the first argument to builtins.path"
).atPos(pos).debugThrow();
if (name.empty())
name = path->baseName();
@@ -2770,10 +2770,7 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
return;
}
if (!args[0]->isLambda())
state.debugThrowLastTrace(TypeError({
.msg = hintfmt("'functionArgs' requires a function"),
.errPos = state.positions[pos]
}));
state.error<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow();
if (!args[0]->lambda.fun->hasFormals()) {
v.mkAttrs(&state.emptyBindings);
@@ -2943,10 +2940,10 @@ static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Val
{
state.forceList(list, pos, "while evaluating the first argument passed to builtins.elemAt");
if (n < 0 || (unsigned int) n >= list.listSize())
state.debugThrowLastTrace(Error({
.msg = hintfmt("list index %1% is out of bounds", n),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"list index %1% is out of bounds",
n
).atPos(pos).debugThrow();
state.forceValue(*list.listElems()[n], pos);
v = *list.listElems()[n];
}
@@ -2991,10 +2988,7 @@ static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value
{
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.tail");
if (args[0]->listSize() == 0)
state.debugThrowLastTrace(Error({
.msg = hintfmt("'tail' called on an empty list"),
.errPos = state.positions[pos]
}));
state.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
state.mkList(v, args[0]->listSize() - 1);
for (unsigned int n = 0; n < v.listSize(); ++n)
@@ -3251,7 +3245,7 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
if (len < 0)
state.error("cannot create list of size %1%", len).debugThrow<EvalError>();
state.error<EvalError>("cannot create list of size %1%", len).atPos(pos).debugThrow();
// More strict than striclty (!) necessary, but acceptable
// as evaluating map without accessing any values makes little sense.
@@ -3568,10 +3562,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
NixFloat f2 = state.forceFloat(*args[1], pos, "while evaluating the second operand of the division");
if (f2 == 0)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("division by zero"),
.errPos = state.positions[pos]
}));
state.error<EvalError>("division by zero").atPos(pos).debugThrow();
if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
v.mkFloat(state.forceFloat(*args[0], pos, "while evaluating the first operand of the division") / f2);
@@ -3580,10 +3571,7 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
NixInt i2 = state.forceInt(*args[1], pos, "while evaluating the second operand of the division");
/* Avoid division overflow as it might raise SIGFPE. */
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("overflow in integer division"),
.errPos = state.positions[pos]
}));
state.error<EvalError>("overflow in integer division").atPos(pos).debugThrow();
v.mkInt(i1 / i2);
}
@@ -3714,10 +3702,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args,
int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
if (start < 0)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("negative start position in 'substring'"),
.errPos = state.positions[pos]
}));
state.error<EvalError>("negative start position in 'substring'").atPos(pos).debugThrow();
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
@@ -3782,10 +3767,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args,
auto algo = state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.hashString");
std::optional<HashAlgorithm> ha = parseHashAlgo(algo);
if (!ha)
state.debugThrowLastTrace(Error({
.msg = hintfmt("unknown hash algo '%1%'", algo),
.errPos = state.positions[pos]
}));
state.error<EvalError>("unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow();
NixStringContext context; // discarded
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
@@ -3951,15 +3933,13 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
} catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
.errPos = state.positions[pos]
}));
state.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
.atPos(pos)
.debugThrow();
} else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid regular expression '%s'", re),
.errPos = state.positions[pos]
}));
state.error<EvalError>("invalid regular expression '%s'", re)
.atPos(pos)
.debugThrow();
}
}
@@ -4055,15 +4035,13 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
} catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
.errPos = state.positions[pos]
}));
state.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
.atPos(pos)
.debugThrow();
} else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid regular expression '%s'", re),
.errPos = state.positions[pos]
}));
state.error<EvalError>("invalid regular expression '%s'", re)
.atPos(pos)
.debugThrow();
}
}
@@ -4139,7 +4117,9 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
state.forceList(*args[0], pos, "while evaluating the first argument passed to builtins.replaceStrings");
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings");
if (args[0]->listSize() != args[1]->listSize())
state.error("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths").atPos(pos).debugThrow<EvalError>();
state.error<EvalError>(
"'from' and 'to' arguments passed to builtins.replaceStrings have different lengths"
).atPos(pos).debugThrow();
std::vector<std::string> from;
from.reserve(args[0]->listSize());

View File

@@ -98,30 +98,30 @@ static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, V
auto contextSize = context.size();
if (contextSize != 1) {
throw EvalError({
.msg = hintfmt("context of string '%s' must have exactly one element, but has %d", *s, contextSize),
.errPos = state.positions[pos]
});
state.error<EvalError>(
"context of string '%s' must have exactly one element, but has %d",
*s,
contextSize
).atPos(pos).debugThrow();
}
NixStringContext context2 {
(NixStringContextElem { std::visit(overloaded {
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
if (!c.path.isDerivation()) {
throw EvalError({
.msg = hintfmt("path '%s' is not a derivation",
state.store->printStorePath(c.path)),
.errPos = state.positions[pos],
});
state.error<EvalError>(
"path '%s' is not a derivation",
state.store->printStorePath(c.path)
).atPos(pos).debugThrow();
}
return NixStringContextElem::DrvDeep {
.drvPath = c.path,
};
},
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
throw EvalError({
.msg = hintfmt("`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'", c.output),
.errPos = state.positions[pos],
});
state.error<EvalError>(
"`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
c.output
).atPos(pos).debugThrow();
},
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
/* Reuse original item because we want this to be idempotent. */
@@ -261,10 +261,10 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
for (auto & i : *args[1]->attrs) {
const auto & name = state.symbols[i.name];
if (!state.store->isStorePath(name))
throw EvalError({
.msg = hintfmt("context key '%s' is not a store path", name),
.errPos = state.positions[i.pos]
});
state.error<EvalError>(
"context key '%s' is not a store path",
name
).atPos(i.pos).debugThrow();
auto namePath = state.store->parseStorePath(name);
if (!settings.readOnlyMode)
state.store->ensurePath(namePath);
@@ -281,10 +281,10 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(name)) {
throw EvalError({
.msg = hintfmt("tried to add all-outputs context of %s, which is not a derivation, to a string", name),
.errPos = state.positions[i.pos]
});
state.error<EvalError>(
"tried to add all-outputs context of %s, which is not a derivation, to a string",
name
).atPos(i.pos).debugThrow();
}
context.emplace(NixStringContextElem::DrvDeep {
.drvPath = namePath,
@@ -296,10 +296,10 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
if (iter->value->listSize() && !isDerivation(name)) {
throw EvalError({
.msg = hintfmt("tried to add derivation output context of %s, which is not a derivation, to a string", name),
.errPos = state.positions[i.pos]
});
state.error<EvalError>(
"tried to add derivation output context of %s, which is not a derivation, to a string",
name
).atPos(i.pos).debugThrow();
}
for (auto elem : iter->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");

View File

@@ -23,20 +23,20 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
auto rewrittenPath = makeContentAddressed(fromStore, *state.store, fromPath);
if (toPathMaybe && *toPathMaybe != rewrittenPath)
throw Error({
.msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
.msg = HintFmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
state.store->printStorePath(fromPath),
state.store->printStorePath(rewrittenPath),
state.store->printStorePath(*toPathMaybe)),
.errPos = state.positions[pos]
.pos = state.positions[pos]
});
if (!toPathMaybe)
throw Error({
.msg = hintfmt(
.msg = HintFmt(
"rewriting '%s' to content-addressed form yielded '%s'\n"
"Use this value for the 'toPath' attribute passed to 'fetchClosure'",
state.store->printStorePath(fromPath),
state.store->printStorePath(rewrittenPath)),
.errPos = state.positions[pos]
.pos = state.positions[pos]
});
}
@@ -50,11 +50,11 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
// We don't perform the rewriting when outPath already exists, as an optimisation.
// However, we can quickly detect a mistake if the toPath is input addressed.
throw Error({
.msg = hintfmt(
.msg = HintFmt(
"The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n"
"Set 'toPath' to an empty string to make Nix report the correct content-addressed path.",
state.store->printStorePath(toPath)),
.errPos = state.positions[pos]
.pos = state.positions[pos]
});
}
@@ -73,14 +73,14 @@ static void runFetchClosureWithContentAddressedPath(EvalState & state, const Pos
if (!info->isContentAddressed(*state.store)) {
throw Error({
.msg = hintfmt(
.msg = HintFmt(
"The 'fromPath' value '%s' is input-addressed, but 'inputAddressed' is set to 'false' (default).\n\n"
"If you do intend to fetch an input-addressed store path, add\n\n"
" inputAddressed = true;\n\n"
"to the 'fetchClosure' arguments.\n\n"
"Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.",
state.store->printStorePath(fromPath)),
.errPos = state.positions[pos]
.pos = state.positions[pos]
});
}
@@ -99,11 +99,11 @@ static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosId
if (info->isContentAddressed(*state.store)) {
throw Error({
.msg = hintfmt(
.msg = HintFmt(
"The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n"
"Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed",
state.store->printStorePath(fromPath)),
.errPos = state.positions[pos]
.pos = state.positions[pos]
});
}
@@ -153,15 +153,15 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
else
throw Error({
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
.errPos = state.positions[pos]
.msg = HintFmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
.pos = state.positions[pos]
});
}
if (!fromPath)
throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.errPos = state.positions[pos]
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.pos = state.positions[pos]
});
bool inputAddressed = inputAddressedMaybe.value_or(false);
@@ -169,17 +169,17 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
if (inputAddressed) {
if (toPath)
throw Error({
.msg = hintfmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
.msg = HintFmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
"inputAddressed",
"toPath"),
.errPos = state.positions[pos]
.pos = state.positions[pos]
});
}
if (!fromStoreUrl)
throw Error({
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.errPos = state.positions[pos]
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.pos = state.positions[pos]
});
auto parsedURL = parseURL(*fromStoreUrl);
@@ -188,14 +188,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
parsedURL.scheme != "https" &&
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
throw Error({
.msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
.errPos = state.positions[pos]
.msg = HintFmt("'fetchClosure' only supports http:// and https:// stores"),
.pos = state.positions[pos]
});
if (!parsedURL.query.empty())
throw Error({
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
.errPos = state.positions[pos]
.msg = HintFmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
.pos = state.positions[pos]
});
auto fromStore = openStore(parsedURL.to_string());

View File

@@ -38,17 +38,11 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else
throw EvalError({
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
.errPos = state.positions[attr.pos]
});
state.error<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow();
}
if (url.empty())
throw EvalError({
.msg = hintfmt("'url' argument required"),
.errPos = state.positions[pos]
});
state.error<EvalError>("'url' argument required").atPos(pos).debugThrow();
} else
url = state.coerceToString(pos, *args[0], context,

View File

@@ -9,6 +9,7 @@
#include "tarball.hh"
#include "url.hh"
#include "value-to-json.hh"
#include "fetch-to-store.hh"
#include <ctime>
#include <iomanip>
@@ -24,8 +25,6 @@ void emitTreeAttrs(
bool emptyRevFallback,
bool forceDirty)
{
assert(input.isLocked());
auto attrs = state.buildBindings(100);
state.mkStorePathString(storePath, attrs.alloc(state.sOutPath));
@@ -100,16 +99,14 @@ static void fetchTree(
if (auto aType = args[0]->attrs->get(state.sType)) {
if (type)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unexpected attribute 'type'"),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"unexpected attribute 'type'"
).atPos(pos).debugThrow();
type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
} else if (!type)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"attribute 'type' is missing in call to 'fetchTree'"
).atPos(pos).debugThrow();
attrs.emplace("type", type.value());
@@ -132,8 +129,8 @@ static void fetchTree(
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
}
else
state.debugThrowLastTrace(TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.symbols[attr.name], showType(*attr.value)));
state.error<TypeError>("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.symbols[attr.name], showType(*attr.value)).debugThrow();
}
if (params.isFetchGit && !attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) {
@@ -142,10 +139,9 @@ static void fetchTree(
if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'name' isnt supported in call to 'fetchTree'"),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"attribute 'name' isnt supported in call to 'fetchTree'"
).atPos(pos).debugThrow();
input = fetchers::Input::fromAttrs(std::move(attrs));
} else {
@@ -163,10 +159,9 @@ static void fetchTree(
input = fetchers::Input::fromAttrs(std::move(attrs));
} else {
if (!experimentalFeatureSettings.isEnabled(Xp::Flakes))
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("passing a string argument to 'fetchTree' requires the 'flakes' experimental feature"),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"passing a string argument to 'fetchTree' requires the 'flakes' experimental feature"
).atPos(pos).debugThrow();
input = fetchers::Input::fromURL(url);
}
}
@@ -175,10 +170,14 @@ static void fetchTree(
input = lookupInRegistries(state.store, input).first;
if (evalSettings.pureEval && !input.isLocked()) {
auto fetcher = "fetchTree";
if (params.isFetchGit)
state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchGit' requires a locked input, at %s", state.positions[pos]));
else
state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]));
fetcher = "fetchGit";
state.error<EvalError>(
"in pure evaluation mode, '%s' will not fetch unlocked input '%s'",
fetcher, input.to_string()
).atPos(pos).debugThrow();
}
state.checkURI(input.toURLString());
@@ -432,17 +431,13 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
.errPos = state.positions[attr.pos]
}));
state.error<EvalError>("unsupported argument '%s' to '%s'", n, who)
.atPos(pos).debugThrow();
}
if (!url)
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'url' argument required"),
.errPos = state.positions[pos]
}));
state.error<EvalError>(
"'url' argument required").atPos(pos).debugThrow();
} else
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
@@ -455,7 +450,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
name = baseNameOf(*url);
if (evalSettings.pureEval && !expectedHash)
state.debugThrowLastTrace(EvalError("in pure evaluation mode, '%s' requires a 'sha256' argument", who));
state.error<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow();
// early exit if pinned and already in the store
if (expectedHash && expectedHash->algo == HashAlgorithm::SHA256) {
@@ -477,16 +472,22 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
// https://github.com/NixOS/nix/issues/4313
auto storePath =
unpack
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).storePath
? fetchToStore(*state.store, fetchers::downloadTarball(*url).accessor, FetchMode::Copy, name)
: fetchers::downloadFile(state.store, *url, name, (bool) expectedHash).storePath;
if (expectedHash) {
auto hash = unpack
? state.store->queryPathInfo(storePath)->narHash
: hashFile(HashAlgorithm::SHA256, state.store->toRealPath(storePath));
if (hash != *expectedHash)
state.debugThrowLastTrace(EvalError((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
*url, expectedHash->to_string(HashFormat::Nix32, true), hash.to_string(HashFormat::Nix32, true)));
if (hash != *expectedHash) {
state.error<EvalError>(
"hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
*url,
expectedHash->to_string(HashFormat::Nix32, true),
hash.to_string(HashFormat::Nix32, true)
).withExitStatus(102)
.debugThrow();
}
}
state.allowAndSetStorePathString(storePath, v);

View File

@@ -83,10 +83,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
try {
visit(val, toml::parse(tomlStream, "fromTOML" /* the "filename" */));
} catch (std::exception & e) { // TODO: toml::syntax_error
throw EvalError({
.msg = hintfmt("while parsing a TOML string: %s", e.what()),
.errPos = state.positions[pos]
});
state.error<EvalError>("while parsing TOML: %s", e.what()).atPos(pos).debugThrow();
}
}

View File

@@ -17,24 +17,29 @@ struct PrintOptions
* If true, output ANSI color sequences.
*/
bool ansiColors = false;
/**
* If true, force values.
*/
bool force = false;
/**
* If true and `force` is set, print derivations as
* `«derivation /nix/store/...»` instead of as attribute sets.
*/
bool derivationPaths = false;
/**
* If true, track which values have been printed and skip them on
* subsequent encounters. Useful for self-referential values.
*/
bool trackRepeated = true;
/**
* Maximum depth to evaluate to.
*/
size_t maxDepth = std::numeric_limits<size_t>::max();
/**
* Maximum number of attributes in attribute sets to print.
*
@@ -42,6 +47,7 @@ struct PrintOptions
* attribute set encountered.
*/
size_t maxAttrs = std::numeric_limits<size_t>::max();
/**
* Maximum number of list items to print.
*
@@ -49,10 +55,26 @@ struct PrintOptions
* list encountered.
*/
size_t maxListItems = std::numeric_limits<size_t>::max();
/**
* Maximum string length to print.
*/
size_t maxStringLength = std::numeric_limits<size_t>::max();
/**
* Indentation width for pretty-printing.
*
* If set to 0 (the default), values are not pretty-printed.
*/
size_t prettyIndent = 0;
/**
* True if pretty-printing is enabled.
*/
inline bool shouldPrettyPrint()
{
return prettyIndent > 0;
}
};
/**

View File

@@ -152,7 +152,8 @@ struct ImportantFirstAttrNameCmp
}
};
typedef std::set<Value *> ValuesSeen;
typedef std::set<const void *> ValuesSeen;
typedef std::vector<std::pair<std::string, Value *>> AttrVec;
class Printer
{
@@ -163,6 +164,37 @@ private:
std::optional<ValuesSeen> seen;
size_t attrsPrinted = 0;
size_t listItemsPrinted = 0;
std::string indent;
void increaseIndent()
{
if (options.shouldPrettyPrint()) {
indent.append(options.prettyIndent, ' ');
}
}
void decreaseIndent()
{
if (options.shouldPrettyPrint()) {
assert(indent.size() >= options.prettyIndent);
indent.resize(indent.size() - options.prettyIndent);
}
}
/**
* Print a space (for separating items or attributes).
*
* If pretty-printing is enabled, a newline and the current `indent` is
* printed instead.
*/
void printSpace(bool prettyPrint)
{
if (prettyPrint) {
output << "\n" << indent;
} else {
output << " ";
}
}
void printRepeated()
{
@@ -255,14 +287,36 @@ private:
output << "»";
if (options.ansiColors)
output << ANSI_NORMAL;
} catch (BaseError & e) {
} catch (Error & e) {
printError_(e);
}
}
bool shouldPrettyPrintAttrs(AttrVec & v)
{
if (!options.shouldPrettyPrint() || v.empty()) {
return false;
}
// Pretty-print attrsets with more than one item.
if (v.size() > 1) {
return true;
}
auto item = v[0].second;
if (!item) {
return true;
}
// Pretty-print single-item attrsets only if they contain nested
// structures.
auto itemType = item->type();
return itemType == nList || itemType == nAttrs || itemType == nThunk;
}
void printAttrs(Value & v, size_t depth)
{
if (seen && !seen->insert(&v).second) {
if (seen && !seen->insert(v.attrs).second) {
printRepeated();
return;
}
@@ -270,9 +324,10 @@ private:
if (options.force && options.derivationPaths && state.isDerivation(v)) {
printDerivation(v);
} else if (depth < options.maxDepth) {
output << "{ ";
increaseIndent();
output << "{";
std::vector<std::pair<std::string, Value *>> sorted;
AttrVec sorted;
for (auto & i : *v.attrs)
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
@@ -281,7 +336,11 @@ private:
else
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());
auto prettyPrint = shouldPrettyPrintAttrs(sorted);
for (auto & i : sorted) {
printSpace(prettyPrint);
if (attrsPrinted >= options.maxAttrs) {
printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
break;
@@ -290,13 +349,38 @@ private:
printAttributeName(output, i.first);
output << " = ";
print(*i.second, depth + 1);
output << "; ";
output << ";";
attrsPrinted++;
}
decreaseIndent();
printSpace(prettyPrint);
output << "}";
} else
} else {
output << "{ ... }";
}
}
bool shouldPrettyPrintList(std::span<Value * const> list)
{
if (!options.shouldPrettyPrint() || list.empty()) {
return false;
}
// Pretty-print lists with more than one item.
if (list.size() > 1) {
return true;
}
auto item = list[0];
if (!item) {
return true;
}
// Pretty-print single-item lists only if they contain nested
// structures.
auto itemType = item->type();
return itemType == nList || itemType == nAttrs || itemType == nThunk;
}
void printList(Value & v, size_t depth)
@@ -306,11 +390,16 @@ private:
return;
}
output << "[ ";
if (depth < options.maxDepth) {
for (auto elem : v.listItems()) {
increaseIndent();
output << "[";
auto listItems = v.listItems();
auto prettyPrint = shouldPrettyPrintList(listItems);
for (auto elem : listItems) {
printSpace(prettyPrint);
if (listItemsPrinted >= options.maxListItems) {
printElided(v.listSize() - listItemsPrinted, "item", "items");
printElided(listItems.size() - listItemsPrinted, "item", "items");
break;
}
@@ -319,13 +408,15 @@ private:
} else {
printNullptr();
}
output << " ";
listItemsPrinted++;
}
decreaseIndent();
printSpace(prettyPrint);
output << "]";
} else {
output << "[ ... ]";
}
else
output << "... ";
output << "]";
}
void printFunction(Value & v)
@@ -405,11 +496,11 @@ private:
output << ANSI_NORMAL;
}
void printError_(BaseError & e)
void printError_(Error & e)
{
if (options.ansiColors)
output << ANSI_RED;
output << "«" << e.msg() << "»";
output << "«error: " << filterANSIEscapes(e.info().msg.str(), true) << "»";
if (options.ansiColors)
output << ANSI_NORMAL;
}
@@ -422,7 +513,7 @@ private:
if (options.force) {
try {
state.forceValue(v, v.determinePos(noPos));
} catch (BaseError & e) {
} catch (Error & e) {
printError_(e);
return;
}
@@ -488,6 +579,7 @@ public:
{
attrsPrinted = 0;
listItemsPrinted = 0;
indent.clear();
if (options.trackRepeated) {
seen.emplace();
@@ -511,4 +603,11 @@ std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer)
return output;
}
template<>
HintFmt & HintFmt::operator%(const ValuePrinter & value)
{
fmt % value;
return *this;
}
}

View File

@@ -9,6 +9,7 @@
#include <iostream>
#include "fmt.hh"
#include "print-options.hh"
namespace nix {
@@ -78,4 +79,13 @@ public:
};
std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer);
/**
* `ValuePrinter` does its own ANSI formatting, so we don't color it
* magenta.
*/
template<>
HintFmt & HintFmt::operator%(const ValuePrinter & value);
}

View File

@@ -0,0 +1,20 @@
#pragma once
namespace nix {
/**
* Exit status returned from the REPL.
*/
enum class ReplExitStatus {
/**
* The user exited with `:quit`. The program (e.g., if the REPL was acting
* as the debugger) should exit.
*/
QuitAll,
/**
* The user exited with `:continue`. The program should continue running.
*/
Continue,
};
}

View File

@@ -64,7 +64,7 @@ json printValueAsJSON(EvalState & state, bool strict,
out[j] = printValueAsJSON(state, strict, *a.value, a.pos, context, copyToStore);
} catch (Error & e) {
e.addTrace(state.positions[a.pos],
hintfmt("while evaluating attribute '%1%'", j));
HintFmt("while evaluating attribute '%1%'", j));
throw;
}
}
@@ -80,8 +80,8 @@ json printValueAsJSON(EvalState & state, bool strict,
try {
out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
} catch (Error & e) {
e.addTrace({},
hintfmt("while evaluating list element at index %1%", i));
e.addTrace(state.positions[pos],
HintFmt("while evaluating list element at index %1%", i));
throw;
}
i++;
@@ -99,13 +99,12 @@ json printValueAsJSON(EvalState & state, bool strict,
case nThunk:
case nFunction:
auto e = TypeError({
.msg = hintfmt("cannot convert %1% to JSON", showType(v)),
.errPos = state.positions[v.determinePos(pos)]
});
e.addTrace(state.positions[pos], hintfmt("message for the trace"));
state.debugThrowLastTrace(e);
throw e;
state.error<TypeError>(
"cannot convert %1% to JSON",
showType(v)
)
.atPos(v.determinePos(pos))
.debugThrow();
}
return out;
}
@@ -119,7 +118,8 @@ void printValueAsJSON(EvalState & state, bool strict,
json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
NixStringContext & context, bool copyToStore) const
{
state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
state.error<TypeError>("cannot convert %1% to JSON", showType())
.debugThrow();
}

View File

@@ -105,7 +105,7 @@ class ExternalValueBase
* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
* error.
*/
virtual std::string coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
virtual std::string coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
/**
* Compare to another value of the same type. Defaults to uncomparable,

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