1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-07-03 14:31:46 +02:00

Merge remote-tracking branch 'origin/master' into lazy-trees

This commit is contained in:
Eelco Dolstra 2023-01-26 14:29:15 +01:00
commit 3621d0769d
97 changed files with 3007 additions and 727 deletions

28
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,28 @@
# Motivation
<!-- Briefly explain what the change is about and why it is desirable. -->
# Context
<!-- Provide context. Reference open issues if available. -->
<!-- Non-trivial change: Briefly outline the implementation strategy. -->
<!-- Invasive change: Discuss alternative designs or approaches you considered. -->
<!-- Large change: Provide instructions to reviewers how to read the diff. -->
# Checklist for maintainers
<!-- Contributors: please leave this as is -->
Maintainers: tick if completed or explain if not relevant
- [ ] agreed on idea
- [ ] agreed on implementation strategy
- [ ] tests, as appropriate
- functional tests - `tests/**.sh`
- unit tests - `src/*/tests`
- integration tests
- [ ] documentation in the manual
- [ ] code and comments are self-explanatory
- [ ] commit message explains why the change was made
- [ ] new feature or bug fix: updated release notes

View file

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

View file

@ -1 +1 @@
2.13.0 2.14.0

View file

@ -0,0 +1,77 @@
diff --git a/darwin_stop_world.c b/darwin_stop_world.c
index 3dbaa3fb..36a1d1f7 100644
--- a/darwin_stop_world.c
+++ b/darwin_stop_world.c
@@ -352,6 +352,7 @@ GC_INNER void GC_push_all_stacks(void)
int nthreads = 0;
word total_size = 0;
mach_msg_type_number_t listcount = (mach_msg_type_number_t)THREAD_TABLE_SZ;
+ size_t stack_limit;
if (!EXPECT(GC_thr_initialized, TRUE))
GC_thr_init();
@@ -407,6 +408,19 @@ GC_INNER void GC_push_all_stacks(void)
GC_push_all_stack_sections(lo, hi, p->traced_stack_sect);
}
if (altstack_lo) {
+ // When a thread goes into a coroutine, we lose its original sp until
+ // control flow returns to the thread.
+ // While in the coroutine, the sp points outside the thread stack,
+ // so we can detect this and push the entire thread stack instead,
+ // as an approximation.
+ // We assume that the coroutine has similarly added its entire stack.
+ // This could be made accurate by cooperating with the application
+ // via new functions and/or callbacks.
+ stack_limit = pthread_get_stacksize_np(p->id);
+ if (altstack_lo >= altstack_hi || altstack_lo < altstack_hi - stack_limit) { // sp outside stack
+ altstack_lo = altstack_hi - stack_limit;
+ }
+
total_size += altstack_hi - altstack_lo;
GC_push_all_stack(altstack_lo, altstack_hi);
}
diff --git a/pthread_stop_world.c b/pthread_stop_world.c
index b5d71e62..aed7b0bf 100644
--- a/pthread_stop_world.c
+++ b/pthread_stop_world.c
@@ -768,6 +768,8 @@ STATIC void GC_restart_handler(int sig)
/* world is stopped. Should not fail if it isn't. */
GC_INNER void GC_push_all_stacks(void)
{
+ size_t stack_limit;
+ pthread_attr_t pattr;
GC_bool found_me = FALSE;
size_t nthreads = 0;
int i;
@@ -851,6 +853,31 @@ GC_INNER void GC_push_all_stacks(void)
hi = p->altstack + p->altstack_size;
/* FIXME: Need to scan the normal stack too, but how ? */
/* FIXME: Assume stack grows down */
+ } else {
+ if (pthread_getattr_np(p->id, &pattr)) {
+ ABORT("GC_push_all_stacks: pthread_getattr_np failed!");
+ }
+ if (pthread_attr_getstacksize(&pattr, &stack_limit)) {
+ ABORT("GC_push_all_stacks: pthread_attr_getstacksize failed!");
+ }
+ if (pthread_attr_destroy(&pattr)) {
+ ABORT("GC_push_all_stacks: pthread_attr_destroy failed!");
+ }
+ // When a thread goes into a coroutine, we lose its original sp until
+ // control flow returns to the thread.
+ // While in the coroutine, the sp points outside the thread stack,
+ // so we can detect this and push the entire thread stack instead,
+ // as an approximation.
+ // We assume that the coroutine has similarly added its entire stack.
+ // This could be made accurate by cooperating with the application
+ // via new functions and/or callbacks.
+ #ifndef STACK_GROWS_UP
+ if (lo >= hi || lo < hi - stack_limit) { // sp outside stack
+ lo = hi - stack_limit;
+ }
+ #else
+ #error "STACK_GROWS_UP not supported in boost_coroutine2 (as of june 2021), so we don't support it in Nix."
+ #endif
}
GC_push_all_stack_sections(lo, hi, traced_stack_sect);
# ifdef STACK_GROWS_UP

View file

