diff --git a/.editorconfig b/.editorconfig
index 887ecadba..86360e658 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -17,7 +17,7 @@ indent_style = space
indent_size = 2
# 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_size = 4
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 893f4a56f..975c90b91 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -21,7 +21,7 @@ jobs:
fetch-depth: 0
- name: Create backport PRs
# should be kept in sync with `version`
- uses: zeebe-io/backport-action@v2.1.0
+ uses: zeebe-io/backport-action@v2.1.1
with:
# Config README: https://github.com/zeebe-io/backport-action#backport-action
github_token: ${{ secrets.GITHUB_TOKEN }}
diff --git a/Makefile.config.in b/Makefile.config.in
index aadece0e1..c85e028c2 100644
--- a/Makefile.config.in
+++ b/Makefile.config.in
@@ -15,6 +15,7 @@ GTEST_LIBS = @GTEST_LIBS@
HAVE_LIBCPUID = @HAVE_LIBCPUID@
HAVE_SECCOMP = @HAVE_SECCOMP@
HOST_OS = @host_os@
+INSTALL_UNIT_TESTS = @INSTALL_UNIT_TESTS@
LDFLAGS = @LDFLAGS@
LIBARCHIVE_LIBS = @LIBARCHIVE_LIBS@
LIBBROTLI_LIBS = @LIBBROTLI_LIBS@
@@ -31,6 +32,8 @@ SODIUM_LIBS = @SODIUM_LIBS@
SQLITE3_LIBS = @SQLITE3_LIBS@
bash = @bash@
bindir = @bindir@
+checkbindir = @checkbindir@
+checklibdir = @checklibdir@
datadir = @datadir@
datarootdir = @datarootdir@
doc_generate = @doc_generate@
diff --git a/configure.ac b/configure.ac
index 1cda0852a..71e93feaa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -167,6 +167,18 @@ AC_ARG_ENABLE(tests, AS_HELP_STRING([--disable-tests],[Do not build the tests]),
ENABLE_TESTS=$enableval, ENABLE_TESTS=yes)
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.
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)
diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in
index 794f78a07..8dc464abd 100644
--- a/doc/manual/src/SUMMARY.md.in
+++ b/doc/manual/src/SUMMARY.md.in
@@ -115,6 +115,7 @@
- [C++ style guide](contributing/cxx.md)
- [Release Notes](release-notes/release-notes.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.17 (2023-07-24)](release-notes/rl-2.17.md)
- [Release 2.16 (2023-05-31)](release-notes/rl-2.16.md)
diff --git a/doc/manual/src/contributing/testing.md b/doc/manual/src/contributing/testing.md
index 3d75ebe7b..0b45b88a3 100644
--- a/doc/manual/src/contributing/testing.md
+++ b/doc/manual/src/contributing/testing.md
@@ -133,17 +133,17 @@ ran test tests/functional/${testName}.sh... [PASS]
or without `make`:
```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]
```
To see the complete output, one can also run:
```shell-session
-$ ./mk/debug-test.sh tests/functional/${testName}.sh
-+ foo
+$ ./mk/debug-test.sh tests/functional/${testName}.sh tests/functional/init.sh
++(${testName}.sh:1) foo
output from foo
-+ bar
++(${testName}.sh:2) 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:
```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
GNU gdb (GDB) 12.1
diff --git a/doc/manual/src/language/constructs.md b/doc/manual/src/language/constructs.md
index a3590f55d..a82ec5960 100644
--- a/doc/manual/src/language/constructs.md
+++ b/doc/manual/src/language/constructs.md
@@ -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
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 have the following form:
@@ -146,65 +172,65 @@ three kinds of patterns:
- If a pattern is a single identifier, then the function matches any
argument. Example:
-
+
```nix
let negate = x: !x;
concat = x: y: x + y;
in if negate true then concat "foo" "bar" else ""
```
-
+
Note that `concat` is a function that takes one argument and returns
a function that takes another argument. This allows partial
parameterisation (i.e., only filling some of the arguments of a
function); e.g.,
-
+
```nix
map (concat "foo") [ "bar" "bla" "abc" ]
```
-
+
evaluates to `[ "foobar" "foobla" "fooabc" ]`.
- A *set pattern* of the form `{ name1, name2, …, nameN }` matches a
set containing the listed attributes, and binds the values of those
attributes to variables in the function body. For example, the
function
-
+
```nix
{ x, y, z }: z + y + 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
additional arguments, you can use an ellipsis (`...`):
-
+
```nix
{ x, y, z, ... }: z + y + x
```
-
+
This works on any set that contains at least the three named
attributes.
-
+
It is possible to provide *default values* for attributes, in
which case they are allowed to be missing. A default value is
specified by writing `name ? e`, where *e* is an arbitrary
expression. For example,
-
+
```nix
{ x, y ? "foo", z ? "bar" }: z + y + x
```
-
+
specifies a function that only requires an attribute named `x`, but
optionally accepts `y` and `z`.
- An `@`-pattern provides a means of referring to the whole value
being matched:
-
+
```nix
args@{ x, y, z, ... }: z + y + x + args.a
```
-
+
but can also be written as:
-
+
```nix
{ x, y, z, ... } @ args: z + y + x + args.a
```
diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md
index cc825b4cf..e9cbb5c92 100644
--- a/doc/manual/src/language/operators.md
+++ b/doc/manual/src/language/operators.md
@@ -25,7 +25,7 @@
| Inequality | *expr* `!=` *expr* | none | 11 |
| Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 |
| Logical disjunction (`OR`) | *bool* \|\|
*bool* | left | 13 |
-| [Logical implication] | *bool* `->` *bool* | none | 14 |
+| [Logical implication] | *bool* `->` *bool* | right | 14 |
[string]: ./values.md#type-string
[path]: ./values.md#type-path
diff --git a/doc/manual/src/release-notes/rl-2.19.md b/doc/manual/src/release-notes/rl-2.19.md
new file mode 100644
index 000000000..4eecaf929
--- /dev/null
+++ b/doc/manual/src/release-notes/rl-2.19.md
@@ -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.` or `legacyPackages.`.
+
+- 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.
diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md
index 1e6ad6922..78ae99f4b 100644
--- a/doc/manual/src/release-notes/rl-next.md
+++ b/doc/manual/src/release-notes/rl-next.md
@@ -1,75 +1,2 @@
# Release X.Y (202?-??-??)
-- 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 to 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.
-
-- Introduce new flake installable syntax `flakeref#.attrPath` where the "." prefix denotes no searching of default attribute prefixes like `packages.` or `legacyPackages.`.
-
-- 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).
-
-- `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 document 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 th 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).
diff --git a/flake.nix b/flake.nix
index d6a173081..06a6fe8ea 100644
--- a/flake.nix
+++ b/flake.nix
@@ -165,6 +165,10 @@
testConfigureFlags = [
"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 = [
@@ -410,7 +414,8 @@
src = nixSrc;
VERSION_SUFFIX = versionSuffix;
- outputs = [ "out" "dev" "doc" ];
+ outputs = [ "out" "dev" "doc" ]
+ ++ lib.optional (currentStdenv.hostPlatform != currentStdenv.buildPlatform) "check";
nativeBuildInputs = nativeBuildDeps;
buildInputs = buildDeps
@@ -716,7 +721,8 @@
stdenv.mkDerivation {
name = "nix";
- outputs = [ "out" "dev" "doc" ];
+ outputs = [ "out" "dev" "doc" ]
+ ++ lib.optional (stdenv.hostPlatform != stdenv.buildPlatform) "check";
nativeBuildInputs = nativeBuildDeps
++ lib.optional stdenv.cc.isClang pkgs.buildPackages.bear
diff --git a/mk/common-test.sh b/mk/common-test.sh
index 7ab25febf..00ccd1584 100644
--- a/mk/common-test.sh
+++ b/mk/common-test.sh
@@ -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%.*}" 'NIX_REMOTE=')
+TESTS_ENVIRONMENT=(
+ "TEST_NAME=$test_name"
+ 'NIX_REMOTE='
+ 'PS4=+(${BASH_SOURCE[0]-$0}:$LINENO) '
+)
: ${BASH:=/usr/bin/env bash}
+run () {
+ cd "$(dirname $1)" && env "${TESTS_ENVIRONMENT[@]}" $BASH -x -e -u -o pipefail $(basename $1)
+}
+
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 () {
- cd "$test_dir/$(dirname $test)" && env "${TESTS_ENVIRONMENT[@]}" $BASH -e $(basename $test)
+ run "$test"
}
diff --git a/mk/debug-test.sh b/mk/debug-test.sh
index b5b628ecd..52482c01e 100755
--- a/mk/debug-test.sh
+++ b/mk/debug-test.sh
@@ -3,9 +3,12 @@
set -eu -o pipefail
test=$1
+init=${2-}
dir="$(dirname "${BASH_SOURCE[0]}")"
source "$dir/common-test.sh"
-(init_test)
+if [ -n "$init" ]; then
+ (init_test)
+fi
run_test_proper
diff --git a/mk/lib.mk b/mk/lib.mk
index e86a7f1a4..49abe9862 100644
--- a/mk/lib.mk
+++ b/mk/lib.mk
@@ -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, $(noinst-scripts), $(eval programs-list += $(script)))
$(foreach template, $(template-files), $(eval $(call instantiate-template,$(template))))
+install_test_init=tests/functional/init.sh
$(foreach test, $(install-tests), \
- $(eval $(call run-install-test,$(test))) \
+ $(eval $(call run-test,$(test),$(install_test_init))) \
$(eval installcheck: $(test).test))
$(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) \
$(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)))
$(foreach file, $(man-pages), $(eval $(call install-data-in, $(file), $(mandir)/man$(patsubst .%,%,$(suffix $(file))))))
diff --git a/mk/run-test.sh b/mk/run-test.sh
index 1a1d65930..da9c5a473 100755
--- a/mk/run-test.sh
+++ b/mk/run-test.sh
@@ -8,6 +8,7 @@ yellow=""
normal=""
test=$1
+init=${2-}
dir="$(dirname "${BASH_SOURCE[0]}")"
source "$dir/common-test.sh"
@@ -21,7 +22,9 @@ if [ -t 1 ]; then
fi
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=$?
}
diff --git a/mk/tests.mk b/mk/tests.mk
index ec8128bdf..bac9b704a 100644
--- a/mk/tests.mk
+++ b/mk/tests.mk
@@ -2,19 +2,22 @@
test-deps =
-define run-install-test
+define run-bash
- .PHONY: $1.test
- $1.test: $1 $(test-deps)
- @env BASH=$(bash) $(bash) mk/run-test.sh $1 < /dev/null
-
- .PHONY: $1.test-debug
- $1.test-debug: $1 $(test-deps)
- @env BASH=$(bash) $(bash) mk/debug-test.sh $1 < /dev/null
+ .PHONY: $1
+ $1: $2
+ @env BASH=$(bash) $(bash) $3 < /dev/null
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
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index dfe81cbf7..46a49c891 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -1,6 +1,7 @@
#include "eval.hh"
#include "eval-settings.hh"
#include "hash.hh"
+#include "primops.hh"
#include "types.hh"
#include "util.hh"
#include "store-api.hh"
@@ -722,6 +723,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)
{
/* Hack to make constants lazy: turn them into a application of
@@ -1748,6 +1766,12 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v)
Value vFun;
fun->eval(state, env, vFun);
+ // 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.
Value * vArgs[args.size()];
for (size_t i = 0; i < args.size(); ++i)
vArgs[i] = args[i]->maybeThunk(state, env);
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 048dff42b..9a92992c1 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -18,6 +18,12 @@
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 EvalState;
@@ -71,6 +77,12 @@ struct PrimOp
* Optional experimental for this to be gated on.
*/
std::optional 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".
*/
-SourcePath resolveExprPath(const SourcePath & path);
+SourcePath resolveExprPath(SourcePath path);
struct InvalidPathError : EvalError
{
diff --git a/src/libexpr/local.mk b/src/libexpr/local.mk
index d243b9cec..ed7bf9490 100644
--- a/src/libexpr/local.mk
+++ b/src/libexpr/local.mk
@@ -43,7 +43,9 @@ $(foreach i, $(wildcard src/libexpr/value/*.hh), \
$(foreach i, $(wildcard src/libexpr/flake/*.hh), \
$(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
diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y
index b86cef217..f6cf1f689 100644
--- a/src/libexpr/parser.y
+++ b/src/libexpr/parser.y
@@ -686,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
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 (path2.lstat().type == InputAccessor::tDirectory)
- return path2 + "default.nix";
+ if (path.lstat().type == InputAccessor::tDirectory)
+ return path + "default.nix";
- return path2;
+ return path;
}
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 8d3a18526..a8d44d8b7 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -29,7 +29,6 @@
#include
-
namespace nix {
@@ -2375,7 +2374,7 @@ static RegisterPrimOp primop_path({
like `@`.
- filter\
- A function of the type expected by `builtins.filterSource`,
+ A function of the type expected by [`builtins.filterSource`](#builtins-filterSource),
with the same semantics.
- recursive\
@@ -2550,6 +2549,7 @@ static void prim_removeAttrs(EvalState & state, const PosIdx pos, Value * * args
/* Get the attribute names to be removed.
We keep them as Attrs instead of Symbols so std::set_difference
can be used to remove them from attrs[0]. */
+ // 64: large enough to fit the attributes of a derivation
boost::container::small_vector names;
names.reserve(args[1]->listSize());
for (auto elem : args[1]->listItems()) {
@@ -2730,7 +2730,7 @@ static void prim_catAttrs(EvalState & state, const PosIdx pos, Value * * args, V
state.forceList(*args[1], pos, "while evaluating the second argument passed to builtins.catAttrs");
Value * res[args[1]->listSize()];
- unsigned int found = 0;
+ size_t found = 0;
for (auto v2 : args[1]->listItems()) {
state.forceAttrs(*v2, pos, "while evaluating an element in the list passed as second argument to builtins.catAttrs");
@@ -3066,7 +3066,7 @@ static void prim_filter(EvalState & state, const PosIdx pos, Value * * args, Val
// FIXME: putting this on the stack is risky.
Value * vs[args[1]->listSize()];
- unsigned int k = 0;
+ size_t k = 0;
bool same = true;
for (unsigned int n = 0; n < args[1]->listSize(); ++n) {
@@ -3191,10 +3191,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.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;
for (auto elem : args[1]->listItems()) {
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) {
v.mkBool(any);
return;
@@ -3456,7 +3460,7 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
for (unsigned int n = 0; n < nrLists; ++n) {
Value * vElem = args[1]->listElems()[n];
state.callFunction(*args[0], *vElem, lists[n], pos);
- state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to buitlins.concatMap");
+ state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)), "while evaluating the return value of the function passed to builtins.concatMap");
len += lists[n].listSize();
}
diff --git a/src/libexpr/primops.hh b/src/libexpr/primops.hh
index 930e7f32a..45486608f 100644
--- a/src/libexpr/primops.hh
+++ b/src/libexpr/primops.hh
@@ -8,6 +8,22 @@
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
{
typedef std::vector PrimOps;
diff --git a/src/libexpr/tests/error_traces.cc b/src/libexpr/tests/error_traces.cc
index 139366bcd..81498f65a 100644
--- a/src/libexpr/tests/error_traces.cc
+++ b/src/libexpr/tests/error_traces.cc
@@ -906,12 +906,12 @@ namespace nix {
ASSERT_TRACE2("concatMap (x: 1) [ \"foo\" ] # TODO",
TypeError,
hintfmt("value is %s while a list was expected", "an integer"),
- hintfmt("while evaluating the return value of the function passed to buitlins.concatMap"));
+ hintfmt("while evaluating the return value of the function passed to builtins.concatMap"));
ASSERT_TRACE2("concatMap (x: \"foo\") [ 1 2 ] # TODO",
TypeError,
hintfmt("value is %s while a list was expected", "a string"),
- hintfmt("while evaluating the return value of the function passed to buitlins.concatMap"));
+ hintfmt("while evaluating the return value of the function passed to builtins.concatMap"));
}
diff --git a/src/libexpr/tests/local.mk b/src/libexpr/tests/local.mk
index 331a5ead6..6d2a04aaf 100644
--- a/src/libexpr/tests/local.mk
+++ b/src/libexpr/tests/local.mk
@@ -6,7 +6,11 @@ libexpr-tests_NAME := libnixexpr-tests
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 := \
$(wildcard $(d)/*.cc) \
diff --git a/src/libexpr/tests/value/print.cc b/src/libexpr/tests/value/print.cc
index 5e96e12ec..a4f6fc014 100644
--- a/src/libexpr/tests/value/print.cc
+++ b/src/libexpr/tests/value/print.cc
@@ -114,7 +114,8 @@ TEST_F(ValuePrintingTests, vLambda)
TEST_F(ValuePrintingTests, vPrimOp)
{
Value vPrimOp;
- vPrimOp.mkPrimOp(nullptr);
+ PrimOp primOp{};
+ vPrimOp.mkPrimOp(&primOp);
test(vPrimOp, "");
}
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index 622e613ea..bcff8ae55 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -3,6 +3,7 @@
#include
#include
+#include
#include "symbol-table.hh"
#include "value/context.hh"
@@ -158,42 +159,60 @@ public:
inline bool isPrimOp() const { return internalType == tPrimOp; };
inline bool isPrimOpApp() const { return internalType == tPrimOpApp; };
+ /**
+ * Strings in the evaluator carry a so-called `context` which
+ * is a list of strings representing store paths. This is to
+ * allow users to write things like
+ *
+ * "--with-freetype2-library=" + freetype + "/lib"
+ *
+ * where `freetype` is a derivation (or a source to be copied
+ * to the store). If we just concatenated the strings without
+ * keeping track of the referenced store paths, then if the
+ * string is used as a derivation attribute, the derivation
+ * will not have the correct dependencies in its inputDrvs and
+ * inputSrcs.
+
+ * The semantics of the context is as follows: when a string
+ * with context C is used as a derivation attribute, then the
+ * derivations in C will be added to the inputDrvs of the
+ * derivation, and the other store paths in C will be added to
+ * the inputSrcs of the derivations.
+
+ * For canonicity, the store paths should be in sorted order.
+ */
+ struct StringWithContext {
+ const char * c_str;
+ const char * * context; // must be in sorted order
+ };
+
+ struct Path {
+ InputAccessor * accessor;
+ const char * path;
+ };
+
+ struct ClosureThunk {
+ Env * env;
+ Expr * expr;
+ };
+
+ struct FunctionApplicationThunk {
+ Value * left, * right;
+ };
+
+ struct Lambda {
+ Env * env;
+ ExprLambda * fun;
+ };
+
union
{
NixInt integer;
bool boolean;
- /**
- * Strings in the evaluator carry a so-called `context` which
- * is a list of strings representing store paths. This is to
- * allow users to write things like
+ StringWithContext string;
- * "--with-freetype2-library=" + freetype + "/lib"
-
- * where `freetype` is a derivation (or a source to be copied
- * to the store). If we just concatenated the strings without
- * keeping track of the referenced store paths, then if the
- * string is used as a derivation attribute, the derivation
- * will not have the correct dependencies in its inputDrvs and
- * inputSrcs.
-
- * The semantics of the context is as follows: when a string
- * with context C is used as a derivation attribute, then the
- * derivations in C will be added to the inputDrvs of the
- * derivation, and the other store paths in C will be added to
- * the inputSrcs of the derivations.
-
- * For canonicity, the store paths should be in sorted order.
- */
- struct {
- const char * c_str;
- const char * * context; // must be in sorted order
- } string;
-
- struct {
- InputAccessor * accessor;
- const char * path;
- } _path;
+ Path _path;
Bindings * attrs;
struct {
@@ -201,21 +220,11 @@ public:
Value * * elems;
} bigList;
Value * smallList[2];
- struct {
- Env * env;
- Expr * expr;
- } thunk;
- struct {
- Value * left, * right;
- } app;
- struct {
- Env * env;
- ExprLambda * fun;
- } lambda;
+ ClosureThunk thunk;
+ FunctionApplicationThunk app;
+ Lambda lambda;
PrimOp * primOp;
- struct {
- Value * left, * right;
- } primOpApp;
+ FunctionApplicationThunk primOpApp;
ExternalValueBase * external;
NixFloat fpoint;
};
@@ -354,13 +363,7 @@ public:
// Value will be overridden anyways
}
- inline void mkPrimOp(PrimOp * p)
- {
- clearValue();
- internalType = tPrimOp;
- primOp = p;
- }
-
+ void mkPrimOp(PrimOp * p);
inline void mkPrimOpApp(Value * l, Value * r)
{
@@ -393,7 +396,13 @@ public:
return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems;
}
- const Value * const * listElems() const
+ std::span listItems() const
+ {
+ assert(isList());
+ return std::span(listElems(), listSize());
+ }
+
+ Value * const * listElems() const
{
return internalType == tList1 || internalType == tList2 ? smallList : bigList.elems;
}
@@ -412,34 +421,6 @@ public:
*/
bool isTrivial() const;
- auto listItems()
- {
- struct ListIterable
- {
- typedef Value * const * iterator;
- iterator _begin, _end;
- iterator begin() const { return _begin; }
- iterator end() const { return _end; }
- };
- assert(isList());
- auto begin = listElems();
- return ListIterable { begin, begin + listSize() };
- }
-
- auto listItems() const
- {
- struct ConstListIterable
- {
- typedef const Value * const * iterator;
- iterator _begin, _end;
- iterator begin() const { return _begin; }
- iterator end() const { return _end; }
- };
- assert(isList());
- auto begin = listElems();
- return ConstListIterable { begin, begin + listSize() };
- }
-
SourcePath path() const
{
assert(internalType == tPath);
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 360c6b70b..81eef7c47 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -1317,9 +1317,26 @@ void DerivationGoal::handleChildOutput(int fd, std::string_view data)
auto s = handleJSONLogMessage(*json, worker.act, hook->activities, true);
// ensure that logs from a builder using `ssh-ng://` as protocol
// are also available to `nix log`.
- if (s && !isWrittenToLog && logSink && (*json)["type"] == resBuildLogLine) {
- auto f = (*json)["fields"];
- (*logSink)((f.size() > 0 ? f.at(0).get() : "") + "\n");
+ if (s && !isWrittenToLog && logSink) {
+ const auto type = (*json)["type"];
+ const auto fields = (*json)["fields"];
+ if (type == resBuildLogLine) {
+ (*logSink)((fields.size() > 0 ? fields[0].get() : "") + "\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();
@@ -1474,6 +1491,7 @@ void DerivationGoal::done(
SingleDrvOutputs builtOutputs,
std::optional ex)
{
+ outputLocks.unlock();
buildResult.status = status;
if (ex)
buildResult.errorMsg = fmt("%s", normaltxt(ex->info().msg));
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index adb011e30..a9f930773 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -652,8 +652,8 @@ void LocalDerivationGoal::startBuilder()
#if __linux__
/* Create a temporary directory in which we set up the chroot
environment using bind-mounts. We put it in the Nix store
- to ensure that we can create hard-links to non-directory
- inputs in the fake Nix store in the chroot (see below). */
+ so that the build outputs can be moved efficiently from the
+ chroot to their final location. */
chrootRootDir = worker.store.Store::toRealPath(drvPath) + ".chroot";
deletePath(chrootRootDir);
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index 1fecd1c97..6d9c8b9d6 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -151,11 +151,10 @@ StorePath writeDerivation(Store & store,
/* Read string `s' from stream `str'. */
static void expect(std::istream & str, std::string_view s)
{
- char s2[s.size()];
- str.read(s2, s.size());
- std::string_view s2View { s2, s.size() };
- if (s2View != s)
- throw FormatError("expected string '%s', got '%s'", s, s2View);
+ for (auto & c : s) {
+ if (str.get() != c)
+ throw FormatError("expected string '%1%'", s);
+ }
}
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 8d05ae4bd..93fa60682 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -330,9 +330,7 @@ typedef std::unordered_map> UncheckedRoots
static void readProcLink(const std::string & file, UncheckedRoots & roots)
{
- /* 64 is the starting buffer size gnu readlink uses... */
- auto bufsiz = ssize_t{64};
-try_again:
+ constexpr auto bufsiz = PATH_MAX;
char buf[bufsiz];
auto res = readlink(file.c_str(), buf, bufsiz);
if (res == -1) {
@@ -341,10 +339,7 @@ try_again:
throw SysError("reading symlink");
}
if (res == bufsiz) {
- if (SSIZE_MAX / 2 < bufsiz)
- throw Error("stupidly long symlink");
- bufsiz *= 2;
- goto try_again;
+ throw Error("overly long symlink starting with '%1%'", std::string_view(buf, bufsiz));
}
if (res > 0 && buf[0] == '/')
roots[std::string(static_cast(buf), res)]
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 8e034f5a9..27caf42c4 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -1084,6 +1084,16 @@ public:
true, // document default
Xp::ConfigurableImpureEnv
};
+
+ Setting upgradeNixStorePathUrl{
+ this,
+ "https://github.com/NixOS/nixpkgs/raw/master/nixos/modules/installer/tools/nix-fallback-paths.nix",
+ "upgrade-nix-store-path-url",
+ R"(
+ Used by `nix upgrade-nix`, the URL of the file that contains the
+ store paths of the latest Nix release.
+ )"
+ };
};
diff --git a/src/libstore/tests/local.mk b/src/libstore/tests/local.mk
index 03becc7d1..e9b8b4f99 100644
--- a/src/libstore/tests/local.mk
+++ b/src/libstore/tests/local.mk
@@ -6,7 +6,11 @@ libstore-tests-exe_NAME = libnixstore-tests
libstore-tests-exe_DIR := $(d)
-libstore-tests-exe_INSTALL_DIR :=
+ifeq ($(INSTALL_UNIT_TESTS), yes)
+ libstore-tests-exe_INSTALL_DIR := $(checkbindir)
+else
+ libstore-tests-exe_INSTALL_DIR :=
+endif
libstore-tests-exe_LIBS = libstore-tests
@@ -18,7 +22,11 @@ libstore-tests_NAME = libnixstore-tests
libstore-tests_DIR := $(d)
-libstore-tests_INSTALL_DIR :=
+ifeq ($(INSTALL_UNIT_TESTS), yes)
+ libstore-tests_INSTALL_DIR := $(checklibdir)
+else
+ libstore-tests_INSTALL_DIR :=
+endif
libstore-tests_SOURCES := $(wildcard $(d)/*.cc)
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 4359c5e8e..4480a03f5 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -97,6 +97,8 @@ struct Parser {
virtual void operator()(std::shared_ptr & state, Strings & r) = 0;
Parser(std::string_view s) : remaining(s) {};
+
+ virtual ~Parser() { };
};
struct ParseQuoted : public Parser {
diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc
index 88fb55713..ac4d189e1 100644
--- a/src/libutil/experimental-features.cc
+++ b/src/libutil/experimental-features.cc
@@ -96,6 +96,14 @@ constexpr std::array xpFeatureDetails
[`nix`](@docroot@/command-ref/new-cli/nix.md) for details.
)",
},
+ {
+ .tag = Xp::GitHashing,
+ .name = "git-hashing",
+ .description = R"(
+ Allow creating (content-addressed) store objects which are hashed via Git's hashing algorithm.
+ These store objects will not be understandable by older versions of Nix.
+ )",
+ },
{
.tag = Xp::RecursiveNix,
.name = "recursive-nix",
diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh
index f580fd030..c355b8081 100644
--- a/src/libutil/experimental-features.hh
+++ b/src/libutil/experimental-features.hh
@@ -22,6 +22,7 @@ enum struct ExperimentalFeature
Flakes,
FetchTree,
NixCommand,
+ GitHashing,
RecursiveNix,
NoUrlLiterals,
FetchClosure,
diff --git a/src/libutil/git.cc b/src/libutil/git.cc
index f35c2fdb7..a4bd60096 100644
--- a/src/libutil/git.cc
+++ b/src/libutil/git.cc
@@ -1,9 +1,263 @@
-#include "git.hh"
-
+#include
+#include
+#include
+#include