diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 984f9a9ea..a5005f8a0 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -1,36 +1,54 @@
---
name: Bug report
-about: Create a report to help us improve
+about: Report unexpected or incorrect behaviour
title: ''
labels: bug
assignees: ''
---
-**Describe the bug**
+## Describe the bug
-A clear and concise description of what the bug is.
+
-**Steps To Reproduce**
+## Steps To Reproduce
-1. Go to '...'
-2. Click on '....'
-3. Scroll down to '....'
-4. See error
+
-A clear and concise description of what you expected to happen.
+## Expected behavior
-**`nix-env --version` output**
+
-**Additional context**
+## Metadata
-Add any other context about the problem here.
+
-**Priorities**
+## Additional context
+
+
+
+## Checklist
+
+
+
+- [ ] checked [latest Nix manual] \([source])
+- [ ] checked [open bug issues and pull requests] for possible duplicates
+
+[latest Nix manual]: https://nixos.org/manual/nix/unstable/
+[source]: https://github.com/NixOS/nix/tree/master/doc/manual/source
+[open bug issues and pull requests]: https://github.com/NixOS/nix/labels/bug
+
+---
Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc).
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 42c658b52..c75a46951 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -1,24 +1,39 @@
---
name: Feature request
-about: Suggest an idea for this project
+about: Suggest a new feature
title: ''
labels: feature
assignees: ''
---
-**Is your feature request related to a problem? Please describe.**
-A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+## Is your feature request related to a problem?
-**Describe the solution you'd like**
-A clear and concise description of what you want to happen.
+
-**Describe alternatives you've considered**
-A clear and concise description of any alternative solutions or features you've considered.
+## Proposed solution
-**Additional context**
-Add any other context or screenshots about the feature request here.
+
-**Priorities**
+## Alternative solutions
+
+
+
+## Additional context
+
+
+
+## Checklist
+
+
+
+- [ ] checked [latest Nix manual] \([source])
+- [ ] checked [open feature issues and pull requests] for possible duplicates
+
+[latest Nix manual]: https://nixos.org/manual/nix/unstable/
+[source]: https://github.com/NixOS/nix/tree/master/doc/manual/source
+[open feature issues and pull requests]: https://github.com/NixOS/nix/labels/feature
+
+---
Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc).
diff --git a/.github/ISSUE_TEMPLATE/installer.md b/.github/ISSUE_TEMPLATE/installer.md
index 3768a49c9..ed5e1ce87 100644
--- a/.github/ISSUE_TEMPLATE/installer.md
+++ b/.github/ISSUE_TEMPLATE/installer.md
@@ -23,14 +23,25 @@ assignees: ''
Output
-```log
+
-
+```log
```
-## Priorities
+## Checklist
+
+
+
+- [ ] checked [latest Nix manual] \([source])
+- [ ] checked [open installer issues and pull requests] for possible duplicates
+
+[latest Nix manual]: https://nixos.org/manual/nix/unstable/
+[source]: https://github.com/NixOS/nix/tree/master/doc/manual/source
+[open installer issues and pull requests]: https://github.com/NixOS/nix/labels/installer
+
+---
Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc).
diff --git a/.github/ISSUE_TEMPLATE/missing_documentation.md b/.github/ISSUE_TEMPLATE/missing_documentation.md
index cf663e28d..6c334b722 100644
--- a/.github/ISSUE_TEMPLATE/missing_documentation.md
+++ b/.github/ISSUE_TEMPLATE/missing_documentation.md
@@ -26,6 +26,6 @@ assignees: ''
[source]: https://github.com/NixOS/nix/tree/master/doc/manual/source
[open documentation issues and pull requests]: https://github.com/NixOS/nix/labels/documentation
-## Priorities
+---
Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc).
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 69da87db7..c6843d86f 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -17,10 +17,12 @@ so you understand the process and the expectations.
-->
-# Motivation
+## Motivation
+
-# Context
+## Context
+
@@ -29,7 +31,7 @@ so you understand the process and the expectations.
-# Priorities and Process
+---
Add :+1: to [pull requests you find important](https://github.com/NixOS/nix/pulls?q=is%3Aopen+sort%3Areactions-%2B1-desc).
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 27f60574e..be96bb484 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -23,7 +23,9 @@ jobs:
- uses: cachix/install-nix-action@v30
with:
# The sandbox would otherwise be disabled by default on Darwin
- extra_nix_config: "sandbox = true"
+ extra_nix_config: |
+ sandbox = true
+ max-jobs = 1
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/cachix-action@v15
if: needs.check_secrets.outputs.cachix == 'true'
@@ -128,7 +130,7 @@ jobs:
- run: exec bash -c "nix-channel --update && nix-env -iA nixpkgs.hello && hello"
docker_push_image:
- needs: [check_secrets, tests]
+ needs: [check_secrets, tests, vm_tests]
permissions:
contents: read
packages: write
@@ -194,7 +196,13 @@ jobs:
- uses: actions/checkout@v4
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- - run: nix build -L .#hydraJobs.tests.githubFlakes .#hydraJobs.tests.tarballFlakes .#hydraJobs.tests.functional_user
+ - run: |
+ nix build -L \
+ .#hydraJobs.tests.functional_user \
+ .#hydraJobs.tests.githubFlakes \
+ .#hydraJobs.tests.nix-docker \
+ .#hydraJobs.tests.tarballFlakes \
+ ;
flake_regressions:
needs: vm_tests
@@ -214,4 +222,4 @@ jobs:
path: flake-regressions/tests
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- - run: nix build --out-link ./new-nix && PATH=$(pwd)/new-nix/bin:$PATH MAX_FLAKES=25 flake-regressions/eval-all.sh
+ - run: nix build -L --out-link ./new-nix && PATH=$(pwd)/new-nix/bin:$PATH MAX_FLAKES=25 flake-regressions/eval-all.sh
diff --git a/.mergify.yml b/.mergify.yml
index c297d3d5e..c545bbe6a 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -2,9 +2,6 @@ queue_rules:
- name: default
# all required tests need to go here
merge_conditions:
- - check-success=installer
- - check-success=installer_test (macos-latest)
- - check-success=installer_test (ubuntu-latest)
- check-success=tests (macos-latest)
- check-success=tests (ubuntu-latest)
- check-success=vm_tests
@@ -90,3 +87,13 @@ pull_request_rules:
- "2.24-maintenance"
labels:
- merge-queue
+
+ - name: backport patches to 2.25
+ conditions:
+ - label=backport 2.25-maintenance
+ actions:
+ backport:
+ branches:
+ - "2.25-maintenance"
+ labels:
+ - merge-queue
diff --git a/build-utils-meson/common/meson.build b/build-utils-meson/common/meson.build
new file mode 100644
index 000000000..f0322183e
--- /dev/null
+++ b/build-utils-meson/common/meson.build
@@ -0,0 +1,22 @@
+# This is only conditional to work around
+# https://github.com/mesonbuild/meson/issues/13293. It should be
+# unconditional.
+if not (host_machine.system() == 'windows' and cxx.get_id() == 'gcc')
+ deps_private += dependency('threads')
+endif
+
+add_project_arguments(
+ '-Wdeprecated-copy',
+ '-Werror=suggest-override',
+ '-Werror=switch',
+ '-Werror=switch-enum',
+ '-Werror=unused-result',
+ '-Wignored-qualifiers',
+ '-Wimplicit-fallthrough',
+ '-Wno-deprecated-declarations',
+ language : 'cpp',
+)
+
+if get_option('buildtype') not in ['debug']
+ add_project_arguments('-O3', language : 'cpp')
+endif
diff --git a/build-utils-meson/diagnostics/meson.build b/build-utils-meson/diagnostics/meson.build
deleted file mode 100644
index 30eedfc13..000000000
--- a/build-utils-meson/diagnostics/meson.build
+++ /dev/null
@@ -1,11 +0,0 @@
-add_project_arguments(
- '-Wdeprecated-copy',
- '-Werror=suggest-override',
- '-Werror=switch',
- '-Werror=switch-enum',
- '-Werror=unused-result',
- '-Wignored-qualifiers',
- '-Wimplicit-fallthrough',
- '-Wno-deprecated-declarations',
- language : 'cpp',
-)
diff --git a/build-utils-meson/threads/meson.build b/build-utils-meson/threads/meson.build
deleted file mode 100644
index 294160de1..000000000
--- a/build-utils-meson/threads/meson.build
+++ /dev/null
@@ -1,6 +0,0 @@
-# This is only conditional to work around
-# https://github.com/mesonbuild/meson/issues/13293. It should be
-# unconditional.
-if not (host_machine.system() == 'windows' and cxx.get_id() == 'gcc')
- deps_private += dependency('threads')
-endif
diff --git a/doc/manual/rl-next/nix-copy-flags.md b/doc/manual/rl-next/nix-copy-flags.md
new file mode 100644
index 000000000..f5b2b9716
--- /dev/null
+++ b/doc/manual/rl-next/nix-copy-flags.md
@@ -0,0 +1,18 @@
+---
+synopsis: "`nix copy` supports `--profile` and `--out-link`"
+prs: [11657]
+---
+
+The `nix copy` command now has flags `--profile` and `--out-link`, similar to `nix build`. `--profile` makes a profile point to the
+top-level store path, while `--out-link` create symlinks to the top-level store paths.
+
+For example, when updating the local NixOS system profile from a NixOS system closure on a remote machine, instead of
+```
+# nix copy --from ssh://server $path
+# nix build --profile /nix/var/nix/profiles/system $path
+```
+you can now do
+```
+# nix copy --from ssh://server --profile /nix/var/nix/profiles/system $path
+```
+The advantage is that this avoids a time window where *path* is not a garbage collector root, and so could be deleted by a concurrent `nix store gc` process.
diff --git a/doc/manual/source/command-ref/nix-shell.md b/doc/manual/source/command-ref/nix-shell.md
index 69a711bd5..e95db9bea 100644
--- a/doc/manual/source/command-ref/nix-shell.md
+++ b/doc/manual/source/command-ref/nix-shell.md
@@ -88,7 +88,9 @@ All options not listed here are passed to `nix-store
cleared before the interactive shell is started, so you get an
environment that more closely corresponds to the “real” Nix build. A
few variables, in particular `HOME`, `USER` and `DISPLAY`, are
- retained.
+ retained. Note that the shell used to run commands is obtained from
+ [`NIX_BUILD_SHELL`](#env-NIX_BUILD_SHELL) / `` from
+ `NIX_PATH`, and therefore not affected by `--pure`.
- `--packages` / `-p` *packages*…
@@ -112,11 +114,30 @@ All options not listed here are passed to `nix-store
# Environment variables
-- `NIX_BUILD_SHELL`
+- [`NIX_BUILD_SHELL`](#env-NIX_BUILD_SHELL)
- Shell used to start the interactive environment. Defaults to the
- `bash` found in ``, falling back to the `bash` found in
- `PATH` if not found.
+ Shell used to start the interactive environment.
+ Defaults to the `bash` from `bashInteractive` found in ``, falling back to the `bash` found in `PATH` if not found.
+
+ > **Note**
+ >
+ > The shell obtained using this method may not necessarily be the same as any shells requested in *path*.
+
+
+
+ > **Example
+ >
+ > Despite `--pure`, this invocation will not result in a fully reproducible shell environment:
+ >
+ > ```nix
+ > #!/usr/bin/env -S nix-shell --pure
+ > let
+ > pkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/854fdc68881791812eddd33b2fed94b954979a8e.tar.gz") {};
+ > in
+ > pkgs.mkShell {
+ > buildInputs = pkgs.bashInteractive;
+ > }
+ > ```
{{#include ./env-common.md}}
diff --git a/doc/manual/source/installation/installing-docker.md b/doc/manual/source/installation/installing-docker.md
index 6f77d6a57..9354c1a72 100644
--- a/doc/manual/source/installation/installing-docker.md
+++ b/doc/manual/source/installation/installing-docker.md
@@ -57,3 +57,21 @@ $ nix build ./\#hydraJobs.dockerImage.x86_64-linux
$ docker load -i ./result/image.tar.gz
$ docker run -ti nix:2.5pre20211105
```
+
+# Docker image with non-root Nix
+
+If you would like to run Nix in a container under a user other than `root`,
+you can build an image with a non-root single-user installation of Nix
+by specifying the `uid`, `gid`, `uname`, and `gname` arguments to `docker.nix`:
+
+```console
+$ nix build --file docker.nix \
+ --arg uid 1000 \
+ --arg gid 1000 \
+ --argstr uname user \
+ --argstr gname user \
+ --argstr name nix-user \
+ --out-link nix-user.tar.gz
+$ docker load -i nix-user.tar.gz
+$ docker run -ti nix-user
+```
diff --git a/docker.nix b/docker.nix
index bd16b71cd..e2e9da728 100644
--- a/docker.nix
+++ b/docker.nix
@@ -9,6 +9,10 @@
, maxLayers ? 100
, nixConf ? {}
, flake-registry ? null
+, uid ? 0
+, gid ? 0
+, uname ? "root"
+, gname ? "root"
}:
let
defaultPkgs = with pkgs; [
@@ -50,6 +54,15 @@ let
description = "Unprivileged account (don't use!)";
};
+ } // lib.optionalAttrs (uid != 0) {
+ "${uname}" = {
+ uid = uid;
+ shell = "${pkgs.bashInteractive}/bin/bash";
+ home = "/home/${uname}";
+ gid = gid;
+ groups = [ "${gname}" ];
+ description = "Nix user";
+ };
} // lib.listToAttrs (
map
(
@@ -70,6 +83,8 @@ let
root.gid = 0;
nixbld.gid = 30000;
nobody.gid = 65534;
+ } // lib.optionalAttrs (gid != 0) {
+ "${gname}".gid = gid;
};
userToPasswd = (
@@ -150,6 +165,8 @@ let
in
"${n} = ${vStr}") (defaultNixConf // nixConf))) + "\n";
+ userHome = if uid == 0 then "/root" else "/home/${uname}";
+
baseSystem =
let
nixpkgs = pkgs.path;
@@ -237,26 +254,26 @@ let
mkdir -p $out/etc/nix
cat $nixConfContentsPath > $out/etc/nix/nix.conf
- mkdir -p $out/root
- mkdir -p $out/nix/var/nix/profiles/per-user/root
+ mkdir -p $out${userHome}
+ mkdir -p $out/nix/var/nix/profiles/per-user/${uname}
ln -s ${profile} $out/nix/var/nix/profiles/default-1-link
- ln -s $out/nix/var/nix/profiles/default-1-link $out/nix/var/nix/profiles/default
- ln -s /nix/var/nix/profiles/default $out/root/.nix-profile
+ ln -s /nix/var/nix/profiles/default-1-link $out/nix/var/nix/profiles/default
+ ln -s /nix/var/nix/profiles/default $out${userHome}/.nix-profile
- ln -s ${channel} $out/nix/var/nix/profiles/per-user/root/channels-1-link
- ln -s $out/nix/var/nix/profiles/per-user/root/channels-1-link $out/nix/var/nix/profiles/per-user/root/channels
+ ln -s ${channel} $out/nix/var/nix/profiles/per-user/${uname}/channels-1-link
+ ln -s /nix/var/nix/profiles/per-user/${uname}/channels-1-link $out/nix/var/nix/profiles/per-user/${uname}/channels
- mkdir -p $out/root/.nix-defexpr
- ln -s $out/nix/var/nix/profiles/per-user/root/channels $out/root/.nix-defexpr/channels
- echo "${channelURL} ${channelName}" > $out/root/.nix-channels
+ mkdir -p $out${userHome}/.nix-defexpr
+ ln -s /nix/var/nix/profiles/per-user/${uname}/channels $out${userHome}/.nix-defexpr/channels
+ echo "${channelURL} ${channelName}" > $out${userHome}/.nix-channels
mkdir -p $out/bin $out/usr/bin
ln -s ${pkgs.coreutils}/bin/env $out/usr/bin/env
ln -s ${pkgs.bashInteractive}/bin/bash $out/bin/sh
'' + (lib.optionalString (flake-registry-path != null) ''
- nixCacheDir="/root/.cache/nix"
+ nixCacheDir="${userHome}/.cache/nix"
mkdir -p $out$nixCacheDir
globalFlakeRegistryPath="$nixCacheDir/flake-registry.json"
ln -s ${flake-registry-path} $out$globalFlakeRegistryPath
@@ -268,7 +285,7 @@ let
in
pkgs.dockerTools.buildLayeredImageWithNixDb {
- inherit name tag maxLayers;
+ inherit name tag maxLayers uid gid uname gname;
contents = [ baseSystem ];
@@ -279,25 +296,28 @@ pkgs.dockerTools.buildLayeredImageWithNixDb {
fakeRootCommands = ''
chmod 1777 tmp
chmod 1777 var/tmp
+ chown -R ${toString uid}:${toString gid} .${userHome}
+ chown -R ${toString uid}:${toString gid} nix
'';
config = {
- Cmd = [ "/root/.nix-profile/bin/bash" ];
+ Cmd = [ "${userHome}/.nix-profile/bin/bash" ];
+ User = "${toString uid}:${toString gid}";
Env = [
- "USER=root"
+ "USER=${uname}"
"PATH=${lib.concatStringsSep ":" [
- "/root/.nix-profile/bin"
+ "${userHome}/.nix-profile/bin"
"/nix/var/nix/profiles/default/bin"
"/nix/var/nix/profiles/default/sbin"
]}"
"MANPATH=${lib.concatStringsSep ":" [
- "/root/.nix-profile/share/man"
+ "${userHome}/.nix-profile/share/man"
"/nix/var/nix/profiles/default/share/man"
]}"
"SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
"GIT_SSL_CAINFO=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
"NIX_SSL_CERT_FILE=/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"
- "NIX_PATH=/nix/var/nix/profiles/per-user/root/channels:/root/.nix-defexpr/channels"
+ "NIX_PATH=/nix/var/nix/profiles/per-user/${uname}/channels:${userHome}/.nix-defexpr/channels"
];
};
diff --git a/flake.nix b/flake.nix
index 06025e3b7..794736af4 100644
--- a/flake.nix
+++ b/flake.nix
@@ -124,18 +124,36 @@
# without "polluting" the top level "`pkgs`" attrset.
# This also has the benefit of providing us with a distinct set of packages
# we can iterate over.
- nixComponents = lib.makeScope final.nixDependencies.newScope (import ./packaging/components.nix {
- inherit (final) lib;
- inherit officialRelease;
- src = self;
- });
+ nixComponents =
+ lib.makeScopeWithSplicing'
+ {
+ inherit (final) splicePackages;
+ inherit (final.nixDependencies) newScope;
+ }
+ {
+ otherSplices = final.generateSplicesForMkScope "nixComponents";
+ f = import ./packaging/components.nix {
+ inherit (final) lib;
+ inherit officialRelease;
+ src = self;
+ };
+ };
# The dependencies are in their own scope, so that they don't have to be
# in Nixpkgs top level `pkgs` or `nixComponents`.
- nixDependencies = lib.makeScope final.newScope (import ./packaging/dependencies.nix {
- inherit inputs stdenv;
- pkgs = final;
- });
+ nixDependencies =
+ lib.makeScopeWithSplicing'
+ {
+ inherit (final) splicePackages;
+ inherit (final) newScope; # layered directly on pkgs, unlike nixComponents above
+ }
+ {
+ otherSplices = final.generateSplicesForMkScope "nixDependencies";
+ f = import ./packaging/dependencies.nix {
+ inherit inputs stdenv;
+ pkgs = final;
+ };
+ };
nix = final.nixComponents.nix-cli;
diff --git a/maintainers/flake-module.nix b/maintainers/flake-module.nix
index fdb031302..1d4e85c8c 100644
--- a/maintainers/flake-module.nix
+++ b/maintainers/flake-module.nix
@@ -496,7 +496,6 @@
''^scripts/create-darwin-volume\.sh$''
''^scripts/install-darwin-multi-user\.sh$''
''^scripts/install-multi-user\.sh$''
- ''^scripts/install-nix-from-closure\.sh$''
''^scripts/install-systemd-multi-user\.sh$''
''^src/nix/get-env\.sh$''
''^tests/functional/ca/build-dry\.sh$''
diff --git a/meson.build b/meson.build
index 8985b631e..49adf9832 100644
--- a/meson.build
+++ b/meson.build
@@ -34,6 +34,7 @@ endif
subproject('libutil-c')
subproject('libstore-c')
subproject('libexpr-c')
+subproject('libflake-c')
subproject('libmain-c')
# Language Bindings
diff --git a/packaging/components.nix b/packaging/components.nix
index c29e04ae9..e1f661be8 100644
--- a/packaging/components.nix
+++ b/packaging/components.nix
@@ -44,6 +44,7 @@ in
nix-expr-tests = callPackage ../src/libexpr-tests/package.nix { };
nix-flake = callPackage ../src/libflake/package.nix { };
+ nix-flake-c = callPackage ../src/libflake-c/package.nix { };
nix-flake-tests = callPackage ../src/libflake-tests/package.nix { };
nix-main = callPackage ../src/libmain/package.nix { };
diff --git a/packaging/dependencies.nix b/packaging/dependencies.nix
index 13766f2c0..a8005ce16 100644
--- a/packaging/dependencies.nix
+++ b/packaging/dependencies.nix
@@ -70,6 +70,9 @@ let
pkgs.buildPackages.meson
pkgs.buildPackages.ninja
] ++ prevAttrs.nativeBuildInputs or [];
+ mesonCheckFlags = prevAttrs.mesonCheckFlags or [] ++ [
+ "--print-errorlogs"
+ ];
};
mesonBuildLayer = finalAttrs: prevAttrs:
diff --git a/packaging/everything.nix b/packaging/everything.nix
index b09b9d2a9..0b04d2c6d 100644
--- a/packaging/everything.nix
+++ b/packaging/everything.nix
@@ -19,6 +19,7 @@
nix-expr-tests,
nix-flake,
+ nix-flake-c,
nix-flake-tests,
nix-main,
@@ -53,6 +54,7 @@ let
nix-expr-c
nix-fetchers
nix-flake
+ nix-flake-c
nix-main
nix-main-c
nix-store
@@ -86,6 +88,7 @@ let
"nix-expr-c"
"nix-fetchers"
"nix-flake"
+ "nix-flake-c"
"nix-main"
"nix-main-c"
"nix-store"
@@ -169,6 +172,7 @@ in
nix-expr
nix-expr-c
nix-flake
+ nix-flake-c
nix-main
nix-main-c
;
diff --git a/scripts/binary-tarball.nix b/scripts/binary-tarball.nix
index 104189b0c..671c8e96e 100644
--- a/scripts/binary-tarball.nix
+++ b/scripts/binary-tarball.nix
@@ -23,7 +23,7 @@ in
runCommand "nix-binary-tarball-${version}" env ''
cp ${installerClosureInfo}/registration $TMPDIR/reginfo
cp ${./create-darwin-volume.sh} $TMPDIR/create-darwin-volume.sh
- substitute ${./install-nix-from-closure.sh} $TMPDIR/install \
+ substitute ${./install-nix-from-tarball.sh} $TMPDIR/install \
--subst-var-by nix ${nix} \
--subst-var-by cacert ${cacert}
diff --git a/scripts/install-nix-from-closure.sh b/scripts/install-nix-from-tarball.sh
similarity index 96%
rename from scripts/install-nix-from-closure.sh
rename to scripts/install-nix-from-tarball.sh
index 794622530..007fe85ee 100644
--- a/scripts/install-nix-from-closure.sh
+++ b/scripts/install-nix-from-tarball.sh
@@ -48,15 +48,14 @@ case "$(uname -s)" in
INSTALL_MODE=no-daemon;;
esac
-# space-separated string
-ACTIONS=
+ACTION=
# handle the command line flags
while [ $# -gt 0 ]; do
case $1 in
--daemon)
INSTALL_MODE=daemon
- ACTIONS="${ACTIONS}install "
+ ACTION=install
;;
--no-daemon)
if [ "$(uname -s)" = "Darwin" ]; then
@@ -65,18 +64,14 @@ while [ $# -gt 0 ]; do
fi
INSTALL_MODE=no-daemon
# intentional tail space
- ACTIONS="${ACTIONS}install "
+ ACTION=install
;;
- # --uninstall)
- # # intentional tail space
- # ACTIONS="${ACTIONS}uninstall "
- # ;;
--yes)
export NIX_INSTALLER_YES=1;;
--no-channel-add)
export NIX_INSTALLER_NO_CHANNEL_ADD=1;;
--daemon-user-count)
- export NIX_USER_COUNT=$2
+ export NIX_USER_COUNT="$2"
shift;;
--no-modify-profile)
NIX_INSTALLER_NO_MODIFY_PROFILE=1;;
@@ -128,7 +123,7 @@ done
if [ "$INSTALL_MODE" = "daemon" ]; then
printf '\e[1;31mSwitching to the Multi-user Installer\e[0m\n'
- exec "$self/install-multi-user" $ACTIONS # let ACTIONS split
+ exec "$self/install-multi-user" $ACTION
exit 0
fi
diff --git a/src/external-api-docs/doxygen.cfg.in b/src/external-api-docs/doxygen.cfg.in
index 8e235dae5..3af2f5b81 100644
--- a/src/external-api-docs/doxygen.cfg.in
+++ b/src/external-api-docs/doxygen.cfg.in
@@ -40,6 +40,7 @@ GENERATE_LATEX = NO
INPUT = \
@src@/src/libutil-c \
@src@/src/libexpr-c \
+ @src@/src/libflake-c \
@src@/src/libstore-c \
@src@/src/external-api-docs/README.md
diff --git a/src/external-api-docs/package.nix b/src/external-api-docs/package.nix
index 0c592955a..57c5138cf 100644
--- a/src/external-api-docs/package.nix
+++ b/src/external-api-docs/package.nix
@@ -30,6 +30,7 @@ mkMesonDerivation (finalAttrs: {
# Source is not compiled, but still must be available for Doxygen
# to gather comments.
(cpp ../libexpr-c)
+ (cpp ../libflake-c)
(cpp ../libstore-c)
(cpp ../libutil-c)
];
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
index 1a4c76ec5..86d13fab7 100644
--- a/src/libcmd/command.cc
+++ b/src/libcmd/command.cc
@@ -179,30 +179,34 @@ BuiltPathsCommand::BuiltPathsCommand(bool recursive)
void BuiltPathsCommand::run(ref store, Installables && installables)
{
- BuiltPaths paths;
+ BuiltPaths rootPaths, allPaths;
+
if (all) {
if (installables.size())
throw UsageError("'--all' does not expect arguments");
// XXX: Only uses opaque paths, ignores all the realisations
for (auto & p : store->queryAllValidPaths())
- paths.emplace_back(BuiltPath::Opaque{p});
+ rootPaths.emplace_back(BuiltPath::Opaque{p});
+ allPaths = rootPaths;
} else {
- paths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
+ rootPaths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
+ allPaths = rootPaths;
+
if (recursive) {
// XXX: This only computes the store path closure, ignoring
// intermediate realisations
StorePathSet pathsRoots, pathsClosure;
- for (auto & root : paths) {
+ for (auto & root : rootPaths) {
auto rootFromThis = root.outPaths();
pathsRoots.insert(rootFromThis.begin(), rootFromThis.end());
}
store->computeFSClosure(pathsRoots, pathsClosure);
for (auto & path : pathsClosure)
- paths.emplace_back(BuiltPath::Opaque{path});
+ allPaths.emplace_back(BuiltPath::Opaque{path});
}
}
- run(store, std::move(paths));
+ run(store, std::move(allPaths), std::move(rootPaths));
}
StorePathsCommand::StorePathsCommand(bool recursive)
@@ -210,10 +214,10 @@ StorePathsCommand::StorePathsCommand(bool recursive)
{
}
-void StorePathsCommand::run(ref store, BuiltPaths && paths)
+void StorePathsCommand::run(ref store, BuiltPaths && allPaths, BuiltPaths && rootPaths)
{
StorePathSet storePaths;
- for (auto & builtPath : paths)
+ for (auto & builtPath : allPaths)
for (auto & p : builtPath.outPaths())
storePaths.insert(p);
@@ -245,7 +249,7 @@ void MixProfile::updateProfile(const StorePath & storePath)
{
if (!profile)
return;
- auto store = getStore().dynamic_pointer_cast();
+ auto store = getDstStore().dynamic_pointer_cast();
if (!store)
throw Error("'--profile' is not supported for this Nix store");
auto profile2 = absPath(*profile);
@@ -365,4 +369,31 @@ void MixEnvironment::setEnviron()
return;
}
+void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & buildables, LocalFSStore & store)
+{
+ for (const auto & [_i, buildable] : enumerate(buildables)) {
+ auto i = _i;
+ std::visit(
+ overloaded{
+ [&](const BuiltPath::Opaque & bo) {
+ auto symlink = outLink;
+ if (i)
+ symlink += fmt("-%d", i);
+ store.addPermRoot(bo.path, absPath(symlink.string()));
+ },
+ [&](const BuiltPath::Built & bfd) {
+ for (auto & output : bfd.outputs) {
+ auto symlink = outLink;
+ if (i)
+ symlink += fmt("-%d", i);
+ if (output.first != "out")
+ symlink += fmt("-%s", output.first);
+ store.addPermRoot(output.second, absPath(symlink.string()));
+ }
+ },
+ },
+ buildable.raw());
+ }
+}
+
}
diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh
index 8da4327c2..23529848f 100644
--- a/src/libcmd/command.hh
+++ b/src/libcmd/command.hh
@@ -18,6 +18,7 @@ extern char ** savedArgv;
class EvalState;
struct Pos;
class Store;
+class LocalFSStore;
static constexpr Command::Category catHelp = -1;
static constexpr Command::Category catSecondary = 100;
@@ -46,7 +47,20 @@ struct StoreCommand : virtual Command
{
StoreCommand();
void run() override;
+
+ /**
+ * Return the default Nix store.
+ */
ref getStore();
+
+ /**
+ * Return the destination Nix store.
+ */
+ virtual ref getDstStore()
+ {
+ return getStore();
+ }
+
virtual ref createStore();
/**
* Main entry point, with a `Store` provided
@@ -69,7 +83,7 @@ struct CopyCommand : virtual StoreCommand
ref createStore() override;
- ref getDstStore();
+ ref getDstStore() override;
};
/**
@@ -239,7 +253,7 @@ public:
BuiltPathsCommand(bool recursive = false);
- virtual void run(ref store, BuiltPaths && paths) = 0;
+ virtual void run(ref store, BuiltPaths && allPaths, BuiltPaths && rootPaths) = 0;
void run(ref store, Installables && installables) override;
@@ -252,7 +266,7 @@ struct StorePathsCommand : public BuiltPathsCommand
virtual void run(ref store, StorePaths && storePaths) = 0;
- void run(ref store, BuiltPaths && paths) override;
+ void run(ref store, BuiltPaths && allPaths, BuiltPaths && rootPaths) override;
};
/**
@@ -354,4 +368,10 @@ std::string showVersions(const std::set & versions);
void printClosureDiff(
ref store, const StorePath & beforePath, const StorePath & afterPath, std::string_view indent);
+/**
+ * Create symlinks prefixed by `outLink` to the store paths in
+ * `buildables`.
+ */
+void createOutLinks(const std::filesystem::path & outLink, const BuiltPaths & buildables, LocalFSStore & store);
+
}
diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc
index ccbf957d9..de967e3fe 100644
--- a/src/libcmd/common-eval-args.cc
+++ b/src/libcmd/common-eval-args.cc
@@ -29,13 +29,13 @@ EvalSettings evalSettings {
{
{
"flake",
- [](ref store, std::string_view rest) {
+ [](EvalState & state, std::string_view rest) {
experimentalFeatureSettings.require(Xp::Flakes);
// FIXME `parseFlakeRef` should take a `std::string_view`.
auto flakeRef = parseFlakeRef(fetchSettings, std::string { rest }, {}, true, false);
debug("fetching flake search path element '%s''", rest);
- auto storePath = flakeRef.resolve(store).fetchTree(store).first;
- return store->toRealPath(storePath);
+ auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first;
+ return state.rootPath(state.store->toRealPath(storePath));
},
},
},
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index 227bb64ed..250cd1413 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -858,7 +858,7 @@ std::vector RawInstallablesCommand::getFlakeRefsForCompletion()
applyDefaultInstallables(rawInstallables);
std::vector res;
res.reserve(rawInstallables.size());
- for (auto i : rawInstallables)
+ for (const auto & i : rawInstallables)
res.push_back(parseFlakeRefWithFragment(
fetchSettings,
expandTilde(i),
@@ -918,4 +918,12 @@ void BuiltPathsCommand::applyDefaultInstallables(std::vector & rawI
rawInstallables.push_back(".");
}
+BuiltPaths toBuiltPaths(const std::vector & builtPathsWithResult)
+{
+ BuiltPaths res;
+ for (auto & i : builtPathsWithResult)
+ res.push_back(i.path);
+ return res;
+}
+
}
diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
index bf5759230..c995c3019 100644
--- a/src/libcmd/installables.hh
+++ b/src/libcmd/installables.hh
@@ -86,6 +86,8 @@ struct BuiltPathWithResult
std::optional result;
};
+BuiltPaths toBuiltPaths(const std::vector & builtPathsWithResult);
+
/**
* Shorthand, for less typing and helping us keep the choice of
* collection in sync.
diff --git a/src/libcmd/meson.build b/src/libcmd/meson.build
index c484cf998..1f27c1614 100644
--- a/src/libcmd/meson.build
+++ b/src/libcmd/meson.build
@@ -30,8 +30,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json
@@ -72,7 +70,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'built-path.cc',
diff --git a/src/libexpr-c/meson.build b/src/libexpr-c/meson.build
index 4160f0d5a..5bcca29e0 100644
--- a/src/libexpr-c/meson.build
+++ b/src/libexpr-c/meson.build
@@ -29,8 +29,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
# TODO rename, because it will conflict with downstream projects
configdata.set_quoted('PACKAGE_VERSION', meson.project_version())
@@ -55,7 +53,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'nix_api_expr.cc',
diff --git a/src/libexpr-c/nix_api_expr.cc b/src/libexpr-c/nix_api_expr.cc
index 333e99460..a024248cd 100644
--- a/src/libexpr-c/nix_api_expr.cc
+++ b/src/libexpr-c/nix_api_expr.cc
@@ -6,6 +6,7 @@
#include "eval-gc.hh"
#include "globals.hh"
#include "eval-settings.hh"
+#include "ref.hh"
#include "nix_api_expr.h"
#include "nix_api_expr_internal.h"
@@ -18,6 +19,29 @@
# include
#endif
+/**
+ * @brief Allocate and initialize using self-reference
+ *
+ * This allows a brace initializer to reference the object being constructed.
+ *
+ * @warning Use with care, as the pointer points to an object that is not fully constructed yet.
+ *
+ * @tparam T Type to allocate
+ * @tparam F A function type for `init`, taking a T* and returning the initializer for T
+ * @param init Function that takes a T* and returns the initializer for T
+ * @return Pointer to allocated and initialized object
+ */
+template
+static T * unsafe_new_with_self(F && init)
+{
+ // Allocate
+ void * p = ::operator new(
+ sizeof(T),
+ static_cast(alignof(T)));
+ // Initialize with placement new
+ return new (p) T(init(static_cast(p)));
+}
+
nix_err nix_libexpr_init(nix_c_context * context)
{
if (context)
@@ -67,7 +91,7 @@ nix_err nix_value_call_multi(nix_c_context * context, EvalState * state, nix_val
if (context)
context->last_err_code = NIX_OK;
try {
- state->state.callFunction(fn->value, nargs, (nix::Value * *)args, value->value, nix::noPos);
+ state->state.callFunction(fn->value, {(nix::Value * *) args, nargs}, value->value, nix::noPos);
state->state.forceValue(value->value, nix::noPos);
}
NIXC_CATCH_ERRS
@@ -93,7 +117,42 @@ nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_val
NIXC_CATCH_ERRS
}
-EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c, Store * store)
+nix_eval_state_builder * nix_eval_state_builder_new(nix_c_context * context, Store * store)
+{
+ if (context)
+ context->last_err_code = NIX_OK;
+ try {
+ return unsafe_new_with_self([&](auto * self) {
+ return nix_eval_state_builder{
+ .store = nix::ref(store->ptr),
+ .settings = nix::EvalSettings{/* &bool */ self->readOnlyMode},
+ .fetchSettings = nix::fetchers::Settings{},
+ .readOnlyMode = true,
+ };
+ });
+ }
+ NIXC_CATCH_ERRS_NULL
+}
+
+void nix_eval_state_builder_free(nix_eval_state_builder * builder)
+{
+ delete builder;
+}
+
+nix_err nix_eval_state_builder_load(nix_c_context * context, nix_eval_state_builder * builder)
+{
+ if (context)
+ context->last_err_code = NIX_OK;
+ try {
+ // TODO: load in one go?
+ builder->settings.readOnlyMode = nix::settings.readOnlyMode;
+ loadConfFile(builder->settings);
+ loadConfFile(builder->fetchSettings);
+ }
+ NIXC_CATCH_ERRS
+}
+
+nix_err nix_eval_state_builder_set_lookup_path(nix_c_context * context, nix_eval_state_builder * builder, const char ** lookupPath_c)
{
if (context)
context->last_err_code = NIX_OK;
@@ -102,28 +161,47 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c
if (lookupPath_c != nullptr)
for (size_t i = 0; lookupPath_c[i] != nullptr; i++)
lookupPath.push_back(lookupPath_c[i]);
+ builder->lookupPath = nix::LookupPath::parse(lookupPath);
+ }
+ NIXC_CATCH_ERRS
+}
- void * p = ::operator new(
- sizeof(EvalState),
- static_cast(alignof(EvalState)));
- auto * p2 = static_cast(p);
- new (p) EvalState {
- .fetchSettings = nix::fetchers::Settings{},
- .settings = nix::EvalSettings{
- nix::settings.readOnlyMode,
- },
- .state = nix::EvalState(
- nix::LookupPath::parse(lookupPath),
- store->ptr,
- p2->fetchSettings,
- p2->settings),
- };
- loadConfFile(p2->settings);
- return p2;
+EvalState * nix_eval_state_build(nix_c_context * context, nix_eval_state_builder * builder)
+{
+ if (context)
+ context->last_err_code = NIX_OK;
+ try {
+ return unsafe_new_with_self([&](auto * self) {
+ return EvalState{
+ .fetchSettings = std::move(builder->fetchSettings),
+ .settings = std::move(builder->settings),
+ .state = nix::EvalState(
+ builder->lookupPath,
+ builder->store,
+ self->fetchSettings,
+ self->settings),
+ };
+ });
}
NIXC_CATCH_ERRS_NULL
}
+EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c, Store * store)
+{
+ auto builder = nix_eval_state_builder_new(context, store);
+ if (builder == nullptr)
+ return nullptr;
+
+ if (nix_eval_state_builder_load(context, builder) != NIX_OK)
+ return nullptr;
+
+ if (nix_eval_state_builder_set_lookup_path(context, builder, lookupPath_c)
+ != NIX_OK)
+ return nullptr;
+
+ return nix_eval_state_build(context, builder);
+}
+
void nix_state_free(EvalState * state)
{
delete state;
diff --git a/src/libexpr-c/nix_api_expr.h b/src/libexpr-c/nix_api_expr.h
index e680f5ff1..f8d181452 100644
--- a/src/libexpr-c/nix_api_expr.h
+++ b/src/libexpr-c/nix_api_expr.h
@@ -30,6 +30,11 @@ extern "C" {
// cffi start
// Type definitions
+/**
+ * @brief Builder for EvalState
+ */
+typedef struct nix_eval_state_builder nix_eval_state_builder;
+
/**
* @brief Represents a state of the Nix language evaluator.
*
@@ -174,12 +179,70 @@ nix_err nix_value_force(nix_c_context * context, EvalState * state, nix_value *
nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_value * value);
/**
- * @brief Create a new Nix language evaluator state.
+ * @brief Create a new nix_eval_state_builder
+ *
+ * The settings are initialized to their default value.
+ * Values can be sourced elsewhere with nix_eval_state_builder_load.
+ *
+ * @param[out] context Optional, stores error information
+ * @param[in] store The Nix store to use.
+ * @return A new nix_eval_state_builder or NULL on failure.
+ */
+nix_eval_state_builder * nix_eval_state_builder_new(nix_c_context * context, Store * store);
+
+/**
+ * @brief Read settings from the ambient environment
+ *
+ * Settings are sourced from environment variables and configuration files,
+ * as documented in the Nix manual.
+ *
+ * @param[out] context Optional, stores error information
+ * @param[out] builder The builder to modify.
+ * @return NIX_OK if successful, an error code otherwise.
+ */
+nix_err nix_eval_state_builder_load(nix_c_context * context, nix_eval_state_builder * builder);
+
+/**
+ * @brief Set the lookup path for `<...>` expressions
+ *
+ * @param[in] context Optional, stores error information
+ * @param[in] builder The builder to modify.
+ * @param[in] lookupPath Null-terminated array of strings corresponding to entries in NIX_PATH.
+ */
+nix_err nix_eval_state_builder_set_lookup_path(
+ nix_c_context * context, nix_eval_state_builder * builder, const char ** lookupPath);
+
+/**
+ * @brief Create a new Nix language evaluator state
+ *
+ * Remember to nix_eval_state_builder_free after building the state.
+ *
+ * @param[out] context Optional, stores error information
+ * @param[in] builder The builder to use and free
+ * @return A new Nix state or NULL on failure.
+ * @see nix_eval_state_builder_new, nix_eval_state_builder_free
+ */
+EvalState * nix_eval_state_build(nix_c_context * context, nix_eval_state_builder * builder);
+
+/**
+ * @brief Free a nix_eval_state_builder
+ *
+ * Does not fail.
+ *
+ * @param[in] builder The builder to free.
+ */
+void nix_eval_state_builder_free(nix_eval_state_builder * builder);
+
+/**
+ * @brief Create a new Nix language evaluator state
+ *
+ * For more control, use nix_eval_state_builder
*
* @param[out] context Optional, stores error information
* @param[in] lookupPath Null-terminated array of strings corresponding to entries in NIX_PATH.
* @param[in] store The Nix store to use.
* @return A new Nix state or NULL on failure.
+ * @see nix_state_builder_new
*/
EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath, Store * store);
diff --git a/src/libexpr-c/nix_api_expr_internal.h b/src/libexpr-c/nix_api_expr_internal.h
index 12f24b6eb..f59664011 100644
--- a/src/libexpr-c/nix_api_expr_internal.h
+++ b/src/libexpr-c/nix_api_expr_internal.h
@@ -6,6 +6,17 @@
#include "eval-settings.hh"
#include "attr-set.hh"
#include "nix_api_value.h"
+#include "search-path.hh"
+
+struct nix_eval_state_builder
+{
+ nix::ref store;
+ nix::EvalSettings settings;
+ nix::fetchers::Settings fetchSettings;
+ nix::LookupPath lookupPath;
+ // TODO: make an EvalSettings setting own this instead?
+ bool readOnlyMode;
+};
struct EvalState
{
diff --git a/src/libexpr-c/nix_api_value.h b/src/libexpr-c/nix_api_value.h
index 8a0813ebe..711b0adbc 100644
--- a/src/libexpr-c/nix_api_value.h
+++ b/src/libexpr-c/nix_api_value.h
@@ -213,7 +213,7 @@ nix_get_string(nix_c_context * context, const nix_value * value, nix_get_string_
/** @brief Get path as string
* @param[out] context Optional, stores error information
* @param[in] value Nix value to inspect
- * @return string
+ * @return string, if the type is NIX_TYPE_PATH
* @return NULL in case of error.
*/
const char * nix_get_path_string(nix_c_context * context, const nix_value * value);
diff --git a/src/libexpr-test-support/meson.build b/src/libexpr-test-support/meson.build
index b9e7f390d..33d9e17a6 100644
--- a/src/libexpr-test-support/meson.build
+++ b/src/libexpr-test-support/meson.build
@@ -24,11 +24,10 @@ deps_public_maybe_subproject = [
dependency('nix-store'),
dependency('nix-store-test-support'),
dependency('nix-expr'),
+ dependency('nix-expr-c'),
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
rapidcheck = dependency('rapidcheck')
deps_public += rapidcheck
@@ -41,7 +40,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'tests/value/context.cc',
diff --git a/src/libexpr-test-support/package.nix b/src/libexpr-test-support/package.nix
index bcf6118e0..7e92d145f 100644
--- a/src/libexpr-test-support/package.nix
+++ b/src/libexpr-test-support/package.nix
@@ -4,6 +4,7 @@
, nix-store-test-support
, nix-expr
+, nix-expr-c
, rapidcheck
@@ -35,6 +36,7 @@ mkMesonLibrary (finalAttrs: {
propagatedBuildInputs = [
nix-store-test-support
nix-expr
+ nix-expr-c
rapidcheck
];
diff --git a/src/libexpr-test-support/tests/libexpr.hh b/src/libexpr-test-support/tests/libexpr.hh
index 045607e87..095ea1d0e 100644
--- a/src/libexpr-test-support/tests/libexpr.hh
+++ b/src/libexpr-test-support/tests/libexpr.hh
@@ -40,6 +40,12 @@ namespace nix {
return v;
}
+ Value * maybeThunk(std::string input, bool forceValue = true) {
+ Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root));
+ assert(e);
+ return e->maybeThunk(state, state.baseEnv);
+ }
+
Symbol createSymbol(const char * value) {
return state.symbols.create(value);
}
diff --git a/src/libexpr-tests/eval.cc b/src/libexpr-tests/eval.cc
index 93d3f658f..61f6be0db 100644
--- a/src/libexpr-tests/eval.cc
+++ b/src/libexpr-tests/eval.cc
@@ -138,4 +138,27 @@ TEST(nix_isAllowedURI, non_scheme_colon) {
ASSERT_FALSE(isAllowedURI("https://foo/bar:baz", allowed));
}
-} // namespace nix
\ No newline at end of file
+class EvalStateTest : public LibExprTest {};
+
+TEST_F(EvalStateTest, getBuiltins_ok) {
+ auto evaled = maybeThunk("builtins");
+ auto & builtins = state.getBuiltins();
+ ASSERT_TRUE(builtins.type() == nAttrs);
+ ASSERT_EQ(evaled, &builtins);
+}
+
+TEST_F(EvalStateTest, getBuiltin_ok) {
+ auto & builtin = state.getBuiltin("toString");
+ ASSERT_TRUE(builtin.type() == nFunction);
+ // FIXME
+ // auto evaled = maybeThunk("builtins.toString");
+ // ASSERT_EQ(evaled, &builtin);
+ auto & builtin2 = state.getBuiltin("true");
+ ASSERT_EQ(state.forceBool(builtin2, noPos, "in unit test"), true);
+}
+
+TEST_F(EvalStateTest, getBuiltin_fail) {
+ ASSERT_THROW(state.getBuiltin("nonexistent"), EvalError);
+}
+
+} // namespace nix
diff --git a/src/libexpr-tests/meson.build b/src/libexpr-tests/meson.build
index 5a5c9f1d4..b50c18c9c 100644
--- a/src/libexpr-tests/meson.build
+++ b/src/libexpr-tests/meson.build
@@ -25,8 +25,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
subdir('build-utils-meson/export-all-symbols')
subdir('build-utils-meson/windows-version')
@@ -51,7 +49,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'derived-path.cc',
diff --git a/src/libexpr-tests/nix_api_expr.cc b/src/libexpr-tests/nix_api_expr.cc
index b37ac44b3..5ed78d2fc 100644
--- a/src/libexpr-tests/nix_api_expr.cc
+++ b/src/libexpr-tests/nix_api_expr.cc
@@ -7,12 +7,49 @@
#include "tests/nix_api_expr.hh"
#include "tests/string_callback.hh"
+#include "file-system.hh"
#include
#include
namespace nixC {
+TEST_F(nix_api_store_test, nix_eval_state_lookup_path)
+{
+ auto tmpDir = nix::createTempDir();
+ auto delTmpDir = std::make_unique(tmpDir, true);
+ auto nixpkgs = tmpDir + "/pkgs";
+ auto nixos = tmpDir + "/cfg";
+ std::filesystem::create_directories(nixpkgs);
+ std::filesystem::create_directories(nixos);
+
+ std::string nixpkgsEntry = "nixpkgs=" + nixpkgs;
+ std::string nixosEntry = "nixos-config=" + nixos;
+ const char * lookupPath[] = {nixpkgsEntry.c_str(), nixosEntry.c_str(), nullptr};
+
+ auto builder = nix_eval_state_builder_new(ctx, store);
+ assert_ctx_ok();
+
+ ASSERT_EQ(NIX_OK, nix_eval_state_builder_set_lookup_path(ctx, builder, lookupPath));
+ assert_ctx_ok();
+
+ auto state = nix_eval_state_build(ctx, builder);
+ assert_ctx_ok();
+
+ nix_eval_state_builder_free(builder);
+
+ Value * value = nix_alloc_value(ctx, state);
+ nix_expr_eval_from_string(ctx, state, "builtins.seq ", ".", value);
+ assert_ctx_ok();
+
+ ASSERT_EQ(nix_get_type(ctx, value), NIX_TYPE_PATH);
+ assert_ctx_ok();
+
+ auto pathStr = nix_get_path_string(ctx, value);
+ assert_ctx_ok();
+ ASSERT_EQ(0, strcmp(pathStr, nixpkgs.c_str()));
+}
+
TEST_F(nix_api_expr_test, nix_expr_eval_from_string)
{
nix_expr_eval_from_string(nullptr, state, "builtins.nixVersion", ".", value);
diff --git a/src/libexpr-tests/trivial.cc b/src/libexpr-tests/trivial.cc
index e455a571b..d77b4d53b 100644
--- a/src/libexpr-tests/trivial.cc
+++ b/src/libexpr-tests/trivial.cc
@@ -177,6 +177,57 @@ namespace nix {
)
);
+// The following macros ultimately define 48 tests (16 variations on three
+// templates). Each template tests an expression that can be written in 2^4
+// different ways, by making four choices about whether to write a particular
+// attribute path segment as `x.y = ...;` (collapsed) or `x = { y = ...; };`
+// (expanded).
+//
+// The nestedAttrsetMergeXXXX tests check that the expression
+// `{ a.b.c = 1; a.b.d = 2; }` has the same value regardless of how it is
+// expanded. (That exact expression is exercised in test
+// nestedAttrsetMerge0000, because it is fully collapsed. The test
+// nestedAttrsetMerge1001 would instead examine
+// `{ a = { b.c = 1; }; a.b = { d = 2; }; }`.)
+//
+// The nestedAttrsetMergeDupXXXX tests check that the expression
+// `{ a.b.c = 1; a.b.c = 2; }` throws a duplicate attribute error, again
+// regardless of how it is expanded.
+//
+// The nestedAttrsetMergeLetXXXX tests check that the expression
+// `let a.b.c = 1; a.b.d = 2; in a` has the same value regardless of how it is
+// expanded.
+#define X_EXPAND_IF0(k, v) k "." v
+#define X_EXPAND_IF1(k, v) k " = { " v " };"
+#define X4(w, x, y, z) \
+ TEST_F(TrivialExpressionTest, nestedAttrsetMerge##w##x##y##z) { \
+ auto v = eval("{ a.b = { c = 1; d = 2; }; } == { " \
+ X_EXPAND_IF##w("a", X_EXPAND_IF##x("b", "c = 1;")) " " \
+ X_EXPAND_IF##y("a", X_EXPAND_IF##z("b", "d = 2;")) " }"); \
+ ASSERT_THAT(v, IsTrue()); \
+ }; \
+ TEST_F(TrivialExpressionTest, nestedAttrsetMergeDup##w##x##y##z) { \
+ ASSERT_THROW(eval("{ " \
+ X_EXPAND_IF##w("a", X_EXPAND_IF##x("b", "c = 1;")) " " \
+ X_EXPAND_IF##y("a", X_EXPAND_IF##z("b", "c = 2;")) " }"), Error); \
+ }; \
+ TEST_F(TrivialExpressionTest, nestedAttrsetMergeLet##w##x##y##z) { \
+ auto v = eval("{ b = { c = 1; d = 2; }; } == (let " \
+ X_EXPAND_IF##w("a", X_EXPAND_IF##x("b", "c = 1;")) " " \
+ X_EXPAND_IF##y("a", X_EXPAND_IF##z("b", "d = 2;")) " in a)"); \
+ ASSERT_THAT(v, IsTrue()); \
+ };
+#define X3(...) X4(__VA_ARGS__, 0) X4(__VA_ARGS__, 1)
+#define X2(...) X3(__VA_ARGS__, 0) X3(__VA_ARGS__, 1)
+#define X1(...) X2(__VA_ARGS__, 0) X2(__VA_ARGS__, 1)
+ X1(0) X1(1)
+#undef X_EXPAND_IF0
+#undef X_EXPAND_IF1
+#undef X1
+#undef X2
+#undef X3
+#undef X4
+
TEST_F(TrivialExpressionTest, functor) {
auto v = eval("{ __functor = self: arg: self.v + arg; v = 10; } 5");
ASSERT_THAT(v, IsIntEq(15));
diff --git a/src/libexpr/attr-path.cc b/src/libexpr/attr-path.cc
index 2f67260c5..822ec7620 100644
--- a/src/libexpr/attr-path.cc
+++ b/src/libexpr/attr-path.cc
@@ -129,7 +129,6 @@ std::pair findPackageFilename(EvalState & state, Value & v
try {
auto colon = fn.rfind(':');
if (colon == std::string::npos) fail();
- std::string filename(fn, 0, colon);
auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos));
return {SourcePath{path.accessor, CanonPath(fn.substr(0, colon))}, lineno};
} catch (std::invalid_argument & e) {
diff --git a/src/libexpr/eval-inline.hh b/src/libexpr/eval-inline.hh
index d5ce238b2..631c0f396 100644
--- a/src/libexpr/eval-inline.hh
+++ b/src/libexpr/eval-inline.hh
@@ -87,11 +87,15 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
{
if (v.isThunk()) {
Env * env = v.payload.thunk.env;
+ assert(env || v.isBlackhole());
Expr * expr = v.payload.thunk.expr;
try {
v.mkBlackhole();
//checkInterrupt();
- expr->eval(*this, *env, v);
+ if (env) [[likely]]
+ expr->eval(*this, *env, v);
+ else
+ ExprBlackHole::throwInfiniteRecursionError(*this, v);
} catch (...) {
v.mkThunk(env, expr);
tryFixupBlackHolePos(v, pos);
diff --git a/src/libexpr/eval-settings.hh b/src/libexpr/eval-settings.hh
index 115e3ee50..a8fcce539 100644
--- a/src/libexpr/eval-settings.hh
+++ b/src/libexpr/eval-settings.hh
@@ -3,10 +3,11 @@
#include "config.hh"
#include "ref.hh"
+#include "source-path.hh"
namespace nix {
-class Store;
+class EvalState;
struct EvalSettings : Config
{
@@ -18,11 +19,8 @@ struct EvalSettings : Config
*
* The return value is (a) whether the entry was valid, and, if so,
* what does it map to.
- *
- * @todo Return (`std::optional` of) `SourceAccssor` or something
- * more structured instead of mere `std::string`?
*/
- using LookupPathHook = std::optional(ref store, std::string_view);
+ using LookupPathHook = std::optional(EvalState & state, std::string_view);
/**
* Map from "scheme" to a `LookupPathHook`.
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index e21f70553..05f58957e 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -448,7 +448,7 @@ void EvalState::addConstant(const std::string & name, Value * v, Constant info)
/* Install value the base environment. */
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
- baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(name2), v));
+ getBuiltins().payload.attrs->push_back(Attr(symbols.create(name2), v));
}
}
@@ -516,16 +516,26 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
else {
staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v;
- baseEnv.values[0]->payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
+ getBuiltins().payload.attrs->push_back(Attr(symbols.create(primOp.name), v));
}
return v;
}
+Value & EvalState::getBuiltins()
+{
+ return *baseEnv.values[0];
+}
+
+
Value & EvalState::getBuiltin(const std::string & name)
{
- return *baseEnv.values[0]->attrs()->find(symbols.create(name))->value;
+ auto it = getBuiltins().attrs()->get(symbols.create(name));
+ if (it)
+ return *it->value;
+ else
+ error("builtin '%1%' not found", name).debugThrow();
}
@@ -588,14 +598,14 @@ std::optional EvalState::getDoc(Value & v)
if (isFunctor(v)) {
try {
Value & functor = *v.attrs()->find(sFunctor)->value;
- Value * vp = &v;
+ Value * vp[] = {&v};
Value partiallyApplied;
// The first paramater is not user-provided, and may be
// handled by code that is opaque to the user, like lib.const = x: y: y;
// So preferably we show docs that are relevant to the
// "partially applied" function returned by e.g. `const`.
// We apply the first argument:
- callFunction(functor, 1, &vp, partiallyApplied, noPos);
+ callFunction(functor, vp, partiallyApplied, noPos);
auto _level = addCallDepth(noPos);
return getDoc(partiallyApplied);
}
@@ -1460,7 +1470,7 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
v.mkLambda(&env, this);
}
-void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
+void EvalState::callFunction(Value & fun, std::span args, Value & vRes, const PosIdx pos)
{
auto _level = addCallDepth(pos);
@@ -1475,16 +1485,16 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
auto makeAppChain = [&]()
{
vRes = vCur;
- for (size_t i = 0; i < nrArgs; ++i) {
+ for (auto arg : args) {
auto fun2 = allocValue();
*fun2 = vRes;
- vRes.mkPrimOpApp(fun2, args[i]);
+ vRes.mkPrimOpApp(fun2, arg);
}
};
const Attr * functor;
- while (nrArgs > 0) {
+ while (args.size() > 0) {
if (vCur.isLambda()) {
@@ -1587,15 +1597,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
throw;
}
- nrArgs--;
- args += 1;
+ args = args.subspan(1);
}
else if (vCur.isPrimOp()) {
size_t argsLeft = vCur.primOp()->arity;
- if (nrArgs < argsLeft) {
+ if (args.size() < argsLeft) {
/* We don't have enough arguments, so create a tPrimOpApp chain. */
makeAppChain();
return;
@@ -1607,15 +1616,14 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (countCalls) primOpCalls[fn->name]++;
try {
- fn->fun(*this, vCur.determinePos(noPos), args, vCur);
+ fn->fun(*this, vCur.determinePos(noPos), args.data(), vCur);
} catch (Error & e) {
if (fn->addTrace)
addErrorTrace(e, pos, "while calling the '%1%' builtin", fn->name);
throw;
}
- nrArgs -= argsLeft;
- args += argsLeft;
+ args = args.subspan(argsLeft);
}
}
@@ -1631,7 +1639,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
auto arity = primOp->primOp()->arity;
auto argsLeft = arity - argsDone;
- if (nrArgs < argsLeft) {
+ if (args.size() < argsLeft) {
/* We still don't have enough arguments, so extend the tPrimOpApp chain. */
makeAppChain();
return;
@@ -1663,8 +1671,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
throw;
}
- nrArgs -= argsLeft;
- args += argsLeft;
+ args = args.subspan(argsLeft);
}
}
@@ -1675,13 +1682,12 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
Value * args2[] = {allocValue(), args[0]};
*args2[0] = vCur;
try {
- callFunction(*functor->value, 2, args2, vCur, functor->pos);
+ callFunction(*functor->value, args2, vCur, functor->pos);
} catch (Error & e) {
e.addTrace(positions[pos], "while calling a functor (an attribute set with a '__functor' attribute)");
throw;
}
- nrArgs--;
- args++;
+ args = args.subspan(1);
}
else
@@ -1724,7 +1730,7 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v)
for (size_t i = 0; i < args.size(); ++i)
vArgs[i] = args[i]->maybeThunk(state, env);
- state.callFunction(vFun, args.size(), vArgs.data(), v, pos);
+ state.callFunction(vFun, vArgs, v, pos);
}
@@ -2046,9 +2052,12 @@ void ExprPos::eval(EvalState & state, Env & env, Value & v)
state.mkPos(v, pos);
}
-
-void ExprBlackHole::eval(EvalState & state, Env & env, Value & v)
+void ExprBlackHole::eval(EvalState & state, [[maybe_unused]] Env & env, Value & v)
{
+ throwInfiniteRecursionError(state, v);
+}
+
+[[gnu::noinline]] [[noreturn]] void ExprBlackHole::throwInfiniteRecursionError(EvalState & state, Value &v) {
state.error("infinite recursion encountered")
.atPos(v.determinePos(noPos))
.debugThrow();
@@ -3029,8 +3038,8 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
if (!rOpt) continue;
auto r = *rOpt;
- Path res = suffix == "" ? r : concatStrings(r, "/", suffix);
- if (pathExists(res)) return rootPath(CanonPath(canonPath(res)));
+ auto res = (r / CanonPath(suffix)).resolveSymlinks();
+ if (res.pathExists()) return res;
}
if (hasPrefix(path, "nix/"))
@@ -3045,13 +3054,13 @@ SourcePath EvalState::findFile(const LookupPath & lookupPath, const std::string_
}
-std::optional EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
+std::optional EvalState::resolveLookupPathPath(const LookupPath::Path & value0, bool initAccessControl)
{
auto & value = value0.s;
auto i = lookupPathResolved.find(value);
if (i != lookupPathResolved.end()) return i->second;
- auto finish = [&](std::string res) {
+ auto finish = [&](SourcePath res) {
debug("resolved search path element '%s' to '%s'", value, res);
lookupPathResolved.emplace(value, res);
return res;
@@ -3064,7 +3073,7 @@ std::optional EvalState::resolveLookupPathPath(const LookupPath::Pa
fetchSettings,
EvalSettings::resolvePseudoUrl(value));
auto storePath = fetchToStore(*store, SourcePath(accessor), FetchMode::Copy);
- return finish(store->toRealPath(storePath));
+ return finish(rootPath(store->toRealPath(storePath)));
} catch (Error & e) {
logWarning({
.msg = HintFmt("Nix search path entry '%1%' cannot be downloaded, ignoring", value)
@@ -3076,29 +3085,29 @@ std::optional EvalState::resolveLookupPathPath(const LookupPath::Pa
auto scheme = value.substr(0, colPos);
auto rest = value.substr(colPos + 1);
if (auto * hook = get(settings.lookupPathHooks, scheme)) {
- auto res = (*hook)(store, rest);
+ auto res = (*hook)(*this, rest);
if (res)
return finish(std::move(*res));
}
}
{
- auto path = absPath(value);
+ auto path = rootPath(value);
/* Allow access to paths in the search path. */
if (initAccessControl) {
- allowPath(path);
- if (store->isInStore(path)) {
+ allowPath(path.path.abs());
+ if (store->isInStore(path.path.abs())) {
try {
StorePathSet closure;
- store->computeFSClosure(store->toStorePath(path).first, closure);
+ store->computeFSClosure(store->toStorePath(path.path.abs()).first, closure);
for (auto & p : closure)
allowPath(p);
} catch (InvalidPath &) { }
}
}
- if (pathExists(path))
+ if (path.pathExists())
return finish(std::move(path));
else {
logWarning({
@@ -3109,7 +3118,6 @@ std::optional EvalState::resolveLookupPathPath(const LookupPath::Pa
debug("failed to resolve search path element '%s'", value);
return std::nullopt;
-
}
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index a1882dded..3ac3c8a8a 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -347,7 +347,7 @@ private:
LookupPath lookupPath;
- std::map> lookupPathResolved;
+ std::map> lookupPathResolved;
/**
* Cache used by prim_match().
@@ -452,9 +452,9 @@ public:
*
* If the specified search path element is a URI, download it.
*
- * If it is not found, return `std::nullopt`
+ * If it is not found, return `std::nullopt`.
*/
- std::optional resolveLookupPathPath(
+ std::optional resolveLookupPathPath(
const LookupPath::Path & elem,
bool initAccessControl = false);
@@ -623,8 +623,19 @@ private:
public:
+ /**
+ * Retrieve a specific builtin, equivalent to evaluating `builtins.${name}`.
+ * @param name The attribute name of the builtin to retrieve.
+ * @throws EvalError if the builtin does not exist.
+ */
Value & getBuiltin(const std::string & name);
+ /**
+ * Retrieve the `builtins` attrset, equivalent to evaluating the reference `builtins`.
+ * Always returns an attribute set value.
+ */
+ Value & getBuiltins();
+
struct Doc
{
Pos pos;
@@ -690,13 +701,12 @@ public:
bool isFunctor(Value & fun);
- // FIXME: use std::span
- void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos);
+ void callFunction(Value & fun, std::span args, Value & vRes, const PosIdx pos);
void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
{
Value * args[] = {&arg};
- callFunction(fun, 1, args, vRes, pos);
+ callFunction(fun, args, vRes, pos);
}
/**
@@ -809,7 +819,6 @@ public:
bool callPathFilter(
Value * filterFun,
const SourcePath & path,
- std::string_view pathArg,
PosIdx pos);
DocComment getDocCommentForPos(PosIdx pos);
diff --git a/src/libexpr/meson.build b/src/libexpr/meson.build
index 4d8a38b43..28318579e 100644
--- a/src/libexpr/meson.build
+++ b/src/libexpr/meson.build
@@ -27,8 +27,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
boost = dependency(
'boost',
modules : ['container', 'context'],
@@ -79,7 +77,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
parser_tab = custom_target(
input : 'parser.y',
diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh
index 948839bd9..2950ff1fd 100644
--- a/src/libexpr/nixexpr.hh
+++ b/src/libexpr/nixexpr.hh
@@ -468,6 +468,7 @@ struct ExprBlackHole : Expr
void show(const SymbolTable & symbols, std::ostream & str) const override {}
void eval(EvalState & state, Env & env, Value & v) override;
void bindVars(EvalState & es, const std::shared_ptr & env) override {}
+ [[noreturn]] static void throwInfiniteRecursionError(EvalState & state, Value & v);
};
extern ExprBlackHole eBlackHole;
diff --git a/src/libexpr/parser-state.hh b/src/libexpr/parser-state.hh
index 8ad0d9ad7..21a880e8e 100644
--- a/src/libexpr/parser-state.hh
+++ b/src/libexpr/parser-state.hh
@@ -88,6 +88,7 @@ struct ParserState
void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos);
void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos);
void addAttr(ExprAttrs * attrs, AttrPath && attrPath, const ParserLocation & loc, Expr * e, const ParserLocation & exprLoc);
+ void addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symbol, ExprAttrs::AttrDef && def);
Formals * validateFormals(Formals * formals, PosIdx pos = noPos, Symbol arg = {});
Expr * stripIndentation(const PosIdx pos,
std::vector>> && es);
@@ -120,64 +121,29 @@ inline void ParserState::addAttr(ExprAttrs * attrs, AttrPath && attrPath, const
// Checking attrPath validity.
// ===========================
for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
+ ExprAttrs * nested;
if (i->symbol) {
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
if (j != attrs->attrs.end()) {
- if (j->second.kind != ExprAttrs::AttrDef::Kind::Inherited) {
- ExprAttrs * attrs2 = dynamic_cast(j->second.e);
- if (!attrs2) dupAttr(attrPath, pos, j->second.pos);
- attrs = attrs2;
- } else
+ nested = dynamic_cast(j->second.e);
+ if (!nested) {
+ attrPath.erase(i + 1, attrPath.end());
dupAttr(attrPath, pos, j->second.pos);
+ }
} else {
- ExprAttrs * nested = new ExprAttrs;
+ nested = new ExprAttrs;
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos);
- attrs = nested;
}
} else {
- ExprAttrs *nested = new ExprAttrs;
+ nested = new ExprAttrs;
attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, nested, pos));
- attrs = nested;
}
+ attrs = nested;
}
// Expr insertion.
// ==========================
if (i->symbol) {
- ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
- if (j != attrs->attrs.end()) {
- // This attr path is already defined. However, if both
- // e and the expr pointed by the attr path are two attribute sets,
- // we want to merge them.
- // Otherwise, throw an error.
- auto ae = dynamic_cast(e);
- auto jAttrs = dynamic_cast(j->second.e);
- if (jAttrs && ae) {
- if (ae->inheritFromExprs && !jAttrs->inheritFromExprs)
- jAttrs->inheritFromExprs = std::make_unique>();
- for (auto & ad : ae->attrs) {
- auto j2 = jAttrs->attrs.find(ad.first);
- if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error.
- dupAttr(ad.first, j2->second.pos, ad.second.pos);
- jAttrs->attrs.emplace(ad.first, ad.second);
- if (ad.second.kind == ExprAttrs::AttrDef::Kind::InheritedFrom) {
- auto & sel = dynamic_cast(*ad.second.e);
- auto & from = dynamic_cast(*sel.e);
- from.displ += jAttrs->inheritFromExprs->size();
- }
- }
- jAttrs->dynamicAttrs.insert(jAttrs->dynamicAttrs.end(), ae->dynamicAttrs.begin(), ae->dynamicAttrs.end());
- if (ae->inheritFromExprs) {
- jAttrs->inheritFromExprs->insert(jAttrs->inheritFromExprs->end(),
- ae->inheritFromExprs->begin(), ae->inheritFromExprs->end());
- }
- } else {
- dupAttr(attrPath, pos, j->second.pos);
- }
- } else {
- // This attr path is not defined. Let's create it.
- attrs->attrs.emplace(i->symbol, ExprAttrs::AttrDef(e, pos));
- e->setName(i->symbol);
- }
+ addAttr(attrs, attrPath, i->symbol, ExprAttrs::AttrDef(e, pos));
} else {
attrs->dynamicAttrs.push_back(ExprAttrs::DynamicAttrDef(i->expr, e, pos));
}
@@ -189,6 +155,60 @@ inline void ParserState::addAttr(ExprAttrs * attrs, AttrPath && attrPath, const
}
}
+/**
+ * Precondition: attrPath is used for error messages and should already contain
+ * symbol as its last element.
+ */
+inline void ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symbol, ExprAttrs::AttrDef && def)
+{
+ ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(symbol);
+ if (j != attrs->attrs.end()) {
+ // This attr path is already defined. However, if both
+ // e and the expr pointed by the attr path are two attribute sets,
+ // we want to merge them.
+ // Otherwise, throw an error.
+ auto ae = dynamic_cast(def.e);
+ auto jAttrs = dynamic_cast(j->second.e);
+
+ // N.B. In a world in which we are less bound by our past mistakes, we
+ // would also test that jAttrs and ae are not recursive. The effect of
+ // not doing so is that any `rec` marker on ae is discarded, and any
+ // `rec` marker on jAttrs will apply to the attributes in ae.
+ // See https://github.com/NixOS/nix/issues/9020.
+ if (jAttrs && ae) {
+ if (ae->inheritFromExprs && !jAttrs->inheritFromExprs)
+ jAttrs->inheritFromExprs = std::make_unique>();
+ for (auto & ad : ae->attrs) {
+ if (ad.second.kind == ExprAttrs::AttrDef::Kind::InheritedFrom) {
+ auto & sel = dynamic_cast(*ad.second.e);
+ auto & from = dynamic_cast(*sel.e);
+ from.displ += jAttrs->inheritFromExprs->size();
+ }
+ attrPath.emplace_back(AttrName(ad.first));
+ addAttr(jAttrs, attrPath, ad.first, std::move(ad.second));
+ attrPath.pop_back();
+ }
+ ae->attrs.clear();
+ jAttrs->dynamicAttrs.insert(jAttrs->dynamicAttrs.end(),
+ std::make_move_iterator(ae->dynamicAttrs.begin()),
+ std::make_move_iterator(ae->dynamicAttrs.end()));
+ ae->dynamicAttrs.clear();
+ if (ae->inheritFromExprs) {
+ jAttrs->inheritFromExprs->insert(jAttrs->inheritFromExprs->end(),
+ std::make_move_iterator(ae->inheritFromExprs->begin()),
+ std::make_move_iterator(ae->inheritFromExprs->end()));
+ ae->inheritFromExprs = nullptr;
+ }
+ } else {
+ dupAttr(attrPath, def.pos, j->second.pos);
+ }
+ } else {
+ // This attr path is not defined. Let's create it.
+ attrs->attrs.emplace(symbol, def);
+ def.e->setName(symbol);
+ }
+}
+
inline Formals * ParserState::validateFormals(Formals * formals, PosIdx pos, Symbol arg)
{
std::sort(formals->formals.begin(), formals->formals.end(),
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 45d9f86ac..7e13e945c 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -66,14 +66,12 @@ StringMap EvalState::realiseContext(const NixStringContext & context, StorePathS
ensureValid(b.drvPath->getBaseStorePath());
},
[&](const NixStringContextElem::Opaque & o) {
- auto ctxS = store->printStorePath(o.path);
ensureValid(o.path);
if (maybePathsOut)
maybePathsOut->emplace(o.path);
},
[&](const NixStringContextElem::DrvDeep & d) {
/* Treat same as Opaque */
- auto ctxS = store->printStorePath(d.drvPath);
ensureValid(d.drvPath);
if (maybePathsOut)
maybePathsOut->emplace(d.drvPath);
@@ -724,7 +722,7 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
/* Call the `operator' function with `e' as argument. */
Value newElements;
- state.callFunction(*op->value, 1, &e, newElements, noPos);
+ state.callFunction(*op->value, {&e, 1}, newElements, noPos);
state.forceList(newElements, noPos, "while evaluating the return value of the `operator` passed to builtins.genericClosure");
/* Add the values returned by the operator to the work set. */
@@ -1603,7 +1601,8 @@ static RegisterPrimOp primop_placeholder({
*************************************************************/
-/* Convert the argument to a path. !!! obsolete? */
+/* Convert the argument to a path and then to a string (confusing,
+ eh?). !!! obsolete? */
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
NixStringContext context;
@@ -2437,7 +2436,6 @@ static RegisterPrimOp primop_toFile({
bool EvalState::callPathFilter(
Value * filterFun,
const SourcePath & path,
- std::string_view pathArg,
PosIdx pos)
{
auto st = path.lstat();
@@ -2445,12 +2443,12 @@ bool EvalState::callPathFilter(
/* Call the filter function. The first argument is the path, the
second is a string indicating the type of the file. */
Value arg1;
- arg1.mkString(pathArg);
+ arg1.mkString(path.path.abs());
// assert that type is not "unknown"
Value * args []{&arg1, fileTypeToString(*this, st.type)};
Value res;
- callFunction(*filterFun, 2, args, res, pos);
+ callFunction(*filterFun, args, res, pos);
return forceBool(res, pos, "while evaluating the return value of the path filter function");
}
@@ -2488,7 +2486,7 @@ static void addPath(
if (filterFun)
filter = std::make_unique([&](const Path & p) {
auto p2 = CanonPath(p);
- return state.callPathFilter(filterFun, {path.accessor, p2}, p2.abs(), pos);
+ return state.callPathFilter(filterFun, {path.accessor, p2}, pos);
});
std::optional expectedStorePath;
@@ -2614,13 +2612,13 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `sha256` attribute passed to builtins.path"), HashAlgorithm::SHA256);
else
state.error(
- "unsupported argument '%1%' to 'addPath'",
+ "unsupported argument '%1%' to 'builtins.path'",
state.symbols[attr.name]
).atPos(attr.pos).debugThrow();
}
if (!path)
state.error(
- "missing required 'path' attribute in the first argument to builtins.path"
+ "missing required 'path' attribute in the first argument to 'builtins.path'"
).atPos(pos).debugThrow();
if (name.empty())
name = path->baseName();
@@ -3487,7 +3485,7 @@ static void prim_foldlStrict(EvalState & state, const PosIdx pos, Value * * args
for (auto [n, elem] : enumerate(args[2]->listItems())) {
Value * vs []{vCur, elem};
vCur = n == args[2]->listSize() - 1 ? &v : state.allocValue();
- state.callFunction(*args[0], 2, vs, *vCur, pos);
+ state.callFunction(*args[0], vs, *vCur, pos);
}
state.forceValue(v, pos);
} else {
@@ -3637,7 +3635,7 @@ static void prim_sort(EvalState & state, const PosIdx pos, Value * * args, Value
Value * vs[] = {a, b};
Value vBool;
- state.callFunction(*args[0], 2, vs, vBool, noPos);
+ state.callFunction(*args[0], vs, vBool, noPos);
return state.forceBool(vBool, pos, "while evaluating the return value of the sorting function passed to builtins.sort");
};
@@ -4385,7 +4383,7 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
for (auto i = begin; i != end; ++i) {
assert(idx <= 2 * len + 1 - 3);
- auto match = *i;
+ const auto & match = *i;
// Add a string for non-matched characters.
list[idx++] = mkString(state, match.prefix());
@@ -4937,7 +4935,7 @@ void EvalState::createBaseEnv()
/* Now that we've added all primops, sort the `builtins' set,
because attribute lookups expect it to be sorted. */
- baseEnv.values[0]->payload.attrs->sort();
+ getBuiltins().payload.attrs->sort();
staticBaseEnv->sort();
diff --git a/src/libexpr/primops/context.cc b/src/libexpr/primops/context.cc
index 02683b173..ede7d97ba 100644
--- a/src/libexpr/primops/context.cc
+++ b/src/libexpr/primops/context.cc
@@ -132,6 +132,8 @@ static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, V
},
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
/* Reuse original item because we want this to be idempotent. */
+ /* FIXME: Suspicious move out of const. This is actually a copy, so the comment
+ above does not make much sense. */
return std::move(c);
},
}, context.begin()->raw) }),
diff --git a/src/libexpr/primops/fetchClosure.cc b/src/libexpr/primops/fetchClosure.cc
index fc5bb3145..04b8d0595 100644
--- a/src/libexpr/primops/fetchClosure.cc
+++ b/src/libexpr/primops/fetchClosure.cc
@@ -40,7 +40,7 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
});
}
- auto toPath = *toPathMaybe;
+ const auto & toPath = *toPathMaybe;
// check and return
diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc
index d9e8eb4c0..34b978113 100644
--- a/src/libexpr/primops/fetchTree.cc
+++ b/src/libexpr/primops/fetchTree.cc
@@ -33,9 +33,8 @@ void emitTreeAttrs(
// FIXME: support arbitrary input attributes.
- auto narHash = input.getNarHash();
- assert(narHash);
- attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true));
+ if (auto narHash = input.getNarHash())
+ attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true));
if (input.getType() == "git")
attrs.alloc("submodules").mkBool(
diff --git a/src/libfetchers-tests/meson.build b/src/libfetchers-tests/meson.build
index d948dbad6..fdab6ba6c 100644
--- a/src/libfetchers-tests/meson.build
+++ b/src/libfetchers-tests/meson.build
@@ -24,8 +24,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
subdir('build-utils-meson/export-all-symbols')
subdir('build-utils-meson/windows-version')
@@ -44,7 +42,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'public-key.cc',
diff --git a/src/libfetchers/cache.cc b/src/libfetchers/cache.cc
index b0b6cb887..6c2241f3a 100644
--- a/src/libfetchers/cache.cc
+++ b/src/libfetchers/cache.cc
@@ -36,7 +36,7 @@ struct CacheImpl : Cache
{
auto state(_state.lock());
- auto dbPath = getCacheDir() + "/fetcher-cache-v2.sqlite";
+ auto dbPath = getCacheDir() + "/fetcher-cache-v3.sqlite";
createDirs(dirOf(dbPath));
state->db = SQLite(dbPath);
diff --git a/src/libfetchers/fetch-to-store.cc b/src/libfetchers/fetch-to-store.cc
index 65aa72a6c..fe347a59d 100644
--- a/src/libfetchers/fetch-to-store.cc
+++ b/src/libfetchers/fetch-to-store.cc
@@ -44,6 +44,8 @@ StorePath fetchToStore(
: store.addToStore(
name, path, method, HashAlgorithm::SHA256, {}, filter2, repair);
+ debug(mode == FetchMode::DryRun ? "hashed '%s'" : "copied '%s' to '%s'", path, store.printStorePath(storePath));
+
if (cacheKey && mode == FetchMode::Copy)
fetchers::getCache()->upsert(*cacheKey, store, {}, storePath);
diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc
index 35d785849..6704ace9f 100644
--- a/src/libfetchers/git.cc
+++ b/src/libfetchers/git.cc
@@ -522,8 +522,6 @@ struct GitInputScheme : InputScheme
auto origRev = input.getRev();
- std::string name = input.getName();
-
auto originalRef = input.getRef();
auto ref = originalRef ? *originalRef : getDefaultRef(repoInfo);
input.attrs.insert_or_assign("ref", ref);
diff --git a/src/libfetchers/meson.build b/src/libfetchers/meson.build
index 2d8df4be9..73295e561 100644
--- a/src/libfetchers/meson.build
+++ b/src/libfetchers/meson.build
@@ -26,8 +26,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json
@@ -46,7 +44,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'attrs.cc',
@@ -55,15 +53,15 @@ sources = files(
'fetch-to-store.cc',
'fetchers.cc',
'filtering-source-accessor.cc',
- 'git.cc',
'git-utils.cc',
+ 'git.cc',
'github.cc',
'indirect.cc',
'mercurial.cc',
'mounted-source-accessor.cc',
'path.cc',
- 'store-path-accessor.cc',
'registry.cc',
+ 'store-path-accessor.cc',
'tarball.cc',
)
@@ -74,11 +72,11 @@ headers = files(
'cache.hh',
'fetch-settings.hh',
'fetch-to-store.hh',
+ 'fetchers.hh',
'filtering-source-accessor.hh',
'git-utils.hh',
'git-lfs-fetch.hh',
'mounted-source-accessor.hh',
- 'fetchers.hh',
'registry.hh',
'store-path-accessor.hh',
'tarball.hh',
diff --git a/src/libfetchers/registry.cc b/src/libfetchers/registry.cc
index 7f7a09053..c761028ab 100644
--- a/src/libfetchers/registry.cc
+++ b/src/libfetchers/registry.cc
@@ -94,12 +94,9 @@ void Registry::add(
void Registry::remove(const Input & input)
{
- // FIXME: use C++20 std::erase.
- for (auto i = entries.begin(); i != entries.end(); )
- if (i->from == input)
- i = entries.erase(i);
- else
- ++i;
+ entries.erase(
+ std::remove_if(entries.begin(), entries.end(), [&](const Entry & entry) { return entry.from == input; }),
+ entries.end());
}
static Path getSystemRegistryPath()
diff --git a/src/libflake-c/.version b/src/libflake-c/.version
new file mode 120000
index 000000000..b7badcd0c
--- /dev/null
+++ b/src/libflake-c/.version
@@ -0,0 +1 @@
+../../.version
\ No newline at end of file
diff --git a/src/libflake-c/build-utils-meson b/src/libflake-c/build-utils-meson
new file mode 120000
index 000000000..91937f183
--- /dev/null
+++ b/src/libflake-c/build-utils-meson
@@ -0,0 +1 @@
+../../build-utils-meson/
\ No newline at end of file
diff --git a/src/libflake-c/meson.build b/src/libflake-c/meson.build
new file mode 100644
index 000000000..00d9650e7
--- /dev/null
+++ b/src/libflake-c/meson.build
@@ -0,0 +1,93 @@
+project('nix-flake-c', 'cpp',
+ version : files('.version'),
+ default_options : [
+ 'cpp_std=c++2a',
+ # TODO(Qyriad): increase the warning level
+ 'warning_level=1',
+ 'debug=true',
+ 'optimization=2',
+ 'errorlogs=true', # Please print logs for tests that fail
+ ],
+ meson_version : '>= 1.1',
+ license : 'LGPL-2.1-or-later',
+)
+
+cxx = meson.get_compiler('cpp')
+
+subdir('build-utils-meson/deps-lists')
+
+configdata = configuration_data()
+
+deps_private_maybe_subproject = [
+ dependency('nix-util'),
+ dependency('nix-store'),
+ dependency('nix-expr'),
+ dependency('nix-flake'),
+]
+deps_public_maybe_subproject = [
+ dependency('nix-util-c'),
+ dependency('nix-store-c'),
+ dependency('nix-expr-c'),
+]
+subdir('build-utils-meson/subprojects')
+
+# TODO rename, because it will conflict with downstream projects
+configdata.set_quoted('PACKAGE_VERSION', meson.project_version())
+
+config_h = configure_file(
+ configuration : configdata,
+ output : 'config-flake.h',
+)
+
+add_project_arguments(
+ # TODO(Qyriad): Yes this is how the autoconf+Make system did it.
+ # It would be nice for our headers to be idempotent instead.
+
+ # From C++ libraries, only for internals
+ '-include', 'config-util.hh',
+ '-include', 'config-store.hh',
+ '-include', 'config-expr.hh',
+ # not generated (yet?)
+ # '-include', 'config-flake.hh',
+
+ # From C libraries, for our public, installed headers too
+ '-include', 'config-util.h',
+ '-include', 'config-store.h',
+ '-include', 'config-expr.h',
+ '-include', 'config-flake.h',
+ language : 'cpp',
+)
+
+subdir('build-utils-meson/common')
+
+sources = files(
+ 'nix_api_flake.cc',
+)
+
+include_dirs = [include_directories('.')]
+
+headers = [config_h] + files(
+ 'nix_api_flake.h',
+)
+
+# TODO move this header to libexpr, maybe don't use it in tests?
+headers += files('nix_api_flake.h')
+
+subdir('build-utils-meson/export-all-symbols')
+subdir('build-utils-meson/windows-version')
+
+this_library = library(
+ 'nixflakec',
+ sources,
+ dependencies : deps_public + deps_private + deps_other,
+ include_directories : include_dirs,
+ link_args: linker_export_flags,
+ prelink : true, # For C++ static initializers
+ install : true,
+)
+
+install_headers(headers, subdir : 'nix', preserve_path : true)
+
+libraries_private = []
+
+subdir('build-utils-meson/export')
diff --git a/src/libflake-c/nix_api_flake.cc b/src/libflake-c/nix_api_flake.cc
new file mode 100644
index 000000000..17cf6572d
--- /dev/null
+++ b/src/libflake-c/nix_api_flake.cc
@@ -0,0 +1,32 @@
+#include "nix_api_flake.h"
+#include "nix_api_flake_internal.hh"
+#include "nix_api_util_internal.h"
+
+#include "flake/flake.hh"
+
+nix_flake_settings * nix_flake_settings_new(nix_c_context * context)
+{
+ try {
+ auto settings = nix::make_ref();
+ return new nix_flake_settings{settings};
+ }
+ NIXC_CATCH_ERRS_NULL
+}
+
+void nix_flake_settings_free(nix_flake_settings * settings)
+{
+ delete settings;
+}
+
+nix_err nix_flake_init_global(nix_c_context * context, nix_flake_settings * settings)
+{
+ static std::shared_ptr registeredSettings;
+ try {
+ if (registeredSettings)
+ throw nix::Error("nix_flake_init_global already initialized");
+
+ registeredSettings = settings->settings;
+ nix::flake::initLib(*registeredSettings);
+ }
+ NIXC_CATCH_ERRS
+}
diff --git a/src/libflake-c/nix_api_flake.h b/src/libflake-c/nix_api_flake.h
new file mode 100644
index 000000000..80051298d
--- /dev/null
+++ b/src/libflake-c/nix_api_flake.h
@@ -0,0 +1,46 @@
+#ifndef NIX_API_FLAKE_H
+#define NIX_API_FLAKE_H
+/** @defgroup libflake libflake
+ * @brief Bindings to the Nix Flakes library
+ *
+ * @{
+ */
+/** @file
+ * @brief Main entry for the libflake C bindings
+ */
+
+#include "nix_api_store.h"
+#include "nix_api_util.h"
+#include "nix_api_expr.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+// cffi start
+
+typedef struct nix_flake_settings nix_flake_settings;
+
+// Function prototypes
+/**
+ * Create a nix_flake_settings initialized with default values.
+ * @param[out] context Optional, stores error information
+ * @return A new nix_flake_settings or NULL on failure.
+ * @see nix_flake_settings_free
+ */
+nix_flake_settings * nix_flake_settings_new(nix_c_context * context);
+
+/**
+ * @brief Release the resources associated with a nix_flake_settings.
+ */
+void nix_flake_settings_free(nix_flake_settings * settings);
+
+/**
+ * @brief Register Flakes support process-wide.
+ */
+nix_err nix_flake_init_global(nix_c_context * context, nix_flake_settings * settings);
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif
diff --git a/src/libflake-c/nix_api_flake_internal.hh b/src/libflake-c/nix_api_flake_internal.hh
new file mode 100644
index 000000000..4c154a342
--- /dev/null
+++ b/src/libflake-c/nix_api_flake_internal.hh
@@ -0,0 +1,9 @@
+#pragma once
+
+#include "ref.hh"
+#include "flake/settings.hh"
+
+struct nix_flake_settings
+{
+ nix::ref settings;
+};
diff --git a/src/libflake-c/package.nix b/src/libflake-c/package.nix
new file mode 100644
index 000000000..a70cbf94e
--- /dev/null
+++ b/src/libflake-c/package.nix
@@ -0,0 +1,60 @@
+{ lib
+, stdenv
+, mkMesonLibrary
+
+, nix-store-c
+, nix-expr-c
+, nix-flake
+
+# Configuration Options
+
+, version
+}:
+
+let
+ inherit (lib) fileset;
+in
+
+mkMesonLibrary (finalAttrs: {
+ pname = "nix-flake-c";
+ inherit version;
+
+ workDir = ./.;
+ fileset = fileset.unions [
+ ../../build-utils-meson
+ ./build-utils-meson
+ ../../.version
+ ./.version
+ ./meson.build
+ # ./meson.options
+ (fileset.fileFilter (file: file.hasExt "cc") ./.)
+ (fileset.fileFilter (file: file.hasExt "hh") ./.)
+ (fileset.fileFilter (file: file.hasExt "h") ./.)
+ ];
+
+ propagatedBuildInputs = [
+ nix-expr-c
+ nix-store-c
+ nix-flake
+ ];
+
+ preConfigure =
+ # "Inline" .version so it's not a symlink, and includes the suffix.
+ # Do the meson utils, without modification.
+ ''
+ chmod u+w ./.version
+ echo ${version} > ../../.version
+ '';
+
+ mesonFlags = [
+ ];
+
+ env = lib.optionalAttrs (stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux")) {
+ LDFLAGS = "-fuse-ld=gold";
+ };
+
+ meta = {
+ platforms = lib.platforms.unix ++ lib.platforms.windows;
+ };
+
+})
diff --git a/src/libflake-tests/meson.build b/src/libflake-tests/meson.build
index 592a7493b..c494c414e 100644
--- a/src/libflake-tests/meson.build
+++ b/src/libflake-tests/meson.build
@@ -19,13 +19,12 @@ subdir('build-utils-meson/deps-lists')
deps_private_maybe_subproject = [
dependency('nix-expr-test-support'),
dependency('nix-flake'),
+ dependency('nix-flake-c'),
]
deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
subdir('build-utils-meson/export-all-symbols')
subdir('build-utils-meson/windows-version')
@@ -44,10 +43,11 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'flakeref.cc',
+ 'nix_api_flake.cc',
'url-name.cc',
)
@@ -70,6 +70,7 @@ test(
this_exe,
env : {
'_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data',
+ 'NIX_CONFIG': 'extra-experimental-features = flakes',
},
protocol : 'gtest',
)
diff --git a/src/libflake-tests/nix_api_flake.cc b/src/libflake-tests/nix_api_flake.cc
new file mode 100644
index 000000000..21109d181
--- /dev/null
+++ b/src/libflake-tests/nix_api_flake.cc
@@ -0,0 +1,51 @@
+#include "nix_api_store.h"
+#include "nix_api_store_internal.h"
+#include "nix_api_util.h"
+#include "nix_api_util_internal.h"
+#include "nix_api_expr.h"
+#include "nix_api_value.h"
+#include "nix_api_flake.h"
+
+#include "tests/nix_api_expr.hh"
+#include "tests/string_callback.hh"
+
+#include
+#include
+
+namespace nixC {
+
+TEST_F(nix_api_store_test, nix_api_init_global_getFlake_exists)
+{
+ nix_libstore_init(ctx);
+ assert_ctx_ok();
+ nix_libexpr_init(ctx);
+ assert_ctx_ok();
+
+ auto settings = nix_flake_settings_new(ctx);
+ assert_ctx_ok();
+ ASSERT_NE(nullptr, settings);
+
+ nix_flake_init_global(ctx, settings);
+ assert_ctx_ok();
+
+ nix_eval_state_builder * builder = nix_eval_state_builder_new(ctx, store);
+ ASSERT_NE(nullptr, builder);
+ assert_ctx_ok();
+
+ auto state = nix_eval_state_build(ctx, builder);
+ assert_ctx_ok();
+ ASSERT_NE(nullptr, state);
+
+ nix_eval_state_builder_free(builder);
+
+ auto value = nix_alloc_value(ctx, state);
+ assert_ctx_ok();
+ ASSERT_NE(nullptr, value);
+
+ nix_err err = nix_expr_eval_from_string(ctx, state, "builtins.getFlake", ".", value);
+ assert_ctx_ok();
+ ASSERT_EQ(NIX_OK, err);
+ ASSERT_EQ(NIX_TYPE_FUNCTION, nix_get_type(ctx, value));
+}
+
+} // namespace nixC
diff --git a/src/libflake-tests/package.nix b/src/libflake-tests/package.nix
index 67e716979..b3a8ac466 100644
--- a/src/libflake-tests/package.nix
+++ b/src/libflake-tests/package.nix
@@ -4,6 +4,7 @@
, mkMesonExecutable
, nix-flake
+, nix-flake-c
, nix-expr-test-support
, rapidcheck
@@ -38,6 +39,7 @@ mkMesonExecutable (finalAttrs: {
buildInputs = [
nix-flake
+ nix-flake-c
nix-expr-test-support
rapidcheck
gtest
@@ -67,6 +69,7 @@ mkMesonExecutable (finalAttrs: {
mkdir -p "$HOME"
'' + ''
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
+ export NIX_CONFIG="extra-experimental-features = flakes"
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
touch $out
'');
diff --git a/src/libflake/flake/flake.cc b/src/libflake/flake/flake.cc
index edb76f861..19b622a34 100644
--- a/src/libflake/flake/flake.cc
+++ b/src/libflake/flake/flake.cc
@@ -816,7 +816,7 @@ void callFlake(EvalState & state,
assert(vFetchFinalTree);
Value * args[] = {vLocks, &vOverrides, *vFetchFinalTree};
- state.callFunction(*vCallFlake, 3, args, vRes, noPos);
+ state.callFunction(*vCallFlake, args, vRes, noPos);
}
void initLib(const Settings & settings)
diff --git a/src/libflake/flake/flakeref.cc b/src/libflake/flake/flakeref.cc
index 01fe747f9..9616fe0ea 100644
--- a/src/libflake/flake/flakeref.cc
+++ b/src/libflake/flake/flakeref.cc
@@ -67,6 +67,20 @@ std::optional maybeParseFlakeRef(
}
}
+static std::pair fromParsedURL(
+ const fetchers::Settings & fetchSettings,
+ ParsedURL && parsedURL,
+ bool isFlake)
+{
+ auto dir = getOr(parsedURL.query, "dir", "");
+ parsedURL.query.erase("dir");
+
+ std::string fragment;
+ std::swap(fragment, parsedURL.fragment);
+
+ return {FlakeRef(fetchers::Input::fromURL(fetchSettings, parsedURL, isFlake), dir), fragment};
+}
+
std::pair parsePathFlakeRefWithFragment(
const fetchers::Settings & fetchSettings,
const std::string & url,
@@ -89,7 +103,7 @@ std::pair parsePathFlakeRefWithFragment(
fragment = percentDecode(url.substr(fragmentStart+1));
}
if (pathEnd != std::string::npos && fragmentStart != std::string::npos && url[pathEnd] == '?') {
- query = decodeQuery(url.substr(pathEnd+1, fragmentStart-pathEnd-1));
+ query = decodeQuery(url.substr(pathEnd + 1, fragmentStart - pathEnd - 1));
}
if (baseDir) {
@@ -153,6 +167,7 @@ std::pair parsePathFlakeRefWithFragment(
.authority = "",
.path = flakeRoot,
.query = query,
+ .fragment = fragment,
};
if (subdir != "") {
@@ -164,9 +179,7 @@ std::pair parsePathFlakeRefWithFragment(
if (pathExists(flakeRoot + "/.git/shallow"))
parsedURL.query.insert_or_assign("shallow", "1");
- return std::make_pair(
- FlakeRef(fetchers::Input::fromURL(fetchSettings, parsedURL), getOr(parsedURL.query, "dir", "")),
- fragment);
+ return fromParsedURL(fetchSettings, std::move(parsedURL), isFlake);
}
subdir = std::string(baseNameOf(flakeRoot)) + (subdir.empty() ? "" : "/" + subdir);
@@ -185,11 +198,12 @@ std::pair parsePathFlakeRefWithFragment(
attrs.insert_or_assign("path", path);
return std::make_pair(FlakeRef(fetchers::Input::fromAttrs(fetchSettings, std::move(attrs)), ""), fragment);
-};
+}
-
-/* Check if 'url' is a flake ID. This is an abbreviated syntax for
- 'flake:?ref=[&rev='. */
+/**
+ * Check if `url` is a flake ID. This is an abbreviated syntax for
+ * `flake:?ref=][&rev=`.
+ */
static std::optional> parseFlakeIdRef(
const fetchers::Settings & fetchSettings,
const std::string & url,
@@ -227,22 +241,11 @@ std::optional> parseURLFlakeRef(
bool isFlake
)
{
- ParsedURL parsedURL;
try {
- parsedURL = parseURL(url);
+ return fromParsedURL(fetchSettings, parseURL(url), isFlake);
} catch (BadURL &) {
return std::nullopt;
}
-
- std::string fragment;
- std::swap(fragment, parsedURL.fragment);
-
- auto input = fetchers::Input::fromURL(fetchSettings, parsedURL, isFlake);
- input.parent = baseDir;
-
- return std::make_pair(
- FlakeRef(std::move(input), getOr(parsedURL.query, "dir", "")),
- fragment);
}
std::pair parseFlakeRefWithFragment(
@@ -254,8 +257,6 @@ std::pair parseFlakeRefWithFragment(
{
using namespace fetchers;
- std::smatch match;
-
if (auto res = parseFlakeIdRef(fetchSettings, url, isFlake)) {
return *res;
} else if (auto res = parseURLFlakeRef(fetchSettings, url, baseDir, isFlake)) {
diff --git a/src/libflake/meson.build b/src/libflake/meson.build
index d2bb179df..2c1a70a18 100644
--- a/src/libflake/meson.build
+++ b/src/libflake/meson.build
@@ -26,8 +26,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
deps_public += nlohmann_json
@@ -41,7 +39,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'flake/config.cc',
diff --git a/src/libmain-c/meson.build b/src/libmain-c/meson.build
index 0ec0e3f6d..3cb1e4baa 100644
--- a/src/libmain-c/meson.build
+++ b/src/libmain-c/meson.build
@@ -29,8 +29,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
# TODO rename, because it will conflict with downstream projects
configdata.set_quoted('PACKAGE_VERSION', meson.project_version())
@@ -55,7 +53,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'nix_api_main.cc',
diff --git a/src/libmain/meson.build b/src/libmain/meson.build
index 7fcadf06d..6c6298e2b 100644
--- a/src/libmain/meson.build
+++ b/src/libmain/meson.build
@@ -26,8 +26,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
pubsetbuf_test = '''
#include
@@ -60,7 +58,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'common-args.cc',
diff --git a/src/libstore-c/meson.build b/src/libstore-c/meson.build
index d4f86eeff..44b5fe11d 100644
--- a/src/libstore-c/meson.build
+++ b/src/libstore-c/meson.build
@@ -27,8 +27,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
# TODO rename, because it will conflict with downstream projects
configdata.set_quoted('PACKAGE_VERSION', meson.project_version())
@@ -51,7 +49,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'nix_api_store.cc',
diff --git a/src/libstore-test-support/meson.build b/src/libstore-test-support/meson.build
index 98ec9882e..1f230914f 100644
--- a/src/libstore-test-support/meson.build
+++ b/src/libstore-test-support/meson.build
@@ -22,11 +22,10 @@ deps_public_maybe_subproject = [
dependency('nix-util'),
dependency('nix-util-test-support'),
dependency('nix-store'),
+ dependency('nix-store-c'),
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
rapidcheck = dependency('rapidcheck')
deps_public += rapidcheck
@@ -38,7 +37,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'tests/derived-path.cc',
diff --git a/src/libstore-test-support/package.nix b/src/libstore-test-support/package.nix
index 48f8b5e6b..2543049fe 100644
--- a/src/libstore-test-support/package.nix
+++ b/src/libstore-test-support/package.nix
@@ -4,6 +4,7 @@
, nix-util-test-support
, nix-store
+, nix-store-c
, rapidcheck
@@ -35,6 +36,7 @@ mkMesonLibrary (finalAttrs: {
propagatedBuildInputs = [
nix-util-test-support
nix-store
+ nix-store-c
rapidcheck
];
diff --git a/src/libstore-tests/meson.build b/src/libstore-tests/meson.build
index f4f67d73a..fc9152f2f 100644
--- a/src/libstore-tests/meson.build
+++ b/src/libstore-tests/meson.build
@@ -25,8 +25,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
subdir('build-utils-meson/export-all-symbols')
subdir('build-utils-meson/windows-version')
@@ -52,7 +50,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'common-protocol.cc',
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 34ed16a38..bf1a25db1 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -991,7 +991,10 @@ Goal::Co DerivationGoal::buildDone()
auto nixLogCommand = experimentalFeatureSettings.isEnabled(Xp::NixCommand)
? "nix log"
: "nix-store -l";
- msg += fmt("For full logs, run '" ANSI_BOLD "%s %s" ANSI_NORMAL "'.",
+ // The command is on a separate line for easy copying, such as with triple click.
+ // This message will be indented elsewhere, so removing the indentation before the
+ // command will not put it at the start of the line unfortunately.
+ msg += fmt("For full logs, run:\n " ANSI_BOLD "%s %s" ANSI_NORMAL,
nixLogCommand,
worker.store.printStorePath(drvPath));
}
@@ -1226,7 +1229,7 @@ HookReply DerivationGoal::tryBuildHook()
hook->toHook.writeSide.close();
/* Create the log file and pipe. */
- Path logFile = openLogFile();
+ [[maybe_unused]] Path logFile = openLogFile();
std::set fds;
fds.insert(hook->fromHook.readSide.get());
diff --git a/src/libstore/build/drv-output-substitution-goal.cc b/src/libstore/build/drv-output-substitution-goal.cc
index dedcad2b1..f069c0d94 100644
--- a/src/libstore/build/drv-output-substitution-goal.cc
+++ b/src/libstore/build/drv-output-substitution-goal.cc
@@ -32,7 +32,7 @@ Goal::Co DrvOutputSubstitutionGoal::init()
bool substituterFailed = false;
- for (auto sub : subs) {
+ for (const auto & sub : subs) {
trace("trying next substituter");
/* The callback of the curl download below can outlive `this` (if
diff --git a/src/libstore/build/substitution-goal.cc b/src/libstore/build/substitution-goal.cc
index 315500719..983c86601 100644
--- a/src/libstore/build/substitution-goal.cc
+++ b/src/libstore/build/substitution-goal.cc
@@ -57,7 +57,7 @@ Goal::Co PathSubstitutionGoal::init()
bool substituterFailed = false;
- for (auto sub : subs) {
+ for (const auto & sub : subs) {
trace("trying next substituter");
cleanup();
diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc
index 9b6f67852..1f37b0c38 100644
--- a/src/libstore/derivations.cc
+++ b/src/libstore/derivations.cc
@@ -1017,29 +1017,31 @@ std::string hashPlaceholder(const OutputNameView outputName)
return "/" + hashString(HashAlgorithm::SHA256, concatStrings("nix-output:", outputName)).to_string(HashFormat::Nix32, false);
}
-
-
-
-static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites)
+void BasicDerivation::applyRewrites(const StringMap & rewrites)
{
- debug("Rewriting the derivation");
+ if (rewrites.empty()) return;
- for (auto & rewrite : rewrites) {
+ debug("rewriting the derivation");
+
+ for (auto & rewrite : rewrites)
debug("rewriting %s as %s", rewrite.first, rewrite.second);
- }
- drv.builder = rewriteStrings(drv.builder, rewrites);
- for (auto & arg : drv.args) {
+ builder = rewriteStrings(builder, rewrites);
+ for (auto & arg : args)
arg = rewriteStrings(arg, rewrites);
- }
StringPairs newEnv;
- for (auto & envVar : drv.env) {
+ for (auto & envVar : env) {
auto envName = rewriteStrings(envVar.first, rewrites);
auto envValue = rewriteStrings(envVar.second, rewrites);
newEnv.emplace(envName, envValue);
}
- drv.env = newEnv;
+ env = std::move(newEnv);
+}
+
+static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites)
+{
+ drv.applyRewrites(rewrites);
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
for (auto & [outputName, output] : drv.outputs) {
diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh
index 40740d545..765b66ade 100644
--- a/src/libstore/derivations.hh
+++ b/src/libstore/derivations.hh
@@ -325,6 +325,12 @@ struct BasicDerivation
static std::string_view nameFromPath(const StorePath & storePath);
+ /**
+ * Apply string rewrites to the `env`, `args` and `builder`
+ * fields.
+ */
+ void applyRewrites(const StringMap & rewrites);
+
bool operator == (const BasicDerivation &) const = default;
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
//auto operator <=> (const BasicDerivation &) const = default;
diff --git a/src/libstore/export-import.cc b/src/libstore/export-import.cc
index cb36c0c1b..1c62cdfad 100644
--- a/src/libstore/export-import.cc
+++ b/src/libstore/export-import.cc
@@ -13,14 +13,9 @@ void Store::exportPaths(const StorePathSet & paths, Sink & sink)
auto sorted = topoSortPaths(paths);
std::reverse(sorted.begin(), sorted.end());
- std::string doneLabel("paths exported");
- //logger->incExpected(doneLabel, sorted.size());
-
for (auto & path : sorted) {
- //Activity act(*logger, lvlInfo, "exporting path '%s'", path);
sink << 1;
exportPath(path, sink);
- //logger->incProgress(doneLabel);
}
sink << 0;
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index 73195794a..45dfe4ad8 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -4,6 +4,7 @@
#include "finally.hh"
#include "unix-domain-socket.hh"
#include "signals.hh"
+#include "posix-fs-canonicalise.hh"
#if !defined(__linux__)
// For shelling out to lsof
@@ -763,13 +764,18 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
}
}
}
-
for (auto & path : topoSortPaths(visited)) {
if (!dead.insert(path).second) continue;
if (shouldDelete) {
- invalidatePathChecked(path);
- deleteFromStore(path.to_string());
- referrersCache.erase(path);
+ try {
+ invalidatePathChecked(path);
+ deleteFromStore(path.to_string());
+ referrersCache.erase(path);
+ } catch (PathInUse &e) {
+ // If we end up here, it's likely a new occurence
+ // of https://github.com/NixOS/nix/issues/11923
+ printError("BUG: %s", e.what());
+ }
}
}
};
diff --git a/src/libstore/keys.cc b/src/libstore/keys.cc
index 70478e7ad..668725fc7 100644
--- a/src/libstore/keys.cc
+++ b/src/libstore/keys.cc
@@ -10,12 +10,12 @@ PublicKeys getDefaultPublicKeys()
// FIXME: filter duplicates
- for (auto s : settings.trustedPublicKeys.get()) {
+ for (const auto & s : settings.trustedPublicKeys.get()) {
PublicKey key(s);
publicKeys.emplace(key.name, key);
}
- for (auto secretKeyFile : settings.secretKeyFiles.get()) {
+ for (const auto & secretKeyFile : settings.secretKeyFiles.get()) {
try {
SecretKey secretKey(readFile(secretKeyFile));
publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
diff --git a/src/libstore/local-overlay-store.cc b/src/libstore/local-overlay-store.cc
index b86beba2c..56ff6bef3 100644
--- a/src/libstore/local-overlay-store.cc
+++ b/src/libstore/local-overlay-store.cc
@@ -156,7 +156,7 @@ void LocalOverlayStore::queryGCReferrers(const StorePath & path, StorePathSet &
StorePathSet LocalOverlayStore::queryValidDerivers(const StorePath & path)
{
auto res = LocalStore::queryValidDerivers(path);
- for (auto p : lowerStore->queryValidDerivers(path))
+ for (const auto & p : lowerStore->queryValidDerivers(path))
res.insert(p);
return res;
}
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index eafdac0cd..f708bd1b0 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -95,51 +95,6 @@ struct LocalStore::State::Stmts {
SQLiteStmt AddRealisationReference;
};
-static int getSchema(Path schemaPath)
-{
- int curSchema = 0;
- if (pathExists(schemaPath)) {
- auto s = readFile(schemaPath);
- auto n = string2Int(s);
- if (!n)
- throw Error("'%1%' is corrupt", schemaPath);
- curSchema = *n;
- }
- return curSchema;
-}
-
-void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
-{
- const int nixCASchemaVersion = 4;
- int curCASchema = getSchema(schemaPath);
- if (curCASchema != nixCASchemaVersion) {
- if (curCASchema > nixCASchemaVersion) {
- throw Error("current Nix store ca-schema is version %1%, but I only support %2%",
- curCASchema, nixCASchemaVersion);
- }
-
- if (!lockFile(lockFd.get(), ltWrite, false)) {
- printInfo("waiting for exclusive access to the Nix store for ca drvs...");
- lockFile(lockFd.get(), ltNone, false); // We have acquired a shared lock; release it to prevent deadlocks
- lockFile(lockFd.get(), ltWrite, true);
- }
-
- if (curCASchema == 0) {
- static const char schema[] =
- #include "ca-specific-schema.sql.gen.hh"
- ;
- db.exec(schema);
- curCASchema = nixCASchemaVersion;
- }
-
- if (curCASchema < 4)
- throw Error("experimental CA schema version %d is no longer supported", curCASchema);
-
- writeFile(schemaPath, fmt("%d", nixCASchemaVersion), 0666, true);
- lockFile(lockFd.get(), ltRead, true);
- }
-}
-
LocalStore::LocalStore(
std::string_view scheme,
PathView path,
@@ -316,6 +271,10 @@ LocalStore::LocalStore(
openDB(*state, false);
+ /* Legacy database schema migrations. Don't bump 'schema' for
+ new migrations; instead, add a migration to
+ upgradeDBSchema(). */
+
if (curSchema < 8) {
SQLiteTxn txn(state->db);
state->db.exec("alter table ValidPaths add column ultimate integer");
@@ -342,13 +301,7 @@ LocalStore::LocalStore(
else openDB(*state, false);
- if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
- if (!readOnly) {
- migrateCASchema(state->db, dbDir + "/ca-schema", globalLock);
- } else {
- throw Error("need to migrate to content-addressed schema, but this cannot be done in read-only mode");
- }
- }
+ upgradeDBSchema(*state);
/* Prepare SQL statements. */
state->stmts->RegisterValidPath.create(state->db,
@@ -483,7 +436,17 @@ std::string LocalStore::getUri()
int LocalStore::getSchema()
-{ return nix::getSchema(schemaPath); }
+{
+ int curSchema = 0;
+ if (pathExists(schemaPath)) {
+ auto s = readFile(schemaPath);
+ auto n = string2Int(s);
+ if (!n)
+ throw Error("'%1%' is corrupt", schemaPath);
+ curSchema = *n;
+ }
+ return curSchema;
+}
void LocalStore::openDB(State & state, bool create)
{
@@ -566,6 +529,42 @@ void LocalStore::openDB(State & state, bool create)
}
+void LocalStore::upgradeDBSchema(State & state)
+{
+ state.db.exec("create table if not exists SchemaMigrations (migration text primary key not null);");
+
+ std::set schemaMigrations;
+
+ {
+ SQLiteStmt querySchemaMigrations;
+ querySchemaMigrations.create(state.db, "select migration from SchemaMigrations;");
+ auto useQuerySchemaMigrations(querySchemaMigrations.use());
+ while (useQuerySchemaMigrations.next())
+ schemaMigrations.insert(useQuerySchemaMigrations.getStr(0));
+ }
+
+ auto doUpgrade = [&](const std::string & migrationName, const std::string & stmt)
+ {
+ if (schemaMigrations.contains(migrationName))
+ return;
+
+ debug("executing Nix database schema migration '%s'...", migrationName);
+
+ SQLiteTxn txn(state.db);
+ state.db.exec(stmt + fmt(";\ninsert into SchemaMigrations values('%s')", migrationName));
+ txn.commit();
+
+ schemaMigrations.insert(migrationName);
+ };
+
+ if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations))
+ doUpgrade(
+ "20220326-ca-derivations",
+ #include "ca-specific-schema.sql.gen.hh"
+ );
+}
+
+
/* To improve purity, users may want to make the Nix store a read-only
bind mount. So make the Nix store writable for this process. */
void LocalStore::makeStoreWritable()
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 21848cc4d..83154d651 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -356,6 +356,8 @@ private:
void openDB(State & state, bool create);
+ void upgradeDBSchema(State & state);
+
void makeStoreWritable();
uint64_t queryValidPathId(State & state, const StorePath & path);
diff --git a/src/libstore/meson.build b/src/libstore/meson.build
index 101879e90..f836b8d4f 100644
--- a/src/libstore/meson.build
+++ b/src/libstore/meson.build
@@ -82,7 +82,6 @@ if host_machine.system() == 'windows'
endif
subdir('build-utils-meson/libatomic')
-subdir('build-utils-meson/threads')
boost = dependency(
'boost',
@@ -180,7 +179,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'binary-cache-store.cc',
diff --git a/src/libstore/nar-info.cc b/src/libstore/nar-info.cc
index 8b2557060..27fcc2864 100644
--- a/src/libstore/nar-info.cc
+++ b/src/libstore/nar-info.cc
@@ -118,7 +118,7 @@ std::string NarInfo::to_string(const Store & store) const
if (deriver)
res += "Deriver: " + std::string(deriver->to_string()) + "\n";
- for (auto sig : sigs)
+ for (const auto & sig : sigs)
res += "Sig: " + sig + "\n";
if (ca)
diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc
index bcbf0b55e..bf351a56d 100644
--- a/src/libstore/s3-binary-cache-store.cc
+++ b/src/libstore/s3-binary-cache-store.cc
@@ -454,7 +454,7 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual
debug("got %d keys, next marker '%s'",
contents.size(), res.GetNextMarker());
- for (auto object : contents) {
+ for (const auto & object : contents) {
auto & key = object.GetKey();
if (key.size() != 40 || !hasSuffix(key, ".narinfo")) continue;
paths.insert(parseStorePath(storeDir + "/" + key.substr(0, key.size() - 8) + "-" + MissingName));
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 10577fa2a..78cc3b917 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -1332,7 +1332,7 @@ ref openStore(StoreReference && storeURI)
return std::make_shared(params);
},
[&](const StoreReference::Specified & g) {
- for (auto implem : *Implementations::registered)
+ for (const auto & implem : *Implementations::registered)
if (implem.uriSchemes.count(g.scheme))
return implem.create(g.scheme, g.authority, params);
@@ -1363,7 +1363,7 @@ std::list][> getDefaultSubstituters()
}
};
- for (auto uri : settings.substituters.get())
+ for (const auto & uri : settings.substituters.get())
addStore(uri);
stores.sort([](ref & a, ref & b) {
diff --git a/src/libstore/unix/build/local-derivation-goal.cc b/src/libstore/unix/build/local-derivation-goal.cc
index dcfaadeef..06a2f85be 100644
--- a/src/libstore/unix/build/local-derivation-goal.cc
+++ b/src/libstore/unix/build/local-derivation-goal.cc
@@ -883,7 +883,7 @@ void LocalDerivationGoal::startBuilder()
printMsg(lvlVomit, "setting builder env variable '%1%'='%2%'", i.first, i.second);
/* Create the log file. */
- Path logFile = openLogFile();
+ [[maybe_unused]] Path logFile = openLogFile();
/* Create a pseudoterminal to get the output of the builder. */
builderOut = posix_openpt(O_RDWR | O_NOCTTY);
diff --git a/src/libutil-c/meson.build b/src/libutil-c/meson.build
index 3d5a0b9c2..d44453676 100644
--- a/src/libutil-c/meson.build
+++ b/src/libutil-c/meson.build
@@ -25,8 +25,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
# TODO rename, because it will conflict with downstream projects
configdata.set_quoted('PACKAGE_VERSION', meson.project_version())
@@ -47,7 +45,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'nix_api_util.cc',
diff --git a/src/libutil-test-support/meson.build b/src/libutil-test-support/meson.build
index c5e1ba80b..4afed01ca 100644
--- a/src/libutil-test-support/meson.build
+++ b/src/libutil-test-support/meson.build
@@ -20,11 +20,10 @@ deps_private_maybe_subproject = [
]
deps_public_maybe_subproject = [
dependency('nix-util'),
+ dependency('nix-util-c'),
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
rapidcheck = dependency('rapidcheck')
deps_public += rapidcheck
@@ -35,7 +34,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'tests/hash.cc',
diff --git a/src/libutil-test-support/package.nix b/src/libutil-test-support/package.nix
index 2525e1602..c403e762c 100644
--- a/src/libutil-test-support/package.nix
+++ b/src/libutil-test-support/package.nix
@@ -3,6 +3,7 @@
, mkMesonLibrary
, nix-util
+, nix-util-c
, rapidcheck
@@ -33,6 +34,7 @@ mkMesonLibrary (finalAttrs: {
propagatedBuildInputs = [
nix-util
+ nix-util-c
rapidcheck
];
diff --git a/src/libutil-test-support/tests/gtest-with-params.hh b/src/libutil-test-support/tests/gtest-with-params.hh
index d72aec4fd..a6e23ad89 100644
--- a/src/libutil-test-support/tests/gtest-with-params.hh
+++ b/src/libutil-test-support/tests/gtest-with-params.hh
@@ -40,7 +40,7 @@ void checkGTestWith(Testable && testable, MakeTestParams makeTestParams)
} else {
std::ostringstream ss;
printResultMessage(result, ss);
- FAIL() << ss.str() << std::endl;
+ throw std::runtime_error(ss.str());
}
}
}
diff --git a/src/libutil-test-support/tests/nix_api_util.hh b/src/libutil-test-support/tests/nix_api_util.hh
index efd200116..006dc497c 100644
--- a/src/libutil-test-support/tests/nix_api_util.hh
+++ b/src/libutil-test-support/tests/nix_api_util.hh
@@ -26,14 +26,13 @@ protected:
inline void assert_ctx_ok()
{
-
if (nix_err_code(ctx) == NIX_OK) {
return;
}
unsigned int n;
const char * p = nix_err_msg(nullptr, ctx, &n);
std::string msg(p, n);
- FAIL() << "nix_err_code(ctx) != NIX_OK, message: " << msg;
+ throw std::runtime_error(std::string("nix_err_code(ctx) != NIX_OK, message: ") + msg);
}
inline void assert_ctx_err()
@@ -41,7 +40,7 @@ protected:
if (nix_err_code(ctx) != NIX_OK) {
return;
}
- FAIL() << "Got NIX_OK, but expected an error!";
+ throw std::runtime_error("Got NIX_OK, but expected an error!");
}
};
diff --git a/src/libutil-tests/hilite.cc b/src/libutil-tests/hilite.cc
index 1ff5980d5..5ef581888 100644
--- a/src/libutil-tests/hilite.cc
+++ b/src/libutil-tests/hilite.cc
@@ -52,8 +52,7 @@ namespace nix {
std::regex("pt"),
};
std::vector matches;
- for(auto regex : regexes)
- {
+ for (const auto & regex : regexes) {
for(auto it = std::sregex_iterator(str.begin(), str.end(), regex); it != std::sregex_iterator(); ++it) {
matches.push_back(*it);
}
diff --git a/src/libutil-tests/meson.build b/src/libutil-tests/meson.build
index 5c3b5e5a3..f59350774 100644
--- a/src/libutil-tests/meson.build
+++ b/src/libutil-tests/meson.build
@@ -25,8 +25,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
subdir('build-utils-meson/export-all-symbols')
subdir('build-utils-meson/windows-version')
@@ -44,7 +42,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'args.cc',
diff --git a/src/libutil-tests/nix_api_util.cc b/src/libutil-tests/nix_api_util.cc
index b36f71042..7b77bd87f 100644
--- a/src/libutil-tests/nix_api_util.cc
+++ b/src/libutil-tests/nix_api_util.cc
@@ -136,7 +136,6 @@ TEST_F(nix_api_util_context, nix_err_name)
// no error
EXPECT_THROW(nix_err_name(NULL, ctx, OBSERVE_STRING(err_name)), nix::Error);
- std::string err_msg_ref;
try {
throw nix::Error("testing error");
} catch (...) {
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 385b6cd34..05ecf724e 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -348,7 +348,7 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang)
/* Now that all the other args are processed, run the deferred completions.
*/
- for (auto d : deferredCompletions)
+ for (const auto & d : deferredCompletions)
d.completer(*completions, d.n, d.prefix);
}
diff --git a/src/libutil/config.hh b/src/libutil/config.hh
index c0c59ac68..e98e09bf7 100644
--- a/src/libutil/config.hh
+++ b/src/libutil/config.hh
@@ -115,6 +115,8 @@ public:
* Re-applies all previously attempted changes to unknown settings
*/
void reapplyUnknownSettings();
+
+ virtual ~AbstractConfig() = default;
};
/**
diff --git a/src/libutil/file-system.cc b/src/libutil/file-system.cc
index 92996ea47..829700336 100644
--- a/src/libutil/file-system.cc
+++ b/src/libutil/file-system.cc
@@ -602,7 +602,11 @@ std::pair createTempFile(const Path & prefix)
void createSymlink(const Path & target, const Path & link)
{
- fs::create_symlink(target, link);
+ try {
+ fs::create_symlink(target, link);
+ } catch (fs::filesystem_error & e) {
+ throw SysError("creating symlink '%1%' -> '%2%'", link, target);
+ }
}
void replaceSymlink(const fs::path & target, const fs::path & link)
@@ -615,10 +619,16 @@ void replaceSymlink(const fs::path & target, const fs::path & link)
fs::create_symlink(target, tmp);
} catch (fs::filesystem_error & e) {
if (e.code() == std::errc::file_exists) continue;
- throw;
+ throw SysError("creating symlink '%1%' -> '%2%'", tmp, target);
+ }
+
+ try {
+ fs::rename(tmp, link);
+ } catch (fs::filesystem_error & e) {
+ if (e.code() == std::errc::file_exists) continue;
+ throw SysError("renaming '%1%' to '%2%'", tmp, link);
}
- fs::rename(tmp, link);
break;
}
diff --git a/src/libutil/file-system.hh b/src/libutil/file-system.hh
index 4c08cdf58..3c49181a0 100644
--- a/src/libutil/file-system.hh
+++ b/src/libutil/file-system.hh
@@ -250,8 +250,6 @@ void setWriteTime(const std::filesystem::path & path, const struct stat & st);
/**
* Create a symlink.
*
- * In the process of being deprecated for
- * `std::filesystem::create_symlink`.
*/
void createSymlink(const Path & target, const Path & link);
diff --git a/src/libutil/meson.build b/src/libutil/meson.build
index a6dc86394..11b4ea592 100644
--- a/src/libutil/meson.build
+++ b/src/libutil/meson.build
@@ -54,7 +54,6 @@ endforeach
configdata.set('HAVE_DECL_AT_SYMLINK_NOFOLLOW', cxx.has_header_symbol('fcntl.h', 'AT_SYMLINK_NOFOLLOW').to_int())
subdir('build-utils-meson/libatomic')
-subdir('build-utils-meson/threads')
if host_machine.system() == 'windows'
socket = cxx.find_library('ws2_32')
@@ -121,7 +120,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
sources = files(
'archive.cc',
diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc
index 168d2ed32..381e7ae38 100644
--- a/src/libutil/serialise.cc
+++ b/src/libutil/serialise.cc
@@ -90,7 +90,6 @@ void Source::operator () (std::string_view data)
void Source::drainInto(Sink & sink)
{
- std::string s;
std::array buf;
while (true) {
size_t n;
@@ -427,7 +426,7 @@ Error readError(Source & source)
auto type = readString(source);
assert(type == "Error");
auto level = (Verbosity) readInt(source);
- auto name = readString(source); // removed
+ [[maybe_unused]] auto name = readString(source); // removed
auto msg = readString(source);
ErrorInfo info {
.level = level,
diff --git a/src/libutil/source-accessor.cc b/src/libutil/source-accessor.cc
index e797951c7..d3e304f74 100644
--- a/src/libutil/source-accessor.cc
+++ b/src/libutil/source-accessor.cc
@@ -84,9 +84,10 @@ CanonPath SourceAccessor::resolveSymlinks(
todo.pop_front();
if (c == "" || c == ".")
;
- else if (c == "..")
- res.pop();
- else {
+ else if (c == "..") {
+ if (!res.isRoot())
+ res.pop();
+ } else {
res.push(c);
if (mode == SymlinkResolution::Full || !todo.empty()) {
if (auto st = maybeLstat(res); st && st->type == SourceAccessor::tSymlink) {
diff --git a/src/libutil/terminal.cc b/src/libutil/terminal.cc
index db7a6fcd1..4c127ddb0 100644
--- a/src/libutil/terminal.cc
+++ b/src/libutil/terminal.cc
@@ -26,7 +26,7 @@ bool isTTY()
std::string filterANSIEscapes(std::string_view s, bool filterAll, unsigned int width)
{
- std::string t, e;
+ std::string t;
size_t w = 0;
auto i = s.begin();
diff --git a/src/libutil/url.cc b/src/libutil/url.cc
index 9ed49dcbe..63b9734ee 100644
--- a/src/libutil/url.cc
+++ b/src/libutil/url.cc
@@ -77,7 +77,7 @@ std::map decodeQuery(const std::string & query)
{
std::map result;
- for (auto s : tokenizeString(query, "&")) {
+ for (const auto & s : tokenizeString(query, "&")) {
auto e = s.find('=');
if (e == std::string::npos) {
warn("dubious URI query '%s' is missing equal sign '%s', ignoring", s, "=");
diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc
index c394836da..de01e1afc 100644
--- a/src/nix-build/nix-build.cc
+++ b/src/nix-build/nix-build.cc
@@ -340,13 +340,15 @@ static void main_nix_build(int argc, char * * argv)
exprs = {state->parseStdin()};
else
for (auto i : remainingArgs) {
- auto baseDir = inShebang && !packages ? absPath(dirOf(script)) : i;
-
- if (fromArgs)
+ if (fromArgs) {
+ auto shebangBaseDir = absPath(dirOf(script));
exprs.push_back(state->parseExprFromString(
std::move(i),
- (inShebang && compatibilitySettings.nixShellShebangArgumentsRelativeToScript) ? lookupFileArg(*state, baseDir) : state->rootPath(".")
+ (inShebang && compatibilitySettings.nixShellShebangArgumentsRelativeToScript)
+ ? lookupFileArg(*state, shebangBaseDir)
+ : state->rootPath(".")
));
+ }
else {
auto absolute = i;
try {
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index ba2baccee..e9eb52708 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -481,12 +481,13 @@ static void printMissing(EvalState & state, PackageInfos & elems)
{
std::vector targets;
for (auto & i : elems)
- if (auto drvPath = i.queryDrvPath())
- targets.emplace_back(DerivedPath::Built{
+ if (auto drvPath = i.queryDrvPath()) {
+ auto path = DerivedPath::Built{
.drvPath = makeConstantStorePathRef(*drvPath),
.outputs = OutputsSpec::All { },
- });
- else
+ };
+ targets.emplace_back(std::move(path));
+ } else
targets.emplace_back(DerivedPath::Opaque{
.path = i.queryOutPath(),
});
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index c823c930e..b731b25af 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -222,7 +222,7 @@ static void opPrintFixedPath(Strings opFlags, Strings opArgs)
{
auto method = FileIngestionMethod::Flat;
- for (auto i : opFlags)
+ for (const auto & i : opFlags)
if (i == "--recursive") method = FileIngestionMethod::NixArchive;
else throw UsageError("unknown flag '%1%'", i);
diff --git a/src/nix/build.cc b/src/nix/build.cc
index da9132d02..3569b0cde 100644
--- a/src/nix/build.cc
+++ b/src/nix/build.cc
@@ -42,29 +42,6 @@ static nlohmann::json builtPathsWithResultToJSON(const std::vector& buildables, LocalFSStore& store2)
-{
- for (const auto & [_i, buildable] : enumerate(buildables)) {
- auto i = _i;
- std::visit(overloaded {
- [&](const BuiltPath::Opaque & bo) {
- auto symlink = outLink;
- if (i) symlink += fmt("-%d", i);
- store2.addPermRoot(bo.path, absPath(symlink.string()));
- },
- [&](const BuiltPath::Built & bfd) {
- for (auto & output : bfd.outputs) {
- auto symlink = outLink;
- if (i) symlink += fmt("-%d", i);
- if (output.first != "out") symlink += fmt("-%s", output.first);
- store2.addPermRoot(output.second, absPath(symlink.string()));
- }
- },
- }, buildable.path.raw());
- }
-}
-
struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
{
Path outLink = "result";
@@ -140,7 +117,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile
if (outLink != "")
if (auto store2 = store.dynamic_pointer_cast())
- createOutLinks(outLink, buildables, *store2);
+ createOutLinks(outLink, toBuiltPaths(buildables), *store2);
if (printOutputPaths) {
stopProgressBar();
diff --git a/src/nix/copy.cc b/src/nix/copy.cc
index 151d28277..399a6c0fd 100644
--- a/src/nix/copy.cc
+++ b/src/nix/copy.cc
@@ -1,11 +1,13 @@
#include "command.hh"
#include "shared.hh"
#include "store-api.hh"
+#include "local-fs-store.hh"
using namespace nix;
-struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand
+struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand, MixProfile
{
+ std::optional outLink;
CheckSigsFlag checkSigs = CheckSigs;
SubstituteFlag substitute = NoSubstitute;
@@ -13,6 +15,15 @@ struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand
CmdCopy()
: BuiltPathsCommand(true)
{
+ addFlag({
+ .longName = "out-link",
+ .shortName = 'o',
+ .description = "Create symlinks prefixed with *path* to the top-level store paths fetched from the source store.",
+ .labels = {"path"},
+ .handler = {&outLink},
+ .completer = completePath
+ });
+
addFlag({
.longName = "no-check-sigs",
.description = "Do not require that paths are signed by trusted keys.",
@@ -43,19 +54,28 @@ struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand
Category category() override { return catSecondary; }
- void run(ref srcStore, BuiltPaths && paths) override
+ void run(ref srcStore, BuiltPaths && allPaths, BuiltPaths && rootPaths) override
{
auto dstStore = getDstStore();
RealisedPath::Set stuffToCopy;
- for (auto & builtPath : paths) {
+ for (auto & builtPath : allPaths) {
auto theseRealisations = builtPath.toRealisedPaths(*srcStore);
stuffToCopy.insert(theseRealisations.begin(), theseRealisations.end());
}
copyPaths(
*srcStore, *dstStore, stuffToCopy, NoRepair, checkSigs, substitute);
+
+ updateProfile(rootPaths);
+
+ if (outLink) {
+ if (auto store2 = dstStore.dynamic_pointer_cast())
+ createOutLinks(*outLink, rootPaths, *store2);
+ else
+ throw Error("'--out-link' is not supported for this Nix store");
+ }
}
};
diff --git a/src/nix/copy.md b/src/nix/copy.md
index 6ab7cdee3..813050fcb 100644
--- a/src/nix/copy.md
+++ b/src/nix/copy.md
@@ -55,6 +55,15 @@ R""(
# nix copy --to /tmp/nix nixpkgs#hello --no-check-sigs
```
+* Update the NixOS system profile to point to a closure copied from a
+ remote machine:
+
+ ```console
+ # nix copy --from ssh://server \
+ --profile /nix/var/nix/profiles/system \
+ /nix/store/r14v3km89zm3prwsa521fab5kgzvfbw4-nixos-system-foobar-24.05.20240925.759537f
+ ```
+
# Description
`nix copy` copies store path closures between two Nix stores. The
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 9a95bc695..1736add9a 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -611,7 +611,7 @@ struct CmdDevelop : Common, MixEnvironment
else if (!command.empty()) {
std::vector args;
args.reserve(command.size());
- for (auto s : command)
+ for (const auto & s : command)
args.push_back(shellEscape(s));
script += fmt("exec %s\n", concatStringsSep(" ", args));
}
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index 3a54763a1..9f3584a11 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -94,7 +94,7 @@ public:
.label="inputs",
.optional=true,
.handler={[&](std::vector inputsToUpdate){
- for (auto inputToUpdate : inputsToUpdate) {
+ for (const auto & inputToUpdate : inputsToUpdate) {
InputPath inputPath;
try {
inputPath = flake::parseInputPath(inputToUpdate);
@@ -643,10 +643,11 @@ struct CmdFlakeCheck : FlakeCommand
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
*attr2.value, attr2.pos);
if (drvPath && attr_name == settings.thisSystem.get()) {
- drvPaths.push_back(DerivedPath::Built {
+ auto path = DerivedPath::Built {
.drvPath = makeConstantStorePathRef(*drvPath),
.outputs = OutputsSpec::All { },
- });
+ };
+ drvPaths.push_back(std::move(path));
}
}
}
@@ -891,37 +892,32 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto cursor = installable.getCursor(*evalState);
- auto templateDirAttr = cursor->getAttr("path");
- auto templateDir = templateDirAttr->getString();
-
- if (!store->isInStore(templateDir))
- evalState->error(
- "'%s' was not found in the Nix store\n"
- "If you've set '%s' to a string, try using a path instead.",
- templateDir, templateDirAttr->getAttrPathStr()).debugThrow();
+ auto templateDirAttr = cursor->getAttr("path")->forceValue();
+ NixStringContext context;
+ auto templateDir = evalState->coerceToPath(noPos, templateDirAttr, context, "");
std::vector changedFiles;
std::vector conflictedFiles;
- std::function copyDir;
- copyDir = [&](const fs::path & from, const fs::path & to)
+ std::function copyDir;
+ copyDir = [&](const SourcePath & from, const fs::path & to)
{
fs::create_directories(to);
- for (auto & entry : fs::directory_iterator{from}) {
+ for (auto & [name, entry] : from.readDirectory()) {
checkInterrupt();
- auto from2 = entry.path();
- auto to2 = to / entry.path().filename();
- auto st = entry.symlink_status();
+ auto from2 = from / name;
+ auto to2 = to / name;
+ auto st = from2.lstat();
auto to_st = fs::symlink_status(to2);
- if (fs::is_directory(st))
+ if (st.type == SourceAccessor::tDirectory)
copyDir(from2, to2);
- else if (fs::is_regular_file(st)) {
- auto contents = readFile(from2.string());
+ else if (st.type == SourceAccessor::tRegular) {
+ auto contents = from2.readFile();
if (fs::exists(to_st)) {
auto contents2 = readFile(to2.string());
if (contents != contents2) {
- printError("refusing to overwrite existing file '%s'\n please merge it manually with '%s'", to2.string(), from2.string());
+ printError("refusing to overwrite existing file '%s'\n please merge it manually with '%s'", to2.string(), from2);
conflictedFiles.push_back(to2);
} else {
notice("skipping identical file: %s", from2);
@@ -930,18 +926,18 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
} else
writeFile(to2, contents);
}
- else if (fs::is_symlink(st)) {
- auto target = fs::read_symlink(from2);
+ else if (st.type == SourceAccessor::tSymlink) {
+ auto target = from2.readLink();
if (fs::exists(to_st)) {
if (fs::read_symlink(to2) != target) {
- printError("refusing to overwrite existing file '%s'\n please merge it manually with '%s'", to2.string(), from2.string());
+ printError("refusing to overwrite existing file '%s'\n please merge it manually with '%s'", to2.string(), from2);
conflictedFiles.push_back(to2);
} else {
notice("skipping identical file: %s", from2);
}
continue;
} else
- fs::create_symlink(target, to2);
+ createSymlink(target, to2);
}
else
throw Error("file '%s' has unsupported type", from2);
@@ -957,14 +953,14 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
for (auto & s : changedFiles) args.emplace_back(s.string());
runProgram("git", true, args);
}
- auto welcomeText = cursor->maybeGetAttr("welcomeText");
- if (welcomeText) {
+
+ if (auto welcomeText = cursor->maybeGetAttr("welcomeText")) {
notice("\n");
notice(renderMarkdownToTerminal(welcomeText->getString()));
}
if (!conflictedFiles.empty())
- throw Error("Encountered %d conflicts - see above", conflictedFiles.size());
+ throw Error("encountered %d conflicts - see above", conflictedFiles.size());
}
};
diff --git a/src/nix/flake.md b/src/nix/flake.md
index a9b703762..1e9895f6e 100644
--- a/src/nix/flake.md
+++ b/src/nix/flake.md
@@ -84,6 +84,8 @@ Here are some examples of flake references in their URL-like representation:
repository on GitHub.
* `github:NixOS/nixpkgs/nixos-20.09`: The `nixos-20.09` branch of the
`nixpkgs` repository.
+* `github:NixOS/nixpkgs/pull/357207/head`: The `357207` pull request
+ of the nixpkgs repository.
* `github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293`: A
specific revision of the `nixpkgs` repository.
* `github:edolstra/nix-warez?dir=blender`: A flake in a subdirectory
@@ -243,6 +245,9 @@ Currently the `type` attribute can be one of the following:
* `./sub/dir` (when used on the command line and `dir/flake.nix` is in a git repository)
* `git+https://example.org/my/repo`
* `git+https://example.org/my/repo?dir=flake1`
+ * `git+https://example.org/my/repo?shallow=1` A shallow clone of the repository.
+ For large repositories, the shallow clone option can significantly speed up fresh clones compared
+ to non-shallow clones, while still providing faster updates than other fetch methods such as `tarball:` or `github:`.
* `git+ssh://git@github.com/NixOS/nix?ref=v1.2.3`
* `git://github.com/edolstra/dwarffs?ref=unstable&rev=e486d8d40e626a20e06d792db8cc5ac5aba9a5b4`
* `git+file:///home/my-user/some-repo/some-repo`
diff --git a/src/nix/fmt.md b/src/nix/fmt.md
index a2afde61c..b4693eb65 100644
--- a/src/nix/fmt.md
+++ b/src/nix/fmt.md
@@ -22,13 +22,13 @@ With [nixpkgs-fmt](https://github.com/nix-community/nixpkgs-fmt):
}
```
-With [nixfmt](https://github.com/serokell/nixfmt):
+With [nixfmt](https://github.com/NixOS/nixfmt):
```nix
# flake.nix
{
outputs = { nixpkgs, self }: {
- formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt;
+ formatter.x86_64-linux = nixpkgs.legacyPackages.x86_64-linux.nixfmt-rfc-style;
};
}
```
diff --git a/src/nix/hash.cc b/src/nix/hash.cc
index 62266fda1..2f9b3fe7c 100644
--- a/src/nix/hash.cc
+++ b/src/nix/hash.cc
@@ -79,7 +79,7 @@ struct CmdHashBase : Command
void run() override
{
- for (auto path : paths) {
+ for (const auto & path : paths) {
auto makeSink = [&]() -> std::unique_ptr {
if (modulus)
return std::make_unique(hashAlgo, *modulus);
@@ -182,7 +182,7 @@ struct CmdToBase : Command
void run() override
{
warn("The old format conversion sub commands of `nix hash` were deprecated in favor of `nix hash convert`.");
- for (auto s : args)
+ for (const auto & s : args)
logger->cout(Hash::parseAny(s, hashAlgo).to_string(hashFormat, hashFormat == HashFormat::SRI));
}
};
diff --git a/src/nix/main.cc b/src/nix/main.cc
index eff2d60a4..b0e26e093 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -435,7 +435,8 @@ void mainWrapped(int argc, char * * argv)
evalSettings.pureEval = false;
EvalState state({}, openStore("dummy://"), fetchSettings, evalSettings);
auto builtinsJson = nlohmann::json::object();
- for (auto & builtin : *state.baseEnv.values[0]->attrs()) {
+ for (auto & builtinPtr : state.getBuiltins().attrs()->lexicographicOrder(state.symbols)) {
+ auto & builtin = *builtinPtr;
auto b = nlohmann::json::object();
if (!builtin.value->isPrimOp()) continue;
auto primOp = builtin.value->primOp();
diff --git a/src/nix/meson.build b/src/nix/meson.build
index 60ee48035..5c70c8216 100644
--- a/src/nix/meson.build
+++ b/src/nix/meson.build
@@ -32,8 +32,6 @@ deps_public_maybe_subproject = [
]
subdir('build-utils-meson/subprojects')
-subdir('build-utils-meson/threads')
-
subdir('build-utils-meson/export-all-symbols')
subdir('build-utils-meson/windows-version')
@@ -65,7 +63,7 @@ add_project_arguments(
language : 'cpp',
)
-subdir('build-utils-meson/diagnostics')
+subdir('build-utils-meson/common')
subdir('build-utils-meson/generate-header')
nix_sources = [config_h] + files(
diff --git a/src/nix/realisation.cc b/src/nix/realisation.cc
index e1f231222..a386d98ea 100644
--- a/src/nix/realisation.cc
+++ b/src/nix/realisation.cc
@@ -36,7 +36,7 @@ struct CmdRealisationInfo : BuiltPathsCommand, MixJSON
Category category() override { return catSecondary; }
- void run(ref store, BuiltPaths && paths) override
+ void run(ref store, BuiltPaths && paths, BuiltPaths && rootPaths) override
{
experimentalFeatureSettings.require(Xp::CaDerivations);
RealisedPath::Set realisations;
diff --git a/src/nix/run.cc b/src/nix/run.cc
index c9857e13e..a9f9ef60f 100644
--- a/src/nix/run.cc
+++ b/src/nix/run.cc
@@ -180,9 +180,9 @@ void chrootHelper(int argc, char * * argv)
if (mount(realStoreDir.c_str(), (tmpDir + storeDir).c_str(), "", MS_BIND, 0) == -1)
throw SysError("mounting '%s' on '%s'", realStoreDir, storeDir);
- for (auto entry : fs::directory_iterator{"/"}) {
+ for (const auto & entry : fs::directory_iterator{"/"}) {
checkInterrupt();
- auto src = entry.path();
+ const auto & src = entry.path();
fs::path dst = tmpDir / entry.path().filename();
if (pathExists(dst)) continue;
auto st = entry.symlink_status();
diff --git a/src/nix/search.cc b/src/nix/search.cc
index c8d0b9e96..30b96c500 100644
--- a/src/nix/search.cc
+++ b/src/nix/search.cc
@@ -161,7 +161,6 @@ struct CmdSearch : InstallableValueCommand, MixJSON
{"description", description},
};
} else {
- auto name2 = hiliteMatches(name.name, nameMatches, ANSI_GREEN, "\e[0;2m");
if (results > 1) logger->cout("");
logger->cout(
"* %s%s",
diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc
index 2afe4b267..134d4f34a 100644
--- a/src/nix/sigs.cc
+++ b/src/nix/sigs.cc
@@ -41,7 +41,6 @@ struct CmdCopySigs : StorePathsCommand
ThreadPool pool;
- std::string doneLabel = "done";
std::atomic added{0};
//logger->setExpected(doneLabel, storePaths.size());
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index 124a05bed..52585fe08 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -129,7 +129,7 @@ struct CmdVerify : StorePathsCommand
size_t validSigs = 0;
auto doSigs = [&](StringSet sigs) {
- for (auto sig : sigs) {
+ for (const auto & sig : sigs) {
if (!sigsSeen.insert(sig).second) continue;
if (validSigs < ValidPathInfo::maxSigs && info->checkSignature(*store, publicKeys, sig))
validSigs++;
diff --git a/src/perl/meson.build b/src/perl/meson.build
index dcb6a68a4..52d85fd60 100644
--- a/src/perl/meson.build
+++ b/src/perl/meson.build
@@ -65,7 +65,13 @@ yath = find_program('yath', required : false)
# Required Libraries
#-------------------------------------------------
-bzip2_dep = dependency('bzip2')
+bzip2_dep = dependency('bzip2', required: false)
+if not bzip2_dep.found()
+ bzip2_dep = cpp.find_library('bz2')
+ if not bzip2_dep.found()
+ error('No "bzip2" pkg-config or "bz2" library found')
+ endif
+endif
curl_dep = dependency('libcurl')
libsodium_dep = dependency('libsodium')
diff --git a/tests/functional/flakes/commit-lock-file-summary.sh b/tests/functional/flakes/commit-lock-file-summary.sh
new file mode 100644
index 000000000..314d43ec3
--- /dev/null
+++ b/tests/functional/flakes/commit-lock-file-summary.sh
@@ -0,0 +1,44 @@
+#!/usr/bin/env bash
+
+source ./common.sh
+
+TODO_NixOS
+
+createFlake1
+
+lockfileSummaryFlake=$TEST_ROOT/lockfileSummaryFlake
+createGitRepo "$lockfileSummaryFlake" "--initial-branch=main"
+
+# Test that the --commit-lock-file-summary flag and its alias work
+cat > "$lockfileSummaryFlake/flake.nix" < "$flake2Dir/flake.nix" < "$flakeDir/flake.nix" < "$flake2Dir/flake.nix" < "$flake3Dir/flake.nix" < "$nonFlakeDir/README.md" < "$nonFlakeDir/shebang.sh" < $nonFlakeDir/shebang-comments.sh < $nonFlakeDir/shebang-different-comments.sh < $nonFlakeDir/shebang-reject.sh < $nonFlakeDir/shebang-inline-expr.sh <> $nonFlakeDir/shebang-inline-expr.sh <<"EOF"
-#! nix --offline shell
-#! nix --impure --expr ``
-#! nix let flake = (builtins.getFlake (toString ../flake1)).packages;
-#! nix fooScript = flake.${builtins.currentSystem}.fooScript;
-#! nix /* just a comment !@#$%^&*()__+ # */
-#! nix in fooScript
-#! nix ``
-#! nix --no-write-lock-file --command bash
-set -ex
-foo
-echo "$@"
-EOF
-chmod +x $nonFlakeDir/shebang-inline-expr.sh
-
-cat > $nonFlakeDir/fooScript.nix <<"EOF"
-let flake = (builtins.getFlake (toString ../flake1)).packages;
- fooScript = flake.${builtins.currentSystem}.fooScript;
- in fooScript
-EOF
-
-cat > $nonFlakeDir/shebang-file.sh <> $nonFlakeDir/shebang-file.sh <<"EOF"
-#! nix --offline shell
-#! nix --impure --file ./fooScript.nix
-#! nix --no-write-lock-file --command bash
-set -ex
-foo
-echo "$@"
-EOF
-chmod +x $nonFlakeDir/shebang-file.sh
-
# Construct a custom registry, additionally test the --registry flag
nix registry add --registry "$registry" flake1 "git+file://$flake1Dir"
-nix registry add --registry "$registry" flake2 "git+file://$percentEncodedFlake2Dir"
nix registry add --registry "$registry" flake3 "git+file://$percentEncodedFlake3Dir"
-nix registry add --registry "$registry" flake4 flake3
nix registry add --registry "$registry" nixpkgs flake1
# Test 'nix registry list'.
-[[ $(nix registry list | wc -l) == 5 ]]
+[[ $(nix registry list | wc -l) == 4 ]]
nix registry list | grep '^global'
nix registry list | grepInverse '^user' # nothing in user registry
@@ -346,77 +220,8 @@ _NIX_FORCE_HTTP=1 nix build -o "$TEST_ROOT/result" "git+file://$percentEncodedFl
mv "$flake1Dir.tmp" "$flake1Dir"
mv "$flake2Dir.tmp" "$flake2Dir"
-# Add nonFlakeInputs to flake3.
-rm "$flake3Dir/flake.nix"
-
-cat > "$flake3Dir/flake.nix" < \$out
- [[ \$(cat \${inputs.nonFlake}/README.md) = \$(cat \${inputs.nonFlakeFile}) ]]
- [[ \${inputs.nonFlakeFile} = \${inputs.nonFlakeFile2} ]]
- '';
- };
- };
-}
-EOF
-
-cp "${config_nix}" "$flake3Dir"
-
-git -C "$flake3Dir" add flake.nix config.nix
-git -C "$flake3Dir" commit -m 'Add nonFlakeInputs'
-
-# Check whether `nix build` works with a lockfile which is missing a
-# nonFlakeInputs.
-nix build -o "$TEST_ROOT/result" "$flake3Dir#sth" --commit-lock-file
-
-nix build -o "$TEST_ROOT/result" flake3#fnord
-[[ $(cat $TEST_ROOT/result) = FNORD ]]
-
-# Check whether flake input fetching is lazy: flake3#sth does not
-# depend on flake2, so this shouldn't fail.
-rm -rf "$TEST_HOME/.cache"
-clearStore
-mv "$flake2Dir" "$flake2Dir.tmp"
-mv "$nonFlakeDir" "$nonFlakeDir.tmp"
-nix build -o "$TEST_ROOT/result" flake3#sth
-(! nix build -o "$TEST_ROOT/result" flake3#xyzzy)
-(! nix build -o "$TEST_ROOT/result" flake3#fnord)
-mv "$flake2Dir.tmp" "$flake2Dir"
-mv "$nonFlakeDir.tmp" "$nonFlakeDir"
-nix build -o "$TEST_ROOT/result" flake3#xyzzy flake3#fnord
-
# Test doing multiple `lookupFlake`s
-nix build -o "$TEST_ROOT/result" flake4#xyzzy
+nix build -o "$TEST_ROOT/result" flake3#xyzzy
# Test 'nix flake update' and --override-flake.
nix flake lock "$flake3Dir"
@@ -425,53 +230,15 @@ nix flake lock "$flake3Dir"
nix flake update --flake "$flake3Dir" --override-flake flake2 nixpkgs
[[ ! -z $(git -C "$flake3Dir" diff master || echo failed) ]]
-# Make branch "removeXyzzy" where flake3 doesn't have xyzzy anymore
-git -C "$flake3Dir" checkout -b removeXyzzy
-rm "$flake3Dir/flake.nix"
-
-cat > "$flake3Dir/flake.nix" < \$out
- '';
- };
- };
-}
-EOF
-nix flake lock "$flake3Dir"
-git -C "$flake3Dir" add flake.nix flake.lock
-git -C "$flake3Dir" commit -m 'Remove packages.xyzzy'
-git -C "$flake3Dir" checkout master
-
-# Test whether fuzzy-matching works for registry entries.
-(! nix build -o "$TEST_ROOT/result" flake4/removeXyzzy#xyzzy)
-nix build -o "$TEST_ROOT/result" flake4/removeXyzzy#sth
-
# Testing the nix CLI
nix registry add flake1 flake3
-[[ $(nix registry list | wc -l) == 6 ]]
-nix registry pin flake1
-[[ $(nix registry list | wc -l) == 6 ]]
-nix registry pin flake1 flake3
-[[ $(nix registry list | wc -l) == 6 ]]
-nix registry remove flake1
[[ $(nix registry list | wc -l) == 5 ]]
+nix registry pin flake1
+[[ $(nix registry list | wc -l) == 5 ]]
+nix registry pin flake1 flake3
+[[ $(nix registry list | wc -l) == 5 ]]
+nix registry remove flake1
+[[ $(nix registry list | wc -l) == 4 ]]
# Test 'nix registry list' with a disabled global registry.
nix registry add user-flake1 git+file://$flake1Dir
@@ -481,7 +248,7 @@ nix --flake-registry "" registry list | grepQuietInverse '^global' # nothing in
nix --flake-registry "" registry list | grepQuiet '^user'
nix registry remove user-flake1
nix registry remove user-flake2
-[[ $(nix registry list | wc -l) == 5 ]]
+[[ $(nix registry list | wc -l) == 4 ]]
# Test 'nix flake clone'.
rm -rf $TEST_ROOT/flake1-v2
@@ -643,46 +410,3 @@ nix flake metadata "$flake2Dir" --reference-lock-file $TEST_ROOT/flake2-overridd
# reference-lock-file can only be used if allow-dirty is set.
expectStderr 1 nix flake metadata "$flake2Dir" --no-allow-dirty --reference-lock-file $TEST_ROOT/flake2-overridden.lock
-
-# Test shebang
-[[ $($nonFlakeDir/shebang.sh) = "foo" ]]
-[[ $($nonFlakeDir/shebang.sh "bar") = "foo"$'\n'"bar" ]]
-[[ $($nonFlakeDir/shebang-comments.sh ) = "foo" ]]
-[[ "$($nonFlakeDir/shebang-different-comments.sh)" = "$(cat $nonFlakeDir/shebang-different-comments.sh)" ]]
-[[ $($nonFlakeDir/shebang-inline-expr.sh baz) = "foo"$'\n'"baz" ]]
-[[ $($nonFlakeDir/shebang-file.sh baz) = "foo"$'\n'"baz" ]]
-expect 1 $nonFlakeDir/shebang-reject.sh 2>&1 | grepQuiet -F 'error: unsupported unquoted character in nix shebang: *. Use double backticks to escape?'
-
-# Test that the --commit-lock-file-summary flag and its alias work
-cat > "$lockfileSummaryFlake/flake.nix" < "$nonFlakeDir/README.md" < "$flake3Dir/flake.nix" < \$out
+ [[ \$(cat \${inputs.nonFlake}/README.md) = \$(cat \${inputs.nonFlakeFile}) ]]
+ [[ \${inputs.nonFlakeFile} = \${inputs.nonFlakeFile2} ]]
+ '';
+ };
+ };
+}
+EOF
+
+cp "${config_nix}" "$flake3Dir"
+
+git -C "$flake3Dir" add flake.nix config.nix
+git -C "$flake3Dir" commit -m 'Add nonFlakeInputs'
+
+# Check whether `nix build` works with a lockfile which is missing a
+# nonFlakeInputs.
+nix build -o "$TEST_ROOT/result" "$flake3Dir#sth" --commit-lock-file
+
+nix registry add --registry "$registry" flake3 "git+file://$flake3Dir"
+
+nix build -o "$TEST_ROOT/result" flake3#fnord
+[[ $(cat "$TEST_ROOT/result") = FNORD ]]
+
+# Check whether flake input fetching is lazy: flake3#sth does not
+# depend on flake2, so this shouldn't fail.
+rm -rf "$TEST_HOME/.cache"
+clearStore
+mv "$flake2Dir" "$flake2Dir.tmp"
+mv "$nonFlakeDir" "$nonFlakeDir.tmp"
+nix build -o "$TEST_ROOT/result" flake3#sth
+(! nix build -o "$TEST_ROOT/result" flake3#xyzzy)
+(! nix build -o "$TEST_ROOT/result" flake3#fnord)
+mv "$flake2Dir.tmp" "$flake2Dir"
+mv "$nonFlakeDir.tmp" "$nonFlakeDir"
+nix build -o "$TEST_ROOT/result" flake3#xyzzy flake3#fnord
+
+# Make branch "removeXyzzy" where flake3 doesn't have xyzzy anymore
+git -C "$flake3Dir" checkout -b removeXyzzy
+rm "$flake3Dir/flake.nix"
+
+cat > "$flake3Dir/flake.nix" < \$out
+ '';
+ };
+ };
+}
+EOF
+nix flake lock "$flake3Dir"
+git -C "$flake3Dir" add flake.nix flake.lock
+git -C "$flake3Dir" commit -m 'Remove packages.xyzzy'
+git -C "$flake3Dir" checkout master
+
+# Test whether fuzzy-matching works for registry entries.
+nix registry add --registry "$registry" flake4 flake3
+(! nix build -o "$TEST_ROOT/result" flake4/removeXyzzy#xyzzy)
+nix build -o "$TEST_ROOT/result" flake4/removeXyzzy#sth
diff --git a/tests/functional/flakes/shebang.sh b/tests/functional/flakes/shebang.sh
new file mode 100644
index 000000000..576a62e2c
--- /dev/null
+++ b/tests/functional/flakes/shebang.sh
@@ -0,0 +1,109 @@
+#!/usr/bin/env bash
+
+source ./common.sh
+
+TODO_NixOS
+
+createFlake1
+
+scriptDir="$TEST_ROOT/nonFlake"
+mkdir -p "$scriptDir"
+
+cat > "$scriptDir/shebang.sh" < "$scriptDir/shebang-comments.sh" < "$scriptDir/shebang-different-comments.sh" < "$scriptDir/shebang-reject.sh" < "$scriptDir/shebang-inline-expr.sh" <> "$scriptDir/shebang-inline-expr.sh" <<"EOF"
+#! nix --offline shell
+#! nix --impure --expr ``
+#! nix let flake = (builtins.getFlake (toString ../flake1)).packages;
+#! nix fooScript = flake.${builtins.currentSystem}.fooScript;
+#! nix /* just a comment !@#$%^&*()__+ # */
+#! nix in fooScript
+#! nix ``
+#! nix --no-write-lock-file --command bash
+set -ex
+foo
+echo "$@"
+EOF
+chmod +x "$scriptDir/shebang-inline-expr.sh"
+
+cat > "$scriptDir/fooScript.nix" <<"EOF"
+let flake = (builtins.getFlake (toString ../flake1)).packages;
+ fooScript = flake.${builtins.currentSystem}.fooScript;
+ in fooScript
+EOF
+
+cat > "$scriptDir/shebang-file.sh" <> "$scriptDir/shebang-file.sh" <<"EOF"
+#! nix --offline shell
+#! nix --impure --file ./fooScript.nix
+#! nix --no-write-lock-file --command bash
+set -ex
+foo
+echo "$@"
+EOF
+chmod +x "$scriptDir/shebang-file.sh"
+
+[[ $("$scriptDir/shebang.sh") = "foo" ]]
+[[ $("$scriptDir/shebang.sh" "bar") = "foo"$'\n'"bar" ]]
+[[ $("$scriptDir/shebang-comments.sh" ) = "foo" ]]
+[[ "$("$scriptDir/shebang-different-comments.sh")" = "$(cat "$scriptDir/shebang-different-comments.sh")" ]]
+[[ $("$scriptDir/shebang-inline-expr.sh" baz) = "foo"$'\n'"baz" ]]
+[[ $("$scriptDir/shebang-file.sh" baz) = "foo"$'\n'"baz" ]]
+expect 1 "$scriptDir/shebang-reject.sh" 2>&1 | grepQuiet -F 'error: unsupported unquoted character in nix shebang: *. Use double backticks to escape?'
diff --git a/tests/functional/lang/eval-fail-attrset-merge-drops-later-rec.err.exp b/tests/functional/lang/eval-fail-attrset-merge-drops-later-rec.err.exp
new file mode 100644
index 000000000..d1cdc7b76
--- /dev/null
+++ b/tests/functional/lang/eval-fail-attrset-merge-drops-later-rec.err.exp
@@ -0,0 +1,5 @@
+error: undefined variable 'd'
+ at /pwd/lang/eval-fail-attrset-merge-drops-later-rec.nix:1:26:
+ 1| { a.b = 1; a = rec { c = d + 2; d = 3; }; }.c
+ | ^
+ 2|
diff --git a/tests/functional/lang/eval-fail-attrset-merge-drops-later-rec.nix b/tests/functional/lang/eval-fail-attrset-merge-drops-later-rec.nix
new file mode 100644
index 000000000..fdb314b91
--- /dev/null
+++ b/tests/functional/lang/eval-fail-attrset-merge-drops-later-rec.nix
@@ -0,0 +1 @@
+{ a.b = 1; a = rec { c = d + 2; d = 3; }; }.c
diff --git a/tests/functional/lang/eval-okay-regrettable-rec-attrset-merge.exp b/tests/functional/lang/eval-okay-regrettable-rec-attrset-merge.exp
new file mode 100644
index 000000000..1e8b31496
--- /dev/null
+++ b/tests/functional/lang/eval-okay-regrettable-rec-attrset-merge.exp
@@ -0,0 +1 @@
+6
diff --git a/tests/functional/lang/eval-okay-regrettable-rec-attrset-merge.nix b/tests/functional/lang/eval-okay-regrettable-rec-attrset-merge.nix
new file mode 100644
index 000000000..8df6a2ad8
--- /dev/null
+++ b/tests/functional/lang/eval-okay-regrettable-rec-attrset-merge.nix
@@ -0,0 +1,3 @@
+# This is for backwards compatibility, not because we like it.
+# See https://github.com/NixOS/nix/issues/9020.
+{ a = rec { b = c + 1; d = 2; }; a.c = d + 3; }.a.b
diff --git a/tests/functional/lang/parse-fail-mixed-nested-attrs1.err.exp b/tests/functional/lang/parse-fail-mixed-nested-attrs1.err.exp
index a4472156b..49a07323f 100644
--- a/tests/functional/lang/parse-fail-mixed-nested-attrs1.err.exp
+++ b/tests/functional/lang/parse-fail-mixed-nested-attrs1.err.exp
@@ -1,6 +1,6 @@
-error: attribute 'z' already defined at «stdin»:3:16
- at «stdin»:2:3:
- 1| {
+error: attribute 'x.z' already defined at «stdin»:2:3
+ at «stdin»:3:16:
2| x.z = 3;
- | ^
3| x = { y = 3; z = 3; };
+ | ^
+ 4| }
diff --git a/tests/functional/lang/parse-fail-mixed-nested-attrs2.err.exp b/tests/functional/lang/parse-fail-mixed-nested-attrs2.err.exp
index ead1f0dbd..36fab2fe6 100644
--- a/tests/functional/lang/parse-fail-mixed-nested-attrs2.err.exp
+++ b/tests/functional/lang/parse-fail-mixed-nested-attrs2.err.exp
@@ -1,6 +1,6 @@
-error: attribute 'y' already defined at «stdin»:3:9
- at «stdin»:2:3:
- 1| {
+error: attribute 'x.y.y' already defined at «stdin»:2:3
+ at «stdin»:3:9:
2| x.y.y = 3;
- | ^
3| x = { y.y= 3; z = 3; };
+ | ^
+ 4| }
diff --git a/tests/functional/meson.build b/tests/functional/meson.build
index 0d46f9ce2..933595cd5 100644
--- a/tests/functional/meson.build
+++ b/tests/functional/meson.build
@@ -17,12 +17,10 @@ fs = import('fs')
nix = find_program('nix')
bash = find_program('bash', native : true)
busybox = find_program('busybox', native : true, required : false)
-if host_machine.system() == 'windows'
- # Because of the state of symlinks on Windows, coreutils.exe doesn't usually exist, but things like ls.exe will
- coreutils = find_program('ls', native : true)
-else
- coreutils = find_program('coreutils', native : true)
-endif
+# Look up `coreutils` package by searching for `ls` binary.
+# Previously we looked up `coreutils` on `linux`, but that is not
+# guaranteed to exist either.
+coreutils = find_program('ls', native : true)
dot = find_program('dot', native : true, required : false)
nix_bin_dir = fs.parent(nix.full_path())
diff --git a/tests/functional/nix-shell.sh b/tests/functional/nix-shell.sh
index 2b78216f4..b054b7f75 100755
--- a/tests/functional/nix-shell.sh
+++ b/tests/functional/nix-shell.sh
@@ -167,6 +167,35 @@ EOF
chmod a+x $TEST_ROOT/marco/polo/default.nix
(cd $TEST_ROOT/marco && ./polo/default.nix | grepQuiet "Polo")
+# https://github.com/NixOS/nix/issues/11892
+mkdir $TEST_ROOT/issue-11892
+cat >$TEST_ROOT/issue-11892/shebangscript <$TEST_ROOT/issue-11892/my_package.nix < \$out/bin/my_package
+ cat \$out/bin/my_package
+ chmod a+x \$out/bin/my_package
+ '';
+}
+EOF
+chmod a+x $TEST_ROOT/issue-11892/shebangscript
+$TEST_ROOT/issue-11892/shebangscript \
+ | tee /dev/stderr \
+ | grepQuiet "ok baz11892"
+
#####################
# Flake equivalents #
diff --git a/tests/functional/restricted.sh b/tests/functional/restricted.sh
index 00ee4ddc8..a92a9b8a3 100755
--- a/tests/functional/restricted.sh
+++ b/tests/functional/restricted.sh
@@ -23,7 +23,7 @@ nix-instantiate --restrict-eval ./simple.nix -I src1=./simple.nix -I src2=./conf
(! nix-instantiate --restrict-eval --eval -E 'builtins.readFile ./simple.nix')
nix-instantiate --restrict-eval --eval -E 'builtins.readFile ./simple.nix' -I src=../..
-expectStderr 1 nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in builtins.readFile ' | grepQuiet "forbidden in restricted mode"
+expectStderr 1 nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in builtins.readFile ' | grepQuiet "was not found in the Nix search path"
nix-instantiate --restrict-eval --eval -E 'let __nixPath = [ { prefix = "foo"; path = ./.; } ]; in builtins.readFile ' -I src=.
p=$(nix eval --raw --expr "builtins.fetchurl file://${_NIX_TEST_SOURCE_DIR}/restricted.sh" --impure --restrict-eval --allowed-uris "file://${_NIX_TEST_SOURCE_DIR}")
diff --git a/tests/functional/shell.nix b/tests/functional/shell.nix
index 9cae14b78..4b1a0623a 100644
--- a/tests/functional/shell.nix
+++ b/tests/functional/shell.nix
@@ -37,7 +37,7 @@ let pkgs = rec {
mkdir -p $out
ln -s ${setupSh} $out/setup
'';
- };
+ } // { inherit mkDerivation; };
shellDrv = mkDerivation {
name = "shellDrv";
@@ -94,5 +94,9 @@ let pkgs = rec {
chmod a+rx $out/bin/ruby
'';
+ inherit (cfg) shell;
+
+ callPackage = f: args: f (pkgs // args);
+
inherit pkgs;
}; in pkgs
diff --git a/tests/functional/zstd.sh b/tests/functional/zstd.sh
index 450fb7d35..226d5eb68 100755
--- a/tests/functional/zstd.sh
+++ b/tests/functional/zstd.sh
@@ -18,7 +18,10 @@ HASH=$(nix hash path "$outPath")
clearStore
clearCacheCache
-nix copy --from "$cacheURI" "$outPath" --no-check-sigs
+nix copy --from "$cacheURI" "$outPath" --no-check-sigs --profile "$TEST_ROOT/profile" --out-link "$TEST_ROOT/result"
+
+[[ -e $TEST_ROOT/profile ]]
+[[ -e $TEST_ROOT/result ]]
if ls "$cacheDir/nar/"*.zst &> /dev/null; then
echo "files do exist"
diff --git a/tests/nixos/default.nix b/tests/nixos/default.nix
index 17bfdea38..c5f4a23aa 100644
--- a/tests/nixos/default.nix
+++ b/tests/nixos/default.nix
@@ -124,6 +124,8 @@ in
nix-copy = runNixOSTestFor "x86_64-linux" ./nix-copy.nix;
+ nix-docker = runNixOSTestFor "x86_64-linux" ./nix-docker.nix;
+
nssPreload = runNixOSTestFor "x86_64-linux" ./nss-preload.nix;
githubFlakes = runNixOSTestFor "x86_64-linux" ./github-flakes.nix;
diff --git a/tests/nixos/nix-docker-test.sh b/tests/nixos/nix-docker-test.sh
new file mode 100644
index 000000000..1f65e1a94
--- /dev/null
+++ b/tests/nixos/nix-docker-test.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+# docker.nix test script. Runs inside a built docker.nix container.
+
+set -eEuo pipefail
+
+export NIX_CONFIG='substituters = http://cache:5000?trusted=1'
+
+cd /tmp
+
+# Test getting a fetched derivation
+test "$("$(nix-build -E '(import {}).hello')"/bin/hello)" = "Hello, world!"
+
+# Test building a simple derivation
+# shellcheck disable=SC2016
+nix-build -E '
+let
+ pkgs = import {};
+in
+builtins.derivation {
+ name = "test";
+ system = builtins.currentSystem;
+ builder = "${pkgs.bash}/bin/bash";
+ args = ["-c" "echo OK > $out"];
+}'
+test "$(cat result)" = OK
+
+# Ensure #!/bin/sh shebang works
+echo '#!/bin/sh' > ./shebang-test
+echo 'echo OK' >> ./shebang-test
+chmod +x ./shebang-test
+test "$(./shebang-test)" = OK
+
+# Ensure #!/usr/bin/env shebang works
+echo '#!/usr/bin/env bash' > ./shebang-test
+echo 'echo OK' >> ./shebang-test
+chmod +x ./shebang-test
+test "$(./shebang-test)" = OK
+
+# Test nix-shell
+{
+ echo '#!/usr/bin/env nix-shell'
+ echo '#! nix-shell -i bash'
+ echo '#! nix-shell -p hello'
+ echo 'hello'
+} > ./nix-shell-test
+chmod +x ./nix-shell-test
+test "$(./nix-shell-test)" = "Hello, world!"
diff --git a/tests/nixos/nix-docker.nix b/tests/nixos/nix-docker.nix
new file mode 100644
index 000000000..dfd508988
--- /dev/null
+++ b/tests/nixos/nix-docker.nix
@@ -0,0 +1,53 @@
+# Test the container built by ../../docker.nix.
+
+{ lib, config, nixpkgs, hostPkgs, ... }:
+
+let
+ pkgs = config.nodes.machine.nixpkgs.pkgs;
+
+ nixImage = import ../../docker.nix {
+ inherit (config.nodes.machine.nixpkgs) pkgs;
+ };
+ nixUserImage = import ../../docker.nix {
+ inherit (config.nodes.machine.nixpkgs) pkgs;
+ name = "nix-user";
+ uid = 1000;
+ gid = 1000;
+ uname = "user";
+ gname = "user";
+ };
+
+ containerTestScript = ./nix-docker-test.sh;
+
+in {
+ name = "nix-docker";
+
+ nodes =
+ { machine =
+ { config, lib, pkgs, ... }:
+ { virtualisation.diskSize = 4096;
+ };
+ cache =
+ { config, lib, pkgs, ... }:
+ { virtualisation.additionalPaths = [ pkgs.stdenv pkgs.hello ];
+ services.harmonia.enable = true;
+ networking.firewall.allowedTCPPorts = [ 5000 ];
+ };
+ };
+
+ testScript = { nodes }: ''
+ cache.wait_for_unit("harmonia.service")
+
+ machine.succeed("mkdir -p /etc/containers")
+ machine.succeed("""echo '{"default":[{"type":"insecureAcceptAnything"}]}' > /etc/containers/policy.json""")
+
+ machine.succeed("${pkgs.podman}/bin/podman load -i ${nixImage}")
+ machine.succeed("${pkgs.podman}/bin/podman run --rm nix nix --version")
+ machine.succeed("${pkgs.podman}/bin/podman run --rm -i nix < ${containerTestScript}")
+
+ machine.succeed("${pkgs.podman}/bin/podman load -i ${nixUserImage}")
+ machine.succeed("${pkgs.podman}/bin/podman run --rm nix-user nix --version")
+ machine.succeed("${pkgs.podman}/bin/podman run --rm -i nix-user < ${containerTestScript}")
+ machine.succeed("[[ $(${pkgs.podman}/bin/podman run --rm nix-user stat -c %u /nix/store) = 1000 ]]")
+ '';
+}
]