@ -277,6 +277,12 @@ fi
PKG_CHECK_MODULES([GTEST], [gtest_main]) PKG_CHECK_MODULES([GTEST], [gtest_main])
# Look for rapidcheck.
# No pkg-config yet, https://github.com/emil-e/rapidcheck/issues/302
AC_CHECK_HEADERS([rapidcheck/gtest.h], [], [], [#include <gtest/gtest.h>])
AC_CHECK_LIB([rapidcheck], [])
# Look for nlohmann/json. # Look for nlohmann/json.
PKG_CHECK_MODULES([NLOHMANN_JSON], [nlohmann_json >= 3.9]) PKG_CHECK_MODULES([NLOHMANN_JSON], [nlohmann_json >= 3.9])

View file

@ -67,6 +67,7 @@
- [CLI guideline](contributing/cli-guideline.md) - [CLI guideline](contributing/cli-guideline.md)
- [Release Notes](release-notes/release-notes.md) - [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.md) - [Release X.Y (202?-??-??)](release-notes/rl-next.md)
- [Release 2.13 (2023-01-17)](release-notes/rl-2.13.md)
- [Release 2.12 (2022-12-06)](release-notes/rl-2.12.md) - [Release 2.12 (2022-12-06)](release-notes/rl-2.12.md)
- [Release 2.11 (2022-08-25)](release-notes/rl-2.11.md) - [Release 2.11 (2022-08-25)](release-notes/rl-2.11.md)
- [Release 2.10 (2022-07-11)](release-notes/rl-2.10.md) - [Release 2.10 (2022-07-11)](release-notes/rl-2.10.md)

View file

@ -66,11 +66,11 @@ The operation `--realise` essentially “builds” the specified store
paths. Realisation is a somewhat overloaded term: paths. Realisation is a somewhat overloaded term:
- If the store path is a *derivation*, realisation ensures that the - If the store path is a *derivation*, realisation ensures that the
output paths of the derivation are [valid](../glossary.md) (i.e., output paths of the derivation are [valid] (i.e.,
the output path and its closure exist in the file system). This the output path and its closure exist in the file system). This
can be done in several ways. First, it is possible that the can be done in several ways. First, it is possible that the
outputs are already valid, in which case we are done outputs are already valid, in which case we are done
immediately. Otherwise, there may be [substitutes](../glossary.md) immediately. Otherwise, there may be [substitutes]
that produce the outputs (e.g., by downloading them). Finally, the that produce the outputs (e.g., by downloading them). Finally, the
outputs can be produced by running the build task described outputs can be produced by running the build task described
by the derivation. by the derivation.
@ -82,6 +82,9 @@ paths. Realisation is a somewhat overloaded term:
produced through substitutes. If there are no (successful) produced through substitutes. If there are no (successful)
substitutes, realisation fails. substitutes, realisation fails.
[valid]: ../glossary.md#gloss-validity
[substitutes]: ../glossary.md#gloss-substitute
The output path of each derivation is printed on standard output. (For The output path of each derivation is printed on standard output. (For
non-derivations argument, the argument itself is printed.) non-derivations argument, the argument itself is printed.)
@ -295,8 +298,8 @@ error: cannot delete path `/nix/store/zq0h41l75vlb4z45kzgjjmsjxvcv1qk7-mesa-6.4'
## Description ## Description
The operation `--query` displays various bits of information about the The operation `--query` displays information about [store path]s.
store paths . The queries are described below. At most one query can be The queries are described below. At most one query can be
specified. The default query is `--outputs`. specified. The default query is `--outputs`.
The paths *paths* may also be symlinks from outside of the Nix store, to The paths *paths* may also be symlinks from outside of the Nix store, to
@ -316,12 +319,12 @@ symlink.
## Queries ## Queries
- `--outputs`\ - `--outputs`\
Prints out the [output paths](../glossary.md) of the store Prints out the [output path]s of the store
derivations *paths*. These are the paths that will be produced when derivations *paths*. These are the paths that will be produced when
the derivation is built. the derivation is built.
- `--requisites`; `-R`\ - `--requisites`; `-R`\
Prints out the [closure](../glossary.md) of the store path *paths*. Prints out the [closure] of the given *paths*.
This query has one option: This query has one option:
@ -338,10 +341,12 @@ symlink.
derivation and specifying the option `--include-outputs`. derivation and specifying the option `--include-outputs`.
- `--references`\ - `--references`\
Prints the set of [references](../glossary.md) of the store paths Prints the set of [references]s of the store paths
*paths*, that is, their immediate dependencies. (For *all* *paths*, that is, their immediate dependencies. (For *all*
dependencies, use `--requisites`.) dependencies, use `--requisites`.)
[reference]: ../glossary.md#gloss-reference
- `--referrers`\ - `--referrers`\
Prints the set of *referrers* of the store paths *paths*, that is, Prints the set of *referrers* of the store paths *paths*, that is,
the store paths currently existing in the Nix store that refer to the store paths currently existing in the Nix store that refer to
@ -356,11 +361,13 @@ symlink.
in the Nix store that are dependent on *paths*. in the Nix store that are dependent on *paths*.
- `--deriver`; `-d`\ - `--deriver`; `-d`\
Prints the [deriver](../glossary.md) of the store paths *paths*. If Prints the [deriver] of the store paths *paths*. If
the path has no deriver (e.g., if it is a source file), or if the the path has no deriver (e.g., if it is a source file), or if the
deriver is not known (e.g., in the case of a binary-only deriver is not known (e.g., in the case of a binary-only
deployment), the string `unknown-deriver` is printed. deployment), the string `unknown-deriver` is printed.
[deriver]: ../glossary.md#gloss-deriver
- `--graph`\ - `--graph`\
Prints the references graph of the store paths *paths* in the format Prints the references graph of the store paths *paths* in the format
of the `dot` tool of AT\&T's [Graphviz of the `dot` tool of AT\&T's [Graphviz

View file

@ -92,7 +92,8 @@ $ nix develop
The unit-tests for each Nix library (`libexpr`, `libstore`, etc..) are defined The unit-tests for each Nix library (`libexpr`, `libstore`, etc..) are defined
under `src/{library_name}/tests` using the under `src/{library_name}/tests` using the
[googletest](https://google.github.io/googletest/) framework. [googletest](https://google.github.io/googletest/) and
[rapidcheck](https://github.com/emil-e/rapidcheck) frameworks.
You can run the whole testsuite with `make check`, or the tests for a specific component with `make libfoo-tests_RUN`. Finer-grained filtering is also possible using the [--gtest_filter](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) command-line option. You can run the whole testsuite with `make check`, or the tests for a specific component with `make libfoo-tests_RUN`. Finer-grained filtering is also possible using the [--gtest_filter](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) command-line option.

View file

@ -19,6 +19,17 @@
[store derivation]: #gloss-store-derivation [store derivation]: #gloss-store-derivation
- [realise]{#gloss-realise}, realisation\
Ensure a [store path] is [valid][validity].
This means either running the `builder` executable as specified in the corresponding [derivation] or fetching a pre-built [store object] from a [substituter].
See [`nix-build`](./command-ref/nix-build.md) and [`nix-store --realise`](./command-ref/nix-store.md#operation---realise).
See [`nix build`](./command-ref/new-cli/nix3-build.md) (experimental).
[realise]: #gloss-realise
- [content-addressed derivation]{#gloss-content-addressed-derivation}\ - [content-addressed derivation]{#gloss-content-addressed-derivation}\
A derivation which has the A derivation which has the
[`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed) [`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed)
@ -101,6 +112,8 @@
copy store objects it doesn't have. For details, see the copy store objects it doesn't have. For details, see the
[`substituters` option](./command-ref/conf-file.md#conf-substituters). [`substituters` option](./command-ref/conf-file.md#conf-substituters).
[substituter]: #gloss-substituter
- [purity]{#gloss-purity}\ - [purity]{#gloss-purity}\
The assumption that equal Nix derivations when run always produce The assumption that equal Nix derivations when run always produce
the same output. This cannot be guaranteed in general (e.g., a the same output. This cannot be guaranteed in general (e.g., a
@ -143,19 +156,25 @@
to path `Q`, then `Q` is in the closure of `P`. Further, if `Q` to path `Q`, then `Q` is in the closure of `P`. Further, if `Q`
references `R` then `R` is also in the closure of `P`. references `R` then `R` is also in the closure of `P`.
[closure]: #gloss-closure
- [output path]{#gloss-output-path}\ - [output path]{#gloss-output-path}\
A [store path] produced by a [derivation]. A [store path] produced by a [derivation].
[output path]: #gloss-output-path [output path]: #gloss-output-path
- [deriver]{#gloss-deriver}\ - [deriver]{#gloss-deriver}\
The deriver of an *output path* is the store The [store derivation] that produced an [output path].
derivation that built it.
- [validity]{#gloss-validity}\ - [validity]{#gloss-validity}\
A store path is considered *valid* if it exists in the file system, A store path is valid if all [store object]s in its [closure] can be read from the [store].
is listed in the Nix database as being valid, and if all paths in
its closure are also valid. For a local store, this means:
- The store path leads to an existing [store object] in that [store].
- The store path is listed in the Nix database as being valid.
- All paths in the store path's [closure] are valid.
[validity]: #gloss-validity
- [user environment]{#gloss-user-env}\ - [user environment]{#gloss-user-env}\
An automatically generated store object that consists of a set of An automatically generated store object that consists of a set of

View file

@ -191,12 +191,12 @@ This is an incomplete overview of language features, by example.
<tr> <tr>
<td> <td>
<nixpkgs> `<nixpkgs>`
</td> </td>
<td> <td>
Search path. Value determined by [`$NIX_PATH` environment variable](../command-ref/env-common.md#env-NIX_PATH). Search path for Nix files. Value determined by [`$NIX_PATH` environment variable](../command-ref/env-common.md#env-NIX_PATH).
</td> </td>
</tr> </tr>

View file

@ -24,7 +24,7 @@
| [Equality] | *expr* `==` *expr* | none | 11 | | [Equality] | *expr* `==` *expr* | none | 11 |
| Inequality | *expr* `!=` *expr* | none | 11 | | Inequality | *expr* `!=` *expr* | none | 11 |
| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 | | Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
| Logical disjunction (`OR`) | *bool* `||` *bool* | left | 13 | | Logical disjunction (`OR`) | *bool* `\|\|` *bool* | left | 13 |
| [Logical implication] | *bool* `->` *bool* | none | 14 | | [Logical implication] | *bool* `->` *bool* | none | 14 |
[string]: ./values.md#type-string [string]: ./values.md#type-string
@ -120,12 +120,12 @@ The result is a string.
## Update ## Update
> *attrset1* + *attrset2* > *attrset1* // *attrset2*
Update [attribute set] *attrset1* with names and values from *attrset2*. Update [attribute set] *attrset1* with names and values from *attrset2*.
The returned attribute set will have of all the attributes in *e1* and *e2*. The returned attribute set will have of all the attributes in *attrset1* and *attrset2*.
If an attribute name is present in both, the attribute value from the former is taken. If an attribute name is present in both, the attribute value from the latter is taken.
[Update]: #update [Update]: #update

View file

@ -32,13 +32,13 @@ which should print something like:
Priority: 30 Priority: 30
On the client side, you can tell Nix to use your binary cache using On the client side, you can tell Nix to use your binary cache using
`--option extra-binary-caches`, e.g.: `--substituters`, e.g.:
```console ```console
$ nix-env -iA nixpkgs.firefox --option extra-binary-caches http://avalon:8080/ $ nix-env -iA nixpkgs.firefox --substituters http://avalon:8080/
``` ```
The option `extra-binary-caches` tells Nix to use this binary cache in The option `substituters` tells Nix to use this binary cache in
addition to your default caches, such as <https://cache.nixos.org>. addition to your default caches, such as <https://cache.nixos.org>.
Thus, for any path in the closure of Firefox, Nix will first check if Thus, for any path in the closure of Firefox, Nix will first check if
the path is available on the server `avalon` or another binary caches. the path is available on the server `avalon` or another binary caches.
@ -47,4 +47,4 @@ If not, it will fall back to building from source.
You can also tell Nix to always use your binary cache by adding a line You can also tell Nix to always use your binary cache by adding a line
to the `nix.conf` configuration file like this: to the `nix.conf` configuration file like this:
binary-caches = http://avalon:8080/ https://cache.nixos.org/ substituters = http://avalon:8080/ https://cache.nixos.org/

View file

@ -0,0 +1,44 @@
# Release 2.13 (2023-01-17)
* The `repeat` and `enforce-determinism` options have been removed
since they had been broken under many circumstances for a long time.
* You can now use [flake references] in the [old command line interface], e.g.
[flake references]: ../command-ref/new-cli/nix3-flake.md#flake-references
[old command line interface]: ../command-ref/main-commands.md
```shell-session
# nix-build flake:nixpkgs -A hello
# nix-build -I nixpkgs=flake:github:NixOS/nixpkgs/nixos-22.05 \
'<nixpkgs>' -A hello
# NIX_PATH=nixpkgs=flake:nixpkgs nix-build '<nixpkgs>' -A hello
```
* Instead of "antiquotation", the more common term [string interpolation](../language/string-interpolation.md) is now used consistently.
Historical release notes were not changed.
* Error traces have been reworked to provide detailed explanations and more
accurate error locations. A short excerpt of the trace is now shown by
default when an error occurs.
* Allow explicitly selecting outputs in a store derivation installable, just like we can do with other sorts of installables.
For example,
```shell-session
# nix build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev
```
now works just as
```shell-session
# nix build nixpkgs#glibc^dev
```
does already.
* On Linux, `nix develop` now sets the
[*personality*](https://man7.org/linux/man-pages/man2/personality.2.html)
for the development shell in the same way as the actual build of the
derivation. This makes shells for `i686-linux` derivations work
correctly on `x86_64-linux`.
* You can now disable the global flake registry by setting the `flake-registry`
configuration option to an empty string. The same can be achieved at runtime with
`--flake-registry ""`.

View file

@ -1,23 +1,10 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
* The `repeat` and `enforce-determinism` options have been removed * A new function `builtins.readFileType` is available. It is similar to
since they had been broken under many circumstances for a long time. `builtins.readDir` but acts on a single file or directory.
* You can now use [flake references] in the [old command line interface], e.g. * The `builtins.readDir` function has been optimized when encountering not-yet-known
file types from POSIX's `readdir`. In such cases the type of each file is/was
[flake references]: ../command-ref/new-cli/nix3-flake.md#flake-references discovered by making multiple syscalls. This change makes these operations
[old command line interface]: ../command-ref/main-commands.md lazy such that these lookups will only be performed if the attribute is used.
This optimization affects a minority of filesystems and operating systems.
```
# nix-build flake:nixpkgs -A hello
# nix-build -I nixpkgs=flake:github:NixOS/nixpkgs/nixos-22.05 \
'<nixpkgs>' -A hello
# NIX_PATH=nixpkgs=flake:nixpkgs nix-build '<nixpkgs>' -A hello
```
* Instead of "antiquotation", the more common term [string interpolation](../language/string-interpolation.md) is now used consistently.
Historical release notes were not changed.
* Error traces have been reworked to provide detailed explanations and more
accurate error locations. A short excerpt of the trace is now shown by
default when an error occurs.

View file

@ -82,7 +82,9 @@
}); });
configureFlags = configureFlags =
lib.optionals stdenv.isLinux [ [
"CXXFLAGS=-I${lib.getDev rapidcheck}/extras/gtest/include"
] ++ lib.optionals stdenv.isLinux [
"--with-boost=${boost}/lib" "--with-boost=${boost}/lib"
"--with-sandbox-shell=${sh}/bin/busybox" "--with-sandbox-shell=${sh}/bin/busybox"
] ]
@ -121,6 +123,7 @@
boost boost
lowdown-nix lowdown-nix
gtest gtest
rapidcheck
] ]
++ lib.optionals stdenv.isLinux [libseccomp] ++ lib.optionals stdenv.isLinux [libseccomp]
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
@ -133,9 +136,14 @@
}); });
propagatedDeps = propagatedDeps =
[ (boehmgc.override { [ ((boehmgc.override {
enableLargeConfig = true; enableLargeConfig = true;
}).overrideAttrs(o: {
patches = (o.patches or []) ++ [
./boehmgc-coroutine-sp-fallback.diff
];
}) })
)
nlohmann_json nlohmann_json
]; ];
}; };
@ -462,6 +470,10 @@
src = self; src = self;
configureFlags = [
"CXXFLAGS=-I${lib.getDev pkgs.rapidcheck}/extras/gtest/include"
];
enableParallelBuilding = true; enableParallelBuilding = true;
nativeBuildInputs = nativeBuildDeps; nativeBuildInputs = nativeBuildDeps;
@ -537,6 +549,12 @@
mkdir $out mkdir $out
''; '';
tests.nixpkgsLibTests =
nixpkgs.lib.genAttrs systems (system:
import (nixpkgs + "/lib/tests/release.nix")
{ pkgs = nixpkgsFor.${system}; }
);
metrics.nixpkgs = import "${nixpkgs-regression}/pkgs/top-level/metrics.nix" { metrics.nixpkgs = import "${nixpkgs-regression}/pkgs/top-level/metrics.nix" {
pkgs = nixpkgsFor.x86_64-linux; pkgs = nixpkgsFor.x86_64-linux;
nixpkgs = nixpkgs-regression; nixpkgs = nixpkgs-regression;
@ -567,6 +585,7 @@
binaryTarball = self.hydraJobs.binaryTarball.${system}; binaryTarball = self.hydraJobs.binaryTarball.${system};
perlBindings = self.hydraJobs.perlBindings.${system}; perlBindings = self.hydraJobs.perlBindings.${system};
installTests = self.hydraJobs.installTests.${system}; installTests = self.hydraJobs.installTests.${system};
nixpkgsLibTests = self.hydraJobs.tests.nixpkgsLibTests.${system};
} // (nixpkgs.lib.optionalAttrs (builtins.elem system linux64BitSystems)) { } // (nixpkgs.lib.optionalAttrs (builtins.elem system linux64BitSystems)) {
dockerImage = self.hydraJobs.dockerImage.${system}; dockerImage = self.hydraJobs.dockerImage.${system};
}); });
@ -648,6 +667,7 @@
inherit system crossSystem; inherit system crossSystem;
overlays = [ self.overlays.default ]; overlays = [ self.overlays.default ];
}; };
inherit (nixpkgsCross) lib;
in with commonDeps { pkgs = nixpkgsCross; }; nixpkgsCross.stdenv.mkDerivation { in with commonDeps { pkgs = nixpkgsCross; }; nixpkgsCross.stdenv.mkDerivation {
name = "nix-${version}"; name = "nix-${version}";
@ -660,7 +680,11 @@
nativeBuildInputs = nativeBuildDeps; nativeBuildInputs = nativeBuildDeps;
buildInputs = buildDeps ++ propagatedDeps; buildInputs = buildDeps ++ propagatedDeps;
configureFlags = [ "--sysconfdir=/etc" "--disable-doc-gen" ]; configureFlags = [
"CXXFLAGS=-I${lib.getDev nixpkgsCross.rapidcheck}/extras/gtest/include"
"--sysconfdir=/etc"
"--disable-doc-gen"
];
enableParallelBuilding = true; enableParallelBuilding = true;

View file

@ -36,17 +36,45 @@ Issues on the board progress through the following states:
- No Status - No Status
Team members can add pull requests or issues to discuss or review together.
During the discussion meeting, the team triages new items. During the discussion meeting, the team triages new items.
To be considered, issues and pull requests must have a high-level description to provide the whole team with the necessary context at a glance.
On every meeting, at least one item from each of the following categories is inspected:
1. [critical](https://github.com/NixOS/nix/labels/critical)
2. [security](https://github.com/NixOS/nix/labels/security)
3. [regression](https://github.com/NixOS/nix/labels/regression)
4. [bug](https://github.com/NixOS/nix/issues?q=is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc)
- [oldest pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Acreated-asc)
- [most popular pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Areactions-%2B1-desc)
- [oldest issues](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc)
- [most popular issues](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc)
Team members can also add pull requests or issues they would like the whole team to consider.
If there is disagreement on the general idea behind an issue or pull request, it is moved to _To discuss_, otherwise to _In review_. If there is disagreement on the general idea behind an issue or pull request, it is moved to _To discuss_, otherwise to _In review_.
- To discuss - To discuss
Pull requests and issues that are important and controversial are discussed by the team during discussion meetings. Pull requests and issues that are deemed important and controversial are discussed by the team during discussion meetings.
This may be where the merit of the change itself or the implementation strategy is contested by a team member. This may be where the merit of the change itself or the implementation strategy is contested by a team member.
As a general guideline, the order of items is determined as follows:
- Prioritise pull requests over issues
Contributors who took the time to implement concrete change proposals should not wait indefinitely.
- Prioritise fixing bugs over documentation, improvements or new features
The team values stability and accessibility higher than raw functionality.
- Interleave issues and PRs
This way issues without attempts at a solution get a chance to get addressed.
- In review - In review
Pull requests in this column are reviewed together during work meetings. Pull requests in this column are reviewed together during work meetings.

View file

@ -24,12 +24,17 @@ $1
EOF EOF
} }
escape_systemd_env() {
temp_var="${1//\'/\\\'}"
echo "${temp_var//\%/%%}"
}
# Gather all non-empty proxy environment variables into a string # Gather all non-empty proxy environment variables into a string
create_systemd_proxy_env() { create_systemd_proxy_env() {
vars="http_proxy https_proxy ftp_proxy no_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY NO_PROXY" vars="http_proxy https_proxy ftp_proxy no_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY NO_PROXY"
for v in $vars; do for v in $vars; do
if [ "x${!v:-}" != "x" ]; then if [ "x${!v:-}" != "x" ]; then
echo "Environment=${v}=${!v}" echo "Environment=${v}=$(escape_systemd_env ${!v})"
fi fi
done done
} }

View file

@ -1,5 +1,6 @@
#include "globals.hh" #include "globals.hh"
#include "installables.hh" #include "installables.hh"
#include "outputs-spec.hh"
#include "util.hh" #include "util.hh"
#include "command.hh" #include "command.hh"
#include "attr-path.hh" #include "attr-path.hh"
@ -402,18 +403,6 @@ struct InstallableStorePath : Installable
ref<Store> store; ref<Store> store;
DerivedPath req; DerivedPath req;
InstallableStorePath(ref<Store> store, StorePath && storePath)
: store(store),
req(storePath.isDerivation()
? (DerivedPath) DerivedPath::Built {
.drvPath = std::move(storePath),
.outputs = {},
}
: (DerivedPath) DerivedPath::Opaque {
.path = std::move(storePath),
})
{ }
InstallableStorePath(ref<Store> store, DerivedPath && req) InstallableStorePath(ref<Store> store, DerivedPath && req)
: store(store), req(std::move(req)) : store(store), req(std::move(req))
{ } { }
@ -446,19 +435,19 @@ struct InstallableAttrPath : InstallableValue
SourceExprCommand & cmd; SourceExprCommand & cmd;
RootValue v; RootValue v;
std::string attrPath; std::string attrPath;
OutputsSpec outputsSpec; ExtendedOutputsSpec extendedOutputsSpec;
InstallableAttrPath( InstallableAttrPath(
ref<EvalState> state, ref<EvalState> state,
SourceExprCommand & cmd, SourceExprCommand & cmd,
Value * v, Value * v,
const std::string & attrPath, const std::string & attrPath,
OutputsSpec outputsSpec) ExtendedOutputsSpec extendedOutputsSpec)
: InstallableValue(state) : InstallableValue(state)
, cmd(cmd) , cmd(cmd)
, v(allocRootValue(v)) , v(allocRootValue(v))
, attrPath(attrPath) , attrPath(attrPath)
, outputsSpec(std::move(outputsSpec)) , extendedOutputsSpec(std::move(extendedOutputsSpec))
{ } { }
std::string what() const override { return attrPath; } std::string what() const override { return attrPath; }
@ -481,30 +470,39 @@ struct InstallableAttrPath : InstallableValue
// Backward compatibility hack: group results by drvPath. This // Backward compatibility hack: group results by drvPath. This
// helps keep .all output together. // helps keep .all output together.
std::map<StorePath, DerivedPath::Built> byDrvPath; std::map<StorePath, OutputsSpec> byDrvPath;
for (auto & drvInfo : drvInfos) { for (auto & drvInfo : drvInfos) {
auto drvPath = drvInfo.queryDrvPath(); auto drvPath = drvInfo.queryDrvPath();
if (!drvPath) if (!drvPath)
throw Error("'%s' is not a derivation", what()); throw Error("'%s' is not a derivation", what());
auto newOutputs = std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
std::set<std::string> outputsToInstall; std::set<std::string> outputsToInstall;
for (auto & output : drvInfo.queryOutputs(false, true))
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
outputsToInstall = *outputNames;
else
for (auto & output : drvInfo.queryOutputs(false, std::get_if<DefaultOutputs>(&outputsSpec)))
outputsToInstall.insert(output.first); outputsToInstall.insert(output.first);
return OutputsSpec::Names { std::move(outputsToInstall) };
},
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
return e;
},
}, extendedOutputsSpec.raw());
auto derivedPath = byDrvPath.emplace(*drvPath, DerivedPath::Built { .drvPath = *drvPath }).first; auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs);
for (auto & output : outputsToInstall) if (!didInsert)
derivedPath->second.outputs.insert(output); iter->second = iter->second.union_(newOutputs);
} }
DerivedPathsWithInfo res; DerivedPathsWithInfo res;
for (auto & [_, info] : byDrvPath) for (auto & [drvPath, outputs] : byDrvPath)
res.push_back({ .path = { info } }); res.push_back({
.path = DerivedPath::Built {
.drvPath = drvPath,
.outputs = outputs,
},
});
return res; return res;
} }
@ -581,7 +579,7 @@ InstallableFlake::InstallableFlake(
ref<EvalState> state, ref<EvalState> state,
FlakeRef && flakeRef, FlakeRef && flakeRef,
std::string_view fragment, std::string_view fragment,
OutputsSpec outputsSpec, ExtendedOutputsSpec extendedOutputsSpec,
Strings attrPaths, Strings attrPaths,
Strings prefixes, Strings prefixes,
const flake::LockFlags & lockFlags) const flake::LockFlags & lockFlags)
@ -589,7 +587,7 @@ InstallableFlake::InstallableFlake(
flakeRef(flakeRef), flakeRef(flakeRef),
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}), attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
prefixes(fragment == "" ? Strings{} : prefixes), prefixes(fragment == "" ? Strings{} : prefixes),
outputsSpec(std::move(outputsSpec)), extendedOutputsSpec(std::move(extendedOutputsSpec)),
lockFlags(lockFlags) lockFlags(lockFlags)
{ {
if (cmd && cmd->getAutoArgs(*state)->size()) if (cmd && cmd->getAutoArgs(*state)->size())
@ -638,48 +636,47 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
auto drvPath = attr->forceDerivation(); auto drvPath = attr->forceDerivation();
std::set<std::string> outputsToInstall;
std::optional<NixInt> priority; std::optional<NixInt> priority;
if (attr->maybeGetAttr(state->sOutputSpecified)) {
} else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
if (auto aPriority = aMeta->maybeGetAttr("priority"))
priority = aPriority->getInt();
}
return {{
.path = DerivedPath::Built {
.drvPath = std::move(drvPath),
.outputs = std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
std::set<std::string> outputsToInstall;
if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) { if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
if (aOutputSpecified->getBool()) { if (aOutputSpecified->getBool()) {
if (auto aOutputName = attr->maybeGetAttr("outputName")) if (auto aOutputName = attr->maybeGetAttr("outputName"))
outputsToInstall = { aOutputName->getString() }; outputsToInstall = { aOutputName->getString() };
} }
} } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall")) if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
for (auto & s : aOutputsToInstall->getListOfStrings()) for (auto & s : aOutputsToInstall->getListOfStrings())
outputsToInstall.insert(s); outputsToInstall.insert(s);
if (auto aPriority = aMeta->maybeGetAttr("priority"))
priority = aPriority->getInt();
}
if (outputsToInstall.empty() || std::get_if<AllOutputs>(&outputsSpec)) {
outputsToInstall.clear();
if (auto aOutputs = attr->maybeGetAttr(state->sOutputs))
for (auto & s : aOutputs->getListOfStrings())
outputsToInstall.insert(s);
} }
if (outputsToInstall.empty()) if (outputsToInstall.empty())
outputsToInstall.insert("out"); outputsToInstall.insert("out");
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec)) return OutputsSpec::Names { std::move(outputsToInstall) };
outputsToInstall = *outputNames; },
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec {
return {{ return e;
.path = DerivedPath::Built { },
.drvPath = std::move(drvPath), }, extendedOutputsSpec.raw()),
.outputs = std::move(outputsToInstall),
}, },
.info = { .info = {
.priority = priority, .priority = priority,
.originalRef = flakeRef, .originalRef = flakeRef,
.resolvedRef = getLockedFlake()->flake.lockedRef, .resolvedRef = getLockedFlake()->flake.lockedRef,
.attrPath = attrPath, .attrPath = attrPath,
.outputsSpec = outputsSpec, .extendedOutputsSpec = extendedOutputsSpec,
} }
}}; }};
} }
@ -796,12 +793,12 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
} }
for (auto & s : ss) { for (auto & s : ss) {
auto [prefix, outputsSpec] = parseOutputsSpec(s); auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
result.push_back( result.push_back(
std::make_shared<InstallableAttrPath>( std::make_shared<InstallableAttrPath>(
state, *this, vFile, state, *this, vFile,
prefix == "." ? "" : prefix, prefix == "." ? "" : std::string { prefix },
outputsSpec)); extendedOutputsSpec));
} }
} else { } else {
@ -809,24 +806,46 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
for (auto & s : ss) { for (auto & s : ss) {
std::exception_ptr ex; std::exception_ptr ex;
auto found = s.rfind('^'); auto [prefix_, extendedOutputsSpec_] = ExtendedOutputsSpec::parse(s);
if (found != std::string::npos) { // To avoid clang's pedantry
try { auto prefix = std::move(prefix_);
result.push_back(std::make_shared<InstallableStorePath>( auto extendedOutputsSpec = std::move(extendedOutputsSpec_);
store,
DerivedPath::Built::parse(*store, s.substr(0, found), s.substr(found + 1))));
continue;
} catch (BadStorePath &) {
} catch (...) {
if (!ex)
ex = std::current_exception();
}
}
found = s.find('/'); auto found = prefix.find('/');
if (found != std::string::npos) { if (found != std::string::npos) {
try { try {
result.push_back(std::make_shared<InstallableStorePath>(store, store->followLinksToStorePath(s))); auto derivedPath = std::visit(overloaded {
// If the user did not use ^, we treat the output more liberally.
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
// First, we accept a symlink chain or an actual store path.
auto storePath = store->followLinksToStorePath(prefix);
// Second, we see if the store path ends in `.drv` to decide what sort
// of derived path they want.
//
// This handling predates the `^` syntax. The `^*` in
// `/nix/store/hash-foo.drv^*` unambiguously means "do the
// `DerivedPath::Built` case", so plain `/nix/store/hash-foo.drv` could
// also unambiguously mean "do the DerivedPath::Opaque` case".
//
// Issue #7261 tracks reconsidering this `.drv` dispatching.
return storePath.isDerivation()
? (DerivedPath) DerivedPath::Built {
.drvPath = std::move(storePath),
.outputs = OutputsSpec::All {},
}
: (DerivedPath) DerivedPath::Opaque {
.path = std::move(storePath),
};
},
// If the user did use ^, we just do exactly what is written.
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
return DerivedPath::Built {
.drvPath = store->parseStorePath(prefix),
.outputs = outputSpec,
};
},
}, extendedOutputsSpec.raw());
result.push_back(std::make_shared<InstallableStorePath>(store, std::move(derivedPath)));
continue; continue;
} catch (BadStorePath &) { } catch (BadStorePath &) {
} catch (...) { } catch (...) {
@ -836,13 +855,13 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
} }
try { try {
auto [flakeRef, fragment, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(s, absPath(".")); auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, absPath("."));
result.push_back(std::make_shared<InstallableFlake>( result.push_back(std::make_shared<InstallableFlake>(
this, this,
getEvalState(), getEvalState(),
std::move(flakeRef), std::move(flakeRef),
fragment, fragment,
outputsSpec, extendedOutputsSpec,
getDefaultFlakeAttrPaths(), getDefaultFlakeAttrPaths(),
getDefaultFlakeAttrPathPrefixes(), getDefaultFlakeAttrPathPrefixes(),
lockFlags)); lockFlags));
@ -917,32 +936,7 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> Instal
for (auto & aux : backmap[path]) { for (auto & aux : backmap[path]) {
std::visit(overloaded { std::visit(overloaded {
[&](const DerivedPath::Built & bfd) { [&](const DerivedPath::Built & bfd) {
OutputPathMap outputs; auto outputs = resolveDerivedPath(*store, bfd, &*evalStore);
auto drv = evalStore->readDerivation(bfd.drvPath);
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
auto drvOutputs = drv.outputsAndOptPaths(*store);
for (auto & output : bfd.outputs) {
auto outputHash = get(outputHashes, output);
if (!outputHash)
throw Error(
"the derivation '%s' doesn't have an output named '%s'",
store->printStorePath(bfd.drvPath), output);
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
DrvOutput outputId { *outputHash, output };
auto realisation = store->queryRealisation(outputId);
if (!realisation)
throw MissingRealisation(outputId);
outputs.insert_or_assign(output, realisation->outPath);
} else {
// If ca-derivations isn't enabled, assume that
// the output path is statically known.
auto drvOutput = get(drvOutputs, output);
assert(drvOutput);
assert(drvOutput->second);
outputs.insert_or_assign(
output, *drvOutput->second);
}
}
res.push_back({aux.installable, { res.push_back({aux.installable, {
.path = BuiltPath::Built { bfd.drvPath, outputs }, .path = BuiltPath::Built { bfd.drvPath, outputs },
.info = aux.info}}); .info = aux.info}});

View file

@ -59,7 +59,7 @@ struct ExtraPathInfo
std::optional<FlakeRef> resolvedRef; std::optional<FlakeRef> resolvedRef;
std::optional<std::string> attrPath; std::optional<std::string> attrPath;
// FIXME: merge with DerivedPath's 'outputs' field? // FIXME: merge with DerivedPath's 'outputs' field?
std::optional<OutputsSpec> outputsSpec; std::optional<ExtendedOutputsSpec> extendedOutputsSpec;
}; };
/* A derived path with any additional info that commands might /* A derived path with any additional info that commands might
@ -169,7 +169,7 @@ struct InstallableFlake : InstallableValue
FlakeRef flakeRef; FlakeRef flakeRef;
Strings attrPaths; Strings attrPaths;
Strings prefixes; Strings prefixes;
OutputsSpec outputsSpec; ExtendedOutputsSpec extendedOutputsSpec;
const flake::LockFlags & lockFlags; const flake::LockFlags & lockFlags;
mutable std::shared_ptr<flake::LockedFlake> _lockedFlake; mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
@ -178,7 +178,7 @@ struct InstallableFlake : InstallableValue
ref<EvalState> state, ref<EvalState> state,
FlakeRef && flakeRef, FlakeRef && flakeRef,
std::string_view fragment, std::string_view fragment,
OutputsSpec outputsSpec, ExtendedOutputsSpec extendedOutputsSpec,
Strings attrPaths, Strings attrPaths,
Strings prefixes, Strings prefixes,
const flake::LockFlags & lockFlags); const flake::LockFlags & lockFlags);

View file

@ -397,7 +397,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
Expr * e = parseString(expr); Expr * e = parseString(expr);
Value v; Value v;
e->eval(*state, *env, v); e->eval(*state, *env, v);
state->forceAttrs(v, noPos, "nevermind, it is ignored anyway"); state->forceAttrs(v, noPos, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
for (auto & i : *v.attrs) { for (auto & i : *v.attrs) {
std::string_view name = state->symbols[i.name]; std::string_view name = state->symbols[i.name];
@ -641,7 +641,12 @@ bool NixRepl::processLine(std::string line)
Path drvPathRaw = state->store->printStorePath(drvPath); Path drvPathRaw = state->store->printStorePath(drvPath);
if (command == ":b" || command == ":bl") { if (command == ":b" || command == ":bl") {
state->store->buildPaths({DerivedPath::Built{drvPath}}); state->store->buildPaths({
DerivedPath::Built {
.drvPath = drvPath,
.outputs = OutputsSpec::All { },
},
});
auto drv = state->store->readDerivation(drvPath); auto drv = state->store->readDerivation(drvPath);
logger->cout("\nThis derivation produced the following outputs:"); logger->cout("\nThis derivation produced the following outputs:");
for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath)) { for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath)) {

View file

@ -12,7 +12,9 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <iostream>
#include <cstring> #include <cstring>
#include <optional>
#include <unistd.h> #include <unistd.h>
#include <sys/time.h> #include <sys/time.h>
#include <sys/resource.h> #include <sys/resource.h>
@ -1891,7 +1893,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
state.error("cannot add %1% to a float", showType(*vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>(); state.error("cannot add %1% to a float", showType(*vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
} else if (firstType == nPath) { } else if (firstType == nPath) {
if (!first) { if (!first) {
auto part = state.coerceToString(i_pos, *vTmp, context, false, false); auto part = state.coerceToString(i_pos, *vTmp, context, "while evaluating a path segment", false, false);
if (sSize <= 1 && !hasPrefix(*part, "/") && accessor != state.rootFS.get_ptr()) if (sSize <= 1 && !hasPrefix(*part, "/") && accessor != state.rootFS.get_ptr())
state.error( state.error(
"cannot append non-absolute path '%1%' to '%2%' (hint: change it to '/%1%')", "cannot append non-absolute path '%1%' to '%2%' (hint: change it to '/%1%')",
@ -1904,7 +1906,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
} }
} else { } else {
if (s.empty()) s.reserve(es->size()); if (s.empty()) s.reserve(es->size());
auto part = state.coerceToString(i_pos, *vTmp, context, false, firstType == nString, "while evaluating a path segment"); auto part = state.coerceToString(i_pos, *vTmp, context, "while evaluating a path segment", false, firstType == nString);
sSize += part->size(); sSize += part->size();
s.emplace_back(std::move(part)); s.emplace_back(std::move(part));
} }
@ -2100,15 +2102,21 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
if (i != v.attrs->end()) { if (i != v.attrs->end()) {
Value v1; Value v1;
callFunction(*i->value, v, v1, pos); callFunction(*i->value, v, v1, pos);
return coerceToString(pos, v1, context, coerceMore, copyToStore, return coerceToString(pos, v1, context,
"while evaluating the result of the `toString` attribute").toOwned(); "while evaluating the result of the `__toString` attribute",
coerceMore, copyToStore).toOwned();
} }
return {}; return {};
} }
BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context, BackedStringView EvalState::coerceToString(
bool coerceMore, bool copyToStore, std::string_view errorCtx) const PosIdx pos,
Value & v,
PathSet & context,
std::string_view errorCtx,
bool coerceMore,
bool copyToStore)
{ {
forceValue(v, pos); forceValue(v, pos);
@ -2129,13 +2137,22 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
if (maybeString) if (maybeString)
return std::move(*maybeString); return std::move(*maybeString);
auto i = v.attrs->find(sOutPath); auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end()) if (i == v.attrs->end()) {
error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>(); error("cannot coerce %1% to a string", showType(v))
return coerceToString(pos, *i->value, context, coerceMore, copyToStore, errorCtx); .withTrace(pos, errorCtx)
.debugThrow<TypeError>();
}
return coerceToString(pos, *i->value, context, errorCtx, coerceMore, copyToStore);
} }
if (v.type() == nExternal) if (v.type() == nExternal) {
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx); try {
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
} catch (Error & e) {
e.addTrace(nullptr, errorCtx);
throw;
}
}
if (coerceMore) { if (coerceMore) {
/* Note that `false' is represented as an empty string for /* Note that `false' is represented as an empty string for
@ -2150,8 +2167,9 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
std::string result; std::string result;
for (auto [n, v2] : enumerate(v.listItems())) { for (auto [n, v2] : enumerate(v.listItems())) {
try { try {
result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, result += *coerceToString(noPos, *v2, context,
"while evaluating one element of the list"); "while evaluating one element of the list",
coerceMore, copyToStore);
} catch (Error & e) { } catch (Error & e) {
e.addTrace(positions[pos], errorCtx); e.addTrace(positions[pos], errorCtx);
throw; throw;
@ -2165,7 +2183,9 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
} }
} }
error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>(); error("cannot coerce %1% to a string", showType(v))
.withTrace(pos, errorCtx)
.debugThrow<TypeError>();
} }
@ -2215,7 +2235,7 @@ SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & contex
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx) StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{ {
auto path = coerceToString(pos, v, context, false, false, errorCtx).toOwned(); auto path = coerceToString(pos, v, context, errorCtx, false, false).toOwned();
if (auto storePath = store->maybeParseStorePath(path)) if (auto storePath = store->maybeParseStorePath(path))
return *storePath; return *storePath;
error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>(); error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
@ -2421,13 +2441,11 @@ void EvalState::printStats()
} }
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
{ {
auto e = TypeError({ throw TypeError({
.msg = hintfmt("cannot coerce %1% to a string", showType()) .msg = hintfmt("cannot coerce %1% to a string", showType())
}); });
e.addTrace(pos, errorCtx);
throw e;
} }

View file

@ -206,6 +206,9 @@ public:
throw std::move(error); 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; ErrorBuilder * errorBuilder;
template<typename... Args> template<typename... Args>
@ -386,8 +389,8 @@ public:
booleans and lists to a string. If `copyToStore' is set, booleans and lists to a string. If `copyToStore' is set,
referenced paths are copied to the Nix store as a side effect. */ referenced paths are copied to the Nix store as a side effect. */
BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context, BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
bool coerceMore = false, bool copyToStore = true, std::string_view errorCtx,
std::string_view errorCtx = ""); bool coerceMore = false, bool copyToStore = true);
StorePath copyPathToStore(PathSet & context, const SourcePath & path); StorePath copyPathToStore(PathSet & context, const SourcePath & path);

View file

@ -202,7 +202,7 @@ static Flake readFlake(
PathSet emptyContext = {}; PathSet emptyContext = {};
flake.config.settings.emplace( flake.config.settings.emplace(
state.symbols[setting.name], state.symbols[setting.name],
state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, "").toOwned()); state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true).toOwned());
} }
else if (setting.value->type() == nInt) else if (setting.value->type() == nInt)
flake.config.settings.emplace( flake.config.settings.emplace(

View file

@ -239,15 +239,15 @@ std::pair<ref<InputAccessor>, FlakeRef> FlakeRef::lazyFetch(ref<Store> store) co
return {accessor, FlakeRef(std::move(lockedInput), subdir)}; return {accessor, FlakeRef(std::move(lockedInput), subdir)};
} }
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec( std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
const std::string & url, const std::string & url,
const std::optional<Path> & baseDir, const std::optional<Path> & baseDir,
bool allowMissing, bool allowMissing,
bool isFlake) bool isFlake)
{ {
auto [prefix, outputsSpec] = parseOutputsSpec(url); auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(url);
auto [flakeRef, fragment] = parseFlakeRefWithFragment(prefix, baseDir, allowMissing, isFlake); auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, baseDir, allowMissing, isFlake);
return {std::move(flakeRef), fragment, outputsSpec}; return {std::move(flakeRef), fragment, extendedOutputsSpec};
} }
} }

