Compare commits

..

89 Commits

Author SHA1 Message Date
Robert Hensing
db4153d272 Merge pull request #11049 from NixOS/backport-11046-to-2.20-maintenance
[Backport 2.20-maintenance] [Backport 2.21-maintenance] libstore: fix sandboxed builds on macOS
2024-07-05 19:48:14 +02:00
Emily
87d2913bbf libstore: fix sandboxed builds on macOS
The recent fix for CVE-2024-38531 broke the sandbox on macOS
completely. As it’s not practical to use `chroot(2)` on
macOS, the build takes place in the main filesystem tree, and the
world‐unreadable wrapper directory prevents the build from accessing
its `$TMPDIR` at all.

The macOS sandbox probably shouldn’t be treated as any kind of a
security boundary in its current state, but this specific vulnerability
wasn’t possible to exploit on macOS anyway, as creating `set{u,g}id`
binaries is blocked by sandbox policy.

Locking down the build sandbox further may be a good idea in future,
but it already has significant compatibility issues. For now, restore
the previous status quo on macOS.

Thanks to @alois31 for helping me come to a better understanding of
the vulnerability.

Fixes: 1d3696f0fb
Closes: #11002
(cherry picked from commit af2e1142b1)
(cherry picked from commit 9feee13952)
2024-07-05 15:59:25 +00:00
Emily
98a7d3b0a4 libstore: clean up the build directory properly
After the fix for CVE-2024-38531, this was only removing the nested
build directory, rather than the top‐level temporary directory.

Fixes: 1d3696f0fb
(cherry picked from commit 76e4adfaac)
(cherry picked from commit 0d68b40dda)
2024-07-05 15:59:25 +00:00
Robert Hensing
1e896c1738 Merge pull request #11026 from NixOS/backport-11022-to-2.20-maintenance
[Backport 2.20-maintenance] Use proper struct sockpeercred for SO_PEERCRED for OpenBSD
2024-07-03 20:39:58 +02:00
John Ericson
c8d2bc72a5 Remove invalid release notes YAML field
There is no PR for this, since it was an embargoed fix before
disclosure.

(cherry picked from commit 32e67eba8b)
2024-07-03 20:02:23 +02:00
kn
4a42535dc0 Use proper struct sockpeercred for SO_PEERCRED for OpenBSD
getsockopt(2) documents this;  ucred is wrong ("cr_" member prefix, no pid).

(cherry picked from commit 10ccdb7a41)
2024-07-03 15:57:06 +00:00
John Ericson
2040540717 Ident some CPP in nix daemon
Makes it easier for me to read.

(cherry picked from commit a09360400b)
2024-07-03 15:57:06 +00:00
Eelco Dolstra
7891e56fb1 Bump version 2024-06-27 11:07:06 +02:00
tomberek
2b15b0b9b0 Merge pull request from GHSA-q82p-44mg-mgh5
Fix sandbox escape 2.20
2024-06-26 18:49:22 -04:00
Eelco Dolstra
caf4082dce Fix --no-sandbox
When sandboxing is disabled, we cannot put $TMPDIR underneath an
inaccessible directory.

(cherry picked from commit 86ca2d6d94c0581fda0c666c5e022784952f3542)
(cherry picked from commit 8f58b98770)
2024-06-21 16:39:44 +02:00
Eelco Dolstra
eee27e83e0 Formatting
(cherry picked from commit 3af22860759509d5040ff70618247031d96a095c)
2024-06-21 16:39:44 +02:00
Eelco Dolstra
879d814a75 Put the chroot inside a directory that isn't group/world-accessible
Previously, the .chroot directory had permission 750 or 755 (depending
on the uid-range system feature) and was owned by root/nixbld. This
makes it possible for any nixbld user (if uid-range is disabled) or
any user (if uid-range is enabled) to inspect the contents of the
chroot of an active build and maybe interfere with it (e.g. via /tmp
in the chroot, which has 1777 permission).

To prevent this, the root is now a subdirectory of .chroot, which has
permission 700 and is owned by root/root.

(cherry picked from commit af280e72fa0e62e1c2eaccfb992c0dbb6f27f895)
2024-06-21 16:39:44 +02:00
John Ericson
d3ca72cfd5 Merge pull request #10850 from NixOS/backport-10549-to-2.20-maintenance
[Backport 2.20-maintenance] Fix exportReferencesGraph when given store subpath
2024-06-04 06:46:39 -04:00
Alyssa Ross
f6b6c996a7 Fix exportReferencesGraph when given store subpath
With Nix 2.3, it was possible to pass a subpath of a store path to
exportReferencesGraph:

	with import <nixpkgs> {};

	let
	  hello = writeShellScriptBin "hello" ''
	    echo ${toString builtins.currentTime}
	  '';
	in

	writeClosure [ "${hello}/bin/hello" ]

This regressed with Nix 2.4, with a very confusing error message, that
presumably indicates it was unintentional:

	error: path '/nix/store/3gl7kgjr4pwf03f0x70dgx9ln3bhl7zc-hello/bin/hello' is not in the Nix store

(cherry picked from commit 0774e8ba33)
2024-06-04 10:26:19 +00:00
Robert Hensing
d78915d211 Merge pull request #10844 from NixOS/backport-9897-to-2.20-maintenance
[Backport 2.20-maintenance] libutil/url: fix git+file:./ parse error
2024-06-04 11:04:38 +02:00
Bryan Lai
7b39e21e77 libutil/url: fix git+file:./ parse error
Previously, the "file:./" prefix was not correctly recognized in
fixGitURL; instead, it was mistaken as a file path, which resulted in a
parsed url of the form "file://file:./".

This commit fixes the issue by properly detecting the "file:" prefix.
Note, however, that unlike "file://", the "file:./" URI is _not_
standardized, but has been widely used to referred to relative file
paths. In particular, the "git+file:./" did work for nix<=2.18, and was
broken since nix 2.19.0.

Finally, this commit fixes the issue completely for the 2.19 series, but
is still inadequate for the 2.20 series due to new behaviors from the
switch to libgit2. However, it does improve the correctness of parsing
even though it is not yet a complete solution.

