Merge remote-tracking branch 'nixos/master'

This commit is contained in:
Max Headroom 2023-01-06 22:44:20 +01:00
commit 1c35324a97
93 changed files with 1957 additions and 1199 deletions

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@v0.0.9 uses: zeebe-io/backport-action@v1.0.1
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

@ -41,8 +41,6 @@ AC_DEFINE_UNQUOTED(SYSTEM, ["$system"], [platform identifier ('cpu-os')])
test "$localstatedir" = '${prefix}/var' && localstatedir=/nix/var test "$localstatedir" = '${prefix}/var' && localstatedir=/nix/var
CFLAGS=
CXXFLAGS=
AC_PROG_CC AC_PROG_CC
AC_PROG_CXX AC_PROG_CXX
AC_PROG_CPP AC_PROG_CPP

View file

@ -29,6 +29,7 @@
- [Nix Language](language/index.md) - [Nix Language](language/index.md)
- [Data Types](language/values.md) - [Data Types](language/values.md)
- [Language Constructs](language/constructs.md) - [Language Constructs](language/constructs.md)
- [String interpolation](language/string-interpolation.md)
- [Operators](language/operators.md) - [Operators](language/operators.md)
- [Derivations](language/derivations.md) - [Derivations](language/derivations.md)
- [Advanced Attributes](language/advanced-attributes.md) - [Advanced Attributes](language/advanced-attributes.md)
@ -59,6 +60,7 @@
@manpages@ @manpages@
- [Files](command-ref/files.md) - [Files](command-ref/files.md)
- [nix.conf](command-ref/conf-file.md) - [nix.conf](command-ref/conf-file.md)
- [Architecture](architecture/architecture.md)
- [Glossary](glossary.md) - [Glossary](glossary.md)
- [Contributing](contributing/contributing.md) - [Contributing](contributing/contributing.md)
- [Hacking](contributing/hacking.md) - [Hacking](contributing/hacking.md)

View file