View file

@ -80,7 +80,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment( std::optional<std::pair<FlakeRef, std::string>> maybeParseFlakeRefWithFragment(
const std::string & url, const std::optional<Path> & baseDir = {}); const std::string & url, const std::optional<Path> & baseDir = {});
std::tuple<FlakeRef, std::string, OutputsSpec> parseFlakeRefWithFragmentAndOutputsSpec( std::tuple<FlakeRef, std::string, ExtendedOutputsSpec> parseFlakeRefWithFragmentAndExtendedOutputsSpec(
const std::string & url, const std::string & url,
const std::optional<Path> & baseDir = {}, const std::optional<Path> & baseDir = {},
bool allowMissing = false, bool allowMissing = false,

View file

@ -54,7 +54,7 @@ StringMap EvalState::realiseContext(const PathSet & context)
[&](const NixStringContextElem::Built & b) { [&](const NixStringContextElem::Built & b) {
drvs.push_back(DerivedPath::Built { drvs.push_back(DerivedPath::Built {
.drvPath = b.drvPath, .drvPath = b.drvPath,
.outputs = std::set { b.output }, .outputs = OutputsSpec::Names { b.output },
}); });
ensureValid(b.drvPath); ensureValid(b.drvPath);
}, },
@ -85,16 +85,12 @@ StringMap EvalState::realiseContext(const PathSet & context)
store->buildPaths(buildReqs); store->buildPaths(buildReqs);
/* Get all the output paths corresponding to the placeholders we had */ /* Get all the output paths corresponding to the placeholders we had */
for (auto & [drvPath, outputs] : drvs) { for (auto & drv : drvs) {
const auto outputPaths = store->queryDerivationOutputMap(drvPath); auto outputs = resolveDerivedPath(*store, drv);
for (auto & outputName : outputs) { for (auto & [outputName, outputPath] : outputs) {
auto outputPath = get(outputPaths, outputName);
if (!outputPath)
debugThrowLastTrace(Error("derivation '%s' does not have an output named '%s'",
store->printStorePath(drvPath), outputName));
res.insert_or_assign( res.insert_or_assign(
downstreamPlaceholder(*store, drvPath, outputName), downstreamPlaceholder(*store, drv.drvPath, outputName),
store->printStorePath(*outputPath) store->printStorePath(outputPath)
); );
} }
} }
@ -356,26 +352,22 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
auto elems = args[0]->listElems(); auto elems = args[0]->listElems();
auto count = args[0]->listSize(); auto count = args[0]->listSize();
if (count == 0) if (count == 0)
state.debugThrowLastTrace(EvalError({ state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
.msg = hintfmt("at least one argument to 'exec' required"),
.errPos = state.positions[pos]
}));
PathSet context; PathSet context;
auto program = state.coerceToString(pos, *elems[0], context, false, false, auto program = state.coerceToString(pos, *elems[0], context,
"while evaluating the first element of the argument passed to builtins.exec").toOwned(); "while evaluating the first element of the argument passed to builtins.exec",
false, false).toOwned();
Strings commandArgs; Strings commandArgs;
for (unsigned int i = 1; i < args[0]->listSize(); ++i) { for (unsigned int i = 1; i < args[0]->listSize(); ++i) {
commandArgs.push_back(state.coerceToString(pos, *elems[i], context, false, false, commandArgs.push_back(
"while evaluating an element of the argument passed to builtins.exec").toOwned()); state.coerceToString(pos, *elems[i], context,
"while evaluating an element of the argument passed to builtins.exec",
false, false).toOwned());
} }
try { try {
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
} catch (InvalidPathError & e) { } catch (InvalidPathError & e) {
state.debugThrowLastTrace(EvalError({ state.error("cannot execute '%1%', since path '%2%' is not valid", program, e.path).atPos(pos).debugThrow<EvalError>();
.msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
program, e.path),
.errPos = state.positions[pos]
}));
} }
auto output = runProgram(program, true, commandArgs); auto output = runProgram(program, true, commandArgs);
@ -605,6 +597,7 @@ struct CompareValues
state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>(); state.error("cannot compare %s with %s; values of that type are incomparable", showType(*v1), showType(*v2)).debugThrow<EvalError>();
} }
} catch (Error & e) { } catch (Error & e) {
if (!errorCtx.empty())
e.addTrace(nullptr, errorCtx); e.addTrace(nullptr, errorCtx);
throw; throw;
} }
@ -627,15 +620,7 @@ static Bindings::iterator getAttr(
{ {
Bindings::iterator value = attrSet->find(attrSym); Bindings::iterator value = attrSet->find(attrSym);
if (value == attrSet->end()) { if (value == attrSet->end()) {
throw TypeError({ state.error("attribute '%s' missing", state.symbols[attrSym]).withTrace(noPos, errorCtx).debugThrow<TypeError>();
.msg = hintfmt("attribute '%s' missing %s", state.symbols[attrSym], normaltxt(errorCtx)),
.errPos = state.positions[attrSet->pos],
});
// TODO XXX
// Adding another trace for the function name to make it clear
// which call received wrong arguments.
//e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName));
//state.debugThrowLastTrace(e);
} }
return value; return value;
} }
@ -809,7 +794,8 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
} catch (Error & e) { } catch (Error & e) {
PathSet context; PathSet context;
e.addTrace(nullptr, state.decodePaths(*state.coerceToString(pos, *args[0], context, e.addTrace(nullptr, state.decodePaths(*state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to 'builtins.addErrorContext'"))); "while evaluating the error message passed to 'builtins.addErrorContext'",
false, false)));
throw; throw;
} }
} }
@ -1013,6 +999,7 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
* Derivations * Derivations
*************************************************************/ *************************************************************/
static void derivationStrictInternal(EvalState & state, const std::string & name, Bindings * attrs, Value & v);
/* Construct (as a unobservable side effect) a Nix derivation /* Construct (as a unobservable side effect) a Nix derivation
expression that performs the derivation described by the argument expression that performs the derivation described by the argument
@ -1023,32 +1010,68 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val
derivation. */ derivation. */
static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
using nlohmann::json;
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict"); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.derivationStrict");
Bindings * attrs = args[0]->attrs;
/* Figure out the name first (for stack backtraces). */ /* Figure out the name first (for stack backtraces). */
Bindings::iterator attr = getAttr(state, state.sName, args[0]->attrs, "in the attrset passed as argument to builtins.derivationStrict"); Bindings::iterator nameAttr = getAttr(state, state.sName, attrs, "in the attrset passed as argument to builtins.derivationStrict");
std::string drvName; std::string drvName;
const auto posDrvName = attr->pos;
try { try {
drvName = state.forceStringNoCtx(*attr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict"); drvName = state.forceStringNoCtx(*nameAttr->value, pos, "while evaluating the `name` attribute passed to builtins.derivationStrict");
} catch (Error & e) { } catch (Error & e) {
e.addTrace(state.positions[posDrvName], "while evaluating the derivation attribute 'name'"); e.addTrace(state.positions[nameAttr->pos], "while evaluating the derivation attribute 'name'");
throw; throw;
} }
try {
derivationStrictInternal(state, drvName, attrs, v);
} catch (Error & e) {
Pos pos = state.positions[nameAttr->pos];
/*
* Here we make two abuses of the error system
*
* 1. We print the location as a string to avoid a code snippet being
* printed. While the location of the name attribute is a good hint, the
* exact code there is irrelevant.
*
* 2. We mark this trace as a frame trace, meaning that we stop printing
* less important traces from now on. In particular, this prevents the
* display of the automatic "while calling builtins.derivationStrict"
* trace, which is of little use for the public we target here.
*
* Please keep in mind that error reporting is done on a best-effort
* basis in nix. There is no accurate location for a derivation, as it
* often results from the composition of several functions
* (derivationStrict, derivation, mkDerivation, mkPythonModule, etc.)
*/
e.addTrace(nullptr, hintfmt(
"while evaluating derivation '%s'\n"
" whose name attribute is located at %s",
drvName, pos), true);
throw;
}
}
static void derivationStrictInternal(EvalState & state, const std::string &
drvName, Bindings * attrs, Value & v)
{
/* Check whether attributes should be passed as a JSON file. */ /* Check whether attributes should be passed as a JSON file. */
using nlohmann::json;
std::optional<json> jsonObject; std::optional<json> jsonObject;
attr = args[0]->attrs->find(state.sStructuredAttrs); auto attr = attrs->find(state.sStructuredAttrs);
if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos, "while evaluating the `__structuredAttrs` attribute passed to builtins.derivationStrict")) if (attr != attrs->end() &&
state.forceBool(*attr->value, noPos,
"while evaluating the `__structuredAttrs` "
"attribute passed to builtins.derivationStrict"))
jsonObject = json::object(); jsonObject = json::object();
/* Check whether null attributes should be ignored. */ /* Check whether null attributes should be ignored. */
bool ignoreNulls = false; bool ignoreNulls = false;
attr = args[0]->attrs->find(state.sIgnoreNulls); attr = attrs->find(state.sIgnoreNulls);
if (attr != args[0]->attrs->end()) 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. */ /* Build the derivation expression by processing the attributes. */
Derivation drv; Derivation drv;
@ -1065,7 +1088,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
StringSet outputs; StringSet outputs;
outputs.insert("out"); outputs.insert("out");
for (auto & i : args[0]->attrs->lexicographicOrder(state.symbols)) { for (auto & i : attrs->lexicographicOrder(state.symbols)) {
if (i->name == state.sIgnoreNulls) continue; if (i->name == state.sIgnoreNulls) continue;
const std::string & key = state.symbols[i->name]; const std::string & key = state.symbols[i->name];
vomit("processing attribute '%1%'", key); vomit("processing attribute '%1%'", key);
@ -1076,7 +1099,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
else else
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s), .msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
.errPos = state.positions[posDrvName] .errPos = state.positions[noPos]
})); }));
}; };
@ -1086,7 +1109,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (outputs.find(j) != outputs.end()) if (outputs.find(j) != outputs.end())
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("duplicate derivation output '%1%'", j), .msg = hintfmt("duplicate derivation output '%1%'", j),
.errPos = state.positions[posDrvName] .errPos = state.positions[noPos]
})); }));
/* !!! Check whether j is a valid attribute /* !!! Check whether j is a valid attribute
name. */ name. */
@ -1096,34 +1119,35 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (j == "drv") if (j == "drv")
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid derivation output name 'drv'" ), .msg = hintfmt("invalid derivation output name 'drv'" ),
.errPos = state.positions[posDrvName] .errPos = state.positions[noPos]
})); }));
outputs.insert(j); outputs.insert(j);
} }
if (outputs.empty()) if (outputs.empty())
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation cannot have an empty set of outputs"), .msg = hintfmt("derivation cannot have an empty set of outputs"),
.errPos = state.positions[posDrvName] .errPos = state.positions[noPos]
})); }));
}; };
try { try {
// This try-catch block adds context for most errors.
// Use this empty error context to signify that we defer to it.
const std::string_view context_below("");
if (ignoreNulls) { if (ignoreNulls) {
state.forceValue(*i->value, pos); state.forceValue(*i->value, noPos);
if (i->value->type() == nNull) continue; if (i->value->type() == nNull) continue;
} }
if (i->name == state.sContentAddressed) { if (i->name == state.sContentAddressed) {
contentAddressed = state.forceBool(*i->value, pos, contentAddressed = state.forceBool(*i->value, noPos, context_below);
"while evaluating the `__contentAddressed` attribute passed to builtins.derivationStrict");
if (contentAddressed) if (contentAddressed)
settings.requireExperimentalFeature(Xp::CaDerivations); settings.requireExperimentalFeature(Xp::CaDerivations);
} }
else if (i->name == state.sImpure) { else if (i->name == state.sImpure) {
isImpure = state.forceBool(*i->value, pos, isImpure = state.forceBool(*i->value, noPos, context_below);
"while evaluating the 'impure' attribute passed to builtins.derivationStrict");
if (isImpure) if (isImpure)
settings.requireExperimentalFeature(Xp::ImpureDerivations); settings.requireExperimentalFeature(Xp::ImpureDerivations);
} }
@ -1131,11 +1155,11 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
/* The `args' attribute is special: it supplies the /* The `args' attribute is special: it supplies the
command-line arguments to the builder. */ command-line arguments to the builder. */
else if (i->name == state.sArgs) { else if (i->name == state.sArgs) {
state.forceList(*i->value, pos, state.forceList(*i->value, noPos, context_below);
"while evaluating the `args` attribute passed to builtins.derivationStrict");
for (auto elem : i->value->listItems()) { for (auto elem : i->value->listItems()) {
auto s = state.coerceToString(posDrvName, *elem, context, true, auto s = state.coerceToString(noPos, *elem, context,
"while evaluating an element of the `args` argument passed to builtins.derivationStrict").toOwned(); "while evaluating an element of the argument list",
true).toOwned();
drv.args.push_back(s); drv.args.push_back(s);
} }
} }
@ -1148,29 +1172,29 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (i->name == state.sStructuredAttrs) continue; 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) if (i->name == state.sBuilder)
drv.builder = state.forceString(*i->value, context, posDrvName, "while evaluating the `builder` attribute passed to builtins.derivationStrict"); drv.builder = state.forceString(*i->value, context, noPos, context_below);
else if (i->name == state.sSystem) else if (i->name == state.sSystem)
drv.platform = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `system` attribute passed to builtins.derivationStrict"); drv.platform = state.forceStringNoCtx(*i->value, noPos, context_below);
else if (i->name == state.sOutputHash) else if (i->name == state.sOutputHash)
outputHash = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHash` attribute passed to builtins.derivationStrict"); outputHash = state.forceStringNoCtx(*i->value, noPos, context_below);
else if (i->name == state.sOutputHashAlgo) else if (i->name == state.sOutputHashAlgo)
outputHashAlgo = state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashAlgo` attribute passed to builtins.derivationStrict"); outputHashAlgo = state.forceStringNoCtx(*i->value, noPos, context_below);
else if (i->name == state.sOutputHashMode) else if (i->name == state.sOutputHashMode)
handleHashMode(state.forceStringNoCtx(*i->value, posDrvName, "while evaluating the `outputHashMode` attribute passed to builtins.derivationStrict")); handleHashMode(state.forceStringNoCtx(*i->value, noPos, context_below));
else if (i->name == state.sOutputs) { else if (i->name == state.sOutputs) {
/* Require outputs to be a list of strings. */ /* Require outputs to be a list of strings. */
state.forceList(*i->value, posDrvName, "while evaluating the `outputs` attribute passed to builtins.derivationStrict"); state.forceList(*i->value, noPos, context_below);
Strings ss; Strings ss;
for (auto elem : i->value->listItems()) for (auto elem : i->value->listItems())
ss.emplace_back(state.forceStringNoCtx(*elem, posDrvName, "while evaluating an element of the `outputs` attribute passed to builtins.derivationStrict")); ss.emplace_back(state.forceStringNoCtx(*elem, noPos, context_below));
handleOutputs(ss); handleOutputs(ss);
} }
} else { } else {
auto s = state.coerceToString(i->pos, *i->value, context, true, "while evaluating an attribute passed to builtins.derivationStrict").toOwned(); auto s = state.coerceToString(noPos, *i->value, context, context_below, true).toOwned();
drv.env.emplace(key, s); drv.env.emplace(key, s);
if (i->name == state.sBuilder) drv.builder = std::move(s); if (i->name == state.sBuilder) drv.builder = std::move(s);
else if (i->name == state.sSystem) drv.platform = std::move(s); else if (i->name == state.sSystem) drv.platform = std::move(s);
@ -1184,8 +1208,8 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
} }
} catch (Error & e) { } catch (Error & e) {
e.addTrace(nullptr, e.addTrace(state.positions[i->pos],
hintfmt("while evaluating the attribute '%1%' of the derivation '%2%'", key, drvName), hintfmt("while evaluating attribute '%1%' of derivation '%2%'", key, drvName),
true); true);
throw; throw;
} }
@ -1230,20 +1254,20 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (drv.builder == "") if (drv.builder == "")
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'builder' missing"), .msg = hintfmt("required attribute 'builder' missing"),
.errPos = state.positions[posDrvName] .errPos = state.positions[noPos]
})); }));
if (drv.platform == "") if (drv.platform == "")
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'system' missing"), .msg = hintfmt("required attribute 'system' missing"),
.errPos = state.positions[posDrvName] .errPos = state.positions[noPos]
})); }));
/* Check whether the derivation name is valid. */ /* Check whether the derivation name is valid. */
if (isDerivation(drvName)) if (isDerivation(drvName))
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension), .msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
.errPos = state.positions[posDrvName] .errPos = state.positions[noPos]
})); }));
if (outputHash) { if (outputHash) {
@ -1254,7 +1278,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (outputs.size() != 1 || *(outputs.begin()) != "out") if (outputs.size() != 1 || *(outputs.begin()) != "out")
state.debugThrowLastTrace(Error({ state.debugThrowLastTrace(Error({
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"), .msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
.errPos = state.positions[posDrvName] .errPos = state.positions[noPos]
})); }));
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo)); auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
@ -1275,7 +1299,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (contentAddressed && isImpure) if (contentAddressed && isImpure)
throw EvalError({ throw EvalError({
.msg = hintfmt("derivation cannot be both content-addressed and impure"), .msg = hintfmt("derivation cannot be both content-addressed and impure"),
.errPos = state.positions[posDrvName] .errPos = state.positions[noPos]
}); });
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256); auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256);
@ -1319,7 +1343,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (!h) if (!h)
throw AssertionError({ throw AssertionError({
.msg = hintfmt("derivation produced no hash for output '%s'", i), .msg = hintfmt("derivation produced no hash for output '%s'", i),
.errPos = state.positions[posDrvName], .errPos = state.positions[noPos],
}); });
auto outPath = state.store->makeOutputPath(i, *h, drvName); auto outPath = state.store->makeOutputPath(i, *h, drvName);
drv.env[i] = state.store->printStorePath(outPath); drv.env[i] = state.store->printStorePath(outPath);
@ -1352,11 +1376,12 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
drvHashes.lock()->insert_or_assign(drvPath, h); drvHashes.lock()->insert_or_assign(drvPath, h);
} }
auto attrs = state.buildBindings(1 + drv.outputs.size()); auto result = state.buildBindings(1 + drv.outputs.size());
attrs.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS}); result.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS});
for (auto & i : drv.outputs) for (auto & i : drv.outputs)
mkOutputString(state, attrs, drvPath, drv, i); mkOutputString(state, result, drvPath, drv, i);
v.mkAttrs(attrs);
v.mkAttrs(result);
} }
static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info { static RegisterPrimOp primop_derivationStrict(RegisterPrimOp::Info {
@ -1502,7 +1527,9 @@ static RegisterPrimOp primop_pathExists({
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.baseNameOf")), context); v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to builtins.baseNameOf",
false, false)), context);
} }
static RegisterPrimOp primop_baseNameOf({ static RegisterPrimOp primop_baseNameOf({
@ -1527,8 +1554,9 @@ static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Valu
auto path = args[0]->path(); auto path = args[0]->path();
v.mkPath(path.path.isRoot() ? path : path.parent()); v.mkPath(path.path.isRoot() ? path : path.parent());
} else { } else {
auto path = state.coerceToString(pos, *args[0], context, false, false, auto path = state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to 'builtins.dirOf'"); "while evaluating the first argument passed to 'builtins.dirOf'",
false, false);
auto dir = dirOf(*path); auto dir = dirOf(*path);
v.mkString(dir, context); v.mkString(dir, context);
} }
@ -1596,8 +1624,9 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath"); i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
PathSet context; PathSet context;
auto path = state.coerceToString(pos, *i->value, context, false, false, auto path = state.coerceToString(pos, *i->value, context,
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile").toOwned(); "while evaluating the `path` attribute of an element of the list passed to builtins.findFile",
false, false).toOwned();
try { try {
auto rewrites = state.realiseContext(context); auto rewrites = state.realiseContext(context);
@ -1660,18 +1689,56 @@ static std::string_view fileTypeToString(InputAccessor::Type type)
"unknown"; "unknown";
} }
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
auto path = realisePath(state, pos, *args[0]);
/* Retrieve the directory entry type and stringize it. */
v.mkString(fileTypeToString(path.lstat().type));
}
static RegisterPrimOp primop_readFileType({
.name = "__readFileType",
.args = {"p"},
.doc = R"(
Determine the directory entry type of a filesystem node, being
one of "directory", "regular", "symlink", or "unknown".
)",
.fun = prim_readFileType,
});
/* Read a directory (without . or ..) */ /* Read a directory (without . or ..) */
static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
auto path = realisePath(state, pos, *args[0]); auto path = realisePath(state, pos, *args[0]);
// Retrieve directory entries for all nodes in a directory.
// This is similar to `getFileType` but is optimized to reduce system calls
// on many systems.
auto entries = path.readDirectory(); auto entries = path.readDirectory();
auto attrs = state.buildBindings(entries.size()); auto attrs = state.buildBindings(entries.size());
// If we hit unknown directory entry types we may need to fallback to
// using `getFileType` on some systems.
// In order to reduce system calls we make each lookup lazy by using
// `builtins.readFileType` application.
Value * readFileType = nullptr;
for (auto & [name, type] : entries) { for (auto & [name, type] : entries) {
if (!type) auto & attr = attrs.alloc(name);
type = (path + name).lstat().type; if (!type) {
attrs.alloc(name).mkString(fileTypeToString(*type)); // Some filesystems or operating systems may not be able to return
// 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);
if (!readFileType)
readFileType = &state.getBuiltin("readFileType");
attr.mkApp(readFileType, epath);
} else {
// This branch of the conditional is much more likely.
// Here we just stringize the directory entry type.
attr.mkString(fileTypeToString(*type));
}
} }
v.mkAttrs(attrs); v.mkAttrs(attrs);
@ -2665,14 +2732,9 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
for (unsigned int n = 0; n < listSize; ++n) { for (unsigned int n = 0; n < listSize; ++n) {
Value * vElem = listElems[n]; Value * vElem = listElems[n];
try {
state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith"); state.forceAttrs(*vElem, noPos, "while evaluating a value of the list passed as second argument to builtins.zipAttrsWith");
for (auto & attr : *vElem->attrs) for (auto & attr : *vElem->attrs)
attrsSeen[attr.name].first++; attrsSeen[attr.name].first++;
} catch (TypeError & e) {
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith"));
state.debugThrowLastTrace(e);
}
} }
auto attrs = state.buildBindings(attrsSeen.size()); auto attrs = state.buildBindings(attrsSeen.size());
@ -3056,13 +3118,13 @@ 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"); auto len = state.forceInt(*args[1], pos, "while evaluating the second argument passed to builtins.genList");
if (len < 0) if (len < 0)
state.debugThrowLastTrace(EvalError({ state.error("cannot create list of size %1%", len).debugThrow<EvalError>();
.msg = hintfmt("cannot create list of size %1%", len),
.errPos = state.positions[pos] // More strict than striclty (!) necessary, but acceptable
})); // as evaluating map without accessing any values makes little sense.
state.forceFunction(*args[0], noPos, "while evaluating the first argument passed to builtins.genList");
state.mkList(v, len); state.mkList(v, len);
for (unsigned int n = 0; n < (unsigned int) len; ++n) { for (unsigned int n = 0; n < (unsigned int) len; ++n) {
auto arg = state.allocValue(); auto arg = state.allocValue();
arg->mkInt(n); arg->mkInt(n);
@ -3110,6 +3172,8 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
auto comparator = [&](Value * a, Value * b) { auto comparator = [&](Value * a, Value * b) {
/* Optimization: if the comparator is lessThan, bypass /* Optimization: if the comparator is lessThan, bypass
callFunction. */ callFunction. */
/* TODO: (layus) this is absurd. An optimisation like this
should be outside the lambda creation */
if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan) if (args[0]->isPrimOp() && args[0]->primOp->fun == prim_lessThan)
return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b); return CompareValues(state, noPos, "while evaluating the ordering function passed to builtins.sort")(a, b);
@ -3270,12 +3334,7 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
for (unsigned int n = 0; n < nrLists; ++n) { for (unsigned int n = 0; n < nrLists; ++n) {
Value * vElem = args[1]->listElems()[n]; Value * vElem = args[1]->listElems()[n];
state.callFunction(*args[0], *vElem, lists[n], pos); state.callFunction(*args[0], *vElem, lists[n], pos);
try {
state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap"); state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
} catch (TypeError &e) {
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap"));
state.debugThrowLastTrace(e);
}
len += lists[n].listSize(); len += lists[n].listSize();
} }
@ -3455,7 +3514,7 @@ static void prim_lessThan(EvalState & state, const PosIdx pos, Value * * args, V
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
state.forceValue(*args[1], pos); state.forceValue(*args[1], pos);
// pos is exact here, no need for a message. // pos is exact here, no need for a message.
CompareValues comp(state, pos, ""); CompareValues comp(state, noPos, "");
v.mkBool(comp(args[0], args[1])); v.mkBool(comp(args[0], args[1]));
} }
@ -3482,7 +3541,9 @@ static RegisterPrimOp primop_lessThan({
static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
auto s = state.coerceToString(pos, *args[0], context, true, false, "while evaluating the first argument passed to builtins.toString"); auto s = state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to builtins.toString",
true, false);
v.mkString(*s, context); v.mkString(*s, context);
} }
@ -3834,21 +3895,18 @@ 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[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"); state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.replaceStrings");
if (args[0]->listSize() != args[1]->listSize()) if (args[0]->listSize() != args[1]->listSize())
state.debugThrowLastTrace(EvalError({ state.error("'from' and 'to' arguments passed to builtins.replaceStrings have different lengths").atPos(pos).debugThrow<EvalError>();
.msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
.errPos = state.positions[pos]
}));
std::vector<std::string> from; std::vector<std::string> from;
from.reserve(args[0]->listSize()); from.reserve(args[0]->listSize());
for (auto elem : args[0]->listItems()) for (auto elem : args[0]->listItems())
from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace in builtins.replaceStrings")); from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace passed to builtins.replaceStrings"));
std::vector<std::pair<std::string, PathSet>> to; std::vector<std::pair<std::string, PathSet>> to;
to.reserve(args[1]->listSize()); to.reserve(args[1]->listSize());
for (auto elem : args[1]->listItems()) { for (auto elem : args[1]->listItems()) {
PathSet ctx; PathSet ctx;
auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings of builtins.replaceStrings"); auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
to.emplace_back(s, std::move(ctx)); to.emplace_back(s, std::move(ctx));
} }