(cherry picked from commit 8594f3cd5a)
2024-06-04 08:27:10 +00:00
github-actions[bot]
ab48ea416a remove link to relocated manual page (#10705)
fix old anchor redirects to point to the correct location

(cherry picked from commit 45697ba502)

Co-authored-by: Valentin Gagarin <valentin.gagarin@tweag.io>
2024-05-15 22:41:14 +02:00
Robert Hensing
2cb5f579bf Merge pull request #10671 from NixOS/backport-10588-to-2.20-maintenance
[Backport 2.20-maintenance] Fix fetchGit/fetchTree for nested submodules
2024-05-09 11:33:13 +02:00
Robert Hensing
630497bff7 Fix fetchGit nested submodules
(cherry picked from commit 750bcaa330)
2024-05-09 09:13:59 +00:00
Théophane Hufschmitt
bb8a4a3d0d Add a release note for the build-dir hardening 2024-04-22 15:34:48 +02:00
Théophane Hufschmitt
0e4baff868 Run the builds in a daemon-controled directory
Instead of running the builds under
`$TMPDIR/{unique-build-directory-owned-by-the-build-user}`, run them
under `$TMPDIR/{unique-build-directory-owned-by-the-daemon}/{subdir-owned-by-the-build-user}`
where the build directory is only readable and traversable by the daemon user.

This achieves two things:

1. It prevents builders from making their build directory world-readable
   (or even writeable), which would allow the outside world to interact
   with them.
2. It prevents external processes running as the build user (either
   because that somehow leaked, maybe as a consequence of 1., or because
   `build-users` isn't in use) from gaining access to the build
   directory.
2024-04-22 15:34:48 +02:00
Théophane Hufschmitt
cad14405c2 Add a test for the user sandboxing 2024-04-22 15:34:48 +02:00
Théophane Hufschmitt
fcdf99b5f5 Merge pull request #10459 from Ma27/backport-rl-2.20-changes
[2.20] Backport changes to release notes
2024-04-11 20:39:28 +02:00
Théophane Hufschmitt
1cf8c57990 Merge pull request #10471 from NixOS/backport-10456-to-2.20-maintenance
[Backport 2.20-maintenance] Fix adding symlink to the sandbox paths
2024-04-11 18:26:02 +02:00
Théophane Hufschmitt
ccb9779b96 Fix permission denied when building symlink derivation which points to a symlink out of the store
Bind-mounting symlinks is apparently not possible, which is why the
thing was failing.

Fortunately, symlinks are small, so we can fallback to copy them at no cost.

Fix https://github.com/NixOS/nix/issues/9579

Co-authored-by: Artturin <Artturin@artturin.com>
(cherry picked from commit 913db9f738)
2024-04-11 12:19:07 +00:00
Théophane Hufschmitt
f7146d25ec Add a test for depending on a symlink store path
Regression test for https://github.com/NixOS/nix/issues/9579

(cherry picked from commit 872d93eb13)
2024-04-11 12:19:07 +00:00
Maximilian Bosch
077bc08f9a doc/rl-2.20: clarify builders-use-substitutes vs. substitute-on-destination
...as this lead to confusion before.

(cherry picked from commit 50557adb3b)
2024-04-11 14:18:10 +02:00
Maximilian Bosch
9e077b2d47 doc/rl-2.20: add missing entry about nix copy --to ssh-ng://...
This requires `--substitute-on-destination` if you want the remote side
to substitute instead of copying if possible.

For completeness sake, document it here.

Also, the stable Nix from nixpkgs is still 2.18, so more folks may
stumble upon this when this is bumped, so I'd expect this to be actually
useful.

Closes #10182

(cherry picked from commit f34b8de5b2)
2024-04-11 14:18:06 +02:00
Eelco Dolstra
202842e898 Merge pull request #10461 from NixOS/backport-10413-to-2.20-maintenance
[Backport 2.20-maintenance] path-info: print correct path when using `nix path-info --store file://... --all --json`
2024-04-10 22:49:22 +02:00
Maximilian Bosch
8b84348a78 path-info: print correct path when using nix path-info --store file://... --all --json
When querying all paths in a binary cache store, the path's representation
is `<hash>-x` (where `x` is the value of `MissingName`) because the .narinfo
filenames only contain the hash.

Before cc46ea1630 this worked correctly,
because the entire path info was read and the path from this
representation was printed, i.e. in the form `<hash>-<name>`. Since then
however, the direct result from `queryAllValidPaths()` was used as `path`.

Added a regression test to make sure the behavior remains correct.

(cherry picked from commit c80cd6bb06)
2024-04-10 17:37:36 +00:00
Rebecca Turner
7c6bd8b25f Add release notes for "Functions are printed with more detail"
(cherry picked from commit abb5fef355)
2024-04-10 17:36:11 +02:00
Rebecca Turner
a383f3e408 Add release notes for "Nix no longer attempts to git add files that are .gitignored"
(cherry picked from commit 9a5d52262f)
2024-04-10 17:31:43 +02:00
Eelco Dolstra
c79d5195e5 Bump version 2024-04-05 17:24:37 +02:00
Eelco Dolstra
7bc4af7301 Merge pull request #10393 from NixOS/backport-10391-to-2.20-maintenance
[Backport 2.20-maintenance] Handle the case where a parent of ~/.nix-defexpr is a symlink
2024-04-03 18:52:21 +02:00
Eelco Dolstra
70a2c5f607 Handle the case where a parent of ~/.nix-defexpr is a symlink
Fixes https://github.com/DeterminateSystems/nix-installer/issues/912 and probably #10247.

(cherry picked from commit 09551fabd0)
2024-04-03 16:24:03 +00:00
Théophane Hufschmitt
59c629eb13 Merge pull request #10355 from NixOS/backport-10259-to-2.20-maintenance
[Backport 2.20-maintenance] doc: builtins.addDrvOutputDependencies: fix link target
2024-03-29 12:41:28 +01:00
Yueh-Shun Li
8bddaa14d4 builtins.addDrvOutputDependencies: fix commentary
(cherry picked from commit d2b512959c)
2024-03-29 10:56:46 +00:00
Yueh-Shun Li
34684db54d doc: builtins.addDrvOutputDependencies: fix link target
(cherry picked from commit 39b0b8452f)
2024-03-29 10:56:46 +00:00
Robert Hensing
ac9bedda2c Merge pull request #10220 from lheckemann/backport-debugger-fix
[backport] fix debugger crashing while printing envs
2024-03-11 15:46:01 +01:00
pennae
631b2de30f fix debugger crashing while printing envs
fixes #9932

(cherry picked from commit 5ccb06ee1b)
2024-03-11 08:28:17 +01:00
Eelco Dolstra
fea2043060 GitHub fetcher: Ignore treeHash attribute for forward compatibility
See https://github.com/NixOS/nix/pull/10197.
2024-03-08 16:02:01 +01:00
Eelco Dolstra
02069f3058 Bump version 2024-03-07 16:49:52 +01:00
Eelco Dolstra
f8170ce9f1 Merge pull request from GHSA-2ffj-w4mj-pg37
Sandbox escape 2.20
2024-03-07 11:56:24 +01:00
Théophane Hufschmitt
d6918898c9 Add release notes 2024-03-07 09:38:54 +01:00
Théophane Hufschmitt
244f3eee0b Copy the output of fixed-output derivations before registering them
It is possible to exfiltrate a file descriptor out of the build sandbox
of FODs, and use it to modify the store path after it has been
registered.
To avoid that issue, don't register the output of the build, but a copy
of it (that will be free of any leaked file descriptor).
2024-03-07 09:38:51 +01:00
Théophane Hufschmitt
4645652975 Add a NixOS test for the sandbox escape
Test that we can't leverage abstract unix domain sockets to leak file
descriptors out of the sandbox and modify the path after it has been
registered.
2024-03-07 09:38:24 +01:00
Théophane Hufschmitt
584d64bebc Merge pull request #10154 from intelfx/work/fix-null-deref
libfetchers/git: fix UB due to invalid usage of unique_ptr
2024-03-05 09:10:28 +01:00
Ivan Shapovalov
651e62781f libfetchers/git: use unique_ptr::get() instead of operator*()
According to N4950 20.3.1.3.5 [unique.ptr.single.observers]/1,
the behavior is undefined if get() == nullptr. Use get() instead of
operator*() on a possibly-null unique_ptr.

Fixes #10123.
2024-03-05 03:50:26 +01:00
Théophane Hufschmitt
82d7d740c9 Merge pull request #10142 from NixOS/backport-10073-to-2.20-maintenance
[Backport 2.20-maintenance] Accept multiple inputs in `nix flake update`
2024-03-04 10:31:19 +01:00
Olmo Kramer
b005d736ef Add test for nix flake update with multiple inputs
(cherry picked from commit b1ad729add)
2024-03-04 08:54:00 +00:00
Olmo Kramer
31c908a9e2 Accept multiple inputs in nix flake update
(cherry picked from commit 9f11b1b0c4)
2024-03-04 08:54:00 +00:00
Eelco Dolstra
b636f1ecd8 Bump version 2024-02-28 20:23:14 +01:00
Robert Hensing
edcb3430ef Merge pull request #10102 from NixOS/backport-10044-to-2.20-maintenance
[Backport 2.20-maintenance] Handle empty Git repositories / workdirs
2024-02-28 03:00:35 +01:00
Eelco Dolstra
15c0a7b2ce Support empty Git repositories / workdirs
Fixes #10039.

(cherry picked from commit 9e762454cf)
2024-02-28 01:40:43 +00:00
Eelco Dolstra
2e78ef5612 AllowListInputAccessor: Clarify that the "allowed paths" are actually allowed prefixes
E.g. adding "/" will allow access to the root and *everything below it*.

(cherry picked from commit d52d91fe7a)
2024-02-28 01:40:43 +00:00
Eelco Dolstra
7599d4bbed Bump version 2024-02-21 16:22:16 +01:00
Eelco Dolstra
8a8172cd2b Merge pull request #10050 from NixOS/backport-10049-to-2.20-maintenance
[Backport 2.20-maintenance] Don't send settings that depend on disabled experimental features to the daemon
2024-02-21 13:05:51 +01:00
Eelco Dolstra
7b45cc30a1 Merge pull request #10057 from NixOS/backport-10055-to-2.20-maintenance
[Backport 2.20-maintenance] Faster flake.lock parsing
2024-02-21 12:20:21 +01:00
Graham Dennis
e52d384766 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.

(cherry picked from commit 7fd0de38c6)
2024-02-21 11:19:23 +00:00
Eelco Dolstra
0b32c8763b 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.

(cherry picked from commit 0acd783190)
2024-02-20 14:53:28 +00:00
Eelco Dolstra
adb1d56862 Merge pull request #10045 from NixOS/backport-10043-to-2.20-maintenance
[Backport 2.20-maintenance] fetchToStore(): Don't always respect settings.readOnlyMode
2024-02-20 12:50:30 +01:00
Eelco Dolstra
28dd392948 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.

(cherry picked from commit 7cb4d0c5b7)
2024-02-20 11:08:06 +00:00
Eelco Dolstra
7f02d17881 Don't say "copying X to the store" in read-only mode
(cherry picked from commit 6162105675)
2024-02-20 11:08:06 +00:00
Eelco Dolstra
ce23ef4a77 Bump version 2024-02-19 15:37:26 +01:00
Robert Hensing
98c22e8798 Merge pull request #10023 from NixOS/backport-9985-to-2.20-maintenance
[Backport 2.20-maintenance] Restore `builtins.pathExists` behavior on broken symlinks
2024-02-16 22:55:21 +01:00
John Ericson
02f7025deb Add note about this being a temp solution
(cherry picked from commit e27b7e04bf)
2024-02-16 14:24:23 +00:00
Alois Wohlschlager
0571e6e9b4 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.

(cherry picked from commit 89e21ab4bd)
2024-02-16 14:24:23 +00:00
Eelco Dolstra
982d07d009 Merge pull request #10011 from NixOS/backport-10006-to-2.20-maintenance
[Backport 2.20-maintenance] <nix/fetchurl.nix>: Restore support for "impure = true"
2024-02-13 23:03:59 +01:00
Eelco Dolstra
7f66d4f167 <nix/fetchurl.nix>: Restore support for "impure = true"
(cherry picked from commit bb63bd50e6)
2024-02-13 21:51:13 +00:00
Eelco Dolstra
52e53a2983 Merge pull request #9991 from NixOS/backport-9976-to-2.20-maintenance
[Backport 2.20-maintenance] Restore manual pages
2024-02-12 15:31:57 +01:00
Alois Wohlschlager
c5a8b9050c 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.

(cherry picked from commit 8f3253c6f4)
2024-02-12 14:22:06 +00:00
Eelco Dolstra
86dfeebb3d Merge pull request #9958 from NixOS/backport-9949-to-2.20-maintenance
[Backport 2.20-maintenance] fix location of `_redirects` file
2024-02-07 15:26:07 +01:00
Valentin Gagarin
8f14bf4712 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

(cherry picked from commit 2d74b56aee)
2024-02-07 10:35:09 +00:00
Théophane Hufschmitt
10e1579c81 Merge pull request #9910 from NixOS/backport-9902-to-2.20-maintenance
[Backport 2.20-maintenance] builtin:fetchurl: Ensure a fixed-output derivation
2024-02-02 14:13:01 +01:00
Eelco Dolstra
b6bf4a80d8 Better test fix
(cherry picked from commit e67458e5b8)
2024-02-02 13:00:54 +00:00
Eelco Dolstra
955be03476 Fix test
(cherry picked from commit 05535be03a)
2024-02-02 13:00:54 +00:00
Eelco Dolstra
aab4a17258 builtin:fetchurl: Get output hash info from the drv
(cherry picked from commit b8b739e484)
2024-02-02 13:00:54 +00:00
Eelco Dolstra
df2156a5d2 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.

(cherry picked from commit 1ee42c5b88)
2024-02-02 13:00:54 +00:00
John Ericson
db82034fee Merge pull request #9891 from NixOS/backport-9867-to-2.20-maintenance
[Backport 2.20-maintenance] #912 allow leading period
2024-01-31 15:36:03 -05:00
Robert Hensing
b5947b55e2 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.

(cherry picked from commit f1b4663805)
2024-01-31 18:11:17 +00:00
Robert Hensing
60fb31a87d test: Generate distinct hashes
Gen::just is the constant generator. Don't just return that!
(cherry picked from commit 8406da2877)
2024-01-31 18:11:17 +00:00
Robert Hensing
b35958bd7c test: Generate distinct path names
Gen::just is the constant generator. Don't just return that!
(cherry picked from commit 69bbd5852a)
2024-01-31 18:11:17 +00:00
Robert Hensing
f36832ce13 parseStorePath: Support leading period
(cherry picked from commit b13e6a76b4)
2024-01-31 18:11:17 +00:00
Robert Hensing
0f4db25957 Revert "StorePath: reject names starting with '.'"
This reverts commit 24bda0c7b3.

(cherry picked from commit 9ddd0f2af8)
2024-01-31 18:11:17 +00:00
Eelco Dolstra
8f42912c80 Bump version 2024-01-30 18:58:56 +01:00
Eelco Dolstra
a4a4ef9b53 Merge pull request #9886 from NixOS/backport-9884-to-2.20-maintenance
[Backport 2.20-maintenance] Resolve symlinks in a few more places
2024-01-30 17:10:35 +01:00
Eelco Dolstra
5ad5b4447c Resolve symlinks in a few more places
Fixes #9882.

(cherry picked from commit b36ff47e7c)
2024-01-30 16:10:21 +00:00
Eelco Dolstra
1b2b240f22 Bump version 2024-01-29 22:56:56 +01:00
Eelco Dolstra
16e1ff3bcb Mark as stable 2024-01-29 18:59:20 +01:00
247 changed files with 3389 additions and 5367 deletions

View File

@@ -1,3 +0,0 @@
# 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.1
uses: zeebe-io/backport-action@v2.4.0
with:
# Config README: https://github.com/zeebe-io/backport-action#backport-action
github_token: ${{ secrets.GITHUB_TOKEN }}

3
.gitignore vendored
View File

@@ -108,9 +108,6 @@ 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.21.0
2.20.8

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,7 +12,6 @@ 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 \
@@ -42,7 +41,6 @@ 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 \

View File

@@ -47,10 +47,6 @@ 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,8 +6,6 @@ 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": "command-ref/conf-file.html#conf-builders",
"chap-distributed-builds": "advanced-topics/distributed-builds.html",
"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",
@@ -290,10 +290,10 @@ const redirects = {
"ssec-gc-roots": "package-management/garbage-collector-roots.html",
"chap-package-management": "package-management/package-management.html",
"sec-profiles": "package-management/profiles.html",
"ssec-s3-substituter": "package-management/s3-substituter.html",
"ssec-s3-substituter-anonymous-reads": "package-management/s3-substituter.html#anonymous-reads-to-your-s3-compatible-binary-cache",
"ssec-s3-substituter-authenticated-reads": "package-management/s3-substituter.html#authenticated-reads-to-your-s3-binary-cache",
"ssec-s3-substituter-authenticated-writes": "package-management/s3-substituter.html#authenticated-writes-to-your-s3-compatible-binary-cache",
"ssec-s3-substituter": "store/types/s3-substituter.html",
"ssec-s3-substituter-anonymous-reads": "store/types/s3-substituter.html#anonymous-reads-to-your-s3-compatible-binary-cache",
"ssec-s3-substituter-authenticated-reads": "store/types/s3-substituter.html#authenticated-reads-to-your-s3-binary-cache",
"ssec-s3-substituter-authenticated-writes": "store/types/s3-substituter.html#authenticated-writes-to-your-s3-compatible-binary-cache",
"sec-sharing-packages": "package-management/sharing-packages.html",
"ssec-ssh-substituter": "package-management/ssh-substituter.html",
"chap-quick-start": "quick-start.html",
@@ -358,11 +358,7 @@ 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

@@ -1,40 +0,0 @@
---
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

@@ -1,9 +0,0 @@
---
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

@@ -1,9 +0,0 @@
---
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

@@ -1,25 +0,0 @@
---
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

@@ -1,25 +0,0 @@
---
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,14 @@
---
synopsis: Fix a FOD sandbox escape
issues:
prs:
---
Cooperating Nix derivations could send file descriptors to files in the Nix
store to each other via Unix domain sockets in the abstract namespace. This
allowed one derivation to modify the output of the other derivation, after Nix
has registered the path as "valid" and immutable in the Nix database.
In particular, this allowed the output of fixed-output derivations to be
modified from their expected content.
This isn't the case any more.

View File

@@ -0,0 +1,7 @@
---
synopsis: Harden the user sandboxing
significance: significant
issues:
---
The build directory has been hardened against interference with the outside world by nesting it inside another directory owned by (and only readable by) the daemon user.

View File

@@ -1,50 +0,0 @@
---
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

@@ -1,13 +0,0 @@
---
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

@@ -1,24 +0,0 @@
---
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

@@ -1,37 +0,0 @@
---
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

@@ -1,8 +0,0 @@
---
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

@@ -1,22 +0,0 @@
---
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

@@ -1,9 +0,0 @@
---
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

@@ -42,7 +42,6 @@
- [Serving a Nix store via HTTP](package-management/binary-cache-substituter.md)
- [Copying Closures via SSH](package-management/copy-closure.md)
- [Serving a Nix store via SSH](package-management/ssh-substituter.md)
- [Serving a Nix store via S3](package-management/s3-substituter.md)
- [Remote Builds](advanced-topics/distributed-builds.md)
- [Tuning Cores and Jobs](advanced-topics/cores-vs-jobs.md)
- [Verifying Build Reproducibility](advanced-topics/diff-hook.md)
@@ -104,12 +103,11 @@
- [Channels](command-ref/files/channels.md)
- [Default Nix expression](command-ref/files/default-nix-expression.md)
- [Architecture and Design](architecture/architecture.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)
- [JSON Formats](json/index.md)
- [Store Object Info](json/store-object-info.md)
- [Derivation](json/derivation.md)
- [Protocols](protocols/index.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,6 +36,5 @@
/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,8 +36,16 @@ error: cannot connect to 'mac'
then you need to ensure that the `PATH` of non-interactive login shells
contains Nix.
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:
> **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:
```console
$ uname
@@ -52,20 +60,97 @@ $ cat ./result
Darwin
```
It is possible to specify multiple build machines separated by a semicolon or a newline, e.g.
It is possible to specify multiple builders separated by a semicolon or
a newline, e.g.
```console
--builders 'ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd'
```
Remote build machines can also be configured in [`nix.conf`](@docroot@/command-ref/conf-file.md), e.g.
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.
builders = ssh://mac x86_64-darwin ; ssh://beastie x86_64-freebsd
Finally, remote build machines can be configured in a separate configuration
file included in `builders` via the syntax `@/path/to/file`. For example,
Finally, remote builders can be configured in a separate configuration
file included in `builders` via the syntax `@file`. For example,
builders = @/etc/nix/machines
causes the list of machines in `/etc/nix/machines` to be included.
(This is the default.)
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`.

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`](@docroot@/command-ref/nix-env/delete-generations.md#generations-old) on each found profile.
This is the equivalent of invoking `nix-env --delete-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>...`](#generations-list)</span>:\
- <span id="generations-list">`<number>...`</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.
- <span id="generations-old">[The special value `old`](#generations-old)</span>
- The special value <span id="generations-old">`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`](#generations-time)</span>:\
- <span id="generations-time">`<number>d`</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>`](#generations-count)</span>:\
- <span id="generations-count">`+<number>`</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 OPTIMIZE=0
[nix-shell]$ make -j $NIX_BUILD_CORES
```
To install it in `$(pwd)/outputs` and test it:
```console
[nix-shell]$ make install OPTIMIZE=0
[nix-shell]$ make install
[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 build machines] and [binary format emulation]
platform. Common solutions include [remote builders] and [binary format emulation]
(only supported on NixOS).
[remote builders]: @docroot@/language/derivations.md#attr-builder
[remote builders]: ../advanced-topics/distributed-builds.md
[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 machine](@docroot@/command-ref/conf-file.md#conf-builders) and retrieving the outputs
- Delegating to a [remote builder](@docroot@/advanced-topics/distributed-builds.html) 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,12 +59,23 @@
- [store]{#gloss-store}
A collection of [store objects][store object], with operations to manipulate that collection.
See [Nix Store](./store/index.md) for details.
A collection of store objects, with operations to manipulate that collection.
See [Nix store](./store/index.md) for details.
There are many types of stores, see [Store Types](./store/types/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.
[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}
@@ -76,7 +87,7 @@
- [store path]{#gloss-store-path}
The location of a [store object] in the file system, i.e., an immediate child of the Nix store directory.
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.
> **Example**
>
@@ -232,7 +243,6 @@
- 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

@@ -1,138 +0,0 @@
# 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

@@ -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].
Hash of the [file system object] part of the store object when serialized as a [Nix Archive](#gloss-nar).
* `narSize`:
Size of the [file system object] part of the store object when serialized as a [Nix Archive].
Size of the [file system object] part of the store object when serialized as a [Nix Archive](#gloss-nar).
* `references`:
@@ -30,7 +30,6 @@ 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

@@ -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](@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.
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.
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](@docroot@/language/derivations.md#attr-builder) by forwarding build requests to other machines.
It can automatically [build on other platforms](../advanced-topics/distributed-builds.md) by forwarding build requests to other machines.
[`system` configuration option]: @docroot@/command-ref/conf-file.md#conf-system

View File

@@ -1,8 +1,6 @@
# Import From Derivation
The value of a Nix expression can depend on the contents of a [store object].
[store object]: @docroot@/glossary.md#gloss-store-object
The value of a Nix expression can depend on the contents of a [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 [strings][string] and merge their string contexts.
Concatenate two [string]s and merge their string contexts.
[String concatenation]: #string-concatenation
@@ -94,7 +94,7 @@ Concatenate two [strings][string] and merge their string contexts.
>
> *path* `+` *path*
Concatenate two [paths][path].
Concatenate two [path]s.
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 [numbers][number]
- lexicographic for [strings][string] and [paths][path]
- item-wise lexicographic for [lists][list]:
- [arithmetic] for [number]s
- lexicographic for [string]s and [path]s
- item-wise lexicographic for [list]s:
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
[Comparison]: #comparison-operators
## Equality
- [Attribute sets][attribute set] and [lists][list] are compared recursively, and therefore are fully evaluated.
- Comparison of [functions][function] always returns `false`.
- [Attribute sets][attribute set] and [list]s are compared recursively, and therefore are fully evaluated.
- Comparison of [function]s always returns `false`.
- Numbers are type-compatible, see [arithmetic] operators.
- Floating point numbers only differ up to a limited precision.

View File

@@ -20,8 +20,6 @@ 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,8 +156,6 @@ 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

@@ -1,126 +0,0 @@
# 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

@@ -191,12 +191,8 @@
[#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.
- `nix copy` to a `ssh-ng` store now needs `--substitute-on-destination` (a.k.a. `-s`)
in order to substitute paths on the remote store instead of copying them.
The behavior is consistent with `nix copy` to a different kind of remote store.
Previously this behavior was controlled by the
`builders-use-substitutes` setting and `--substitute-on-destination` was ignored.

View File

@@ -19,7 +19,7 @@
inherit (import (builtins.fetchTarball { url = "https://github.com/NixOS/nix/archive/1bdcd7fc8a6a40b2e805bad759b36e64e911036b.tar.gz"; sha256 = "sha256:14ljlpdsp4x7h1fkhbmc4bd3vsqnx8zdql4h3037wh09ad6a0893"; }))
fileset;
officialRelease = false;
officialRelease = true;
# Set to true to build the release notes for the next release.
buildUnreleasedNotes = false;
@@ -152,30 +152,6 @@
'';
};
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;
@@ -198,7 +174,7 @@
nix =
let
officialRelease = false;
officialRelease = true;
versionSuffix =
if officialRelease
then ""
@@ -210,7 +186,7 @@
stdenv
versionSuffix
;
officialRelease = false;
officialRelease = true;
boehmgc = final.boehmgc-nix;
libgit2 = final.libgit2-nix;
busybox-sandbox-shell = final.busybox-sandbox-shell or final.default-busybox-sandbox-shell;
@@ -390,9 +366,6 @@
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,8 +1,6 @@
ifdef HOST_LINUX
$(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.socket nix-daemon.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

@@ -1,21 +0,0 @@
[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

@@ -1,12 +0,0 @@
[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

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

View File

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

View File

@@ -5,12 +5,12 @@
, nix, curl, bzip2, xz, boost, libsodium, darwin
}:
perl.pkgs.toPerlModule (stdenv.mkDerivation (finalAttrs: {
perl.pkgs.toPerlModule (stdenv.mkDerivation {
name = "nix-perl-${nix.version}";
src = fileset.toSource {
root = ../.;
fileset = fileset.unions ([
fileset = fileset.unions [
../.version
../m4
../mk
@@ -20,10 +20,7 @@ perl.pkgs.toPerlModule (stdenv.mkDerivation (finalAttrs: {
./configure.ac
./lib
./local.mk
] ++ lib.optionals finalAttrs.doCheck [
./.yath.rc
./t
]);
];
};
nativeBuildInputs =
@@ -43,13 +40,6 @@ perl.pkgs.toPerlModule (stdenv.mkDerivation (finalAttrs: {
++ 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}"
@@ -58,4 +48,4 @@ perl.pkgs.toPerlModule (stdenv.mkDerivation (finalAttrs: {
enableParallelBuilding = true;
postUnpack = "sourceRoot=$sourceRoot/perl";
}))
})

View File

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

View File

@@ -17,74 +17,36 @@
#include <sodium.h>
#include <nlohmann/json.hpp>
using namespace nix;
static bool libStoreInitialized = false;
struct StoreWrapper {
ref<Store> store;
};
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);
}
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()
StoreWrapper *
StoreWrapper::new(char * s = nullptr)
CODE:
static std::shared_ptr<Store> _store;
try {
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());
}
OUTPUT:
RETVAL
void init()
CODE:
if (!libStoreInitialized) {
initLibStore();
libStoreInitialized = true;
}
store();
void setVerbosity(int level)
@@ -92,11 +54,10 @@ void setVerbosity(int level)
verbosity = (Verbosity) level;
int
StoreWrapper::isValidPath(char * path)
int isValidPath(char * path)
CODE:
try {
RETVAL = THIS->store->isValidPath(THIS->store->parseStorePath(path));
RETVAL = store()->isValidPath(store()->parseStorePath(path));
} catch (Error & e) {
croak("%s", e.what());
}
@@ -104,56 +65,52 @@ StoreWrapper::isValidPath(char * path)
RETVAL
SV *
StoreWrapper::queryReferences(char * path)
SV * queryReferences(char * path)
PPCODE:
try {
for (auto & i : THIS->store->queryPathInfo(THIS->store->parseStorePath(path))->references)
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
for (auto & i : store()->queryPathInfo(store()->parseStorePath(path))->references)
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(i).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::queryPathHash(char * path)
SV * queryPathHash(char * path)
PPCODE:
try {
auto s = THIS->store->queryPathInfo(THIS->store->parseStorePath(path))->narHash.to_string(HashFormat::Nix32, true);
auto s = store()->queryPathInfo(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 *
StoreWrapper::queryDeriver(char * path)
SV * queryDeriver(char * path)
PPCODE:
try {
auto info = THIS->store->queryPathInfo(THIS->store->parseStorePath(path));
auto info = store()->queryPathInfo(store()->parseStorePath(path));
if (!info->deriver) XSRETURN_UNDEF;
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(*info->deriver).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(*info->deriver).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::queryPathInfo(char * path, int base32)
SV * queryPathInfo(char * path, int base32)
PPCODE:
try {
auto info = THIS->store->queryPathInfo(THIS->store->parseStorePath(path));
auto info = store()->queryPathInfo(store()->parseStorePath(path));
if (!info->deriver)
XPUSHs(&PL_sv_undef);
else
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(*info->deriver).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(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(THIS->store->printStorePath(i).c_str(), 0));
av_push(refs, newSVpv(store()->printStorePath(i).c_str(), 0));
XPUSHs(sv_2mortal(newRV((SV *) refs)));
AV * sigs = newAV();
for (auto & i : info->sigs)
@@ -163,11 +120,10 @@ StoreWrapper::queryPathInfo(char * path, int base32)
croak("%s", e.what());
}
SV *
StoreWrapper::queryRawRealisation(char * outputId)
SV * queryRawRealisation(char * outputId)
PPCODE:
try {
auto realisation = THIS->store->queryRealisation(DrvOutput::parse(outputId));
auto realisation = store()->queryRealisation(DrvOutput::parse(outputId));
if (realisation)
XPUSHs(sv_2mortal(newSVpv(realisation->toJSON().dump().c_str(), 0)));
else
@@ -177,50 +133,46 @@ StoreWrapper::queryRawRealisation(char * outputId)
}
SV *
StoreWrapper::queryPathFromHashPart(char * hashPart)
SV * queryPathFromHashPart(char * hashPart)
PPCODE:
try {
auto path = THIS->store->queryPathFromHashPart(hashPart);
XPUSHs(sv_2mortal(newSVpv(path ? THIS->store->printStorePath(*path).c_str() : "", 0)));
auto path = store()->queryPathFromHashPart(hashPart);
XPUSHs(sv_2mortal(newSVpv(path ? store()->printStorePath(*path).c_str() : "", 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::computeFSClosure(int flipDirection, int includeOutputs, ...)
SV * computeFSClosure(int flipDirection, int includeOutputs, ...)
PPCODE:
try {
StorePathSet paths;
for (int n = 2; n < items; ++n)
THIS->store->computeFSClosure(THIS->store->parseStorePath(SvPV_nolen(ST(n))), paths, flipDirection, includeOutputs);
store()->computeFSClosure(store()->parseStorePath(SvPV_nolen(ST(n))), paths, flipDirection, includeOutputs);
for (auto & i : paths)
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(i).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::topoSortPaths(...)
SV * topoSortPaths(...)
PPCODE:
try {
StorePathSet paths;
for (int n = 0; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
auto sorted = THIS->store->topoSortPaths(paths);
for (int n = 0; n < items; ++n) paths.insert(store()->parseStorePath(SvPV_nolen(ST(n))));
auto sorted = store()->topoSortPaths(paths);
for (auto & i : sorted)
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(i).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(i).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::followLinksToStorePath(char * path)
SV * followLinksToStorePath(char * path)
CODE:
try {
RETVAL = newSVpv(THIS->store->printStorePath(THIS->store->followLinksToStorePath(path)).c_str(), 0);
RETVAL = newSVpv(store()->printStorePath(store()->followLinksToStorePath(path)).c_str(), 0);
} catch (Error & e) {
croak("%s", e.what());
}
@@ -228,37 +180,34 @@ StoreWrapper::followLinksToStorePath(char * path)
RETVAL
void
StoreWrapper::exportPaths(int fd, ...)
void exportPaths(int fd, ...)
PPCODE:
try {
StorePathSet paths;
for (int n = 1; n < items; ++n) paths.insert(THIS->store->parseStorePath(SvPV_nolen(ST(n))));
for (int n = 1; n < items; ++n) paths.insert(store()->parseStorePath(SvPV_nolen(ST(n))));
FdSink sink(fd);
THIS->store->exportPaths(paths, sink);
store()->exportPaths(paths, sink);
} catch (Error & e) {
croak("%s", e.what());
}
void
StoreWrapper::importPaths(int fd, int dontCheckSigs)
void importPaths(int fd, int dontCheckSigs)
PPCODE:
try {
FdSource source(fd);
THIS->store->importPaths(source, dontCheckSigs ? NoCheckSigs : CheckSigs);
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 {
auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(path);
PosixSourceAccessor accessor;
Hash h = hashPath(
accessor, canonPath,
accessor, CanonPath::fromCwd(path),
FileIngestionMethod::Recursive, parseHashAlgo(algo)).first;
auto s = h.to_string(base32 ? HashFormat::Nix32 : HashFormat::Base16, false);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
@@ -331,67 +280,64 @@ int checkSignature(SV * publicKey_, SV * sig_, char * msg)
RETVAL
SV *
StoreWrapper::addToStore(char * srcPath, int recursive, char * algo)
SV * addToStore(char * srcPath, int recursive, char * algo)
PPCODE:
try {
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
auto [accessor, canonPath] = PosixSourceAccessor::createAtRoot(srcPath);
auto path = THIS->store->addToStore(
PosixSourceAccessor accessor;
auto path = store()->addToStore(
std::string(baseNameOf(srcPath)),
accessor, canonPath,
accessor, CanonPath::fromCwd(srcPath),
method, parseHashAlgo(algo));
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(path).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::makeFixedOutputPath(int recursive, char * algo, char * hash, char * name)
SV * 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 = THIS->store->makeFixedOutputPath(name, FixedOutputInfo {
auto path = store()->makeFixedOutputPath(name, FixedOutputInfo {
.method = method,
.hash = h,
.references = {},
});
XPUSHs(sv_2mortal(newSVpv(THIS->store->printStorePath(path).c_str(), 0)));
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
}
SV *
StoreWrapper::derivationFromPath(char * drvPath)
SV * derivationFromPath(char * drvPath)
PREINIT:
HV *hash;
CODE:
try {
Derivation drv = THIS->store->derivationFromPath(THIS->store->parseStorePath(drvPath));
Derivation drv = store()->derivationFromPath(store()->parseStorePath(drvPath));
hash = newHV();
HV * outputs = newHV();
for (auto & i : drv.outputsAndOptPaths(*THIS->store)) {
for (auto & i : drv.outputsAndOptPaths(*store())) {
hv_store(
outputs, i.first.c_str(), i.first.size(),
!i.second.second
? newSV(0) /* null value */
: newSVpv(THIS->store->printStorePath(*i.second.second).c_str(), 0),
: newSVpv(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(THIS->store->printStorePath(i.first).c_str(), 0)); // !!! ignores i->second
av_push(inputDrvs, newSVpv(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(THIS->store->printStorePath(i).c_str(), 0));
av_push(inputSrcs, newSVpv(store()->printStorePath(i).c_str(), 0));
hv_stores(hash, "inputSrcs", newRV((SV *) inputSrcs));
hv_stores(hash, "platform", newSVpv(drv.platform.c_str(), 0));
@@ -415,11 +361,10 @@ StoreWrapper::derivationFromPath(char * drvPath)
RETVAL
void
StoreWrapper::addTempRoot(char * storePath)
void addTempRoot(char * storePath)
PPCODE:
try {
THIS->store->addTempRoot(THIS->store->parseStorePath(storePath));
store()->addTempRoot(store()->parseStorePath(storePath));
} catch (Error & e) {
croak("%s", e.what());
}

View File

@@ -41,6 +41,3 @@ 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

View File

@@ -1,13 +0,0 @@
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 "u=rw,go=r" "/nix/var/nix/profiles/default$NIX_DAEMON_DEST" "$NIX_DAEMON_DEST"
/usr/bin/install -m -rw-r--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 = HintFmt(errorText);
auto error = hintformat(errorText);
error
% drvstr
% neededSystem

View File

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

View File

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

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->string());
args.push_back(path->abs());
return args;
}

View File

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

View File

@@ -1,121 +0,0 @@
#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

@@ -1,21 +0,0 @@
#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,27 +52,6 @@ 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
@@ -96,13 +75,13 @@ struct NixRepl
std::function<AnnotatedValues()> getValues);
virtual ~NixRepl();
ReplExitStatus mainLoop() override;
void mainLoop() override;
void initEnv() override;
StringSet completePrefix(const std::string & prefix);
bool getLine(std::string & input, const std::string & prompt);
StorePath getDerivationPath(Value & v);
ProcessLineResult processLine(std::string line);
bool processLine(std::string line);
void loadFile(const Path & path);
void loadFlake(const std::string & flakeRef);
@@ -122,8 +101,7 @@ struct NixRepl
.ansiColors = true,
.force = true,
.derivationPaths = true,
.maxDepth = maxDepth,
.prettyIndent = 2
.maxDepth = maxDepth
});
}
};
@@ -254,7 +232,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);
@@ -265,19 +243,10 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi
return out;
}
static bool isFirstRepl = true;
ReplExitStatus NixRepl::mainLoop()
void NixRepl::mainLoop()
{
if (isFirstRepl) {
std::string_view debuggerNotice = "";
if (state->debugRepl) {
debuggerNotice = " debugger";
}
notice("Nix %1%%2%\nType :? for help.", nixVersion, debuggerNotice);
}
isFirstRepl = false;
std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
loadFiles();
@@ -308,25 +277,15 @@ ReplExitStatus 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("");
// TODO: Should Ctrl-D exit just the current debugger session or
// the entire program?
return ReplExitStatus::QuitAll;
break;
}
logger->resume();
try {
switch (processLine(input)) {
case ProcessLineResult::Quit:
return ReplExitStatus::QuitAll;
case ProcessLineResult::Continue:
return ReplExitStatus::Continue;
case ProcessLineResult::PromptAgain:
break;
default:
abort();
}
if (!removeWhitespace(input).empty() && !processLine(input)) return;
} 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
@@ -463,6 +422,8 @@ 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.
}
@@ -514,11 +475,10 @@ void NixRepl::loadDebugTraceEnv(DebugTrace & dt)
}
}
ProcessLineResult NixRepl::processLine(std::string line)
bool NixRepl::processLine(std::string line)
{
line = trim(line);
if (line.empty())
return ProcessLineResult::PromptAgain;
if (line == "") return true;
_isInterrupted = false;
@@ -613,13 +573,13 @@ ProcessLineResult 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 ProcessLineResult::Continue;
return false;
}
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 ProcessLineResult::Continue;
return false;
}
else if (command == ":a" || command == ":add") {
@@ -762,7 +722,8 @@ ProcessLineResult NixRepl::processLine(std::string line)
else if (command == ":q" || command == ":quit") {
state->debugStop = false;
return ProcessLineResult::Quit;
state->debugQuit = true;
return false;
}
else if (command == ":doc") {
@@ -823,7 +784,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
}
}
return ProcessLineResult::PromptAgain;
return true;
}
void NixRepl::loadFile(const Path & path)
@@ -929,7 +890,7 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
Expr * NixRepl::parseString(std::string s)
{
return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv);
return state->parseExprFromString(std::move(s), state->rootPath(CanonPath::fromCwd()), staticEnv);
}
@@ -954,7 +915,7 @@ std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
}
ReplExitStatus AbstractNixRepl::runSimple(
void AbstractNixRepl::runSimple(
ref<EvalState> evalState,
const ValMap & extraEnv)
{
@@ -976,7 +937,7 @@ ReplExitStatus AbstractNixRepl::runSimple(
for (auto & [name, value] : extraEnv)
repl->addVarToScope(repl->state->symbols.create(name), *value);
return repl->mainLoop();
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 ReplExitStatus runSimple(
static void runSimple(
ref<EvalState> evalState,
const ValMap & extraEnv);
virtual void initEnv() = 0;
virtual ReplExitStatus mainLoop() = 0;
virtual void mainLoop() = 0;
};
}

View File

@@ -65,10 +65,10 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
if (!attrIndex) {
if (v->type() != nAttrs)
state.error<TypeError>(
throw TypeError(
"the expression selected by the selection path '%1%' should be a set but is %2%",
attrPath,
showType(*v)).debugThrow();
showType(*v));
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())
state.error<TypeError>(
throw TypeError(
"the expression selected by the selection path '%1%' should be a list but is %2%",
attrPath,
showType(*v)).debugThrow();
showType(*v));
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(root->state, "cached failure of attribute '%s'", getAttrPathStr(name));
throw CachedEvalError("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;
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
}
}
@@ -508,7 +508,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
if (v.type() != nAttrs)
return nullptr;
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
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<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
}
}
auto & v = forceValue();
if (v.type() != nString && v.type() != nPath)
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
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<TypeError>("'%s' is not a string", getAttrPathStr()).debugThrow();
root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
}
}
@@ -630,7 +630,7 @@ string_t AttrCursor::getStringWithContext()
else if (v.type() == nPath)
return {v.path().to_string(), {}};
else
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr()).debugThrow();
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
}
bool AttrCursor::getBool()
@@ -643,14 +643,14 @@ bool AttrCursor::getBool()
debug("using cached Boolean attribute '%s'", getAttrPathStr());
return *b;
} else
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
}
}
auto & v = forceValue();
if (v.type() != nBool)
root->state.error<TypeError>("'%s' is not a Boolean", getAttrPathStr()).debugThrow();
root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
return v.boolean;
}
@@ -665,14 +665,14 @@ NixInt AttrCursor::getInt()
debug("using cached integer attribute '%s'", getAttrPathStr());
return i->x;
} else
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
throw TypeError("'%s' is not an integer", getAttrPathStr());
}
}
auto & v = forceValue();
if (v.type() != nInt)
root->state.error<TypeError>("'%s' is not an integer", getAttrPathStr()).debugThrow();
throw TypeError("'%s' is not an integer", getAttrPathStr());
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
root->state.error<TypeError>("'%s' is not a list of strings", getAttrPathStr()).debugThrow();
throw TypeError("'%s' is not a list of strings", getAttrPathStr());
}
}
@@ -697,7 +697,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
root->state.forceValue(v, noPos);
if (v.type() != nList)
root->state.error<TypeError>("'%s' is not a list", getAttrPathStr()).debugThrow();
throw TypeError("'%s' is not a list", getAttrPathStr());
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<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
}
}
auto & v = forceValue();
if (v.type() != nAttrs)
root->state.error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs)

View File

@@ -1,113 +0,0 @@
#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>;
}

View File

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

View File

@@ -127,16 +127,6 @@ 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,7 +3,6 @@
#include "hash.hh"
#include "primops.hh"
#include "print-options.hh"
#include "shared.hh"
#include "types.hh"
#include "util.hh"
#include "store-api.hh"
@@ -340,6 +339,46 @@ 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,
@@ -395,14 +434,14 @@ EvalState::EvalState(
, emptyBindings(0)
, rootFS(
evalSettings.restrictEval || evalSettings.pureEval
? ref<InputAccessor>(AllowListInputAccessor::create(makeFSInputAccessor(), {},
? ref<InputAccessor>(AllowListInputAccessor::create(makeFSInputAccessor(CanonPath::root), {},
[](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())
: makeFSInputAccessor(CanonPath::root))
, corepkgsFS(makeMemoryInputAccessor())
, internalFS(makeMemoryInputAccessor())
, derivationInternal{corepkgsFS->addFile(
@@ -417,6 +456,7 @@ EvalState::EvalState(
, buildStore(buildStore ? buildStore : store)
, debugRepl(nullptr)
, debugStop(false)
, debugQuit(false)
, trylevel(0)
, regexCache(makeRegexCache())
#if HAVE_BOEHMGC
@@ -773,7 +813,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
? std::make_unique<DebugTraceStacker>(
*this,
DebugTrace {
.pos = error->info().pos ? error->info().pos : positions[expr.getPos()],
.pos = error->info().errPos ? error->info().errPos : positions[expr.getPos()],
.expr = expr,
.env = env,
.hint = error->info().msg,
@@ -783,26 +823,18 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
if (error)
{
printError("%s\n", error->what());
printError("%s\n\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);
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();
}
(debugRepl)(ref<EvalState>(shared_from_this()), *vm);
}
}
@@ -813,23 +845,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 Args & ... formatArgs)
const char * s,
const std::string & s2)
{
return std::make_unique<DebugTraceStacker>(state,
DebugTrace {
.pos = std::move(pos),
.expr = expr,
.env = env,
.hint = HintFmt(formatArgs...),
.hint = hintfmt(s, s2),
.isError = false
});
}
@@ -900,7 +932,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
return j->value;
}
if (!fromWith->parentWith)
error<UndefinedVarError>("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow();
error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow<UndefinedVarError>();
for (size_t l = fromWith->prevWith; l; --l, env = env->up) ;
fromWith = fromWith->parentWith;
}
@@ -1106,7 +1138,7 @@ void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
// computation.
if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e)))
error<EvalError>("file '%s' must be an attribute set", path).debugThrow();
error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
eval(e, v);
} catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string());
@@ -1137,11 +1169,10 @@ 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<TypeError>(
"expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).withFrame(env, *e).debugThrow();
error("expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withFrame(env, *e).debugThrow<TypeError>();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
@@ -1155,11 +1186,10 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx po
try {
e->eval(*this, env, v);
if (v.type() != nAttrs)
error<TypeError>(
"expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).withFrame(env, *e).debugThrow();
error("expected a set but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withFrame(env, *e).debugThrow<TypeError>();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
@@ -1268,7 +1298,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<EvalError>("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow();
state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>();
i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */
@@ -1294,19 +1324,6 @@ 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);
}
@@ -1393,8 +1410,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<EvalError>("attribute '%1%' missing", state.symbols[name])
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow();
state.error("attribute '%1%' missing", state.symbols[name])
.atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow<EvalError>();
}
}
vAttrs = j->value;
@@ -1467,7 +1484,7 @@ public:
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
{
if (callDepth > evalSettings.maxCallDepth)
error<EvalError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();
error("stack overflow; max-call-depth exceeded").atPos(pos).template debugThrow<EvalError>();
CallDepth _level(callDepth);
auto trace = evalSettings.traceFunctionCalls
@@ -1525,13 +1542,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<TypeError>("function '%1%' called without required argument '%2%'",
error("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();
.debugThrow<TypeError>();
}
env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else {
@@ -1551,14 +1568,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<TypeError>("function '%1%' called with unexpected argument '%2%'",
error("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();
.debugThrow<TypeError>();
}
abort(); // can't happen
}
@@ -1690,12 +1707,11 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
}
else
error<TypeError>(
"attempt to call something which is not a function but %1%: %2%",
error("attempt to call something which is not a function but %1%: %2%",
showType(vCur),
ValuePrinter(*this, vCur, errorPrintOptions))
.atPos(pos)
.debugThrow();
.debugThrow<TypeError>();
}
vRes = vCur;
@@ -1704,18 +1720,6 @@ 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);
@@ -1777,12 +1781,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
if (j != args.end()) {
attrs.insert(*j);
} else if (!i.def) {
error<MissingArgumentError>(R"(cannot evaluate a function that has an argument without a value ('%1%')
error(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();
.atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow<MissingArgumentError>();
}
}
}
@@ -1813,7 +1817,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<AssertionError>("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow();
state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow<AssertionError>();
}
body->eval(state, env, v);
}
@@ -1991,14 +1995,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n;
nf += vTmp.fpoint;
} else
state.error<EvalError>("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
} else if (firstType == nFloat) {
if (vTmp.type() == nInt) {
nf += vTmp.integer;
} else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint;
} else
state.error<EvalError>("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow();
state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
} else {
if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not
@@ -2020,7 +2024,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
v.mkFloat(nf);
else if (firstType == nPath) {
if (!context.empty())
state.error<EvalError>("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow();
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
v.mkPath(state.rootPath(CanonPath(canonPath(str()))));
} else
v.mkStringMove(c_str(), context);
@@ -2035,9 +2039,8 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v)
void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
{
state.error<InfiniteRecursionError>("infinite recursion encountered")
.atPos(v.determinePos(noPos))
.debugThrow();
state.error("infinite recursion encountered")
.debugThrow<InfiniteRecursionError>();
}
// always force this to be separate, otherwise forceValue may inline it and take
@@ -2051,7 +2054,7 @@ void EvalState::tryFixupBlackHolePos(Value & v, PosIdx pos)
try {
std::rethrow_exception(e);
} catch (InfiniteRecursionError & e) {
e.atPos(positions[pos]);
e.err.errPos = positions[pos];
} catch (...) {
}
}
@@ -2099,18 +2102,15 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCt
try {
forceValue(v, pos);
if (v.type() != nInt)
error<TypeError>(
"expected an integer but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
error("expected an integer but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
return v.integer;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
return v.integer;
}
@@ -2121,11 +2121,10 @@ 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<TypeError>(
"expected a float but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
error("expected a float but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
return v.fpoint;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
@@ -2139,18 +2138,15 @@ bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx
try {
forceValue(v, pos);
if (v.type() != nBool)
error<TypeError>(
"expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
error("expected a Boolean but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
return v.boolean;
}
@@ -2165,11 +2161,10 @@ void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view erro
try {
forceValue(v, pos);
if (v.type() != nFunction && !isFunctor(v))
error<TypeError>(
"expected a function but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
error("expected a function but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
@@ -2182,11 +2177,10 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
try {
forceValue(v, pos);
if (v.type() != nString)
error<TypeError>(
"expected a string but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
).atPos(pos).debugThrow();
error("expected a string but found %1%: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.debugThrow<TypeError>();
return v.string_view();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
@@ -2215,7 +2209,7 @@ std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::s
{
auto s = forceString(v, pos, errorCtx);
if (v.context()) {
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();
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>();
}
return s;
}
@@ -2280,13 +2274,11 @@ BackedStringView EvalState::coerceToString(
return std::move(*maybeString);
auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end()) {
error<TypeError>(
"cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
)
error("cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withTrace(pos, errorCtx)
.debugThrow();
.debugThrow<TypeError>();
}
return coerceToString(pos, *i->value, context, errorCtx,
coerceMore, copyToStore, canonicalizePath);
@@ -2294,7 +2286,7 @@ BackedStringView EvalState::coerceToString(
if (v.type() == nExternal) {
try {
return v.external->coerceToString(*this, pos, context, coerceMore, copyToStore);
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
} catch (Error & e) {
e.addTrace(nullptr, errorCtx);
throw;
@@ -2330,19 +2322,18 @@ BackedStringView EvalState::coerceToString(
}
}
error<TypeError>("cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions)
)
error("cannot coerce %1% to a string: %2%",
showType(v),
ValuePrinter(*this, v, errorPrintOptions))
.withTrace(pos, errorCtx)
.debugThrow();
.debugThrow<TypeError>();
}
StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path)
{
if (nix::isDerivation(path.path.abs()))
error<EvalError>("file names are not allowed to end in '%1%'", drvExtension).debugThrow();
error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
auto i = srcToStore.find(path);
@@ -2398,7 +2389,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<EvalError>("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow();
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return rootPath(CanonPath(path));
}
@@ -2408,7 +2399,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<EvalError>("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow();
error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
}
@@ -2418,18 +2409,18 @@ std::pair<SingleDerivedPath, std::string_view> EvalState::coerceToSingleDerivedP
auto s = forceString(v, context, pos, errorCtx);
auto csize = context.size();
if (csize != 1)
error<EvalError>(
error(
"string '%s' has %d entries in its context. It should only have exactly one entry",
s, csize)
.withTrace(pos, errorCtx).debugThrow();
.withTrace(pos, errorCtx).debugThrow<EvalError>();
auto derivedPath = std::visit(overloaded {
[&](NixStringContextElem::Opaque && o) -> SingleDerivedPath {
return std::move(o);
},
[&](NixStringContextElem::DrvDeep &&) -> SingleDerivedPath {
error<EvalError>(
error(
"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();
s).withTrace(pos, errorCtx).debugThrow<EvalError>();
},
[&](NixStringContextElem::Built && b) -> SingleDerivedPath {
return std::move(b);
@@ -2452,16 +2443,16 @@ SingleDerivedPath EvalState::coerceToSingleDerivedPath(const PosIdx pos, Value &
error message. */
std::visit(overloaded {
[&](const SingleDerivedPath::Opaque & o) {
error<EvalError>(
error(
"path string '%s' has context with the different path '%s'",
s, sExpected)
.withTrace(pos, errorCtx).debugThrow();
.withTrace(pos, errorCtx).debugThrow<EvalError>();
},
[&](const SingleDerivedPath::Built & b) {
error<EvalError>(
error(
"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();
.withTrace(pos, errorCtx).debugThrow<EvalError>();
}
}, derivedPath.raw());
}
@@ -2546,7 +2537,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
case nThunk: // Must not be left by forceValue
default:
error<EvalError>("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow();
error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>();
}
}
@@ -2707,14 +2698,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;
}
@@ -2756,7 +2747,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("."), staticBaseEnv);
return parse(s->data(), s->size(), Pos::Stdin{.source = s}, rootPath(CanonPath::fromCwd()), staticBaseEnv);
}
@@ -2785,12 +2776,13 @@ SourcePath EvalState::findFile(const SearchPath & searchPath, const std::string_
if (hasPrefix(path, "nix/"))
return {corepkgsFS, CanonPath(path.substr(3))};
error<ThrownError>(
evalSettings.pureEval
debugThrow(ThrownError({
.msg = hintfmt(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
).atPos(pos).debugThrow();
path),
.errPos = positions[pos]
}), 0, 0);
}
@@ -2804,13 +2796,12 @@ std::optional<std::string> EvalState::resolveSearchPathPath(const SearchPath::Pa
if (EvalSettings::isPseudoUrl(value)) {
try {
auto accessor = fetchers::downloadTarball(
EvalSettings::resolvePseudoUrl(value)).accessor;
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
auto storePath = fetchers::downloadTarball(
store, EvalSettings::resolvePseudoUrl(value), "source", false).storePath;
res = { store->toRealPath(storePath) };
} catch (Error & e) {
} catch (FileTransferError & 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)
});
}
}
@@ -2843,7 +2834,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;
}
@@ -2874,11 +2865,11 @@ Expr * EvalState::parse(
}
std::string ExternalValueBase::coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
std::string ExternalValueBase::coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
{
state.error<TypeError>(
"cannot coerce %1% to a string: %2%", showType(), *this
).atPos(pos).debugThrow();
throw TypeError({
.msg = hintfmt("cannot coerce %1% to a string: %2%", showType(), *this)
});
}

View File

@@ -2,7 +2,6 @@
///@file
#include "attr-set.hh"
#include "eval-error.hh"
#include "types.hh"
#include "value.hh"
#include "nixexpr.hh"
@@ -11,7 +10,6 @@
#include "experimental-features.hh"
#include "input-accessor.hh"
#include "search-path.hh"
#include "repl-exit-status.hh"
#include <map>
#include <optional>
@@ -149,10 +147,49 @@ struct DebugTrace {
std::shared_ptr<Pos> pos;
const Expr & expr;
const Env & env;
HintFmt hint;
hintformat 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:
@@ -220,8 +257,9 @@ public:
/**
* Debugger
*/
ReplExitStatus (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv);
void (* 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;
@@ -236,11 +274,39 @@ public:
void runDebugRepl(const Error * error, const Env & env, const Expr & expr);
template<class T, typename... Args>
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>
[[nodiscard, gnu::noinline]]
EvalErrorBuilder<T> & error(const Args & ... args) {
// `EvalErrorBuilder::debugThrow` performs the corresponding `delete`.
return *new EvalErrorBuilder<T>(*this, args...);
ErrorBuilder & error(const Args & ... args) {
errorBuilder = ErrorBuilder::create(*this, args...);
return *errorBuilder;
}
private:
@@ -306,11 +372,6 @@ public:
*/
SourcePath rootPath(CanonPath path);
/**
* Variant which accepts relative paths too.
*/
SourcePath rootPath(PathView path);
/**
* Allow access to a path.
*/
@@ -758,6 +819,7 @@ struct DebugTraceStacker {
DebugTraceStacker(EvalState & evalState, DebugTrace t);
~DebugTraceStacker()
{
// assert(evalState.debugTraces.front() == trace);
evalState.debugTraces.pop_front();
}
EvalState & evalState;
@@ -783,6 +845,22 @@ 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,52 +1,20 @@
# 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:
lockFileStr: rootSrc: rootSubdir:
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 overrides ? ${key}
then
overrides.${key}.sourceInfo
else
# FIXME: remove obsolete node.info.
fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
if key == lockFile.root
then rootSrc
else fetchTree (node.info or {} // removeAttrs node.locked ["dir"]);
subdir = overrides.${key}.dir or node.locked.dir or "";
subdir = if key == lockFile.root then rootSubdir else node.locked.dir or "";
outPath = sourceInfo + ((if subdir == "" then "" else "/") + subdir);
@@ -56,6 +24,25 @@ 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
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();
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.symbols[attr.name], showType(*attr.value));
}
#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)
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();
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
state.symbols[setting.name], showType(*setting.value));
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
}
flake.config.settings.emplace(state.symbols[setting.name], ss);
}
else
state.error<TypeError>("flake configuration setting '%s' is %s",
state.symbols[setting.name], showType(*setting.value)).debugThrow();
throw TypeError("flake configuration setting '%s' is %s",
state.symbols[setting.name], showType(*setting.value));
}
}
@@ -365,7 +365,6 @@ 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 });
@@ -536,13 +535,11 @@ LockedFlake lockFlake(
}
}
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);
}
computeLocks(
mustRefetch
? getFlake(state, oldLock->lockedRef, false, flakeCache, inputPath).inputs
: fakeInputs,
childNode, inputPath, oldLock, lockRootPath, parentPath, !mustRefetch);
} else {
/* We need to create a new lock file entry. So fetch
@@ -587,7 +584,6 @@ 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
@@ -600,13 +596,11 @@ LockedFlake lockFlake(
}
else {
auto [storePath, resolvedRef, lockedRef] = fetchOrSubstituteTree(
auto [sourceInfo, 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);
}
}
@@ -621,8 +615,6 @@ 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,
@@ -715,6 +707,14 @@ 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,11 +724,7 @@ LockedFlake lockFlake(
}
}
return LockedFlake {
.flake = std::move(flake),
.lockFile = std::move(newLockFile),
.nodePaths = std::move(nodePaths)
};
return LockedFlake { .flake = std::move(flake), .lockFile = std::move(newLockFile) };
} catch (Error & e) {
e.addTrace({}, "while updating the lock file of flake '%s'", flake.lockedRef.to_string());
@@ -740,48 +736,30 @@ void callFlake(EvalState & state,
const LockedFlake & lockedFlake,
Value & vRes)
{
experimentalFeatureSettings.require(Xp::Flakes);
auto vLocks = state.allocValue();
auto vRootSrc = state.allocValue();
auto vRootSubdir = state.allocValue();
auto vTmp1 = state.allocValue();
auto vTmp2 = state.allocValue();
auto [lockFileStr, keyMap] = lockedFlake.lockFile.to_string();
vLocks->mkString(lockedFlake.lockFile.to_string());
auto overrides = state.buildBindings(lockedFlake.nodePaths.size());
emitTreeAttrs(
state,
lockedFlake.flake.storePath,
lockedFlake.flake.lockedRef.input,
*vRootSrc,
false,
lockedFlake.flake.forceDirty);
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);
vRootSubdir->mkString(lockedFlake.flake.lockedRef.subdir);
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, vOverrides, vRes, noPos);
state.callFunction(*vTmp1, *vRootSrc, *vTmp2, noPos);
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
}
static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
@@ -887,11 +865,11 @@ static void prim_flakeRefToString(
attrs.emplace(state.symbols[attr.name],
std::string(attr.value->string_view()));
} else {
state.error<EvalError>(
state.error(
"flake reference attribute sets may only contain integers, Booleans, "
"and strings, but attribute '%s' is %s",
state.symbols[attr.name],
showType(*attr.value)).debugThrow();
showType(*attr.value)).debugThrow<EvalError>();
}
}
auto flakeRef = FlakeRef::fromAttrs(attrs);

View File

@@ -103,13 +103,6 @@ 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 unlocked input '%s'",
throw Error("lock file contains mutable lock '%s'",
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
}
@@ -134,10 +134,10 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path)
// a bit since we don't need to worry about cycles.
}
std::pair<nlohmann::json, LockFile::KeyMap> LockFile::toJSON() const
nlohmann::json LockFile::toJSON() const
{
nlohmann::json nodes;
KeyMap nodeKeys;
std::unordered_map<std::shared_ptr<const Node>, std::string> nodeKeys;
std::unordered_set<std::string> keys;
std::function<std::string(const std::string & key, ref<const Node> node)> dumpNode;
@@ -194,13 +194,12 @@ std::pair<nlohmann::json, LockFile::KeyMap> LockFile::toJSON() const
json["root"] = dumpNode("root", root);
json["nodes"] = std::move(nodes);
return {json, std::move(nodeKeys)};
return json;
}
std::pair<std::string, LockFile::KeyMap> LockFile::to_string() const
std::string LockFile::to_string() const
{
auto [json, nodeKeys] = toJSON();
return {json.dump(2), std::move(nodeKeys)};
return toJSON().dump(2);
}
LockFile LockFile::read(const Path & path)
@@ -211,7 +210,7 @@ LockFile LockFile::read(const Path & path)
std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
{
stream << lockFile.toJSON().first.dump(2);
stream << lockFile.toJSON().dump(2);
return stream;
}
@@ -244,7 +243,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const
bool LockFile::operator ==(const LockFile & other) const
{
// FIXME: slow
return toJSON().first == other.toJSON().first;
return toJSON() == other.toJSON();
}
bool LockFile::operator !=(const LockFile & other) const

View File

@@ -59,15 +59,14 @@ struct LockFile
typedef std::map<ref<const Node>, std::string> KeyMap;
std::pair<nlohmann::json, KeyMap> toJSON() const;
nlohmann::json toJSON() const;
std::pair<std::string, KeyMap> to_string() const;
std::string to_string() const;
static LockFile read(const Path & path);
/**
* Check whether this lock file has any unlocked inputs. If so,
* return one.
* Check whether this lock file has any unlocked inputs.
*/
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()) state->error<TypeError>("derivation name missing").debugThrow();
if (i == attrs->end()) throw TypeError("derivation name missing");
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
}
return name;
@@ -396,8 +396,7 @@ static void getDerivations(EvalState & state, Value & vIn,
}
}
else
state.error<TypeError>("expression does not evaluate to a derivation (or a set or list of those)").debugThrow();
else throw TypeError("expression does not evaluate to a derivation (or a set or list of those)");
}

View File

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

View File

@@ -1,16 +1,13 @@
#pragma once
///@file
#include "error.hh"
#include "eval.hh"
#include <string>
namespace nix {
class EvalState;
struct Value;
MakeError(JSONParseError, Error);
MakeError(JSONParseError, EvalError);
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(ErrorInfo{
.msg = HintFmt("invalid integer '%1%'", yytext),
.pos = state->positions[CUR_POS],
throw ParseError({
.msg = hintfmt("invalid integer '%1%'", yytext),
.errPos = 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(ErrorInfo{
.msg = HintFmt("invalid float '%1%'", yytext),
.pos = state->positions[CUR_POS],
throw ParseError({
.msg = hintfmt("invalid float '%1%'", yytext),
.errPos = state->positions[CUR_POS],
});
return FLOAT_LIT;
}
@@ -285,9 +285,9 @@ or { return OR_KW; }
<INPATH_SLASH>{ANY} |
<INPATH_SLASH><<EOF>> {
throw ParseError(ErrorInfo{
.msg = HintFmt("path has a trailing slash"),
.pos = state->positions[CUR_POS],
throw ParseError({
.msg = hintfmt("path has a trailing slash"),
.errPos = 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)
es.error<UndefinedVarError>(
"undefined variable '%1%'",
es.symbols[name]
).atPos(pos).debugThrow();
throw UndefinedVarError({
.msg = hintfmt("undefined variable '%1%'", es.symbols[name]),
.errPos = es.positions[pos]
});
for (auto * e = env.get(); e && !fromWith; e = e->up)
fromWith = e->isWith;
this->level = withLevel;
@@ -409,6 +409,9 @@ 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;
@@ -420,9 +423,6 @@ 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,6 +447,9 @@ 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,13 +9,110 @@
#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]),
.pos = positions[pos]
.errPos = 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]),
.pos = positions[pos]
.msg = hintfmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]),
.errPos = 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]),
.pos = positions[duplicate->second]
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[duplicate->first]),
.errPos = positions[duplicate->second]
});
if (arg && formals->has(arg))
throw ParseError({
.msg = HintFmt("duplicate formal function argument '%1%'", symbols[arg]),
.pos = positions[pos]
.msg = hintfmt("duplicate formal function argument '%1%'", symbols[arg]),
.errPos = 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),
.pos = state->positions[state->at(*loc)]
.msg = hintfmt(error),
.errPos = 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"),
.pos = state->positions[CUR_POS]
.msg = hintfmt("dynamic attributes not allowed in let"),
.errPos = 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"),
.pos = state->positions[CUR_POS]
.msg = hintfmt("URL literals are disabled"),
.errPos = 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"),
.pos = state->positions[state->at(@2)]
.msg = hintfmt("dynamic attributes not allowed in inherit"),
.errPos = state->positions[state->at(@2)]
});
}
| { $$ = new AttrPath; }

View File

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

View File

@@ -1,48 +0,0 @@
#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 = {};
}

View File

@@ -1,83 +0,0 @@
#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,6 +39,10 @@ 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;
@@ -47,7 +51,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
for (auto & c : context) {
auto ensureValid = [&](const StorePath & p) {
if (!store->isValidPath(p))
error<InvalidPathError>(store->printStorePath(p)).debugThrow();
debugThrowLastTrace(InvalidPathError(store->printStorePath(p)));
};
std::visit(overloaded {
[&](const NixStringContextElem::Built & b) {
@@ -74,10 +78,9 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
if (drvs.empty()) return {};
if (!evalSettings.enableImportFromDerivation)
error<EvalError>(
debugThrowLastTrace(Error(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
drvs.begin()->to_string(*store)
).debugThrow();
drvs.begin()->to_string(*store)));
/* Build/substitute the context. */
std::vector<DerivedPath> buildReqs;
@@ -109,7 +112,7 @@ StringMap EvalState::realiseContext(const NixStringContext & context)
for (auto & outputPath : outputsToCopyAndAllow) {
/* Add the output of this derivations to the allowed
paths. */
allowPath(outputPath);
allowPath(store->toRealPath(outputPath));
}
return res;
@@ -337,16 +340,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.error<EvalError>("could not open '%1%': %2%", path, dlerror()).debugThrow();
state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
dlerror();
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
if(!func) {
char *message = dlerror();
if (message)
state.error<EvalError>("could not load symbol '%1%' from '%2%': %3%", sym, path, message).debugThrow();
state.debugThrowLastTrace(EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message));
else
state.error<EvalError>("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path).debugThrow();
state.debugThrowLastTrace(EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path));
}
(func)(state, v);
@@ -362,7 +365,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<EvalError>("at least one argument to 'exec' required").atPos(pos).debugThrow();
state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
NixStringContext context;
auto program = state.coerceToString(pos, *elems[0], context,
"while evaluating the first element of the argument passed to builtins.exec",
@@ -377,7 +380,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<EvalError>("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow();
state.error("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow<EvalError>();
}
auto output = runProgram(program, true, commandArgs);
@@ -579,7 +582,7 @@ struct CompareValues
if (v1->type() == nInt && v2->type() == nFloat)
return v1->integer < v2->fpoint;
if (v1->type() != v2->type())
state.error<EvalError>("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow();
state.error("cannot compare %s with %s", showType(*v1), showType(*v2)).debugThrow<EvalError>();
// Allow selecting a subset of enum values
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wswitch-enum"
@@ -607,7 +610,7 @@ struct CompareValues
}
}
default:
state.error<EvalError>("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow();
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
#pragma GCC diagnostic pop
}
} catch (Error & e) {
@@ -634,7 +637,7 @@ static Bindings::iterator getAttr(
{
Bindings::iterator value = attrSet->find(attrSym);
if (value == attrSet->end()) {
state.error<TypeError>("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow();
state.error("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow<TypeError>();
}
return value;
}
@@ -754,12 +757,21 @@ static RegisterPrimOp primop_break({
if (state.debugRepl && !state.debugTraces.empty()) {
auto error = Error(ErrorInfo {
.level = lvlInfo,
.msg = HintFmt("breakpoint reached"),
.pos = state.positions[pos],
.msg = hintfmt("breakpoint reached"),
.errPos = 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.
@@ -778,7 +790,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.error<Abort>("evaluation aborted with the following error message: '%1%'", s).debugThrow();
state.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s));
}
});
@@ -797,7 +809,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.error<ThrownError>(s).debugThrow();
state.debugThrowLastTrace(ThrownError(s));
}
});
@@ -811,7 +823,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;
}
}
@@ -870,7 +882,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);
ReplExitStatus (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
void (* 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. */
@@ -986,10 +998,6 @@ 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];
}
@@ -1001,12 +1009,6 @@ 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,
});
@@ -1072,7 +1074,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);
@@ -1086,10 +1088,9 @@ 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, pos,
state.forceBool(*attr->value, noPos,
"while evaluating the `__structuredAttrs` "
"attribute passed to builtins.derivationStrict"))
jsonObject = json::object();
@@ -1098,7 +1099,7 @@ drvName, Bindings * attrs, Value & v)
bool ignoreNulls = false;
attr = attrs->find(state.sIgnoreNulls);
if (attr != attrs->end())
ignoreNulls = state.forceBool(*attr->value, pos, "while evaluating the `__ignoreNulls` attribute " "passed to builtins.derivationStrict");
ignoreNulls = state.forceBool(*attr->value, noPos, "while evaluating the `__ignoreNulls` attribute " "passed to builtins.derivationStrict");
/* Build the derivation expression by processing the attributes. */
Derivation drv;
@@ -1127,33 +1128,37 @@ drvName, Bindings * attrs, Value & v)
experimentalFeatureSettings.require(Xp::DynamicDerivations);
ingestionMethod = TextIngestionMethod {};
} else
state.error<EvalError>(
"invalid value '%s' for 'outputHashMode' attribute", s
).atPos(v).debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
.errPos = state.positions[noPos]
}));
};
auto handleOutputs = [&](const Strings & ss) {
outputs.clear();
for (auto & j : ss) {
if (outputs.find(j) != outputs.end())
state.error<EvalError>("duplicate derivation output '%1%'", j)
.atPos(v)
.debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("duplicate derivation output '%1%'", j),
.errPos = state.positions[noPos]
}));
/* !!! 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.error<EvalError>("invalid derivation output name 'drv'")
.atPos(v)
.debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid derivation output name 'drv'" ),
.errPos = state.positions[noPos]
}));
outputs.insert(j);
}
if (outputs.empty())
state.error<EvalError>("derivation cannot have an empty set of outputs")
.atPos(v)
.debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation cannot have an empty set of outputs"),
.errPos = state.positions[noPos]
}));
};
try {
@@ -1162,16 +1167,16 @@ drvName, Bindings * attrs, Value & v)
const std::string_view context_below("");
if (ignoreNulls) {
state.forceValue(*i->value, pos);
state.forceValue(*i->value, noPos);
if (i->value->type() == nNull) continue;
}
if (i->name == state.sContentAddressed && state.forceBool(*i->value, pos, context_below)) {
if (i->name == state.sContentAddressed && state.forceBool(*i->value, noPos, context_below)) {
contentAddressed = true;
experimentalFeatureSettings.require(Xp::CaDerivations);
}
else if (i->name == state.sImpure && state.forceBool(*i->value, pos, context_below)) {
else if (i->name == state.sImpure && state.forceBool(*i->value, noPos, context_below)) {
isImpure = true;
experimentalFeatureSettings.require(Xp::ImpureDerivations);
}
@@ -1179,9 +1184,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, pos, context_below);
state.forceList(*i->value, noPos, context_below);
for (auto elem : i->value->listItems()) {
auto s = state.coerceToString(pos, *elem, context,
auto s = state.coerceToString(noPos, *elem, context,
"while evaluating an element of the argument list",
true).toOwned();
drv.args.push_back(s);
@@ -1196,29 +1201,29 @@ drvName, Bindings * attrs, Value & v)
if (i->name == state.sStructuredAttrs) continue;
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context);
(*jsonObject)[key] = printValueAsJSON(state, true, *i->value, noPos, context);
if (i->name == state.sBuilder)
drv.builder = state.forceString(*i->value, context, pos, context_below);
drv.builder = state.forceString(*i->value, context, noPos, context_below);
else if (i->name == state.sSystem)
drv.platform = state.forceStringNoCtx(*i->value, pos, context_below);
drv.platform = state.forceStringNoCtx(*i->value, noPos, context_below);
else if (i->name == state.sOutputHash)
outputHash = state.forceStringNoCtx(*i->value, pos, context_below);
outputHash = state.forceStringNoCtx(*i->value, noPos, context_below);
else if (i->name == state.sOutputHashAlgo)
outputHashAlgo = state.forceStringNoCtx(*i->value, pos, context_below);
outputHashAlgo = state.forceStringNoCtx(*i->value, noPos, context_below);
else if (i->name == state.sOutputHashMode)
handleHashMode(state.forceStringNoCtx(*i->value, pos, context_below));
handleHashMode(state.forceStringNoCtx(*i->value, noPos, context_below));
else if (i->name == state.sOutputs) {
/* Require outputs to be a list of strings. */
state.forceList(*i->value, pos, context_below);
state.forceList(*i->value, noPos, context_below);
Strings ss;
for (auto elem : i->value->listItems())
ss.emplace_back(state.forceStringNoCtx(*elem, pos, context_below));
ss.emplace_back(state.forceStringNoCtx(*elem, noPos, context_below));
handleOutputs(ss);
}
} else {
auto s = state.coerceToString(pos, *i->value, context, context_below, true).toOwned();
auto s = state.coerceToString(noPos, *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);
@@ -1233,7 +1238,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;
}
@@ -1276,14 +1281,16 @@ drvName, Bindings * attrs, Value & v)
/* Do we have all required attributes? */
if (drv.builder == "")
state.error<EvalError>("required attribute 'builder' missing")
.atPos(v)
.debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'builder' missing"),
.errPos = state.positions[noPos]
}));
if (drv.platform == "")
state.error<EvalError>("required attribute 'system' missing")
.atPos(v)
.debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'system' missing"),
.errPos = state.positions[noPos]
}));
/* Check whether the derivation name is valid. */
if (isDerivation(drvName) &&
@@ -1291,10 +1298,10 @@ drvName, Bindings * attrs, Value & v)
outputs.size() == 1 &&
*(outputs.begin()) == "out"))
{
state.error<EvalError>(
"derivation names are allowed to end in '%s' only if they produce a single derivation file",
drvExtension
).atPos(v).debugThrow();
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]
}));
}
if (outputHash) {
@@ -1303,9 +1310,10 @@ drvName, Bindings * attrs, Value & v)
Ignore `__contentAddressed` because fixed output derivations are
already content addressed. */
if (outputs.size() != 1 || *(outputs.begin()) != "out")
state.error<EvalError>(
"multiple outputs are not supported in fixed-output derivations"
).atPos(v).debugThrow();
state.debugThrowLastTrace(Error({
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
.errPos = state.positions[noPos]
}));
auto h = newHashAllowEmpty(*outputHash, parseHashAlgoOpt(outputHashAlgo));
@@ -1324,8 +1332,10 @@ drvName, Bindings * attrs, Value & v)
else if (contentAddressed || isImpure) {
if (contentAddressed && isImpure)
state.error<EvalError>("derivation cannot be both content-addressed and impure")
.atPos(v).debugThrow();
throw EvalError({
.msg = hintfmt("derivation cannot be both content-addressed and impure"),
.errPos = state.positions[noPos]
});
auto ha = parseHashAlgoOpt(outputHashAlgo).value_or(HashAlgorithm::SHA256);
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
@@ -1366,10 +1376,10 @@ drvName, Bindings * attrs, Value & v)
for (auto & i : outputs) {
auto h = get(hashModulo.hashes, i);
if (!h)
state.error<AssertionError>(
"derivation produced no hash for output '%s'",
i
).atPos(v).debugThrow();
throw AssertionError({
.msg = hintfmt("derivation produced no hash for output '%s'", i),
.errPos = state.positions[noPos],
});
auto outPath = state.store->makeOutputPath(i, *h, drvName);
drv.env[i] = state.store->printStorePath(outPath);
drv.outputs.insert_or_assign(
@@ -1475,10 +1485,10 @@ static RegisterPrimOp primop_toPath({
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
if (evalSettings.pureEval)
state.error<EvalError>(
"'%s' is not allowed in pure evaluation mode",
"builtins.storePath"
).atPos(pos).debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"),
.errPos = state.positions[pos]
}));
NixStringContext context;
auto path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to 'builtins.storePath'").path;
@@ -1488,8 +1498,10 @@ 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.error<EvalError>("path '%1%' is not in the Nix store", path)
.atPos(pos).debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = state.positions[pos]
}));
auto path2 = state.store->toStorePath(path.abs()).first;
if (!settings.readOnlyMode)
state.store->ensurePath(path2);
@@ -1607,10 +1619,7 @@ 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.error<EvalError>(
"the contents of the file '%1%' cannot be represented as a Nix string",
path
).atPos(pos).debugThrow();
state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
StorePathSet refs;
if (state.store->isInStore(path.path.abs())) {
try {
@@ -1667,11 +1676,10 @@ 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.error<EvalError>(
"cannot find '%1%', since path '%2%' is not valid",
path,
e.path
).atPos(pos).debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
.errPos = state.positions[pos]
}));
}
searchPath.elements.emplace_back(SearchPath::Elem {
@@ -1740,7 +1748,10 @@ 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.error<EvalError>("unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow();
state.debugThrowLastTrace(Error({
.msg = hintfmt("unknown hash algo '%1%'", algo),
.errPos = state.positions[pos]
}));
auto path = realisePath(state, pos, *args[1]);
@@ -1808,7 +1819,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);
@@ -2060,12 +2071,13 @@ 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.error<EvalError>(
"files created by %1% may not reference derivations, but %2% references %3%",
"builtins.toFile",
name,
c.to_string()
).atPos(pos).debugThrow();
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]
}));
}
auto storePath = settings.readOnlyMode
@@ -2241,10 +2253,7 @@ static void addPath(
filter.get(),
state.repair);
if (expectedHash && expectedStorePath != dstPath)
state.error<EvalError>(
"store path mismatch in (possibly filtered) path added from '%s'",
path
).atPos(pos).debugThrow();
state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
state.allowAndSetStorePathString(dstPath, v);
} else
state.allowAndSetStorePathString(*expectedStorePath, v);
@@ -2344,15 +2353,16 @@ 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.error<EvalError>(
"unsupported argument '%1%' to 'addPath'",
state.symbols[attr.name]
).atPos(attr.pos).debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]),
.errPos = state.positions[attr.pos]
}));
}
if (!path)
state.error<EvalError>(
"missing required 'path' attribute in the first argument to builtins.path"
).atPos(pos).debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
.errPos = state.positions[pos]
}));
if (name.empty())
name = path->baseName();
@@ -2770,7 +2780,10 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
return;
}
if (!args[0]->isLambda())
state.error<TypeError>("'functionArgs' requires a function").atPos(pos).debugThrow();
state.debugThrowLastTrace(TypeError({
.msg = hintfmt("'functionArgs' requires a function"),
.errPos = state.positions[pos]
}));
if (!args[0]->lambda.fun->hasFormals()) {
v.mkAttrs(&state.emptyBindings);
@@ -2940,10 +2953,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.error<EvalError>(
"list index %1% is out of bounds",
n
).atPos(pos).debugThrow();
state.debugThrowLastTrace(Error({
.msg = hintfmt("list index %1% is out of bounds", n),
.errPos = state.positions[pos]
}));
state.forceValue(*list.listElems()[n], pos);
v = *list.listElems()[n];
}
@@ -2988,7 +3001,10 @@ 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.error<EvalError>("'tail' called on an empty list").atPos(pos).debugThrow();
state.debugThrowLastTrace(Error({
.msg = hintfmt("'tail' called on an empty list"),
.errPos = state.positions[pos]
}));
state.mkList(v, args[0]->listSize() - 1);
for (unsigned int n = 0; n < v.listSize(); ++n)
@@ -3245,7 +3261,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<EvalError>("cannot create list of size %1%", len).atPos(pos).debugThrow();
state.error("cannot create list of size %1%", len).debugThrow<EvalError>();
// More strict than striclty (!) necessary, but acceptable
// as evaluating map without accessing any values makes little sense.
@@ -3562,7 +3578,10 @@ 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.error<EvalError>("division by zero").atPos(pos).debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("division by zero"),
.errPos = state.positions[pos]
}));
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);
@@ -3571,7 +3590,10 @@ 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.error<EvalError>("overflow in integer division").atPos(pos).debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("overflow in integer division"),
.errPos = state.positions[pos]
}));
v.mkInt(i1 / i2);
}
@@ -3702,7 +3724,10 @@ 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.error<EvalError>("negative start position in 'substring'").atPos(pos).debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("negative start position in 'substring'"),
.errPos = state.positions[pos]
}));
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
@@ -3767,7 +3792,10 @@ 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.error<EvalError>("unknown hash algorithm '%1%'", algo).atPos(pos).debugThrow();
state.debugThrowLastTrace(Error({
.msg = hintfmt("unknown hash algo '%1%'", algo),
.errPos = state.positions[pos]
}));
NixStringContext context; // discarded
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
@@ -3933,13 +3961,15 @@ 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.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
.atPos(pos)
.debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
.errPos = state.positions[pos]
}));
} else
state.error<EvalError>("invalid regular expression '%s'", re)
.atPos(pos)
.debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid regular expression '%s'", re),
.errPos = state.positions[pos]
}));
}
}
@@ -4035,13 +4065,15 @@ 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.error<EvalError>("memory limit exceeded by regular expression '%s'", re)
.atPos(pos)
.debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
.errPos = state.positions[pos]
}));
} else
state.error<EvalError>("invalid regular expression '%s'", re)
.atPos(pos)
.debugThrow();
state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid regular expression '%s'", re),
.errPos = state.positions[pos]
}));
}
}
@@ -4117,9 +4149,7 @@ 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<EvalError>(
"'from' and 'to' arguments passed to builtins.replaceStrings have different lengths"
).atPos(pos).debugThrow();
state.error("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths").atPos(pos).debugThrow<EvalError>();
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) {
state.error<EvalError>(
"context of string '%s' must have exactly one element, but has %d",
*s,
contextSize
).atPos(pos).debugThrow();
throw EvalError({
.msg = hintfmt("context of string '%s' must have exactly one element, but has %d", *s, contextSize),
.errPos = state.positions[pos]
});
}
NixStringContext context2 {
(NixStringContextElem { std::visit(overloaded {
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
if (!c.path.isDerivation()) {
state.error<EvalError>(
"path '%s' is not a derivation",
state.store->printStorePath(c.path)
).atPos(pos).debugThrow();
throw EvalError({
.msg = hintfmt("path '%s' is not a derivation",
state.store->printStorePath(c.path)),
.errPos = state.positions[pos],
});
}
return NixStringContextElem::DrvDeep {
.drvPath = c.path,
};
},
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
state.error<EvalError>(
"`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
c.output
).atPos(pos).debugThrow();
throw EvalError({
.msg = hintfmt("`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'", c.output),
.errPos = state.positions[pos],
});
},
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
/* Reuse original item because we want this to be idempotent. */
@@ -144,7 +144,7 @@ static RegisterPrimOp primop_addDrvOutputDependencies({
The original string context element must not be empty or have multiple elements, and it must not have any other type of element other than a constant or derivation deep element.
The latter is supported so this function is idempotent.
This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-addDrvOutputDependencies).
This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-unsafeDiscardOutputDependency).
)",
.fun = prim_addDrvOutputDependencies
});
@@ -246,7 +246,7 @@ static RegisterPrimOp primop_getContext({
/* Append the given context to a given string.
See the commentary above unsafeGetContext for details of the
See the commentary above getContext for details of the
context representation.
*/
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
@@ -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))
state.error<EvalError>(
"context key '%s' is not a store path",
name
).atPos(i.pos).debugThrow();
throw EvalError({
.msg = hintfmt("context key '%s' is not a store path", name),
.errPos = state.positions[i.pos]
});
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)) {
state.error<EvalError>(
"tried to add all-outputs context of %s, which is not a derivation, to a string",
name
).atPos(i.pos).debugThrow();
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]
});
}
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)) {
state.error<EvalError>(
"tried to add derivation output context of %s, which is not a derivation, to a string",
name
).atPos(i.pos).debugThrow();
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]
});
}
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)),
.pos = state.positions[pos]
.errPos = 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)),
.pos = state.positions[pos]
.errPos = 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)),
.pos = state.positions[pos]
.errPos = 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)),
.pos = state.positions[pos]
.errPos = 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)),
.pos = state.positions[pos]
.errPos = 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),
.pos = state.positions[pos]
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
.errPos = state.positions[pos]
});
}
if (!fromPath)
throw Error({
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.pos = state.positions[pos]
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.errPos = 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"),
.pos = state.positions[pos]
.errPos = state.positions[pos]
});
}
if (!fromStoreUrl)
throw Error({
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.pos = state.positions[pos]
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.errPos = 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"),
.pos = state.positions[pos]
.msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
.errPos = state.positions[pos]
});
if (!parsedURL.query.empty())
throw Error({
.msg = HintFmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
.pos = state.positions[pos]
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
.errPos = state.positions[pos]
});
auto fromStore = openStore(parsedURL.to_string());