@ -0,0 +1,115 @@
# Architecture
This chapter describes how Nix works.
It should help users understand why Nix behaves as it does, and it should help developers understand how to modify Nix and how to write similar tools.
## Overview
Nix consists of [hierarchical layers].
[hierarchical layers]: https://en.m.wikipedia.org/wiki/Multitier_architecture#Layers
The following [concept map] shows its main components (rectangles), the objects they operate on (rounded rectangles), and their interactions (connecting phrases):
[concept map]: https://en.m.wikipedia.org/wiki/Concept_map
```
.----------------.
| Nix expression |----------.
'----------------' |
| passed to
| |
+----------|-------------------|--------------------------------+
| Nix | V |
| | +-------------------------+ |
| | | commmand line interface |------. |
| | +-------------------------+ | |
| | | | |
| evaluated by calls manages |
| | | | |
| | V | |
| | +--------------------+ | |
| '-------->| language evaluator | | |
| +--------------------+ | |
| | | |
| produces | |
| | V |
| +----------------------------|------------------------------+ |
| | store | | |
| | referenced by V builds | |
| | .-------------. .------------. .--------------. | |
| | | build input |----->| build plan |----->| build result | | |
| | '-------------' '------------' '--------------' | |
| +-------------------------------------------------|---------+ |
+---------------------------------------------------|-----------+
|
represented as
|
V
.---------------.
| file |
'---------------'
```
At the top is the [command line interface](../command-ref/command-ref.md) that drives the underlying layers.
The [Nix language](../language/index.md) evaluator transforms Nix expressions into self-contained *build plans*, which are used to derive *build results* from referenced *build inputs*.
The command line interface and Nix expressions are what users deal with most.
> **Note**
> The Nix language itself does not have a notion of *packages* or *configurations*.
> As far as we are concerned here, the inputs and results of a build plan are just data.
Underlying the command line interface and the Nix language evaluator is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them.
It can also execute build plans to produce new data, which are made available to the operating system as files.
A build plan itself is a series of *build tasks*, together with their build inputs.
> **Important**
> A build task in Nix is called [derivation](../glossary#gloss-derivation).
Each build task has a special build input executed as *build instructions* in order to perform the build.
The result of a build task can be input to another build task.
The following [data flow diagram] shows a build plan for illustration.
Build inputs used as instructions to a build task are marked accordingly:
[data flow diagram]: https://en.m.wikipedia.org/wiki/Data-flow_diagram
```
+--------------------------------------------------------------------+
| build plan |
| |
| .-------------. |
| | build input |---------. |
| '-------------' | |
| instructions |
| | |
| v |
| .-------------. .----------. |
| | build input |-->( build task )-------. |
| '-------------' '----------' | |
| instructions |
| | |
| v |
| .-------------. .----------. .--------------. |
| | build input |---------. ( build task )--->| build result | |
| '-------------' | '----------' '--------------' |
| instructions ^ |
| | | |
| v | |
| .-------------. .----------. | |
| | build input |-->( build task )-------' |
| '-------------' '----------' |
| ^ |
| | |
| | |
| .-------------. | |
| | build input |---------' |
| '-------------' |
| |
+--------------------------------------------------------------------+
```

View file

@ -37,10 +37,12 @@ directory containing at least a file named `default.nix`.
`nix-build` is essentially a wrapper around `nix-build` is essentially a wrapper around
[`nix-instantiate`](nix-instantiate.md) (to translate a high-level Nix [`nix-instantiate`](nix-instantiate.md) (to translate a high-level Nix
expression to a low-level store derivation) and [`nix-store expression to a low-level [store derivation]) and [`nix-store
--realise`](nix-store.md#operation---realise) (to build the store --realise`](nix-store.md#operation---realise) (to build the store
derivation). derivation).
[store derivation]: ../glossary.md#gloss-store-derivation
> **Warning** > **Warning**
> >
> The result of the build is automatically registered as a root of the > The result of the build is automatically registered as a root of the

View file

@ -47,7 +47,9 @@ authentication, you can avoid typing the passphrase with `ssh-agent`.
Enable compression of the SSH connection. Enable compression of the SSH connection.
- `--include-outputs`\ - `--include-outputs`\
Also copy the outputs of store derivations included in the closure. Also copy the outputs of [store derivation]s included in the closure.
[store derivation]: ../../glossary.md#gloss-store-derivation
- `--use-substitutes` / `-s`\ - `--use-substitutes` / `-s`\
Attempt to download missing paths on the target machine using Nixs Attempt to download missing paths on the target machine using Nixs

View file

@ -205,10 +205,12 @@ a number of possible ways:
unambiguous way, which is necessary if there are multiple unambiguous way, which is necessary if there are multiple
derivations with the same name. derivations with the same name.
- If *args* are store derivations, then these are - If *args* are [store derivation]s, then these are
[realised](nix-store.md#operation---realise), and the resulting output paths [realised](nix-store.md#operation---realise), and the resulting output paths
are installed. are installed.
[store derivation]: ../glossary.md#gloss-store-derivation
- If *args* are store paths that are not store derivations, then these - If *args* are store paths that are not store derivations, then these
are [realised](nix-store.md#operation---realise) and installed. are [realised](nix-store.md#operation---realise) and installed.
@ -280,7 +282,7 @@ To copy the store path with symbolic name `gcc` from another profile:
$ nix-env -i --from-profile /nix/var/nix/profiles/foo gcc $ nix-env -i --from-profile /nix/var/nix/profiles/foo gcc
``` ```
To install a specific store derivation (typically created by To install a specific [store derivation] (typically created by
`nix-instantiate`): `nix-instantiate`):
```console ```console
@ -665,7 +667,7 @@ derivation is shown unless `--no-name` is specified.
Print the `system` attribute of the derivation. Print the `system` attribute of the derivation.
- `--drv-path`\ - `--drv-path`\
Print the path of the store derivation. Print the path of the [store derivation].
- `--out-path`\ - `--out-path`\
Print the output path of the derivation. Print the output path of the derivation.

View file

@ -17,13 +17,14 @@
# Description # Description
The command `nix-instantiate` generates [store The command `nix-instantiate` produces [store derivation]s from (high-level) Nix expressions.
derivations](../glossary.md) from (high-level) Nix expressions. It It evaluates the Nix expressions in each of *files* (which defaults to
evaluates the Nix expressions in each of *files* (which defaults to
*./default.nix*). Each top-level expression should evaluate to a *./default.nix*). Each top-level expression should evaluate to a
derivation, a list of derivations, or a set of derivations. The paths derivation, a list of derivations, or a set of derivations. The paths
of the resulting store derivations are printed on standard output. of the resulting store derivations are printed on standard output.
[store derivation]: ../glossary.md#gloss-store-derivation
If *files* is the character `-`, then a Nix expression will be read from If *files* is the character `-`, then a Nix expression will be read from
standard input. standard input.
@ -79,8 +80,7 @@ standard input.
# Examples # Examples
Instantiating store derivations from a Nix expression, and building them Instantiate [store derivation]s from a Nix expression, and build them using `nix-store`:
using `nix-store`:
```console ```console
$ nix-instantiate test.nix (instantiate) $ nix-instantiate test.nix (instantiate)

View file

@ -137,8 +137,10 @@ or.
## Examples ## Examples
This operation is typically used to build store derivations produced by This operation is typically used to build [store derivation]s produced by
[`nix-instantiate`](nix-instantiate.md): [`nix-instantiate`](./nix-instantiate.md):
[store derivation]: ../glossary.md#gloss-store-derivation
```console ```console
$ nix-store -r $(nix-instantiate ./test.nix) $ nix-store -r $(nix-instantiate ./test.nix)
@ -298,7 +300,7 @@ symlink.
## Common query options ## Common query options
- `--use-output`; `-u`\ - `--use-output`; `-u`\
For each argument to the query that is a store derivation, apply the For each argument to the query that is a [store derivation], apply the
query to the output path of the derivation instead. query to the output path of the derivation instead.
- `--force-realise`; `-f`\ - `--force-realise`; `-f`\
@ -318,7 +320,7 @@ symlink.
This query has one option: This query has one option:
- `--include-outputs` - `--include-outputs`
Also include the existing output paths of store derivations, Also include the existing output paths of [store derivation]s,
and their closures. and their closures.
This query can be used to implement various kinds of deployment. A This query can be used to implement various kinds of deployment. A
@ -372,12 +374,12 @@ symlink.
Prints the references graph of the store paths *paths* in the Prints the references graph of the store paths *paths* in the
[GraphML](http://graphml.graphdrawing.org/) file format. This can be [GraphML](http://graphml.graphdrawing.org/) file format. This can be
used to visualise dependency graphs. To obtain a build-time used to visualise dependency graphs. To obtain a build-time
dependency graph, apply this to a store derivation. To obtain a dependency graph, apply this to a [store derivation]. To obtain a
runtime dependency graph, apply it to an output path. runtime dependency graph, apply it to an output path.
- `--binding` *name*; `-b` *name*\ - `--binding` *name*; `-b` *name*\
Prints the value of the attribute *name* (i.e., environment Prints the value of the attribute *name* (i.e., environment
variable) of the store derivations *paths*. It is an error for a variable) of the [store derivation]s *paths*. It is an error for a
derivation to not have the specified attribute. derivation to not have the specified attribute.
- `--hash`\ - `--hash`\

View file

@ -99,8 +99,79 @@ You can run the whole testsuite with `make check`, or the tests for a specific c
### Functional tests ### Functional tests
The functional tests reside under the `tests` directory and are listed in `tests/local.mk`. The functional tests reside under the `tests` directory and are listed in `tests/local.mk`.
The whole testsuite can be run with `make install && make installcheck`. Each test is a bash script.
Individual tests can be run with `make tests/{testName}.sh.test`.
The whole test suite can be run with:
```shell-session
$ make install && make installcheck
ran test tests/foo.sh... [PASS]
ran test tests/bar.sh... [PASS]
...
```
Individual tests can be run with `make`:
```shell-session
$ make tests/${testName}.sh.test
ran test tests/${testName}.sh... [PASS]
```
or without `make`:
```shell-session
$ ./mk/run-test.sh tests/${testName}.sh
ran test tests/${testName}.sh... [PASS]
```
To see the complete output, one can also run:
```shell-session
$ ./mk/debug-test.sh tests/${testName}.sh
+ foo
output from foo
+ bar
output from bar
...
```
The test script will then be traced with `set -x` and the output displayed as it happens, regardless of whether the test succeeds or fails.
#### Debugging failing functional tests
When a functional test fails, it usually does so somewhere in the middle of the script.
To figure out what's wrong, it is convenient to run the test regularly up to the failing `nix` command, and then run that command with a debugger like GDB.
For example, if the script looks like:
```bash
foo
nix blah blub
bar
```
edit it like so:
```diff
foo
-nix blah blub
+gdb --args nix blah blub
bar
```
Then, running the test with `./mk/debug-test.sh` will drop you into GDB once the script reaches that point:
```shell-session
$ ./mk/debug-test.sh tests/${testName}.sh
...
+ gdb blash blub
GNU gdb (GDB) 12.1
...
(gdb)
```
One can debug the Nix invocation in all the usual ways.
For example, enter `run` to start the Nix invocation.
### Integration tests ### Integration tests

View file

@ -7,6 +7,18 @@
translated into low-level *store derivations* (implicitly by translated into low-level *store derivations* (implicitly by
`nix-env` and `nix-build`, or explicitly by `nix-instantiate`). `nix-env` and `nix-build`, or explicitly by `nix-instantiate`).
[derivation]: #gloss-derivation
- [store derivation]{#gloss-store-derivation}\
A [derivation] represented as a `.drv` file in the [store].
It has a [store path], like any [store object].
Example: `/nix/store/g946hcz4c8mdvq2g8vxx42z51qb71rvp-git-2.38.1.drv`
See [`nix show-derivation`](./command-ref/new-cli/nix3-show-derivation.md) (experimental) for displaying the contents of store derivations.
[store derivation]: #gloss-store-derivation
- [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)
@ -34,6 +46,8 @@
directory on another machine, accessed via `ssh` or directory on another machine, accessed via `ssh` or
served by the `nix-serve` Perl script. served by the `nix-serve` Perl script.
[store]: #gloss-store
- [chroot store]{#gloss-chroot-store}\ - [chroot store]{#gloss-chroot-store}\
A local store whose canonical path is anything other than `/nix/store`. A local store whose canonical path is anything other than `/nix/store`.
@ -46,9 +60,13 @@
cache](https://cache.nixos.org). cache](https://cache.nixos.org).
- [store path]{#gloss-store-path}\ - [store path]{#gloss-store-path}\
The location in the file system of a store object, i.e., an The location of a [store object] in the file system, i.e., an
immediate child of the Nix store directory. immediate child of the Nix store directory.
Example: `/nix/store/a040m110amc4h71lds2jmr8qrkj2jhxd-git-2.38.1`
[store path]: #gloss-store-path
- [store object]{#gloss-store-object}\ - [store object]{#gloss-store-object}\
A file that is an immediate child of the Nix store directory. These A file that is an immediate child of the Nix store directory. These
can be regular files, but also entire directory trees. Store objects can be regular files, but also entire directory trees. Store objects
@ -56,6 +74,8 @@
derivation outputs (objects produced by running a build task), or derivation outputs (objects produced by running a build task), or
derivations (files describing a build task). derivations (files describing a build task).
[store object]: #gloss-store-object
- [input-addressed store object]{#gloss-input-addressed-store-object}\ - [input-addressed store object]{#gloss-input-addressed-store-object}\
A store object produced by building a A store object produced by building a
non-[content-addressed](#gloss-content-addressed-derivation), non-[content-addressed](#gloss-content-addressed-derivation),
@ -124,7 +144,9 @@
references `R` then `R` is also in the closure of `P`. references `R` then `R` is also in the closure of `P`.
- [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
- [deriver]{#gloss-deriver}\ - [deriver]{#gloss-deriver}\
The deriver of an *output path* is the store The deriver of an *output path* is the store
@ -156,3 +178,12 @@
- [`ε`]{#gloss-epsilon}\ - [`ε`]{#gloss-epsilon}\
The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute. The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute.
- [string interpolation]{#gloss-string-interpolation}\
Expanding expressions enclosed in `${ }` within a [string], [path], or [attribute name].
See [String interpolation](./language/string-interpolation.md) for details.
[string]: ./language/values.md#type-string
[path]: ./language/values.md#type-path
[attribute name]: ./language/values.md#attribute-set

View file

@ -120,10 +120,10 @@ sudo rm -rf /nix /etc/nix /etc/profile/nix.sh ~root/.nix-profile ~root/.nix-defe
Remove build users and their group: Remove build users and their group:
```console ```console
for i in $(seq 30001 30032); do for i in $(seq 1 32); do
sudo userdel $i sudo userdel nixbld$i
done done
sudo groupdel 30000 sudo groupdel nixbld
``` ```
There may also be references to Nix in There may also be references to Nix in

View file

@ -1,28 +1,167 @@
# Operators # Operators
The table below lists the operators in the Nix language, in | Name | Syntax | Associativity | Precedence |
order of precedence (from strongest to weakest binding). |----------------------------------------|--------------------------------------------|---------------|------------|
| [Attribute selection] | *attrset* `.` *attrpath* \[ `or` *expr* \] | none | 1 |
| Function application | *func* *expr* | left | 2 |
| [Arithmetic negation][arithmetic] | `-` *number* | none | 3 |
| [Has attribute] | *attrset* `?` *attrpath* | none | 4 |
| List concatenation | *list* `++` *list* | right | 5 |
| [Multiplication][arithmetic] | *number* `*` *number* | left | 6 |
| [Division][arithmetic] | *number* `/` *number* | left | 6 |
| [Subtraction][arithmetic] | *number* `-` *number* | left | 7 |
| [Addition][arithmetic] | *number* `+` *number* | left | 7 |
| [String concatenation] | *string* `+` *string* | left | 7 |
| [Path concatenation] | *path* `+` *path* | left | 7 |
| [Path and string concatenation] | *path* `+` *string* | left | 7 |
| [String and path concatenation] | *string* `+` *path* | left | 7 |
| Logical negation (`NOT`) | `!` *bool* | none | 8 |
| [Update] | *attrset* `//` *attrset* | right | 9 |
| [Less than][Comparison] | *expr* `<` *expr* | none | 10 |
| [Less than or equal to][Comparison] | *expr* `<=` *expr* | none | 10 |
| [Greater than][Comparison] | *expr* `>` *expr* | none | 10 |
| [Greater than or equal to][Comparison] | *expr* `>=` *expr* | none | 10 |
| [Equality] | *expr* `==` *expr* | none | 11 |
| Inequality | *expr* `!=` *expr* | none | 11 |
| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
| Logical disjunction (`OR`) | *bool* `||` *bool* | left | 13 |
| [Logical implication] | *bool* `->` *bool* | none | 14 |
[string]: ./values.md#type-string
[path]: ./values.md#type-path
[number]: ./values.md#type-number
[list]: ./values.md#list
[attribute set]: ./values.md#attribute-set
## Attribute selection
Select the attribute denoted by attribute path *attrpath* from [attribute set] *attrset*.
If the attribute doesnt exist, return *value* if provided, otherwise abort evaluation.
<!-- FIXME: the following should to into its own language syntax section, but that needs more work to fit in well -->
An attribute path is a dot-separated list of attribute names.
An attribute name can be an identifier or a string.
> *attrpath* = *name* [ `.` *name* ]...
> *name* = *identifier* | *string*
> *identifier* ~ `[a-zA-Z_][a-zA-Z0-9_'-]*`
[Attribute selection]: #attribute-selection
## Has attribute
> *attrset* `?` *attrpath*
Test whether [attribute set] *attrset* contains the attribute denoted by *attrpath*.
The result is a [Boolean] value.
[Boolean]: ./values.md#type-boolean
[Has attribute]: #has-attribute
## Arithmetic
Numbers are type-compatible:
Pure integer operations will always return integers, whereas any operation involving at least one floating point number return a floating point number.
See also [Comparison] and [Equality].
The `+` operator is overloaded to also work on strings and paths.
[arithmetic]: #arithmetic
## String concatenation
> *string* `+` *string*
Concatenate two [string]s and merge their string contexts.
[String concatenation]: #string-concatenation
## Path concatenation
> *path* `+` *path*
Concatenate two [path]s.
The result is a path.
[Path concatenation]: #path-concatenation
## Path and string concatenation
> *path* + *string*
Concatenate *[path]* with *[string]*.
The result is a path.
> **Note**
>
> The string must not have a string context that refers to a [store path].
[Path and string concatenation]: #path-and-string-concatenation
## String and path concatenation
> *string* + *path*
Concatenate *[string]* with *[path]*.
The result is a string.
> **Important**
>
> The file or directory at *path* must exist and is copied to the [store].
> The path appears in the result as the corresponding [store path].
[store path]: ../glossary.md#gloss-store-path
[store]: ../glossary.md#gloss-store
[Path and string concatenation]: #path-and-string-concatenation
## Update
> *attrset1* + *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*.
If an attribute name is present in both, the attribute value from the former is taken.
[Update]: #update
## Comparison
Comparison is
- [arithmetic] for [number]s
- lexicographic for [string]s and [path]s
- item-wise lexicographic for [list]s:
elements at the same index in both lists are compared according to their type and skipped if they are equal.
All comparison operators are implemented in terms of `<`, and the following equivalencies hold:
| comparison | implementation |
|--------------|-----------------------|
| *a* `<=` *b* | `! (` *b* `<` *a* `)` |
| *a* `>` *b* | *b* `<` *a* |
| *a* `>=` *b* | `! (` *a* `<` *b* `)` |
[Comparison]: #comparison-operators
## Equality
- [Attribute sets][attribute set] and [list]s are compared recursively, and therefore are fully evaluated.
- Comparison of [function]s always returns `false`.
- Numbers are type-compatible, see [arithmetic] operators.
- Floating point numbers only differ up to a limited precision.
[function]: ./constructs.md#functions
[Equality]: #equality
## Logical implication
Equivalent to `!`*b1* `||` *b2*.
[Logical implication]: #logical-implication
| Name | Syntax | Associativity | Description | Precedence |
| ------------------------ | ----------------------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| Select | *e* `.` *attrpath* \[ `or` *def* \] | none | Select attribute denoted by the attribute path *attrpath* from set *e*. (An attribute path is a dot-separated list of attribute names.) If the attribute doesnt exist, return *def* if provided, otherwise abort evaluation. | 1 |
| Application | *e1* *e2* | left | Call function *e1* with argument *e2*. | 2 |
| Arithmetic Negation | `-` *e* | none | Arithmetic negation. | 3 |
| Has Attribute | *e* `?` *attrpath* | none | Test whether set *e* contains the attribute denoted by *attrpath*; return `true` or `false`. | 4 |
| List Concatenation | *e1* `++` *e2* | right | List concatenation. | 5 |
| Multiplication | *e1* `*` *e2*, | left | Arithmetic multiplication. | 6 |
| Division | *e1* `/` *e2* | left | Arithmetic division. | 6 |
| Addition | *e1* `+` *e2* | left | Arithmetic addition. | 7 |
| Subtraction | *e1* `-` *e2* | left | Arithmetic subtraction. | 7 |
| String Concatenation | *string1* `+` *string2* | left | String concatenation. | 7 |
| Not | `!` *e* | none | Boolean negation. | 8 |
| Update | *e1* `//` *e2* | right | Return a set consisting of the attributes in *e1* and *e2* (with the latter taking precedence over the former in case of equally named attributes). | 9 |
| Less Than | *e1* `<` *e2*, | none | Arithmetic/lexicographic comparison. | 10 |
| Less Than or Equal To | *e1* `<=` *e2* | none | Arithmetic/lexicographic comparison. | 10 |
| Greater Than | *e1* `>` *e2* | none | Arithmetic/lexicographic comparison. | 10 |
| Greater Than or Equal To | *e1* `>=` *e2* | none | Arithmetic/lexicographic comparison. | 10 |
| Equality | *e1* `==` *e2* | none | Equality. | 11 |
| Inequality | *e1* `!=` *e2* | none | Inequality. | 11 |
| Logical AND | *e1* `&&` *e2* | left | Logical AND. | 12 |
| Logical OR | *e1* <code>&#124;&#124;</code> *e2* | left | Logical OR. | 13 |
| Logical Implication | *e1* `->` *e2* | none | Logical implication (equivalent to <code>!e1 &#124;&#124; e2</code>). | 14 |

View file

@ -0,0 +1,82 @@
# String interpolation
String interpolation is a language feature where a [string], [path], or [attribute name] can contain expressions enclosed in `${ }` (dollar-sign with curly brackets).
Such a string is an *interpolated string*, and an expression inside is an *interpolated expression*.
Interpolated expressions must evaluate to one of the following:
- a [string]
- a [path]
- a [derivation]
[string]: ./values.md#type-string
[path]: ./values.md#type-path
[attribute name]: ./values.md#attribute-set
[derivation]: ../glossary.md#gloss-derivation
## Examples
### String
Rather than writing
```nix
"--with-freetype2-library=" + freetype + "/lib"
```
(where `freetype` is a [derivation]), you can instead write
```nix
"--with-freetype2-library=${freetype}/lib"
```
The latter is automatically translated to the former.
A more complicated example (from the Nix expression for [Qt](http://www.trolltech.com/products/qt)):
```nix
configureFlags = "
-system-zlib -system-libpng -system-libjpeg
${if openglSupport then "-dlopen-opengl
-L${mesa}/lib -I${mesa}/include
-L${libXmu}/lib -I${libXmu}/include" else ""}
${if threadSupport then "-thread" else "-no-thread"}
";
```
Note that Nix expressions and strings can be arbitrarily nested;
in this case the outer string contains various interpolated expressions that themselves contain strings (e.g., `"-thread"`), some of which in turn contain interpolated expressions (e.g., `${mesa}`).
### Path
Rather than writing
```nix
./. + "/" + foo + "-" + bar + ".nix"
```
or
```nix
./. + "/${foo}-${bar}.nix"
```
you can instead write
```nix
./${foo}-${bar}.nix
```
### Attribute name
Attribute names can be created dynamically with string interpolation:
```nix
let name = "foo"; in
{
${name} = "bar";
}
```
{ foo = "bar"; }

View file

@ -13,41 +13,9 @@
returns and tabs can be written as `\n`, `\r` and `\t`, returns and tabs can be written as `\n`, `\r` and `\t`,
respectively. respectively.
You can include the result of an expression into a string by You can include the results of other expressions into a string by enclosing them in `${ }`, a feature known as [string interpolation].
enclosing it in `${...}`, a feature known as *antiquotation*. The
enclosed expression must evaluate to something that can be coerced
into a string (meaning that it must be a string, a path, or a
derivation). For instance, rather than writing
```nix [string interpolation]: ./string-interpolation.md
"--with-freetype2-library=" + freetype + "/lib"
```
(where `freetype` is a derivation), you can instead write the more
natural
```nix
"--with-freetype2-library=${freetype}/lib"
```
The latter is automatically translated to the former. A more
complicated example (from the Nix expression for
[Qt](http://www.trolltech.com/products/qt)):
```nix
configureFlags = "
-system-zlib -system-libpng -system-libjpeg
${if openglSupport then "-dlopen-opengl
-L${mesa}/lib -I${mesa}/include
-L${libXmu}/lib -I${libXmu}/include" else ""}
${if threadSupport then "-thread" else "-no-thread"}
";
```
Note that Nix expressions and strings can be arbitrarily nested; in
this case the outer string contains various antiquotations that
themselves contain strings (e.g., `"-thread"`), some of which in
turn contain expressions (e.g., `${mesa}`).
The second way to write string literals is as an *indented string*, The second way to write string literals is as an *indented string*,
which is enclosed between pairs of *double single-quotes*, like so: which is enclosed between pairs of *double single-quotes*, like so:
@ -75,7 +43,7 @@
Note that the whitespace and newline following the opening `''` is Note that the whitespace and newline following the opening `''` is
ignored if there is no non-whitespace text on the initial line. ignored if there is no non-whitespace text on the initial line.
Antiquotation (`${expr}`) is supported in indented strings. Indented strings support [string interpolation].
Since `${` and `''` have special meaning in indented strings, you Since `${` and `''` have special meaning in indented strings, you
need a way to quote them. `$` can be escaped by prefixing it with need a way to quote them. `$` can be escaped by prefixing it with
@ -117,9 +85,10 @@
Numbers, which can be *integers* (like `123`) or *floating point* Numbers, which can be *integers* (like `123`) or *floating point*
(like `123.43` or `.27e13`). (like `123.43` or `.27e13`).
Numbers are type-compatible: pure integer operations will always See [arithmetic] and [comparison] operators for semantics.
return integers, whereas any operation involving at least one
floating point number will have a floating point number as a result. [arithmetic]: ./operators.md#arithmetic
[comparison]: ./operators.md#comparison
- <a id="type-path" href="#type-path">Path</a> - <a id="type-path" href="#type-path">Path</a>
@ -143,26 +112,23 @@
environment variable `NIX_PATH` will be searched for the given file environment variable `NIX_PATH` will be searched for the given file
or directory name. or directory name.
Antiquotation is supported in any paths except those in angle brackets. When an [interpolated string][string interpolation] evaluates to a path, the path is first copied into the Nix store and the resulting string is the [store path] of the newly created [store object].
`./${foo}-${bar}.nix` is a more convenient way of writing
`./. + "/" + foo + "-" + bar + ".nix"` or `./. + "/${foo}-${bar}.nix"`. At
least one slash must appear *before* any antiquotations for this to be
recognized as a path. `a.${foo}/b.${bar}` is a syntactically valid division
operation. `./a.${foo}/b.${bar}` is a path.
When a path appears in an antiquotation, and is thus coerced into a string, [store path]: ../glossary.md#gloss-store-path
the path is first copied into the Nix store and the resulting string is [store object]: ../glossary.md#gloss-store-object
the Nix store path. For instance `"${./foo.txt}" will cause `foo.txt` in
the current directory to be copied into the Nix store and result in the
string `"/nix/store/<HASH>-foo.txt"`.
Note that the Nix language assumes that all input files will remain For instance, evaluating `"${./foo.txt}"` will cause `foo.txt` in the current directory to be copied into the Nix store and result in the string `"/nix/store/<hash>-foo.txt"`.
_unchanged_ during the course of the Nix expression evaluation.
If you for example antiquote a file path during a `nix repl` session, and Note that the Nix language assumes that all input files will remain _unchanged_ while evaluating a Nix expression.
then later in the same session, after having changed the file contents, For example, assume you used a file path in an interpolated string during a `nix repl` session.
evaluate the antiquotation with the file path again, then Nix will still Later in the same session, after having changed the file contents, evaluating the interpolated string with the file path again might not return a new store path, since Nix might not re-read the file contents.
return the first store path. It will _not_ reread the file contents to
produce a different Nix store path. Paths themselves, except those in angle brackets (`< >`), support [string interpolation].
At least one slash (`/`) must appear *before* any interpolated expression for the result to be recognized as a path.
`a.${foo}/b.${bar}` is a syntactically valid division operation.
`./a.${foo}/b.${bar}` is a path.
- <a id="type-boolean" href="#type-boolean">Boolean</a> - <a id="type-boolean" href="#type-boolean">Boolean</a>
@ -235,23 +201,33 @@ will evaluate to `"Xyzzy"` because there is no `c` attribute in the set.
You can use arbitrary double-quoted strings as attribute names: You can use arbitrary double-quoted strings as attribute names:
```nix ```nix
{ "foo ${bar}" = 123; "nix-1.0" = 456; }."foo ${bar}" { "$!@#?" = 123; }."$!@#?"
``` ```
This will evaluate to `123` (Assuming `bar` is antiquotable). In the
case where an attribute name is just a single antiquotation, the quotes
can be dropped:
```nix ```nix
{ foo = 123; }.${bar} or 456 let bar = "bar";
{ "foo ${bar}" = 123; }."foo ${bar}"
``` ```
This will evaluate to `123` if `bar` evaluates to `"foo"` when coerced Both will evaluate to `123`.
to a string and `456` otherwise (again assuming `bar` is antiquotable).
Attribute names support [string interpolation]:
```nix
let bar = "foo"; in
{ foo = 123; }.${bar}
```
```nix
let bar = "foo"; in
{ ${bar} = 123; }.foo
```
Both will evaluate to `123`.
In the special case where an attribute name inside of a set declaration In the special case where an attribute name inside of a set declaration
evaluates to `null` (which is normally an error, as `null` is not evaluates to `null` (which is normally an error, as `null` cannot be coerced to
antiquotable), that attribute is simply not added to the set: a string), that attribute is simply not added to the set:
```nix ```nix
{ ${if foo then "bar" else null} = true; } { ${if foo then "bar" else null} = true; }

View file

@ -15,13 +15,9 @@
# NIX_PATH=nixpkgs=flake:nixpkgs nix-build '<nixpkgs>' -A hello # NIX_PATH=nixpkgs=flake:nixpkgs nix-build '<nixpkgs>' -A hello
``` ```
* Allow explicitly selecting outputs in a store derivation installable, just like we can do with other sorts of installables. * Instead of "antiquotation", the more common term [string interpolation](../language/string-interpolation.md) is now used consistently.
For example, Historical release notes were not changed.
```shell-session
$ nix-build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev` * Error traces have been reworked to provide detailed explanations and more
``` accurate error locations. A short excerpt of the trace is now shown by
now works just as default when an error occurs.
```shell-session
$ nix-build glibc^dev`
```
does already.

11
mk/common-test.sh Normal file
View file

@ -0,0 +1,11 @@
TESTS_ENVIRONMENT=("TEST_NAME=${test%.*}" 'NIX_REMOTE=')
: ${BASH:=/usr/bin/env bash}
init_test () {
cd tests && env "${TESTS_ENVIRONMENT[@]}" $BASH -e init.sh 2>/dev/null > /dev/null
}
run_test_proper () {
cd $(dirname $test) && env "${TESTS_ENVIRONMENT[@]}" $BASH -e $(basename $test)
}

11
mk/debug-test.sh Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -eu
test=$1
dir="$(dirname "${BASH_SOURCE[0]}")"
source "$dir/common-test.sh"
(init_test)
run_test_proper

View file

@ -1,4 +1,4 @@
#!/bin/sh #!/usr/bin/env bash
set -u set -u
@ -7,7 +7,12 @@ green=""
yellow="" yellow=""
normal="" normal=""
post_run_msg="ran test $1..." test=$1
dir="$(dirname "${BASH_SOURCE[0]}")"
source "$dir/common-test.sh"
post_run_msg="ran test $test..."
if [ -t 1 ]; then if [ -t 1 ]; then
red="" red=""
green="" green=""
@ -16,12 +21,12 @@ if [ -t 1 ]; then
fi fi
run_test () { run_test () {
(cd tests && env ${TESTS_ENVIRONMENT} init.sh 2>/dev/null > /dev/null) (init_test 2>/dev/null > /dev/null)
log="$(cd $(dirname $1) && env ${TESTS_ENVIRONMENT} $(basename $1) 2>&1)" log="$(run_test_proper 2>&1)"
status=$? status=$?
} }
run_test "$1" run_test
# Hack: Retry the test if it fails with “unexpected EOF reading a line” as these # Hack: Retry the test if it fails with “unexpected EOF reading a line” as these
# appear randomly without anyone knowing why. # appear randomly without anyone knowing why.
@ -32,7 +37,7 @@ if [[ $status -ne 0 && $status -ne 99 && \
]]; then ]]; then
echo "$post_run_msg [${yellow}FAIL$normal] (possibly flaky, so will be retried)" echo "$post_run_msg [${yellow}FAIL$normal] (possibly flaky, so will be retried)"
echo "$log" | sed 's/^/ /' echo "$log" | sed 's/^/ /'
run_test "$1" run_test
fi fi
if [ $status -eq 0 ]; then if [ $status -eq 0 ]; then

View file

@ -8,7 +8,11 @@ define run-install-test
.PHONY: $1.test .PHONY: $1.test
$1.test: $1 $(test-deps) $1.test: $1 $(test-deps)
@env TEST_NAME=$(basename $1) TESTS_ENVIRONMENT="$(tests-environment)" mk/run_test.sh $1 < /dev/null @env BASH=$(bash) $(bash) mk/run-test.sh $1 < /dev/null
.PHONY: $1.test-debug
$1.test-debug: $1 $(test-deps)
@env BASH=$(bash) $(bash) mk/debug-test.sh $1 < /dev/null
endef endef

View file

@ -575,7 +575,7 @@ EOF
# to extract _just_ the user's note, instead it is prefixed with # to extract _just_ the user's note, instead it is prefixed with
# some plist junk. This was causing the user note to always be set, # some plist junk. This was causing the user note to always be set,
# even if there was no reason for it. # even if there was no reason for it.
if ! poly_user_note_get "$username" | grep -q "Nix build user $coreid"; then if poly_user_note_get "$username" | grep -q "Nix build user $coreid"; then
row " Note" "Nix build user $coreid" row " Note" "Nix build user $coreid"
else else
poly_user_note_set "$username" "Nix build user $coreid" poly_user_note_set "$username" "Nix build user $coreid"

View file

@ -183,7 +183,7 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
addFlag({ addFlag({
.longName = "derivation", .longName = "derivation",
.description = "Operate on the store derivation rather than its outputs.", .description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.",
.category = installablesCategory, .category = installablesCategory,
.handler = {&operateOn, OperateOn::Derivation}, .handler = {&operateOn, OperateOn::Derivation},
}); });
@ -667,7 +667,7 @@ ref<eval_cache::EvalCache> openEvalCache(
auto vFlake = state.allocValue(); auto vFlake = state.allocValue();
flake::callFlake(state, *lockedFlake, *vFlake); flake::callFlake(state, *lockedFlake, *vFlake);
state.forceAttrs(*vFlake, noPos); state.forceAttrs(*vFlake, noPos, "while parsing cached flake data");
auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
assert(aOutputs); assert(aOutputs);
@ -1128,10 +1128,7 @@ std::vector<std::pair<std::shared_ptr<Installable>, BuiltPathWithResult>> Instal
DrvOutput outputId { *outputHash, output }; DrvOutput outputId { *outputHash, output };
auto realisation = store->queryRealisation(outputId); auto realisation = store->queryRealisation(outputId);
if (!realisation) if (!realisation)
throw Error( throw MissingRealisation(outputId);
"cannot operate on an output of the "
"unbuilt derivation '%s'",
outputId.to_string());
outputs.insert_or_assign(output, realisation->outPath); outputs.insert_or_assign(output, realisation->outPath);
} else { } else {
// If ca-derivations isn't enabled, assume that // If ca-derivations isn't enabled, assume that

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); state->forceAttrs(v, noPos, "nevermind, it is ignored anyway");
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];
@ -590,7 +590,7 @@ bool NixRepl::processLine(std::string line)
const auto [path, line] = [&] () -> std::pair<Path, uint32_t> { const auto [path, line] = [&] () -> std::pair<Path, uint32_t> {
if (v.type() == nPath || v.type() == nString) { if (v.type() == nPath || v.type() == nString) {
PathSet context; PathSet context;
auto path = state->coerceToPath(noPos, v, context); auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
return {path, 0}; return {path, 0};
} else if (v.isLambda()) { } else if (v.isLambda()) {
auto pos = state->positions[v.lambda.fun->pos]; auto pos = state->positions[v.lambda.fun->pos];
@ -834,7 +834,7 @@ void NixRepl::loadFiles()
void NixRepl::addAttrsToScope(Value & attrs) void NixRepl::addAttrsToScope(Value & attrs)
{ {
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }); state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope");
if (displ + attrs.attrs->size() >= envSize) if (displ + attrs.attrs->size() >= envSize)
throw Error("environment full; cannot add more variables"); throw Error("environment full; cannot add more variables");
@ -939,7 +939,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
Bindings::iterator i = v.attrs->find(state->sDrvPath); Bindings::iterator i = v.attrs->find(state->sDrvPath);
PathSet context; PathSet context;
if (i != v.attrs->end()) if (i != v.attrs->end())
str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context)); str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
else else
str << "???"; str << "???";
str << "»"; str << "»";

View file

@ -118,7 +118,7 @@ std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value &
// FIXME: is it possible to extract the Pos object instead of doing this // FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing? // toString + parsing?
auto pos = state.forceString(*v2); auto pos = state.forceString(*v2, noPos, "while evaluating the 'meta.position' attribute of a derivation");
auto colon = pos.rfind(':'); auto colon = pos.rfind(':');
if (colon == std::string::npos) if (colon == std::string::npos)

View file

@ -385,7 +385,7 @@ Value & AttrCursor::getValue()
if (!_value) { if (!_value) {
if (parent) { if (parent) {
auto & vParent = parent->first->getValue(); auto & vParent = parent->first->getValue();
root->state.forceAttrs(vParent, noPos); root->state.forceAttrs(vParent, noPos, "while searching for an attribute");
auto attr = vParent.attrs->get(parent->second); auto attr = vParent.attrs->get(parent->second);
if (!attr) if (!attr)
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr()); throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
@ -571,14 +571,14 @@ std::string AttrCursor::getString()
debug("using cached string attribute '%s'", getAttrPathStr()); debug("using cached string attribute '%s'", getAttrPathStr());
return s->first; return s->first;
} else } else
root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nString && v.type() != nPath) if (v.type() != nString && v.type() != nPath)
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
return v.type() == nString ? v.string.s : v.path; return v.type() == nString ? v.string.s : v.path;
} }
@ -602,7 +602,7 @@ string_t AttrCursor::getStringWithContext()
return *s; return *s;
} }
} else } else
root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr())); root->state.error("'%s' is not a string", getAttrPathStr()).debugThrow<TypeError>();
} }
} }
@ -613,7 +613,7 @@ string_t AttrCursor::getStringWithContext()
else if (v.type() == nPath) else if (v.type() == nPath)
return {v.path, {}}; return {v.path, {}};
else else
root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type()))); root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
} }
bool AttrCursor::getBool() bool AttrCursor::getBool()
@ -626,14 +626,14 @@ bool AttrCursor::getBool()
debug("using cached Boolean attribute '%s'", getAttrPathStr()); debug("using cached Boolean attribute '%s'", getAttrPathStr());
return *b; return *b;
} else } else
root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nBool) if (v.type() != nBool)
root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr())); root->state.error("'%s' is not a Boolean", getAttrPathStr()).debugThrow<TypeError>();
return v.boolean; return v.boolean;
} }
@ -685,7 +685,7 @@ std::vector<std::string> AttrCursor::getListOfStrings()
std::vector<std::string> res; std::vector<std::string> res;
for (auto & elem : v.listItems()) for (auto & elem : v.listItems())
res.push_back(std::string(root->state.forceStringNoCtx(*elem))); res.push_back(std::string(root->state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
if (root->db) if (root->db)
cachedValue = {root->db->setListOfStrings(getKey(), res), res}; cachedValue = {root->db->setListOfStrings(getKey(), res), res};
@ -703,14 +703,14 @@ std::vector<Symbol> AttrCursor::getAttrs()
debug("using cached attrset attribute '%s'", getAttrPathStr()); debug("using cached attrset attribute '%s'", getAttrPathStr());
return *attrs; return *attrs;
} else } else
root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nAttrs) if (v.type() != nAttrs)
root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr())); root->state.error("'%s' is not an attribute set", getAttrPathStr()).debugThrow<TypeError>();
std::vector<Symbol> attrs; std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs) for (auto & attr : *getValue().attrs)

View file

@ -103,33 +103,36 @@ void EvalState::forceValue(Value & v, Callable getPos)
else if (v.isApp()) else if (v.isApp())
callFunction(*v.app.left, *v.app.right, v, noPos); callFunction(*v.app.left, *v.app.right, v, noPos);
else if (v.isBlackhole()) else if (v.isBlackhole())
throwEvalError(getPos(), "infinite recursion encountered"); error("infinite recursion encountered").atPos(getPos()).template debugThrow<EvalError>();
} }
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const PosIdx pos) inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceAttrs(v, [&]() { return pos; }); forceAttrs(v, [&]() { return pos; }, errorCtx);
} }
template <typename Callable> template <typename Callable>
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, Callable getPos) inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx)
{ {
forceValue(v, getPos); forceValue(v, noPos);
if (v.type() != nAttrs) if (v.type() != nAttrs) {
throwTypeError(getPos(), "value is %1% while a set was expected", v); PosIdx pos = getPos();
error("value is %1% while a set was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}
} }
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const PosIdx pos) inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v, pos); forceValue(v, noPos);
if (!v.isList()) if (!v.isList()) {
throwTypeError(pos, "value is %1% while a list was expected", v); error("value is %1% while a list was expected", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
}
} }

View file

@ -67,22 +67,19 @@ static char * dupString(const char * s)
// When there's no need to write to the string, we can optimize away empty // When there's no need to write to the string, we can optimize away empty
// string allocations. // string allocations.
// This function handles makeImmutableStringWithLen(null, 0) by returning the // This function handles makeImmutableString(std::string_view()) by returning
// empty string. // the empty string.
static const char * makeImmutableStringWithLen(const char * s, size_t size) static const char * makeImmutableString(std::string_view s)
{ {
const size_t size = s.size();
if (size == 0) if (size == 0)
return ""; return "";
auto t = allocString(size + 1); auto t = allocString(size + 1);
memcpy(t, s, size); memcpy(t, s.data(), size);
t[size] = 0; t[size] = '\0';
return t; return t;
} }
static inline const char * makeImmutableString(std::string_view s) {
return makeImmutableStringWithLen(s.data(), s.size());
}
RootValue allocRootValue(Value * v) RootValue allocRootValue(Value * v)
{ {
@ -321,7 +318,7 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
} else { } else {
Value nameValue; Value nameValue;
name.expr->eval(state, env, nameValue); name.expr->eval(state, env, nameValue);
state.forceStringNoCtx(nameValue); state.forceStringNoCtx(nameValue, noPos, "while evaluating an attribute name");
return state.symbols.create(nameValue.string.s); return state.symbols.create(nameValue.string.s);
} }
} }
@ -417,6 +414,44 @@ static Strings parseNixPath(const std::string & s)
return res; return res;
} }
ErrorBuilder & ErrorBuilder::atPos(PosIdx pos)
{
info.errPos = state.positions[pos];
return *this;
}
ErrorBuilder & ErrorBuilder::withTrace(PosIdx pos, const std::string_view text)
{
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = false });
return *this;
}
ErrorBuilder & ErrorBuilder::withFrameTrace(PosIdx pos, const std::string_view text)
{
info.traces.push_front(Trace{ .pos = state.positions[pos], .hint = hintformat(std::string(text)), .frame = true });
return *this;
}
ErrorBuilder & ErrorBuilder::withSuggestions(Suggestions & s)
{
info.suggestions = s;
return *this;
}
ErrorBuilder & ErrorBuilder::withFrame(const Env & env, const Expr & expr)
{
// NOTE: This is abusing side-effects.
// TODO: check compatibility with nested debugger calls.
state.debugTraces.push_front(DebugTrace {
.pos = nullptr,
.expr = expr,
.env = env,
.hint = hintformat("Fake frame for debugging purposes"),
.isError = true
});
return *this;
}
EvalState::EvalState( EvalState::EvalState(
const Strings & _searchPath, const Strings & _searchPath,
@ -649,25 +684,7 @@ void EvalState::addConstant(const std::string & name, Value * v)
Value * EvalState::addPrimOp(const std::string & name, Value * EvalState::addPrimOp(const std::string & name,
size_t arity, PrimOpFun primOp) size_t arity, PrimOpFun primOp)
{ {
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name; return addPrimOp(PrimOp { .fun = primOp, .arity = arity, .name = name });
auto sym = symbols.create(name2);
/* Hack to make constants lazy: turn them into a application of
the primop to a dummy value. */
if (arity == 0) {
auto vPrimOp = allocValue();
vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = name2 });
Value v;
v.mkApp(vPrimOp, vPrimOp);
return addConstant(name, v);
}
Value * v = allocValue();
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 });
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
return v;
} }
@ -845,176 +862,14 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
} }
} }
/* Every "format" object (even temporary) takes up a few hundred bytes
of stack space, which is a real killer in the recursive
evaluator. So here are some helper functions for throwing
exceptions. */
void EvalState::throwEvalError(const PosIdx pos, const char * s, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx pos, const char * s)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s),
.errPos = positions[pos]
}));
}
void EvalState::throwEvalError(const char * s, const std::string & s2)
{
debugThrowLastTrace(EvalError(s, s2));
}
void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const std::string & s2, Env & env, Expr & expr)
{
debugThrow(EvalError(ErrorInfo{
.msg = hintfmt(s, s2),
.errPos = positions[pos],
.suggestions = suggestions,
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2),
.errPos = positions[pos]
}));
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s, s2),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const char * s, const std::string & s2,
const std::string & s3)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = positions[noPos]
}));
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
const std::string & s3)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = positions[pos]
}));
}
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
const std::string & s3, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s, s2, s3),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2, Env & env, Expr & expr)
{
// p1 is where the error occurred; p2 is a position mentioned in the message.
debugThrow(EvalError({
.msg = hintfmt(s, symbols[sym], positions[p2]),
.errPos = positions[p1]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v)
{
debugThrowLastTrace(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
}));
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v, Env & env, Expr & expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const char * s)
{
debugThrowLastTrace(TypeError({
.msg = hintfmt(s),
.errPos = positions[pos]
}));
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun,
const Symbol s2, Env & env, Expr &expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const ExprLambda & fun, const Symbol s2, Env & env, Expr &expr)
{
debugThrow(TypeError(ErrorInfo {
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = positions[pos],
.suggestions = suggestions,
}), env, expr);
}
void EvalState::throwTypeError(const char * s, const Value & v, Env & env, Expr &expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[expr.getPos()],
}), env, expr);
}
void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{
debugThrow(AssertionError({
.msg = hintfmt(s, s1),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{
debugThrow(UndefinedVarError({
.msg = hintfmt(s, s1),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{
debugThrow(MissingArgumentError({
.msg = hintfmt(s, s1),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
{ {
e.addTrace(nullptr, s, s2); e.addTrace(nullptr, s, s2);
} }
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame) const
{ {
e.addTrace(positions[pos], s, s2); e.addTrace(positions[pos], hintfmt(s, s2), frame);
} }
static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker( static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
@ -1091,7 +946,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
if (env->type == Env::HasWithExpr) { if (env->type == Env::HasWithExpr) {
if (noEval) return 0; if (noEval) return 0;
Value * v = allocValue(); Value * v = allocValue();
evalAttrs(*env->up, (Expr *) env->values[0], *v); evalAttrs(*env->up, (Expr *) env->values[0], *v, noPos, "<borked>");
env->values[0] = v; env->values[0] = v;
env->type = Env::HasWithAttrs; env->type = Env::HasWithAttrs;
} }
@ -1101,7 +956,7 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
return j->value; return j->value;
} }
if (!env->prevWith) if (!env->prevWith)
throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name], *env, const_cast<ExprVar&>(var)); error("undefined variable '%1%'", symbols[var.name]).atPos(var.pos).withFrame(*env, var).debugThrow<UndefinedVarError>();
for (size_t l = env->prevWith; l; --l, env = env->up) ; for (size_t l = env->prevWith; l; --l, env = env->up) ;
} }
} }
@ -1251,7 +1106,7 @@ void EvalState::cacheFile(
// computation. // computation.
if (mustBeTrivial && if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e))) !(dynamic_cast<ExprAttrs *>(e)))
throw EvalError("file '%s' must be an attribute set", path); error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
eval(e, v); eval(e, v);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath); addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
@ -1269,31 +1124,31 @@ void EvalState::eval(Expr * e, Value & v)
} }
inline bool EvalState::evalBool(Env & env, Expr * e) inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx)
{ {
Value v; try {
e->eval(*this, env, v); Value v;
if (v.type() != nBool) e->eval(*this, env, v);
throwTypeError(noPos, "value is %1% while a Boolean was expected", v, env, *e); if (v.type() != nBool)
return v.boolean; error("value is %1% while a Boolean was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos) inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
Value v; try {
e->eval(*this, env, v); e->eval(*this, env, v);
if (v.type() != nBool) if (v.type() != nAttrs)
throwTypeError(pos, "value is %1% while a Boolean was expected", v, env, *e); error("value is %1% while a set was expected", showType(v)).withFrame(env, *e).debugThrow<TypeError>();
return v.boolean; } catch (Error & e) {
} e.addTrace(positions[pos], errorCtx);
throw;
}
inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v)
{
e->eval(*this, env, v);
if (v.type() != nAttrs)
throwTypeError(noPos, "value is %1% while a set was expected", v, env, *e);
} }
@ -1366,7 +1221,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
Hence we need __overrides.) */ Hence we need __overrides.) */
if (hasOverrides) { if (hasOverrides) {
Value * vOverrides = (*v.attrs)[overrides->second.displ].value; Value * vOverrides = (*v.attrs)[overrides->second.displ].value;
state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }); state.forceAttrs(*vOverrides, [&]() { return vOverrides->determinePos(noPos); }, "while evaluating the `__overrides` attribute");
Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size()); Bindings * newBnds = state.allocBindings(v.attrs->capacity() + vOverrides->attrs->size());
for (auto & i : *v.attrs) for (auto & i : *v.attrs)
newBnds->push_back(i); newBnds->push_back(i);
@ -1394,11 +1249,11 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
state.forceValue(nameVal, i.pos); state.forceValue(nameVal, i.pos);
if (nameVal.type() == nNull) if (nameVal.type() == nNull)
continue; continue;
state.forceStringNoCtx(nameVal); state.forceStringNoCtx(nameVal, i.pos, "while evaluating the name of a dynamic attribute");
auto nameSym = state.symbols.create(nameVal.string.s); auto nameSym = state.symbols.create(nameVal.string.s);
Bindings::iterator j = v.attrs->find(nameSym); Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end()) if (j != v.attrs->end())
state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos, env, *this); state.error("dynamic attribute '%1%' already defined at %2%", state.symbols[nameSym], state.positions[j->pos]).atPos(i.pos).withFrame(env, *this).debugThrow<EvalError>();
i.valueExpr->setName(nameSym); i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */ /* Keep sorted order so find can catch duplicates */
@ -1495,15 +1350,14 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
return; return;
} }
} else { } else {
state.forceAttrs(*vAttrs, pos); state.forceAttrs(*vAttrs, pos, "while selecting an attribute");
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) { if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
std::set<std::string> allAttrNames; std::set<std::string> allAttrNames;
for (auto & attr : *vAttrs->attrs) for (auto & attr : *vAttrs->attrs)
allAttrNames.insert(state.symbols[attr.name]); allAttrNames.insert(state.symbols[attr.name]);
state.throwEvalError( auto suggestions = Suggestions::bestMatches(allAttrNames, state.symbols[name]);
pos, state.error("attribute '%1%' missing", state.symbols[name])
Suggestions::bestMatches(allAttrNames, state.symbols[name]), .atPos(pos).withSuggestions(suggestions).withFrame(env, *this).debugThrow<EvalError>();
"attribute '%1%' missing", state.symbols[name], env, *this);
} }
} }
vAttrs = j->value; vAttrs = j->value;
@ -1598,7 +1452,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (!lambda.hasFormals()) if (!lambda.hasFormals())
env2.values[displ++] = args[0]; env2.values[displ++] = args[0];
else { else {
forceAttrs(*args[0], pos); try {
forceAttrs(*args[0], lambda.pos, "while evaluating the value passed for the lambda argument");
} catch (Error & e) {
if (pos) e.addTrace(positions[pos], "from call site");
throw;
}
if (lambda.arg) if (lambda.arg)
env2.values[displ++] = args[0]; env2.values[displ++] = args[0];
@ -1610,8 +1469,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (auto & i : lambda.formals->formals) { for (auto & i : lambda.formals->formals) {
auto j = args[0]->attrs->get(i.name); auto j = args[0]->attrs->get(i.name);
if (!j) { if (!j) {
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'", if (!i.def) {
lambda, i.name, *fun.lambda.env, lambda); error("function '%1%' called without required argument '%2%'",
(lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
symbols[i.name])
.atPos(lambda.pos)
.withTrace(pos, "from call site")
.withFrame(*fun.lambda.env, lambda)
.debugThrow<TypeError>();
}
env2.values[displ++] = i.def->maybeThunk(*this, env2); env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else { } else {
attrsUsed++; attrsUsed++;
@ -1629,11 +1495,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
std::set<std::string> formalNames; std::set<std::string> formalNames;
for (auto & formal : lambda.formals->formals) for (auto & formal : lambda.formals->formals)
formalNames.insert(symbols[formal.name]); formalNames.insert(symbols[formal.name]);
throwTypeError( auto suggestions = Suggestions::bestMatches(formalNames, symbols[i.name]);
pos, error("function '%1%' called with unexpected argument '%2%'",
Suggestions::bestMatches(formalNames, symbols[i.name]), (lambda.name ? std::string(symbols[lambda.name]) : "anonymous lambda"),
"%1% called with unexpected argument '%2%'", symbols[i.name])
lambda, i.name, *fun.lambda.env, lambda); .atPos(lambda.pos)
.withTrace(pos, "from call site")
.withSuggestions(suggestions)
.withFrame(*fun.lambda.env, lambda)
.debugThrow<TypeError>();
} }
abort(); // can't happen abort(); // can't happen
} }
@ -1656,11 +1526,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
lambda.body->eval(*this, env2, vCur); lambda.body->eval(*this, env2, vCur);
} catch (Error & e) { } catch (Error & e) {
if (loggerSettings.showTrace.get()) { if (loggerSettings.showTrace.get()) {
addErrorTrace(e, lambda.pos, "while calling %s", addErrorTrace(
(lambda.name e,
? concatStrings("'", symbols[lambda.name], "'") lambda.pos,
: "anonymous lambda")); "while calling %s",
addErrorTrace(e, pos, "while evaluating call site%s", ""); lambda.name
? concatStrings("'", symbols[lambda.name], "'")
: "anonymous lambda",
true);
if (pos) addErrorTrace(e, pos, "from call site%s", "", true);
} }
throw; throw;
} }
@ -1679,9 +1553,17 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
return; return;
} else { } else {
/* We have all the arguments, so call the primop. */ /* We have all the arguments, so call the primop. */
auto name = vCur.primOp->name;
nrPrimOpCalls++; nrPrimOpCalls++;
if (countCalls) primOpCalls[vCur.primOp->name]++; if (countCalls) primOpCalls[name]++;
vCur.primOp->fun(*this, pos, args, vCur);
try {
vCur.primOp->fun(*this, noPos, args, vCur);
} catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw;
}
nrArgs -= argsLeft; nrArgs -= argsLeft;
args += argsLeft; args += argsLeft;
@ -1716,9 +1598,20 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
for (size_t i = 0; i < argsLeft; ++i) for (size_t i = 0; i < argsLeft; ++i)
vArgs[argsDone + i] = args[i]; vArgs[argsDone + i] = args[i];
auto name = primOp->primOp->name;
nrPrimOpCalls++; nrPrimOpCalls++;
if (countCalls) primOpCalls[primOp->primOp->name]++; if (countCalls) primOpCalls[name]++;
primOp->primOp->fun(*this, pos, vArgs, vCur);
try {
// TODO:
// 1. Unify this and above code. Heavily redundant.
// 2. Create a fake env (arg1, arg2, etc.) and a fake expr (arg1: arg2: etc: builtins.name arg1 arg2 etc)
// so the debugger allows to inspect the wrong parameters passed to the builtin.
primOp->primOp->fun(*this, noPos, vArgs, vCur);
} catch (Error & e) {
addErrorTrace(e, pos, "while calling the '%1%' builtin", name);
throw;
}
nrArgs -= argsLeft; nrArgs -= argsLeft;
args += argsLeft; args += argsLeft;
@ -1731,14 +1624,18 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
heap-allocate a copy and use that instead. */ heap-allocate a copy and use that instead. */
Value * args2[] = {allocValue(), args[0]}; Value * args2[] = {allocValue(), args[0]};
*args2[0] = vCur; *args2[0] = vCur;
/* !!! Should we use the attr pos here? */ try {
callFunction(*functor->value, 2, args2, vCur, pos); callFunction(*functor->value, 2, args2, vCur, functor->pos);
} catch (Error & e) {
e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
throw;
}
nrArgs--; nrArgs--;
args++; args++;
} }
else else
throwTypeError(pos, "attempt to call something which is not a function but %1%", vCur); error("attempt to call something which is not a function but %1%", showType(vCur)).atPos(pos).debugThrow<TypeError>();
} }
vRes = vCur; vRes = vCur;
@ -1802,13 +1699,12 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
if (j != args.end()) { if (j != args.end()) {
attrs.insert(*j); attrs.insert(*j);
} else if (!i.def) { } else if (!i.def) {
throwMissingArgumentError(i.pos, R"(cannot evaluate a function that has an argument without a value ('%1%') error(R"(cannot evaluate a function that has an argument without a value ('%1%')
Nix attempted to evaluate a function as a top level expression; in Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See values, or passed explicitly with '--arg' or '--argstr'. See
https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name], https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name])
*fun.lambda.env, *fun.lambda.fun); .atPos(i.pos).withFrame(*fun.lambda.env, *fun.lambda.fun).debugThrow<MissingArgumentError>();
} }
} }
} }
@ -1831,16 +1727,17 @@ void ExprWith::eval(EvalState & state, Env & env, Value & v)
void ExprIf::eval(EvalState & state, Env & env, Value & v) void ExprIf::eval(EvalState & state, Env & env, Value & v)
{ {
(state.evalBool(env, cond, pos) ? then : else_)->eval(state, env, v); // We cheat in the parser, and pass the position of the condition as the position of the if itself.
(state.evalBool(env, cond, pos, "while evaluating a branch condition") ? then : else_)->eval(state, env, v);
} }
void ExprAssert::eval(EvalState & state, Env & env, Value & v) void ExprAssert::eval(EvalState & state, Env & env, Value & v)
{ {
if (!state.evalBool(env, cond, pos)) { if (!state.evalBool(env, cond, pos, "in the condition of the assert statement")) {
std::ostringstream out; std::ostringstream out;
cond->show(state.symbols, out); cond->show(state.symbols, out);
state.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this); state.error("assertion '%1%' failed", out.str()).atPos(pos).withFrame(env, *this).debugThrow<AssertionError>();
} }
body->eval(state, env, v); body->eval(state, env, v);
} }
@ -1848,7 +1745,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
void ExprOpNot::eval(EvalState & state, Env & env, Value & v) void ExprOpNot::eval(EvalState & state, Env & env, Value & v)
{ {
v.mkBool(!state.evalBool(env, e)); v.mkBool(!state.evalBool(env, e, noPos, "in the argument of the not operator")); // XXX: FIXME: !
} }
@ -1856,7 +1753,7 @@ void ExprOpEq::eval(EvalState & state, Env & env, Value & v)
{ {
Value v1; e1->eval(state, env, v1); Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2); Value v2; e2->eval(state, env, v2);
v.mkBool(state.eqValues(v1, v2)); v.mkBool(state.eqValues(v1, v2, pos, "while testing two values for equality"));
} }
@ -1864,33 +1761,33 @@ void ExprOpNEq::eval(EvalState & state, Env & env, Value & v)
{ {
Value v1; e1->eval(state, env, v1); Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2); Value v2; e2->eval(state, env, v2);
v.mkBool(!state.eqValues(v1, v2)); v.mkBool(!state.eqValues(v1, v2, pos, "while testing two values for inequality"));
} }
void ExprOpAnd::eval(EvalState & state, Env & env, Value & v) void ExprOpAnd::eval(EvalState & state, Env & env, Value & v)
{ {
v.mkBool(state.evalBool(env, e1, pos) && state.evalBool(env, e2, pos)); v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the AND (&&) operator") && state.evalBool(env, e2, pos, "in the right operand of the AND (&&) operator"));
} }
void ExprOpOr::eval(EvalState & state, Env & env, Value & v) void ExprOpOr::eval(EvalState & state, Env & env, Value & v)
{ {
v.mkBool(state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos)); v.mkBool(state.evalBool(env, e1, pos, "in the left operand of the OR (||) operator") || state.evalBool(env, e2, pos, "in the right operand of the OR (||) operator"));
} }
void ExprOpImpl::eval(EvalState & state, Env & env, Value & v) void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
{ {
v.mkBool(!state.evalBool(env, e1, pos) || state.evalBool(env, e2, pos)); v.mkBool(!state.evalBool(env, e1, pos, "in the left operand of the IMPL (->) operator") || state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
} }
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v) void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
{ {
Value v1, v2; Value v1, v2;
state.evalAttrs(env, e1, v1); state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
state.evalAttrs(env, e2, v2); state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
state.nrOpUpdates++; state.nrOpUpdates++;
@ -1929,18 +1826,18 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
Value v1; e1->eval(state, env, v1); Value v1; e1->eval(state, env, v1);
Value v2; e2->eval(state, env, v2); Value v2; e2->eval(state, env, v2);
Value * lists[2] = { &v1, &v2 }; Value * lists[2] = { &v1, &v2 };
state.concatLists(v, 2, lists, pos); state.concatLists(v, 2, lists, pos, "while evaluating one of the elements to concatenate");
} }
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos) void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx)
{ {
nrListConcats++; nrListConcats++;
Value * nonEmpty = 0; Value * nonEmpty = 0;
size_t len = 0; size_t len = 0;
for (size_t n = 0; n < nrLists; ++n) { for (size_t n = 0; n < nrLists; ++n) {
forceList(*lists[n], pos); forceList(*lists[n], pos, errorCtx);
auto l = lists[n]->listSize(); auto l = lists[n]->listSize();
len += l; len += l;
if (l) nonEmpty = lists[n]; if (l) nonEmpty = lists[n];
@ -2017,20 +1914,20 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n; nf = n;
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, *this); state.error("cannot add %1% to an integer", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
} else if (firstType == nFloat) { } else if (firstType == nFloat) {
if (vTmp.type() == nInt) { if (vTmp.type() == nInt) {
nf += vTmp.integer; nf += vTmp.integer;
} else if (vTmp.type() == nFloat) { } else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, *this); state.error("cannot add %1% to a float", showType(vTmp)).atPos(i_pos).withFrame(env, *this).debugThrow<EvalError>();
} else { } else {
if (s.empty()) s.reserve(es->size()); if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not /* skip canonization of first path, which would only be not
canonized in the first place if it's coming from a ./${foo} type canonized in the first place if it's coming from a ./${foo} type
path */ path */
auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first); auto part = state.coerceToString(i_pos, vTmp, context, false, firstType == nString, !first, "while evaluating a path segment");
sSize += part->size(); sSize += part->size();
s.emplace_back(std::move(part)); s.emplace_back(std::move(part));
} }
@ -2044,7 +1941,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
v.mkFloat(nf); v.mkFloat(nf);
else if (firstType == nPath) { else if (firstType == nPath) {
if (!context.empty()) if (!context.empty())
state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path", env, *this); state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
v.mkPath(canonPath(str())); v.mkPath(canonPath(str()));
} else } else
v.mkStringMove(c_str(), context); v.mkStringMove(c_str(), context);
@ -2094,33 +1991,47 @@ void EvalState::forceValueDeep(Value & v)
} }
NixInt EvalState::forceInt(Value & v, const PosIdx pos) NixInt EvalState::forceInt(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v, pos); try {
if (v.type() != nInt) forceValue(v, pos);
throwTypeError(pos, "value is %1% while an integer was expected", v); if (v.type() != nInt)
error("value is %1% while an integer was expected", showType(v)).debugThrow<TypeError>();
return v.integer;
}
NixFloat EvalState::forceFloat(Value & v, const PosIdx pos)
{
forceValue(v, pos);
if (v.type() == nInt)
return v.integer; return v.integer;
else if (v.type() != nFloat) } catch (Error & e) {
throwTypeError(pos, "value is %1% while a float was expected", v); e.addTrace(positions[pos], errorCtx);
return v.fpoint; throw;
}
} }
bool EvalState::forceBool(Value & v, const PosIdx pos) NixFloat EvalState::forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v, pos); try {
if (v.type() != nBool) forceValue(v, pos);
throwTypeError(pos, "value is %1% while a Boolean was expected", v); if (v.type() == nInt)
return v.boolean; return v.integer;
else if (v.type() != nFloat)
error("value is %1% while a float was expected", showType(v)).debugThrow<TypeError>();
return v.fpoint;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
}
bool EvalState::forceBool(Value & v, const PosIdx pos, std::string_view errorCtx)
{
try {
forceValue(v, pos);
if (v.type() != nBool)
error("value is %1% while a Boolean was expected", showType(v)).debugThrow<TypeError>();
return v.boolean;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
@ -2130,21 +2041,30 @@ bool EvalState::isFunctor(Value & fun)
} }
void EvalState::forceFunction(Value & v, const PosIdx pos) void EvalState::forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v, pos); try {
if (v.type() != nFunction && !isFunctor(v)) forceValue(v, pos);
throwTypeError(pos, "value is %1% while a function was expected", v); if (v.type() != nFunction && !isFunctor(v))
error("value is %1% while a function was expected", showType(v)).debugThrow<TypeError>();
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
} }
std::string_view EvalState::forceString(Value & v, const PosIdx pos) std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v, pos); try {
if (v.type() != nString) { forceValue(v, pos);
throwTypeError(pos, "value is %1% while a string was expected", v); if (v.type() != nString)
error("value is %1% while a string was expected", showType(v)).debugThrow<TypeError>();
return v.string.s;
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
} }
return v.string.s;
} }
@ -2188,24 +2108,19 @@ NixStringContext Value::getContext(const Store & store)
} }
std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos) std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx)
{ {
auto s = forceString(v, pos); auto s = forceString(v, pos, errorCtx);
copyContext(v, context); copyContext(v, context);
return s; return s;
} }
std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos) std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
auto s = forceString(v, pos); auto s = forceString(v, pos, errorCtx);
if (v.string.context) { if (v.string.context) {
if (pos) error("the string '%1%' is not allowed to refer to a store path (such as '%2%')", v.string.s, v.string.context[0]).withTrace(pos, errorCtx).debugThrow<EvalError>();
throwEvalError(pos, "the string '%1%' is not allowed to refer to a store path (such as '%2%')",
v.string.s, v.string.context[0]);
else
throwEvalError("the string '%1%' is not allowed to refer to a store path (such as '%2%')",
v.string.s, v.string.context[0]);
} }
return s; return s;
} }
@ -2229,14 +2144,15 @@ 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).toOwned(); return coerceToString(pos, v1, context, coerceMore, copyToStore,
"while evaluating the result of the `toString` attribute").toOwned();
} }
return {}; return {};
} }
BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context, BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
bool coerceMore, bool copyToStore, bool canonicalizePath) bool coerceMore, bool copyToStore, bool canonicalizePath, std::string_view errorCtx)
{ {
forceValue(v, pos); forceValue(v, pos);
@ -2260,12 +2176,12 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
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())
throwTypeError(pos, "cannot coerce a set to a string"); error("cannot coerce a set to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
return coerceToString(pos, *i->value, context, coerceMore, copyToStore); return coerceToString(pos, *i->value, context, coerceMore, copyToStore, canonicalizePath, errorCtx);
} }
if (v.type() == nExternal) if (v.type() == nExternal)
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore); return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore, errorCtx);
if (coerceMore) { if (coerceMore) {
/* Note that `false' is represented as an empty string for /* Note that `false' is represented as an empty string for
@ -2279,7 +2195,13 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
if (v.isList()) { if (v.isList()) {
std::string result; std::string result;
for (auto [n, v2] : enumerate(v.listItems())) { for (auto [n, v2] : enumerate(v.listItems())) {
result += *coerceToString(pos, *v2, context, coerceMore, copyToStore); try {
result += *coerceToString(noPos, *v2, context, coerceMore, copyToStore, canonicalizePath,
"while evaluating one element of the list");
} catch (Error & e) {
e.addTrace(positions[pos], errorCtx);
throw;
}
if (n < v.listSize() - 1 if (n < v.listSize() - 1
/* !!! not quite correct */ /* !!! not quite correct */
&& (!v2->isList() || v2->listSize() != 0)) && (!v2->isList() || v2->listSize() != 0))
@ -2289,14 +2211,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
} }
} }
throwTypeError(pos, "cannot coerce %1% to a string", v); error("cannot coerce %1% to a string", showType(v)).withTrace(pos, errorCtx).debugThrow<TypeError>();
} }
std::string EvalState::copyPathToStore(PathSet & context, const Path & path) std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
{ {
if (nix::isDerivation(path)) if (nix::isDerivation(path))
throwEvalError("file names are not allowed to end in '%1%'", drvExtension); error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
Path dstPath; Path dstPath;
auto i = srcToStore.find(path); auto i = srcToStore.find(path);
@ -2317,28 +2239,25 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
} }
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context) Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{ {
auto path = coerceToString(pos, v, context, false, false).toOwned(); auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
if (path == "" || path[0] != '/') if (path == "" || path[0] != '/')
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path); error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
return path; return path;
} }
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context) StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
{ {
auto path = coerceToString(pos, v, context, false, false).toOwned(); auto path = coerceToString(pos, v, context, false, false, true, errorCtx).toOwned();
if (auto storePath = store->maybeParseStorePath(path)) if (auto storePath = store->maybeParseStorePath(path))
return *storePath; return *storePath;
throw EvalError({ error("path '%1%' is not in the Nix store", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
.msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = positions[pos]
});
} }
bool EvalState::eqValues(Value & v1, Value & v2) bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v1, noPos); forceValue(v1, noPos);
forceValue(v2, noPos); forceValue(v2, noPos);
@ -2358,7 +2277,6 @@ bool EvalState::eqValues(Value & v1, Value & v2)
if (v1.type() != v2.type()) return false; if (v1.type() != v2.type()) return false;
switch (v1.type()) { switch (v1.type()) {
case nInt: case nInt:
return v1.integer == v2.integer; return v1.integer == v2.integer;
@ -2377,7 +2295,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
case nList: case nList:
if (v1.listSize() != v2.listSize()) return false; if (v1.listSize() != v2.listSize()) return false;
for (size_t n = 0; n < v1.listSize(); ++n) for (size_t n = 0; n < v1.listSize(); ++n)
if (!eqValues(*v1.listElems()[n], *v2.listElems()[n])) return false; if (!eqValues(*v1.listElems()[n], *v2.listElems()[n], pos, errorCtx)) return false;
return true; return true;
case nAttrs: { case nAttrs: {
@ -2387,7 +2305,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
Bindings::iterator i = v1.attrs->find(sOutPath); Bindings::iterator i = v1.attrs->find(sOutPath);
Bindings::iterator j = v2.attrs->find(sOutPath); Bindings::iterator j = v2.attrs->find(sOutPath);
if (i != v1.attrs->end() && j != v2.attrs->end()) if (i != v1.attrs->end() && j != v2.attrs->end())
return eqValues(*i->value, *j->value); return eqValues(*i->value, *j->value, pos, errorCtx);
} }
if (v1.attrs->size() != v2.attrs->size()) return false; if (v1.attrs->size() != v2.attrs->size()) return false;
@ -2395,7 +2313,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
/* Otherwise, compare the attributes one by one. */ /* Otherwise, compare the attributes one by one. */
Bindings::iterator i, j; Bindings::iterator i, j;
for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j) for (i = v1.attrs->begin(), j = v2.attrs->begin(); i != v1.attrs->end(); ++i, ++j)
if (i->name != j->name || !eqValues(*i->value, *j->value)) if (i->name != j->name || !eqValues(*i->value, *j->value, pos, errorCtx))
return false; return false;
return true; return true;
@ -2412,9 +2330,7 @@ bool EvalState::eqValues(Value & v1, Value & v2)
return v1.fpoint == v2.fpoint; return v1.fpoint == v2.fpoint;
default: default:
throwEvalError("cannot compare %1% with %2%", error("cannot compare %1% with %2%", showType(v1), showType(v2)).withTrace(pos, errorCtx).debugThrow<EvalError>();
showType(v1),
showType(v2));
} }
} }
@ -2538,12 +2454,13 @@ void EvalState::printStats()
} }
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) const
{ {
throw TypeError({ auto e = TypeError({
.msg = hintfmt("cannot coerce %1% to a string", showType()), .msg = hintfmt("cannot coerce %1% to a string", showType())
.errPos = pos
}); });
e.addTrace(pos, errorCtx);
throw e;
} }

View file

@ -60,7 +60,6 @@ void copyContext(const Value & v, PathSet & context);
typedef std::map<Path, StorePath> SrcToStore; typedef std::map<Path, StorePath> SrcToStore;
std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v);
std::string printValue(const EvalState & state, const Value & v); std::string printValue(const EvalState & state, const Value & v);
std::ostream & operator << (std::ostream & os, const ValueType t); std::ostream & operator << (std::ostream & os, const ValueType t);
@ -87,6 +86,43 @@ struct DebugTrace {
void debugError(Error * e, Env & env, Expr & expr); void debugError(Error * e, Env & env, Expr & expr);
class ErrorBuilder
{
private:
EvalState & state;
ErrorInfo info;
ErrorBuilder(EvalState & s, ErrorInfo && i): state(s), info(i) { }
public:
template<typename... Args>
[[nodiscard, gnu::noinline]]
static ErrorBuilder * create(EvalState & s, const Args & ... args)
{
return new ErrorBuilder(s, ErrorInfo { .msg = hintfmt(args...) });
}
[[nodiscard, gnu::noinline]]
ErrorBuilder & atPos(PosIdx pos);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withFrameTrace(PosIdx pos, const std::string_view text);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withSuggestions(Suggestions & s);
[[nodiscard, gnu::noinline]]
ErrorBuilder & withFrame(const Env & e, const Expr & ex);
template<class ErrorType>
[[gnu::noinline, gnu::noreturn]]
void debugThrow();
};
class EvalState : public std::enable_shared_from_this<EvalState> class EvalState : public std::enable_shared_from_this<EvalState>
{ {
public: public:
@ -146,29 +182,35 @@ public:
template<class E> template<class E>
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void debugThrow(E && error, const Env & env, const Expr & expr) void debugThrowLastTrace(E && error)
{ {
if (debugRepl) debugThrow(error, nullptr, nullptr);
runDebugRepl(&error, env, expr);
throw std::move(error);
} }
template<class E> template<class E>
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void debugThrowLastTrace(E && e) void debugThrow(E && error, const Env * env, const Expr * expr)
{ {
// Call this in the situation where Expr and Env are inaccessible. if (debugRepl && ((env && expr) || !debugTraces.empty())) {
// The debugger will start in the last context that's in the if (!env || !expr) {
// DebugTrace stack. const DebugTrace & last = debugTraces.front();
if (debugRepl && !debugTraces.empty()) { env = &last.env;
const DebugTrace & last = debugTraces.front(); expr = &last.expr;
runDebugRepl(&e, last.env, last.expr); }
runDebugRepl(&error, *env, *expr);
} }
throw std::move(e); throw std::move(error);
} }
ErrorBuilder * errorBuilder;
template<typename... Args>
[[nodiscard, gnu::noinline]]
ErrorBuilder & error(const Args & ... args) {
errorBuilder = ErrorBuilder::create(*this, args...);
return *errorBuilder;
}
private: private:
SrcToStore srcToStore; SrcToStore srcToStore;
@ -283,8 +325,8 @@ public:
/* Evaluation the expression, then verify that it has the expected /* Evaluation the expression, then verify that it has the expected
type. */ type. */
inline bool evalBool(Env & env, Expr * e); inline bool evalBool(Env & env, Expr * e);
inline bool evalBool(Env & env, Expr * e, const PosIdx pos); inline bool evalBool(Env & env, Expr * e, const PosIdx pos, std::string_view errorCtx);
inline void evalAttrs(Env & env, Expr * e, Value & v); inline void evalAttrs(Env & env, Expr * e, Value & v, const PosIdx pos, std::string_view errorCtx);
/* If `v' is a thunk, enter it and overwrite `v' with the result /* If `v' is a thunk, enter it and overwrite `v' with the result
of the evaluation of the thunk. If `v' is a delayed function of the evaluation of the thunk. If `v' is a delayed function
@ -300,89 +342,25 @@ public:
void forceValueDeep(Value & v); void forceValueDeep(Value & v);
/* Force `v', and then verify that it has the expected type. */ /* Force `v', and then verify that it has the expected type. */
NixInt forceInt(Value & v, const PosIdx pos); NixInt forceInt(Value & v, const PosIdx pos, std::string_view errorCtx);
NixFloat forceFloat(Value & v, const PosIdx pos); NixFloat forceFloat(Value & v, const PosIdx pos, std::string_view errorCtx);
bool forceBool(Value & v, const PosIdx pos); bool forceBool(Value & v, const PosIdx pos, std::string_view errorCtx);
void forceAttrs(Value & v, const PosIdx pos); void forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx);
template <typename Callable> template <typename Callable>
inline void forceAttrs(Value & v, Callable getPos); inline void forceAttrs(Value & v, Callable getPos, std::string_view errorCtx);
inline void forceList(Value & v, const PosIdx pos); inline void forceList(Value & v, const PosIdx pos, std::string_view errorCtx);
void forceFunction(Value & v, const PosIdx pos); // either lambda or primop void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx); // either lambda or primop
std::string_view forceString(Value & v, const PosIdx pos = noPos); std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx);
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos = noPos); std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx);
std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2, const std::string & s3,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2, const std::string & s3);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const Value & v);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const Value & v,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const char * s, const Value & v,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline]] [[gnu::noinline]]
void addErrorTrace(Error & e, const char * s, const std::string & s2) const; void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
[[gnu::noinline]] [[gnu::noinline]]
void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const; void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2, bool frame = false) const;
public: public:
/* Return true iff the value `v' denotes a derivation (i.e. a /* Return true iff the value `v' denotes a derivation (i.e. a
@ -398,17 +376,18 @@ public:
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, bool coerceMore = false, bool copyToStore = true,
bool canonicalizePath = true); bool canonicalizePath = true,
std::string_view errorCtx = "");
std::string copyPathToStore(PathSet & context, const Path & path); std::string copyPathToStore(PathSet & context, const Path & path);
/* Path coercion. Converts strings, paths and derivations to a /* Path coercion. Converts strings, paths and derivations to a
path. The result is guaranteed to be a canonicalised, absolute path. The result is guaranteed to be a canonicalised, absolute
path. Nothing is copied to the store. */ path. Nothing is copied to the store. */
Path coerceToPath(const PosIdx pos, Value & v, PathSet & context); Path coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
/* Like coerceToPath, but the result must be a store path. */ /* Like coerceToPath, but the result must be a store path. */
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context); StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
public: public:
@ -468,7 +447,7 @@ public:
/* Do a deep equality test between two values. That is, list /* Do a deep equality test between two values. That is, list
elements and attributes are compared recursively. */ elements and attributes are compared recursively. */
bool eqValues(Value & v1, Value & v2); bool eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_view errorCtx);
bool isFunctor(Value & fun); bool isFunctor(Value & fun);
@ -503,7 +482,7 @@ public:
void mkThunk_(Value & v, Expr * expr); void mkThunk_(Value & v, Expr * expr);
void mkPos(Value & v, PosIdx pos); void mkPos(Value & v, PosIdx pos);
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos); void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
/* Print statistics. */ /* Print statistics. */
void printStats(); void printStats();
@ -670,6 +649,13 @@ extern EvalSettings evalSettings;
static const std::string corepkgsPrefix{"/__corepkgs__/"}; static const std::string corepkgsPrefix{"/__corepkgs__/"};
template<class ErrorType>
void ErrorBuilder::debugThrow()
{
// NOTE: We always use the -LastTrace version as we push the new trace in withFrame()
state.debugThrowLastTrace(ErrorType(info));
}
} }
#include "eval-inline.hh" #include "eval-inline.hh"