View file

@ -83,15 +83,13 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext"); state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
auto contextInfos = std::map<StorePath, ContextInfo>(); auto contextInfos = std::map<StorePath, ContextInfo>();
for (const auto & p : context) { for (const auto & p : context) {
Path drv;
std::string output;
NixStringContextElem ctx = NixStringContextElem::parse(*state.store, p); NixStringContextElem ctx = NixStringContextElem::parse(*state.store, p);
std::visit(overloaded { std::visit(overloaded {
[&](NixStringContextElem::DrvDeep & d) { [&](NixStringContextElem::DrvDeep & d) {
contextInfos[d.drvPath].allOutputs = true; contextInfos[d.drvPath].allOutputs = true;
}, },
[&](NixStringContextElem::Built & b) { [&](NixStringContextElem::Built & b) {
contextInfos[b.drvPath].outputs.emplace_back(std::move(output)); contextInfos[b.drvPath].outputs.emplace_back(std::move(b.output));
}, },
[&](NixStringContextElem::Opaque & o) { [&](NixStringContextElem::Opaque & o) {
contextInfos[o.path].path = true; contextInfos[o.path].path = true;

View file

@ -22,7 +22,9 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
for (auto & attr : *args[0]->attrs) { for (auto & attr : *args[0]->attrs) {
std::string_view n(state.symbols[attr.name]); std::string_view n(state.symbols[attr.name]);
if (n == "url") if (n == "url")
url = state.coerceToString(attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").toOwned(); url = state.coerceToString(attr.pos, *attr.value, context,
"while evaluating the `url` attribute passed to builtins.fetchMercurial",
false, false).toOwned();
else if (n == "rev") { else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can // Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name. // be both a revision or a branch/tag name.
@ -48,7 +50,9 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
}); });
} else } else
url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.fetchMercurial").toOwned(); url = state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to builtins.fetchMercurial",
false, false).toOwned();
// FIXME: git externals probably can be used to bypass the URI // FIXME: git externals probably can be used to bypass the URI
// whitelist. Ah well. // whitelist. Ah well.

View file

@ -140,7 +140,7 @@ static void fetchTree(
state.forceValue(*attr.value, attr.pos); state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) { if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, false, false, "").toOwned(); auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
attrs.emplace(state.symbols[attr.name], attrs.emplace(state.symbols[attr.name],
state.symbols[attr.name] == "url" state.symbols[attr.name] == "url"
? type == "git" ? type == "git"
@ -166,7 +166,9 @@ static void fetchTree(
input = fetchers::Input::fromAttrs(std::move(attrs)); input = fetchers::Input::fromAttrs(std::move(attrs));
} else { } else {
auto url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to the fetcher").toOwned(); auto url = state.coerceToString(pos, *args[0], context,
"while evaluating the first argument passed to the fetcher",
false, false).toOwned();
if (type == "git") { if (type == "git") {
fetchers::Attrs attrs; fetchers::Attrs attrs;
@ -255,6 +257,9 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
} else } else
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch"); url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
if (who == "fetchTarball")
url = evalSettings.resolvePseudoUrl(*url);
state.checkURI(*url); state.checkURI(*url);
if (name == "") if (name == "")

File diff suppressed because it is too large Load diff

View file

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

View file

@ -9,6 +9,7 @@
#include "types.hh" #include "types.hh"
#include "split.hh" #include "split.hh"
#include "fs-input-accessor.hh" #include "fs-input-accessor.hh"
#include "store-api.hh"
namespace nix::fetchers { namespace nix::fetchers {

View file

@ -3,6 +3,10 @@
#include "types.hh" #include "types.hh"
#include "path.hh" #include "path.hh"
namespace nix {
class Store;
}
namespace nix::fetchers { namespace nix::fetchers {
struct DownloadFileResult struct DownloadFileResult

View file

@ -502,22 +502,9 @@ void BinaryCacheStore::addSignatures(const StorePath & storePath, const StringSe
writeNarInfo(narInfo); writeNarInfo(narInfo);
} }
std::optional<std::string> BinaryCacheStore::getBuildLog(const StorePath & path) std::optional<std::string> BinaryCacheStore::getBuildLogExact(const StorePath & path)
{ {
auto drvPath = path; auto logPath = "log/" + std::string(baseNameOf(printStorePath(path)));
if (!path.isDerivation()) {
try {
auto info = queryPathInfo(path);
// FIXME: add a "Log" field to .narinfo
if (!info->deriver) return std::nullopt;
drvPath = *info->deriver;
} catch (InvalidPath &) {
return std::nullopt;
}
}
auto logPath = "log/" + std::string(baseNameOf(printStorePath(drvPath)));
debug("fetching build log from binary cache '%s/%s'", getUri(), logPath); debug("fetching build log from binary cache '%s/%s'", getUri(), logPath);

View file

@ -129,7 +129,7 @@ public:
void addSignatures(const StorePath & storePath, const StringSet & sigs) override; void addSignatures(const StorePath & storePath, const StringSet & sigs) override;
std::optional<std::string> getBuildLog(const StorePath & path) override; std::optional<std::string> getBuildLogExact(const StorePath & path) override;
void addBuildLog(const StorePath & drvPath, std::string_view log) override; void addBuildLog(const StorePath & drvPath, std::string_view log) override;

View file

@ -63,7 +63,7 @@
namespace nix { namespace nix {
DerivationGoal::DerivationGoal(const StorePath & drvPath, DerivationGoal::DerivationGoal(const StorePath & drvPath,
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode) const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs }) : Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
, useDerivation(true) , useDerivation(true)
, drvPath(drvPath) , drvPath(drvPath)
@ -82,7 +82,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv, DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode) const OutputsSpec & wantedOutputs, Worker & worker, BuildMode buildMode)
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs }) : Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
, useDerivation(false) , useDerivation(false)
, drvPath(drvPath) , drvPath(drvPath)
@ -142,18 +142,12 @@ void DerivationGoal::work()
(this->*state)(); (this->*state)();
} }
void DerivationGoal::addWantedOutputs(const StringSet & outputs) void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
{ {
/* If we already want all outputs, there is nothing to do. */ auto newWanted = wantedOutputs.union_(outputs);
if (wantedOutputs.empty()) return; if (!newWanted.isSubsetOf(wantedOutputs))
if (outputs.empty()) {
wantedOutputs.clear();
needRestart = true;
} else
for (auto & i : outputs)
if (wantedOutputs.insert(i).second)
needRestart = true; needRestart = true;
wantedOutputs = newWanted;
} }
@ -390,7 +384,7 @@ void DerivationGoal::repairClosure()
auto outputs = queryDerivationOutputMap(); auto outputs = queryDerivationOutputMap();
StorePathSet outputClosure; StorePathSet outputClosure;
for (auto & i : outputs) { for (auto & i : outputs) {
if (!wantOutput(i.first, wantedOutputs)) continue; if (!wantedOutputs.contains(i.first)) continue;
worker.store.computeFSClosure(i.second, outputClosure); worker.store.computeFSClosure(i.second, outputClosure);
} }
@ -422,7 +416,7 @@ void DerivationGoal::repairClosure()
if (drvPath2 == outputsToDrv.end()) if (drvPath2 == outputsToDrv.end())
addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i, Repair))); addWaitee(upcast_goal(worker.makePathSubstitutionGoal(i, Repair)));
else else
addWaitee(worker.makeDerivationGoal(drvPath2->second, StringSet(), bmRepair)); addWaitee(worker.makeDerivationGoal(drvPath2->second, OutputsSpec::All(), bmRepair));
} }
if (waitees.empty()) { if (waitees.empty()) {
@ -991,10 +985,15 @@ void DerivationGoal::resolvedFinished()
StorePathSet outputPaths; StorePathSet outputPaths;
// `wantedOutputs` might be empty, which means “all the outputs” // `wantedOutputs` might merely indicate “all the outputs”
auto realWantedOutputs = wantedOutputs; auto realWantedOutputs = std::visit(overloaded {
if (realWantedOutputs.empty()) [&](const OutputsSpec::All &) {
realWantedOutputs = resolvedDrv.outputNames(); return resolvedDrv.outputNames();
},
[&](const OutputsSpec::Names & names) {
return static_cast<std::set<std::string>>(names);
},
}, wantedOutputs.raw());
for (auto & wantedOutput : realWantedOutputs) { for (auto & wantedOutput : realWantedOutputs) {
auto initialOutput = get(initialOutputs, wantedOutput); auto initialOutput = get(initialOutputs, wantedOutput);
@ -1322,7 +1321,14 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
if (!drv->type().isPure()) return { false, {} }; if (!drv->type().isPure()) return { false, {} };
bool checkHash = buildMode == bmRepair; bool checkHash = buildMode == bmRepair;
auto wantedOutputsLeft = wantedOutputs; auto wantedOutputsLeft = std::visit(overloaded {
[&](const OutputsSpec::All &) {
return StringSet {};
},
[&](const OutputsSpec::Names & names) {
return static_cast<StringSet>(names);
},
}, wantedOutputs.raw());
DrvOutputs validOutputs; DrvOutputs validOutputs;
for (auto & i : queryPartialDerivationOutputMap()) { for (auto & i : queryPartialDerivationOutputMap()) {
@ -1331,7 +1337,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
// this is an invalid output, gets catched with (!wantedOutputsLeft.empty()) // this is an invalid output, gets catched with (!wantedOutputsLeft.empty())
continue; continue;
auto & info = *initialOutput; auto & info = *initialOutput;
info.wanted = wantOutput(i.first, wantedOutputs); info.wanted = wantedOutputs.contains(i.first);
if (info.wanted) if (info.wanted)
wantedOutputsLeft.erase(i.first); wantedOutputsLeft.erase(i.first);
if (i.second) { if (i.second) {
@ -1369,7 +1375,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path }); validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path });
} }
// If we requested all the outputs via the empty set, we are always fine. // If we requested all the outputs, we are always fine.
// If we requested specific elements, the loop above removes all the valid // If we requested specific elements, the loop above removes all the valid
// ones, so any that are left must be invalid. // ones, so any that are left must be invalid.
if (!wantedOutputsLeft.empty()) if (!wantedOutputsLeft.empty())

View file

@ -2,6 +2,7 @@
#include "parsed-derivations.hh" #include "parsed-derivations.hh"
#include "lock.hh" #include "lock.hh"
#include "outputs-spec.hh"
#include "store-api.hh" #include "store-api.hh"
#include "pathlocks.hh" #include "pathlocks.hh"
#include "goal.hh" #include "goal.hh"
@ -55,7 +56,7 @@ struct DerivationGoal : public Goal
/* The specific outputs that we need to build. Empty means all of /* The specific outputs that we need to build. Empty means all of
them. */ them. */
StringSet wantedOutputs; OutputsSpec wantedOutputs;
/* Mapping from input derivations + output names to actual store /* Mapping from input derivations + output names to actual store
paths. This is filled in by waiteeDone() as each dependency paths. This is filled in by waiteeDone() as each dependency
@ -128,10 +129,10 @@ struct DerivationGoal : public Goal
std::string machineName; std::string machineName;
DerivationGoal(const StorePath & drvPath, DerivationGoal(const StorePath & drvPath,
const StringSet & wantedOutputs, Worker & worker, const OutputsSpec & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal); BuildMode buildMode = bmNormal);
DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv, DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
const StringSet & wantedOutputs, Worker & worker, const OutputsSpec & wantedOutputs, Worker & worker,
BuildMode buildMode = bmNormal); BuildMode buildMode = bmNormal);
virtual ~DerivationGoal(); virtual ~DerivationGoal();
@ -142,7 +143,7 @@ struct DerivationGoal : public Goal
void work() override; void work() override;
/* Add wanted outputs to an already existing derivation goal. */ /* Add wanted outputs to an already existing derivation goal. */
void addWantedOutputs(const StringSet & outputs); void addWantedOutputs(const OutputsSpec & outputs);
/* The states. */ /* The states. */
void getDerivation(); void getDerivation();

View file

@ -80,7 +80,7 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
BuildMode buildMode) BuildMode buildMode)
{ {
Worker worker(*this, *this); Worker worker(*this, *this);
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, {}, buildMode); auto goal = worker.makeBasicDerivationGoal(drvPath, drv, OutputsSpec::All {}, buildMode);
try { try {
worker.run(Goals{goal}); worker.run(Goals{goal});
@ -89,7 +89,10 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
return BuildResult { return BuildResult {
.status = BuildResult::MiscFailure, .status = BuildResult::MiscFailure,
.errorMsg = e.msg(), .errorMsg = e.msg(),
.path = DerivedPath::Built { .drvPath = drvPath }, .path = DerivedPath::Built {
.drvPath = drvPath,
.outputs = OutputsSpec::All { },
},
}; };
}; };
} }
@ -130,7 +133,8 @@ void LocalStore::repairPath(const StorePath & path)
auto info = queryPathInfo(path); auto info = queryPathInfo(path);
if (info->deriver && isValidPath(*info->deriver)) { if (info->deriver && isValidPath(*info->deriver)) {
goals.clear(); goals.clear();
goals.insert(worker.makeDerivationGoal(*info->deriver, StringSet(), bmRepair)); // FIXME: Should just build the specific output we need.
goals.insert(worker.makeDerivationGoal(*info->deriver, OutputsSpec::All { }, bmRepair));
worker.run(goals); worker.run(goals);
} else } else
throw Error(worker.exitStatus(), "cannot repair path '%s'", printStorePath(path)); throw Error(worker.exitStatus(), "cannot repair path '%s'", printStorePath(path));