View File

@@ -38,11 +38,17 @@ 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
state.error<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow();
throw EvalError({
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
.errPos = state.positions[attr.pos]
});
}
if (url.empty())
state.error<EvalError>("'url' argument required").atPos(pos).debugThrow();
throw EvalError({
.msg = hintfmt("'url' argument required"),
.errPos = state.positions[pos]
});
} else
url = state.coerceToString(pos, *args[0], context,

View File

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

View File

@@ -83,7 +83,10 @@ 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
state.error<EvalError>("while parsing TOML: %s", e.what()).atPos(pos).debugThrow();
throw EvalError({
.msg = hintfmt("while parsing a TOML string: %s", e.what()),
.errPos = state.positions[pos]
});
}
}

View File

@@ -17,29 +17,24 @@ 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.
*
@@ -47,7 +42,6 @@ struct PrintOptions
* attribute set encountered.
*/
size_t maxAttrs = std::numeric_limits<size_t>::max();
/**
* Maximum number of list items to print.
*
@@ -55,26 +49,10 @@ 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,8 +152,7 @@ struct ImportantFirstAttrNameCmp
}
};
typedef std::set<const void *> ValuesSeen;
typedef std::vector<std::pair<std::string, Value *>> AttrVec;
typedef std::set<Value *> ValuesSeen;
class Printer
{
@@ -164,37 +163,6 @@ 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()
{
@@ -287,36 +255,14 @@ private:
output << "»";
if (options.ansiColors)
output << ANSI_NORMAL;
} catch (Error & e) {
} catch (BaseError & 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.attrs).second) {
if (seen && !seen->insert(&v).second) {
printRepeated();
return;
}
@@ -324,10 +270,9 @@ private:
if (options.force && options.derivationPaths && state.isDerivation(v)) {
printDerivation(v);
} else if (depth < options.maxDepth) {
increaseIndent();
output << "{";
output << "{ ";
AttrVec sorted;
std::vector<std::pair<std::string, Value *>> sorted;
for (auto & i : *v.attrs)
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
@@ -336,11 +281,7 @@ 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;
@@ -349,38 +290,13 @@ 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)
@@ -390,16 +306,11 @@ private:
return;
}
output << "[ ";
if (depth < options.maxDepth) {
increaseIndent();
output << "[";
auto listItems = v.listItems();
auto prettyPrint = shouldPrettyPrintList(listItems);
for (auto elem : listItems) {
printSpace(prettyPrint);
for (auto elem : v.listItems()) {
if (listItemsPrinted >= options.maxListItems) {
printElided(listItems.size() - listItemsPrinted, "item", "items");
printElided(v.listSize() - listItemsPrinted, "item", "items");
break;
}
@@ -408,15 +319,13 @@ private:
} else {
printNullptr();
}
output << " ";
listItemsPrinted++;
}
decreaseIndent();
printSpace(prettyPrint);
output << "]";
} else {
output << "[ ... ]";
}
else
output << "... ";
output << "]";
}
void printFunction(Value & v)
@@ -496,11 +405,11 @@ private:
output << ANSI_NORMAL;
}
void printError_(Error & e)
void printError_(BaseError & e)
{
if (options.ansiColors)
output << ANSI_RED;
output << "«error: " << filterANSIEscapes(e.info().msg.str(), true) << "»";
output << "«" << e.msg() << "»";
if (options.ansiColors)
output << ANSI_NORMAL;
}
@@ -513,7 +422,7 @@ private:
if (options.force) {
try {
state.forceValue(v, v.determinePos(noPos));
} catch (Error & e) {
} catch (BaseError & e) {
printError_(e);
return;
}
@@ -579,7 +488,6 @@ public:
{
attrsPrinted = 0;
listItemsPrinted = 0;
indent.clear();
if (options.trackRepeated) {
seen.emplace();
@@ -603,11 +511,4 @@ 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,7 +9,6 @@
#include <iostream>
#include "fmt.hh"
#include "print-options.hh"
namespace nix {
@@ -79,13 +78,4 @@ 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

@@ -1,20 +0,0 @@
#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(state.positions[pos],
HintFmt("while evaluating list element at index %1%", i));
e.addTrace({},
hintfmt("while evaluating list element at index %1%", i));
throw;
}
i++;
@@ -99,12 +99,13 @@ json printValueAsJSON(EvalState & state, bool strict,
case nThunk:
case nFunction:
state.error<TypeError>(
"cannot convert %1% to JSON",
showType(v)
)
.atPos(v.determinePos(pos))
.debugThrow();
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;
}
return out;
}
@@ -118,8 +119,7 @@ void printValueAsJSON(EvalState & state, bool strict,
json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
NixStringContext & context, bool copyToStore) const
{
state.error<TypeError>("cannot convert %1% to JSON", showType())
.debugThrow();
state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
}

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(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
virtual std::string coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
/**
* Compare to another value of the same type. Defaults to uncomparable,

View File

@@ -19,8 +19,8 @@ public:
: Error("")
{
raw = raw_;
auto hf = HintFmt(args...);
err.msg = HintFmt("Bad String Context element: %1%: %2%", Uncolored(hf.str()), raw);
auto hf = hintfmt(args...);
err.msg = hintfmt("Bad String Context element: %1%: %2%", normaltxt(hf.str()), raw);
}
};

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