View file

@ -259,28 +259,28 @@ static Flake getFlake(
if (setting.value->type() == nString) if (setting.value->type() == nString)
flake.config.settings.emplace( flake.config.settings.emplace(
state.symbols[setting.name], state.symbols[setting.name],
std::string(state.forceStringNoCtx(*setting.value, setting.pos))); std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
else if (setting.value->type() == nPath) { else if (setting.value->type() == nPath) {
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, true) .toOwned()); state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true, "") .toOwned());
} }
else if (setting.value->type() == nInt) else if (setting.value->type() == nInt)
flake.config.settings.emplace( flake.config.settings.emplace(
state.symbols[setting.name], state.symbols[setting.name],
state.forceInt(*setting.value, setting.pos)); state.forceInt(*setting.value, setting.pos, ""));
else if (setting.value->type() == nBool) else if (setting.value->type() == nBool)
flake.config.settings.emplace( flake.config.settings.emplace(
state.symbols[setting.name], state.symbols[setting.name],
Explicit<bool> { state.forceBool(*setting.value, setting.pos) }); Explicit<bool> { state.forceBool(*setting.value, setting.pos, "") });
else if (setting.value->type() == nList) { else if (setting.value->type() == nList) {
std::vector<std::string> ss; std::vector<std::string> ss;
for (auto elem : setting.value->listItems()) { for (auto elem : setting.value->listItems()) {
if (elem->type() != nString) if (elem->type() != nString)
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected", throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
state.symbols[setting.name], showType(*setting.value)); state.symbols[setting.name], showType(*setting.value));
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos)); ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos, ""));
} }
flake.config.settings.emplace(state.symbols[setting.name], ss); flake.config.settings.emplace(state.symbols[setting.name], ss);
} }
@ -741,7 +741,7 @@ void callFlake(EvalState & state,
static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos)); std::string flakeRefS(state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.getFlake"));
auto flakeRef = parseFlakeRef(flakeRefS, {}, true); auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
if (evalSettings.pureEval && !flakeRef.input.isLocked()) if (evalSettings.pureEval && !flakeRef.input.isLocked())
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]); throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, state.positions[pos]);