View file

@ -1459,7 +1459,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
unknown, downloadSize, narSize); unknown, downloadSize, narSize);
} }
virtual std::optional<std::string> getBuildLog(const StorePath & path) override virtual std::optional<std::string> getBuildLogExact(const StorePath & path) override
{ return std::nullopt; } { return std::nullopt; }
virtual void addBuildLog(const StorePath & path, std::string_view log) override virtual void addBuildLog(const StorePath & path, std::string_view log) override
@ -2735,7 +2735,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
signRealisation(thisRealisation); signRealisation(thisRealisation);
worker.store.registerDrvOutput(thisRealisation); worker.store.registerDrvOutput(thisRealisation);
} }
if (wantOutput(outputName, wantedOutputs)) if (wantedOutputs.contains(outputName))
builtOutputs.emplace(thisRealisation.id, thisRealisation); builtOutputs.emplace(thisRealisation.id, thisRealisation);
} }

View file

@ -42,7 +42,7 @@ Worker::~Worker()
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon( std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
const StorePath & drvPath, const StorePath & drvPath,
const StringSet & wantedOutputs, const OutputsSpec & wantedOutputs,
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal) std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal)
{ {
std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath]; std::weak_ptr<DerivationGoal> & goal_weak = derivationGoals[drvPath];
@ -59,7 +59,7 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoalCommon(
std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath, std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drvPath,
const StringSet & wantedOutputs, BuildMode buildMode) const OutputsSpec & wantedOutputs, BuildMode buildMode)
{ {
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> { return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store) return !dynamic_cast<LocalStore *>(&store)
@ -70,7 +70,7 @@ std::shared_ptr<DerivationGoal> Worker::makeDerivationGoal(const StorePath & drv
std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath, std::shared_ptr<DerivationGoal> Worker::makeBasicDerivationGoal(const StorePath & drvPath,
const BasicDerivation & drv, const StringSet & wantedOutputs, BuildMode buildMode) const BasicDerivation & drv, const OutputsSpec & wantedOutputs, BuildMode buildMode)
{ {
return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> { return makeDerivationGoalCommon(drvPath, wantedOutputs, [&]() -> std::shared_ptr<DerivationGoal> {
return !dynamic_cast<LocalStore *>(&store) return !dynamic_cast<LocalStore *>(&store)

View file

@ -140,15 +140,15 @@ public:
/* derivation goal */ /* derivation goal */
private: private:
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon( std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
const StorePath & drvPath, const StringSet & wantedOutputs, const StorePath & drvPath, const OutputsSpec & wantedOutputs,
std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal); std::function<std::shared_ptr<DerivationGoal>()> mkDrvGoal);
public: public:
std::shared_ptr<DerivationGoal> makeDerivationGoal( std::shared_ptr<DerivationGoal> makeDerivationGoal(
const StorePath & drvPath, const StorePath & drvPath,
const StringSet & wantedOutputs, BuildMode buildMode = bmNormal); const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
std::shared_ptr<DerivationGoal> makeBasicDerivationGoal( std::shared_ptr<DerivationGoal> makeBasicDerivationGoal(
const StorePath & drvPath, const BasicDerivation & drv, const StorePath & drvPath, const BasicDerivation & drv,
const StringSet & wantedOutputs, BuildMode buildMode = bmNormal); const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
/* substitution goal */ /* substitution goal */
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt); std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);

View file

@ -222,7 +222,8 @@ struct ClientSettings
else if (!hasSuffix(s, "/") && trusted.count(s + "/")) else if (!hasSuffix(s, "/") && trusted.count(s + "/"))
subs.push_back(s + "/"); subs.push_back(s + "/");
else else
warn("ignoring untrusted substituter '%s'", s); warn("ignoring untrusted substituter '%s', you are not a trusted user.\n"
"Run `man nix.conf` for more information on the `substituters` configuration option.", s);
res = subs; res = subs;
return true; return true;
}; };

View file

@ -688,12 +688,6 @@ std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation &
} }
bool wantOutput(const std::string & output, const std::set<std::string> & wanted)
{
return wanted.empty() || wanted.find(output) != wanted.end();
}
static DerivationOutput readDerivationOutput(Source & in, const Store & store) static DerivationOutput readDerivationOutput(Source & in, const Store & store)
{ {
const auto pathS = readString(in); const auto pathS = readString(in);

View file

@ -13,6 +13,7 @@
namespace nix { namespace nix {
class Store;
/* Abstract syntax of derivations. */ /* Abstract syntax of derivations. */
@ -294,8 +295,6 @@ typedef std::map<StorePath, DrvHash> DrvHashes;
// FIXME: global, though at least thread-safe. // FIXME: global, though at least thread-safe.
extern Sync<DrvHashes> drvHashes; extern Sync<DrvHashes> drvHashes;
bool wantOutput(const std::string & output, const std::set<std::string> & wanted);
struct Source; struct Source;
struct Sink; struct Sink;

View file

@ -19,11 +19,11 @@ nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
res["drvPath"] = store->printStorePath(drvPath); res["drvPath"] = store->printStorePath(drvPath);
// Fallback for the input-addressed derivation case: We expect to always be // Fallback for the input-addressed derivation case: We expect to always be
// able to print the output paths, so lets do it // able to print the output paths, so lets do it
const auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath); const auto outputMap = store->queryPartialDerivationOutputMap(drvPath);
for (const auto & output : outputs) { for (const auto & [output, outputPathOpt] : outputMap) {
auto knownOutput = get(knownOutputs, output); if (!outputs.contains(output)) continue;
if (knownOutput && *knownOutput) if (outputPathOpt)
res["outputs"][output] = store->printStorePath(**knownOutput); res["outputs"][output] = store->printStorePath(*outputPathOpt);
else else
res["outputs"][output] = nullptr; res["outputs"][output] = nullptr;
} }
@ -63,7 +63,7 @@ std::string DerivedPath::Built::to_string(const Store & store) const
{ {
return store.printStorePath(drvPath) return store.printStorePath(drvPath)
+ "!" + "!"
+ (outputs.empty() ? std::string { "*" } : concatStringsSep(",", outputs)); + outputs.to_string();
} }
std::string DerivedPath::to_string(const Store & store) const std::string DerivedPath::to_string(const Store & store) const
@ -81,15 +81,10 @@ DerivedPath::Opaque DerivedPath::Opaque::parse(const Store & store, std::string_
DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view drvS, std::string_view outputsS) DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view drvS, std::string_view outputsS)
{ {
auto drvPath = store.parseStorePath(drvS); return {
std::set<std::string> outputs; .drvPath = store.parseStorePath(drvS),
if (outputsS != "*") { .outputs = OutputsSpec::parse(outputsS),
outputs = tokenizeString<std::set<std::string>>(outputsS, ","); };
if (outputs.empty())
throw Error(
"Explicit list of wanted outputs '%s' must not be empty. Consider using '*' as a wildcard meaning all outputs if no output in particular is wanted.", outputsS);
}
return {drvPath, outputs};
} }
DerivedPath DerivedPath::parse(const Store & store, std::string_view s) DerivedPath DerivedPath::parse(const Store & store, std::string_view s)

View file

@ -3,6 +3,7 @@
#include "util.hh" #include "util.hh"
#include "path.hh" #include "path.hh"
#include "realisation.hh" #include "realisation.hh"
#include "outputs-spec.hh"
#include <optional> #include <optional>
@ -44,7 +45,7 @@ struct DerivedPathOpaque {
*/ */
struct DerivedPathBuilt { struct DerivedPathBuilt {
StorePath drvPath; StorePath drvPath;
std::set<std::string> outputs; OutputsSpec outputs;
std::string to_string(const Store & store) const; std::string to_string(const Store & store) const;
static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view); static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view);

View file

@ -570,11 +570,15 @@ public:
{"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="}, {"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="},
"trusted-public-keys", "trusted-public-keys",
R"( R"(
A whitespace-separated list of public keys. When paths are copied A whitespace-separated list of public keys.
from another Nix store (such as a binary cache), they must be
signed with one of these keys. For example: At least one of the following condition must be met
`cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= for Nix to accept copying a store object from another
hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=`. Nix store (such as a substituter):
- the store object has been signed using a key in the trusted keys list
- the [`require-sigs`](#conf-require-sigs) option has been set to `false`
- the store object is [output-addressed](@docroot@/glossary.md#gloss-output-addressed-store-object)
)", )",
{"binary-cache-public-keys"}}; {"binary-cache-public-keys"}};
@ -670,13 +674,14 @@ public:
independently. Lower value means higher priority. independently. Lower value means higher priority.
The default is `https://cache.nixos.org`, with a Priority of 40. The default is `https://cache.nixos.org`, with a Priority of 40.
Nix will copy a store path from a remote store only if one At least one of the following conditions must be met for Nix to use
of the following is true: a substituter:
- the store object is signed by one of the [`trusted-public-keys`](#conf-trusted-public-keys)
- the substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list - the substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
- the [`require-sigs`](#conf-require-sigs) option has been set to `false` - the user calling Nix is in the [`trusted-users`](#conf-trusted-users) list
- the store object is [output-addressed](@docroot@/glossary.md#gloss-output-addressed-store-object)
In addition, each store path should be trusted as described
in [`trusted-public-keys`](#conf-trusted-public-keys)
)", )",
{"binary-caches"}}; {"binary-caches"}};

View file

@ -279,7 +279,12 @@ public:
conn->to.flush(); conn->to.flush();
BuildResult status { .path = DerivedPath::Built { .drvPath = drvPath } }; BuildResult status {
.path = DerivedPath::Built {
.drvPath = drvPath,
.outputs = OutputsSpec::All { },
},
};
status.status = (BuildResult::Status) readInt(conn->from); status.status = (BuildResult::Status) readInt(conn->from);
conn->from >> status.errorMsg; conn->from >> status.errorMsg;

View file

@ -87,20 +87,8 @@ void LocalFSStore::narFromPath(const StorePath & path, Sink & sink)
const std::string LocalFSStore::drvsLogDir = "drvs"; const std::string LocalFSStore::drvsLogDir = "drvs";
std::optional<std::string> LocalFSStore::getBuildLog(const StorePath & path_) std::optional<std::string> LocalFSStore::getBuildLogExact(const StorePath & path)
{ {
auto path = path_;
if (!path.isDerivation()) {
try {
auto info = queryPathInfo(path);
if (!info->deriver) return std::nullopt;
path = *info->deriver;
} catch (InvalidPath &) {
return std::nullopt;
}
}
auto baseName = path.to_string(); auto baseName = path.to_string();
for (int j = 0; j < 2; j++) { for (int j = 0; j < 2; j++) {

View file

@ -50,7 +50,7 @@ public:
return getRealStoreDir() + "/" + std::string(storePath, storeDir.size() + 1); return getRealStoreDir() + "/" + std::string(storePath, storeDir.size() + 1);
} }
std::optional<std::string> getBuildLog(const StorePath & path) override; std::optional<std::string> getBuildLogExact(const StorePath & path) override;
}; };

12
src/libstore/log-store.cc Normal file
View file

@ -0,0 +1,12 @@
#include "log-store.hh"
namespace nix {
std::optional<std::string> LogStore::getBuildLog(const StorePath & path) {
auto maybePath = getBuildDerivationPath(path);
if (!maybePath)
return std::nullopt;
return getBuildLogExact(maybePath.value());
}
}

View file

@ -11,7 +11,9 @@ struct LogStore : public virtual Store
/* Return the build log of the specified store path, if available, /* Return the build log of the specified store path, if available,
or null otherwise. */ or null otherwise. */
virtual std::optional<std::string> getBuildLog(const StorePath & path) = 0; std::optional<std::string> getBuildLog(const StorePath & path);
virtual std::optional<std::string> getBuildLogExact(const StorePath & path) = 0;
virtual void addBuildLog(const StorePath & path, std::string_view log) = 0; virtual void addBuildLog(const StorePath & path, std::string_view log) = 0;

View file

@ -185,7 +185,7 @@ void Store::queryMissing(const std::vector<DerivedPath> & targets,
knownOutputPaths = false; knownOutputPaths = false;
break; break;
} }
if (wantOutput(outputName, bfd.outputs) && !isValidPath(*pathOpt)) if (bfd.outputs.contains(outputName) && !isValidPath(*pathOpt))
invalid.insert(*pathOpt); invalid.insert(*pathOpt);
} }
if (knownOutputPaths && invalid.empty()) return; if (knownOutputPaths && invalid.empty()) return;
@ -301,4 +301,47 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references); return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references);
} }
OutputPathMap resolveDerivedPath(Store & store, const DerivedPath::Built & bfd, Store * evalStore_)
{
auto & evalStore = evalStore_ ? *evalStore_ : store;
OutputPathMap outputs;
auto drv = evalStore.readDerivation(bfd.drvPath);
auto outputHashes = staticOutputHashes(store, drv);
auto drvOutputs = drv.outputsAndOptPaths(store);
auto outputNames = std::visit(overloaded {
[&](const OutputsSpec::All &) {
StringSet names;
for (auto & [outputName, _] : drv.outputs)
names.insert(outputName);
return names;
},
[&](const OutputsSpec::Names & names) {
return static_cast<std::set<std::string>>(names);
},
}, bfd.outputs.raw());
for (auto & output : outputNames) {
auto outputHash = get(outputHashes, output);
if (!outputHash)
throw Error(
"the derivation '%s' doesn't have an output named '%s'",
store.printStorePath(bfd.drvPath), output);
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
DrvOutput outputId { *outputHash, output };
auto realisation = store.queryRealisation(outputId);
if (!realisation)
throw MissingRealisation(outputId);
outputs.insert_or_assign(output, realisation->outPath);
} else {
// If ca-derivations isn't enabled, assume that
// the output path is statically known.
auto drvOutput = get(drvOutputs, output);
assert(drvOutput);
assert(drvOutput->second);
outputs.insert_or_assign(output, *drvOutput->second);
}
}
return outputs;
}
} }

View file

