Merge branch 'master' into nixenvjsondrvpath

This commit is contained in:
John Ericson 2023-11-18 13:47:14 -05:00 committed by GitHub
commit 87ac33f29a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
293 changed files with 7443 additions and 3977 deletions

View file

@ -17,7 +17,7 @@ indent_style = space
indent_size = 2 indent_size = 2
# Match c++/shell/perl, set indent to spaces with width of four # Match c++/shell/perl, set indent to spaces with width of four
[*.{hpp,cc,hh,sh,pl}] [*.{hpp,cc,hh,sh,pl,xs}]
indent_style = space indent_style = space
indent_size = 4 indent_size = 4

View file

@ -21,7 +21,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
- name: Create backport PRs - name: Create backport PRs
# should be kept in sync with `version` # should be kept in sync with `version`
uses: zeebe-io/backport-action@v1.4.0 uses: zeebe-io/backport-action@v2.1.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

@ -28,6 +28,8 @@ SODIUM_LIBS = @SODIUM_LIBS@
SQLITE3_LIBS = @SQLITE3_LIBS@ SQLITE3_LIBS = @SQLITE3_LIBS@
bash = @bash@ bash = @bash@
bindir = @bindir@ bindir = @bindir@
checkbindir = @checkbindir@
checklibdir = @checklibdir@
datadir = @datadir@ datadir = @datadir@
datarootdir = @datarootdir@ datarootdir = @datarootdir@
doc_generate = @doc_generate@ doc_generate = @doc_generate@
@ -48,4 +50,5 @@ sysconfdir = @sysconfdir@
system = @system@ system = @system@
ENABLE_BUILD = @ENABLE_BUILD@ ENABLE_BUILD = @ENABLE_BUILD@
ENABLE_TESTS = @ENABLE_TESTS@ ENABLE_TESTS = @ENABLE_TESTS@
INSTALL_UNIT_TESTS = @INSTALL_UNIT_TESTS@
internal_api_docs = @internal_api_docs@ internal_api_docs = @internal_api_docs@

View file

@ -68,6 +68,9 @@ case "$host_os" in
esac esac
ENSURE_NO_GCC_BUG_80431
# Check for pubsetbuf. # Check for pubsetbuf.
AC_MSG_CHECKING([for pubsetbuf]) AC_MSG_CHECKING([for pubsetbuf])
AC_LANG_PUSH(C++) AC_LANG_PUSH(C++)
@ -164,6 +167,18 @@ AC_ARG_ENABLE(tests, AS_HELP_STRING([--disable-tests],[Do not build the tests]),
ENABLE_TESTS=$enableval, ENABLE_TESTS=yes) ENABLE_TESTS=$enableval, ENABLE_TESTS=yes)
AC_SUBST(ENABLE_TESTS) AC_SUBST(ENABLE_TESTS)
AC_ARG_ENABLE(install-unit-tests, AS_HELP_STRING([--enable-install-unit-tests],[Install the unit tests for running later (default no)]),
INSTALL_UNIT_TESTS=$enableval, INSTALL_UNIT_TESTS=no)
AC_SUBST(INSTALL_UNIT_TESTS)
AC_ARG_WITH(check-bin-dir, AS_HELP_STRING([--with-check-bin-dir=PATH],[path to install unit tests for running later (defaults to $libexecdir/nix)]),
checkbindir=$withval, checkbindir=$libexecdir/nix)
AC_SUBST(checkbindir)
AC_ARG_WITH(check-lib-dir, AS_HELP_STRING([--with-check-lib-dir=PATH],[path to install unit tests for running later (defaults to $libdir)]),
checklibdir=$withval, checklibdir=$libdir)
AC_SUBST(checklibdir)
# Building without API docs is the default as Nix' C++ interfaces are internal and unstable. # Building without API docs is the default as Nix' C++ interfaces are internal and unstable.
AC_ARG_ENABLE(internal_api_docs, AS_HELP_STRING([--enable-internal-api-docs],[Build API docs for Nix's internal unstable C++ interfaces]), AC_ARG_ENABLE(internal_api_docs, AS_HELP_STRING([--enable-internal-api-docs],[Build API docs for Nix's internal unstable C++ interfaces]),
internal_api_docs=$enableval, internal_api_docs=no) internal_api_docs=$enableval, internal_api_docs=no)

View file