View file

@ -51,7 +51,7 @@ std::string DrvInfo::queryName() const
if (name == "" && attrs) { if (name == "" && attrs) {
auto i = attrs->find(state->sName); auto i = attrs->find(state->sName);
if (i == attrs->end()) throw TypeError("derivation name missing"); if (i == attrs->end()) throw TypeError("derivation name missing");
name = state->forceStringNoCtx(*i->value); name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
} }
return name; return name;
} }
@ -61,7 +61,7 @@ std::string DrvInfo::querySystem() const
{ {
if (system == "" && attrs) { if (system == "" && attrs) {
auto i = attrs->find(state->sSystem); auto i = attrs->find(state->sSystem);
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos); system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
} }
return system; return system;
} }
@ -75,7 +75,7 @@ std::optional<StorePath> DrvInfo::queryDrvPath() const
if (i == attrs->end()) if (i == attrs->end())
drvPath = {std::nullopt}; drvPath = {std::nullopt};
else else
drvPath = {state->coerceToStorePath(i->pos, *i->value, context)}; drvPath = {state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation")};
} }
return drvPath.value_or(std::nullopt); return drvPath.value_or(std::nullopt);
} }
@ -95,7 +95,7 @@ StorePath DrvInfo::queryOutPath() const
Bindings::iterator i = attrs->find(state->sOutPath); Bindings::iterator i = attrs->find(state->sOutPath);
PathSet context; PathSet context;
if (i != attrs->end()) if (i != attrs->end())
outPath = state->coerceToStorePath(i->pos, *i->value, context); outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
} }
if (!outPath) if (!outPath)
throw UnimplementedError("CA derivations are not yet supported"); throw UnimplementedError("CA derivations are not yet supported");
@ -109,23 +109,23 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
/* Get the outputs list. */ /* Get the outputs list. */
Bindings::iterator i; Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) { if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
state->forceList(*i->value, i->pos); state->forceList(*i->value, i->pos, "while evaluating the 'outputs' attribute of a derivation");
/* For each output... */ /* For each output... */
for (auto elem : i->value->listItems()) { for (auto elem : i->value->listItems()) {
std::string output(state->forceStringNoCtx(*elem, i->pos)); std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation"));
if (withPaths) { if (withPaths) {
/* Evaluate the corresponding set. */ /* Evaluate the corresponding set. */
Bindings::iterator out = attrs->find(state->symbols.create(output)); Bindings::iterator out = attrs->find(state->symbols.create(output));
if (out == attrs->end()) continue; // FIXME: throw error? if (out == attrs->end()) continue; // FIXME: throw error?
state->forceAttrs(*out->value, i->pos); state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
/* And evaluate its outPath attribute. */ /* And evaluate its outPath attribute. */
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath); Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error? if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
PathSet context; PathSet context;
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context)); outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
} else } else
outputs.emplace(output, std::nullopt); outputs.emplace(output, std::nullopt);
} }
@ -137,7 +137,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
return outputs; return outputs;
Bindings::iterator i; Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos)) { if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
Outputs result; Outputs result;
auto out = outputs.find(queryOutputName()); auto out = outputs.find(queryOutputName());
if (out == outputs.end()) if (out == outputs.end())
@ -169,7 +169,7 @@ std::string DrvInfo::queryOutputName() const
{ {
if (outputName == "" && attrs) { if (outputName == "" && attrs) {
Bindings::iterator i = attrs->find(state->sOutputName); Bindings::iterator i = attrs->find(state->sOutputName);
outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value) : ""; outputName = i != attrs->end() ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
} }
return outputName; return outputName;
} }
@ -181,7 +181,7 @@ Bindings * DrvInfo::getMeta()
if (!attrs) return 0; if (!attrs) return 0;
Bindings::iterator a = attrs->find(state->sMeta); Bindings::iterator a = attrs->find(state->sMeta);
if (a == attrs->end()) return 0; if (a == attrs->end()) return 0;
state->forceAttrs(*a->value, a->pos); state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value->attrs; meta = a->value->attrs;
return meta; return meta;
} }
@ -382,7 +382,7 @@ static void getDerivations(EvalState & state, Value & vIn,
`recurseForDerivations = true' attribute. */ `recurseForDerivations = true' attribute. */
if (i->value->type() == nAttrs) { if (i->value->type() == nAttrs) {
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations); Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos)) if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
} }
} }