@ -1,61 +1,195 @@
#include "outputs-spec.hh"
#include "nlohmann/json.hpp"
#include <regex> #include <regex>
#include <nlohmann/json.hpp>
#include "util.hh"
#include "regex-combinators.hh"
#include "outputs-spec.hh"
#include "path-regex.hh"
namespace nix { namespace nix {
std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s) bool OutputsSpec::contains(const std::string & outputName) const
{ {
static std::regex regex(R"((.*)\^((\*)|([a-z]+(,[a-z]+)*)))"); return std::visit(overloaded {
[&](const OutputsSpec::All &) {
std::smatch match; return true;
if (!std::regex_match(s, match, regex)) },
return {s, DefaultOutputs()}; [&](const OutputsSpec::Names & outputNames) {
return outputNames.count(outputName) > 0;
if (match[3].matched) },
return {match[1], AllOutputs()}; }, raw());
return {match[1], tokenizeString<OutputNames>(match[4].str(), ",")};
} }
std::string printOutputsSpec(const OutputsSpec & outputsSpec) static std::string outputSpecRegexStr =
regex::either(
regex::group(R"(\*)"),
regex::group(regex::list(nameRegexStr)));
std::optional<OutputsSpec> OutputsSpec::parseOpt(std::string_view s)
{ {
if (std::get_if<DefaultOutputs>(&outputsSpec)) static std::regex regex(std::string { outputSpecRegexStr });
return "";
if (std::get_if<AllOutputs>(&outputsSpec)) std::smatch match;
return "^*"; std::string s2 { s }; // until some improves std::regex
if (!std::regex_match(s2, match, regex))
return std::nullopt;
if (auto outputNames = std::get_if<OutputNames>(&outputsSpec)) if (match[1].matched)
return "^" + concatStringsSep(",", *outputNames); return { OutputsSpec::All {} };
if (match[2].matched)
return OutputsSpec::Names { tokenizeString<StringSet>(match[2].str(), ",") };
assert(false); assert(false);
} }
void to_json(nlohmann::json & json, const OutputsSpec & outputsSpec)
OutputsSpec OutputsSpec::parse(std::string_view s)
{ {
if (std::get_if<DefaultOutputs>(&outputsSpec)) std::optional spec = parseOpt(s);
json = nullptr; if (!spec)
throw Error("invalid outputs specifier '%s'", s);
else if (std::get_if<AllOutputs>(&outputsSpec)) return *spec;
json = std::vector<std::string>({"*"});
else if (auto outputNames = std::get_if<OutputNames>(&outputsSpec))
json = *outputNames;
} }
void from_json(const nlohmann::json & json, OutputsSpec & outputsSpec)
std::optional<std::pair<std::string_view, ExtendedOutputsSpec>> ExtendedOutputsSpec::parseOpt(std::string_view s)
{ {
if (json.is_null()) auto found = s.rfind('^');
outputsSpec = DefaultOutputs();
else { if (found == std::string::npos)
auto names = json.get<OutputNames>(); return std::pair { s, ExtendedOutputsSpec::Default {} };
if (names == OutputNames({"*"}))
outputsSpec = AllOutputs(); auto specOpt = OutputsSpec::parseOpt(s.substr(found + 1));
if (!specOpt)
return std::nullopt;
return std::pair { s.substr(0, found), ExtendedOutputsSpec::Explicit { *std::move(specOpt) } };
}
std::pair<std::string_view, ExtendedOutputsSpec> ExtendedOutputsSpec::parse(std::string_view s)
{
std::optional spec = parseOpt(s);
if (!spec)
throw Error("invalid extended outputs specifier '%s'", s);
return *spec;
}
std::string OutputsSpec::to_string() const
{
return std::visit(overloaded {
[&](const OutputsSpec::All &) -> std::string {
return "*";
},
[&](const OutputsSpec::Names & outputNames) -> std::string {
return concatStringsSep(",", outputNames);
},
}, raw());
}
std::string ExtendedOutputsSpec::to_string() const
{
return std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default &) -> std::string {
return "";
},
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> std::string {
return "^" + outputSpec.to_string();
},
}, raw());
}
OutputsSpec OutputsSpec::union_(const OutputsSpec & that) const
{
return std::visit(overloaded {
[&](const OutputsSpec::All &) -> OutputsSpec {
return OutputsSpec::All { };
},
[&](const OutputsSpec::Names & theseNames) -> OutputsSpec {
return std::visit(overloaded {
[&](const OutputsSpec::All &) -> OutputsSpec {
return OutputsSpec::All {};
},
[&](const OutputsSpec::Names & thoseNames) -> OutputsSpec {
OutputsSpec::Names ret = theseNames;
ret.insert(thoseNames.begin(), thoseNames.end());
return ret;
},
}, that.raw());
},
}, raw());
}
bool OutputsSpec::isSubsetOf(const OutputsSpec & that) const
{
return std::visit(overloaded {
[&](const OutputsSpec::All &) {
return true;
},
[&](const OutputsSpec::Names & thoseNames) {
return std::visit(overloaded {
[&](const OutputsSpec::All &) {
return false;
},
[&](const OutputsSpec::Names & theseNames) {
bool ret = true;
for (auto & o : theseNames)
if (thoseNames.count(o) == 0)
ret = false;
return ret;
},
}, raw());
},
}, that.raw());
}
}
namespace nlohmann {
using namespace nix;
OutputsSpec adl_serializer<OutputsSpec>::from_json(const json & json) {
auto names = json.get<StringSet>();
if (names == StringSet({"*"}))
return OutputsSpec::All {};
else else
outputsSpec = names; return OutputsSpec::Names { std::move(names) };
}
void adl_serializer<OutputsSpec>::to_json(json & json, OutputsSpec t) {
std::visit(overloaded {
[&](const OutputsSpec::All &) {
json = std::vector<std::string>({"*"});
},
[&](const OutputsSpec::Names & names) {
json = names;
},
}, t.raw());
}
ExtendedOutputsSpec adl_serializer<ExtendedOutputsSpec>::from_json(const json & json) {
if (json.is_null())
return ExtendedOutputsSpec::Default {};
else {
return ExtendedOutputsSpec::Explicit { json.get<OutputsSpec>() };
} }
} }
void adl_serializer<ExtendedOutputsSpec>::to_json(json & json, ExtendedOutputsSpec t) {
std::visit(overloaded {
[&](const ExtendedOutputsSpec::Default &) {
json = nullptr;
},
[&](const ExtendedOutputsSpec::Explicit & e) {
adl_serializer<OutputsSpec>::to_json(json, e);
},
}, t.raw());
}
} }

View file

@ -1,32 +1,95 @@
#pragma once #pragma once
#include <cassert>
#include <optional>
#include <set>
#include <variant> #include <variant>
#include "util.hh" #include "json-impls.hh"
#include "nlohmann/json_fwd.hpp"
namespace nix { namespace nix {
typedef std::set<std::string> OutputNames; struct OutputNames : std::set<std::string> {
using std::set<std::string>::set;
struct AllOutputs { /* These need to be "inherited manually" */
bool operator < (const AllOutputs & _) const { return false; }
OutputNames(const std::set<std::string> & s)
: std::set<std::string>(s)
{ assert(!empty()); }
OutputNames(std::set<std::string> && s)
: std::set<std::string>(s)
{ assert(!empty()); }
/* This set should always be non-empty, so we delete this
constructor in order make creating empty ones by mistake harder.
*/
OutputNames() = delete;
}; };
struct DefaultOutputs { struct AllOutputs : std::monostate { };
bool operator < (const DefaultOutputs & _) const { return false; }
typedef std::variant<AllOutputs, OutputNames> _OutputsSpecRaw;
struct OutputsSpec : _OutputsSpecRaw {
using Raw = _OutputsSpecRaw;
using Raw::Raw;
/* Force choosing a variant */
OutputsSpec() = delete;
using Names = OutputNames;
using All = AllOutputs;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
inline Raw & raw() {
return static_cast<Raw &>(*this);
}
bool contains(const std::string & output) const;
/* Create a new OutputsSpec which is the union of this and that. */
OutputsSpec union_(const OutputsSpec & that) const;
/* Whether this OutputsSpec is a subset of that. */
bool isSubsetOf(const OutputsSpec & outputs) const;
/* Parse a string of the form 'output1,...outputN' or
'*', returning the outputs spec. */
static OutputsSpec parse(std::string_view s);
static std::optional<OutputsSpec> parseOpt(std::string_view s);
std::string to_string() const;
}; };
typedef std::variant<DefaultOutputs, AllOutputs, OutputNames> OutputsSpec; struct DefaultOutputs : std::monostate { };
/* Parse a string of the form 'prefix^output1,...outputN' or typedef std::variant<DefaultOutputs, OutputsSpec> _ExtendedOutputsSpecRaw;
'prefix^*', returning the prefix and the outputs spec. */
std::pair<std::string, OutputsSpec> parseOutputsSpec(const std::string & s);
std::string printOutputsSpec(const OutputsSpec & outputsSpec); struct ExtendedOutputsSpec : _ExtendedOutputsSpecRaw {
using Raw = _ExtendedOutputsSpecRaw;
using Raw::Raw;
void to_json(nlohmann::json &, const OutputsSpec &); using Default = DefaultOutputs;
void from_json(const nlohmann::json &, OutputsSpec &); using Explicit = OutputsSpec;
inline const Raw & raw() const {
return static_cast<const Raw &>(*this);
}
/* Parse a string of the form 'prefix^output1,...outputN' or
'prefix^*', returning the prefix and the extended outputs spec. */
static std::pair<std::string_view, ExtendedOutputsSpec> parse(std::string_view s);
static std::optional<std::pair<std::string_view, ExtendedOutputsSpec>> parseOpt(std::string_view s);
std::string to_string() const;
};
} }
JSON_IMPL(OutputsSpec)
JSON_IMPL(ExtendedOutputsSpec)

View file

@ -3,6 +3,80 @@
namespace nix { namespace nix {
std::string ValidPathInfo::fingerprint(const Store & store) const
{
if (narSize == 0)
throw Error("cannot calculate fingerprint of path '%s' because its size is not known",
store.printStorePath(path));
return
"1;" + store.printStorePath(path) + ";"
+ narHash.to_string(Base32, true) + ";"
+ std::to_string(narSize) + ";"
+ concatStringsSep(",", store.printStorePathSet(references));
}
void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey)
{
sigs.insert(secretKey.signDetached(fingerprint(store)));
}
bool ValidPathInfo::isContentAddressed(const Store & store) const
{
if (! ca) return false;
auto caPath = std::visit(overloaded {
[&](const TextHash & th) {
return store.makeTextPath(path.name(), th.hash, references);
},
[&](const FixedOutputHash & fsh) {
auto refs = references;
bool hasSelfReference = false;
if (refs.count(path)) {
hasSelfReference = true;
refs.erase(path);
}
return store.makeFixedOutputPath(fsh.method, fsh.hash, path.name(), refs, hasSelfReference);
}
}, *ca);
bool res = caPath == path;
if (!res)
printError("warning: path '%s' claims to be content-addressed but isn't", store.printStorePath(path));
return res;
}
size_t ValidPathInfo::checkSignatures(const Store & store, const PublicKeys & publicKeys) const
{
if (isContentAddressed(store)) return maxSigs;
size_t good = 0;
for (auto & sig : sigs)
if (checkSignature(store, publicKeys, sig))
good++;
return good;
}
bool ValidPathInfo::checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const
{
return verifyDetached(fingerprint(store), sig, publicKeys);
}
Strings ValidPathInfo::shortRefs() const
{
Strings refs;
for (auto & r : references)
refs.push_back(std::string(r.to_string()));
return refs;
}
ValidPathInfo ValidPathInfo::read(Source & source, const Store & store, unsigned int format) ValidPathInfo ValidPathInfo::read(Source & source, const Store & store, unsigned int format)
{ {
return read(source, store, format, store.parseStorePath(readString(source))); return read(source, store, format, store.parseStorePath(readString(source)));
@ -24,6 +98,7 @@ ValidPathInfo ValidPathInfo::read(Source & source, const Store & store, unsigned
return info; return info;
} }
void ValidPathInfo::write( void ValidPathInfo::write(
Sink & sink, Sink & sink,
const Store & store, const Store & store,

View file

@ -0,0 +1,7 @@
#pragma once
namespace nix {
static constexpr std::string_view nameRegexStr = R"([0-9a-zA-Z\+\-\._\?=]+)";
}

View file

@ -15,10 +15,14 @@ std::string StorePathWithOutputs::to_string(const Store & store) const
DerivedPath StorePathWithOutputs::toDerivedPath() const DerivedPath StorePathWithOutputs::toDerivedPath() const
{ {
if (!outputs.empty() || path.isDerivation()) if (!outputs.empty()) {
return DerivedPath::Built { path, outputs }; return DerivedPath::Built { path, OutputsSpec::Names { outputs } };
else } else if (path.isDerivation()) {
assert(outputs.empty());
return DerivedPath::Built { path, OutputsSpec::All { } };
} else {
return DerivedPath::Opaque { path }; return DerivedPath::Opaque { path };
}
} }
@ -41,7 +45,18 @@ std::variant<StorePathWithOutputs, StorePath> StorePathWithOutputs::tryFromDeriv
return StorePathWithOutputs { bo.path }; return StorePathWithOutputs { bo.path };
}, },
[&](const DerivedPath::Built & bfd) -> std::variant<StorePathWithOutputs, StorePath> { [&](const DerivedPath::Built & bfd) -> std::variant<StorePathWithOutputs, StorePath> {
return StorePathWithOutputs { bfd.drvPath, bfd.outputs }; return StorePathWithOutputs {
.path = bfd.drvPath,
// Use legacy encoding of wildcard as empty set
.outputs = std::visit(overloaded {
[&](const OutputsSpec::All &) -> StringSet {
return {};
},
[&](const OutputsSpec::Names & outputs) {
return static_cast<StringSet>(outputs);
},
}, bfd.outputs.raw()),
};
}, },
}, p.raw()); }, p.raw());
} }
@ -52,8 +67,8 @@ std::pair<std::string_view, StringSet> parsePathWithOutputs(std::string_view s)
size_t n = s.find("!"); size_t n = s.find("!");
return n == s.npos return n == s.npos
? std::make_pair(s, std::set<std::string>()) ? std::make_pair(s, std::set<std::string>())
: std::make_pair(((std::string_view) s).substr(0, n), : std::make_pair(s.substr(0, n),
tokenizeString<std::set<std::string>>(((std::string_view) s).substr(n + 1), ",")); tokenizeString<std::set<std::string>>(s.substr(n + 1), ","));
} }

View file