@ -103,7 +103,7 @@ $(d)/src/command-ref/new-cli: $(d)/nix.json $(d)/utils.nix $(d)/generate-manpage
$(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/utils.nix $(d)/generate-settings.nix $(d)/src/command-ref/conf-file-prefix.md $(d)/src/command-ref/experimental-features-shortlist.md $(bindir)/nix $(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/utils.nix $(d)/generate-settings.nix $(d)/src/command-ref/conf-file-prefix.md $(d)/src/command-ref/experimental-features-shortlist.md $(bindir)/nix
@cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp @cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp
$(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-settings.nix { prefix = "opt-"; } (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp; $(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-settings.nix { prefix = "conf"; } (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp;
@mv $@.tmp $@ @mv $@.tmp $@
$(d)/nix.json: $(bindir)/nix $(d)/nix.json: $(bindir)/nix

View file

@ -18,6 +18,8 @@
- [Uninstalling Nix](installation/uninstall.md) - [Uninstalling Nix](installation/uninstall.md)
- [Nix Store](store/index.md) - [Nix Store](store/index.md)
- [File System Object](store/file-system-object.md) - [File System Object](store/file-system-object.md)
- [Store Object](store/store-object.md)
- [Store Path](store/store-path.md)
- [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)
@ -113,6 +115,7 @@
- [C++ style guide](contributing/cxx.md) - [C++ style guide](contributing/cxx.md)
- [Release Notes](release-notes/release-notes.md) - [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.md) - [Release X.Y (202?-??-??)](release-notes/rl-next.md)
- [Release 2.19 (2023-11-17)](release-notes/rl-2.19.md)
- [Release 2.18 (2023-09-20)](release-notes/rl-2.18.md) - [Release 2.18 (2023-09-20)](release-notes/rl-2.18.md)
- [Release 2.17 (2023-07-24)](release-notes/rl-2.17.md) - [Release 2.17 (2023-07-24)](release-notes/rl-2.17.md)
- [Release 2.16 (2023-05-31)](release-notes/rl-2.16.md) - [Release 2.16 (2023-05-31)](release-notes/rl-2.16.md)

View file

@ -63,7 +63,7 @@ The command line interface and Nix expressions are what users deal with most.
> The Nix language itself does not have a notion of *packages* or *configurations*. > 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. > 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. Underlying the command line interface and the Nix language evaluator is the [Nix store](../store/index.md), 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. 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. A build plan itself is a series of *build tasks*, together with their build inputs.

View file

@ -162,6 +162,24 @@ Please observe these guidelines to ease reviews:
> This is a note. > This is a note.
``` ```
Highlight examples as such:
````
> **Example**
>
> ```console
> $ nix --version
> ```
````
Highlight syntax definiions as such, using [EBNF](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form) notation:
````
> **Syntax**
>
> *attribute-set* = `{` [ *attribute-name* `=` *expression* `;` ... ] `}`
````
### The `@docroot@` variable ### The `@docroot@` variable
`@docroot@` provides a base path for links that occur in reusable snippets or other documentation that doesn't have a base path of its own. `@docroot@` provides a base path for links that occur in reusable snippets or other documentation that doesn't have a base path of its own.

View file

@ -210,7 +210,7 @@ See [supported compilation environments](#compilation-environments) and instruct
To use the LSP with your editor, you first need to [set up `clangd`](https://clangd.llvm.org/installation#project-setup) by running: To use the LSP with your editor, you first need to [set up `clangd`](https://clangd.llvm.org/installation#project-setup) by running:
```console ```console
make clean && bear -- make -j$NIX_BUILD_CORES install make clean && bear -- make -j$NIX_BUILD_CORES default check install
``` ```
Configure your editor to use the `clangd` from the shell, either by running it inside the development shell, or by using [nix-direnv](https://github.com/nix-community/nix-direnv) and [the appropriate editor plugin](https://github.com/direnv/direnv/wiki#editor-integration). Configure your editor to use the `clangd` from the shell, either by running it inside the development shell, or by using [nix-direnv](https://github.com/nix-community/nix-direnv) and [the appropriate editor plugin](https://github.com/direnv/direnv/wiki#editor-integration).

View file

@ -133,17 +133,17 @@ ran test tests/functional/${testName}.sh... [PASS]
or without `make`: or without `make`:
```shell-session ```shell-session
$ ./mk/run-test.sh tests/functional/${testName}.sh $ ./mk/run-test.sh tests/functional/${testName}.sh tests/functional/init.sh
ran test tests/functional/${testName}.sh... [PASS] ran test tests/functional/${testName}.sh... [PASS]
``` ```
To see the complete output, one can also run: To see the complete output, one can also run:
```shell-session ```shell-session
$ ./mk/debug-test.sh tests/functional/${testName}.sh $ ./mk/debug-test.sh tests/functional/${testName}.sh tests/functional/init.sh
+ foo +(${testName}.sh:1) foo
output from foo output from foo
+ bar +(${testName}.sh:2) bar
output from bar output from bar
... ...
``` ```
@ -175,7 +175,7 @@ edit it like so:
Then, running the test with `./mk/debug-test.sh` will drop you into GDB once the script reaches that point: Then, running the test with `./mk/debug-test.sh` will drop you into GDB once the script reaches that point:
```shell-session ```shell-session
$ ./mk/debug-test.sh tests/functional/${testName}.sh $ ./mk/debug-test.sh tests/functional/${testName}.sh tests/functional/init.sh
... ...
+ gdb blash blub + gdb blash blub
GNU gdb (GDB) 12.1 GNU gdb (GDB) 12.1

View file

@ -59,7 +59,7 @@
- [store]{#gloss-store} - [store]{#gloss-store}
A collection of store objects, with operations to manipulate that collection. A collection of store objects, with operations to manipulate that collection.
See [Nix Store] for details. See [Nix store](./store/index.md) for details.
There are many types of stores. There are many types of stores.
See [`nix help-stores`](@docroot@/command-ref/new-cli/nix3-help-stores.md) for a complete list. See [`nix help-stores`](@docroot@/command-ref/new-cli/nix3-help-stores.md) for a complete list.
@ -86,10 +86,13 @@
- [store path]{#gloss-store-path} - [store path]{#gloss-store-path}
The location of a [store object] in the file system, i.e., an The location of a [store object](@docroot@/store/index.md#store-object) in the file system, i.e., an immediate child of the Nix store directory.
immediate child of the Nix store directory.
Example: `/nix/store/a040m110amc4h71lds2jmr8qrkj2jhxd-git-2.38.1` > **Example**
>
> `/nix/store/a040m110amc4h71lds2jmr8qrkj2jhxd-git-2.38.1`
See [Store Path](@docroot@/store/store-path.md) for details.
[store path]: #gloss-store-path [store path]: #gloss-store-path

View file

@ -132,6 +132,32 @@ a = src-set.a; b = src-set.b; c = src-set.c;
when used while defining local variables in a let-expression or while when used while defining local variables in a let-expression or while
defining a set. defining a set.
In a `let` expression, `inherit` can be used to selectively bring specific attributes of a set into scope. For example
```nix
let
x = { a = 1; b = 2; };
inherit (builtins) attrNames;
in
{
names = attrNames x;
}
```
is equivalent to
```nix
let
x = { a = 1; b = 2; };
in
{
names = builtins.attrNames x;
}
```
both evaluate to `{ names = [ "a" "b" ]; }`.
## Functions ## Functions
Functions have the following form: Functions have the following form:
@ -146,65 +172,65 @@ three kinds of patterns:
- If a pattern is a single identifier, then the function matches any - If a pattern is a single identifier, then the function matches any
argument. Example: argument. Example:
```nix ```nix
let negate = x: !x; let negate = x: !x;
concat = x: y: x + y; concat = x: y: x + y;
in if negate true then concat "foo" "bar" else "" in if negate true then concat "foo" "bar" else ""
``` ```
Note that `concat` is a function that takes one argument and returns Note that `concat` is a function that takes one argument and returns
a function that takes another argument. This allows partial a function that takes another argument. This allows partial
parameterisation (i.e., only filling some of the arguments of a parameterisation (i.e., only filling some of the arguments of a
function); e.g., function); e.g.,
```nix ```nix
map (concat "foo") [ "bar" "bla" "abc" ] map (concat "foo") [ "bar" "bla" "abc" ]
``` ```
evaluates to `[ "foobar" "foobla" "fooabc" ]`. evaluates to `[ "foobar" "foobla" "fooabc" ]`.
- A *set pattern* of the form `{ name1, name2, …, nameN }` matches a - A *set pattern* of the form `{ name1, name2, …, nameN }` matches a
set containing the listed attributes, and binds the values of those set containing the listed attributes, and binds the values of those
attributes to variables in the function body. For example, the attributes to variables in the function body. For example, the
function function
```nix ```nix
{ x, y, z }: z + y + x { x, y, z }: z + y + x
``` ```
can only be called with a set containing exactly the attributes `x`, can only be called with a set containing exactly the attributes `x`,
`y` and `z`. No other attributes are allowed. If you want to allow `y` and `z`. No other attributes are allowed. If you want to allow
additional arguments, you can use an ellipsis (`...`): additional arguments, you can use an ellipsis (`...`):
```nix ```nix
{ x, y, z, ... }: z + y + x { x, y, z, ... }: z + y + x
``` ```
This works on any set that contains at least the three named This works on any set that contains at least the three named
attributes. attributes.
It is possible to provide *default values* for attributes, in It is possible to provide *default values* for attributes, in
which case they are allowed to be missing. A default value is which case they are allowed to be missing. A default value is
specified by writing `name ? e`, where *e* is an arbitrary specified by writing `name ? e`, where *e* is an arbitrary
expression. For example, expression. For example,
```nix ```nix
{ x, y ? "foo", z ? "bar" }: z + y + x { x, y ? "foo", z ? "bar" }: z + y + x
``` ```
specifies a function that only requires an attribute named `x`, but specifies a function that only requires an attribute named `x`, but
optionally accepts `y` and `z`. optionally accepts `y` and `z`.
- An `@`-pattern provides a means of referring to the whole value - An `@`-pattern provides a means of referring to the whole value
being matched: being matched:
```nix ```nix
args@{ x, y, z, ... }: z + y + x + args.a args@{ x, y, z, ... }: z + y + x + args.a
``` ```
but can also be written as: but can also be written as:
```nix ```nix
{ x, y, z, ... } @ args: z + y + x + args.a { x, y, z, ... } @ args: z + y + x + args.a
``` ```

View file

@ -25,7 +25,7 @@
| Inequality | *expr* `!=` *expr* | none | 11 | | Inequality | *expr* `!=` *expr* | none | 11 |
| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 | | Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
| Logical disjunction (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 | | Logical disjunction (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 |
| [Logical implication] | *bool* `->` *bool* | none | 14 | | [Logical implication] | *bool* `->` *bool* | right | 14 |
[string]: ./values.md#type-string [string]: ./values.md#type-string
[path]: ./values.md#type-path [path]: ./values.md#type-path

View file

@ -0,0 +1,77 @@
# Release 2.19 (2023-11-17)
- The experimental `nix` command can now act as a [shebang interpreter](@docroot@/command-ref/new-cli/nix.md#shebang-interpreter)
by appending the contents of any `#! nix` lines and the script's location into a single call.
- [URL flake references](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references) now support [percent-encoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1) characters.
- [Path-like flake references](@docroot@/command-ref/new-cli/nix3-flake.md#path-like-syntax) now accept arbitrary unicode characters (except `#` and `?`).
- The experimental feature `repl-flake` is no longer needed, as its functionality is now part of the `flakes` experimental feature. To get the previous behavior, use the `--file/--expr` flags accordingly.
- There is a new flake installable syntax `flakeref#.attrPath` where the "." prefix specifies that `attrPath` is interpreted from the root of the flake outputs, with no searching of default attribute prefixes like `packages.<SYSTEM>` or `legacyPackages.<SYSTEM>`.
- Nix adds `apple-virt` to the default system features on macOS systems that support virtualization. This is similar to what's done for the `kvm` system feature on Linux hosts.
- Add a new built-in function [`builtins.convertHash`](@docroot@/language/builtins.md#builtins-convertHash).
- `nix-shell` shebang lines now support single-quoted arguments.
- `builtins.fetchTree` is now its own experimental feature, [`fetch-tree`](@docroot@/contributing/experimental-features.md#xp-fetch-tree).
As described in the documentation for that feature, this is because we anticipate polishing it and then stabilizing it before the rest of flakes.
- The interface for creating and updating lock files has been overhauled:
- [`nix flake lock`](@docroot@/command-ref/new-cli/nix3-flake-lock.md) only creates lock files and adds missing inputs now.
It will *never* update existing inputs.
- [`nix flake update`](@docroot@/command-ref/new-cli/nix3-flake-update.md) does the same, but *will* update inputs.
- Passing no arguments will update all inputs of the current flake, just like it already did.
- Passing input names as arguments will ensure only those are updated. This replaces the functionality of `nix flake lock --update-input`
- To operate on a flake outside the current directory, you must now pass `--flake path/to/flake`.
- The flake-specific flags `--recreate-lock-file` and `--update-input` have been removed from all commands operating on installables.
They are superceded by `nix flake update`.
- Commit signature verification for the [`builtins.fetchGit`](@docroot@/language/builtins.md#builtins-fetchGit) is added as the new [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).
- [`nix path-info --json`](@docroot@/command-ref/new-cli/nix3-path-info.md)
(experimental) now returns a JSON map rather than JSON list.
The `path` field of each object has instead become the key in the outer map, since it is unique.
The `valid` field also goes away because we just use `null` instead.
- Old way:
```json5
[
{
"path": "/nix/store/8fv91097mbh5049i9rglc73dx6kjg3qk-bash-5.2-p15",
"valid": true,
// ...
},
{
"path": "/nix/store/wffw7l0alvs3iw94cbgi1gmmbmw99sqb-home-manager-path",
"valid": false
}
]
```
- New way
```json5
{
"/nix/store/8fv91097mbh5049i9rglc73dx6kjg3qk-bash-5.2-p15": {
// ...
},
"/nix/store/wffw7l0alvs3iw94cbgi1gmmbmw99sqb-home-manager-path": null,
}
```
This makes it match `nix derivation show`, which also maps store paths to information.
- When Nix is installed using the [binary installer](@docroot@/installation/installing-binary.md), in supported shells (Bash, Zsh, Fish)
[`XDG_DATA_DIRS`](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables) is now populated with the path to the `/share` subdirectory of the current profile.
This means that command completion scripts, `.desktop` files, and similar artifacts installed via [`nix-env`](@docroot@/command-ref/nix-env.md) or [`nix profile`](@docroot@/command-ref/new-cli/nix3-profile.md)
(experimental) can be found by any program that follows the [XDG Base Directory Specification](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
- A new command `nix store add` has been added. It replaces `nix store add-file` and `nix store add-path` which are now deprecated.

View file

@ -1,19 +1,3 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
- [URL flake references](@docroot@/command-ref/new-cli/nix3-flake.md#flake-references) now support [percent-encoded](https://datatracker.ietf.org/doc/html/rfc3986#section-2.1) characters. - Fixed a bug where `nix-env --query` ignored `--drv-path` when `--json` was set.
- [Path-like flake references](@docroot@/command-ref/new-cli/nix3-flake.md#path-like-syntax) now accept arbitrary unicode characters (except `#` and `?`).
- The experimental feature `repl-flake` is no longer needed, as its functionality is now part of the `flakes` experimental feature. To get the previous behavior, use the `--file/--expr` flags accordingly.
- Introduce new flake installable syntax `flakeref#.attrPath` where the "." prefix denotes no searching of default attribute prefixes like `packages.<SYSTEM>` or `legacyPackages.<SYSTEM>`.
- Nix adds `apple-virt` to the default system features on macOS systems that support virtualization. This is similar to what's done for the `kvm` system feature on Linux hosts.
- Introduce a new built-in function [`builtins.convertHash`](@docroot@/language/builtins.md#builtins-convertHash).
- Fixed a bug where `nix-env --query` ignored `--drv-path` when `--json` was set.
- `nix-shell` shebang lines now support single-quoted arguments.
- `builtins.fetchTree` is now marked as stable.

View file

@ -1,4 +1,5 @@
# Nix Store # Nix Store
The *Nix store* is an abstraction used by Nix to store immutable filesystem artifacts (such as software packages) that can have dependencies (*references*) between them. The *Nix store* is an abstraction to store immutable file system data (such as software packages) that can have dependencies on other such data.
There are multiple implementations of the Nix store, such as the actual filesystem (`/nix/store`) and binary caches.
There are multiple implementations of Nix stores with different capabilities, such as the actual filesystem (`/nix/store`) or binary caches.

View file

@ -0,0 +1,10 @@
## Store Object
A Nix store is a collection of *store objects* with *references* between them.
A store object consists of
- A [file system object](./file-system-object.md) as data
- A set of [store paths](./store-path.md) as references to other store objects
Store objects are [immutable](https://en.wikipedia.org/wiki/Immutable_object):
Once created, they do not change until they are deleted.

View file

@ -0,0 +1,69 @@
# Store Path
Nix implements references to [store objects](./index.md#store-object) as *store paths*.
Think of a store path as an [opaque], [unique identifier]:
The only way to obtain store path is by adding or building store objects.
A store path will always reference exactly one store object.
[opaque]: https://en.m.wikipedia.org/wiki/Opaque_data_type
[unique identifier]: https://en.m.wikipedia.org/wiki/Unique_identifier
Store paths are pairs of
- A 20-byte digest for identification
- A symbolic name for people to read
> **Example**
>
> - Digest: `b6gvzjyb2pg0kjfwrjmg1vfhh54ad73z`
> - Name: `firefox-33.1`
To make store objects accessible to operating system processes, stores have to expose store objects through the file system.
A store path is rendered to a file system path as the concatenation of
- [Store directory](#store-directory) (typically `/nix/store`)
- Path separator (`/`)
- Digest rendered in a custom variant of [Base32](https://en.wikipedia.org/wiki/Base32) (20 arbitrary bytes become 32 ASCII characters)
- Hyphen (`-`)
- Name
> **Example**
>
> ```
> /nix/store/b6gvzjyb2pg0kjfwrjmg1vfhh54ad73z-firefox-33.1
> |--------| |------------------------------| |----------|
> store directory digest name
> ```
## Store Directory
Every [Nix store](./index.md) has a store directory.
Not every store can be accessed through the file system.
But if the store has a file system representation, the store directory contains the stores [file system objects], which can be addressed by [store paths](#store-path).
[file system objects]: ./file-system-object.md
This means a store path is not just derived from the referenced store object itself, but depends on the store the store object is in.
> **Note**
>
> The store directory defaults to `/nix/store`, but is in principle arbitrary.
It is important which store a given store object belongs to:
Files in the store object can contain store paths, and processes may read these paths.
Nix can only guarantee referential integrity if store paths do not cross store boundaries.
Therefore one can only copy store objects to a different store if
- The source and target stores' directories match
or
- The store object in question has no references, that is, contains no store paths
One cannot copy a store object to a store with a different store directory.
Instead, it has to be rebuilt, together with all its dependencies.
It is in general not enough to replace the store directory string in file contents, as this may render executables unusable by invalidating their internal offsets or checksums.

View file

@ -34,16 +34,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1695283060, "lastModified": 1698876495,
"narHash": "sha256-CJz71xhCLlRkdFUSQEL0pIAAfcnWFXMzd9vXhPrnrEg=", "narHash": "sha256-nsQo2/mkDUFeAjuu92p0dEqhRvHHiENhkKVIV1y0/Oo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "31ed632c692e6a36cfc18083b88ece892f863ed4", "rev": "9eb24edd6a0027fed010ccfe300a9734d029983c",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-23.05-small", "ref": "release-23.05",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View file

@ -1,7 +1,9 @@
{ {
description = "The purely functional package manager"; description = "The purely functional package manager";
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05-small"; # FIXME go back to nixos-23.05-small once
# https://github.com/NixOS/nixpkgs/pull/264875 is included.
inputs.nixpkgs.url = "github:NixOS/nixpkgs/release-23.05";
inputs.nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2"; inputs.nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
inputs.lowdown-src = { url = "github:kristapsdz/lowdown"; flake = false; }; inputs.lowdown-src = { url = "github:kristapsdz/lowdown"; flake = false; };
inputs.flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; inputs.flake-compat = { url = "github:edolstra/flake-compat"; flake = false; };
@ -162,6 +164,10 @@
testConfigureFlags = [ testConfigureFlags = [
"RAPIDCHECK_HEADERS=${lib.getDev rapidcheck}/extras/gtest/include" "RAPIDCHECK_HEADERS=${lib.getDev rapidcheck}/extras/gtest/include"
] ++ lib.optionals (stdenv.hostPlatform != stdenv.buildPlatform) [
"--enable-install-unit-tests"
"--with-check-bin-dir=${builtins.placeholder "check"}/bin"
"--with-check-lib-dir=${builtins.placeholder "check"}/lib"
]; ];
internalApiDocsConfigureFlags = [ internalApiDocsConfigureFlags = [
@ -183,6 +189,7 @@
buildPackages.git buildPackages.git
buildPackages.mercurial # FIXME: remove? only needed for tests buildPackages.mercurial # FIXME: remove? only needed for tests
buildPackages.jq # Also for custom mdBook preprocessor. buildPackages.jq # Also for custom mdBook preprocessor.
buildPackages.openssh # only needed for tests (ssh-keygen)
] ]
++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)]; ++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)];
@ -401,7 +408,8 @@
src = nixSrc; src = nixSrc;
VERSION_SUFFIX = versionSuffix; VERSION_SUFFIX = versionSuffix;
outputs = [ "out" "dev" "doc" ]; outputs = [ "out" "dev" "doc" ]
++ lib.optional (currentStdenv.hostPlatform != currentStdenv.buildPlatform) "check";
nativeBuildInputs = nativeBuildDeps; nativeBuildInputs = nativeBuildDeps;
buildInputs = buildDeps buildInputs = buildDeps
@ -707,7 +715,8 @@
stdenv.mkDerivation { stdenv.mkDerivation {
name = "nix"; name = "nix";
outputs = [ "out" "dev" "doc" ]; outputs = [ "out" "dev" "doc" ]
++ lib.optional (stdenv.hostPlatform != stdenv.buildPlatform) "check";
nativeBuildInputs = nativeBuildDeps nativeBuildInputs = nativeBuildDeps
++ lib.optional stdenv.cc.isClang pkgs.buildPackages.bear ++ lib.optional stdenv.cc.isClang pkgs.buildPackages.bear

64
m4/gcc_bug_80431.m4 Normal file
View file

@ -0,0 +1,64 @@
# Ensure that this bug is not present in the C++ toolchain we are using.
#
# URL for bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80431
#
# The test program is from that issue, with only a slight modification
# to set an exit status instead of printing strings.
AC_DEFUN([ENSURE_NO_GCC_BUG_80431],
[
AC_MSG_CHECKING([that GCC bug 80431 is fixed])
AC_LANG_PUSH(C++)
AC_RUN_IFELSE(
[AC_LANG_PROGRAM(
[[
#include <cstdio>
static bool a = true;
static bool b = true;
struct Options { };
struct Option
{
Option(Options * options)
{
a = false;
}
~Option()
{
b = false;
}
};
struct MyOptions : Options { };
struct MyOptions2 : virtual MyOptions
{
Option foo{this};
};
]],
[[
{
MyOptions2 opts;
}
return (a << 1) | b;
]])],
[status_80431=0],
[status_80431=$?],
[
# Assume we're bug-free when cross-compiling
])
AC_LANG_POP(C++)
AS_CASE([$status_80431],
[0],[
AC_MSG_RESULT(yes)
],
[2],[
AC_MSG_RESULT(no)
AC_MSG_ERROR(Cannot build Nix with C++ compiler with this bug)
],
[
AC_MSG_RESULT(unexpected result $status_80431: not expected failure with bug, ignoring)
])
])

View file

@ -2,7 +2,7 @@
## Motivation ## Motivation
The team's main responsibility is to set a direction for the development of Nix and ensure that the code is in good shape. The team's main responsibility is to guide and direct the development of Nix and ensure that the code is in good shape.
We aim to achieve this by improving the contributor experience and attracting more maintainers that is, by helping other people contributing to Nix and eventually taking responsibility in order to scale the development process to match users' needs. We aim to achieve this by improving the contributor experience and attracting more maintainers that is, by helping other people contributing to Nix and eventually taking responsibility in order to scale the development process to match users' needs.

View file

@ -1,15 +1,27 @@
test_dir=tests/functional # Remove overall test dir (at most one of the two should match) and
# remove file extension.
test_name=$(echo -n "$test" | sed \
-e "s|^unit-test-data/||" \
-e "s|^tests/functional/||" \
-e "s|\.sh$||" \
)
test=$(echo -n "$test" | sed -e "s|^$test_dir/||") TESTS_ENVIRONMENT=(
"TEST_NAME=$test_name"
TESTS_ENVIRONMENT=("TEST_NAME=${test%.*}" 'NIX_REMOTE=') 'NIX_REMOTE='
'PS4=+(${BASH_SOURCE[0]-$0}:$LINENO) '
)
: ${BASH:=/usr/bin/env bash} : ${BASH:=/usr/bin/env bash}
run () {
cd "$(dirname $1)" && env "${TESTS_ENVIRONMENT[@]}" $BASH -x -e -u -o pipefail $(basename $1)
}
init_test () { init_test () {
cd "$test_dir" && env "${TESTS_ENVIRONMENT[@]}" $BASH -e init.sh 2>/dev/null > /dev/null run "$init" 2>/dev/null > /dev/null
} }
run_test_proper () { run_test_proper () {
cd "$test_dir/$(dirname $test)" && env "${TESTS_ENVIRONMENT[@]}" $BASH -e $(basename $test) run "$test"
} }

View file

@ -3,9 +3,12 @@
set -eu -o pipefail set -eu -o pipefail
test=$1 test=$1
init=${2-}
dir="$(dirname "${BASH_SOURCE[0]}")" dir="$(dirname "${BASH_SOURCE[0]}")"
source "$dir/common-test.sh" source "$dir/common-test.sh"
(init_test) if [ -n "$init" ]; then
(init_test)
fi
run_test_proper run_test_proper

View file

@ -122,14 +122,15 @@ $(foreach script, $(bin-scripts), $(eval $(call install-program-in,$(script),$(b
$(foreach script, $(bin-scripts), $(eval programs-list += $(script))) $(foreach script, $(bin-scripts), $(eval programs-list += $(script)))
$(foreach script, $(noinst-scripts), $(eval programs-list += $(script))) $(foreach script, $(noinst-scripts), $(eval programs-list += $(script)))
$(foreach template, $(template-files), $(eval $(call instantiate-template,$(template)))) $(foreach template, $(template-files), $(eval $(call instantiate-template,$(template))))
install_test_init=tests/functional/init.sh
$(foreach test, $(install-tests), \ $(foreach test, $(install-tests), \
$(eval $(call run-install-test,$(test))) \ $(eval $(call run-test,$(test),$(install_test_init))) \
$(eval installcheck: $(test).test)) $(eval installcheck: $(test).test))
$(foreach test-group, $(install-tests-groups), \ $(foreach test-group, $(install-tests-groups), \
$(eval $(call run-install-test-group,$(test-group))) \ $(eval $(call run-test-group,$(test-group),$(install_test_init))) \
$(eval installcheck: $(test-group).test-group) \ $(eval installcheck: $(test-group).test-group) \
$(foreach test, $($(test-group)-tests), \ $(foreach test, $($(test-group)-tests), \
$(eval $(call run-install-test,$(test))) \ $(eval $(call run-test,$(test),$(install_test_init))) \
$(eval $(test-group).test-group: $(test).test))) $(eval $(test-group).test-group: $(test).test)))
$(foreach file, $(man-pages), $(eval $(call install-data-in, $(file), $(mandir)/man$(patsubst .%,%,$(suffix $(file)))))) $(foreach file, $(man-pages), $(eval $(call install-data-in, $(file), $(mandir)/man$(patsubst .%,%,$(suffix $(file))))))

View file

@ -8,6 +8,7 @@ yellow=""
normal="" normal=""
test=$1 test=$1
init=${2-}
dir="$(dirname "${BASH_SOURCE[0]}")" dir="$(dirname "${BASH_SOURCE[0]}")"
source "$dir/common-test.sh" source "$dir/common-test.sh"
@ -21,7 +22,9 @@ if [ -t 1 ]; then
fi fi
run_test () { run_test () {
(init_test 2>/dev/null > /dev/null) if [ -n "$init" ]; then
(init_test 2>/dev/null > /dev/null)
fi
log="$(run_test_proper 2>&1)" && status=0 || status=$? log="$(run_test_proper 2>&1)" && status=0 || status=$?
} }

View file

@ -2,19 +2,22 @@
test-deps = test-deps =
define run-install-test define run-bash
.PHONY: $1.test .PHONY: $1
$1.test: $1 $(test-deps) $1: $2
@env BASH=$(bash) $(bash) mk/run-test.sh $1 < /dev/null @env BASH=$(bash) $(bash) $3 < /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
define run-install-test-group define run-test
$(eval $(call run-bash,$1.test,$1 $(test-deps),mk/run-test.sh $1 $2))
$(eval $(call run-bash,$1.test-debug,$1 $(test-deps),mk/debug-test.sh $1 $2))
endef
define run-test-group
.PHONY: $1.test-group .PHONY: $1.test-group

View file

@ -11,7 +11,6 @@
#include "derivations.hh" #include "derivations.hh"
#include "globals.hh" #include "globals.hh"
#include "store-api.hh" #include "store-api.hh"
#include "util.hh"
#include "crypto.hh" #include "crypto.hh"
#include <sodium.h> #include <sodium.h>

View file

@ -19,6 +19,14 @@ set __ETC_PROFILE_NIX_SOURCED 1
set --export NIX_PROFILES "@localstatedir@/nix/profiles/default $HOME/.nix-profile" set --export NIX_PROFILES "@localstatedir@/nix/profiles/default $HOME/.nix-profile"
# Populate bash completions, .desktop files, etc
if test -z "$XDG_DATA_DIRS"
# According to XDG spec the default is /usr/local/share:/usr/share, don't set something that prevents that default
set --export XDG_DATA_DIRS "/usr/local/share:/usr/share:/nix/var/nix/profiles/default/share"
else
set --export XDG_DATA_DIRS "$XDG_DATA_DIRS:/nix/var/nix/profiles/default/share"
end
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
if test -n "$NIX_SSH_CERT_FILE" if test -n "$NIX_SSH_CERT_FILE"
: # Allow users to override the NIX_SSL_CERT_FILE : # Allow users to override the NIX_SSL_CERT_FILE

View file

@ -30,6 +30,14 @@ fi
export NIX_PROFILES="@localstatedir@/nix/profiles/default $NIX_LINK" export NIX_PROFILES="@localstatedir@/nix/profiles/default $NIX_LINK"
# Populate bash completions, .desktop files, etc
if [ -z "$XDG_DATA_DIRS" ]; then
# According to XDG spec the default is /usr/local/share:/usr/share, don't set something that prevents that default
export XDG_DATA_DIRS="/usr/local/share:/usr/share:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
else
export XDG_DATA_DIRS="$XDG_DATA_DIRS:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
fi
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
if [ -n "${NIX_SSL_CERT_FILE:-}" ]; then if [ -n "${NIX_SSL_CERT_FILE:-}" ]; then
: # Allow users to override the NIX_SSL_CERT_FILE : # Allow users to override the NIX_SSL_CERT_FILE

View file

@ -20,6 +20,14 @@ if test -n "$HOME" && test -n "$USER"
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix # This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
set --export NIX_PROFILES "@localstatedir@/nix/profiles/default $HOME/.nix-profile" set --export NIX_PROFILES "@localstatedir@/nix/profiles/default $HOME/.nix-profile"
# Populate bash completions, .desktop files, etc
if test -z "$XDG_DATA_DIRS"
# According to XDG spec the default is /usr/local/share:/usr/share, don't set something that prevents that default
set --export XDG_DATA_DIRS "/usr/local/share:/usr/share:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
else
set --export XDG_DATA_DIRS "$XDG_DATA_DIRS:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
end
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
if test -n "$NIX_SSH_CERT_FILE" if test -n "$NIX_SSH_CERT_FILE"
: # Allow users to override the NIX_SSL_CERT_FILE : # Allow users to override the NIX_SSL_CERT_FILE

View file

@ -32,6 +32,14 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then
# This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix # This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
export NIX_PROFILES="@localstatedir@/nix/profiles/default $NIX_LINK" export NIX_PROFILES="@localstatedir@/nix/profiles/default $NIX_LINK"
# Populate bash completions, .desktop files, etc
if [ -z "$XDG_DATA_DIRS" ]; then
# According to XDG spec the default is /usr/local/share:/usr/share, don't set something that prevents that default
export XDG_DATA_DIRS="/usr/local/share:/usr/share:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
else
export XDG_DATA_DIRS="$XDG_DATA_DIRS:$NIX_LINK/share:/nix/var/nix/profiles/default/share"
fi
# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
if [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch if [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch
export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt export NIX_SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt

View file

@ -1,3 +1,6 @@
#pragma once
///@file
#include "derived-path.hh" #include "derived-path.hh"
#include "realisation.hh" #include "realisation.hh"

View file

@ -175,7 +175,7 @@ void BuiltPathsCommand::run(ref<Store> store, Installables && installables)
throw UsageError("'--all' does not expect arguments"); throw UsageError("'--all' does not expect arguments");
// XXX: Only uses opaque paths, ignores all the realisations // XXX: Only uses opaque paths, ignores all the realisations
for (auto & p : store->queryAllValidPaths()) for (auto & p : store->queryAllValidPaths())
paths.push_back(BuiltPath::Opaque{p}); paths.emplace_back(BuiltPath::Opaque{p});
} else { } else {
paths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables); paths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
if (recursive) { if (recursive) {
@ -188,7 +188,7 @@ void BuiltPathsCommand::run(ref<Store> store, Installables && installables)
} }
store->computeFSClosure(pathsRoots, pathsClosure); store->computeFSClosure(pathsRoots, pathsClosure);
for (auto & path : pathsClosure) for (auto & path : pathsClosure)
paths.push_back(BuiltPath::Opaque{path}); paths.emplace_back(BuiltPath::Opaque{path});
} }
} }

View file

@ -326,6 +326,12 @@ struct MixEnvironment : virtual Args {
void setEnviron(); void setEnviron();
}; };
void completeFlakeInputPath(
AddCompletions & completions,
ref<EvalState> evalState,
const std::vector<FlakeRef> & flakeRefs,
std::string_view prefix);
void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::string_view prefix); void completeFlakeRef(AddCompletions & completions, ref<Store> store, std::string_view prefix);
void completeFlakeRefWithFragment( void completeFlakeRefWithFragment(

View file

@ -2,7 +2,6 @@
#include "common-eval-args.hh" #include "common-eval-args.hh"
#include "shared.hh" #include "shared.hh"
#include "filetransfer.hh" #include "filetransfer.hh"
#include "util.hh"
#include "eval.hh" #include "eval.hh"
#include "fetchers.hh" #include "fetchers.hh"
#include "registry.hh" #include "registry.hh"
@ -165,7 +164,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
return res.finish(); return res.finish();
} }
SourcePath lookupFileArg(EvalState & state, std::string_view s) SourcePath lookupFileArg(EvalState & state, std::string_view s, CanonPath baseDir)
{ {
if (EvalSettings::isPseudoUrl(s)) { if (EvalSettings::isPseudoUrl(s)) {
auto storePath = fetchers::downloadTarball( auto storePath = fetchers::downloadTarball(
@ -186,7 +185,7 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s)
} }
else else
return state.rootPath(CanonPath::fromCwd(s)); return state.rootPath(CanonPath(s, baseDir));
} }
} }

View file

@ -2,6 +2,7 @@
///@file ///@file
#include "args.hh" #include "args.hh"
#include "canon-path.hh"
#include "common-args.hh" #include "common-args.hh"
#include "search-path.hh" #include "search-path.hh"
@ -28,6 +29,6 @@ private:
std::map<std::string, std::string> autoArgs; std::map<std::string, std::string> autoArgs;
}; };
SourcePath lookupFileArg(EvalState & state, std::string_view s); SourcePath lookupFileArg(EvalState & state, std::string_view s, CanonPath baseDir = CanonPath::fromCwd());
} }

View file

@ -1,5 +1,5 @@
#include "util.hh"
#include "editor-for.hh" #include "editor-for.hh"
#include "environment-variables.hh"
namespace nix { namespace nix {

View file

@ -4,7 +4,6 @@
#include "globals.hh" #include "globals.hh"
#include "installable-value.hh" #include "installable-value.hh"
#include "outputs-spec.hh" #include "outputs-spec.hh"
#include "util.hh"
#include "command.hh" #include "command.hh"
#include "attr-path.hh" #include "attr-path.hh"
#include "common-eval-args.hh" #include "common-eval-args.hh"

View file

@ -4,6 +4,7 @@
#include "installable-attr-path.hh" #include "installable-attr-path.hh"
#include "installable-flake.hh" #include "installable-flake.hh"
#include "outputs-spec.hh" #include "outputs-spec.hh"
#include "users.hh"
#include "util.hh" #include "util.hh"
#include "command.hh" #include "command.hh"
#include "attr-path.hh" #include "attr-path.hh"
@ -28,7 +29,7 @@
namespace nix { namespace nix {
static void completeFlakeInputPath( void completeFlakeInputPath(
AddCompletions & completions, AddCompletions & completions,
ref<EvalState> evalState, ref<EvalState> evalState,
const std::vector<FlakeRef> & flakeRefs, const std::vector<FlakeRef> & flakeRefs,
@ -46,13 +47,6 @@ MixFlakeOptions::MixFlakeOptions()
{ {
auto category = "Common flake-related options"; auto category = "Common flake-related options";
addFlag({
.longName = "recreate-lock-file",
.description = "Recreate the flake's lock file from scratch.",
.category = category,
.handler = {&lockFlags.recreateLockFile, true}
});
addFlag({ addFlag({
.longName = "no-update-lock-file", .longName = "no-update-lock-file",
.description = "Do not allow any updates to the flake's lock file.", .description = "Do not allow any updates to the flake's lock file.",
@ -85,19 +79,6 @@ MixFlakeOptions::MixFlakeOptions()
.handler = {&lockFlags.commitLockFile, true} .handler = {&lockFlags.commitLockFile, true}
}); });
addFlag({
.longName = "update-input",
.description = "Update a specific flake input (ignoring its previous entry in the lock file).",
.category = category,
.labels = {"input-path"},
.handler = {[&](std::string s) {
lockFlags.inputUpdates.insert(flake::parseInputPath(s));
}},
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
completeFlakeInputPath(completions, getEvalState(), getFlakeRefsForCompletion(), prefix);
}}
});
addFlag({ addFlag({
.longName = "override-input", .longName = "override-input",
.description = "Override a specific flake input (e.g. `dwarffs/nixpkgs`). This implies `--no-write-lock-file`.", .description = "Override a specific flake input (e.g. `dwarffs/nixpkgs`). This implies `--no-write-lock-file`.",
@ -107,7 +88,7 @@ MixFlakeOptions::MixFlakeOptions()
lockFlags.writeLockFile = false; lockFlags.writeLockFile = false;
lockFlags.inputOverrides.insert_or_assign( lockFlags.inputOverrides.insert_or_assign(
flake::parseInputPath(inputPath), flake::parseInputPath(inputPath),
parseFlakeRef(flakeRef, absPath("."), true)); parseFlakeRef(flakeRef, absPath(getCommandBaseDir()), true));
}}, }},
.completer = {[&](AddCompletions & completions, size_t n, std::string_view prefix) { .completer = {[&](AddCompletions & completions, size_t n, std::string_view prefix) {
if (n == 0) { if (n == 0) {
@ -149,7 +130,7 @@ MixFlakeOptions::MixFlakeOptions()
auto evalState = getEvalState(); auto evalState = getEvalState();
auto flake = flake::lockFlake( auto flake = flake::lockFlake(
*evalState, *evalState,
parseFlakeRef(flakeRef, absPath(".")), parseFlakeRef(flakeRef, absPath(getCommandBaseDir())),
{ .writeLockFile = false }); { .writeLockFile = false });
for (auto & [inputName, input] : flake.lockFile.root->inputs) { for (auto & [inputName, input] : flake.lockFile.root->inputs) {
auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes
@ -313,6 +294,8 @@ void completeFlakeRefWithFragment(
prefixRoot = "."; prefixRoot = ".";
} }
auto flakeRefS = std::string(prefix.substr(0, hash)); auto flakeRefS = std::string(prefix.substr(0, hash));
// TODO: ideally this would use the command base directory instead of assuming ".".
auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath(".")); auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath("."));
auto evalCache = openEvalCache(*evalState, auto evalCache = openEvalCache(*evalState,
@ -461,10 +444,12 @@ Installables SourceExprCommand::parseInstallables(
auto e = state->parseStdin(); auto e = state->parseStdin();
state->eval(e, *vFile); state->eval(e, *vFile);
} }
else if (file) else if (file) {
state->evalFile(lookupFileArg(*state, *file), *vFile); state->evalFile(lookupFileArg(*state, *file, CanonPath::fromCwd(getCommandBaseDir())), *vFile);
}
else { else {
auto e = state->parseExprFromString(*expr, state->rootPath(CanonPath::fromCwd())); CanonPath dir(CanonPath::fromCwd(getCommandBaseDir()));
auto e = state->parseExprFromString(*expr, state->rootPath(dir));
state->eval(e, *vFile); state->eval(e, *vFile);
} }
@ -499,7 +484,7 @@ Installables SourceExprCommand::parseInstallables(
} }
try { try {
auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, absPath(".")); auto [flakeRef, fragment] = parseFlakeRefWithFragment(std::string { prefix }, absPath(getCommandBaseDir()));
result.push_back(make_ref<InstallableFlake>( result.push_back(make_ref<InstallableFlake>(
this, this,
getEvalState(), getEvalState(),
@ -683,7 +668,7 @@ BuiltPaths Installable::toBuiltPaths(
BuiltPaths res; BuiltPaths res;
for (auto & drvPath : Installable::toDerivations(store, installables, true)) for (auto & drvPath : Installable::toDerivations(store, installables, true))
res.push_back(BuiltPath::Opaque{drvPath}); res.emplace_back(BuiltPath::Opaque{drvPath});
return res; return res;
} }
} }
@ -773,7 +758,7 @@ std::vector<FlakeRef> RawInstallablesCommand::getFlakeRefsForCompletion()
for (auto i : rawInstallables) for (auto i : rawInstallables)
res.push_back(parseFlakeRefWithFragment( res.push_back(parseFlakeRefWithFragment(
expandTilde(i), expandTilde(i),
absPath(".")).first); absPath(getCommandBaseDir())).first);
return res; return res;
} }
@ -795,7 +780,7 @@ std::vector<FlakeRef> InstallableCommand::getFlakeRefsForCompletion()
return { return {
parseFlakeRefWithFragment( parseFlakeRefWithFragment(
expandTilde(_installable), expandTilde(_installable),
absPath(".")).first absPath(getCommandBaseDir())).first
}; };
} }

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
///@file ///@file
#include "util.hh"
#include "path.hh" #include "path.hh"
#include "outputs-spec.hh" #include "outputs-spec.hh"
#include "derived-path.hh" #include "derived-path.hh"

View file

@ -1,6 +1,7 @@
#include "markdown.hh" #include "markdown.hh"
#include "util.hh" #include "util.hh"
#include "finally.hh" #include "finally.hh"
#include "terminal.hh"
#include <sys/queue.h> #include <sys/queue.h>
#include <lowdown.h> #include <lowdown.h>

View file

@ -22,6 +22,7 @@ extern "C" {
#include "repl.hh" #include "repl.hh"
#include "ansicolor.hh" #include "ansicolor.hh"
#include "signals.hh"
#include "shared.hh" #include "shared.hh"
#include "eval.hh" #include "eval.hh"
#include "eval-cache.hh" #include "eval-cache.hh"
@ -36,6 +37,8 @@ extern "C" {
#include "globals.hh" #include "globals.hh"
#include "flake/flake.hh" #include "flake/flake.hh"
#include "flake/lockfile.hh" #include "flake/lockfile.hh"
#include "users.hh"
#include "terminal.hh"
#include "editor-for.hh" #include "editor-for.hh"
#include "finally.hh" #include "finally.hh"
#include "markdown.hh" #include "markdown.hh"

View file

@ -1,6 +1,5 @@
#include "attr-path.hh" #include "attr-path.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "util.hh"
namespace nix { namespace nix {

View file

@ -1,3 +1,4 @@
#include "users.hh"
#include "eval-cache.hh" #include "eval-cache.hh"
#include "sqlite.hh" #include "sqlite.hh"
#include "eval.hh" #include "eval.hh"

View file

@ -1,3 +1,4 @@
#include "users.hh"
#include "globals.hh" #include "globals.hh"
#include "profiles.hh" #include "profiles.hh"
#include "eval.hh" #include "eval.hh"

View file

@ -1,4 +1,6 @@
#pragma once #pragma once
///@file
#include "config.hh" #include "config.hh"
namespace nix { namespace nix {

View file

@ -1,6 +1,7 @@
#include "eval.hh" #include "eval.hh"
#include "eval-settings.hh" #include "eval-settings.hh"
#include "hash.hh" #include "hash.hh"
#include "primops.hh"
#include "types.hh" #include "types.hh"
#include "util.hh" #include "util.hh"
#include "store-api.hh" #include "store-api.hh"
@ -14,6 +15,7 @@
#include "print.hh" #include "print.hh"
#include "fs-input-accessor.hh" #include "fs-input-accessor.hh"
#include "memory-input-accessor.hh" #include "memory-input-accessor.hh"
#include "signals.hh"
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
@ -29,6 +31,7 @@
#include <sys/resource.h> #include <sys/resource.h>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include <boost/container/small_vector.hpp>
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
@ -721,6 +724,23 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info)
} }
void PrimOp::check()
{
if (arity > maxPrimOpArity) {
throw Error("primop arity must not exceed %1%", maxPrimOpArity);
}
}
void Value::mkPrimOp(PrimOp * p)
{
p->check();
clearValue();
internalType = tPrimOp;
primOp = p;
}
Value * EvalState::addPrimOp(PrimOp && primOp) Value * EvalState::addPrimOp(PrimOp && primOp)
{ {
/* Hack to make constants lazy: turn them into a application of /* Hack to make constants lazy: turn them into a application of
@ -1690,7 +1710,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
/* We have all the arguments, so call the primop with /* We have all the arguments, so call the primop with
the previous and new arguments. */ the previous and new arguments. */
Value * vArgs[arity]; Value * vArgs[maxPrimOpArity];
auto n = argsDone; auto n = argsDone;
for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->primOpApp.left) for (Value * arg = &vCur; arg->isPrimOpApp(); arg = arg->primOpApp.left)
vArgs[--n] = arg->primOpApp.right; vArgs[--n] = arg->primOpApp.right;
@ -1747,11 +1767,17 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v)
Value vFun; Value vFun;
fun->eval(state, env, vFun); fun->eval(state, env, vFun);
Value * vArgs[args.size()]; // Empirical arity of Nixpkgs lambdas by regex e.g. ([a-zA-Z]+:(\s|(/\*.*\/)|(#.*\n))*){5}
// 2: over 4000
// 3: about 300
// 4: about 60
// 5: under 10
// This excluded attrset lambdas (`{...}:`). Contributions of mixed lambdas appears insignificant at ~150 total.
boost::container::small_vector<Value *, 4> vArgs(args.size());
for (size_t i = 0; i < args.size(); ++i) for (size_t i = 0; i < args.size(); ++i)
vArgs[i] = args[i]->maybeThunk(state, env); vArgs[i] = args[i]->maybeThunk(state, env);
state.callFunction(vFun, args.size(), vArgs, v, pos); state.callFunction(vFun, args.size(), vArgs.data(), v, pos);
} }
@ -1990,8 +2016,8 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
return result; return result;
}; };
Value values[es->size()]; boost::container::small_vector<Value, conservativeStackReservation> values(es->size());
Value * vTmpP = values; Value * vTmpP = values.data();
for (auto & [i_pos, i] : *es) { for (auto & [i_pos, i] : *es) {
Value & vTmp = *vTmpP++; Value & vTmp = *vTmpP++;

View file

@ -18,6 +18,12 @@
namespace nix { namespace nix {
/**
* We put a limit on primop arity because it lets us use a fixed size array on
* the stack. 8 is already an impractical number of arguments. Use an attrset
* argument for such overly complicated functions.
*/
constexpr size_t maxPrimOpArity = 8;
class Store; class Store;
class EvalState; class EvalState;
@ -71,6 +77,12 @@ struct PrimOp
* Optional experimental for this to be gated on. * Optional experimental for this to be gated on.
*/ */
std::optional<ExperimentalFeature> experimentalFeature; std::optional<ExperimentalFeature> experimentalFeature;
/**
* Validity check to be performed by functions that introduce primops,
* such as RegisterPrimOp() and Value::mkPrimOp().
*/
void check();
}; };
/** /**
@ -827,7 +839,7 @@ std::string showType(const Value & v);
/** /**
* If `path` refers to a directory, then append "/default.nix". * If `path` refers to a directory, then append "/default.nix".
*/ */
SourcePath resolveExprPath(const SourcePath & path); SourcePath resolveExprPath(SourcePath path);
struct InvalidPathError : EvalError struct InvalidPathError : EvalError
{ {

View file

@ -1,6 +1,7 @@
#include "flake.hh" #include "users.hh"
#include "globals.hh" #include "globals.hh"
#include "fetch-settings.hh" #include "fetch-settings.hh"
#include "flake.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>

View file

@ -1,3 +1,4 @@
#include "terminal.hh"
#include "flake.hh" #include "flake.hh"
#include "eval.hh" #include "eval.hh"
#include "eval-settings.hh" #include "eval-settings.hh"
@ -8,6 +9,7 @@
#include "fetchers.hh" #include "fetchers.hh"
#include "finally.hh" #include "finally.hh"
#include "fetch-settings.hh" #include "fetch-settings.hh"
#include "value-to-json.hh"
namespace nix { namespace nix {
@ -140,8 +142,13 @@ static FlakeInput parseFlakeInput(EvalState & state,
attrs.emplace(state.symbols[attr.name], (long unsigned int)attr.value->integer); attrs.emplace(state.symbols[attr.name], (long unsigned int)attr.value->integer);
break; break;
default: default:
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected", if (attr.name == state.symbols.create("publicKeys")) {
state.symbols[attr.name], showType(*attr.value)); experimentalFeatureSettings.require(Xp::VerifiedFetches);
NixStringContext emptyContext = {};
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, emptyContext).dump());
} else
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
state.symbols[attr.name], showType(*attr.value));
} }
#pragma GCC diagnostic pop #pragma GCC diagnostic pop
} }
@ -447,8 +454,8 @@ LockedFlake lockFlake(
assert(input.ref); assert(input.ref);
/* Do we have an entry in the existing lock file? And we /* Do we have an entry in the existing lock file?
don't have a --update-input flag for this input? */ And the input is not in updateInputs? */
std::shared_ptr<LockedNode> oldLock; std::shared_ptr<LockedNode> oldLock;
updatesUsed.insert(inputPath); updatesUsed.insert(inputPath);
@ -472,9 +479,8 @@ LockedFlake lockFlake(
node->inputs.insert_or_assign(id, childNode); node->inputs.insert_or_assign(id, childNode);
/* If we have an --update-input flag for an input /* If we have this input in updateInputs, then we
of this input, then we must fetch the flake to must fetch the flake to update it. */
update it. */
auto lb = lockFlags.inputUpdates.lower_bound(inputPath); auto lb = lockFlags.inputUpdates.lower_bound(inputPath);
auto mustRefetch = auto mustRefetch =
@ -616,19 +622,14 @@ LockedFlake lockFlake(
for (auto & i : lockFlags.inputUpdates) for (auto & i : lockFlags.inputUpdates)
if (!updatesUsed.count(i)) if (!updatesUsed.count(i))
warn("the flag '--update-input %s' does not match any input", printInputPath(i)); warn("'%s' does not match any input of this flake", printInputPath(i));
/* Check 'follows' inputs. */ /* Check 'follows' inputs. */
newLockFile.check(); newLockFile.check();
debug("new lock file: %s", newLockFile); debug("new lock file: %s", newLockFile);
auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock";
auto sourcePath = topRef.input.getSourcePath(); auto sourcePath = topRef.input.getSourcePath();
auto outputLockFilePath = sourcePath ? std::optional{*sourcePath + "/" + relPath} : std::nullopt;
if (lockFlags.outputLockFilePath) {
outputLockFilePath = lockFlags.outputLockFilePath;
}
/* Check whether we need to / can write the new lock file. */ /* Check whether we need to / can write the new lock file. */
if (newLockFile != oldLockFile || lockFlags.outputLockFilePath) { if (newLockFile != oldLockFile || lockFlags.outputLockFilePath) {
@ -636,7 +637,7 @@ LockedFlake lockFlake(
auto diff = LockFile::diff(oldLockFile, newLockFile); auto diff = LockFile::diff(oldLockFile, newLockFile);
if (lockFlags.writeLockFile) { if (lockFlags.writeLockFile) {
if (outputLockFilePath) { if (sourcePath || lockFlags.outputLockFilePath) {
if (auto unlockedInput = newLockFile.isUnlocked()) { if (auto unlockedInput = newLockFile.isUnlocked()) {
if (fetchSettings.warnDirty) if (fetchSettings.warnDirty)
warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput); warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput);
@ -644,41 +645,48 @@ LockedFlake lockFlake(
if (!lockFlags.updateLockFile) if (!lockFlags.updateLockFile)
throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef); throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef);
bool lockFileExists = pathExists(*outputLockFilePath); auto newLockFileS = fmt("%s\n", newLockFile);
if (lockFlags.outputLockFilePath) {
if (lockFlags.commitLockFile)
throw Error("'--commit-lock-file' and '--output-lock-file' are incompatible");
writeFile(*lockFlags.outputLockFilePath, newLockFileS);
} else {
auto relPath = (topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock";
auto outputLockFilePath = *sourcePath + "/" + relPath;
bool lockFileExists = pathExists(outputLockFilePath);
if (lockFileExists) {
auto s = chomp(diff); auto s = chomp(diff);
if (s.empty()) if (lockFileExists) {
warn("updating lock file '%s'", *outputLockFilePath); if (s.empty())
else warn("updating lock file '%s'", outputLockFilePath);
warn("updating lock file '%s':\n%s", *outputLockFilePath, s); else
} else warn("updating lock file '%s':\n%s", outputLockFilePath, s);
warn("creating lock file '%s'", *outputLockFilePath); } else
warn("creating lock file '%s': \n%s", outputLockFilePath, s);
newLockFile.write(*outputLockFilePath); std::optional<std::string> commitMessage = std::nullopt;
std::optional<std::string> commitMessage = std::nullopt; if (lockFlags.commitLockFile) {
if (lockFlags.commitLockFile) { std::string cm;
if (lockFlags.outputLockFilePath) {
throw Error("--commit-lock-file and --output-lock-file are currently incompatible");
}
std::string cm;
cm = fetchSettings.commitLockFileSummary.get(); cm = fetchSettings.commitLockFileSummary.get();
if (cm == "") { if (cm == "") {
cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add"); cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add");
}
cm += "\n\nFlake lock file updates:\n\n";
cm += filterANSIEscapes(diff, true);
commitMessage = cm;
} }
cm += "\n\nFlake lock file updates:\n\n"; topRef.input.putFile(
cm += filterANSIEscapes(diff, true); CanonPath((topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock"),
commitMessage = cm; newLockFileS, commitMessage);
} }
topRef.input.markChangedFile(
(topRef.subdir == "" ? "" : topRef.subdir + "/") + "flake.lock",
commitMessage);
/* Rewriting the lockfile changed the top-level /* Rewriting the lockfile changed the top-level
repo, so we should re-read it. FIXME: we could repo, so we should re-read it. FIXME: we could
also just clear the 'rev' field... */ also just clear the 'rev' field... */

View file

@ -214,12 +214,6 @@ std::ostream & operator <<(std::ostream & stream, const LockFile & lockFile)
return stream; return stream;
} }
void LockFile::write(const Path & path) const
{
createDirs(dirOf(path));
writeFile(path, fmt("%s\n", *this));
}
std::optional<FlakeRef> LockFile::isUnlocked() const std::optional<FlakeRef> LockFile::isUnlocked() const
{ {
std::set<ref<const Node>> nodes; std::set<ref<const Node>> nodes;

View file

@ -65,8 +65,6 @@ struct LockFile
static LockFile read(const Path & path); static LockFile read(const Path & path);
void write(const Path & path) const;
/** /**
* Check whether this lock file has any unlocked inputs. * Check whether this lock file has any unlocked inputs.
*/ */

View file

@ -1,5 +1,4 @@
#include "get-drvs.hh" #include "get-drvs.hh"
#include "util.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "derivations.hh" #include "derivations.hh"
#include "store-api.hh" #include "store-api.hh"

View file

@ -43,7 +43,9 @@ $(foreach i, $(wildcard src/libexpr/value/*.hh), \
$(foreach i, $(wildcard src/libexpr/flake/*.hh), \ $(foreach i, $(wildcard src/libexpr/flake/*.hh), \
$(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644))) $(eval $(call install-file-in, $(i), $(includedir)/nix/flake, 0644)))
$(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh $(d)/primops.cc: $(d)/imported-drv-to-derivation.nix.gen.hh
$(d)/eval.cc: $(d)/primops/derivation.nix.gen.hh $(d)/fetchurl.nix.gen.hh
$(d)/flake/flake.cc: $(d)/flake/call-flake.nix.gen.hh $(d)/flake/flake.cc: $(d)/flake/call-flake.nix.gen.hh

View file

@ -19,6 +19,7 @@
#include <variant> #include <variant>
#include "util.hh" #include "util.hh"
#include "users.hh"
#include "nixexpr.hh" #include "nixexpr.hh"
#include "eval.hh" #include "eval.hh"
@ -685,17 +686,25 @@ Expr * EvalState::parse(
} }
SourcePath resolveExprPath(const SourcePath & path) SourcePath resolveExprPath(SourcePath path)
{ {
unsigned int followCount = 0, maxFollow = 1024;
/* If `path' is a symlink, follow it. This is so that relative /* If `path' is a symlink, follow it. This is so that relative
path references work. */ path references work. */
auto path2 = path.resolveSymlinks(); while (true) {
// Basic cycle/depth limit to avoid infinite loops.
if (++followCount >= maxFollow)
throw Error("too many symbolic links encountered while traversing the path '%s'", path);
if (path.lstat().type != InputAccessor::tSymlink) break;
path = {path.accessor, CanonPath(path.readLink(), path.path.parent().value_or(CanonPath::root))};
}
/* If `path' refers to a directory, append `/default.nix'. */ /* If `path' refers to a directory, append `/default.nix'. */
if (path2.lstat().type == InputAccessor::tDirectory) if (path.lstat().type == InputAccessor::tDirectory)
return path2 + "default.nix"; return path + "default.nix";
return path2; return path;
} }

View file

@ -10,6 +10,7 @@
#include "path-references.hh" #include "path-references.hh"
#include "store-api.hh" #include "store-api.hh"
#include "util.hh" #include "util.hh"
#include "processes.hh"
#include "value-to-json.hh" #include "value-to-json.hh"
#include "value-to-xml.hh" #include "value-to-xml.hh"
#include "primops.hh" #include "primops.hh"
@ -28,7 +29,6 @@
#include <cmath> #include <cmath>
namespace nix { namespace nix {
@ -824,7 +824,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
auto message = state.coerceToString(pos, *args[0], context, auto message = state.coerceToString(pos, *args[0], context,
"while evaluating the error message passed to builtins.addErrorContext", "while evaluating the error message passed to builtins.addErrorContext",
false, false).toOwned(); false, false).toOwned();
e.addTrace(nullptr, message, true); e.addTrace(nullptr, hintfmt(message), true);
throw; throw;
} }
} }
@ -1548,10 +1548,8 @@ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args,
try { try {
auto checked = state.checkSourcePath(path); auto checked = state.checkSourcePath(path);
auto exists = checked.pathExists(); auto st = checked.maybeLstat();
if (exists && mustBeDir) { auto exists = st && (!mustBeDir || st->type == SourceAccessor::tDirectory);
exists = checked.lstat().type == InputAccessor::tDirectory;
}
v.mkBool(exists); v.mkBool(exists);
} catch (SysError & e) { } catch (SysError & e) {
/* Don't give away info from errors while canonicalising /* Don't give away info from errors while canonicalising
@ -2551,6 +2549,7 @@ static void prim_removeAttrs(EvalState & state, const PosIdx pos, Value * * args
/* Get the attribute names to be removed. /* Get the attribute names to be removed.
We keep them as Attrs instead of Symbols so std::set_difference We keep them as Attrs instead of Symbols so std::set_difference
can be used to remove them from attrs[0]. */ can be used to remove them from attrs[0]. */
// 64: large enough to fit the attributes of a derivation
boost::container::small_vector<Attr, 64> names; boost::container::small_vector<Attr, 64> names;
names.reserve(args[1]->listSize()); names.reserve(args[1]->listSize());
for (auto elem : args[1]->listItems()) { for (auto elem : args[1]->listItems()) {
@ -2730,8 +2729,8 @@ static void prim_catAttrs(EvalState & state, const PosIdx pos, Value * * args, V
auto attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.catAttrs")); auto attrName = state.symbols.create(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.catAttrs"));
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.catAttrs"); state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.catAttrs");
Value * res[args[1]->listSize()]; boost::container::small_vector<Value *, nonRecursiveStackReservation> res(args[1]->listSize());
unsigned int found = 0; size_t found = 0;
for (auto v2 : args[1]->listItems()) { for (auto v2 : args[1]->listItems()) {
state.forceAttrs(*v2, pos, "while evaluating an element in the list passed as second argument to builtins.catAttrs"); state.forceAttrs(*v2, pos, "while evaluating an element in the list passed as second argument to builtins.catAttrs");
@ -3065,9 +3064,8 @@ static void prim_filter(EvalState & state, const PosIdx pos, Value * * args, Val
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filter"); state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filter");
// FIXME: putting this on the stack is risky. boost::container::small_vector<Value *, nonRecursiveStackReservation> vs(args[1]->listSize());
Value * vs[args[1]->listSize()]; size_t k = 0;
unsigned int k = 0;
bool same = true; bool same = true;
for (unsigned int n = 0; n < args[1]->listSize(); ++n) { for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
@ -3192,10 +3190,14 @@ static void anyOrAll(bool any, EvalState & state, const PosIdx pos, Value * * ar
state.forceFunction(*args[0], pos, std::string("while evaluating the first argument passed to builtins.") + (any ? "any" : "all")); state.forceFunction(*args[0], pos, std::string("while evaluating the first argument passed to builtins.") + (any ? "any" : "all"));
state.forceList(*args[1], pos, std::string("while evaluating the second argument passed to builtins.") + (any ? "any" : "all")); state.forceList(*args[1], pos, std::string("while evaluating the second argument passed to builtins.") + (any ? "any" : "all"));
std::string_view errorCtx = any
? "while evaluating the return value of the function passed to builtins.any"
: "while evaluating the return value of the function passed to builtins.all";
Value vTmp; Value vTmp;
for (auto elem : args[1]->listItems()) { for (auto elem : args[1]->listItems()) {
state.callFunction(*args[0], *elem, vTmp, pos); state.callFunction(*args[0], *elem, vTmp, pos);
bool res = state.forceBool(vTmp, pos, std::string("while evaluating the return value of the function passed to builtins.") + (any ? "any" : "all")); bool res = state.forceBool(vTmp, pos, errorCtx);
if (res == any) { if (res == any) {
v.mkBool(any); v.mkBool(any);
return; return;
@ -3451,7 +3453,7 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.concatMap"); state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.concatMap");
auto nrLists = args[1]->listSize(); auto nrLists = args[1]->listSize();
Value lists[nrLists]; boost::container::small_vector<Value, conservativeStackReservation> lists(nrLists);
size_t len = 0; size_t len = 0;
for (unsigned int n = 0; n < nrLists; ++n) { for (unsigned int n = 0; n < nrLists; ++n) {

View file

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

View file

@ -7,6 +7,7 @@
#include "registry.hh" #include "registry.hh"
#include "tarball.hh" #include "tarball.hh"
#include "url.hh" #include "url.hh"
#include "value-to-json.hh"
#include <ctime> #include <ctime>
#include <iomanip> #include <iomanip>
@ -125,6 +126,10 @@ static void fetchTree(
attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean}); attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean});
else if (attr.value->type() == nInt) else if (attr.value->type() == nInt)
attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer)); attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
else if (state.symbols[attr.name] == "publicKeys") {
experimentalFeatureSettings.require(Xp::VerifiedFetches);
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
}
else else
state.debugThrowLastTrace(TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", state.debugThrowLastTrace(TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.symbols[attr.name], showType(*attr.value))); state.symbols[attr.name], showType(*attr.value)));
@ -223,6 +228,7 @@ static RegisterPrimOp primop_fetchTree({
``` ```
)", )",
.fun = prim_fetchTree, .fun = prim_fetchTree,
.experimentalFeature = Xp::FetchTree,
}); });
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v, static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v,
@ -427,6 +433,42 @@ static RegisterPrimOp primop_fetchGit({
With this argument being true, it's possible to load a `rev` from *any* `ref` With this argument being true, it's possible to load a `rev` from *any* `ref`
(by default only `rev`s from the specified `ref` are supported). (by default only `rev`s from the specified `ref` are supported).
- `verifyCommit` (default: `true` if `publicKey` or `publicKeys` are provided, otherwise `false`)
Whether to check `rev` for a signature matching `publicKey` or `publicKeys`.
If `verifyCommit` is enabled, then `fetchGit` cannot use a local repository with uncommitted changes.
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).
- `publicKey`
The public key against which `rev` is verified if `verifyCommit` is enabled.
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).
- `keytype` (default: `"ssh-ed25519"`)
The key type of `publicKey`.
Possible values:
- `"ssh-dsa"`
- `"ssh-ecdsa"`
- `"ssh-ecdsa-sk"`
- `"ssh-ed25519"`
- `"ssh-ed25519-sk"`
- `"ssh-rsa"`
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).
- `publicKeys`
The public keys against which `rev` is verified if `verifyCommit` is enabled.
Must be given as a list of attribute sets with the following form:
```nix
{
key = "<public key>";
type = "<key type>"; # optional, default: "ssh-ed25519"
}
```
Requires the [`verified-fetches` experimental feature](@docroot@/contributing/experimental-features.md#xp-feature-verified-fetches).
Here are some examples of how to use `fetchGit`. Here are some examples of how to use `fetchGit`.
- To fetch a private repository over SSH: - To fetch a private repository over SSH:
@ -501,6 +543,21 @@ static RegisterPrimOp primop_fetchGit({
} }
``` ```
- To verify the commit signature:
```nix
builtins.fetchGit {
url = "ssh://git@github.com/nixos/nix.git";
verifyCommit = true;
publicKeys = [
{
type = "ssh-ed25519";
key = "AAAAC3NzaC1lZDI1NTE5AAAAIArPKULJOid8eS6XETwUjO48/HKBWl7FTCK0Z//fplDi";
}
];
}
```
Nix will refetch the branch according to the [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl) setting. Nix will refetch the branch according to the [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl) setting.
This behavior is disabled in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval). This behavior is disabled in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).

View file

@ -1,5 +1,4 @@
#include "search-path.hh" #include "search-path.hh"
#include "util.hh"
namespace nix { namespace nix {

View file

@ -6,7 +6,11 @@ libexpr-tests_NAME := libnixexpr-tests
libexpr-tests_DIR := $(d) libexpr-tests_DIR := $(d)
libexpr-tests_INSTALL_DIR := ifeq ($(INSTALL_UNIT_TESTS), yes)
libexpr-tests_INSTALL_DIR := $(checkbindir)
else
libexpr-tests_INSTALL_DIR :=
endif
libexpr-tests_SOURCES := \ libexpr-tests_SOURCES := \
$(wildcard $(d)/*.cc) \ $(wildcard $(d)/*.cc) \

View file

@ -114,7 +114,8 @@ TEST_F(ValuePrintingTests, vLambda)
TEST_F(ValuePrintingTests, vPrimOp) TEST_F(ValuePrintingTests, vPrimOp)
{ {
Value vPrimOp; Value vPrimOp;
vPrimOp.mkPrimOp(nullptr); PrimOp primOp{};
vPrimOp.mkPrimOp(&primOp);
test(vPrimOp, "<PRIMOP>"); test(vPrimOp, "<PRIMOP>");
} }

View file

@ -1,7 +1,7 @@
#include "value-to-json.hh" #include "value-to-json.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "util.hh"
#include "store-api.hh" #include "store-api.hh"
#include "signals.hh"
#include <cstdlib> #include <cstdlib>
#include <iomanip> #include <iomanip>

View file

@ -1,7 +1,7 @@
#include "value-to-xml.hh" #include "value-to-xml.hh"
#include "xml-writer.hh" #include "xml-writer.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "util.hh" #include "signals.hh"
#include <cstdlib> #include <cstdlib>

View file

@ -354,13 +354,7 @@ public:
// Value will be overridden anyways // Value will be overridden anyways
} }
inline void mkPrimOp(PrimOp * p) void mkPrimOp(PrimOp * p);
{
clearValue();
internalType = tPrimOp;
primOp = p;
}
inline void mkPrimOpApp(Value * l, Value * r) inline void mkPrimOpApp(Value * l, Value * r)
{ {

View file

@ -1,3 +1,4 @@
#include "util.hh"
#include "value/context.hh" #include "value/context.hh"
#include <optional> #include <optional>

View file

@ -1,7 +1,6 @@
#pragma once #pragma once
///@file ///@file
#include "util.hh"
#include "comparator.hh" #include "comparator.hh"
#include "derived-path.hh" #include "derived-path.hh"
#include "variant-wrapper.hh" #include "variant-wrapper.hh"

View file

@ -1,4 +1,5 @@
#include "cache.hh" #include "cache.hh"
#include "users.hh"
#include "sqlite.hh" #include "sqlite.hh"
#include "sync.hh" #include "sync.hh"
#include "store-api.hh" #include "store-api.hh"

View file

@ -2,6 +2,7 @@
///@file ///@file
#include "fetchers.hh" #include "fetchers.hh"
#include "path.hh"
namespace nix::fetchers { namespace nix::fetchers {

View file

@ -3,7 +3,6 @@
#include "types.hh" #include "types.hh"
#include "config.hh" #include "config.hh"
#include "util.hh"
#include <map> #include <map>
#include <limits> #include <limits>

View file

@ -5,12 +5,31 @@
namespace nix::fetchers { namespace nix::fetchers {
std::unique_ptr<std::vector<std::shared_ptr<InputScheme>>> inputSchemes = nullptr; using InputSchemeMap = std::map<std::string_view, std::shared_ptr<InputScheme>>;
std::unique_ptr<InputSchemeMap> inputSchemes = nullptr;
void registerInputScheme(std::shared_ptr<InputScheme> && inputScheme) void registerInputScheme(std::shared_ptr<InputScheme> && inputScheme)
{ {
if (!inputSchemes) inputSchemes = std::make_unique<std::vector<std::shared_ptr<InputScheme>>>(); if (!inputSchemes)
inputSchemes->push_back(std::move(inputScheme)); inputSchemes = std::make_unique<InputSchemeMap>();
auto schemeName = inputScheme->schemeName();
if (inputSchemes->count(schemeName) > 0)
throw Error("Input scheme with name %s already registered", schemeName);
inputSchemes->insert_or_assign(schemeName, std::move(inputScheme));
}
nlohmann::json dumpRegisterInputSchemeInfo() {
using nlohmann::json;
auto res = json::object();
for (auto & [name, scheme] : *inputSchemes) {
auto & r = res[name] = json::object();
r["allowedAttrs"] = scheme->allowedAttrs();
}
return res;
} }
Input Input::fromURL(const std::string & url, bool requireTree) Input Input::fromURL(const std::string & url, bool requireTree)
@ -33,7 +52,7 @@ static void fixupInput(Input & input)
Input Input::fromURL(const ParsedURL & url, bool requireTree) Input Input::fromURL(const ParsedURL & url, bool requireTree)
{ {
for (auto & inputScheme : *inputSchemes) { for (auto & [_, inputScheme] : *inputSchemes) {
auto res = inputScheme->inputFromURL(url, requireTree); auto res = inputScheme->inputFromURL(url, requireTree);
if (res) { if (res) {
experimentalFeatureSettings.require(inputScheme->experimentalFeature()); experimentalFeatureSettings.require(inputScheme->experimentalFeature());
@ -48,20 +67,44 @@ Input Input::fromURL(const ParsedURL & url, bool requireTree)
Input Input::fromAttrs(Attrs && attrs) Input Input::fromAttrs(Attrs && attrs)
{ {
for (auto & inputScheme : *inputSchemes) { auto schemeName = ({
auto res = inputScheme->inputFromAttrs(attrs); auto schemeNameOpt = maybeGetStrAttr(attrs, "type");
if (res) { if (!schemeNameOpt)
experimentalFeatureSettings.require(inputScheme->experimentalFeature()); throw Error("'type' attribute to specify input scheme is required but not provided");
res->scheme = inputScheme; *std::move(schemeNameOpt);
fixupInput(*res); });
return std::move(*res);
}
}
Input input; auto raw = [&]() {
input.attrs = attrs; // Return an input without a scheme; most operations will fail,
fixupInput(input); // but not all of them. Doing this is to support those other
return input; // operations which are supposed to be robust on
// unknown/uninterpretable inputs.
Input input;
input.attrs = attrs;
fixupInput(input);
return input;
};
std::shared_ptr<InputScheme> inputScheme = ({
auto i = inputSchemes->find(schemeName);
i == inputSchemes->end() ? nullptr : i->second;
});
if (!inputScheme) return raw();
experimentalFeatureSettings.require(inputScheme->experimentalFeature());
auto allowedAttrs = inputScheme->allowedAttrs();
for (auto & [name, _] : attrs)
if (name != "type" && allowedAttrs.count(name) == 0)
throw Error("input attribute '%s' not supported by scheme '%s'", name, schemeName);
auto res = inputScheme->inputFromAttrs(attrs);
if (!res) return raw();
res->scheme = inputScheme;
fixupInput(*res);
return std::move(*res);
} }
ParsedURL Input::toURL() const ParsedURL Input::toURL() const
@ -196,12 +239,13 @@ std::optional<Path> Input::getSourcePath() const
return scheme->getSourcePath(*this); return scheme->getSourcePath(*this);
} }
void Input::markChangedFile( void Input::putFile(
std::string_view file, const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const std::optional<std::string> commitMsg) const
{ {
assert(scheme); assert(scheme);
return scheme->markChangedFile(*this, file, commitMsg); return scheme->putFile(*this, path, contents, commitMsg);
} }
std::string Input::getName() const std::string Input::getName() const
@ -292,14 +336,18 @@ Input InputScheme::applyOverrides(
return input; return input;
} }
std::optional<Path> InputScheme::getSourcePath(const Input & input) std::optional<Path> InputScheme::getSourcePath(const Input & input) const
{ {
return {}; return {};
} }
void InputScheme::markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg) void InputScheme::putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const
{ {
assert(false); throw Error("input '%s' does not support modifying file '%s'", input.to_string(), path);
} }
void InputScheme::clone(const Input & input, const Path & destDir) const void InputScheme::clone(const Input & input, const Path & destDir) const
@ -307,9 +355,14 @@ void InputScheme::clone(const Input & input, const Path & destDir) const
throw Error("do not know how to clone input '%s'", input.to_string()); throw Error("do not know how to clone input '%s'", input.to_string());
} }
std::optional<ExperimentalFeature> InputScheme::experimentalFeature() std::optional<ExperimentalFeature> InputScheme::experimentalFeature() const
{ {
return {}; return {};
} }
std::string publicKeys_to_string(const std::vector<PublicKey>& publicKeys)
{
return ((nlohmann::json) publicKeys).dump();
}
} }

View file

@ -3,13 +3,14 @@
#include "types.hh" #include "types.hh"
#include "hash.hh" #include "hash.hh"
#include "path.hh" #include "canon-path.hh"
#include "attrs.hh" #include "attrs.hh"
#include "url.hh" #include "url.hh"
#include <memory> #include <memory>
#include <nlohmann/json_fwd.hpp>
namespace nix { class Store; } namespace nix { class Store; class StorePath; }
namespace nix::fetchers { namespace nix::fetchers {
@ -90,8 +91,13 @@ public:
std::optional<Path> getSourcePath() const; std::optional<Path> getSourcePath() const;
void markChangedFile( /**
std::string_view file, * Write a file to this input, for input types that support
* writing. Optionally commit the change (for e.g. Git inputs).
*/
void putFile(
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const; std::optional<std::string> commitMsg) const;
std::string getName() const; std::string getName() const;
@ -126,6 +132,24 @@ struct InputScheme
virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0; virtual std::optional<Input> inputFromAttrs(const Attrs & attrs) const = 0;
/**
* What is the name of the scheme?
*
* The `type` attribute is used to select which input scheme is
* used, and then the other fields are forwarded to that input
* scheme.
*/
virtual std::string_view schemeName() const = 0;
/**
* Allowed attributes in an attribute set that is converted to an
* input.
*
* `type` is not included from this set, because the `type` field is
parsed first to choose which scheme; `type` is always required.
*/
virtual StringSet allowedAttrs() const = 0;
virtual ParsedURL toURL(const Input & input) const; virtual ParsedURL toURL(const Input & input) const;
virtual Input applyOverrides( virtual Input applyOverrides(
@ -135,16 +159,20 @@ struct InputScheme
virtual void clone(const Input & input, const Path & destDir) const; virtual void clone(const Input & input, const Path & destDir) const;
virtual std::optional<Path> getSourcePath(const Input & input); virtual std::optional<Path> getSourcePath(const Input & input) const;
virtual void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg); virtual void putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const;
virtual std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) = 0; virtual std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) = 0;
/** /**
* Is this `InputScheme` part of an experimental feature? * Is this `InputScheme` part of an experimental feature?
*/ */
virtual std::optional<ExperimentalFeature> experimentalFeature(); virtual std::optional<ExperimentalFeature> experimentalFeature() const;
virtual bool isDirect(const Input & input) const virtual bool isDirect(const Input & input) const
{ return true; } { return true; }
@ -152,4 +180,15 @@ struct InputScheme
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher); void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);
nlohmann::json dumpRegisterInputSchemeInfo();
struct PublicKey
{
std::string type = "ssh-ed25519";
std::string key;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE_WITH_DEFAULT(PublicKey, type, key)
std::string publicKeys_to_string(const std::vector<PublicKey>&);
} }

View file

@ -36,11 +36,11 @@ struct FSInputAccessorImpl : FSInputAccessor, PosixSourceAccessor
return isAllowed(absPath) && PosixSourceAccessor::pathExists(absPath); return isAllowed(absPath) && PosixSourceAccessor::pathExists(absPath);
} }
Stat lstat(const CanonPath & path) override std::optional<Stat> maybeLstat(const CanonPath & path) override
{ {
auto absPath = makeAbsPath(path); auto absPath = makeAbsPath(path);
checkAllowed(absPath); checkAllowed(absPath);
return PosixSourceAccessor::lstat(absPath); return PosixSourceAccessor::maybeLstat(absPath);
} }
DirEntries readDirectory(const CanonPath & path) override DirEntries readDirectory(const CanonPath & path) override

View file

@ -1,11 +1,12 @@
#include "fetchers.hh" #include "fetchers.hh"
#include "users.hh"
#include "cache.hh" #include "cache.hh"
#include "globals.hh" #include "globals.hh"
#include "tarfile.hh" #include "tarfile.hh"
#include "store-api.hh" #include "store-api.hh"
#include "url-parts.hh" #include "url-parts.hh"
#include "pathlocks.hh" #include "pathlocks.hh"
#include "util.hh" #include "processes.hh"
#include "git.hh" #include "git.hh"
#include "fetch-settings.hh" #include "fetch-settings.hh"
@ -143,6 +144,69 @@ struct WorkdirInfo
bool hasHead = false; bool hasHead = false;
}; };
std::vector<PublicKey> getPublicKeys(const Attrs & attrs) {
std::vector<PublicKey> publicKeys;
if (attrs.contains("publicKeys")) {
nlohmann::json publicKeysJson = nlohmann::json::parse(getStrAttr(attrs, "publicKeys"));
ensureType(publicKeysJson, nlohmann::json::value_t::array);
publicKeys = publicKeysJson.get<std::vector<PublicKey>>();
}
else {
publicKeys = {};
}
if (attrs.contains("publicKey"))
publicKeys.push_back(PublicKey{maybeGetStrAttr(attrs, "keytype").value_or("ssh-ed25519"),getStrAttr(attrs, "publicKey")});
return publicKeys;
}
void doCommitVerification(const Path repoDir, const Path gitDir, const std::string rev, const std::vector<PublicKey>& publicKeys) {
// Create ad-hoc allowedSignersFile and populate it with publicKeys
auto allowedSignersFile = createTempFile().second;
std::string allowedSigners;
for (const PublicKey& k : publicKeys) {
if (k.type != "ssh-dsa"
&& k.type != "ssh-ecdsa"
&& k.type != "ssh-ecdsa-sk"
&& k.type != "ssh-ed25519"
&& k.type != "ssh-ed25519-sk"
&& k.type != "ssh-rsa")
warn("Unknown keytype: %s\n"
"Please use one of\n"
"- ssh-dsa\n"
" ssh-ecdsa\n"
" ssh-ecdsa-sk\n"
" ssh-ed25519\n"
" ssh-ed25519-sk\n"
" ssh-rsa", k.type);
allowedSigners += "* " + k.type + " " + k.key + "\n";
}
writeFile(allowedSignersFile, allowedSigners);
// Run verification command
auto [status, output] = runProgram(RunOptions {
.program = "git",
.args = {"-c", "gpg.ssh.allowedSignersFile=" + allowedSignersFile, "-C", repoDir,
"--git-dir", gitDir, "verify-commit", rev},
.mergeStderrToStdout = true,
});
/* Evaluate result through status code and checking if public key fingerprints appear on stderr
* This is neccessary because the git command might also succeed due to the commit being signed by gpg keys
* that are present in the users key agent. */
std::string re = R"(Good "git" signature for \* with .* key SHA256:[)";
for (const PublicKey& k : publicKeys){
// Calculate sha256 fingerprint from public key and escape the regex symbol '+' to match the key literally
auto fingerprint = trim(hashString(htSHA256, base64Decode(k.key)).to_string(nix::HashFormat::Base64, false), "=");
auto escaped_fingerprint = std::regex_replace(fingerprint, std::regex("\\+"), "\\+" );
re += "(" + escaped_fingerprint + ")";
}
re += "]";
if (status == 0 && std::regex_search(output, std::regex(re)))
printTalkative("Signature verification on commit %s succeeded", rev);
else
throw Error("Commit signature verification on commit %s failed: \n%s", rev, output);
}
// Returns whether a git workdir is clean and has commits. // Returns whether a git workdir is clean and has commits.
WorkdirInfo getWorkdirInfo(const Input & input, const Path & workdir) WorkdirInfo getWorkdirInfo(const Input & input, const Path & workdir)
{ {
@ -272,9 +336,9 @@ struct GitInputScheme : InputScheme
attrs.emplace("type", "git"); attrs.emplace("type", "git");
for (auto & [name, value] : url.query) { for (auto & [name, value] : url.query) {
if (name == "rev" || name == "ref") if (name == "rev" || name == "ref" || name == "keytype" || name == "publicKey" || name == "publicKeys")
attrs.emplace(name, value); attrs.emplace(name, value);
else if (name == "shallow" || name == "submodules" || name == "allRefs") else if (name == "shallow" || name == "submodules" || name == "allRefs" || name == "verifyCommit")
attrs.emplace(name, Explicit<bool> { value == "1" }); attrs.emplace(name, Explicit<bool> { value == "1" });
else else
url2.query.emplace(name, value); url2.query.emplace(name, value);
@ -285,17 +349,47 @@ struct GitInputScheme : InputScheme
return inputFromAttrs(attrs); return inputFromAttrs(attrs);
} }
std::string_view schemeName() const override
{
return "git";
}
StringSet allowedAttrs() const override
{
return {
"url",
"ref",
"rev",
"shallow",
"submodules",
"lastModified",
"revCount",
"narHash",
"allRefs",
"name",
"dirtyRev",
"dirtyShortRev",
"verifyCommit",
"keytype",
"publicKey",
"publicKeys",
};
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != "git") return {}; for (auto & [name, _] : attrs)
if (name == "verifyCommit"
for (auto & [name, value] : attrs) || name == "keytype"
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs" && name != "name" && name != "dirtyRev" && name != "dirtyShortRev") || name == "publicKey"
throw Error("unsupported Git input attribute '%s'", name); || name == "publicKeys")
experimentalFeatureSettings.require(Xp::VerifiedFetches);
maybeGetBoolAttr(attrs, "shallow"); maybeGetBoolAttr(attrs, "shallow");
maybeGetBoolAttr(attrs, "submodules"); maybeGetBoolAttr(attrs, "submodules");
maybeGetBoolAttr(attrs, "allRefs"); maybeGetBoolAttr(attrs, "allRefs");
maybeGetBoolAttr(attrs, "verifyCommit");
if (auto ref = maybeGetStrAttr(attrs, "ref")) { if (auto ref = maybeGetStrAttr(attrs, "ref")) {
if (std::regex_search(*ref, badGitRefRegex)) if (std::regex_search(*ref, badGitRefRegex))
@ -318,6 +412,15 @@ struct GitInputScheme : InputScheme
if (auto ref = input.getRef()) url.query.insert_or_assign("ref", *ref); if (auto ref = input.getRef()) url.query.insert_or_assign("ref", *ref);
if (maybeGetBoolAttr(input.attrs, "shallow").value_or(false)) if (maybeGetBoolAttr(input.attrs, "shallow").value_or(false))
url.query.insert_or_assign("shallow", "1"); url.query.insert_or_assign("shallow", "1");
if (maybeGetBoolAttr(input.attrs, "verifyCommit").value_or(false))
url.query.insert_or_assign("verifyCommit", "1");
auto publicKeys = getPublicKeys(input.attrs);
if (publicKeys.size() == 1) {
url.query.insert_or_assign("keytype", publicKeys.at(0).type);
url.query.insert_or_assign("publicKey", publicKeys.at(0).key);
}
else if (publicKeys.size() > 1)
url.query.insert_or_assign("publicKeys", publicKeys_to_string(publicKeys));
return url; return url;
} }
@ -354,7 +457,7 @@ struct GitInputScheme : InputScheme
runProgram("git", true, args, {}, true); runProgram("git", true, args, {}, true);
} }
std::optional<Path> getSourcePath(const Input & input) override std::optional<Path> getSourcePath(const Input & input) const override
{ {
auto url = parseURL(getStrAttr(input.attrs, "url")); auto url = parseURL(getStrAttr(input.attrs, "url"));
if (url.scheme == "file" && !input.getRef() && !input.getRev()) if (url.scheme == "file" && !input.getRef() && !input.getRev())
@ -362,18 +465,26 @@ struct GitInputScheme : InputScheme
return {}; return {};
} }
void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg) override void putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const override
{ {
auto sourcePath = getSourcePath(input); auto root = getSourcePath(input);
assert(sourcePath); if (!root)
throw Error("cannot commit '%s' to Git repository '%s' because it's not a working tree", path, input.to_string());
writeFile((CanonPath(*root) + path).abs(), contents);
auto gitDir = ".git"; auto gitDir = ".git";
runProgram("git", true, runProgram("git", true,
{ "-C", *sourcePath, "--git-dir", gitDir, "add", "--intent-to-add", "--", std::string(file) }); { "-C", *root, "--git-dir", gitDir, "add", "--intent-to-add", "--", std::string(path.rel()) });
if (commitMsg) if (commitMsg)
runProgram("git", true, runProgram("git", true,
{ "-C", *sourcePath, "--git-dir", gitDir, "commit", std::string(file), "-m", *commitMsg }); { "-C", *root, "--git-dir", gitDir, "commit", std::string(path.rel()), "-m", *commitMsg });
} }
std::pair<bool, std::string> getActualUrl(const Input & input) const std::pair<bool, std::string> getActualUrl(const Input & input) const
@ -399,6 +510,8 @@ struct GitInputScheme : InputScheme
bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false); bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false);
bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false); bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false);
bool allRefs = maybeGetBoolAttr(input.attrs, "allRefs").value_or(false); bool allRefs = maybeGetBoolAttr(input.attrs, "allRefs").value_or(false);
std::vector<PublicKey> publicKeys = getPublicKeys(input.attrs);
bool verifyCommit = maybeGetBoolAttr(input.attrs, "verifyCommit").value_or(!publicKeys.empty());
std::string cacheType = "git"; std::string cacheType = "git";
if (shallow) cacheType += "-shallow"; if (shallow) cacheType += "-shallow";
@ -419,6 +532,8 @@ struct GitInputScheme : InputScheme
{"type", cacheType}, {"type", cacheType},
{"name", name}, {"name", name},
{"rev", input.getRev()->gitRev()}, {"rev", input.getRev()->gitRev()},
{"verifyCommit", verifyCommit},
{"publicKeys", publicKeys_to_string(publicKeys)},
}); });
}; };
@ -441,12 +556,15 @@ struct GitInputScheme : InputScheme
auto [isLocal, actualUrl_] = getActualUrl(input); auto [isLocal, actualUrl_] = getActualUrl(input);
auto actualUrl = actualUrl_; // work around clang bug auto actualUrl = actualUrl_; // work around clang bug
/* If this is a local directory and no ref or revision is given, /* If this is a local directory, no ref or revision is given and no signature verification is needed,
allow fetching directly from a dirty workdir. */ allow fetching directly from a dirty workdir. */
if (!input.getRef() && !input.getRev() && isLocal) { if (!input.getRef() && !input.getRev() && isLocal) {
auto workdirInfo = getWorkdirInfo(input, actualUrl); auto workdirInfo = getWorkdirInfo(input, actualUrl);
if (!workdirInfo.clean) { if (!workdirInfo.clean) {
return fetchFromWorkdir(store, input, actualUrl, workdirInfo); if (verifyCommit)
throw Error("Can't fetch from a dirty workdir with commit signature verification enabled.");
else
return fetchFromWorkdir(store, input, actualUrl, workdirInfo);
} }
} }
@ -454,6 +572,8 @@ struct GitInputScheme : InputScheme
{"type", cacheType}, {"type", cacheType},
{"name", name}, {"name", name},
{"url", actualUrl}, {"url", actualUrl},
{"verifyCommit", verifyCommit},
{"publicKeys", publicKeys_to_string(publicKeys)},
}); });
Path repoDir; Path repoDir;
@ -611,6 +731,9 @@ struct GitInputScheme : InputScheme
); );
} }
if (verifyCommit)
doCommitVerification(repoDir, gitDir, input.getRev()->gitRev(), publicKeys);
if (submodules) { if (submodules) {
Path tmpGitDir = createTempDir(); Path tmpGitDir = createTempDir();
AutoDelete delTmpGitDir(tmpGitDir, true); AutoDelete delTmpGitDir(tmpGitDir, true);

View file

@ -27,13 +27,11 @@ std::regex hostRegex(hostRegexS, std::regex::ECMAScript);
struct GitArchiveInputScheme : InputScheme struct GitArchiveInputScheme : InputScheme
{ {
virtual std::string type() const = 0;
virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0; virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0;
std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override std::optional<Input> inputFromURL(const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != type()) return {}; if (url.scheme != schemeName()) return {};
auto path = tokenizeString<std::vector<std::string>>(url.path, "/"); auto path = tokenizeString<std::vector<std::string>>(url.path, "/");
@ -91,7 +89,7 @@ struct GitArchiveInputScheme : InputScheme
throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url.url, *ref, rev->gitRev()); throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url.url, *ref, rev->gitRev());
Input input; Input input;
input.attrs.insert_or_assign("type", type()); input.attrs.insert_or_assign("type", std::string { schemeName() });
input.attrs.insert_or_assign("owner", path[0]); input.attrs.insert_or_assign("owner", path[0]);
input.attrs.insert_or_assign("repo", path[1]); input.attrs.insert_or_assign("repo", path[1]);
if (rev) input.attrs.insert_or_assign("rev", rev->gitRev()); if (rev) input.attrs.insert_or_assign("rev", rev->gitRev());
@ -101,14 +99,21 @@ struct GitArchiveInputScheme : InputScheme
return input; return input;
} }
StringSet allowedAttrs() const override
{
return {
"owner",
"repo",
"ref",
"rev",
"narHash",
"lastModified",
"host",
};
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != type()) return {};
for (auto & [name, value] : attrs)
if (name != "type" && name != "owner" && name != "repo" && name != "ref" && name != "rev" && name != "narHash" && name != "lastModified" && name != "host")
throw Error("unsupported input attribute '%s'", name);
getStrAttr(attrs, "owner"); getStrAttr(attrs, "owner");
getStrAttr(attrs, "repo"); getStrAttr(attrs, "repo");
@ -128,7 +133,7 @@ struct GitArchiveInputScheme : InputScheme
if (ref) path += "/" + *ref; if (ref) path += "/" + *ref;
if (rev) path += "/" + rev->to_string(HashFormat::Base16, false); if (rev) path += "/" + rev->to_string(HashFormat::Base16, false);
return ParsedURL { return ParsedURL {
.scheme = type(), .scheme = std::string { schemeName() },
.path = path, .path = path,
}; };
} }
@ -220,7 +225,7 @@ struct GitArchiveInputScheme : InputScheme
return {result.storePath, input}; return {result.storePath, input};
} }
std::optional<ExperimentalFeature> experimentalFeature() override std::optional<ExperimentalFeature> experimentalFeature() const override
{ {
return Xp::Flakes; return Xp::Flakes;
} }
@ -228,7 +233,7 @@ struct GitArchiveInputScheme : InputScheme
struct GitHubInputScheme : GitArchiveInputScheme struct GitHubInputScheme : GitArchiveInputScheme
{ {
std::string type() const override { return "github"; } std::string_view schemeName() const override { return "github"; }
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{ {
@ -309,7 +314,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
struct GitLabInputScheme : GitArchiveInputScheme struct GitLabInputScheme : GitArchiveInputScheme
{ {
std::string type() const override { return "gitlab"; } std::string_view schemeName() const override { return "gitlab"; }
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{ {
@ -377,7 +382,7 @@ struct GitLabInputScheme : GitArchiveInputScheme
struct SourceHutInputScheme : GitArchiveInputScheme struct SourceHutInputScheme : GitArchiveInputScheme
{ {
std::string type() const override { return "sourcehut"; } std::string_view schemeName() const override { return "sourcehut"; }
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{ {

View file

@ -1,5 +1,6 @@
#include "fetchers.hh" #include "fetchers.hh"
#include "url-parts.hh" #include "url-parts.hh"
#include "path.hh"
namespace nix::fetchers { namespace nix::fetchers {
@ -49,14 +50,23 @@ struct IndirectInputScheme : InputScheme
return input; return input;
} }
std::string_view schemeName() const override
{
return "indirect";
}
StringSet allowedAttrs() const override
{
return {
"id",
"ref",
"rev",
"narHash",
};
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != "indirect") return {};
for (auto & [name, value] : attrs)
if (name != "type" && name != "id" && name != "ref" && name != "rev" && name != "narHash")
throw Error("unsupported indirect input attribute '%s'", name);
auto id = getStrAttr(attrs, "id"); auto id = getStrAttr(attrs, "id");
if (!std::regex_match(id, flakeRegex)) if (!std::regex_match(id, flakeRegex))
throw BadURL("'%s' is not a valid flake ID", id); throw BadURL("'%s' is not a valid flake ID", id);
@ -92,7 +102,7 @@ struct IndirectInputScheme : InputScheme
throw Error("indirect input '%s' cannot be fetched directly", input.to_string()); throw Error("indirect input '%s' cannot be fetched directly", input.to_string());
} }
std::optional<ExperimentalFeature> experimentalFeature() override std::optional<ExperimentalFeature> experimentalFeature() const override
{ {
return Xp::Flakes; return Xp::Flakes;
} }

View file

@ -1,8 +1,10 @@
#pragma once #pragma once
///@file
#include "source-accessor.hh" #include "source-accessor.hh"
#include "ref.hh" #include "ref.hh"
#include "types.hh" #include "types.hh"
#include "file-system.hh"
#include "repair-flag.hh" #include "repair-flag.hh"
#include "content-address.hh" #include "content-address.hh"
@ -14,7 +16,7 @@ struct SourcePath;
class StorePath; class StorePath;
class Store; class Store;
struct InputAccessor : SourceAccessor, std::enable_shared_from_this<InputAccessor> struct InputAccessor : virtual SourceAccessor, std::enable_shared_from_this<InputAccessor>
{ {
/** /**
* Return the maximum last-modified time of the files in this * Return the maximum last-modified time of the files in this

View file

@ -1,48 +1,16 @@
#include "memory-input-accessor.hh" #include "memory-input-accessor.hh"
#include "memory-source-accessor.hh"
namespace nix { namespace nix {
struct MemoryInputAccessorImpl : MemoryInputAccessor struct MemoryInputAccessorImpl : MemoryInputAccessor, MemorySourceAccessor
{ {
std::map<CanonPath, std::string> files;
std::string readFile(const CanonPath & path) override
{
auto i = files.find(path);
if (i == files.end())
throw Error("file '%s' does not exist", path);
return i->second;
}
bool pathExists(const CanonPath & path) override
{
auto i = files.find(path);
return i != files.end();
}
Stat lstat(const CanonPath & path) override
{
auto i = files.find(path);
if (i != files.end())
return Stat { .type = tRegular, .isExecutable = false };
throw Error("file '%s' does not exist", path);
}
DirEntries readDirectory(const CanonPath & path) override
{
return {};
}
std::string readLink(const CanonPath & path) override
{
throw UnimplementedError("MemoryInputAccessor::readLink");
}
SourcePath addFile(CanonPath path, std::string && contents) override SourcePath addFile(CanonPath path, std::string && contents) override
{ {
files.emplace(path, std::move(contents)); return {
ref(shared_from_this()),
return {ref(shared_from_this()), std::move(path)}; MemorySourceAccessor::addFile(path, std::move(contents))
};
} }
}; };

View file

@ -1,4 +1,6 @@
#include "fetchers.hh" #include "fetchers.hh"
#include "processes.hh"
#include "users.hh"
#include "cache.hh" #include "cache.hh"
#include "globals.hh" #include "globals.hh"
#include "tarfile.hh" #include "tarfile.hh"
@ -69,14 +71,25 @@ struct MercurialInputScheme : InputScheme
return inputFromAttrs(attrs); return inputFromAttrs(attrs);
} }
std::string_view schemeName() const override
{
return "hg";
}
StringSet allowedAttrs() const override
{
return {
"url",
"ref",
"rev",
"revCount",
"narHash",
"name",
};
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != "hg") return {};
for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "revCount" && name != "narHash" && name != "name")
throw Error("unsupported Mercurial input attribute '%s'", name);
parseURL(getStrAttr(attrs, "url")); parseURL(getStrAttr(attrs, "url"));
if (auto ref = maybeGetStrAttr(attrs, "ref")) { if (auto ref = maybeGetStrAttr(attrs, "ref")) {
@ -109,7 +122,7 @@ struct MercurialInputScheme : InputScheme
return res; return res;
} }
std::optional<Path> getSourcePath(const Input & input) override std::optional<Path> getSourcePath(const Input & input) const override
{ {
auto url = parseURL(getStrAttr(input.attrs, "url")); auto url = parseURL(getStrAttr(input.attrs, "url"));
if (url.scheme == "file" && !input.getRef() && !input.getRev()) if (url.scheme == "file" && !input.getRef() && !input.getRev())
@ -117,18 +130,27 @@ struct MercurialInputScheme : InputScheme
return {}; return {};
} }
void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg) override void putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const override
{ {
auto sourcePath = getSourcePath(input); auto [isLocal, repoPath] = getActualUrl(input);
assert(sourcePath); if (!isLocal)
throw Error("cannot commit '%s' to Mercurial repository '%s' because it's not a working tree", path, input.to_string());
auto absPath = CanonPath(repoPath) + path;
writeFile(absPath.abs(), contents);
// FIXME: shut up if file is already tracked. // FIXME: shut up if file is already tracked.
runHg( runHg(
{ "add", *sourcePath + "/" + std::string(file) }); { "add", absPath.abs() });
if (commitMsg) if (commitMsg)
runHg( runHg(
{ "commit", *sourcePath + "/" + std::string(file), "-m", *commitMsg }); { "commit", absPath.abs(), "-m", *commitMsg });
} }
std::pair<bool, std::string> getActualUrl(const Input & input) const std::pair<bool, std::string> getActualUrl(const Input & input) const

View file

@ -32,23 +32,30 @@ struct PathInputScheme : InputScheme
return input; return input;
} }
std::string_view schemeName() const override
{
return "path";
}
StringSet allowedAttrs() const override
{
return {
"path",
/* Allow the user to pass in "fake" tree info
attributes. This is useful for making a pinned tree work
the same as the repository from which is exported (e.g.
path:/nix/store/...-source?lastModified=1585388205&rev=b0c285...).
*/
"rev",
"revCount",
"lastModified",
"narHash",
};
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
if (maybeGetStrAttr(attrs, "type") != "path") return {};
getStrAttr(attrs, "path"); getStrAttr(attrs, "path");
for (auto & [name, value] : attrs)
/* Allow the user to pass in "fake" tree info
attributes. This is useful for making a pinned tree
work the same as the repository from which is exported
(e.g. path:/nix/store/...-source?lastModified=1585388205&rev=b0c285...). */
if (name == "type" || name == "rev" || name == "revCount" || name == "lastModified" || name == "narHash" || name == "path")
// checked in Input::fromAttrs
;
else
throw Error("unsupported path input attribute '%s'", name);
Input input; Input input;
input.attrs = attrs; input.attrs = attrs;
return input; return input;
@ -66,14 +73,28 @@ struct PathInputScheme : InputScheme
}; };
} }
std::optional<Path> getSourcePath(const Input & input) override std::optional<Path> getSourcePath(const Input & input) const override
{ {
return getStrAttr(input.attrs, "path"); return getStrAttr(input.attrs, "path");
} }
void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg) override void putFile(
const Input & input,
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const override
{ {
// nothing to do writeFile((CanonPath(getAbsPath(input)) + path).abs(), contents);
}
CanonPath getAbsPath(const Input & input) const
{
auto path = getStrAttr(input.attrs, "path");
if (path[0] == '/')
return CanonPath(path);
throw Error("cannot fetch input '%s' because it uses a relative path", input.to_string());
} }
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
@ -121,7 +142,7 @@ struct PathInputScheme : InputScheme
return {std::move(*storePath), input}; return {std::move(*storePath), input};
} }
std::optional<ExperimentalFeature> experimentalFeature() override std::optional<ExperimentalFeature> experimentalFeature() const override
{ {
return Xp::Flakes; return Xp::Flakes;
} }

View file

@ -1,6 +1,6 @@
#include "registry.hh" #include "registry.hh"
#include "tarball.hh" #include "tarball.hh"
#include "util.hh" #include "users.hh"
#include "globals.hh" #include "globals.hh"
#include "store-api.hh" #include "store-api.hh"
#include "local-fs-store.hh" #include "local-fs-store.hh"

View file

@ -184,7 +184,6 @@ DownloadTarballResult downloadTarball(
// An input scheme corresponding to a curl-downloadable resource. // An input scheme corresponding to a curl-downloadable resource.
struct CurlInputScheme : InputScheme struct CurlInputScheme : InputScheme
{ {
virtual const std::string inputType() const = 0;
const std::set<std::string> transportUrlSchemes = {"file", "http", "https"}; const std::set<std::string> transportUrlSchemes = {"file", "http", "https"};
const bool hasTarballExtension(std::string_view path) const const bool hasTarballExtension(std::string_view path) const
@ -222,22 +221,27 @@ struct CurlInputScheme : InputScheme
url.query.erase("rev"); url.query.erase("rev");
url.query.erase("revCount"); url.query.erase("revCount");
input.attrs.insert_or_assign("type", inputType()); input.attrs.insert_or_assign("type", std::string { schemeName() });
input.attrs.insert_or_assign("url", url.to_string()); input.attrs.insert_or_assign("url", url.to_string());
return input; return input;
} }
StringSet allowedAttrs() const override
{
return {
"type",
"url",
"narHash",
"name",
"unpack",
"rev",
"revCount",
"lastModified",
};
}
std::optional<Input> inputFromAttrs(const Attrs & attrs) const override std::optional<Input> inputFromAttrs(const Attrs & attrs) const override
{ {
auto type = maybeGetStrAttr(attrs, "type");
if (type != inputType()) return {};
// FIXME: some of these only apply to TarballInputScheme.
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack", "rev", "revCount", "lastModified"};
for (auto & [name, value] : attrs)
if (!allowedNames.count(name))
throw Error("unsupported %s input attribute '%s'", *type, name);
Input input; Input input;
input.attrs = attrs; input.attrs = attrs;
@ -258,14 +262,14 @@ struct CurlInputScheme : InputScheme
struct FileInputScheme : CurlInputScheme struct FileInputScheme : CurlInputScheme
{ {
const std::string inputType() const override { return "file"; } std::string_view schemeName() const override { return "file"; }
bool isValidURL(const ParsedURL & url, bool requireTree) const override bool isValidURL(const ParsedURL & url, bool requireTree) const override
{ {
auto parsedUrlScheme = parseUrlScheme(url.scheme); auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport)) return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application && (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType() ? parsedUrlScheme.application.value() == schemeName()
: (!requireTree && !hasTarballExtension(url.path))); : (!requireTree && !hasTarballExtension(url.path)));
} }
@ -278,7 +282,7 @@ struct FileInputScheme : CurlInputScheme
struct TarballInputScheme : CurlInputScheme struct TarballInputScheme : CurlInputScheme
{ {
const std::string inputType() const override { return "tarball"; } std::string_view schemeName() const override { return "tarball"; }
bool isValidURL(const ParsedURL & url, bool requireTree) const override bool isValidURL(const ParsedURL & url, bool requireTree) const override
{ {
@ -286,7 +290,7 @@ struct TarballInputScheme : CurlInputScheme
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport)) return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application && (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType() ? parsedUrlScheme.application.value() == schemeName()
: (requireTree || hasTarballExtension(url.path))); : (requireTree || hasTarballExtension(url.path)));
} }

View file

@ -1,7 +1,9 @@
#include "common-args.hh" #include "common-args.hh"
#include "args/root.hh" #include "args/root.hh"
#include "globals.hh" #include "globals.hh"
#include "logging.hh"
#include "loggers.hh" #include "loggers.hh"
#include "util.hh"
namespace nix { namespace nix {

View file

@ -1,6 +1,6 @@
#include "loggers.hh" #include "loggers.hh"
#include "environment-variables.hh"
#include "progress-bar.hh" #include "progress-bar.hh"
#include "util.hh"
namespace nix { namespace nix {

View file

@ -1,5 +1,5 @@
#include "progress-bar.hh" #include "progress-bar.hh"
#include "util.hh" #include "terminal.hh"
#include "sync.hh" #include "sync.hh"
#include "store-api.hh" #include "store-api.hh"
#include "names.hh" #include "names.hh"

View file

@ -1,10 +1,11 @@
#include "globals.hh" #include "globals.hh"
#include "current-process.hh"
#include "shared.hh" #include "shared.hh"
#include "store-api.hh" #include "store-api.hh"
#include "gc-store.hh" #include "gc-store.hh"
#include "util.hh"
#include "loggers.hh" #include "loggers.hh"
#include "progress-bar.hh" #include "progress-bar.hh"
#include "signals.hh"
#include <algorithm> #include <algorithm>
#include <cctype> #include <cctype>

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
///@file ///@file
#include "util.hh" #include "processes.hh"
#include "args.hh" #include "args.hh"
#include "args/root.hh" #include "args/root.hh"
#include "common-args.hh" #include "common-args.hh"

View file

@ -2,7 +2,7 @@
#include "binary-cache-store.hh" #include "binary-cache-store.hh"
#include "compression.hh" #include "compression.hh"
#include "derivations.hh" #include "derivations.hh"
#include "fs-accessor.hh" #include "source-accessor.hh"
#include "globals.hh" #include "globals.hh"
#include "nar-info.hh" #include "nar-info.hh"
#include "sync.hh" #include "sync.hh"
@ -11,6 +11,7 @@
#include "nar-accessor.hh" #include "nar-accessor.hh"
#include "thread-pool.hh" #include "thread-pool.hh"
#include "callback.hh" #include "callback.hh"
#include "signals.hh"
#include <chrono> #include <chrono>
#include <future> #include <future>
@ -143,7 +144,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
write the compressed NAR to disk), into a HashSink (to get the write the compressed NAR to disk), into a HashSink (to get the
NAR hash), and into a NarAccessor (to get the NAR listing). */ NAR hash), and into a NarAccessor (to get the NAR listing). */
HashSink fileHashSink { htSHA256 }; HashSink fileHashSink { htSHA256 };
std::shared_ptr<FSAccessor> narAccessor; std::shared_ptr<SourceAccessor> narAccessor;
HashSink narHashSink { htSHA256 }; HashSink narHashSink { htSHA256 };
{ {
FdSink fileSink(fdTemp.get()); FdSink fileSink(fdTemp.get());
@ -195,7 +196,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
if (writeNARListing) { if (writeNARListing) {
nlohmann::json j = { nlohmann::json j = {
{"version", 1}, {"version", 1},
{"root", listNar(ref<FSAccessor>(narAccessor), "", true)}, {"root", listNar(ref<SourceAccessor>(narAccessor), CanonPath::root, true)},
}; };
upsertFile(std::string(info.path.hashPart()) + ".ls", j.dump(), "application/json"); upsertFile(std::string(info.path.hashPart()) + ".ls", j.dump(), "application/json");
@ -206,9 +207,9 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
specify the NAR file and member containing the debug info. */ specify the NAR file and member containing the debug info. */
if (writeDebugInfo) { if (writeDebugInfo) {
std::string buildIdDir = "/lib/debug/.build-id"; CanonPath buildIdDir("lib/debug/.build-id");
if (narAccessor->stat(buildIdDir).type == FSAccessor::tDirectory) { if (auto st = narAccessor->maybeLstat(buildIdDir); st && st->type == SourceAccessor::tDirectory) {
ThreadPool threadPool(25); ThreadPool threadPool(25);
@ -231,17 +232,17 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
std::regex regex1("^[0-9a-f]{2}$"); std::regex regex1("^[0-9a-f]{2}$");
std::regex regex2("^[0-9a-f]{38}\\.debug$"); std::regex regex2("^[0-9a-f]{38}\\.debug$");
for (auto & s1 : narAccessor->readDirectory(buildIdDir)) { for (auto & [s1, _type] : narAccessor->readDirectory(buildIdDir)) {
auto dir = buildIdDir + "/" + s1; auto dir = buildIdDir + s1;
if (narAccessor->stat(dir).type != FSAccessor::tDirectory if (narAccessor->lstat(dir).type != SourceAccessor::tDirectory
|| !std::regex_match(s1, regex1)) || !std::regex_match(s1, regex1))
continue; continue;
for (auto & s2 : narAccessor->readDirectory(dir)) { for (auto & [s2, _type] : narAccessor->readDirectory(dir)) {
auto debugPath = dir + "/" + s2; auto debugPath = dir + s2;
if (narAccessor->stat(debugPath).type != FSAccessor::tRegular if (narAccessor->lstat(debugPath).type != SourceAccessor::tRegular
|| !std::regex_match(s2, regex2)) || !std::regex_match(s2, regex2))
continue; continue;
@ -250,7 +251,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
std::string key = "debuginfo/" + buildId; std::string key = "debuginfo/" + buildId;
std::string target = "../" + narInfo->url; std::string target = "../" + narInfo->url;
threadPool.enqueue(std::bind(doFile, std::string(debugPath, 1), key, target)); threadPool.enqueue(std::bind(doFile, std::string(debugPath.rel()), key, target));
} }
} }
@ -503,9 +504,9 @@ void BinaryCacheStore::registerDrvOutput(const Realisation& info) {
upsertFile(filePath, info.toJSON().dump(), "application/json"); upsertFile(filePath, info.toJSON().dump(), "application/json");
} }
ref<FSAccessor> BinaryCacheStore::getFSAccessor() ref<SourceAccessor> BinaryCacheStore::getFSAccessor(bool requireValidPath)
{ {
return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()), localNarCache); return make_ref<RemoteFSAccessor>(ref<Store>(shared_from_this()), requireValidPath, localNarCache);
} }
void BinaryCacheStore::addSignatures(const StorePath & storePath, const StringSet & sigs) void BinaryCacheStore::addSignatures(const StorePath & storePath, const StringSet & sigs)

View file

@ -17,28 +17,28 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
{ {
using StoreConfig::StoreConfig; using StoreConfig::StoreConfig;
const Setting<std::string> compression{(StoreConfig*) this, "xz", "compression", const Setting<std::string> compression{this, "xz", "compression",
"NAR compression method (`xz`, `bzip2`, `gzip`, `zstd`, or `none`)."}; "NAR compression method (`xz`, `bzip2`, `gzip`, `zstd`, or `none`)."};
const Setting<bool> writeNARListing{(StoreConfig*) this, false, "write-nar-listing", const Setting<bool> writeNARListing{this, false, "write-nar-listing",
"Whether to write a JSON file that lists the files in each NAR."}; "Whether to write a JSON file that lists the files in each NAR."};
const Setting<bool> writeDebugInfo{(StoreConfig*) this, false, "index-debug-info", const Setting<bool> writeDebugInfo{this, false, "index-debug-info",
R"( R"(
Whether to index DWARF debug info files by build ID. This allows [`dwarffs`](https://github.com/edolstra/dwarffs) to Whether to index DWARF debug info files by build ID. This allows [`dwarffs`](https://github.com/edolstra/dwarffs) to
fetch debug info on demand fetch debug info on demand
)"}; )"};
const Setting<Path> secretKeyFile{(StoreConfig*) this, "", "secret-key", const Setting<Path> secretKeyFile{this, "", "secret-key",
"Path to the secret key used to sign the binary cache."}; "Path to the secret key used to sign the binary cache."};
const Setting<Path> localNarCache{(StoreConfig*) this, "", "local-nar-cache", const Setting<Path> localNarCache{this, "", "local-nar-cache",
"Path to a local cache of NARs fetched from this binary cache, used by commands such as `nix store cat`."}; "Path to a local cache of NARs fetched from this binary cache, used by commands such as `nix store cat`."};
const Setting<bool> parallelCompression{(StoreConfig*) this, false, "parallel-compression", const Setting<bool> parallelCompression{this, false, "parallel-compression",
"Enable multi-threaded compression of NARs. This is currently only available for `xz` and `zstd`."}; "Enable multi-threaded compression of NARs. This is currently only available for `xz` and `zstd`."};
const Setting<int> compressionLevel{(StoreConfig*) this, -1, "compression-level", const Setting<int> compressionLevel{this, -1, "compression-level",
R"( R"(
The *preset level* to be used when compressing NARs. The *preset level* to be used when compressing NARs.
The meaning and accepted values depend on the compression method selected. The meaning and accepted values depend on the compression method selected.
@ -148,7 +148,7 @@ public:
void narFromPath(const StorePath & path, Sink & sink) override; void narFromPath(const StorePath & path, Sink & sink) override;
ref<FSAccessor> getFSAccessor() override; ref<SourceAccessor> getFSAccessor(bool requireValidPath) override;
void addSignatures(const StorePath & storePath, const StringSet & sigs) override; void addSignatures(const StorePath & storePath, const StringSet & sigs) override;

View file

@ -0,0 +1,37 @@
#include "child.hh"
#include "current-process.hh"
#include "logging.hh"
#include <fcntl.h>
#include <unistd.h>
namespace nix {
void commonChildInit()
{
logger = makeSimpleLogger();
const static std::string pathNullDevice = "/dev/null";
restoreProcessContext(false);
/* Put the child in a separate session (and thus a separate
process group) so that it has no controlling terminal (meaning
that e.g. ssh cannot open /dev/tty) and it doesn't receive
terminal signals. */
if (setsid() == -1)
throw SysError("creating a new session");
/* Dup stderr to stdout. */
if (dup2(STDERR_FILENO, STDOUT_FILENO) == -1)
throw SysError("cannot dup stderr into stdout");
/* Reroute stdin to /dev/null. */
int fdDevNull = open(pathNullDevice.c_str(), O_RDWR);
if (fdDevNull == -1)
throw SysError("cannot open '%1%'", pathNullDevice);
if (dup2(fdDevNull, STDIN_FILENO) == -1)
throw SysError("cannot dup null device into stdin");
close(fdDevNull);
}
}

View file

@ -0,0 +1,11 @@
#pragma once
///@file
namespace nix {
/**
* Common initialisation performed in child processes.
*/
void commonChildInit();
}

View file

@ -1317,9 +1317,26 @@ void DerivationGoal::handleChildOutput(int fd, std::string_view data)
auto s = handleJSONLogMessage(*json, worker.act, hook->activities, true); auto s = handleJSONLogMessage(*json, worker.act, hook->activities, true);
// ensure that logs from a builder using `ssh-ng://` as protocol // ensure that logs from a builder using `ssh-ng://` as protocol
// are also available to `nix log`. // are also available to `nix log`.
if (s && !isWrittenToLog && logSink && (*json)["type"] == resBuildLogLine) { if (s && !isWrittenToLog && logSink) {
auto f = (*json)["fields"]; const auto type = (*json)["type"];
(*logSink)((f.size() > 0 ? f.at(0).get<std::string>() : "") + "\n"); const auto fields = (*json)["fields"];
if (type == resBuildLogLine) {
(*logSink)((fields.size() > 0 ? fields[0].get<std::string>() : "") + "\n");
} else if (type == resSetPhase && ! fields.is_null()) {
const auto phase = fields[0];
if (! phase.is_null()) {
// nixpkgs' stdenv produces lines in the log to signal
// phase changes.
// We want to get the same lines in case of remote builds.
// The format is:
// @nix { "action": "setPhase", "phase": "$curPhase" }
const auto logLine = nlohmann::json::object({
{"action", "setPhase"},
{"phase", phase}
});
(*logSink)("@nix " + logLine.dump(-1, ' ', false, nlohmann::json::error_handler_t::replace) + "\n");
}
}
} }
} }
currentHookLine.clear(); currentHookLine.clear();
@ -1474,6 +1491,7 @@ void DerivationGoal::done(
SingleDrvOutputs builtOutputs, SingleDrvOutputs builtOutputs,
std::optional<Error> ex) std::optional<Error> ex)
{ {
outputLocks.unlock();
buildResult.status = status; buildResult.status = status;
if (ex) if (ex)
buildResult.errorMsg = fmt("%s", normaltxt(ex->info().msg)); buildResult.errorMsg = fmt("%s", normaltxt(ex->info().msg));

View file

@ -1,5 +1,7 @@
#include "globals.hh" #include "globals.hh"
#include "hook-instance.hh" #include "hook-instance.hh"
#include "file-system.hh"
#include "child.hh"
namespace nix { namespace nix {

View file

@ -3,6 +3,7 @@
#include "logging.hh" #include "logging.hh"
#include "serialise.hh" #include "serialise.hh"
#include "processes.hh"
namespace nix { namespace nix {

View file

@ -15,7 +15,10 @@
#include "json-utils.hh" #include "json-utils.hh"
#include "cgroup.hh" #include "cgroup.hh"
#include "personality.hh" #include "personality.hh"
#include "current-process.hh"
#include "namespaces.hh" #include "namespaces.hh"
#include "child.hh"
#include "unix-domain-socket.hh"
#include <regex> #include <regex>
#include <queue> #include <queue>
@ -649,8 +652,8 @@ void LocalDerivationGoal::startBuilder()
#if __linux__ #if __linux__
/* Create a temporary directory in which we set up the chroot /* Create a temporary directory in which we set up the chroot
environment using bind-mounts. We put it in the Nix store environment using bind-mounts. We put it in the Nix store
to ensure that we can create hard-links to non-directory so that the build outputs can be moved efficiently from the
inputs in the fake Nix store in the chroot (see below). */ chroot to their final location. */
chrootRootDir = worker.store.Store::toRealPath(drvPath) + ".chroot"; chrootRootDir = worker.store.Store::toRealPath(drvPath) + ".chroot";
deletePath(chrootRootDir); deletePath(chrootRootDir);
@ -1563,10 +1566,11 @@ void LocalDerivationGoal::addDependency(const StorePath & path)
Path source = worker.store.Store::toRealPath(path); Path source = worker.store.Store::toRealPath(path);
Path target = chrootRootDir + worker.store.printStorePath(path); Path target = chrootRootDir + worker.store.printStorePath(path);
if (pathExists(target)) if (pathExists(target)) {
// There is a similar debug message in doBind, so only run it in this block to not have double messages. // There is a similar debug message in doBind, so only run it in this block to not have double messages.
debug("bind-mounting %s -> %s", target, source); debug("bind-mounting %s -> %s", target, source);
throw Error("store path '%s' already exists in the sandbox", worker.store.printStorePath(path)); throw Error("store path '%s' already exists in the sandbox", worker.store.printStorePath(path));
}
/* Bind-mount the path into the sandbox. This requires /* Bind-mount the path into the sandbox. This requires
entering its mount namespace, which is not possible entering its mount namespace, which is not possible
@ -1619,6 +1623,8 @@ void setupSeccomp()
seccomp_release(ctx); seccomp_release(ctx);
}); });
constexpr std::string_view nativeSystem = SYSTEM;
if (nativeSystem == "x86_64-linux" && if (nativeSystem == "x86_64-linux" &&
seccomp_arch_add(ctx, SCMP_ARCH_X86) != 0) seccomp_arch_add(ctx, SCMP_ARCH_X86) != 0)
throw SysError("unable to add 32-bit seccomp architecture"); throw SysError("unable to add 32-bit seccomp architecture");

View file

@ -3,6 +3,7 @@
#include "derivation-goal.hh" #include "derivation-goal.hh"
#include "local-store.hh" #include "local-store.hh"
#include "processes.hh"
namespace nix { namespace nix {

View file

@ -4,6 +4,7 @@
#include "drv-output-substitution-goal.hh" #include "drv-output-substitution-goal.hh"
#include "local-derivation-goal.hh" #include "local-derivation-goal.hh"
#include "hook-instance.hh" #include "hook-instance.hh"
#include "signals.hh"
#include <poll.h> #include <poll.h>

View file

@ -1,5 +1,4 @@
#include "serialise.hh" #include "serialise.hh"
#include "util.hh"
#include "path-with-outputs.hh" #include "path-with-outputs.hh"
#include "store-api.hh" #include "store-api.hh"
#include "build-result.hh" #include "build-result.hh"

View file

@ -1,4 +1,5 @@
#include "crypto.hh" #include "crypto.hh"
#include "file-system.hh"
#include "util.hh" #include "util.hh"
#include "globals.hh" #include "globals.hh"

View file

@ -454,13 +454,13 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
eagerly consume the entire stream it's given, past the eagerly consume the entire stream it's given, past the
length of the Nar. */ length of the Nar. */
TeeSource savedNARSource(from, saved); TeeSource savedNARSource(from, saved);
ParseSink sink; /* null sink; just parse the NAR */ NullParseSink sink; /* just parse the NAR */
parseDump(sink, savedNARSource); parseDump(sink, savedNARSource);
} else { } else {
/* Incrementally parse the NAR file, stripping the /* Incrementally parse the NAR file, stripping the
metadata, and streaming the sole file we expect into metadata, and streaming the sole file we expect into
`saved`. */ `saved`. */
RetrieveRegularNARSink savedRegular { saved }; RegularFileSink savedRegular { saved };
parseDump(savedRegular, from); parseDump(savedRegular, from);
if (!savedRegular.regular) throw Error("regular file expected"); if (!savedRegular.regular) throw Error("regular file expected");
} }
@ -899,7 +899,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
source = std::make_unique<TunnelSource>(from, to); source = std::make_unique<TunnelSource>(from, to);
else { else {
TeeSource tee { from, saved }; TeeSource tee { from, saved };
ParseSink ether; NullParseSink ether;
parseDump(ether, tee); parseDump(ether, tee);
source = std::make_unique<StringSource>(saved.s); source = std::make_unique<StringSource>(saved.s);
} }

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