View file

@ -8,7 +8,6 @@
#include "error.hh" #include "error.hh"
#include "chunked-vector.hh" #include "chunked-vector.hh"
namespace nix { namespace nix {

View file

@ -401,22 +401,22 @@ expr_op
| '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); } | '-' expr_op %prec NEGATE { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {new ExprInt(0), $2}); }
| expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); } | expr_op EQ expr_op { $$ = new ExprOpEq($1, $3); }
| expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); } | expr_op NEQ expr_op { $$ = new ExprOpNEq($1, $3); }
| expr_op '<' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); } | expr_op '<' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3}); }
| expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); } | expr_op LEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1})); }
| expr_op '>' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); } | expr_op '>' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$3, $1}); }
| expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); } | expr_op GEQ expr_op { $$ = new ExprOpNot(new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__lessThan")), {$1, $3})); }
| expr_op AND expr_op { $$ = new ExprOpAnd(CUR_POS, $1, $3); } | expr_op AND expr_op { $$ = new ExprOpAnd(makeCurPos(@2, data), $1, $3); }
| expr_op OR expr_op { $$ = new ExprOpOr(CUR_POS, $1, $3); } | expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); }
| expr_op IMPL expr_op { $$ = new ExprOpImpl(CUR_POS, $1, $3); } | expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); }
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); } | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(makeCurPos(@2, data), $1, $3); }
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
| expr_op '+' expr_op | expr_op '+' expr_op
{ $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<PosIdx, Expr *>>({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } { $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector<std::pair<PosIdx, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
| expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); } | expr_op '-' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
| expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); } | expr_op '*' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
| expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); } | expr_op '/' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__div")), {$1, $3}); }
| expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(CUR_POS, $1, $3); } | expr_op CONCAT expr_op { $$ = new ExprOpConcatLists(makeCurPos(@2, data), $1, $3); }
| expr_op '$' expr_op { $$ = new ExprCall(CUR_POS, $1, {$3}); } | expr_op '$' expr_op { $$ = new ExprCall(makeCurPos(@2, data), $1, {$3}); }
| expr_app | expr_app
; ;
@ -784,13 +784,13 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
if (hasPrefix(path, "nix/")) if (hasPrefix(path, "nix/"))
return concatStrings(corepkgsPrefix, path.substr(4)); return concatStrings(corepkgsPrefix, path.substr(4));
debugThrowLastTrace(ThrownError({ debugThrow(ThrownError({
.msg = hintfmt(evalSettings.pureEval .msg = hintfmt(evalSettings.pureEval
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)" ? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)", : "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
path), path),
.errPos = positions[pos] .errPos = positions[pos]
})); }), 0, 0);
} }

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,7 @@ namespace nix {
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
auto s = state.coerceToString(pos, *args[0], context); auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
v.mkString(*s); v.mkString(*s);
} }
@ -18,7 +18,7 @@ static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringCo
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
state.forceString(*args[0], context, pos); state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
v.mkBool(!context.empty()); v.mkBool(!context.empty());
} }
@ -34,7 +34,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
auto s = state.coerceToString(pos, *args[0], context); auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
PathSet context2; PathSet context2;
for (auto & p : context) for (auto & p : context)
@ -73,7 +73,7 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
Strings outputs; Strings outputs;
}; };
PathSet context; PathSet context;
state.forceString(*args[0], context, pos); state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
auto contextInfos = std::map<Path, ContextInfo>(); auto contextInfos = std::map<Path, ContextInfo>();
for (const auto & p : context) { for (const auto & p : context) {
Path drv; Path drv;
@ -137,9 +137,9 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
PathSet context; PathSet context;
auto orig = state.forceString(*args[0], context, pos); auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
state.forceAttrs(*args[1], pos); state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
auto sPath = state.symbols.create("path"); auto sPath = state.symbols.create("path");
auto sAllOutputs = state.symbols.create("allOutputs"); auto sAllOutputs = state.symbols.create("allOutputs");
@ -147,24 +147,24 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
const auto & name = state.symbols[i.name]; const auto & name = state.symbols[i.name];
if (!state.store->isStorePath(name)) if (!state.store->isStorePath(name))
throw EvalError({ throw EvalError({
.msg = hintfmt("Context key '%s' is not a store path", name), .msg = hintfmt("context key '%s' is not a store path", name),
.errPos = state.positions[i.pos] .errPos = state.positions[i.pos]
}); });
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(state.store->parseStorePath(name)); state.store->ensurePath(state.store->parseStorePath(name));
state.forceAttrs(*i.value, i.pos); state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
auto iter = i.value->attrs->find(sPath); auto iter = i.value->attrs->find(sPath);
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos)) if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
context.emplace(name); context.emplace(name);
} }
iter = i.value->attrs->find(sAllOutputs); iter = i.value->attrs->find(sAllOutputs);
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
if (state.forceBool(*iter->value, iter->pos)) { if (state.forceBool(*iter->value, iter->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(name)) { if (!isDerivation(name)) {
throw EvalError({ throw EvalError({
.msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", name), .msg = hintfmt("tried to add all-outputs context of %s, which is not a derivation, to a string", name),
.errPos = state.positions[i.pos] .errPos = state.positions[i.pos]
}); });
} }
@ -174,15 +174,15 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
iter = i.value->attrs->find(state.sOutputs); iter = i.value->attrs->find(state.sOutputs);
if (iter != i.value->attrs->end()) { if (iter != i.value->attrs->end()) {
state.forceList(*iter->value, iter->pos); state.forceList(*iter->value, iter->pos, "while evaluating the `outputs` attribute of a string context");
if (iter->value->listSize() && !isDerivation(name)) { if (iter->value->listSize() && !isDerivation(name)) {
throw EvalError({ throw EvalError({
.msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", name), .msg = hintfmt("tried to add derivation output context of %s, which is not a derivation, to a string", name),
.errPos = state.positions[i.pos] .errPos = state.positions[i.pos]
}); });
} }
for (auto elem : iter->value->listItems()) { for (auto elem : iter->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, iter->pos); auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
context.insert(concatStrings("!", outputName, "!", name)); context.insert(concatStrings("!", outputName, "!", name));
} }
} }

View file

@ -7,7 +7,7 @@ namespace nix {
static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
state.forceAttrs(*args[0], pos); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure");
std::optional<std::string> fromStoreUrl; std::optional<std::string> fromStoreUrl;
std::optional<StorePath> fromPath; std::optional<StorePath> fromPath;
@ -19,7 +19,8 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
if (attrName == "fromPath") { if (attrName == "fromPath") {
PathSet context; PathSet context;
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context); fromPath = state.coerceToStorePath(attr.pos, *attr.value, context,
"while evaluating the 'fromPath' attribute passed to builtins.fetchClosure");
} }
else if (attrName == "toPath") { else if (attrName == "toPath") {
@ -27,12 +28,14 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
toCA = true; toCA = true;
if (attr.value->type() != nString || attr.value->string.s != std::string("")) { if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
PathSet context; PathSet context;
toPath = state.coerceToStorePath(attr.pos, *attr.value, context); toPath = state.coerceToStorePath(attr.pos, *attr.value, context,
"while evaluating the 'toPath' attribute passed to builtins.fetchClosure");
} }
} }
else if (attrName == "fromStore") else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos); fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos,
"while evaluating the 'fromStore' attribute passed to builtins.fetchClosure");
else else
throw Error({ throw Error({

View file

@ -19,23 +19,21 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
if (args[0]->type() == nAttrs) { if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos);
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).toOwned(); url = state.coerceToString(attr.pos, *attr.value, context, false, false, "while evaluating the `url` attribute passed to builtins.fetchMercurial").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.
auto value = state.forceStringNoCtx(*attr.value, attr.pos); auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
if (std::regex_match(value.begin(), value.end(), revRegex)) if (std::regex_match(value.begin(), value.end(), revRegex))
rev = Hash::parseAny(value, htSHA1); rev = Hash::parseAny(value, htSHA1);
else else
ref = value; ref = value;
} }
else if (n == "name") else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos); name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else else
throw EvalError({ throw EvalError({
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]), .msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
@ -50,7 +48,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
}); });
} else } else
url = state.coerceToString(pos, *args[0], context, false, false).toOwned(); url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to builtins.fetchMercurial").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

@ -102,7 +102,7 @@ static void fetchTree(
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
if (args[0]->type() == nAttrs) { if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchTree");
fetchers::Attrs attrs; fetchers::Attrs attrs;
@ -112,7 +112,7 @@ static void fetchTree(
.msg = hintfmt("unexpected attribute 'type'"), .msg = hintfmt("unexpected attribute 'type'"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
})); }));
type = state.forceStringNoCtx(*aType->value, aType->pos); type = state.forceStringNoCtx(*aType->value, aType->pos, "while evaluating the `type` attribute passed to builtins.fetchTree");
} else if (!type) } else if (!type)
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
@ -125,7 +125,7 @@ static void fetchTree(
if (attr.name == state.sType) continue; if (attr.name == state.sType) continue;
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"
@ -151,7 +151,7 @@ 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).toOwned(); auto url = state.coerceToString(pos, *args[0], context, false, false, "while evaluating the first argument passed to the fetcher").toOwned();
if (type == "git") { if (type == "git") {
fetchers::Attrs attrs; fetchers::Attrs attrs;
@ -195,16 +195,14 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
if (args[0]->type() == nAttrs) { if (args[0]->type() == nAttrs) {
state.forceAttrs(*args[0], pos);
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.forceStringNoCtx(*attr.value, attr.pos); url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
else if (n == "sha256") else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256); expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), htSHA256);
else if (n == "name") else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos); name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else else
state.debugThrowLastTrace(EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who), .msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
@ -218,7 +216,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
.errPos = state.positions[pos] .errPos = state.positions[pos]
})); }));
} else } else
url = state.forceStringNoCtx(*args[0], pos); url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
state.checkURI(*url); state.checkURI(*url);

View file

@ -7,7 +7,7 @@ namespace nix {
static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val) static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val)
{ {
auto toml = state.forceStringNoCtx(*args[0], pos); auto toml = state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.fromTOML");
std::istringstream tomlStream(std::string{toml}); std::istringstream tomlStream(std::string{toml});

View file

@ -0,0 +1,94 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "libexprtests.hh"
namespace nix {
using namespace testing;
// Testing eval of PrimOp's
class ErrorTraceTest : public LibExprTest { };
#define ASSERT_TRACE1(args, type, message) \
ASSERT_THROW( \
try { \
eval("builtins." args); \
} catch (BaseError & e) { \
ASSERT_EQ(PrintToString(e.info().msg), \
PrintToString(message)); \
auto trace = e.info().traces.rbegin(); \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
throw; \
} \
, type \
)
#define ASSERT_TRACE2(args, type, message, context) \
ASSERT_THROW( \
try { \
eval("builtins." args); \
} catch (BaseError & e) { \
ASSERT_EQ(PrintToString(e.info().msg), \
PrintToString(message)); \
auto trace = e.info().traces.rbegin(); \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(context)); \
++trace; \
ASSERT_EQ(PrintToString(trace->hint), \
PrintToString(hintfmt("while calling the '%s' builtin", "genericClosure"))); \
throw; \
} \
, type \
)
TEST_F(ErrorTraceTest, genericClosure) { \
ASSERT_TRACE2("genericClosure 1",
TypeError,
hintfmt("value is %s while a set was expected", "an integer"),
hintfmt("while evaluating the first argument passed to builtins.genericClosure"));
ASSERT_TRACE1("genericClosure {}",
TypeError,
hintfmt("attribute '%s' missing %s", "startSet", normaltxt("in the attrset passed as argument to builtins.genericClosure")));
ASSERT_TRACE2("genericClosure { startSet = 1; }",
TypeError,
hintfmt("value is %s while a list was expected", "an integer"),
hintfmt("while evaluating the 'startSet' attribute passed as argument to builtins.genericClosure"));
// Okay: "genericClosure { startSet = []; }"
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = true; }",
TypeError,
hintfmt("value is %s while a function was expected", "a Boolean"),
hintfmt("while evaluating the 'operator' attribute passed as argument to builtins.genericClosure"));
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: true; }",
TypeError,
hintfmt("value is %s while a list was expected", "a Boolean"),
hintfmt("while evaluating the return value of the `operator` passed to builtins.genericClosure")); // TODO: inconsistent naming
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [ true ]; }",
TypeError,
hintfmt("value is %s while a set was expected", "a Boolean"),
hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
ASSERT_TRACE1("genericClosure { startSet = [{ key = 1;}]; operator = item: [ {} ]; }",
TypeError,
hintfmt("attribute '%s' missing %s", "key", normaltxt("in one of the attrsets generated by (or initially passed to) builtins.genericClosure")));
ASSERT_TRACE2("genericClosure { startSet = [{ key = 1;}]; operator = item: [{ key = ''a''; }]; }",
EvalError,
hintfmt("cannot compare %s with %s", "a string", "an integer"),
hintfmt("while comparing the `key` attributes of two genericClosure elements"));
ASSERT_TRACE2("genericClosure { startSet = [ true ]; operator = item: [{ key = ''a''; }]; }",
TypeError,
hintfmt("value is %s while a set was expected", "a Boolean"),
hintfmt("while evaluating one of the elements generated by (or initially passed to) builtins.genericClosure"));
}
} /* namespace nix */

View file

@ -12,6 +12,7 @@ namespace nix {
class LibExprTest : public ::testing::Test { class LibExprTest : public ::testing::Test {
public: public:
static void SetUpTestSuite() { static void SetUpTestSuite() {
initLibStore();
initGC(); initGC();
} }

View file

@ -823,4 +823,10 @@ namespace nix {
for (const auto [n, elem] : enumerate(v.listItems())) for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsStringEq(expected[n])); ASSERT_THAT(*elem, IsStringEq(expected[n]));
} }
TEST_F(PrimOpTest, genericClosure_not_strict) {
// Operator should not be used when startSet is empty
auto v = eval("builtins.genericClosure { startSet = []; }");
ASSERT_THAT(v, IsListOfSize(0));
}
} /* namespace nix */ } /* namespace nix */

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) const; virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore, std::string_view errorCtx) 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