@ -2,7 +2,6 @@
#include "path.hh" #include "path.hh"
#include "derived-path.hh" #include "derived-path.hh"
#include "nlohmann/json_fwd.hpp"
namespace nix { namespace nix {

View file

@ -8,8 +8,10 @@ static void checkName(std::string_view path, std::string_view name)
{ {
if (name.empty()) if (name.empty())
throw BadStorePath("store path '%s' has an empty name", path); throw BadStorePath("store path '%s' has an empty name", path);
if (name.size() > 211) if (name.size() > StorePath::MaxPathLen)
throw BadStorePath("store path '%s' has a name longer than 211 characters", path); throw BadStorePath("store path '%s' has a name longer than '%d characters",
StorePath::MaxPathLen, path);
// See nameRegexStr for the definition
for (auto c : name) for (auto c : name)
if (!((c >= '0' && c <= '9') if (!((c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'z') || (c >= 'a' && c <= 'z')

View file

@ -5,7 +5,6 @@
namespace nix { namespace nix {
class Store;
struct Hash; struct Hash;
class StorePath class StorePath
@ -17,6 +16,8 @@ public:
/* Size of the hash part of store paths, in base-32 characters. */ /* Size of the hash part of store paths, in base-32 characters. */
constexpr static size_t HashLen = 32; // i.e. 160 bits constexpr static size_t HashLen = 32; // i.e. 160 bits
constexpr static size_t MaxPathLen = 211;
StorePath() = delete; StorePath() = delete;
StorePath(std::string_view baseName); StorePath(std::string_view baseName);
@ -64,7 +65,6 @@ public:
typedef std::set<StorePath> StorePathSet; typedef std::set<StorePath> StorePathSet;
typedef std::vector<StorePath> StorePaths; typedef std::vector<StorePath> StorePaths;
typedef std::map<std::string, StorePath> OutputPathMap;
typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap; typedef std::map<StorePath, std::optional<ContentAddress>> StorePathCAMap;

View file

@ -7,6 +7,8 @@
namespace nix { namespace nix {
class Store;
struct DrvOutput { struct DrvOutput {
// The hash modulo of the derivation // The hash modulo of the derivation
Hash drvHash; Hash drvHash;

View file

@ -867,8 +867,8 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
OutputPathMap outputs; OutputPathMap outputs;
auto drv = evalStore->readDerivation(bfd.drvPath); auto drv = evalStore->readDerivation(bfd.drvPath);
const auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive const auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
const auto drvOutputs = drv.outputsAndOptPaths(*this); auto built = resolveDerivedPath(*this, bfd, &*evalStore);
for (auto & output : bfd.outputs) { for (auto & [output, outputPath] : built) {
auto outputHash = get(outputHashes, output); auto outputHash = get(outputHashes, output);
if (!outputHash) if (!outputHash)
throw Error( throw Error(
@ -882,16 +882,11 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
throw MissingRealisation(outputId); throw MissingRealisation(outputId);
res.builtOutputs.emplace(realisation->id, *realisation); res.builtOutputs.emplace(realisation->id, *realisation);
} else { } else {
// If ca-derivations isn't enabled, assume that
// the output path is statically known.
const auto drvOutput = get(drvOutputs, output);
assert(drvOutput);
assert(drvOutput->second);
res.builtOutputs.emplace( res.builtOutputs.emplace(
outputId, outputId,
Realisation { Realisation {
.id = outputId, .id = outputId,
.outPath = *drvOutput->second, .outPath = outputPath,
}); });
} }
} }
@ -915,7 +910,12 @@ BuildResult RemoteStore::buildDerivation(const StorePath & drvPath, const BasicD
writeDerivation(conn->to, *this, drv); writeDerivation(conn->to, *this, drv);
conn->to << buildMode; conn->to << buildMode;
conn.processStderr(); conn.processStderr();
BuildResult res { .path = DerivedPath::Built { .drvPath = drvPath } }; BuildResult res {
.path = DerivedPath::Built {
.drvPath = drvPath,
.outputs = OutputsSpec::All { },
},
};
res.status = (BuildResult::Status) readInt(conn->from); res.status = (BuildResult::Status) readInt(conn->from);
conn->from >> res.errorMsg; conn->from >> res.errorMsg;
if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 29) { if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 29) {

View file

@ -53,8 +53,8 @@ public:
{ return false; } { return false; }
// FIXME extend daemon protocol, move implementation to RemoteStore // FIXME extend daemon protocol, move implementation to RemoteStore
std::optional<std::string> getBuildLog(const StorePath & path) override std::optional<std::string> getBuildLogExact(const StorePath & path) override
{ unsupported("getBuildLog"); } { unsupported("getBuildLogExact"); }
private: private:

View file

@ -1214,79 +1214,6 @@ std::string showPaths(const PathSet & paths)
} }
std::string ValidPathInfo::fingerprint(const Store & store) const
{
if (narSize == 0)
throw Error("cannot calculate fingerprint of path '%s' because its size is not known",
store.printStorePath(path));
return
"1;" + store.printStorePath(path) + ";"
+ narHash.to_string(Base32, true) + ";"
+ std::to_string(narSize) + ";"
+ concatStringsSep(",", store.printStorePathSet(references));
}
void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey)
{
sigs.insert(secretKey.signDetached(fingerprint(store)));
}
bool ValidPathInfo::isContentAddressed(const Store & store) const
{
if (! ca) return false;
auto caPath = std::visit(overloaded {
[&](const TextHash & th) {
return store.makeTextPath(path.name(), th.hash, references);
},
[&](const FixedOutputHash & fsh) {
auto refs = references;
bool hasSelfReference = false;
if (refs.count(path)) {
hasSelfReference = true;
refs.erase(path);
}
return store.makeFixedOutputPath(fsh.method, fsh.hash, path.name(), refs, hasSelfReference);
}
}, *ca);
bool res = caPath == path;
if (!res)
printError("warning: path '%s' claims to be content-addressed but isn't", store.printStorePath(path));
return res;
}
size_t ValidPathInfo::checkSignatures(const Store & store, const PublicKeys & publicKeys) const
{
if (isContentAddressed(store)) return maxSigs;
size_t good = 0;
for (auto & sig : sigs)
if (checkSignature(store, publicKeys, sig))
good++;
return good;
}
bool ValidPathInfo::checkSignature(const Store & store, const PublicKeys & publicKeys, const std::string & sig) const
{
return verifyDetached(fingerprint(store), sig, publicKeys);
}
Strings ValidPathInfo::shortRefs() const
{
Strings refs;
for (auto & r : references)
refs.push_back(std::string(r.to_string()));
return refs;
}
Derivation Store::derivationFromPath(const StorePath & drvPath) Derivation Store::derivationFromPath(const StorePath & drvPath)
{ {
ensurePath(drvPath); ensurePath(drvPath);
@ -1305,6 +1232,34 @@ Derivation readDerivationCommon(Store& store, const StorePath& drvPath, bool req
} }
} }
std::optional<StorePath> Store::getBuildDerivationPath(const StorePath & path)
{
if (!path.isDerivation()) {
try {
auto info = queryPathInfo(path);
if (!info->deriver) return std::nullopt;
return *info->deriver;
} catch (InvalidPath &) {
return std::nullopt;
}
}
if (!settings.isExperimentalFeatureEnabled(Xp::CaDerivations) || !isValidPath(path))
return path;
auto drv = readDerivation(path);
if (!drv.type().hasKnownOutputPaths()) {
// The build log is actually attached to the corresponding
// resolved derivation, so we need to get it first
auto resolvedDrv = drv.tryResolve(*this);
if (resolvedDrv)
return writeDerivation(*this, *resolvedDrv, NoRepair, true);
}
return path;
}
Derivation Store::readDerivation(const StorePath & drvPath) Derivation Store::readDerivation(const StorePath & drvPath)
{ return readDerivationCommon(*this, drvPath, true); } { return readDerivationCommon(*this, drvPath, true); }

View file

@ -71,6 +71,9 @@ class NarInfoDiskCache;
class Store; class Store;
typedef std::map<std::string, StorePath> OutputPathMap;
enum CheckSigsFlag : bool { NoCheckSigs = false, CheckSigs = true }; enum CheckSigsFlag : bool { NoCheckSigs = false, CheckSigs = true };
enum SubstituteFlag : bool { NoSubstitute = false, Substitute = true }; enum SubstituteFlag : bool { NoSubstitute = false, Substitute = true };
enum AllowInvalidFlag : bool { DisallowInvalid = false, AllowInvalid = true }; enum AllowInvalidFlag : bool { DisallowInvalid = false, AllowInvalid = true };
@ -120,6 +123,8 @@ public:
typedef std::map<std::string, std::string> Params; typedef std::map<std::string, std::string> Params;
protected: protected:
struct PathInfoCacheValue { struct PathInfoCacheValue {
@ -620,6 +625,13 @@ public:
*/ */
StorePathSet exportReferences(const StorePathSet & storePaths, const StorePathSet & inputPaths); StorePathSet exportReferences(const StorePathSet & storePaths, const StorePathSet & inputPaths);
/**
* Given a store path, return the realisation actually used in the realisation of this path:
* - If the path is a content-addressed derivation, try to resolve it
* - Otherwise, find one of its derivers
*/
std::optional<StorePath> getBuildDerivationPath(const StorePath &);
/* Hack to allow long-running processes like hydra-queue-runner to /* Hack to allow long-running processes like hydra-queue-runner to
occasionally flush their path info cache. */ occasionally flush their path info cache. */
void clearPathInfoCache() void clearPathInfoCache()
@ -721,6 +733,11 @@ void copyClosure(
void removeTempRoots(); void removeTempRoots();
/* Resolve the derived path completely, failing if any derivation output
is unknown. */
OutputPathMap resolveDerivedPath(Store &, const DerivedPath::Built &, Store * evalStore = nullptr);
/* Return a Store object to access the Nix store denoted by /* Return a Store object to access the Nix store denoted by
uri (slight misnomer...). Supported values are: uri (slight misnomer...). Supported values are:

View file

@ -0,0 +1,23 @@
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "store-api.hh"
namespace nix {
class LibStoreTest : public ::testing::Test {
public:
static void SetUpTestSuite() {
initLibStore();
}
protected:
LibStoreTest()
: store(openStore("dummy://"))
{ }
ref<Store> store;
};
} /* namespace nix */

View file

@ -12,4 +12,4 @@ libstore-tests_CXXFLAGS += -I src/libstore -I src/libutil
libstore-tests_LIBS = libstore libutil libstore-tests_LIBS = libstore libutil
libstore-tests_LDFLAGS := $(GTEST_LIBS) libstore-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS)

View file

@ -1,46 +1,201 @@
#include "outputs-spec.hh" #include "outputs-spec.hh"
#include <nlohmann/json.hpp>
#include <gtest/gtest.h> #include <gtest/gtest.h>
namespace nix { namespace nix {
TEST(parseOutputsSpec, basic) #ifndef NDEBUG
{ TEST(OutputsSpec, no_empty_names) {
{ ASSERT_DEATH(OutputsSpec::Names { std::set<std::string> { } }, "");
auto [prefix, outputsSpec] = parseOutputsSpec("foo"); }
ASSERT_EQ(prefix, "foo"); #endif
ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
#define TEST_DONT_PARSE(NAME, STR) \
TEST(OutputsSpec, bad_ ## NAME) { \
std::optional OutputsSpecOpt = \
OutputsSpec::parseOpt(STR); \
ASSERT_FALSE(OutputsSpecOpt); \
} }
{ TEST_DONT_PARSE(empty, "")
auto [prefix, outputsSpec] = parseOutputsSpec("foo^*"); TEST_DONT_PARSE(garbage, "&*()")
ASSERT_EQ(prefix, "foo"); TEST_DONT_PARSE(double_star, "**")
ASSERT_TRUE(std::get_if<AllOutputs>(&outputsSpec)); TEST_DONT_PARSE(star_first, "*,foo")
TEST_DONT_PARSE(star_second, "foo,*")
#undef TEST_DONT_PARSE
TEST(OutputsSpec, all) {
std::string_view str = "*";
OutputsSpec expected = OutputsSpec::All { };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_out) {
std::string_view str = "out";
OutputsSpec expected = OutputsSpec::Names { "out" };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_underscore) {
std::string_view str = "a_b";
OutputsSpec expected = OutputsSpec::Names { "a_b" };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_numberic) {
std::string_view str = "01";
OutputsSpec expected = OutputsSpec::Names { "01" };
ASSERT_EQ(OutputsSpec::parse(str), expected);
ASSERT_EQ(expected.to_string(), str);
}
TEST(OutputsSpec, names_out_bin) {
OutputsSpec expected = OutputsSpec::Names { "out", "bin" };
ASSERT_EQ(OutputsSpec::parse("out,bin"), expected);
// N.B. This normalization is OK.
ASSERT_EQ(expected.to_string(), "bin,out");
}
#define TEST_SUBSET(X, THIS, THAT) \
X((OutputsSpec { THIS }).isSubsetOf(THAT));
TEST(OutputsSpec, subsets_all_all) {
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::All { }, OutputsSpec::All { });
}
TEST(OutputsSpec, subsets_names_all) {
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, OutputsSpec::All { });
}
TEST(OutputsSpec, subsets_names_names_eq) {
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, OutputsSpec::Names { "a" });
}
TEST(OutputsSpec, subsets_names_names_noneq) {
TEST_SUBSET(ASSERT_TRUE, OutputsSpec::Names { "a" }, (OutputsSpec::Names { "a", "b" }));
}
TEST(OutputsSpec, not_subsets_all_names) {
TEST_SUBSET(ASSERT_FALSE, OutputsSpec::All { }, OutputsSpec::Names { "a" });
}
TEST(OutputsSpec, not_subsets_names_names) {
TEST_SUBSET(ASSERT_FALSE, (OutputsSpec::Names { "a", "b" }), (OutputsSpec::Names { "a" }));
}
#undef TEST_SUBSET
#define TEST_UNION(RES, THIS, THAT) \
ASSERT_EQ(OutputsSpec { RES }, (OutputsSpec { THIS }).union_(THAT));
TEST(OutputsSpec, union_all_all) {
TEST_UNION(OutputsSpec::All { }, OutputsSpec::All { }, OutputsSpec::All { });
}
TEST(OutputsSpec, union_all_names) {
TEST_UNION(OutputsSpec::All { }, OutputsSpec::All { }, OutputsSpec::Names { "a" });
}
TEST(OutputsSpec, union_names_all) {
TEST_UNION(OutputsSpec::All { }, OutputsSpec::Names { "a" }, OutputsSpec::All { });
}
TEST(OutputsSpec, union_names_names) {
TEST_UNION((OutputsSpec::Names { "a", "b" }), OutputsSpec::Names { "a" }, OutputsSpec::Names { "b" });
}
#undef TEST_UNION
#define TEST_DONT_PARSE(NAME, STR) \
TEST(ExtendedOutputsSpec, bad_ ## NAME) { \
std::optional extendedOutputsSpecOpt = \
ExtendedOutputsSpec::parseOpt(STR); \
ASSERT_FALSE(extendedOutputsSpecOpt); \
} }
{ TEST_DONT_PARSE(carot_empty, "^")
auto [prefix, outputsSpec] = parseOutputsSpec("foo^out"); TEST_DONT_PARSE(prefix_carot_empty, "foo^")
ASSERT_EQ(prefix, "foo"); TEST_DONT_PARSE(garbage, "^&*()")
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out"})); TEST_DONT_PARSE(double_star, "^**")
} TEST_DONT_PARSE(star_first, "^*,foo")
TEST_DONT_PARSE(star_second, "^foo,*")
{ #undef TEST_DONT_PARSE
auto [prefix, outputsSpec] = parseOutputsSpec("foo^out,bin");
ASSERT_EQ(prefix, "foo");
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"}));
}
{ TEST(ExtendedOutputsSpec, defeault) {
auto [prefix, outputsSpec] = parseOutputsSpec("foo^bar^out,bin"); std::string_view str = "foo";
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
ASSERT_EQ(prefix, "foo");
ExtendedOutputsSpec expected = ExtendedOutputsSpec::Default { };
ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
}
TEST(ExtendedOutputsSpec, all) {
std::string_view str = "foo^*";
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
ASSERT_EQ(prefix, "foo");
ExtendedOutputsSpec expected = OutputsSpec::All { };
ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
}
TEST(ExtendedOutputsSpec, out) {
std::string_view str = "foo^out";
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(str);
ASSERT_EQ(prefix, "foo");
ExtendedOutputsSpec expected = OutputsSpec::Names { "out" };
ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), str);
}
TEST(ExtendedOutputsSpec, out_bin) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse("foo^out,bin");
ASSERT_EQ(prefix, "foo");
ExtendedOutputsSpec expected = OutputsSpec::Names { "out", "bin" };
ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), "foo^bin,out");
}
TEST(ExtendedOutputsSpec, many_carrot) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse("foo^bar^out,bin");
ASSERT_EQ(prefix, "foo^bar"); ASSERT_EQ(prefix, "foo^bar");
ASSERT_TRUE(std::get<OutputNames>(outputsSpec) == OutputNames({"out", "bin"})); ExtendedOutputsSpec expected = OutputsSpec::Names { "out", "bin" };
} ASSERT_EQ(extendedOutputsSpec, expected);
ASSERT_EQ(std::string { prefix } + expected.to_string(), "foo^bar^bin,out");
{
auto [prefix, outputsSpec] = parseOutputsSpec("foo^&*()");
ASSERT_EQ(prefix, "foo^&*()");
ASSERT_TRUE(std::get_if<DefaultOutputs>(&outputsSpec));
}
} }
#define TEST_JSON(TYPE, NAME, STR, VAL) \
\
TEST(TYPE, NAME ## _to_json) { \
using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \
STR ## _json, \
((nlohmann::json) TYPE { VAL })); \
} \
\
TEST(TYPE, NAME ## _from_json) { \
using nlohmann::literals::operator "" _json; \
ASSERT_EQ( \
TYPE { VAL }, \
(STR ## _json).get<TYPE>()); \
}
TEST_JSON(OutputsSpec, all, R"(["*"])", OutputsSpec::All { })
TEST_JSON(OutputsSpec, name, R"(["a"])", OutputsSpec::Names { "a" })
TEST_JSON(OutputsSpec, names, R"(["a","b"])", (OutputsSpec::Names { "a", "b" }))
TEST_JSON(ExtendedOutputsSpec, def, R"(null)", ExtendedOutputsSpec::Default { })
TEST_JSON(ExtendedOutputsSpec, all, R"(["*"])", ExtendedOutputsSpec::Explicit { OutputsSpec::All { } })
TEST_JSON(ExtendedOutputsSpec, name, R"(["a"])", ExtendedOutputsSpec::Explicit { OutputsSpec::Names { "a" } })
TEST_JSON(ExtendedOutputsSpec, names, R"(["a","b"])", (ExtendedOutputsSpec::Explicit { OutputsSpec::Names { "a", "b" } }))
#undef TEST_JSON
} }

144
src/libstore/tests/path.cc Normal file
View file

@ -0,0 +1,144 @@
#include <regex>
#include <nlohmann/json.hpp>
#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
#include "path-regex.hh"
#include "store-api.hh"
#include "libstoretests.hh"
namespace nix {
#define STORE_DIR "/nix/store/"
#define HASH_PART "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q"
class StorePathTest : public LibStoreTest
{
};
static std::regex nameRegex { std::string { nameRegexStr } };
#define TEST_DONT_PARSE(NAME, STR) \
TEST_F(StorePathTest, bad_ ## NAME) { \
std::string_view str = \
STORE_DIR HASH_PART "-" STR; \
ASSERT_THROW( \
store->parseStorePath(str), \
BadStorePath); \
std::string name { STR }; \
EXPECT_FALSE(std::regex_match(name, nameRegex)); \
}
TEST_DONT_PARSE(empty, "")
TEST_DONT_PARSE(garbage, "&*()")
TEST_DONT_PARSE(double_star, "**")
TEST_DONT_PARSE(star_first, "*,foo")
TEST_DONT_PARSE(star_second, "foo,*")
TEST_DONT_PARSE(bang, "foo!o")
#undef TEST_DONT_PARSE
#define TEST_DO_PARSE(NAME, STR) \
TEST_F(StorePathTest, good_ ## NAME) { \
std::string_view str = \
STORE_DIR HASH_PART "-" STR; \
auto p = store->parseStorePath(str); \
std::string name { p.name() }; \
EXPECT_TRUE(std::regex_match(name, nameRegex)); \
}
// 0-9 a-z A-Z + - . _ ? =
TEST_DO_PARSE(numbers, "02345")
TEST_DO_PARSE(lower_case, "foo")
TEST_DO_PARSE(upper_case, "FOO")
TEST_DO_PARSE(plus, "foo+bar")
TEST_DO_PARSE(dash, "foo-dev")
TEST_DO_PARSE(underscore, "foo_bar")
TEST_DO_PARSE(period, "foo.txt")
TEST_DO_PARSE(question_mark, "foo?why")
TEST_DO_PARSE(equals_sign, "foo=foo")
#undef TEST_DO_PARSE
// For rapidcheck
void showValue(const StorePath & p, std::ostream & os) {
os << p.to_string();
}
}
namespace rc {
using namespace nix;
template<>
struct Arbitrary<StorePath> {
static Gen<StorePath> arbitrary();
};
Gen<StorePath> Arbitrary<StorePath>::arbitrary()
{
auto len = *gen::inRange<size_t>(1, StorePath::MaxPathLen);
std::string pre { HASH_PART "-" };
pre.reserve(pre.size() + len);
for (size_t c = 0; c < len; ++c) {
switch (auto i = *gen::inRange<uint8_t>(0, 10 + 2 * 26 + 6)) {
case 0 ... 9:
pre += '0' + i;
case 10 ... 35:
pre += 'A' + (i - 10);
break;
case 36 ... 61:
pre += 'a' + (i - 36);
break;
case 62:
pre += '+';
break;
case 63:
pre += '-';
break;
case 64:
pre += '.';
break;
case 65:
pre += '_';
break;
case 66:
pre += '?';
break;
case 67:
pre += '=';
break;
default:
assert(false);
}
}
return gen::just(StorePath { pre });
}
} // namespace rc
namespace nix {
RC_GTEST_FIXTURE_PROP(
StorePathTest,
prop_regex_accept,
(const StorePath & p))
{
RC_ASSERT(std::regex_match(std::string { p.name() }, nameRegex));
}
RC_GTEST_FIXTURE_PROP(
StorePathTest,
prop_round_rip,
(const StorePath & p))
{
RC_ASSERT(p == store->parseStorePath(store->printStorePath(p)));
}
}

View file

@ -74,6 +74,8 @@ struct AbstractPos
virtual void print(std::ostream & out) const = 0; virtual void print(std::ostream & out) const = 0;
std::optional<LinesOfCode> getCodeLines() const; std::optional<LinesOfCode> getCodeLines() const;
virtual ~AbstractPos() = default;
}; };
std::ostream & operator << (std::ostream & str, const AbstractPos & pos); std::ostream & operator << (std::ostream & str, const AbstractPos & pos);

14
src/libutil/json-impls.hh Normal file
View file

@ -0,0 +1,14 @@
#pragma once
#include "nlohmann/json_fwd.hpp"
// Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
#define JSON_IMPL(TYPE) \
namespace nlohmann { \
using namespace nix; \
template <> \
struct adl_serializer<TYPE> { \
static TYPE from_json(const json & json); \
static void to_json(json & json, TYPE t); \
}; \
}

View file

@ -25,11 +25,17 @@ public:
/* Wait indefinitely until a POLLHUP occurs. */ /* Wait indefinitely until a POLLHUP occurs. */
struct pollfd fds[1]; struct pollfd fds[1];
fds[0].fd = fd; fds[0].fd = fd;
/* This shouldn't be necessary, but macOS doesn't seem to /* Polling for no specific events (i.e. just waiting
like a zeroed out events field. for an error/hangup) doesn't work on macOS
See rdar://37537852. anymore. So wait for read events and ignore
*/ them. */
fds[0].events = POLLHUP; fds[0].events =
#ifdef __APPLE__
POLLRDNORM
#else
0
#endif
;
auto count = poll(fds, 1, -1); auto count = poll(fds, 1, -1);
if (count == -1) abort(); // can't happen if (count == -1) abort(); // can't happen
/* This shouldn't happen, but can on macOS due to a bug. /* This shouldn't happen, but can on macOS due to a bug.
@ -40,10 +46,15 @@ public:
too harmful. too harmful.
*/ */
if (count == 0) continue; if (count == 0) continue;
assert(fds[0].revents & POLLHUP); if (fds[0].revents & POLLHUP) {
triggerInterrupt(); triggerInterrupt();
break; break;
} }
/* This will only happen on macOS. We sleep a bit to
avoid waking up too often if the client is sending
input. */
sleep(1);
}
}); });
}; };

View file

@ -0,0 +1,30 @@
#pragma once
#include <string_view>
namespace nix::regex {
// TODO use constexpr string building like
// https://github.com/akrzemi1/static_string/blob/master/include/ak_toolkit/static_string.hpp
static inline std::string either(std::string_view a, std::string_view b)
{
return std::string { a } + "|" + b;
}
static inline std::string group(std::string_view a)
{
return std::string { "(" } + a + ")";
}
static inline std::string many(std::string_view a)
{
return std::string { "(?:" } + a + ")*";
}
static inline std::string list(std::string_view a)
{
return std::string { a } + many(group("," + a));
}
}

View file

@ -402,7 +402,7 @@ static void main_nix_build(int argc, char * * argv)
auto bashDrv = drv->requireDrvPath(); auto bashDrv = drv->requireDrvPath();
pathsToBuild.push_back(DerivedPath::Built { pathsToBuild.push_back(DerivedPath::Built {
.drvPath = bashDrv, .drvPath = bashDrv,
.outputs = {"out"}, .outputs = OutputsSpec::Names {"out"},
}); });
pathsToCopy.insert(bashDrv); pathsToCopy.insert(bashDrv);
shellDrv = bashDrv; shellDrv = bashDrv;
@ -426,7 +426,7 @@ static void main_nix_build(int argc, char * * argv)
{ {
pathsToBuild.push_back(DerivedPath::Built { pathsToBuild.push_back(DerivedPath::Built {
.drvPath = inputDrv, .drvPath = inputDrv,
.outputs = inputOutputs .outputs = OutputsSpec::Names { inputOutputs },
}); });
pathsToCopy.insert(inputDrv); pathsToCopy.insert(inputDrv);
} }
@ -596,7 +596,7 @@ static void main_nix_build(int argc, char * * argv)
if (outputName == "") if (outputName == "")
throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath)); throw Error("derivation '%s' lacks an 'outputName' attribute", store->printStorePath(drvPath));
pathsToBuild.push_back(DerivedPath::Built{drvPath, {outputName}}); pathsToBuild.push_back(DerivedPath::Built{drvPath, OutputsSpec::Names{outputName}});
pathsToBuildOrdered.push_back({drvPath, {outputName}}); pathsToBuildOrdered.push_back({drvPath, {outputName}});
drvsToCopy.insert(drvPath); drvsToCopy.insert(drvPath);