@ -235,6 +235,7 @@ void initNix()
#endif #endif
preloadNSS(); preloadNSS();
initLibStore();
} }
@ -362,6 +363,7 @@ void printVersion(const std::string & programName)
<< "\n"; << "\n";
std::cout << "Store directory: " << settings.nixStore << "\n"; std::cout << "Store directory: " << settings.nixStore << "\n";
std::cout << "State directory: " << settings.nixStateDir << "\n"; std::cout << "State directory: " << settings.nixStateDir << "\n";
std::cout << "Data directory: " << settings.nixDataDir << "\n";
} }
throw Exit(); throw Exit();
} }
@ -402,8 +404,6 @@ int handleExceptions(const std::string & programName, std::function<void()> fun)
return 1; return 1;
} catch (BaseError & e) { } catch (BaseError & e) {
logError(e.info()); logError(e.info());
if (e.hasTrace() && !loggerSettings.showTrace.get())
printError("(use '--show-trace' to show detailed location information)");
return e.status; return e.status;
} catch (std::bad_alloc & e) { } catch (std::bad_alloc & e) {
printError(error + "out of memory"); printError(error + "out of memory");

View file

@ -39,7 +39,6 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <net/if.h> #include <net/if.h>
#include <netinet/ip.h> #include <netinet/ip.h>
#include <sys/personality.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sched.h> #include <sched.h>
#include <sys/param.h> #include <sys/param.h>
@ -545,7 +544,8 @@ void DerivationGoal::inputsRealised()
However, the impure derivations feature still relies on this However, the impure derivations feature still relies on this
fragile way of doing things, because its builds do not have fragile way of doing things, because its builds do not have
a representation in the store, which is a usability problem a representation in the store, which is a usability problem
in itself */ in itself. When implementing this logic entirely with lookups
make sure that they're cached. */
if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) { if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) {
worker.store.computeFSClosure(*outPath, inputPaths); worker.store.computeFSClosure(*outPath, inputPaths);
} }

View file

@ -15,6 +15,7 @@
#include "callback.hh" #include "callback.hh"
#include "json-utils.hh" #include "json-utils.hh"
#include "cgroup.hh" #include "cgroup.hh"
#include "personality.hh"
#include <regex> #include <regex>
#include <queue> #include <queue>
@ -24,7 +25,6 @@
#include <termios.h> #include <termios.h>
#include <unistd.h> #include <unistd.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sys/utsname.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <sys/socket.h> #include <sys/socket.h>
@ -37,7 +37,6 @@
#include <sys/ioctl.h> #include <sys/ioctl.h>
#include <net/if.h> #include <net/if.h>
#include <netinet/ip.h> #include <netinet/ip.h>
#include <sys/personality.h>
#include <sys/mman.h> #include <sys/mman.h>
#include <sched.h> #include <sched.h>
#include <sys/param.h> #include <sys/param.h>
@ -1964,33 +1963,7 @@ void LocalDerivationGoal::runChild()
/* Close all other file descriptors. */ /* Close all other file descriptors. */
closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}); closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO});
#if __linux__ setPersonality(drv->platform);
/* Change the personality to 32-bit if we're doing an
i686-linux build on an x86_64-linux machine. */
struct utsname utsbuf;
uname(&utsbuf);
if ((drv->platform == "i686-linux"
&& (settings.thisSystem == "x86_64-linux"
|| (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
|| drv->platform == "armv7l-linux"
|| drv->platform == "armv6l-linux")
{
if (personality(PER_LINUX32) == -1)
throw SysError("cannot set 32-bit personality");
}
/* Impersonate a Linux 2.6 machine to get some determinism in
builds that depend on the kernel version. */
if ((drv->platform == "i686-linux" || drv->platform == "x86_64-linux") && settings.impersonateLinux26) {
int cur = personality(0xffffffff);
if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
}
/* Disable address space randomization for improved
determinism. */
int cur = personality(0xffffffff);
if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
#endif
/* Disable core dumps by default. */ /* Disable core dumps by default. */
struct rlimit limit = { 0, RLIM_INFINITY }; struct rlimit limit = { 0, RLIM_INFINITY };
@ -2077,10 +2050,14 @@ void LocalDerivationGoal::runChild()
sandboxProfile += "(deny default (with no-log))\n"; sandboxProfile += "(deny default (with no-log))\n";
} }
sandboxProfile += "(import \"sandbox-defaults.sb\")\n"; sandboxProfile +=
#include "sandbox-defaults.sb"
;
if (!derivationType.isSandboxed()) if (!derivationType.isSandboxed())
sandboxProfile += "(import \"sandbox-network.sb\")\n"; sandboxProfile +=
#include "sandbox-network.sb"
;
/* Add the output paths we'll use at build-time to the chroot */ /* Add the output paths we'll use at build-time to the chroot */
sandboxProfile += "(allow file-read* file-write* process-exec\n"; sandboxProfile += "(allow file-read* file-write* process-exec\n";
@ -2123,7 +2100,9 @@ void LocalDerivationGoal::runChild()
sandboxProfile += additionalSandboxProfile; sandboxProfile += additionalSandboxProfile;
} else } else
sandboxProfile += "(import \"sandbox-minimal.sb\")\n"; sandboxProfile +=
#include "sandbox-minimal.sb"
;
debug("Generated sandbox profile:"); debug("Generated sandbox profile:");
debug(sandboxProfile); debug(sandboxProfile);
@ -2148,8 +2127,6 @@ void LocalDerivationGoal::runChild()
args.push_back(sandboxFile); args.push_back(sandboxFile);
args.push_back("-D"); args.push_back("-D");
args.push_back("_GLOBAL_TMP_DIR=" + globalTmpDir); args.push_back("_GLOBAL_TMP_DIR=" + globalTmpDir);
args.push_back("-D");
args.push_back("IMPORT_DIR=" + settings.nixDataDir + "/nix/sandbox/");
if (allowLocalNetworking) { if (allowLocalNetworking) {
args.push_back("-D"); args.push_back("-D");
args.push_back(std::string("_ALLOW_LOCAL_NETWORKING=1")); args.push_back(std::string("_ALLOW_LOCAL_NETWORKING=1"));

View file

@ -0,0 +1,44 @@
#include "personality.hh"
#include "globals.hh"
#if __linux__
#include <sys/utsname.h>
#include <sys/personality.h>
#endif
#include <cstring>
namespace nix {
void setPersonality(std::string_view system)
{
#if __linux__
/* Change the personality to 32-bit if we're doing an
i686-linux build on an x86_64-linux machine. */
struct utsname utsbuf;
uname(&utsbuf);
if ((system == "i686-linux"
&& (std::string_view(SYSTEM) == "x86_64-linux"
|| (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64"))))
|| system == "armv7l-linux"
|| system == "armv6l-linux")
{
if (personality(PER_LINUX32) == -1)
throw SysError("cannot set 32-bit personality");
}
/* Impersonate a Linux 2.6 machine to get some determinism in
builds that depend on the kernel version. */
if ((system == "i686-linux" || system == "x86_64-linux") && settings.impersonateLinux26) {
int cur = personality(0xffffffff);
if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */);
}
/* Disable address space randomization for improved
determinism. */
int cur = personality(0xffffffff);
if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE);
#endif
}
}

View file

@ -0,0 +1,11 @@
#pragma once
#include <string>
namespace nix {
void setPersonality(std::string_view system);
}

View file

@ -1,3 +1,5 @@
R""(
(define TMPDIR (param "_GLOBAL_TMP_DIR")) (define TMPDIR (param "_GLOBAL_TMP_DIR"))
(deny default) (deny default)
@ -104,3 +106,5 @@
(subpath "/System/Library/Apple/usr/libexec/oah") (subpath "/System/Library/Apple/usr/libexec/oah")
(subpath "/System/Library/LaunchDaemons/com.apple.oahd.plist") (subpath "/System/Library/LaunchDaemons/com.apple.oahd.plist")
(subpath "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist")) (subpath "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist"))
)""

View file

@ -1,5 +1,9 @@
R""(
(allow default) (allow default)
; Disallow creating setuid/setgid binaries, since that ; Disallow creating setuid/setgid binaries, since that
; would allow breaking build user isolation. ; would allow breaking build user isolation.
(deny file-write-setugid) (deny file-write-setugid)
)""

View file

@ -1,3 +1,5 @@
R""(
; Allow local and remote network traffic. ; Allow local and remote network traffic.
(allow network* (local ip) (remote ip)) (allow network* (local ip) (remote ip))
@ -18,3 +20,5 @@
; Allow access to trustd. ; Allow access to trustd.
(allow mach-lookup (global-name "com.apple.trustd")) (allow mach-lookup (global-name "com.apple.trustd"))
(allow mach-lookup (global-name "com.apple.trustd.agent")) (allow mach-lookup (global-name "com.apple.trustd.agent"))
)""

View file

@ -95,7 +95,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
throw Error( throw Error(
"files '%1%' and '%2%' have the same priority %3%; " "files '%1%' and '%2%' have the same priority %3%; "
"use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' " "use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' "
"or type 'nix profile install --help' if using 'nix profile' to find out how" "or type 'nix profile install --help' if using 'nix profile' to find out how "
"to change the priority of one of the conflicting packages" "to change the priority of one of the conflicting packages"
" (0 being the highest priority)", " (0 being the highest priority)",
srcFile, readLink(dstFile), priority); srcFile, readLink(dstFile), priority);

View file

@ -77,60 +77,73 @@ Path LocalFSStore::addPermRoot(const StorePath & storePath, const Path & _gcRoot
} }
void LocalStore::addTempRoot(const StorePath & path) void LocalStore::createTempRootsFile()
{ {
auto state(_state.lock()); auto fdTempRoots(_fdTempRoots.lock());
/* Create the temporary roots file for this process. */ /* Create the temporary roots file for this process. */
if (!state->fdTempRoots) { if (*fdTempRoots) return;
while (1) { while (1) {
if (pathExists(fnTempRoots)) if (pathExists(fnTempRoots))
/* It *must* be stale, since there can be no two /* It *must* be stale, since there can be no two
processes with the same pid. */ processes with the same pid. */
unlink(fnTempRoots.c_str()); unlink(fnTempRoots.c_str());
state->fdTempRoots = openLockFile(fnTempRoots, true); *fdTempRoots = openLockFile(fnTempRoots, true);
debug("acquiring write lock on '%s'", fnTempRoots); debug("acquiring write lock on '%s'", fnTempRoots);
lockFile(state->fdTempRoots.get(), ltWrite, true); lockFile(fdTempRoots->get(), ltWrite, true);
/* Check whether the garbage collector didn't get in our /* Check whether the garbage collector didn't get in our
way. */ way. */
struct stat st; struct stat st;
if (fstat(state->fdTempRoots.get(), &st) == -1) if (fstat(fdTempRoots->get(), &st) == -1)
throw SysError("statting '%1%'", fnTempRoots); throw SysError("statting '%1%'", fnTempRoots);
if (st.st_size == 0) break; if (st.st_size == 0) break;
/* The garbage collector deleted this file before we could /* The garbage collector deleted this file before we could get
get a lock. (It won't delete the file after we get a a lock. (It won't delete the file after we get a lock.)
lock.) Try again. */ Try again. */
} }
}
void LocalStore::addTempRoot(const StorePath & path)
{
createTempRootsFile();
/* Open/create the global GC lock file. */
{
auto fdGCLock(_fdGCLock.lock());
if (!*fdGCLock)
*fdGCLock = openGCLock();
} }
if (!state->fdGCLock)
state->fdGCLock = openGCLock();
restart: restart:
FdLock gcLock(state->fdGCLock.get(), ltRead, false, ""); /* Try to acquire a shared global GC lock (non-blocking). This
only succeeds if the garbage collector is not currently
running. */
FdLock gcLock(_fdGCLock.lock()->get(), ltRead, false, "");
if (!gcLock.acquired) { if (!gcLock.acquired) {
/* We couldn't get a shared global GC lock, so the garbage /* We couldn't get a shared global GC lock, so the garbage
collector is running. So we have to connect to the garbage collector is running. So we have to connect to the garbage
collector and inform it about our root. */ collector and inform it about our root. */
if (!state->fdRootsSocket) { auto fdRootsSocket(_fdRootsSocket.lock());
if (!*fdRootsSocket) {
auto socketPath = stateDir.get() + gcSocketPath; auto socketPath = stateDir.get() + gcSocketPath;
debug("connecting to '%s'", socketPath); debug("connecting to '%s'", socketPath);
state->fdRootsSocket = createUnixDomainSocket(); *fdRootsSocket = createUnixDomainSocket();
try { try {
nix::connect(state->fdRootsSocket.get(), socketPath); nix::connect(fdRootsSocket->get(), socketPath);
} catch (SysError & e) { } catch (SysError & e) {
/* The garbage collector may have exited, so we need to /* The garbage collector may have exited, so we need to
restart. */ restart. */
if (e.errNo == ECONNREFUSED) { if (e.errNo == ECONNREFUSED) {
debug("GC socket connection refused"); debug("GC socket connection refused");
state->fdRootsSocket.close(); fdRootsSocket->close();
goto restart; goto restart;
} }
throw; throw;
@ -139,9 +152,9 @@ void LocalStore::addTempRoot(const StorePath & path)
try { try {
debug("sending GC root '%s'", printStorePath(path)); debug("sending GC root '%s'", printStorePath(path));
writeFull(state->fdRootsSocket.get(), printStorePath(path) + "\n", false); writeFull(fdRootsSocket->get(), printStorePath(path) + "\n", false);
char c; char c;
readFull(state->fdRootsSocket.get(), &c, 1); readFull(fdRootsSocket->get(), &c, 1);
assert(c == '1'); assert(c == '1');
debug("got ack for GC root '%s'", printStorePath(path)); debug("got ack for GC root '%s'", printStorePath(path));
} catch (SysError & e) { } catch (SysError & e) {
@ -149,20 +162,21 @@ void LocalStore::addTempRoot(const StorePath & path)
restart. */ restart. */
if (e.errNo == EPIPE || e.errNo == ECONNRESET) { if (e.errNo == EPIPE || e.errNo == ECONNRESET) {
debug("GC socket disconnected"); debug("GC socket disconnected");
state->fdRootsSocket.close(); fdRootsSocket->close();
goto restart; goto restart;
} }
throw; throw;
} catch (EndOfFile & e) { } catch (EndOfFile & e) {
debug("GC socket disconnected"); debug("GC socket disconnected");
state->fdRootsSocket.close(); fdRootsSocket->close();
goto restart; goto restart;
} }
} }
/* Append the store path to the temporary roots file. */ /* Record the store path in the temporary roots file so it will be
seen by a future run of the garbage collector. */
auto s = printStorePath(path) + '\0'; auto s = printStorePath(path) + '\0';
writeFull(state->fdTempRoots.get(), s); writeFull(_fdTempRoots.lock()->get(), s);
} }

View file

@ -290,4 +290,18 @@ void initPlugins()
settings.pluginFiles.pluginsLoaded = true; settings.pluginFiles.pluginsLoaded = true;
} }
static bool initLibStoreDone = false;
void assertLibStoreInitialized() {
if (!initLibStoreDone) {
printError("The program must call nix::initNix() before calling any libstore library functions.");
abort();
};
}
void initLibStore() {
initLibStoreDone = true;
}
} }

View file

@ -491,6 +491,9 @@ public:
for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` will for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` will
only be mounted in the sandbox if it exists in the host filesystem. only be mounted in the sandbox if it exists in the host filesystem.
If the source is in the Nix store, then its closure will be added to
the sandbox as well.
Depending on how Nix was built, the default value for this option Depending on how Nix was built, the default value for this option
may be empty or provide `/bin/sh` as a bind-mount of `bash`. may be empty or provide `/bin/sh` as a bind-mount of `bash`.
)", )",
@ -984,4 +987,12 @@ std::vector<Path> getUserConfigFiles();
extern const std::string nixVersion; extern const std::string nixVersion;
/* NB: This is not sufficient. You need to call initNix() */
void initLibStore();
/* It's important to initialize before doing _anything_, which is why we
call upon the programmer to handle this correctly. However, we only add
this in a key locations, so as not to litter the code. */
void assertLibStoreInitialized();
} }

View file

@ -91,6 +91,7 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
if (!lockFile(lockFd.get(), ltWrite, false)) { if (!lockFile(lockFd.get(), ltWrite, false)) {
printInfo("waiting for exclusive access to the Nix store for ca drvs..."); printInfo("waiting for exclusive access to the Nix store for ca drvs...");
lockFile(lockFd.get(), ltNone, false); // We have acquired a shared lock; release it to prevent deadlocks
lockFile(lockFd.get(), ltWrite, true); lockFile(lockFd.get(), ltWrite, true);
} }
@ -299,6 +300,7 @@ LocalStore::LocalStore(const Params & params)
if (!lockFile(globalLock.get(), ltWrite, false)) { if (!lockFile(globalLock.get(), ltWrite, false)) {
printInfo("waiting for exclusive access to the Nix store..."); printInfo("waiting for exclusive access to the Nix store...");
lockFile(globalLock.get(), ltNone, false); // We have acquired a shared lock; release it to prevent deadlocks
lockFile(globalLock.get(), ltWrite, true); lockFile(globalLock.get(), ltWrite, true);
} }
@ -439,9 +441,9 @@ LocalStore::~LocalStore()
} }
try { try {
auto state(_state.lock()); auto fdTempRoots(_fdTempRoots.lock());
if (state->fdTempRoots) { if (*fdTempRoots) {
state->fdTempRoots = -1; *fdTempRoots = -1;
unlink(fnTempRoots.c_str()); unlink(fnTempRoots.c_str());
} }
} catch (...) { } catch (...) {

View file

@ -59,15 +59,6 @@ private:
struct Stmts; struct Stmts;
std::unique_ptr<Stmts> stmts; std::unique_ptr<Stmts> stmts;
/* The global GC lock */
AutoCloseFD fdGCLock;
/* The file to which we write our temporary roots. */
AutoCloseFD fdTempRoots;
/* Connection to the garbage collector. */
AutoCloseFD fdRootsSocket;
/* The last time we checked whether to do an auto-GC, or an /* The last time we checked whether to do an auto-GC, or an
auto-GC finished. */ auto-GC finished. */
std::chrono::time_point<std::chrono::steady_clock> lastGCCheck; std::chrono::time_point<std::chrono::steady_clock> lastGCCheck;
@ -156,6 +147,21 @@ public:
void addTempRoot(const StorePath & path) override; void addTempRoot(const StorePath & path) override;
private:
void createTempRootsFile();
/* The file to which we write our temporary roots. */
Sync<AutoCloseFD> _fdTempRoots;
/* The global GC lock. */
Sync<AutoCloseFD> _fdGCLock;
/* Connection to the garbage collector. */
Sync<AutoCloseFD> _fdRootsSocket;
public:
void addIndirectRoot(const Path & path) override; void addIndirectRoot(const Path & path) override;
private: private:

View file

@ -13,10 +13,6 @@ ifdef HOST_LINUX
libstore_LDFLAGS += -ldl libstore_LDFLAGS += -ldl
endif endif
ifdef HOST_DARWIN
libstore_FILES = sandbox-defaults.sb sandbox-minimal.sb sandbox-network.sb
endif
$(foreach file,$(libstore_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/sandbox))) $(foreach file,$(libstore_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/sandbox)))
ifeq ($(ENABLE_S3), 1) ifeq ($(ENABLE_S3), 1)

View file

@ -123,8 +123,12 @@ struct AutoUserLock : UserLock
std::vector<gid_t> getSupplementaryGIDs() override { return {}; } std::vector<gid_t> getSupplementaryGIDs() override { return {}; }
static std::unique_ptr<UserLock> acquire(uid_t nrIds, bool useChroot) static std::unique_ptr<UserLock> acquire(uid_t nrIds, bool useUserNamespace)
{ {
#if !defined(__linux__)
useUserNamespace = false;
#endif
settings.requireExperimentalFeature(Xp::AutoAllocateUids); settings.requireExperimentalFeature(Xp::AutoAllocateUids);
assert(settings.startId > 0); assert(settings.startId > 0);
assert(settings.uidCount % maxIdsPerBuild == 0); assert(settings.uidCount % maxIdsPerBuild == 0);
@ -157,7 +161,7 @@ struct AutoUserLock : UserLock
auto lock = std::make_unique<AutoUserLock>(); auto lock = std::make_unique<AutoUserLock>();
lock->fdUserLock = std::move(fd); lock->fdUserLock = std::move(fd);
lock->firstUid = firstUid; lock->firstUid = firstUid;
if (useChroot) if (useUserNamespace)
lock->firstGid = firstUid; lock->firstGid = firstUid;
else { else {
struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str());
@ -174,10 +178,10 @@ struct AutoUserLock : UserLock
} }
}; };
std::unique_ptr<UserLock> acquireUserLock(uid_t nrIds, bool useChroot) std::unique_ptr<UserLock> acquireUserLock(uid_t nrIds, bool useUserNamespace)
{ {
if (settings.autoAllocateUids) if (settings.autoAllocateUids)
return AutoUserLock::acquire(nrIds, useChroot); return AutoUserLock::acquire(nrIds, useUserNamespace);
else else
return SimpleUserLock::acquire(); return SimpleUserLock::acquire();
} }

View file

@ -31,7 +31,7 @@ struct UserLock
/* Acquire a user lock for a UID range of size `nrIds`. Note that this /* Acquire a user lock for a UID range of size `nrIds`. Note that this
may return nullptr if no user is available. */ may return nullptr if no user is available. */
std::unique_ptr<UserLock> acquireUserLock(uid_t nrIds, bool useChroot); std::unique_ptr<UserLock> acquireUserLock(uid_t nrIds, bool useUserNamespace);
bool useBuildUsers(); bool useBuildUsers();

View file

@ -93,4 +93,14 @@ struct RealisedPath {
GENERATE_CMP(RealisedPath, me->raw); GENERATE_CMP(RealisedPath, me->raw);
}; };
class MissingRealisation : public Error
{
public:
MissingRealisation(DrvOutput & outputId)
: Error( "cannot operate on an output of the "
"unbuilt derivation '%s'",
outputId.to_string())
{}
};
} }

View file

@ -879,10 +879,7 @@ std::vector<BuildResult> RemoteStore::buildPathsWithResults(
auto realisation = auto realisation =
queryRealisation(outputId); queryRealisation(outputId);
if (!realisation) if (!realisation)
throw Error( throw MissingRealisation(outputId);
"cannot operate on an output of unbuilt "
"content-addressed derivation '%s'",
outputId.to_string());
res.builtOutputs.emplace(realisation->id, *realisation); res.builtOutputs.emplace(realisation->id, *realisation);
} else { } else {
// If ca-derivations isn't enabled, assume that // If ca-derivations isn't enabled, assume that

View file

@ -47,9 +47,13 @@ SQLite::SQLite(const Path & path, bool create)
// `unix-dotfile` is needed on NFS file systems and on Windows' Subsystem // `unix-dotfile` is needed on NFS file systems and on Windows' Subsystem
// for Linux (WSL) where useSQLiteWAL should be false by default. // for Linux (WSL) where useSQLiteWAL should be false by default.
const char *vfs = settings.useSQLiteWAL ? 0 : "unix-dotfile"; const char *vfs = settings.useSQLiteWAL ? 0 : "unix-dotfile";
if (sqlite3_open_v2(path.c_str(), &db, int flags = SQLITE_OPEN_READWRITE;
SQLITE_OPEN_READWRITE | (create ? SQLITE_OPEN_CREATE : 0), vfs) != SQLITE_OK) if (create) flags |= SQLITE_OPEN_CREATE;
throw Error("cannot open SQLite database '%s'", path); int ret = sqlite3_open_v2(path.c_str(), &db, flags, vfs);
if (ret != SQLITE_OK) {
const char * err = sqlite3_errstr(ret);
throw Error("cannot open SQLite database '%s': %s", path, err);
}
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
SQLiteError::throw_(db, "setting timeout"); SQLiteError::throw_(db, "setting timeout");

View file

@ -458,6 +458,7 @@ Store::Store(const Params & params)
: StoreConfig(params) : StoreConfig(params)
, state({(size_t) pathInfoCacheSize}) , state({(size_t) pathInfoCacheSize})
{ {
assertLibStoreInitialized();
} }

View file

@ -9,9 +9,9 @@ namespace nix {
const std::string nativeSystem = SYSTEM; const std::string nativeSystem = SYSTEM;
void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint) void BaseError::addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame)
{ {
err.traces.push_front(Trace { .pos = std::move(e), .hint = hint }); err.traces.push_front(Trace { .pos = std::move(e), .hint = hint, .frame = frame });
} }
// c++ std::exception descendants must have a 'const char* what()' function. // c++ std::exception descendants must have a 'const char* what()' function.
@ -200,13 +200,125 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s
auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n"; auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n";
// traces /*
if (showTrace && !einfo.traces.empty()) { * Traces
* ------
*
* The semantics of traces is a bit weird. We have only one option to
* print them and to make them verbose (--show-trace). In the code they
* are always collected, but they are not printed by default. The code
* also collects more traces when the option is on. This means that there
* is no way to print the simplified traces at all.
*
* I (layus) designed the code to attach positions to a restricted set of
* messages. This means that we have a lot of traces with no position at
* all, including most of the base error messages. For example "type
* error: found a string while a set was expected" has no position, but
* will come with several traces detailing it's precise relation to the
* closest know position. This makes erroring without printing traces
* quite useless.
*
* This is why I introduced the idea to always print a few traces on
* error. The number 3 is quite arbitrary, and was selected so as not to
* clutter the console on error. For the same reason, a trace with an
* error position takes more space, and counts as two traces towards the
* limit.
*
* The rest is truncated, unless --show-trace is passed. This preserves
* the same bad semantics of --show-trace to both show the trace and
* augment it with new data. Not too sure what is the best course of
* action.
*
* The issue is that it is fundamentally hard to provide a trace for a
* lazy language. The trace will only cover the current spine of the
* evaluation, missing things that have been evaluated before. For
* example, most type errors are hard to inspect because there is not
* trace for the faulty value. These errors should really print the faulty
* value itself.
*
* In function calls, the --show-trace flag triggers extra traces for each
* function invocation. These work as scopes, allowing to follow the
* current spine of the evaluation graph. Without that flag, the error
* trace should restrict itself to a restricted prefix of that trace,
* until the first scope. If we ever get to such a precise error
* reporting, there would be no need to add an arbitrary limit here. We
* could always print the full trace, and it would just be small without
* the flag.
*
* One idea I had is for XxxError.addTrace() to perform nothing if one
* scope has already been traced. Alternatively, we could stop here when
* we encounter such a scope instead of after an arbitrary number of
* traces. This however requires to augment traces with the notion of
* "scope".
*
* This is particularly visible in code like evalAttrs(...) where we have
* to make a decision between the two following options.
*
* ``` long traces
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx)
* {
* try {
* e->eval(*this, env, v);
* if (v.type() != nAttrs)
* throwTypeError("value is %1% while a set was expected", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
* }
* }
* ```
*
* ``` short traces
* inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v, const Pos & pos, std::string_view errorCtx)
* {
* e->eval(*this, env, v);
* try {
* if (v.type() != nAttrs)
* throwTypeError("value is %1% while a set was expected", v);
* } catch (Error & e) {
* e.addTrace(pos, errorCtx);
* throw;
* }
* }
* ```
*
* The second example can be rewritten more concisely, but kept in this
* form to highlight the symmetry. The first option adds more information,
* because whatever caused an error down the line, in the generic eval
* function, will get annotated with the code location that uses and
* required it. The second option is less verbose, but does not provide
* any context at all as to where and why a failing value was required.
*
* Scopes would fix that, by adding context only when --show-trace is
* passed, and keeping the trace terse otherwise.
*
*/
// Enough indent to align with with the `... `
// prepended to each element of the trace
auto ellipsisIndent = " ";
bool frameOnly = false;
if (!einfo.traces.empty()) {
size_t count = 0;
for (const auto & trace : einfo.traces) { for (const auto & trace : einfo.traces) {
if (!showTrace && count > 3) {
oss << "\n" << ANSI_WARNING "(stack trace truncated; use '--show-trace' to show the full trace)" ANSI_NORMAL << "\n";
break;
}
if (trace.hint.str().empty()) continue;
if (frameOnly && !trace.frame) continue;
count++;
frameOnly = trace.frame;
oss << "\n" << "" << trace.hint.str() << "\n"; oss << "\n" << "" << trace.hint.str() << "\n";
if (trace.pos) { if (trace.pos) {
oss << "\n" << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":"; count++;
oss << "\n" << ellipsisIndent << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":";
if (auto loc = trace.pos->getCodeLines()) { if (auto loc = trace.pos->getCodeLines()) {
oss << "\n"; oss << "\n";

View file

@ -86,6 +86,7 @@ void printCodeLines(std::ostream & out,
struct Trace { struct Trace {
std::shared_ptr<AbstractPos> pos; std::shared_ptr<AbstractPos> pos;
hintformat hint; hintformat hint;
bool frame;
}; };
struct ErrorInfo { struct ErrorInfo {
@ -114,6 +115,8 @@ protected:
public: public:
unsigned int status = 1; // exit status unsigned int status = 1; // exit status
BaseError(const BaseError &) = default;
template<typename... Args> template<typename... Args>
BaseError(unsigned int status, const Args & ... args) BaseError(unsigned int status, const Args & ... args)
: err { .level = lvlError, .msg = hintfmt(args...) } : err { .level = lvlError, .msg = hintfmt(args...) }
@ -152,15 +155,22 @@ public:
const std::string & msg() const { return calcWhat(); } const std::string & msg() const { return calcWhat(); }
const ErrorInfo & info() const { calcWhat(); return err; } const ErrorInfo & info() const { calcWhat(); return err; }
template<typename... Args> void pushTrace(Trace trace)
void addTrace(std::shared_ptr<AbstractPos> && e, const std::string & fs, const Args & ... args)
{ {
addTrace(std::move(e), hintfmt(fs, args...)); err.traces.push_front(trace);
} }
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint); template<typename... Args>
void addTrace(std::shared_ptr<AbstractPos> && e, std::string_view fs, const Args & ... args)
{
addTrace(std::move(e), hintfmt(std::string(fs), args...));
}
void addTrace(std::shared_ptr<AbstractPos> && e, hintformat hint, bool frame = false);
bool hasTrace() const { return !err.traces.empty(); } bool hasTrace() const { return !err.traces.empty(); }
const ErrorInfo & info() { return err; };
}; };
#define MakeError(newClass, superClass) \ #define MakeError(newClass, superClass) \

View file

@ -134,9 +134,9 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); }); state.forceValue(topLevel, [&]() { return topLevel.determinePos(noPos); });
PathSet context; PathSet context;
Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath)); Attr & aDrvPath(*topLevel.attrs->find(state.sDrvPath));
auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context); auto topLevelDrv = state.coerceToStorePath(aDrvPath.pos, *aDrvPath.value, context, "");
Attr & aOutPath(*topLevel.attrs->find(state.sOutPath)); Attr & aOutPath(*topLevel.attrs->find(state.sOutPath));
auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context); auto topLevelOut = state.coerceToStorePath(aOutPath.pos, *aOutPath.value, context, "");
/* Realise the resulting store expression. */ /* Realise the resulting store expression. */
debug("building user environment"); debug("building user environment");

View file

@ -97,13 +97,13 @@ struct CmdBundle : InstallableCommand
throw Error("the bundler '%s' does not produce a derivation", bundler.what()); throw Error("the bundler '%s' does not produce a derivation", bundler.what());
PathSet context2; PathSet context2;
auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2); auto drvPath = evalState->coerceToStorePath(attr1->pos, *attr1->value, context2, "");
auto attr2 = vRes->attrs->get(evalState->sOutPath); auto attr2 = vRes->attrs->get(evalState->sOutPath);
if (!attr2) if (!attr2)
throw Error("the bundler '%s' does not produce a derivation", bundler.what()); throw Error("the bundler '%s' does not produce a derivation", bundler.what());
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 } });
@ -113,7 +113,7 @@ struct CmdBundle : InstallableCommand
auto * attr = vRes->attrs->get(evalState->sName); auto * attr = vRes->attrs->get(evalState->sName);
if (!attr) if (!attr)
throw Error("attribute 'name' missing"); throw Error("attribute 'name' missing");
outLink = evalState->forceStringNoCtx(*attr->value, attr->pos); outLink = evalState->forceStringNoCtx(*attr->value, attr->pos, "");
} }
// TODO: will crash if not a localFSStore? // TODO: will crash if not a localFSStore?

View file

@ -164,6 +164,14 @@ struct BuildEnvironment
{ {
return vars == other.vars && bashFunctions == other.bashFunctions; return vars == other.vars && bashFunctions == other.bashFunctions;
} }
std::string getSystem() const
{
if (auto v = get(vars, "system"))
return getString(*v);
else
return settings.thisSystem;
}
}; };
const static std::string getEnvSh = const static std::string getEnvSh =
@ -570,7 +578,7 @@ struct CmdDevelop : Common, MixEnvironment
} }
} }
runProgramInStore(store, shell, args); runProgramInStore(store, shell, args, buildEnvironment.getSystem());
} }
}; };

View file

@ -112,7 +112,7 @@ struct CmdEval : MixJSON, InstallableCommand
else if (raw) { else if (raw) {
stopProgressBar(); stopProgressBar();
std::cout << *state->coerceToString(noPos, *v, context); std::cout << *state->coerceToString(noPos, *v, context, "while generating the eval command output");
} }
else if (json) { else if (json) {

View file

@ -126,12 +126,12 @@ static void enumerateOutputs(EvalState & state, Value & vFlake,
std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> callback) std::function<void(const std::string & name, Value & vProvide, const PosIdx pos)> callback)
{ {
auto pos = vFlake.determinePos(noPos); auto pos = vFlake.determinePos(noPos);
state.forceAttrs(vFlake, pos); state.forceAttrs(vFlake, pos, "while evaluating a flake to get its outputs");
auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs")); auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs"));
assert(aOutputs); assert(aOutputs);
state.forceAttrs(*aOutputs->value, pos); state.forceAttrs(*aOutputs->value, pos, "while evaluating the outputs of a flake");
auto sHydraJobs = state.symbols.create("hydraJobs"); auto sHydraJobs = state.symbols.create("hydraJobs");
@ -381,23 +381,6 @@ struct CmdFlakeCheck : FlakeCommand
auto checkModule = [&](const std::string & attrPath, Value & v, const PosIdx pos) { auto checkModule = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try { try {
state->forceValue(v, pos); state->forceValue(v, pos);
if (v.isLambda()) {
if (!v.lambda.fun->hasFormals() || !v.lambda.fun->formals->ellipsis)
throw Error("module must match an open attribute set ('{ config, ... }')");
} else if (v.type() == nAttrs) {
for (auto & attr : *v.attrs)
try {
state->forceValue(*attr.value, attr.pos);
} catch (Error & e) {
e.addTrace(
state->positions[attr.pos],
hintfmt("while evaluating the option '%s'", state->symbols[attr.name]));
throw;
}
} else
throw Error("module must be a function or an attribute set");
// FIXME: if we have a 'nixpkgs' input, use it to
// check the module.
} catch (Error & e) { } catch (Error & e) {
e.addTrace(resolve(pos), hintfmt("while checking the NixOS module '%s'", attrPath)); e.addTrace(resolve(pos), hintfmt("while checking the NixOS module '%s'", attrPath));
reportError(e); reportError(e);
@ -408,13 +391,13 @@ struct CmdFlakeCheck : FlakeCommand
checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) { checkHydraJobs = [&](const std::string & attrPath, Value & v, const PosIdx pos) {
try { try {
state->forceAttrs(v, pos); state->forceAttrs(v, pos, "");
if (state->isDerivation(v)) if (state->isDerivation(v))
throw Error("jobset should not be a derivation at top-level"); throw Error("jobset should not be a derivation at top-level");
for (auto & attr : *v.attrs) { for (auto & attr : *v.attrs) {
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]); auto attrPath2 = concatStrings(attrPath, ".", state->symbols[attr.name]);
if (state->isDerivation(*attr.value)) { if (state->isDerivation(*attr.value)) {
Activity act(*logger, lvlChatty, actUnknown, Activity act(*logger, lvlChatty, actUnknown,
@ -436,7 +419,7 @@ struct CmdFlakeCheck : FlakeCommand
fmt("checking NixOS configuration '%s'", attrPath)); fmt("checking NixOS configuration '%s'", attrPath));
Bindings & bindings(*state->allocBindings(0)); Bindings & bindings(*state->allocBindings(0));
auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first; auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first;
state->forceAttrs(*vToplevel, pos); state->forceValue(*vToplevel, pos);
if (!state->isDerivation(*vToplevel)) if (!state->isDerivation(*vToplevel))
throw Error("attribute 'config.system.build.toplevel' is not a derivation"); throw Error("attribute 'config.system.build.toplevel' is not a derivation");
} catch (Error & e) { } catch (Error & e) {
@ -450,12 +433,12 @@ struct CmdFlakeCheck : FlakeCommand
Activity act(*logger, lvlChatty, actUnknown, Activity act(*logger, lvlChatty, actUnknown,
fmt("checking template '%s'", attrPath)); fmt("checking template '%s'", attrPath));
state->forceAttrs(v, pos); state->forceAttrs(v, pos, "");
if (auto attr = v.attrs->get(state->symbols.create("path"))) { if (auto attr = v.attrs->get(state->symbols.create("path"))) {
if (attr->name == state->symbols.create("path")) { if (attr->name == state->symbols.create("path")) {
PathSet context; PathSet context;
auto path = state->coerceToPath(attr->pos, *attr->value, context); auto path = state->coerceToPath(attr->pos, *attr->value, context, "");
if (!store->isInStore(path)) if (!store->isInStore(path))
throw Error("template '%s' has a bad 'path' attribute"); throw Error("template '%s' has a bad 'path' attribute");
// TODO: recursively check the flake in 'path'. // TODO: recursively check the flake in 'path'.
@ -464,7 +447,7 @@ struct CmdFlakeCheck : FlakeCommand
throw Error("template '%s' lacks attribute 'path'", attrPath); throw Error("template '%s' lacks attribute 'path'", attrPath);
if (auto attr = v.attrs->get(state->symbols.create("description"))) if (auto attr = v.attrs->get(state->symbols.create("description")))
state->forceStringNoCtx(*attr->value, attr->pos); state->forceStringNoCtx(*attr->value, attr->pos, "");
else else
throw Error("template '%s' lacks attribute 'description'", attrPath); throw Error("template '%s' lacks attribute 'description'", attrPath);
@ -521,11 +504,11 @@ struct CmdFlakeCheck : FlakeCommand
warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement); warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement);
if (name == "checks") { if (name == "checks") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) { for (auto & attr2 : *attr.value->attrs) {
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]),
@ -537,7 +520,7 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "formatter") { else if (name == "formatter") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
@ -548,11 +531,11 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "packages" || name == "devShells") { else if (name == "packages" || name == "devShells") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) for (auto & attr2 : *attr.value->attrs)
checkDerivation( checkDerivation(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
@ -561,11 +544,11 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "apps") { else if (name == "apps") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) for (auto & attr2 : *attr.value->attrs)
checkApp( checkApp(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
@ -574,7 +557,7 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "defaultPackage" || name == "devShell") { else if (name == "defaultPackage" || name == "devShell") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
@ -585,7 +568,7 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "defaultApp") { else if (name == "defaultApp") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
@ -596,7 +579,7 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "legacyPackages") { else if (name == "legacyPackages") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
checkSystemName(state->symbols[attr.name], attr.pos); checkSystemName(state->symbols[attr.name], attr.pos);
// FIXME: do getDerivations? // FIXME: do getDerivations?
@ -607,7 +590,7 @@ struct CmdFlakeCheck : FlakeCommand
checkOverlay(name, vOutput, pos); checkOverlay(name, vOutput, pos);
else if (name == "overlays") { else if (name == "overlays") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) for (auto & attr : *vOutput.attrs)
checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]), checkOverlay(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos); *attr.value, attr.pos);
@ -617,14 +600,14 @@ struct CmdFlakeCheck : FlakeCommand
checkModule(name, vOutput, pos); checkModule(name, vOutput, pos);
else if (name == "nixosModules") { else if (name == "nixosModules") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) for (auto & attr : *vOutput.attrs)
checkModule(fmt("%s.%s", name, state->symbols[attr.name]), checkModule(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos); *attr.value, attr.pos);
} }
else if (name == "nixosConfigurations") { else if (name == "nixosConfigurations") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) for (auto & attr : *vOutput.attrs)
checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]), checkNixOSConfiguration(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos); *attr.value, attr.pos);
@ -637,14 +620,14 @@ struct CmdFlakeCheck : FlakeCommand
checkTemplate(name, vOutput, pos); checkTemplate(name, vOutput, pos);
else if (name == "templates") { else if (name == "templates") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) for (auto & attr : *vOutput.attrs)
checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]), checkTemplate(fmt("%s.%s", name, state->symbols[attr.name]),
*attr.value, attr.pos); *attr.value, attr.pos);
} }
else if (name == "defaultBundler") { else if (name == "defaultBundler") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
@ -655,11 +638,11 @@ struct CmdFlakeCheck : FlakeCommand
} }
else if (name == "bundlers") { else if (name == "bundlers") {
state->forceAttrs(vOutput, pos); state->forceAttrs(vOutput, pos, "");
for (auto & attr : *vOutput.attrs) { for (auto & attr : *vOutput.attrs) {
const auto & attr_name = state->symbols[attr.name]; const auto & attr_name = state->symbols[attr.name];
checkSystemName(attr_name, attr.pos); checkSystemName(attr_name, attr.pos);
state->forceAttrs(*attr.value, attr.pos); state->forceAttrs(*attr.value, attr.pos, "");
for (auto & attr2 : *attr.value->attrs) { for (auto & attr2 : *attr.value->attrs) {
checkBundler( checkBundler(
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]), fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),

View file

@ -199,7 +199,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
if (!attr) if (!attr)
throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand)); throw UsageError("Nix has no subcommand '%s'", concatStringsSep("", subcommand));
auto markdown = state.forceString(*attr->value); auto markdown = state.forceString(*attr->value, noPos, "while evaluating the lowdown help text");
RunPager pager; RunPager pager;
std::cout << renderMarkdownToTerminal(markdown) << "\n"; std::cout << renderMarkdownToTerminal(markdown) << "\n";

View file

@ -115,12 +115,11 @@ the Nix store. Here are the recognised types of installables:
* **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv` * **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv`
Store derivations are store paths with extension `.drv` and are a By default, if you pass a [store derivation] path to a `nix` subcommand, the command will operate on the [output path]s of the derivation.
low-level representation of a build-time dependency graph used
internally by Nix. By default, if you pass a store derivation to a [output path]: ../../glossary.md#gloss-output-path
`nix` subcommand, it will operate on the *output paths* of the
derivation. For example, `nix path-info` prints information about For example, `nix path-info` prints information about the output paths:
the output paths:
```console ```console
# nix path-info --json /nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv # nix path-info --json /nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv
@ -202,9 +201,11 @@ operate are determined as follows:
a command like `nix shell nixpkgs#libxml2` will provide only those a command like `nix shell nixpkgs#libxml2` will provide only those
two outputs by default. two outputs by default.
Note that a store derivation (given by `.drv` file store path) doesn't have Note that a [store derivation] (given by its `.drv` file store path) doesn't have
any attributes like `meta`, and thus this case doesn't apply to it. any attributes like `meta`, and thus this case doesn't apply to it.
[store derivation]: ../../glossary.md#gloss-store-derivation
* Otherwise, Nix will use all outputs of the derivation. * Otherwise, Nix will use all outputs of the derivation.
# Nix stores # Nix stores

View file

@ -68,7 +68,9 @@ R""(
] ]
``` ```
* Print the path of the store derivation produced by `nixpkgs#hello`: * Print the path of the [store derivation] produced by `nixpkgs#hello`:
[store derivation]: ../../glossary.md#gloss-store-derivation
```console ```console
# nix path-info --derivation nixpkgs#hello # nix path-info --derivation nixpkgs#hello

View file

@ -28,17 +28,17 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
Value vMirrors; Value vMirrors;
// FIXME: use nixpkgs flake // FIXME: use nixpkgs flake
state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors); state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors);
state.forceAttrs(vMirrors, noPos); state.forceAttrs(vMirrors, noPos, "while evaluating the set of all mirrors");
auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName));
if (mirrorList == vMirrors.attrs->end()) if (mirrorList == vMirrors.attrs->end())
throw Error("unknown mirror name '%s'", mirrorName); throw Error("unknown mirror name '%s'", mirrorName);
state.forceList(*mirrorList->value, noPos); state.forceList(*mirrorList->value, noPos, "while evaluating one mirror configuration");
if (mirrorList->value->listSize() < 1) if (mirrorList->value->listSize() < 1)
throw Error("mirror URL '%s' did not expand to anything", url); throw Error("mirror URL '%s' did not expand to anything", url);
std::string mirror(state.forceString(*mirrorList->value->listElems()[0])); std::string mirror(state.forceString(*mirrorList->value->listElems()[0], noPos, "while evaluating the first available mirror"));
return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1); return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1);
} }
@ -196,29 +196,29 @@ static int main_nix_prefetch_url(int argc, char * * argv)
Value vRoot; Value vRoot;
state->evalFile(path, vRoot); state->evalFile(path, vRoot);
Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first); Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first);
state->forceAttrs(v, noPos); state->forceAttrs(v, noPos, "while evaluating the source attribute to prefetch");
/* Extract the URL. */ /* Extract the URL. */
auto * attr = v.attrs->get(state->symbols.create("urls")); auto * attr = v.attrs->get(state->symbols.create("urls"));
if (!attr) if (!attr)
throw Error("attribute 'urls' missing"); throw Error("attribute 'urls' missing");
state->forceList(*attr->value, noPos); state->forceList(*attr->value, noPos, "while evaluating the urls to prefetch");
if (attr->value->listSize() < 1) if (attr->value->listSize() < 1)
throw Error("'urls' list is empty"); throw Error("'urls' list is empty");
url = state->forceString(*attr->value->listElems()[0]); url = state->forceString(*attr->value->listElems()[0], noPos, "while evaluating the first url from the urls list");
/* Extract the hash mode. */ /* Extract the hash mode. */
auto attr2 = v.attrs->get(state->symbols.create("outputHashMode")); auto attr2 = v.attrs->get(state->symbols.create("outputHashMode"));
if (!attr2) if (!attr2)
printInfo("warning: this does not look like a fetchurl call"); printInfo("warning: this does not look like a fetchurl call");
else else
unpack = state->forceString(*attr2->value) == "recursive"; unpack = state->forceString(*attr2->value, noPos, "while evaluating the outputHashMode of the source to prefetch") == "recursive";
/* Extract the name. */ /* Extract the name. */
if (!name) { if (!name) {
auto attr3 = v.attrs->get(state->symbols.create("name")); auto attr3 = v.attrs->get(state->symbols.create("name"));
if (!attr3) if (!attr3)
name = state->forceString(*attr3->value); name = state->forceString(*attr3->value, noPos, "while evaluating the name of the source to prefetch");
} }
} }