View file

@ -480,9 +480,14 @@ static void printMissing(EvalState & state, DrvInfos & elems)
std::vector<DerivedPath> targets; std::vector<DerivedPath> targets;
for (auto & i : elems) for (auto & i : elems)
if (auto drvPath = i.queryDrvPath()) if (auto drvPath = i.queryDrvPath())
targets.push_back(DerivedPath::Built{*drvPath}); targets.push_back(DerivedPath::Built{
.drvPath = *drvPath,
.outputs = OutputsSpec::All { },
});
else else
targets.push_back(DerivedPath::Opaque{i.queryOutPath()}); targets.push_back(DerivedPath::Opaque{
.path = i.queryOutPath(),
});
printMissing(state.store, targets); printMissing(state.store, targets);
} }
@ -753,8 +758,13 @@ static void opSet(Globals & globals, Strings opFlags, Strings opArgs)
auto drvPath = drv.queryDrvPath(); auto drvPath = drv.queryDrvPath();
std::vector<DerivedPath> paths { std::vector<DerivedPath> paths {
drvPath drvPath
? (DerivedPath) (DerivedPath::Built { *drvPath }) ? (DerivedPath) (DerivedPath::Built {
: (DerivedPath) (DerivedPath::Opaque { drv.queryOutPath() }), .drvPath = *drvPath,
.outputs = OutputsSpec::All { },
})
: (DerivedPath) (DerivedPath::Opaque {
.path = drv.queryOutPath(),
}),
}; };
printMissing(globals.state->store, paths); printMissing(globals.state->store, paths);
if (globals.dryRun) return; if (globals.dryRun) return;

View file

@ -86,13 +86,13 @@ UnresolvedApp Installable::toApp(EvalState & state)
/* We want all outputs of the drv */ /* We want all outputs of the drv */
return DerivedPath::Built { return DerivedPath::Built {
.drvPath = d.drvPath, .drvPath = d.drvPath,
.outputs = {}, .outputs = OutputsSpec::All {},
}; };
}, },
[&](const NixStringContextElem::Built & b) -> DerivedPath { [&](const NixStringContextElem::Built & b) -> DerivedPath {
return DerivedPath::Built { return DerivedPath::Built {
.drvPath = b.drvPath, .drvPath = b.drvPath,
.outputs = { b.output }, .outputs = OutputsSpec::Names { b.output },
}; };
}, },
[&](const NixStringContextElem::Opaque & o) -> DerivedPath { [&](const NixStringContextElem::Opaque & o) -> DerivedPath {
@ -127,7 +127,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
return UnresolvedApp { App { return UnresolvedApp { App {
.context = { DerivedPath::Built { .context = { DerivedPath::Built {
.drvPath = drvPath, .drvPath = drvPath,
.outputs = {outputName}, .outputs = OutputsSpec::Names { outputName },
} }, } },
.program = program, .program = program,
}}; }};

View file

@ -75,10 +75,10 @@ struct CmdBundle : InstallableCommand
auto val = installable->toValue(*evalState).first; auto val = installable->toValue(*evalState).first;
auto [bundlerFlakeRef, bundlerName, outputsSpec] = parseFlakeRefWithFragmentAndOutputsSpec(bundler, absPath(".")); auto [bundlerFlakeRef, bundlerName, extendedOutputsSpec] = parseFlakeRefWithFragmentAndExtendedOutputsSpec(bundler, absPath("."));
const flake::LockFlags lockFlags{ .writeLockFile = false }; const flake::LockFlags lockFlags{ .writeLockFile = false };
InstallableFlake bundler{this, InstallableFlake bundler{this,
evalState, std::move(bundlerFlakeRef), bundlerName, outputsSpec, evalState, std::move(bundlerFlakeRef), bundlerName, extendedOutputsSpec,
{"bundlers." + settings.thisSystem.get() + ".default", {"bundlers." + settings.thisSystem.get() + ".default",
"defaultBundler." + settings.thisSystem.get() "defaultBundler." + settings.thisSystem.get()
}, },
@ -105,7 +105,12 @@ struct CmdBundle : InstallableCommand
auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2, ""); auto outPath = evalState->coerceToStorePath(attr2->pos, *attr2->value, context2, "");
store->buildPaths({ DerivedPath::Built { drvPath } }); store->buildPaths({
DerivedPath::Built {
.drvPath = drvPath,
.outputs = OutputsSpec::All { },
},
});
auto outPathS = store->printStorePath(outPath); auto outPathS = store->printStorePath(outPath);

View file

@ -232,7 +232,12 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
auto shellDrvPath = writeDerivation(*evalStore, drv); auto shellDrvPath = writeDerivation(*evalStore, drv);
/* Build the derivation. */ /* Build the derivation. */
store->buildPaths({DerivedPath::Built{shellDrvPath}}, bmNormal, evalStore); store->buildPaths(
{ DerivedPath::Built {
.drvPath = shellDrvPath,
.outputs = OutputsSpec::All { },
}},
bmNormal, evalStore);
for (auto & [_0, optPath] : evalStore->queryPartialDerivationOutputMap(shellDrvPath)) { for (auto & [_0, optPath] : evalStore->queryPartialDerivationOutputMap(shellDrvPath)) {
assert(optPath); assert(optPath);

View file

@ -509,8 +509,12 @@ struct CmdFlakeCheck : FlakeCommand
auto drvPath = checkDerivation( auto drvPath = checkDerivation(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
*attr2.value, attr2.pos); *attr2.value, attr2.pos);
if (drvPath && attr_name == settings.thisSystem.get()) if (drvPath && attr_name == settings.thisSystem.get()) {
drvPaths.push_back(DerivedPath::Built{*drvPath}); drvPaths.push_back(DerivedPath::Built {
.drvPath = *drvPath,
.outputs = OutputsSpec::All { },
});
}
} }
} }
} }
@ -647,6 +651,19 @@ struct CmdFlakeCheck : FlakeCommand
} }
} }
else if (
name == "lib"
|| name == "darwinConfigurations"
|| name == "darwinModules"
|| name == "flakeModule"
|| name == "flakeModules"
|| name == "herculesCI"
|| name == "homeConfigurations"
|| name == "nixopsConfigurations"
)
// Known but unchecked community attribute
;
else else
warn("unknown flake output '%s'", name); warn("unknown flake output '%s'", name);

View file

@ -22,7 +22,7 @@ struct ProfileElementSource
// FIXME: record original attrpath. // FIXME: record original attrpath.
FlakeRef resolvedRef; FlakeRef resolvedRef;
std::string attrPath; std::string attrPath;
OutputsSpec outputs; ExtendedOutputsSpec outputs;
bool operator < (const ProfileElementSource & other) const bool operator < (const ProfileElementSource & other) const
{ {
@ -44,7 +44,7 @@ struct ProfileElement
std::string describe() const std::string describe() const
{ {
if (source) if (source)
return fmt("%s#%s%s", source->originalRef, source->attrPath, printOutputsSpec(source->outputs)); return fmt("%s#%s%s", source->originalRef, source->attrPath, source->outputs.to_string());
StringSet names; StringSet names;
for (auto & path : storePaths) for (auto & path : storePaths)
names.insert(DrvName(path.name()).name); names.insert(DrvName(path.name()).name);
@ -126,7 +126,7 @@ struct ProfileManifest
parseFlakeRef(e[sOriginalUrl]), parseFlakeRef(e[sOriginalUrl]),
parseFlakeRef(e[sUrl]), parseFlakeRef(e[sUrl]),
e["attrPath"], e["attrPath"],
e["outputs"].get<OutputsSpec>() e["outputs"].get<ExtendedOutputsSpec>()
}; };
} }
elements.emplace_back(std::move(element)); elements.emplace_back(std::move(element));
@ -308,12 +308,12 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
auto & [res, info] = builtPaths[installable.get()]; auto & [res, info] = builtPaths[installable.get()];
if (info.originalRef && info.resolvedRef && info.attrPath && info.outputsSpec) { if (info.originalRef && info.resolvedRef && info.attrPath && info.extendedOutputsSpec) {
element.source = ProfileElementSource { element.source = ProfileElementSource {
.originalRef = *info.originalRef, .originalRef = *info.originalRef,
.resolvedRef = *info.resolvedRef, .resolvedRef = *info.resolvedRef,
.attrPath = *info.attrPath, .attrPath = *info.attrPath,
.outputs = *info.outputsSpec, .outputs = *info.extendedOutputsSpec,
}; };
} }
@ -497,7 +497,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
.originalRef = installable->flakeRef, .originalRef = installable->flakeRef,
.resolvedRef = *info.resolvedRef, .resolvedRef = *info.resolvedRef,
.attrPath = *info.attrPath, .attrPath = *info.attrPath,
.outputs = installable->outputsSpec, .outputs = installable->extendedOutputsSpec,
}; };
installables.push_back(installable); installables.push_back(installable);
@ -553,8 +553,8 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
for (size_t i = 0; i < manifest.elements.size(); ++i) { for (size_t i = 0; i < manifest.elements.size(); ++i) {
auto & element(manifest.elements[i]); auto & element(manifest.elements[i]);
logger->cout("%d %s %s %s", i, logger->cout("%d %s %s %s", i,
element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-", element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath + element.source->outputs.to_string() : "-",
element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath + printOutputsSpec(element.source->outputs) : "-", element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath + element.source->outputs.to_string() : "-",
concatStringsSep(" ", store->printStorePathSet(element.storePaths))); concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
} }
} }

View file

@ -9,15 +9,44 @@ using namespace nix;
struct CmdShowConfig : Command, MixJSON struct CmdShowConfig : Command, MixJSON
{ {
std::optional<std::string> name;
CmdShowConfig() {
expectArgs({
.label = {"name"},
.optional = true,
.handler = {&name},
});
}
std::string description() override std::string description() override
{ {
return "show the Nix configuration"; return "show the Nix configuration or the value of a specific setting";
} }
Category category() override { return catUtility; } Category category() override { return catUtility; }
void run() override void run() override
{ {
if (name) {
if (json) {
throw UsageError("'--json' is not supported when specifying a setting name");
}
std::map<std::string, Config::SettingInfo> settings;
globalConfig.getSettings(settings);
auto setting = settings.find(*name);
if (setting == settings.end()) {
throw Error("could not find setting '%1%'", *name);
} else {
const auto & value = setting->second.value;
logger->cout("%s", value);
}
return;
}
if (json) { if (json) {
// FIXME: use appropriate JSON types (bool, ints, etc). // FIXME: use appropriate JSON types (bool, ints, etc).
logger->cout("%s", globalConfig.toJSON().dump()); logger->cout("%s", globalConfig.toJSON().dump());

View file

@ -1,53 +1,6 @@
{ busybox }: { busybox }:
with import ./config.nix; import ./build-hook.nix {
inherit busybox;
let contentAddressed = true;
}
mkDerivation = args:
derivation ({
inherit system;
builder = busybox;
args = ["sh" "-e" args.builder or (builtins.toFile "builder-${args.name}.sh" "if [ -e .attrs.sh ]; then source .attrs.sh; fi; eval \"$buildCommand\"")];
outputHashMode = "recursive";
outputHashAlgo = "sha256";
__contentAddressed = true;
} // removeAttrs args ["builder" "meta"])
// { meta = args.meta or {}; };
input1 = mkDerivation {
shell = busybox;
name = "build-remote-input-1";
buildCommand = "echo FOO > $out";
requiredSystemFeatures = ["foo"];
};
input2 = mkDerivation {
shell = busybox;
name = "build-remote-input-2";
buildCommand = "echo BAR > $out";
requiredSystemFeatures = ["bar"];
};
input3 = mkDerivation {
shell = busybox;
name = "build-remote-input-3";
buildCommand = ''
read x < ${input2}
echo $x BAZ > $out
'';
requiredSystemFeatures = ["baz"];
};
in
mkDerivation {
shell = busybox;
name = "build-remote";
buildCommand =
''
read x < ${input1}
read y < ${input3}
echo "$x $y" > $out
'';
}

View file

@ -1,15 +1,22 @@
{ busybox }: { busybox, contentAddressed ? false }:
with import ./config.nix; with import ./config.nix;
let let
caArgs = if contentAddressed then {
outputHashMode = "recursive";
outputHashAlgo = "sha256";
__contentAddressed = true;
} else {};
mkDerivation = args: mkDerivation = args:
derivation ({ derivation ({
inherit system; inherit system;
builder = busybox; builder = busybox;
args = ["sh" "-e" args.builder or (builtins.toFile "builder-${args.name}.sh" "if [ -e .attrs.sh ]; then source .attrs.sh; fi; eval \"$buildCommand\"")]; args = ["sh" "-e" args.builder or (builtins.toFile "builder-${args.name}.sh" "if [ -e .attrs.sh ]; then source .attrs.sh; fi; eval \"$buildCommand\"")];
} // removeAttrs args ["builder" "meta" "passthru"]) } // removeAttrs args ["builder" "meta" "passthru"]
// caArgs)
// { meta = args.meta or {}; passthru = args.passthru or {}; }; // { meta = args.meta or {}; passthru = args.passthru or {}; };
input1 = mkDerivation { input1 = mkDerivation {

View file

@ -63,12 +63,9 @@ nix path-info --store $TEST_ROOT/machine3 --all \
| grep builder-build-remote-input-3.sh | grep builder-build-remote-input-3.sh
# Temporarily disabled because of https://github.com/NixOS/nix/issues/6209 for i in input1 input3; do
if [[ -z "$CONTENT_ADDRESSED" ]]; then nix log --store $TEST_ROOT/machine0 --file "$file" --arg busybox $busybox passthru."$i" | grep hi-$i
for i in input1 input3; do done
nix log --store $TEST_ROOT/machine0 --file "$file" --arg busybox $busybox passthru."$i" | grep hi-$i
done
fi
# Behavior of keep-failed # Behavior of keep-failed
out="$(nix-build 2>&1 failing.nix \ out="$(nix-build 2>&1 failing.nix \

View file

@ -42,20 +42,21 @@ nix build -f multiple-outputs.nix --json 'a^*' --no-link | jq --exit-status '
nix build -f multiple-outputs.nix --json e --no-link | jq --exit-status ' nix build -f multiple-outputs.nix --json e --no-link | jq --exit-status '
(.[0] | (.[0] |
(.drvPath | match(".*multiple-outputs-e.drv")) and (.drvPath | match(".*multiple-outputs-e.drv")) and
(.outputs | keys == ["a", "b"])) (.outputs | keys == ["a_a", "b"]))
' '
# But not when it's overriden. # But not when it's overriden.
nix build -f multiple-outputs.nix --json e^a --no-link | jq --exit-status ' nix build -f multiple-outputs.nix --json e^a_a --no-link
nix build -f multiple-outputs.nix --json e^a_a --no-link | jq --exit-status '
(.[0] | (.[0] |
(.drvPath | match(".*multiple-outputs-e.drv")) and (.drvPath | match(".*multiple-outputs-e.drv")) and
(.outputs | keys == ["a"])) (.outputs | keys == ["a_a"]))
' '
nix build -f multiple-outputs.nix --json 'e^*' --no-link | jq --exit-status ' nix build -f multiple-outputs.nix --json 'e^*' --no-link | jq --exit-status '
(.[0] | (.[0] |
(.drvPath | match(".*multiple-outputs-e.drv")) and (.drvPath | match(".*multiple-outputs-e.drv")) and
(.outputs | keys == ["a", "b", "c"])) (.outputs | keys == ["a_a", "b", "c"]))
' '
# Test building from raw store path to drv not expression. # Test building from raw store path to drv not expression.
@ -104,7 +105,7 @@ nix build "$drv^*" --no-link --json | jq --exit-status '
nix build --impure -f multiple-outputs.nix --json e --no-link | jq --exit-status ' nix build --impure -f multiple-outputs.nix --json e --no-link | jq --exit-status '
(.[0] | (.[0] |
(.drvPath | match(".*multiple-outputs-e.drv")) and (.drvPath | match(".*multiple-outputs-e.drv")) and
(.outputs | keys == ["a", "b"])) (.outputs | keys == ["a_a", "b"]))
' '
testNormalization () { testNormalization () {

View file

@ -51,3 +51,8 @@ exp_features=$(nix show-config | grep '^experimental-features' | cut -d '=' -f 2
[[ $prev != $exp_cores ]] [[ $prev != $exp_cores ]]
[[ $exp_cores == "4242" ]] [[ $exp_cores == "4242" ]]
[[ $exp_features == "flakes nix-command" ]] [[ $exp_features == "flakes nix-command" ]]
# Test that it's possible to retrieve a single setting's value
val=$(nix show-config | grep '^warn-dirty' | cut -d '=' -f 2 | xargs)
val2=$(nix show-config warn-dirty)
[[ $val == $val2 ]]

View file

@ -4,7 +4,7 @@ clearStore
clearProfiles clearProfiles
checkRef() { checkRef() {
nix-store -q --references $TEST_ROOT/result | grep -q "$1" || fail "missing reference $1" nix-store -q --references $TEST_ROOT/result | grep -q "$1"'$' || fail "missing reference $1"
} }
# Test the export of the runtime dependency graph. # Test the export of the runtime dependency graph.

View file

@ -1 +1 @@
true [ true true true true true true ]

View file

@ -18,7 +18,24 @@ let
}; };
}; };
legit-context = builtins.getContext "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}"; combo-path = "${path}${drv.outPath}${drv.foo.outPath}${drv.drvPath}";
legit-context = builtins.getContext combo-path;
constructed-context = builtins.getContext (builtins.appendContext "" desired-context); reconstructed-path = builtins.appendContext
in legit-context == constructed-context (builtins.unsafeDiscardStringContext combo-path)
desired-context;
# Eta rule for strings with context.
etaRule = str:
str == builtins.appendContext
(builtins.unsafeDiscardStringContext str)
(builtins.getContext str);
in [
(legit-context == desired-context)
(reconstructed-path == combo-path)
(etaRule "foo")
(etaRule drv.drvPath)
(etaRule drv.foo.outPath)
(etaRule (builtins.unsafeDiscardOutputDependency drv.drvPath))
]

View file

@ -1 +1 @@
{ bar = "regular"; foo = "directory"; } { bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }

View file

@ -0,0 +1 @@
{ bar = "regular"; foo = "directory"; ldir = "symlink"; linked = "symlink"; }

View file

@ -0,0 +1,6 @@
{
bar = builtins.readFileType ./readDir/bar;
foo = builtins.readFileType ./readDir/foo;
linked = builtins.readFileType ./readDir/linked;
ldir = builtins.readFileType ./readDir/ldir;
}

1
tests/lang/readDir/ldir Symbolic link
View file

@ -0,0 +1 @@
foo

1
tests/lang/readDir/linked Symbolic link
View file

@ -0,0 +1 @@
foo/git-hates-directories

View file

@ -91,9 +91,9 @@ rec {
e = mkDerivation { e = mkDerivation {
name = "multiple-outputs-e"; name = "multiple-outputs-e";
outputs = [ "a" "b" "c" ]; outputs = [ "a_a" "b" "c" ];
meta.outputsToInstall = [ "a" "b" ]; meta.outputsToInstall = [ "a_a" "b" ];
buildCommand = "mkdir $a $b $c"; buildCommand = "mkdir $a_a $b $c";
}; };
independent = mkDerivation { independent = mkDerivation {
@ -117,4 +117,14 @@ rec {
''; '';
}; };
invalid-output-name-1 = mkDerivation {
name = "invalid-output-name-1";
outputs = [ "out/"];
};
invalid-output-name-2 = mkDerivation {
name = "invalid-output-name-2";
outputs = [ "x" "foo$"];
};
} }

View file

@ -83,3 +83,6 @@ nix-store --gc --keep-derivations --keep-outputs
nix-store --gc --print-roots nix-store --gc --print-roots
rm -rf $NIX_STORE_DIR/.links rm -rf $NIX_STORE_DIR/.links
rmdir $NIX_STORE_DIR rmdir $NIX_STORE_DIR
nix build -f multiple-outputs.nix invalid-output-name-1 2>&1 | grep 'contains illegal character'
nix build -f multiple-outputs.nix invalid-output-name-2 2>&1 | grep 'contains illegal character'