View file

@ -9,6 +9,7 @@
#include "fs-accessor.hh" #include "fs-accessor.hh"
#include "progress-bar.hh" #include "progress-bar.hh"
#include "eval.hh" #include "eval.hh"
#include "build/personality.hh"
#if __linux__ #if __linux__
#include <sys/mount.h> #include <sys/mount.h>
@ -24,7 +25,8 @@ namespace nix {
void runProgramInStore(ref<Store> store, void runProgramInStore(ref<Store> store,
const std::string & program, const std::string & program,
const Strings & args) const Strings & args,
std::optional<std::string_view> system)
{ {
stopProgressBar(); stopProgressBar();
@ -44,7 +46,7 @@ void runProgramInStore(ref<Store> store,
throw Error("store '%s' is not a local store so it does not support command execution", store->getUri()); throw Error("store '%s' is not a local store so it does not support command execution", store->getUri());
if (store->storeDir != store2->getRealStoreDir()) { if (store->storeDir != store2->getRealStoreDir()) {
Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program }; Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), std::string(system.value_or("")), program };
for (auto & arg : args) helperArgs.push_back(arg); for (auto & arg : args) helperArgs.push_back(arg);
execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data()); execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data());
@ -52,6 +54,9 @@ void runProgramInStore(ref<Store> store,
throw SysError("could not execute chroot helper"); throw SysError("could not execute chroot helper");
} }
if (system)
setPersonality(*system);
execvp(program.c_str(), stringsToCharPtrs(args).data()); execvp(program.c_str(), stringsToCharPtrs(args).data());
throw SysError("unable to execute '%s'", program); throw SysError("unable to execute '%s'", program);
@ -241,6 +246,7 @@ void chrootHelper(int argc, char * * argv)
int p = 1; int p = 1;
std::string storeDir = argv[p++]; std::string storeDir = argv[p++];
std::string realStoreDir = argv[p++]; std::string realStoreDir = argv[p++];
std::string system = argv[p++];
std::string cmd = argv[p++]; std::string cmd = argv[p++];
Strings args; Strings args;
while (p < argc) while (p < argc)
@ -304,6 +310,9 @@ void chrootHelper(int argc, char * * argv)
writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1)); writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1));
writeFile("/proc/self/gid_map", fmt("%d %d %d", gid, gid, 1)); writeFile("/proc/self/gid_map", fmt("%d %d %d", gid, gid, 1));
if (system != "")
setPersonality(system);
execvp(cmd.c_str(), stringsToCharPtrs(args).data()); execvp(cmd.c_str(), stringsToCharPtrs(args).data());
throw SysError("unable to exec '%s'", cmd); throw SysError("unable to exec '%s'", cmd);

View file

@ -6,6 +6,7 @@ namespace nix {
void runProgramInStore(ref<Store> store, void runProgramInStore(ref<Store> store,
const std::string & program, const std::string & program,
const Strings & args); const Strings & args,
std::optional<std::string_view> system = std::nullopt);
} }

View file

@ -2,9 +2,11 @@ R""(
# Examples # Examples
* Show the store derivation that results from evaluating the Hello * Show the [store derivation] that results from evaluating the Hello
package: package:
[store derivation]: ../../glossary.md#gloss-store-derivation
```console ```console
# nix show-derivation nixpkgs#hello # nix show-derivation nixpkgs#hello
{ {
@ -37,7 +39,7 @@ R""(
# Description # Description
This command prints on standard output a JSON representation of the This command prints on standard output a JSON representation of the
store derivations to which *installables* evaluate. Store derivations [store derivation]s to which *installables* evaluate. Store derivations
are used internally by Nix. They are store paths with extension `.drv` are used internally by Nix. They are store paths with extension `.drv`
that represent the build-time dependency graph to which a Nix that represent the build-time dependency graph to which a Nix
expression evaluates. expression evaluates.

View file

@ -18,7 +18,9 @@ R""(
(The flag `--substituters ''` avoids querying (The flag `--substituters ''` avoids querying
`https://cache.nixos.org` for the log.) `https://cache.nixos.org` for the log.)
* To copy the log for a specific store derivation via SSH: * To copy the log for a specific [store derivation] via SSH:
[store derivation]: ../../glossary.md#gloss-store-derivation
```console ```console
# nix store copy-log --to ssh-ng://machine /nix/store/ilgm50plpmcgjhcp33z6n4qbnpqfhxym-glibc-2.33-59.drv # nix store copy-log --to ssh-ng://machine /nix/store/ilgm50plpmcgjhcp33z6n4qbnpqfhxym-glibc-2.33-59.drv

View file

@ -144,7 +144,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand
Bindings & bindings(*state->allocBindings(0)); Bindings & bindings(*state->allocBindings(0));
auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first; auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first;
return store->parseStorePath(state->forceString(*v2)); return store->parseStorePath(state->forceString(*v2, noPos, "while evaluating the path tho latest nix version"));
} }
}; };

View file

@ -95,23 +95,13 @@ struct CmdWhyDepends : SourceExprCommand
* to build. * to build.
*/ */
auto dependency = parseInstallable(store, _dependency); auto dependency = parseInstallable(store, _dependency);
auto derivedDependency = dependency->toDerivedPath(); auto optDependencyPath = [&]() -> std::optional<StorePath> {
auto optDependencyPath = std::visit(overloaded { try {
[](const DerivedPath::Opaque & nodrv) -> std::optional<StorePath> { return {Installable::toStorePath(getEvalStore(), store, Realise::Derivation, operateOn, dependency)};
return { nodrv.path }; } catch (MissingRealisation &) {
}, return std::nullopt;
[&](const DerivedPath::Built & hasdrv) -> std::optional<StorePath> { }
if (hasdrv.outputs.size() != 1) { }();
throw Error("argument '%s' should evaluate to one store path", dependency->what());
}
auto outputMap = store->queryPartialDerivationOutputMap(hasdrv.drvPath);
auto maybePath = outputMap.find(*hasdrv.outputs.begin());
if (maybePath == outputMap.end()) {
throw Error("unexpected end of iterator");
}
return maybePath->second;
},
}, derivedDependency.raw());
StorePathSet closure; StorePathSet closure;
store->computeFSClosure({packagePath}, closure, false, false); store->computeFSClosure({packagePath}, closure, false, false);

View file

@ -20,7 +20,7 @@ makeTest ({
(import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel (import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel
]; ];
virtualisation.memorySize = 4096; virtualisation.memorySize = 4096;
nix.binaryCaches = lib.mkForce [ ]; nix.settings.substituters = lib.mkForce [ ];
nix.extraOptions = nix.extraOptions =
'' ''
extra-experimental-features = nix-command auto-allocate-uids cgroups extra-experimental-features = nix-command auto-allocate-uids cgroups

View file

@ -41,9 +41,9 @@ nix flake check $flakeDir
cat > $flakeDir/flake.nix <<EOF cat > $flakeDir/flake.nix <<EOF
{ {
outputs = { self }: { outputs = { self }: {
nixosModules.foo = { nixosModules.foo = assert false; {
a.b.c = 123; a.b.c = 123;
foo = assert false; true; foo = true;
}; };
}; };
} }
@ -63,18 +63,6 @@ EOF
nix flake check $flakeDir nix flake check $flakeDir
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self }: {
nixosModule = { config, pkgs }: {
a.b.c = 123;
};
};
}
EOF
(! nix flake check $flakeDir)
cat > $flakeDir/flake.nix <<EOF cat > $flakeDir/flake.nix <<EOF
{ {
outputs = { self }: { outputs = { self }: {

View file

@ -149,7 +149,7 @@ makeTest (
virtualisation.diskSize = 2048; virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ]; virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
virtualisation.memorySize = 4096; virtualisation.memorySize = 4096;
nix.binaryCaches = lib.mkForce [ ]; nix.settings.substituters = lib.mkForce [ ];
nix.extraOptions = "experimental-features = nix-command flakes"; nix.extraOptions = "experimental-features = nix-command flakes";
networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} = networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} =
[ "channels.nixos.org" "api.github.com" "github.com" ]; [ "channels.nixos.org" "api.github.com" "github.com" ];

View file

@ -110,7 +110,7 @@ let
And finally to interpret \n etc. as in a string: ''\n, ''\r, ''\t. And finally to interpret \n etc. as in a string: ''\n, ''\r, ''\t.
''; '';
# Regression test: antiquotation in '${x}' should work, but didn't. # Regression test: string interpolation in '${x}' should work, but didn't.
s15 = let x = "bla"; in '' s15 = let x = "bla"; in ''
foo foo
'${x}' '${x}'

View file

@ -0,0 +1 @@
[ { } { a = 1; } { a = 1; } { a = "a"; } { m = 1; } { m = "m"; } { n = 1; } { n = "n"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { a = "a"; b = "b"; c = "c"; d = "d"; e = "e"; f = "f"; g = "g"; h = "h"; i = "i"; j = "j"; k = "k"; l = "l"; m = "m"; n = "n"; o = "o"; p = "p"; q = "q"; r = "r"; s = "s"; t = "t"; u = "u"; v = "v"; w = "w"; x = "x"; y = "y"; z = "z"; } true ]

View file

@ -0,0 +1,50 @@
let
alphabet =
{ a = "a";
b = "b";
c = "c";
d = "d";
e = "e";
f = "f";
g = "g";
h = "h";
i = "i";
j = "j";
k = "k";
l = "l";
m = "m";
n = "n";
o = "o";
p = "p";
q = "q";
r = "r";
s = "s";
t = "t";
u = "u";
v = "v";
w = "w";
x = "x";
y = "y";
z = "z";
};
foo = {
inherit (alphabet) f o b a r z q u x;
aa = throw "aa";
};
alphabetFail = builtins.mapAttrs throw alphabet;
in
[ (builtins.intersectAttrs { a = abort "l1"; } { b = abort "r1"; })
(builtins.intersectAttrs { a = abort "l2"; } { a = 1; })
(builtins.intersectAttrs alphabetFail { a = 1; })
(builtins.intersectAttrs { a = abort "laa"; } alphabet)
(builtins.intersectAttrs alphabetFail { m = 1; })
(builtins.intersectAttrs { m = abort "lam"; } alphabet)
(builtins.intersectAttrs alphabetFail { n = 1; })
(builtins.intersectAttrs { n = abort "lan"; } alphabet)
(builtins.intersectAttrs alphabetFail { n = 1; p = 2; })
(builtins.intersectAttrs { n = abort "lan2"; p = abort "lap"; } alphabet)
(builtins.intersectAttrs alphabetFail { n = 1; p = 2; })
(builtins.intersectAttrs { n = abort "lan2"; p = abort "lap"; } alphabet)
(builtins.intersectAttrs alphabetFail alphabet)
(builtins.intersectAttrs alphabet foo == builtins.intersectAttrs foo alphabet)
]

View file

@ -92,6 +92,7 @@ nix_tests = \
fmt.sh \ fmt.sh \
eval-store.sh \ eval-store.sh \
why-depends.sh \ why-depends.sh \
ca/why-depends.sh \
import-derivation.sh \ import-derivation.sh \
ca/import-derivation.sh \ ca/import-derivation.sh \
nix_path.sh \ nix_path.sh \
@ -134,8 +135,6 @@ endif
install-tests += $(foreach x, $(nix_tests), tests/$(x)) install-tests += $(foreach x, $(nix_tests), tests/$(x))
tests-environment = NIX_REMOTE= $(bash) -e
clean-files += $(d)/common.sh $(d)/config.nix $(d)/ca/config.nix clean-files += $(d)/common.sh $(d)/config.nix $(d)/ca/config.nix
test-deps += tests/common.sh tests/config.nix tests/ca/config.nix test-deps += tests/common.sh tests/config.nix tests/ca/config.nix

View file

@ -15,7 +15,7 @@ makeTest (let pkgA = pkgs.cowsay; pkgB = pkgs.wget; pkgC = pkgs.hello; pkgD = pk
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
{ virtualisation.writableStore = true; { virtualisation.writableStore = true;
virtualisation.additionalPaths = [ pkgA pkgD.drvPath ]; virtualisation.additionalPaths = [ pkgA pkgD.drvPath ];
nix.binaryCaches = lib.mkForce [ ]; nix.settings.substituters = lib.mkForce [ ];
}; };
server = server =

View file

@ -98,9 +98,9 @@ rec {
{ address = "192.168.0.10"; prefixLength = 24; } { address = "192.168.0.10"; prefixLength = 24; }
]; ];
nix.sandboxPaths = lib.mkForce []; nix.settings.extra-sandbox-paths = lib.mkForce [];
nix.binaryCaches = lib.mkForce []; nix.settings.substituters = lib.mkForce [];
nix.useSandbox = lib.mkForce true; nix.settings.sandbox = lib.mkForce true;
}; };
}; };

View file

@ -16,7 +16,7 @@ let
{ config, pkgs, ... }: { config, pkgs, ... }:
{ services.openssh.enable = true; { services.openssh.enable = true;
virtualisation.writableStore = true; virtualisation.writableStore = true;
nix.useSandbox = true; nix.settings.sandbox = true;
}; };
# Trivial Nix expression to build remotely. # Trivial Nix expression to build remotely.
@ -44,7 +44,7 @@ in
client = client =
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
{ nix.maxJobs = 0; # force remote building { nix.settings.max-jobs = 0; # force remote building
nix.distributedBuilds = true; nix.distributedBuilds = true;
nix.buildMachines = nix.buildMachines =
[ { hostName = "builder1"; [ { hostName = "builder1";
@ -62,7 +62,7 @@ in
]; ];
virtualisation.writableStore = true; virtualisation.writableStore = true;
virtualisation.additionalPaths = [ config.system.build.extraUtils ]; virtualisation.additionalPaths = [ config.system.build.extraUtils ];
nix.binaryCaches = lib.mkForce [ ]; nix.settings.substituters = lib.mkForce [ ];
programs.ssh.extraConfig = "ConnectTimeout 30"; programs.ssh.extraConfig = "ConnectTimeout 30";
}; };
}; };

View file

@ -13,7 +13,7 @@ makeTest {
nodes.machine = nodes.machine =
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
{ virtualisation.writableStore = true; { virtualisation.writableStore = true;
nix.binaryCaches = lib.mkForce [ ]; nix.settings.substituters = lib.mkForce [ ];
nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ]; nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ];
virtualisation.additionalPaths = [ pkgs.stdenv pkgs.pkgsi686Linux.stdenv ]; virtualisation.additionalPaths = [ pkgs.stdenv pkgs.pkgsi686Linux.stdenv ];
}; };

View file

@ -108,7 +108,7 @@ makeTest (
virtualisation.diskSize = 2048; virtualisation.diskSize = 2048;
virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ]; virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ];
virtualisation.memorySize = 4096; virtualisation.memorySize = 4096;
nix.binaryCaches = lib.mkForce [ ]; nix.settings.substituters = lib.mkForce [ ];
nix.extraOptions = '' nix.extraOptions = ''
experimental-features = nix-command flakes experimental-features = nix-command flakes
flake-registry = https://git.sr.ht/~NixOS/flake-registry/blob/master/flake-registry.json flake-registry = https://git.sr.ht/~NixOS/flake-registry/blob/master/flake-registry.json

View file

@ -6,6 +6,9 @@ cp ./dependencies.nix ./dependencies.builder0.sh ./config.nix $TEST_HOME
cd $TEST_HOME cd $TEST_HOME
nix why-depends --derivation --file ./dependencies.nix input2_drv input1_drv
nix why-depends --file ./dependencies.nix input2_drv input1_drv
nix-build ./dependencies.nix -A input0_drv -o dep nix-build ./dependencies.nix -A input0_drv -o dep
nix-build ./dependencies.nix -o toplevel nix-build ./dependencies.nix -o toplevel