aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md2
-rw-r--r--.github/ISSUE_TEMPLATE/missing_documentation.md28
-rw-r--r--.github/workflows/backport.yml6
-rw-r--r--.github/workflows/ci.yml56
-rw-r--r--.gitignore4
-rw-r--r--.version2
-rw-r--r--README.md2
-rw-r--r--boehmgc-coroutine-sp-fallback.diff32
-rw-r--r--configure.ac9
-rw-r--r--doc/manual/generate-manpage.nix191
-rw-r--r--doc/manual/generate-options.nix10
-rw-r--r--doc/manual/local.mk16
-rw-r--r--doc/manual/redirects.js207
-rw-r--r--doc/manual/src/SUMMARY.md.in24
-rw-r--r--doc/manual/src/advanced-topics/distributed-builds.md4
-rw-r--r--doc/manual/src/command-ref/nix-copy-closure.md4
-rw-r--r--doc/manual/src/command-ref/nix-env.md2
-rw-r--r--doc/manual/src/command-ref/nix-instantiate.md4
-rw-r--r--doc/manual/src/command-ref/nix-store.md2
-rw-r--r--doc/manual/src/command-ref/opt-common.md4
-rw-r--r--doc/manual/src/contributing/hacking.md78
-rw-r--r--doc/manual/src/expressions/arguments-variables.md80
-rw-r--r--doc/manual/src/expressions/build-script.md70
-rw-r--r--doc/manual/src/expressions/expression-language.md12
-rw-r--r--doc/manual/src/expressions/expression-syntax.md93
-rw-r--r--doc/manual/src/expressions/generic-builder.md66
-rw-r--r--doc/manual/src/expressions/language-values.md251
-rw-r--r--doc/manual/src/expressions/simple-building-testing.md61
-rw-r--r--doc/manual/src/expressions/simple-expression.md23
-rw-r--r--doc/manual/src/expressions/writing-nix-expressions.md12
-rw-r--r--doc/manual/src/glossary.md54
-rw-r--r--doc/manual/src/installation/installing-binary.md32
-rw-r--r--doc/manual/src/language/advanced-attributes.md (renamed from doc/manual/src/expressions/advanced-attributes.md)0
-rw-r--r--doc/manual/src/language/builtin-constants.md (renamed from doc/manual/src/expressions/builtin-constants.md)0
-rw-r--r--doc/manual/src/language/builtins-prefix.md (renamed from doc/manual/src/expressions/builtins-prefix.md)0
-rw-r--r--doc/manual/src/language/builtins-suffix.md (renamed from doc/manual/src/expressions/builtins-suffix.md)0
-rw-r--r--doc/manual/src/language/constructs.md (renamed from doc/manual/src/expressions/language-constructs.md)0
-rw-r--r--doc/manual/src/language/derivations.md (renamed from doc/manual/src/expressions/derivations.md)0
-rw-r--r--doc/manual/src/language/index.md33
-rw-r--r--doc/manual/src/language/operators.md (renamed from doc/manual/src/expressions/language-operators.md)2
-rw-r--r--doc/manual/src/language/values.md275
-rw-r--r--doc/manual/src/package-management/package-management.md3
-rw-r--r--doc/manual/src/release-notes/rl-2.11.md5
-rw-r--r--doc/manual/src/release-notes/rl-next.md7
-rw-r--r--doc/manual/utils.nix26
-rw-r--r--docker.nix23
-rw-r--r--flake.lock6
-rw-r--r--flake.nix12
-rw-r--r--misc/zsh/completion.zsh11
-rw-r--r--scripts/install-darwin-multi-user.sh2
-rw-r--r--scripts/install-multi-user.sh115
-rw-r--r--scripts/install-nix-from-closure.sh29
-rwxr-xr-xscripts/install.in4
-rw-r--r--scripts/local.mk2
-rw-r--r--scripts/nix-profile-daemon.fish.in35
-rw-r--r--scripts/nix-profile.fish.in37
-rw-r--r--scripts/nix-profile.sh.in1
-rw-r--r--src/libcmd/command.cc5
-rw-r--r--src/libcmd/common-eval-args.cc2
-rw-r--r--src/libcmd/common-eval-args.hh2
-rw-r--r--src/libcmd/installables.cc2
-rw-r--r--src/libcmd/markdown.cc2
-rw-r--r--src/libcmd/repl.cc21
-rw-r--r--src/libexpr/eval-cache.cc5
-rw-r--r--src/libexpr/eval.cc12
-rw-r--r--src/libexpr/fetchurl.nix8
-rw-r--r--src/libexpr/flake/call-flake.nix2
-rw-r--r--src/libexpr/flake/config.cc2
-rw-r--r--src/libexpr/flake/flake.cc5
-rw-r--r--src/libexpr/flake/flakeref.hh2
-rw-r--r--src/libexpr/flake/lockfile.cc2
-rw-r--r--src/libexpr/primops.cc8
-rw-r--r--src/libexpr/value-to-json.cc21
-rw-r--r--src/libexpr/value-to-json.hh4
-rw-r--r--src/libexpr/value.hh2
-rw-r--r--src/libfetchers/git.cc6
-rw-r--r--src/libmain/common-args.cc1
-rw-r--r--src/libmain/common-args.hh1
-rw-r--r--src/libmain/loggers.cc7
-rw-r--r--src/libmain/progress-bar.cc66
-rw-r--r--src/libmain/progress-bar.hh4
-rw-r--r--src/libmain/shared.cc23
-rw-r--r--src/libmain/shared.hh20
-rw-r--r--src/libmain/stack.cc12
-rw-r--r--src/libstore/binary-cache-store.cc11
-rw-r--r--src/libstore/binary-cache-store.hh3
-rw-r--r--src/libstore/build/derivation-goal.cc11
-rw-r--r--src/libstore/build/hook-instance.cc2
-rw-r--r--src/libstore/build/local-derivation-goal.cc66
-rw-r--r--src/libstore/builtins/unpack-channel.cc3
-rw-r--r--src/libstore/daemon.cc2
-rw-r--r--src/libstore/filetransfer.cc4
-rw-r--r--src/libstore/gc.cc15
-rw-r--r--src/libstore/globals.cc18
-rw-r--r--src/libstore/globals.hh27
-rw-r--r--src/libstore/local-binary-cache-store.cc3
-rw-r--r--src/libstore/local-store.cc44
-rw-r--r--src/libstore/local-store.hh2
-rw-r--r--src/libstore/nar-accessor.cc3
-rw-r--r--src/libstore/optimise-store.cc6
-rw-r--r--src/libstore/remote-store.cc19
-rw-r--r--src/libstore/remote-store.hh6
-rw-r--r--src/libstore/sandbox-defaults.sb6
-rw-r--r--src/libstore/sandbox-network.sb4
-rw-r--r--src/libstore/ssh.cc2
-rw-r--r--src/libstore/store-api.cc223
-rw-r--r--src/libstore/store-api.hh11
-rw-r--r--src/libutil/archive.cc7
-rw-r--r--src/libutil/archive.hh1
-rw-r--r--src/libutil/args.cc6
-rw-r--r--src/libutil/error.hh10
-rw-r--r--src/libutil/filesystem.cc172
-rw-r--r--src/libutil/json.cc27
-rw-r--r--src/libutil/json.hh11
-rw-r--r--src/libutil/logging.hh3
-rw-r--r--src/libutil/serialise.cc20
-rw-r--r--src/libutil/serialise.hh4
-rw-r--r--src/libutil/tests/json.cc4
-rw-r--r--src/libutil/util.cc189
-rw-r--r--src/libutil/util.hh22
-rw-r--r--src/nix-build/nix-build.cc6
-rw-r--r--src/nix-env/nix-env.cc4
-rw-r--r--src/nix-instantiate/nix-instantiate.cc5
-rw-r--r--src/nix-store/nix-store.cc2
-rw-r--r--src/nix/app.cc4
-rw-r--r--src/nix/bundle.md2
-rw-r--r--src/nix/develop.cc5
-rw-r--r--src/nix/develop.md6
-rw-r--r--src/nix/eval.cc3
-rw-r--r--src/nix/flake-update.md2
-rw-r--r--src/nix/flake.cc3
-rw-r--r--src/nix/get-env.sh5
-rw-r--r--src/nix/main.cc12
-rw-r--r--src/nix/make-content-addressed.md2
-rw-r--r--src/nix/path-from-hash-part.cc39
-rw-r--r--src/nix/path-from-hash-part.md20
-rw-r--r--src/nix/profile.md2
-rw-r--r--src/nix/repl.md2
-rw-r--r--src/nix/shell.md6
-rw-r--r--src/nix/verify.cc2
-rw-r--r--tests/build-dry.sh4
-rw-r--r--tests/check.sh14
-rw-r--r--tests/common.sh.in2
-rw-r--r--tests/completions.sh62
-rw-r--r--tests/fetchGit.sh12
-rw-r--r--tests/installer/default.nix220
-rw-r--r--tests/installer/vagrant_insecure_key27
-rw-r--r--tests/lang/eval-okay-eq.exp1
-rw-r--r--tests/lang/eval-okay-eq.exp.disabled1
-rw-r--r--tests/lang/eval-okay-versions.nix3
-rw-r--r--tests/local.mk4
-rw-r--r--tests/path-from-hash-part.sh10
-rw-r--r--tests/signing.sh2
153 files changed, 2343 insertions, 1434 deletions
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 392ed30c6..4fe86d5ec 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
-labels: improvement
+labels: feature
assignees: ''
---
diff --git a/.github/ISSUE_TEMPLATE/missing_documentation.md b/.github/ISSUE_TEMPLATE/missing_documentation.md
new file mode 100644
index 000000000..fbabd868e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/missing_documentation.md
@@ -0,0 +1,28 @@
+---
+name: Missing or incorrect documentation
+about: Help us improve the reference manual
+title: ''
+labels: documentation
+assignees: ''
+
+---
+
+## Problem
+
+<!-- describe your problem -->
+
+## Checklist
+
+<!-- make sure this issue is not redundant or obsolete -->
+
+- [ ] checked [latest Nix manual] \([source])
+- [ ] checked [open documentation 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/src
+[open documentation issues and pull requests]: https://github.com/NixOS/nix/labels/documentation
+
+## Proposal
+
+<!-- propose a solution -->
+
diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml
index 3a2d4de0e..75be788ef 100644
--- a/.github/workflows/backport.yml
+++ b/.github/workflows/backport.yml
@@ -2,9 +2,15 @@ name: Backport
on:
pull_request_target:
types: [closed, labeled]
+permissions:
+ contents: read
jobs:
backport:
name: Backport Pull Request
+ permissions:
+ # for zeebe-io/backport-action
+ contents: write
+ pull-requests: write
if: github.repository_owner == 'NixOS' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
runs-on: ubuntu-latest
steps:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 956f81684..7efb90913 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -9,7 +9,7 @@ permissions: read-all
jobs:
tests:
- needs: [check_cachix]
+ needs: [check_secrets]
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
@@ -19,33 +19,37 @@ jobs:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- - uses: cachix/install-nix-action@v17
+ - uses: cachix/install-nix-action@v18
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- - uses: cachix/cachix-action@v10
- if: needs.check_cachix.outputs.secret == 'true'
+ - uses: cachix/cachix-action@v11
+ if: needs.check_secrets.outputs.cachix == 'true'
with:
name: '${{ env.CACHIX_NAME }}'
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
- run: nix --experimental-features 'nix-command flakes' flake check -L
- check_cachix:
+ check_secrets:
permissions:
contents: none
- name: Cachix secret present for installer tests
+ name: Check Cachix and Docker secrets present for installer tests
runs-on: ubuntu-latest
outputs:
- secret: ${{ steps.secret.outputs.secret }}
+ cachix: ${{ steps.secret.outputs.cachix }}
+ docker: ${{ steps.secret.outputs.docker }}
steps:
- - name: Check for Cachix secret
+ - name: Check for secrets
id: secret
env:
_CACHIX_SECRETS: ${{ secrets.CACHIX_SIGNING_KEY }}${{ secrets.CACHIX_AUTH_TOKEN }}
- run: echo "::set-output name=secret::${{ env._CACHIX_SECRETS != '' }}"
+ _DOCKER_SECRETS: ${{ secrets.DOCKERHUB_USERNAME }}${{ secrets.DOCKERHUB_TOKEN }}
+ run: |
+ echo "::set-output name=cachix::${{ env._CACHIX_SECRETS != '' }}"
+ echo "::set-output name=docker::${{ env._DOCKER_SECRETS != '' }}"
installer:
- needs: [tests, check_cachix]
- if: github.event_name == 'push' && needs.check_cachix.outputs.secret == 'true'
+ needs: [tests, check_secrets]
+ if: github.event_name == 'push' && needs.check_secrets.outputs.cachix == 'true'
runs-on: ubuntu-latest
outputs:
installerURL: ${{ steps.prepare-installer.outputs.installerURL }}
@@ -54,8 +58,8 @@ jobs:
with:
fetch-depth: 0
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- - uses: cachix/install-nix-action@v17
- - uses: cachix/cachix-action@v10
+ - uses: cachix/install-nix-action@v18
+ - uses: cachix/cachix-action@v11
with:
name: '${{ env.CACHIX_NAME }}'
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
@@ -64,8 +68,8 @@ jobs:
run: scripts/prepare-installer-for-github-actions
installer_test:
- needs: [installer, check_cachix]
- if: github.event_name == 'push' && needs.check_cachix.outputs.secret == 'true'
+ needs: [installer, check_secrets]
+ if: github.event_name == 'push' && needs.check_secrets.outputs.cachix == 'true'
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
@@ -73,28 +77,36 @@ jobs:
steps:
- uses: actions/checkout@v3
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- - uses: cachix/install-nix-action@v17
+ - uses: cachix/install-nix-action@v18
with:
install_url: '${{needs.installer.outputs.installerURL}}'
install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve"
- - run: nix-instantiate -E 'builtins.currentTime' --eval
+ - run: sudo apt install fish zsh
+ if: matrix.os == 'ubuntu-latest'
+ - run: brew install fish
+ if: matrix.os == 'macos-latest'
+ - run: exec bash -c "nix-instantiate -E 'builtins.currentTime' --eval"
+ - run: exec sh -c "nix-instantiate -E 'builtins.currentTime' --eval"
+ - run: exec zsh -c "nix-instantiate -E 'builtins.currentTime' --eval"
+ - run: exec fish -c "nix-instantiate -E 'builtins.currentTime' --eval"
docker_push_image:
- needs: [check_cachix, tests]
+ needs: [check_secrets, tests]
if: >-
github.event_name == 'push' &&
github.ref_name == 'master' &&
- needs.check_cachix.outputs.secret == 'true'
+ needs.check_secrets.outputs.cachix == 'true' &&
+ needs.check_secrets.outputs.docker == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- - uses: cachix/install-nix-action@v17
+ - uses: cachix/install-nix-action@v18
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- run: echo NIX_VERSION="$(nix --experimental-features 'nix-command flakes' eval .\#default.version | tr -d \")" >> $GITHUB_ENV
- - uses: cachix/cachix-action@v10
- if: needs.check_cachix.outputs.secret == 'true'
+ - uses: cachix/cachix-action@v11
+ if: needs.check_secrets.outputs.cachix == 'true'
with:
name: '${{ env.CACHIX_NAME }}'
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
diff --git a/.gitignore b/.gitignore
index ba8e95191..8e0db013f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,11 +22,13 @@ perl/Makefile.config
/doc/manual/src/SUMMARY.md
/doc/manual/src/command-ref/new-cli
/doc/manual/src/command-ref/conf-file.md
-/doc/manual/src/expressions/builtins.md
+/doc/manual/src/language/builtins.md
# /scripts/
/scripts/nix-profile.sh
/scripts/nix-profile-daemon.sh
+/scripts/nix-profile.fish
+/scripts/nix-profile-daemon.fish
# /src/libexpr/
/src/libexpr/lexer-tab.cc
diff --git a/.version b/.version
index f161b5d80..3ca2c9b2c 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-2.10.0 \ No newline at end of file
+2.12.0 \ No newline at end of file
diff --git a/README.md b/README.md
index 80d6f128c..8a02c4c75 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Information on additional installation methods is available on the [Nix download
## Building And Developing
-See our [Hacking guide](https://hydra.nixos.org/job/nix/master/build.x86_64-linux/latest/download-by-type/doc/manual/contributing/hacking.html) in our manual for instruction on how to
+See our [Hacking guide](https://nixos.org/manual/nix/stable/contributing/hacking.html) in our manual for instruction on how to
build nix from source with nix-build or how to get a development environment.
## Additional Resources
diff --git a/boehmgc-coroutine-sp-fallback.diff b/boehmgc-coroutine-sp-fallback.diff
index e659bf470..8fdafbecb 100644
--- a/boehmgc-coroutine-sp-fallback.diff
+++ b/boehmgc-coroutine-sp-fallback.diff
@@ -1,3 +1,35 @@
+diff --git a/darwin_stop_world.c b/darwin_stop_world.c
+index 3dbaa3fb..36a1d1f7 100644
+--- a/darwin_stop_world.c
++++ b/darwin_stop_world.c
+@@ -352,6 +352,7 @@ GC_INNER void GC_push_all_stacks(void)
+ int nthreads = 0;
+ word total_size = 0;
+ mach_msg_type_number_t listcount = (mach_msg_type_number_t)THREAD_TABLE_SZ;
++ size_t stack_limit;
+ if (!EXPECT(GC_thr_initialized, TRUE))
+ GC_thr_init();
+
+@@ -407,6 +408,19 @@ GC_INNER void GC_push_all_stacks(void)
+ GC_push_all_stack_sections(lo, hi, p->traced_stack_sect);
+ }
+ if (altstack_lo) {
++ // When a thread goes into a coroutine, we lose its original sp until
++ // control flow returns to the thread.
++ // While in the coroutine, the sp points outside the thread stack,
++ // so we can detect this and push the entire thread stack instead,
++ // as an approximation.
++ // We assume that the coroutine has similarly added its entire stack.
++ // This could be made accurate by cooperating with the application
++ // via new functions and/or callbacks.
++ stack_limit = pthread_get_stacksize_np(p->id);
++ if (altstack_lo >= altstack_hi || altstack_lo < altstack_hi - stack_limit) { // sp outside stack
++ altstack_lo = altstack_hi - stack_limit;
++ }
++
+ total_size += altstack_hi - altstack_lo;
+ GC_push_all_stack(altstack_lo, altstack_hi);
+ }
diff --git a/pthread_stop_world.c b/pthread_stop_world.c
index 4b2c429..1fb4c52 100644
--- a/pthread_stop_world.c
diff --git a/configure.ac b/configure.ac
index f0210ab78..64fa12fc7 100644
--- a/configure.ac
+++ b/configure.ac
@@ -296,15 +296,6 @@ AC_CHECK_FUNCS([setresuid setreuid lchown])
AC_CHECK_FUNCS([strsignal posix_fallocate sysconf])
-# This is needed if bzip2 is a static library, and the Nix libraries
-# are dynamic.
-case "${host_os}" in
- darwin*)
- LDFLAGS="-all_load $LDFLAGS"
- ;;
-esac
-
-
AC_ARG_WITH(sandbox-shell, AS_HELP_STRING([--with-sandbox-shell=PATH],[path of a statically-linked shell to use as /bin/sh in sandboxes]),
sandbox_shell=$withval)
AC_SUBST(sandbox_shell)
diff --git a/doc/manual/generate-manpage.nix b/doc/manual/generate-manpage.nix
index 244cfa0c2..057719e34 100644
--- a/doc/manual/generate-manpage.nix
+++ b/doc/manual/generate-manpage.nix
@@ -1,99 +1,114 @@
-{ command, renderLinks ? false }:
+{ toplevel }:
with builtins;
with import ./utils.nix;
let
- showCommand =
- { command, def, filename }:
- ''
- **Warning**: This program is **experimental** and its interface is subject to change.
- ''
- + "# Name\n\n"
- + "`${command}` - ${def.description}\n\n"
- + "# Synopsis\n\n"
- + showSynopsis { inherit command; args = def.args; }
- + (if def.commands or {} != {}
- then
- let
- categories = sort (x: y: x.id < y.id) (unique (map (cmd: cmd.category) (attrValues def.commands)));
- listCommands = cmds:
- concatStrings (map (name:
- "* "
- + (if renderLinks
- then "[`${command} ${name}`](./${appendName filename name}.md)"
- else "`${command} ${name}`")
- + " - ${cmds.${name}.description}\n")
- (attrNames cmds));
- in
- "where *subcommand* is one of the following:\n\n"
- # FIXME: group by category
- + (if length categories > 1
- then
- concatStrings (map
- (cat:
- "**${toString cat.description}:**\n\n"
- + listCommands (filterAttrs (n: v: v.category == cat) def.commands)
- + "\n"
- ) categories)
- + "\n"
- else
- listCommands def.commands
- + "\n")
- else "")
- + (if def ? doc
- then def.doc + "\n\n"
- else "")
- + (let s = showOptions def.flags; in
- if s != ""
- then "# Options\n\n${s}"
- else "")
- ;
+ showCommand = { command, details, filename, toplevel }:
+ let
+ result = ''
+ > **Warning** \
+ > This program is **experimental** and its interface is subject to change.
+
+ # Name
+
+ `${command}` - ${details.description}
+
+ # Synopsis
+
+ ${showSynopsis command details.args}
+
+ ${maybeSubcommands}
+
+ ${maybeDocumentation}
+
+ ${maybeOptions}
+ '';
+ showSynopsis = command: args:
+ let
+ showArgument = arg: "*${arg.label}*" + (if arg ? arity then "" else "...");
+ arguments = concatStringsSep " " (map showArgument args);
+ in ''
+ `${command}` [*option*...] ${arguments}
+ '';
+ maybeSubcommands = if details ? commands && details.commands != {}
+ then ''
+ where *subcommand* is one of the following:
+
+ ${subcommands}
+ ''
+ else "";
+ subcommands = if length categories > 1
+ then listCategories
+ else listSubcommands details.commands;
+ categories = sort (x: y: x.id < y.id) (unique (map (cmd: cmd.category) (attrValues details.commands)));
+ listCategories = concatStrings (map showCategory categories);
+ showCategory = cat: ''
+ **${toString cat.description}:**
+
+ ${listSubcommands (filterAttrs (n: v: v.category == cat) details.commands)}
+ '';
+ listSubcommands = cmds: concatStrings (attrValues (mapAttrs showSubcommand cmds));
+ showSubcommand = name: subcmd: ''
+ * [`${command} ${name}`](./${appendName filename name}.md) - ${subcmd.description}
+ '';
+ maybeDocumentation = if details ? doc then details.doc else "";
+ maybeOptions = if details.flags == {} then "" else ''
+ # Options
+
+ ${showOptions details.flags toplevel.flags}
+ '';
+ showOptions = options: commonOptions:
+ let
+ allOptions = options // commonOptions;
+ showCategory = cat: ''
+ ${if cat != "" then "**${cat}:**" else ""}
+
+ ${listOptions (filterAttrs (n: v: v.category == cat) allOptions)}
+ '';
+ listOptions = opts: concatStringsSep "\n" (attrValues (mapAttrs showOption opts));
+ showOption = name: option:
+ let
+ shortName = if option ? shortName then "/ `-${option.shortName}`" else "";
+ labels = if option ? labels then (concatStringsSep " " (map (s: "*${s}*") option.labels)) else "";
+ in trim ''
+ - `--${name}` ${shortName} ${labels}
+
+ ${option.description}
+ '';
+ categories = sort builtins.lessThan (unique (map (cmd: cmd.category) (attrValues allOptions)));
+ in concatStrings (map showCategory categories);
+ in squash result;
appendName = filename: name: (if filename == "nix" then "nix3" else filename) + "-" + name;
- showOptions = flags:
+ processCommand = { command, details, filename, toplevel }:
let
- categories = sort builtins.lessThan (unique (map (cmd: cmd.category) (attrValues flags)));
- in
- concatStrings (map
- (cat:
- (if cat != ""
- then "**${cat}:**\n\n"
- else "")
- + concatStrings
- (map (longName:
- let
- flag = flags.${longName};
- in
- " - `--${longName}`"
- + (if flag ? shortName then " / `-${flag.shortName}`" else "")
- + (if flag ? labels then " " + (concatStringsSep " " (map (s: "*${s}*") flag.labels)) else "")
- + " \n"
- + " " + flag.description + "\n\n"
- ) (attrNames (filterAttrs (n: v: v.category == cat) flags))))
- categories);
-
- showSynopsis =
- { command, args }:
- "`${command}` [*option*...] ${concatStringsSep " "
- (map (arg: "*${arg.label}*" + (if arg ? arity then "" else "...")) args)}\n\n";
-
- processCommand = { command, def, filename }:
- [ { name = filename + ".md"; value = showCommand { inherit command def filename; }; inherit command; } ]
- ++ concatMap
- (name: processCommand {
- filename = appendName filename name;
- command = command + " " + name;
- def = def.commands.${name};
- })
- (attrNames def.commands or {});
-
-in
+ cmd = {
+ inherit command;
+ name = filename + ".md";
+ value = showCommand { inherit command details filename toplevel; };
+ };
+ subcommand = subCmd: processCommand {
+ command = command + " " + subCmd;
+ details = details.commands.${subCmd};
+ filename = appendName filename subCmd;
+ inherit toplevel;
+ };
+ in [ cmd ] ++ concatMap subcommand (attrNames details.commands or {});
-let
- manpages = processCommand { filename = "nix"; command = "nix"; def = builtins.fromJSON command; };
- summary = concatStrings (map (manpage: " - [${manpage.command}](command-ref/new-cli/${manpage.name})\n") manpages);
-in
-(listToAttrs manpages) // { "SUMMARY.md" = summary; }
+ parsedToplevel = builtins.fromJSON toplevel;
+ manpages = processCommand {
+ command = "nix";
+ details = parsedToplevel;
+ filename = "nix";
+ toplevel = parsedToplevel;
+ };
+
+ tableOfContents = let
+ showEntry = page:
+ " - [${page.command}](command-ref/new-cli/${page.name})";
+ in concatStringsSep "\n" (map showEntry manpages) + "\n";
+
+in (listToAttrs manpages) // { "SUMMARY.md" = tableOfContents; }
diff --git a/doc/manual/generate-options.nix b/doc/manual/generate-options.nix
index 2d586fa1b..680b709c8 100644
--- a/doc/manual/generate-options.nix
+++ b/doc/manual/generate-options.nix
@@ -11,16 +11,16 @@ concatStrings (map
+ concatStrings (map (s: " ${s}\n") (splitLines option.description)) + "\n\n"
+ (if option.documentDefault
then " **Default:** " + (
- if option.value == "" || option.value == []
+ if option.defaultValue == "" || option.defaultValue == []
then "*empty*"
- else if isBool option.value
- then (if option.value then "`true`" else "`false`")
+ else if isBool option.defaultValue
+ then (if option.defaultValue then "`true`" else "`false`")
else
# n.b. a StringMap value type is specified as a string, but
# this shows the value type. The empty stringmap is "null" in
# JSON, but that converts to "{ }" here.
- (if isAttrs option.value then "`\"\"`"
- else "`" + toString option.value + "`")) + "\n\n"
+ (if isAttrs option.defaultValue then "`\"\"`"
+ else "`" + toString option.defaultValue + "`")) + "\n\n"
else " **Default:** *machine-specific*\n")
+ (if option.aliases != []
then " **Deprecated alias:** " + (concatStringsSep ", " (map (s: "`${s}`") option.aliases)) + "\n\n"
diff --git a/doc/manual/local.mk b/doc/manual/local.mk
index 371ed6f21..486dbd7a2 100644
--- a/doc/manual/local.mk
+++ b/doc/manual/local.mk
@@ -1,5 +1,9 @@
ifeq ($(doc_generate),yes)
+MANUAL_SRCS := \
+ $(call rwildcard, $(d)/src, *.md) \
+ $(call rwildcard, $(d)/src, */*.md)
+
# Generate man pages.
man-pages := $(foreach n, \
nix-env.1 nix-build.1 nix-shell.1 nix-store.1 nix-instantiate.1 \
@@ -46,7 +50,7 @@ $(d)/src/SUMMARY.md: $(d)/src/SUMMARY.md.in $(d)/src/command-ref/new-cli
$(d)/src/command-ref/new-cli: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix
@rm -rf $@
- $(trace-gen) $(nix-eval) --write-to $@ --expr 'import doc/manual/generate-manpage.nix { command = builtins.readFile $<; renderLinks = true; }'
+ $(trace-gen) $(nix-eval) --write-to $@ --expr 'import doc/manual/generate-manpage.nix { toplevel = builtins.readFile $<; }'
$(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/generate-options.nix $(d)/src/command-ref/conf-file-prefix.md $(bindir)/nix
@cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp
@@ -61,10 +65,10 @@ $(d)/conf-file.json: $(bindir)/nix
$(trace-gen) $(dummy-env) $(bindir)/nix show-config --json --experimental-features nix-command > $@.tmp
@mv $@.tmp $@
-$(d)/src/expressions/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/expressions/builtins-prefix.md $(bindir)/nix
- @cat doc/manual/src/expressions/builtins-prefix.md > $@.tmp
+$(d)/src/language/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/language/builtins-prefix.md $(bindir)/nix
+ @cat doc/manual/src/language/builtins-prefix.md > $@.tmp
$(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp
- @cat doc/manual/src/expressions/builtins-suffix.md >> $@.tmp
+ @cat doc/manual/src/language/builtins-suffix.md >> $@.tmp
@mv $@.tmp $@
$(d)/builtins.json: $(bindir)/nix
@@ -92,12 +96,12 @@ doc/manual/generated/man1/nix3-manpages: $(d)/src/command-ref/new-cli
if [[ $$name = SUMMARY ]]; then continue; fi; \
printf "Title: %s\n\n" "$$name" > $$tmpFile; \
cat $$i >> $$tmpFile; \
- lowdown -sT man -M section=1 $$tmpFile -o $(DESTDIR)$$(dirname $@)/$$name.1; \
+ lowdown -sT man --nroff-nolinks -M section=1 $$tmpFile -o $(DESTDIR)$$(dirname $@)/$$name.1; \
rm $$tmpFile; \
done
@touch $@
-$(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/command-ref/conf-file.md $(d)/src/expressions/builtins.md $(call rwildcard, $(d)/src, *.md)
+$(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/command-ref/conf-file.md $(d)/src/language/builtins.md
$(trace-gen) RUST_LOG=warn mdbook build doc/manual -d $(DESTDIR)$(docdir)/manual
endif
diff --git a/doc/manual/redirects.js b/doc/manual/redirects.js
index 19f928c7e..167e221b8 100644
--- a/doc/manual/redirects.js
+++ b/doc/manual/redirects.js
@@ -132,113 +132,106 @@ var redirects = {
"#sec-common-options": "command-ref/opt-common.html",
"#ch-utilities": "command-ref/utilities.html",
"#chap-hacking": "contributing/hacking.html",
- "#adv-attr-allowSubstitutes": "expressions/advanced-attributes.html#adv-attr-allowSubstitutes",
- "#adv-attr-allowedReferences": "expressions/advanced-attributes.html#adv-attr-allowedReferences",
- "#adv-attr-allowedRequisites": "expressions/advanced-attributes.html#adv-attr-allowedRequisites",
- "#adv-attr-disallowedReferences": "expressions/advanced-attributes.html#adv-attr-disallowedReferences",
- "#adv-attr-disallowedRequisites": "expressions/advanced-attributes.html#adv-attr-disallowedRequisites",
- "#adv-attr-exportReferencesGraph": "expressions/advanced-attributes.html#adv-attr-exportReferencesGraph",
- "#adv-attr-impureEnvVars": "expressions/advanced-attributes.html#adv-attr-impureEnvVars",
- "#adv-attr-outputHash": "expressions/advanced-attributes.html#adv-attr-outputHash",
- "#adv-attr-outputHashAlgo": "expressions/advanced-attributes.html#adv-attr-outputHashAlgo",
- "#adv-attr-outputHashMode": "expressions/advanced-attributes.html#adv-attr-outputHashMode",
- "#adv-attr-passAsFile": "expressions/advanced-attributes.html#adv-attr-passAsFile",
- "#adv-attr-preferLocalBuild": "expressions/advanced-attributes.html#adv-attr-preferLocalBuild",
- "#fixed-output-drvs": "expressions/advanced-attributes.html#adv-attr-outputHash",
- "#sec-advanced-attributes": "expressions/advanced-attributes.html",
- "#sec-arguments": "expressions/arguments-variables.html",
- "#sec-build-script": "expressions/build-script.html",
- "#builtin-abort": "expressions/builtins.html#builtins-abort",
- "#builtin-add": "expressions/builtins.html#builtins-add",
- "#builtin-all": "expressions/builtins.html#builtins-all",
- "#builtin-any": "expressions/builtins.html#builtins-any",
- "#builtin-attrNames": "expressions/builtins.html#builtins-attrNames",
- "#builtin-attrValues": "expressions/builtins.html#builtins-attrValues",
- "#builtin-baseNameOf": "expressions/builtins.html#builtins-baseNameOf",
- "#builtin-bitAnd": "expressions/builtins.html#builtins-bitAnd",
- "#builtin-bitOr": "expressions/builtins.html#builtins-bitOr",
- "#builtin-bitXor": "expressions/builtins.html#builtins-bitXor",
- "#builtin-builtins": "expressions/builtins.html#builtins-builtins",
- "#builtin-compareVersions": "expressions/builtins.html#builtins-compareVersions",
- "#builtin-concatLists": "expressions/builtins.html#builtins-concatLists",
- "#builtin-concatStringsSep": "expressions/builtins.html#builtins-concatStringsSep",
- "#builtin-currentSystem": "expressions/builtins.html#builtins-currentSystem",
- "#builtin-deepSeq": "expressions/builtins.html#builtins-deepSeq",
- "#builtin-derivation": "expressions/builtins.html#builtins-derivation",
- "#builtin-dirOf": "expressions/builtins.html#builtins-dirOf",
- "#builtin-div": "expressions/builtins.html#builtins-div",
- "#builtin-elem": "expressions/builtins.html#builtins-elem",
- "#builtin-elemAt": "expressions/builtins.html#builtins-elemAt",
- "#builtin-fetchGit": "expressions/builtins.html#builtins-fetchGit",
- "#builtin-fetchTarball": "expressions/builtins.html#builtins-fetchTarball",
- "#builtin-fetchurl": "expressions/builtins.html#builtins-fetchurl",
- "#builtin-filterSource": "expressions/builtins.html#builtins-filterSource",
- "#builtin-foldl-prime": "expressions/builtins.html#builtins-foldl-prime",
- "#builtin-fromJSON": "expressions/builtins.html#builtins-fromJSON",
- "#builtin-functionArgs": "expressions/builtins.html#builtins-functionArgs",
- "#builtin-genList": "expressions/builtins.html#builtins-genList",
- "#builtin-getAttr": "expressions/builtins.html#builtins-getAttr",
- "#builtin-getEnv": "expressions/builtins.html#builtins-getEnv",
- "#builtin-hasAttr": "expressions/builtins.html#builtins-hasAttr",
- "#builtin-hashFile": "expressions/builtins.html#builtins-hashFile",
- "#builtin-hashString": "expressions/builtins.html#builtins-hashString",
- "#builtin-head": "expressions/builtins.html#builtins-head",
- "#builtin-import": "expressions/builtins.html#builtins-import",
- "#builtin-intersectAttrs": "expressions/builtins.html#builtins-intersectAttrs",
- "#builtin-isAttrs": "expressions/builtins.html#builtins-isAttrs",
- "#builtin-isBool": "expressions/builtins.html#builtins-isBool",
- "#builtin-isFloat": "expressions/builtins.html#builtins-isFloat",
- "#builtin-isFunction": "expressions/builtins.html#builtins-isFunction",
- "#builtin-isInt": "expressions/builtins.html#builtins-isInt",
- "#builtin-isList": "expressions/builtins.html#builtins-isList",
- "#builtin-isNull": "expressions/builtins.html#builtins-isNull",
- "#builtin-isString": "expressions/builtins.html#builtins-isString",
- "#builtin-length": "expressions/builtins.html#builtins-length",
- "#builtin-lessThan": "expressions/builtins.html#builtins-lessThan",
- "#builtin-listToAttrs": "expressions/builtins.html#builtins-listToAttrs",
- "#builtin-map": "expressions/builtins.html#builtins-map",
- "#builtin-match": "expressions/builtins.html#builtins-match",
- "#builtin-mul": "expressions/builtins.html#builtins-mul",
- "#builtin-parseDrvName": "expressions/builtins.html#builtins-parseDrvName",
- "#builtin-path": "expressions/builtins.html#builtins-path",
- "#builtin-pathExists": "expressions/builtins.html#builtins-pathExists",
- "#builtin-placeholder": "expressions/builtins.html#builtins-placeholder",
- "#builtin-readDir": "expressions/builtins.html#builtins-readDir",
- "#builtin-readFile": "expressions/builtins.html#builtins-readFile",
- "#builtin-removeAttrs": "expressions/builtins.html#builtins-removeAttrs",
- "#builtin-replaceStrings": "expressions/builtins.html#builtins-replaceStrings",
- "#builtin-seq": "expressions/builtins.html#builtins-seq",
- "#builtin-sort": "expressions/builtins.html#builtins-sort",
- "#builtin-split": "expressions/builtins.html#builtins-split",
- "#builtin-splitVersion": "expressions/builtins.html#builtins-splitVersion",
- "#builtin-stringLength": "expressions/builtins.html#builtins-stringLength",
- "#builtin-sub": "expressions/builtins.html#builtins-sub",
- "#builtin-substring": "expressions/builtins.html#builtins-substring",
- "#builtin-tail": "expressions/builtins.html#builtins-tail",
- "#builtin-throw": "expressions/builtins.html#builtins-throw",
- "#builtin-toFile": "expressions/builtins.html#builtins-toFile",
- "#builtin-toJSON": "expressions/builtins.html#builtins-toJSON",
- "#builtin-toPath": "expressions/builtins.html#builtins-toPath",
- "#builtin-toString": "expressions/builtins.html#builtins-toString",
- "#builtin-toXML": "expressions/builtins.html#builtins-toXML",
- "#builtin-trace": "expressions/builtins.html#builtins-trace",
- "#builtin-tryEval": "expressions/builtins.html#builtins-tryEval",
- "#builtin-typeOf": "expressions/builtins.html#builtins-typeOf",
- "#ssec-builtins": "expressions/builtins.html",
- "#attr-system": "expressions/derivations.html#attr-system",
- "#ssec-derivation": "expressions/derivations.html",
- "#ch-expression-language": "expressions/expression-language.html",
- "#sec-expression-syntax": "expressions/expression-syntax.html",
- "#sec-generic-builder": "expressions/generic-builder.html",
- "#sec-constructs": "expressions/language-constructs.html",
- "#sect-let-expressions": "expressions/language-constructs.html#let-expressions",
- "#ss-functions": "expressions/language-constructs.html#functions",
- "#sec-language-operators": "expressions/language-operators.html",
- "#table-operators": "expressions/language-operators.html",
- "#ssec-values": "expressions/language-values.html",
- "#sec-building-simple": "expressions/simple-building-testing.html",
- "#ch-simple-expression": "expressions/simple-expression.html",
- "#chap-writing-nix-expressions": "expressions/writing-nix-expressions.html",
+ "#adv-attr-allowSubstitutes": "language/advanced-attributes.html#adv-attr-allowSubstitutes",
+ "#adv-attr-allowedReferences": "language/advanced-attributes.html#adv-attr-allowedReferences",
+ "#adv-attr-allowedRequisites": "language/advanced-attributes.html#adv-attr-allowedRequisites",
+ "#adv-attr-disallowedReferences": "language/advanced-attributes.html#adv-attr-disallowedReferences",
+ "#adv-attr-disallowedRequisites": "language/advanced-attributes.html#adv-attr-disallowedRequisites",
+ "#adv-attr-exportReferencesGraph": "language/advanced-attributes.html#adv-attr-exportReferencesGraph",
+ "#adv-attr-impureEnvVars": "language/advanced-attributes.html#adv-attr-impureEnvVars",
+ "#adv-attr-outputHash": "language/advanced-attributes.html#adv-attr-outputHash",
+ "#adv-attr-outputHashAlgo": "language/advanced-attributes.html#adv-attr-outputHashAlgo",
+ "#adv-attr-outputHashMode": "language/advanced-attributes.html#adv-attr-outputHashMode",
+ "#adv-attr-passAsFile": "language/advanced-attributes.html#adv-attr-passAsFile",
+ "#adv-attr-preferLocalBuild": "language/advanced-attributes.html#adv-attr-preferLocalBuild",
+ "#fixed-output-drvs": "language/advanced-attributes.html#adv-attr-outputHash",
+ "#sec-advanced-attributes": "language/advanced-attributes.html",
+ "#builtin-abort": "language/builtins.html#builtins-abort",
+ "#builtin-add": "language/builtins.html#builtins-add",
+ "#builtin-all": "language/builtins.html#builtins-all",
+ "#builtin-any": "language/builtins.html#builtins-any",
+ "#builtin-attrNames": "language/builtins.html#builtins-attrNames",
+ "#builtin-attrValues": "language/builtins.html#builtins-attrValues",
+ "#builtin-baseNameOf": "language/builtins.html#builtins-baseNameOf",
+ "#builtin-bitAnd": "language/builtins.html#builtins-bitAnd",
+ "#builtin-bitOr": "language/builtins.html#builtins-bitOr",
+ "#builtin-bitXor": "language/builtins.html#builtins-bitXor",
+ "#builtin-builtins": "language/builtins.html#builtins-builtins",
+ "#builtin-compareVersions": "language/builtins.html#builtins-compareVersions",
+ "#builtin-concatLists": "language/builtins.html#builtins-concatLists",
+ "#builtin-concatStringsSep": "language/builtins.html#builtins-concatStringsSep",
+ "#builtin-currentSystem": "language/builtins.html#builtins-currentSystem",
+ "#builtin-deepSeq": "language/builtins.html#builtins-deepSeq",
+ "#builtin-derivation": "language/builtins.html#builtins-derivation",
+ "#builtin-dirOf": "language/builtins.html#builtins-dirOf",
+ "#builtin-div": "language/builtins.html#builtins-div",
+ "#builtin-elem": "language/builtins.html#builtins-elem",
+ "#builtin-elemAt": "language/builtins.html#builtins-elemAt",
+ "#builtin-fetchGit": "language/builtins.html#builtins-fetchGit",
+ "#builtin-fetchTarball": "language/builtins.html#builtins-fetchTarball",
+ "#builtin-fetchurl": "language/builtins.html#builtins-fetchurl",
+ "#builtin-filterSource": "language/builtins.html#builtins-filterSource",
+ "#builtin-foldl-prime": "language/builtins.html#builtins-foldl-prime",
+ "#builtin-fromJSON": "language/builtins.html#builtins-fromJSON",
+ "#builtin-functionArgs": "language/builtins.html#builtins-functionArgs",
+ "#builtin-genList": "language/builtins.html#builtins-genList",
+ "#builtin-getAttr": "language/builtins.html#builtins-getAttr",
+ "#builtin-getEnv": "language/builtins.html#builtins-getEnv",
+ "#builtin-hasAttr": "language/builtins.html#builtins-hasAttr",
+ "#builtin-hashFile": "language/builtins.html#builtins-hashFile",
+ "#builtin-hashString": "language/builtins.html#builtins-hashString",
+ "#builtin-head": "language/builtins.html#builtins-head",
+ "#builtin-import": "language/builtins.html#builtins-import",
+ "#builtin-intersectAttrs": "language/builtins.html#builtins-intersectAttrs",
+ "#builtin-isAttrs": "language/builtins.html#builtins-isAttrs",
+ "#builtin-isBool": "language/builtins.html#builtins-isBool",
+ "#builtin-isFloat": "language/builtins.html#builtins-isFloat",
+ "#builtin-isFunction": "language/builtins.html#builtins-isFunction",
+ "#builtin-isInt": "language/builtins.html#builtins-isInt",
+ "#builtin-isList": "language/builtins.html#builtins-isList",
+ "#builtin-isNull": "language/builtins.html#builtins-isNull",
+ "#builtin-isString": "language/builtins.html#builtins-isString",
+ "#builtin-length": "language/builtins.html#builtins-length",
+ "#builtin-lessThan": "language/builtins.html#builtins-lessThan",
+ "#builtin-listToAttrs": "language/builtins.html#builtins-listToAttrs",
+ "#builtin-map": "language/builtins.html#builtins-map",
+ "#builtin-match": "language/builtins.html#builtins-match",
+ "#builtin-mul": "language/builtins.html#builtins-mul",
+ "#builtin-parseDrvName": "language/builtins.html#builtins-parseDrvName",
+ "#builtin-path": "language/builtins.html#builtins-path",
+ "#builtin-pathExists": "language/builtins.html#builtins-pathExists",
+ "#builtin-placeholder": "language/builtins.html#builtins-placeholder",
+ "#builtin-readDir": "language/builtins.html#builtins-readDir",
+ "#builtin-readFile": "language/builtins.html#builtins-readFile",
+ "#builtin-removeAttrs": "language/builtins.html#builtins-removeAttrs",
+ "#builtin-replaceStrings": "language/builtins.html#builtins-replaceStrings",
+ "#builtin-seq": "language/builtins.html#builtins-seq",
+ "#builtin-sort": "language/builtins.html#builtins-sort",
+ "#builtin-split": "language/builtins.html#builtins-split",
+ "#builtin-splitVersion": "language/builtins.html#builtins-splitVersion",
+ "#builtin-stringLength": "language/builtins.html#builtins-stringLength",
+ "#builtin-sub": "language/builtins.html#builtins-sub",
+ "#builtin-substring": "language/builtins.html#builtins-substring",
+ "#builtin-tail": "language/builtins.html#builtins-tail",
+ "#builtin-throw": "language/builtins.html#builtins-throw",
+ "#builtin-toFile": "language/builtins.html#builtins-toFile",
+ "#builtin-toJSON": "language/builtins.html#builtins-toJSON",
+ "#builtin-toPath": "language/builtins.html#builtins-toPath",
+ "#builtin-toString": "language/builtins.html#builtins-toString",
+ "#builtin-toXML": "language/builtins.html#builtins-toXML",
+ "#builtin-trace": "language/builtins.html#builtins-trace",
+ "#builtin-tryEval": "language/builtins.html#builtins-tryEval",
+ "#builtin-typeOf": "language/builtins.html#builtins-typeOf",
+ "#ssec-builtins": "language/builtins.html",
+ "#attr-system": "language/derivations.html#attr-system",
+ "#ssec-derivation": "language/derivations.html",
+ "#ch-expression-language": "language/index.html",
+ "#sec-constructs": "language/constructs.html",
+ "#sect-let-language": "language/constructs.html#let-language",
+ "#ss-functions": "language/constructs.html#functions",
+ "#sec-language-operators": "language/operators.html",
+ "#table-operators": "language/operators.html",
+ "#ssec-values": "language/values.html",
"#gloss-closure": "glossary.html#gloss-closure",
"#gloss-derivation": "glossary.html#gloss-derivation",
"#gloss-deriver": "glossary.html#gloss-deriver",
diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in
index 9728728aa..908e7e3d9 100644
--- a/doc/manual/src/SUMMARY.md.in
+++ b/doc/manual/src/SUMMARY.md.in
@@ -26,21 +26,14 @@
- [Copying Closures via SSH](package-management/copy-closure.md)
- [Serving a Nix store via SSH](package-management/ssh-substituter.md)
- [Serving a Nix store via S3](package-management/s3-substituter.md)
-- [Writing Nix Expressions](expressions/writing-nix-expressions.md)
- - [A Simple Nix Expression](expressions/simple-expression.md)
- - [Expression Syntax](expressions/expression-syntax.md)
- - [Build Script](expressions/build-script.md)
- - [Arguments and Variables](expressions/arguments-variables.md)
- - [Building and Testing](expressions/simple-building-testing.md)
- - [Generic Builder Syntax](expressions/generic-builder.md)
- - [Writing Nix Expressions](expressions/expression-language.md)
- - [Values](expressions/language-values.md)
- - [Language Constructs](expressions/language-constructs.md)
- - [Operators](expressions/language-operators.md)
- - [Derivations](expressions/derivations.md)
- - [Advanced Attributes](expressions/advanced-attributes.md)
- - [Built-in Constants](expressions/builtin-constants.md)
- - [Built-in Functions](expressions/builtins.md)
+- [Nix Language](language/index.md)
+ - [Data Types](language/values.md)
+ - [Language Constructs](language/constructs.md)
+ - [Operators](language/operators.md)
+ - [Derivations](language/derivations.md)
+ - [Advanced Attributes](language/advanced-attributes.md)
+ - [Built-in Constants](language/builtin-constants.md)
+ - [Built-in Functions](language/builtins.md)
- [Advanced Topics](advanced-topics/advanced-topics.md)
- [Remote Builds](advanced-topics/distributed-builds.md)
- [Tuning Cores and Jobs](advanced-topics/cores-vs-jobs.md)
@@ -72,6 +65,7 @@
- [CLI guideline](contributing/cli-guideline.md)
- [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.md)
+ - [Release 2.11 (2022-08-25)](release-notes/rl-2.11.md)
- [Release 2.10 (2022-07-11)](release-notes/rl-2.10.md)
- [Release 2.9 (2022-05-30)](release-notes/rl-2.9.md)
- [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md)
diff --git a/doc/manual/src/advanced-topics/distributed-builds.md b/doc/manual/src/advanced-topics/distributed-builds.md
index b0d7fbf1a..fefd10100 100644
--- a/doc/manual/src/advanced-topics/distributed-builds.md
+++ b/doc/manual/src/advanced-topics/distributed-builds.md
@@ -12,14 +12,14 @@ machine is accessible via SSH and that it has Nix installed. You can
test whether connecting to the remote Nix instance works, e.g.
```console
-$ nix ping-store --store ssh://mac
+$ nix store ping --store ssh://mac
```
will try to connect to the machine named `mac`. It is possible to
specify an SSH identity file as part of the remote store URI, e.g.
```console
-$ nix ping-store --store ssh://mac?ssh-key=/home/alice/my-key
+$ nix store ping --store ssh://mac?ssh-key=/home/alice/my-key
```
Since builds should be non-interactive, the key should not have a
diff --git a/doc/manual/src/command-ref/nix-copy-closure.md b/doc/manual/src/command-ref/nix-copy-closure.md
index 7047d3012..9a29030bd 100644
--- a/doc/manual/src/command-ref/nix-copy-closure.md
+++ b/doc/manual/src/command-ref/nix-copy-closure.md
@@ -30,8 +30,8 @@ Since `nix-copy-closure` calls `ssh`, you may be asked to type in the
appropriate password or passphrase. In fact, you may be asked _twice_
because `nix-copy-closure` currently connects twice to the remote
machine, first to get the set of paths missing on the target machine,
-and second to send the dump of those paths. If this bothers you, use
-`ssh-agent`.
+and second to send the dump of those paths. When using public key
+authentication, you can avoid typing the passphrase with `ssh-agent`.
# Options
diff --git a/doc/manual/src/command-ref/nix-env.md b/doc/manual/src/command-ref/nix-env.md
index a372c5eae..a5df35d77 100644
--- a/doc/manual/src/command-ref/nix-env.md
+++ b/doc/manual/src/command-ref/nix-env.md
@@ -198,7 +198,7 @@ a number of possible ways:
another.
- If `--from-expression` is given, *args* are Nix
- [functions](../expressions/language-constructs.md#functions)
+ [functions](../language/constructs.md#functions)
that are called with the active Nix expression as their single
argument. The derivations returned by those function calls are
installed. This allows derivations to be specified in an
diff --git a/doc/manual/src/command-ref/nix-instantiate.md b/doc/manual/src/command-ref/nix-instantiate.md
index 2e198daed..8f143729e 100644
--- a/doc/manual/src/command-ref/nix-instantiate.md
+++ b/doc/manual/src/command-ref/nix-instantiate.md
@@ -51,7 +51,7 @@ standard input.
- `--strict`\
When used with `--eval`, recursively evaluate list elements and
attributes. Normally, such sub-expressions are left unevaluated
- (since the Nix expression language is lazy).
+ (since the Nix language is lazy).
> **Warning**
>
@@ -66,7 +66,7 @@ standard input.
When used with `--eval`, print the resulting value as an XML
representation of the abstract syntax tree rather than as an ATerm.
The schema is the same as that used by the [`toXML`
- built-in](../expressions/builtins.md).
+ built-in](../language/builtins.md).
- `--read-write-mode`\
When used with `--eval`, perform evaluation in read/write mode so
diff --git a/doc/manual/src/command-ref/nix-store.md b/doc/manual/src/command-ref/nix-store.md
index dc8faba68..ecd838e8d 100644
--- a/doc/manual/src/command-ref/nix-store.md
+++ b/doc/manual/src/command-ref/nix-store.md
@@ -121,7 +121,7 @@ Special exit codes:
- `102`\
Hash mismatch, the build output was rejected because it does not
match the [`outputHash` attribute of the
- derivation](../expressions/advanced-attributes.md).
+ derivation](../language/advanced-attributes.md).
- `104`\
Not deterministic, the build succeeded in check mode but the
diff --git a/doc/manual/src/command-ref/opt-common.md b/doc/manual/src/command-ref/opt-common.md
index 51d7de18a..e612c416f 100644
--- a/doc/manual/src/command-ref/opt-common.md
+++ b/doc/manual/src/command-ref/opt-common.md
@@ -145,7 +145,7 @@ Most Nix commands accept the following command-line options:
expression evaluator will automatically try to call functions that
it encounters. It can automatically call functions for which every
argument has a [default
- value](../expressions/language-constructs.md#functions) (e.g.,
+ value](../language/constructs.md#functions) (e.g.,
`{ argName ? defaultValue }: ...`). With `--arg`, you can also
call functions that have arguments without a default value (or
override a default value). That is, if the evaluator encounters a
@@ -164,7 +164,7 @@ Most Nix commands accept the following command-line options:
So if you call this Nix expression (e.g., when you do `nix-env -iA
pkgname`), the function will be called automatically using the
- value [`builtins.currentSystem`](../expressions/builtins.md) for
+ value [`builtins.currentSystem`](../language/builtins.md) for
the `system` argument. You can override this using `--arg`, e.g.,
`nix-env -iA pkgname --arg system \"i686-freebsd\"`. (Note that
since the argument is a Nix string literal, you have to escape the
diff --git a/doc/manual/src/contributing/hacking.md b/doc/manual/src/contributing/hacking.md
index 59ce5cac7..9f7d5057b 100644
--- a/doc/manual/src/contributing/hacking.md
+++ b/doc/manual/src/contributing/hacking.md
@@ -42,7 +42,7 @@ $ nix develop
```
To get a shell with a different compilation environment (e.g. stdenv,
-gccStdenv, clangStdenv, clang11Stdenv):
+gccStdenv, clangStdenv, clang11Stdenv, ccacheStdenv):
```console
$ nix-shell -A devShells.x86_64-linux.clang11StdenvPackages
@@ -54,6 +54,9 @@ or if you have a flake-enabled nix:
$ nix develop .#clang11StdenvPackages
```
+Note: you can use `ccacheStdenv` to drastically improve rebuild
+time. By default, ccache keeps artifacts in `~/.cache/ccache/`.
+
To build Nix itself in this shell:
```console
@@ -83,9 +86,7 @@ by:
$ nix develop
```
-## Testing
-
-Nix comes with three different flavors of tests: unit, functional and integration.
+## Running tests
### Unit-tests
@@ -108,3 +109,72 @@ These tests include everything that needs to interact with external services or
Because these tests are expensive and require more than what the standard github-actions setup provides, they only run on the master branch (on <https://hydra.nixos.org/jobset/nix/master>).
You can run them manually with `nix build .#hydraJobs.tests.{testName}` or `nix-build -A hydraJobs.tests.{testName}`
+
+### Installer tests
+
+After a one-time setup, the Nix repository's GitHub Actions continuous integration (CI) workflow can test the installer each time you push to a branch.
+
+Creating a Cachix cache for your installer tests and adding its authorization token to GitHub enables [two installer-specific jobs in the CI workflow](https://github.com/NixOS/nix/blob/88a45d6149c0e304f6eb2efcc2d7a4d0d569f8af/.github/workflows/ci.yml#L50-L91):
+
+- The `installer` job generates installers for the platforms below and uploads them to your Cachix cache:
+ - `x86_64-linux`
+ - `armv6l-linux`
+ - `armv7l-linux`
+ - `x86_64-darwin`
+
+- The `installer_test` job (which runs on `ubuntu-latest` and `macos-latest`) will try to install Nix with the cached installer and run a trivial Nix command.
+
+#### One-time setup
+
+1. Have a GitHub account with a fork of the [Nix repository](https://github.com/NixOS/nix).
+2. At cachix.org:
+ - Create or log in to an account.
+ - Create a Cachix cache using the format `<github-username>-nix-install-tests`.
+ - Navigate to the new cache > Settings > Auth Tokens.
+ - Generate a new Cachix auth token and copy the generated value.
+3. At github.com:
+ - Navigate to your Nix fork > Settings > Secrets > Actions > New repository secret.
+ - Name the secret `CACHIX_AUTH_TOKEN`.
+ - Paste the copied value of the Cachix cache auth token.
+
+#### Using the CI-generated installer for manual testing
+
+After the CI run completes, you can check the output to extract the installer URL:
+1. Click into the detailed view of the CI run.
+2. Click into any `installer_test` run (the URL you're here to extract will be the same in all of them).
+3. Click into the `Run cachix/install-nix-action@v...` step and click the detail triangle next to the first log line (it will also be `Run cachix/install-nix-action@v...`)
+4. Copy the value of `install_url`
+5. To generate an install command, plug this `install_url` and your GitHub username into this template:
+
+ ```console
+ sh <(curl -L <install_url>) --tarball-url-prefix https://<github-username>-nix-install-tests.cachix.org/serve
+ ```
+
+<!-- #### Manually generating test installers
+
+There's obviously a manual way to do this, and it's still the only way for
+platforms that lack GA runners.
+
+I did do this back in Fall 2020 (before the GA approach encouraged here). I'll
+sketch what I recall in case it encourages someone to fill in detail, but: I
+didn't know what I was doing at the time and had to fumble/ask around a lot--
+so I don't want to uphold any of it as "right". It may have been dumb or
+the _hard_ way from the getgo. Fundamentals may have changed since.
+
+Here's the build command I used to do this on and for x86_64-darwin:
+nix build --out-link /tmp/foo ".#checks.x86_64-darwin.binaryTarball"
+
+I used the stable out-link to make it easier to script the next steps:
+link=$(readlink /tmp/foo)
+cp $link/*-darwin.tar.xz ~/somewheres
+
+I've lost the last steps and am just going from memory:
+
+From here, I think I had to extract and modify the `install` script to point
+it at this tarball (which I scped to my own site, but it might make more sense
+to just share them locally). I extracted this script once and then just
+search/replaced in it for each new build.
+
+The installer now supports a `--tarball-url-prefix` flag which _may_ have
+solved this need?
+-->
diff --git a/doc/manual/src/expressions/arguments-variables.md b/doc/manual/src/expressions/arguments-variables.md
deleted file mode 100644
index 12198c879..000000000
--- a/doc/manual/src/expressions/arguments-variables.md
+++ /dev/null
@@ -1,80 +0,0 @@
-# Arguments and Variables
-
-The [Nix expression for GNU Hello](expression-syntax.md) is a
-function; it is missing some arguments that have to be filled in
-somewhere. In the Nix Packages collection this is done in the file
-`pkgs/top-level/all-packages.nix`, where all Nix expressions for
-packages are imported and called with the appropriate arguments. Here
-are some fragments of `all-packages.nix`, with annotations of what
-they mean:
-
-```nix
-...
-
-rec { ①
-
- hello = import ../applications/misc/hello/ex-1 ② { ③
- inherit fetchurl stdenv perl;
- };
-
- perl = import ../development/interpreters/perl { ④
- inherit fetchurl stdenv;
- };
-
- fetchurl = import ../build-support/fetchurl {
- inherit stdenv; ...
- };
-
- stdenv = ...;
-
-}
-```
-
-1. This file defines a set of attributes, all of which are concrete
- derivations (i.e., not functions). In fact, we define a *mutually
- recursive* set of attributes. That is, the attributes can refer to
- each other. This is precisely what we want since we want to “plug”
- the various packages into each other.
-
-2. Here we *import* the Nix expression for GNU Hello. The import
- operation just loads and returns the specified Nix expression. In
- fact, we could just have put the contents of the Nix expression
- for GNU Hello in `all-packages.nix` at this point. That would be
- completely equivalent, but it would make `all-packages.nix` rather
- bulky.
-
- Note that we refer to `../applications/misc/hello/ex-1`, not
- `../applications/misc/hello/ex-1/default.nix`. When you try to
- import a directory, Nix automatically appends `/default.nix` to the
- file name.
-
-3. This is where the actual composition takes place. Here we *call* the
- function imported from `../applications/misc/hello/ex-1` with a set
- containing the things that the function expects, namely `fetchurl`,
- `stdenv`, and `perl`. We use inherit again to use the attributes
- defined in the surrounding scope (we could also have written
- `fetchurl = fetchurl;`, etc.).
-
- The result of this function call is an actual derivation that can be
- built by Nix (since when we fill in the arguments of the function,
- what we get is its body, which is the call to `stdenv.mkDerivation`
- in the [Nix expression for GNU Hello](expression-syntax.md)).
-
- > **Note**
- >
- > Nixpkgs has a convenience function `callPackage` that imports and
- > calls a function, filling in any missing arguments by passing the
- > corresponding attribute from the Nixpkgs set, like this:
- >
- > ```nix
- > hello = callPackage ../applications/misc/hello/ex-1 { };
- > ```
- >
- > If necessary, you can set or override arguments:
- >
- > ```nix
- > hello = callPackage ../applications/misc/hello/ex-1 { stdenv = myStdenv; };
- > ```
-
-4. Likewise, we have to instantiate Perl, `fetchurl`, and the standard
- environment.
diff --git a/doc/manual/src/expressions/build-script.md b/doc/manual/src/expressions/build-script.md
deleted file mode 100644
index b1eacae88..000000000
--- a/doc/manual/src/expressions/build-script.md
+++ /dev/null
@@ -1,70 +0,0 @@
-# Build Script
-
-Here is the builder referenced from Hello's Nix expression (stored in
-`pkgs/applications/misc/hello/ex-1/builder.sh`):
-
-```bash
-source $stdenv/setup ①
-
-PATH=$perl/bin:$PATH ②
-
-tar xvfz $src ③
-cd hello-*
-./configure --prefix=$out ④
-make ⑤
-make install
-```
-
-The builder can actually be made a lot shorter by using the *generic
-builder* functions provided by `stdenv`, but here we write out the build
-steps to elucidate what a builder does. It performs the following steps:
-
-1. When Nix runs a builder, it initially completely clears the
- environment (except for the attributes declared in the derivation).
- This is done to prevent undeclared inputs from being used in the
- build process. If for example the `PATH` contained `/usr/bin`, then
- you might accidentally use `/usr/bin/gcc`.
-
- So the first step is to set up the environment. This is done by
- calling the `setup` script of the standard environment. The
- environment variable `stdenv` points to the location of the
- standard environment being used. (It wasn't specified explicitly
- as an attribute in Hello's Nix expression, but `mkDerivation` adds
- it automatically.)
-
-2. Since Hello needs Perl, we have to make sure that Perl is in the
- `PATH`. The `perl` environment variable points to the location of
- the Perl package (since it was passed in as an attribute to the
- derivation), so `$perl/bin` is the directory containing the Perl
- interpreter.
-
-3. Now we have to unpack the sources. The `src` attribute was bound to
- the result of fetching the Hello source tarball from the network, so
- the `src` environment variable points to the location in the Nix
- store to which the tarball was downloaded. After unpacking, we `cd`
- to the resulting source directory.
-
- The whole build is performed in a temporary directory created in
- `/tmp`, by the way. This directory is removed after the builder
- finishes, so there is no need to clean up the sources afterwards.
- Also, the temporary directory is always newly created, so you don't
- have to worry about files from previous builds interfering with the
- current build.
-
-4. GNU Hello is a typical Autoconf-based package, so we first have to
- run its `configure` script. In Nix every package is stored in a
- separate location in the Nix store, for instance
- `/nix/store/9a54ba97fb71b65fda531012d0443ce2-hello-2.1.1`. Nix
- computes this path by cryptographically hashing all attributes of
- the derivation. The path is passed to the builder through the `out`
- environment variable. So here we give `configure` the parameter
- `--prefix=$out` to cause Hello to be installed in the expected
- location.
-
-5. Finally we build Hello (`make`) and install it into the location
- specified by `out` (`make install`).
-
-If you are wondering about the absence of error checking on the result
-of various commands called in the builder: this is because the shell
-script is evaluated with Bash's `-e` option, which causes the script to
-be aborted if any command fails without an error check.
diff --git a/doc/manual/src/expressions/expression-language.md b/doc/manual/src/expressions/expression-language.md
deleted file mode 100644
index 267fcb983..000000000
--- a/doc/manual/src/expressions/expression-language.md
+++ /dev/null
@@ -1,12 +0,0 @@
-# Nix Expression Language
-
-The Nix expression language is a pure, lazy, functional language. Purity
-means that operations in the language don't have side-effects (for
-instance, there is no variable assignment). Laziness means that
-arguments to functions are evaluated only when they are needed.
-Functional means that functions are “normal” values that can be passed
-around and manipulated in interesting ways. The language is not a
-full-featured, general purpose language. Its main job is to describe
-packages, compositions of packages, and the variability within packages.
-
-This section presents the various features of the language.
diff --git a/doc/manual/src/expressions/expression-syntax.md b/doc/manual/src/expressions/expression-syntax.md
deleted file mode 100644
index 6b93e692c..000000000
--- a/doc/manual/src/expressions/expression-syntax.md
+++ /dev/null
@@ -1,93 +0,0 @@
-# Expression Syntax
-
-Here is a Nix expression for GNU Hello:
-
-```nix
-{ stdenv, fetchurl, perl }: ①
-
-stdenv.mkDerivation { ②
- name = "hello-2.1.1"; ③
- builder = ./builder.sh; ④
- src = fetchurl { ⑤
- url = "ftp://ftp.nluug.nl/pub/gnu/hello/hello-2.1.1.tar.gz";
- sha256 = "1md7jsfd8pa45z73bz1kszpp01yw6x5ljkjk2hx7wl800any6465";
- };
- inherit perl; ⑥
-}
-```
-
-This file is actually already in the Nix Packages collection in
-`pkgs/applications/misc/hello/ex-1/default.nix`. It is customary to
-place each package in a separate directory and call the single Nix
-expression in that directory `default.nix`. The file has the following
-elements (referenced from the figure by number):
-
-1. This states that the expression is a *function* that expects to be
- called with three arguments: `stdenv`, `fetchurl`, and `perl`. They
- are needed to build Hello, but we don't know how to build them here;
- that's why they are function arguments. `stdenv` is a package that
- is used by almost all Nix Packages; it provides a
- “standard” environment consisting of the things you would expect
- in a basic Unix environment: a C/C++ compiler (GCC, to be precise),
- the Bash shell, fundamental Unix tools such as `cp`, `grep`, `tar`,
- etc. `fetchurl` is a function that downloads files. `perl` is the
- Perl interpreter.
-
- Nix functions generally have the form `{ x, y, ..., z }: e` where
- `x`, `y`, etc. are the names of the expected arguments, and where
- *e* is the body of the function. So here, the entire remainder of
- the file is the body of the function; when given the required
- arguments, the body should describe how to build an instance of
- the Hello package.
-
-2. So we have to build a package. Building something from other stuff
- is called a *derivation* in Nix (as opposed to sources, which are
- built by humans instead of computers). We perform a derivation by
- calling `stdenv.mkDerivation`. `mkDerivation` is a function
- provided by `stdenv` that builds a package from a set of
- *attributes*. A set is just a list of key/value pairs where each
- key is a string and each value is an arbitrary Nix
- expression. They take the general form `{ name1 = expr1; ...
- nameN = exprN; }`.
-
-3. The attribute `name` specifies the symbolic name and version of
- the package. Nix doesn't really care about these things, but they
- are used by for instance `nix-env -q` to show a “human-readable”
- name for packages. This attribute is required by `mkDerivation`.
-
-4. The attribute `builder` specifies the builder. This attribute can
- sometimes be omitted, in which case `mkDerivation` will fill in a
- default builder (which does a `configure; make; make install`, in
- essence). Hello is sufficiently simple that the default builder
- would suffice, but in this case, we will show an actual builder
- for educational purposes. The value `./builder.sh` refers to the
- shell script shown in the [next section](build-script.md),
- discussed below.
-
-5. The builder has to know what the sources of the package are. Here,
- the attribute `src` is bound to the result of a call to the
- `fetchurl` function. Given a URL and a SHA-256 hash of the expected
- contents of the file at that URL, this function builds a derivation
- that downloads the file and checks its hash. So the sources are a
- dependency that like all other dependencies is built before Hello
- itself is built.
-
- Instead of `src` any other name could have been used, and in fact
- there can be any number of sources (bound to different attributes).
- However, `src` is customary, and it's also expected by the default
- builder (which we don't use in this example).
-
-6. Since the derivation requires Perl, we have to pass the value of the
- `perl` function argument to the builder. All attributes in the set
- are actually passed as environment variables to the builder, so
- declaring an attribute
-
- ```nix
- perl = perl;
- ```
-
- will do the trick: it binds an attribute `perl` to the function
- argument which also happens to be called `perl`. However, it looks a
- bit silly, so there is a shorter syntax. The `inherit` keyword
- causes the specified attributes to be bound to whatever variables
- with the same name happen to be in scope.
diff --git a/doc/manual/src/expressions/generic-builder.md b/doc/manual/src/expressions/generic-builder.md
deleted file mode 100644
index cf26b5f82..000000000
--- a/doc/manual/src/expressions/generic-builder.md
+++ /dev/null
@@ -1,66 +0,0 @@
-# Generic Builder Syntax
-
-Recall that the [build script for GNU Hello](build-script.md) looked
-something like this:
-
-```bash
-PATH=$perl/bin:$PATH
-tar xvfz $src
-cd hello-*
-./configure --prefix=$out
-make
-make install
-```
-
-The builders for almost all Unix packages look like this — set up some
-environment variables, unpack the sources, configure, build, and
-install. For this reason the standard environment provides some Bash
-functions that automate the build process. Here is what a builder using
-the generic build facilities looks like:
-
-```bash
-buildInputs="$perl" ①
-
-source $stdenv/setup ②
-
-genericBuild ③
-```
-
-Here is what each line means:
-
-1. The `buildInputs` variable tells `setup` to use the indicated
- packages as “inputs”. This means that if a package provides a `bin`
- subdirectory, it's added to `PATH`; if it has a `include`
- subdirectory, it's added to GCC's header search path; and so on.
- (This is implemented in a modular way: `setup` tries to source the
- file `pkg/nix-support/setup-hook` of all dependencies. These “setup
- hooks” can then set up whatever environment variables they want; for
- instance, the setup hook for Perl sets the `PERL5LIB` environment
- variable to contain the `lib/site_perl` directories of all inputs.)
-
-2. The function `genericBuild` is defined in the file `$stdenv/setup`.
-
-3. The final step calls the shell function `genericBuild`, which
- performs the steps that were done explicitly in the previous build
- script. The generic builder is smart enough to figure out whether
- to unpack the sources using `gzip`, `bzip2`, etc. It can be
- customised in many ways; see the Nixpkgs manual for details.
-
-Discerning readers will note that the `buildInputs` could just as well
-have been set in the Nix expression, like this:
-
-```nix
- buildInputs = [ perl ];
-```
-
-The `perl` attribute can then be removed, and the builder becomes even
-shorter:
-
-```bash
-source $stdenv/setup
-genericBuild
-```
-
-In fact, `mkDerivation` provides a default builder that looks exactly
-like that, so it is actually possible to omit the builder for Hello
-entirely.
diff --git a/doc/manual/src/expressions/language-values.md b/doc/manual/src/expressions/language-values.md
deleted file mode 100644
index 75ae9f2eb..000000000
--- a/doc/manual/src/expressions/language-values.md
+++ /dev/null
@@ -1,251 +0,0 @@
-# Values
-
-## Simple Values
-
-Nix has the following basic data types:
-
- - *Strings* can be written in three ways.
-
- The most common way is to enclose the string between double quotes,
- e.g., `"foo bar"`. Strings can span multiple lines. The special
- characters `"` and `\` and the character sequence `${` must be
- escaped by prefixing them with a backslash (`\`). Newlines, carriage
- returns and tabs can be written as `\n`, `\r` and `\t`,
- respectively.
-
- You can include the result of an expression into a string by
- enclosing it in `${...}`, a feature known as *antiquotation*. The
- enclosed expression must evaluate to something that can be coerced
- into a string (meaning that it must be a string, a path, or a
- derivation). For instance, rather than writing
-
- ```nix
- "--with-freetype2-library=" + freetype + "/lib"
- ```
-
- (where `freetype` is a derivation), you can instead write the more
- natural
-
- ```nix
- "--with-freetype2-library=${freetype}/lib"
- ```
-
- The latter is automatically translated to the former. A more
- complicated example (from the Nix expression for
- [Qt](http://www.trolltech.com/products/qt)):
-
- ```nix
- configureFlags = "
- -system-zlib -system-libpng -system-libjpeg
- ${if openglSupport then "-dlopen-opengl
- -L${mesa}/lib -I${mesa}/include
- -L${libXmu}/lib -I${libXmu}/include" else ""}
- ${if threadSupport then "-thread" else "-no-thread"}
- ";
- ```
-
- Note that Nix expressions and strings can be arbitrarily nested; in
- this case the outer string contains various antiquotations that
- themselves contain strings (e.g., `"-thread"`), some of which in
- turn contain expressions (e.g., `${mesa}`).
-
- The second way to write string literals is as an *indented string*,
- which is enclosed between pairs of *double single-quotes*, like so:
-
- ```nix
- ''
- This is the first line.
- This is the second line.
- This is the third line.
- ''
- ```
-
- This kind of string literal intelligently strips indentation from
- the start of each line. To be precise, it strips from each line a
- number of spaces equal to the minimal indentation of the string as a
- whole (disregarding the indentation of empty lines). For instance,
- the first and second line are indented two spaces, while the third
- line is indented four spaces. Thus, two spaces are stripped from
- each line, so the resulting string is
-
- ```nix
- "This is the first line.\nThis is the second line.\n This is the third line.\n"
- ```
-
- Note that the whitespace and newline following the opening `''` is
- ignored if there is no non-whitespace text on the initial line.
-
- Antiquotation (`${expr}`) is supported in indented strings.
-
- Since `${` and `''` have special meaning in indented strings, you
- need a way to quote them. `$` can be escaped by prefixing it with
- `''` (that is, two single quotes), i.e., `''$`. `''` can be escaped
- by prefixing it with `'`, i.e., `'''`. `$` removes any special
- meaning from the following `$`. Linefeed, carriage-return and tab
- characters can be written as `''\n`, `''\r`, `''\t`, and `''\`
- escapes any other character.
-
- Indented strings are primarily useful in that they allow multi-line
- string literals to follow the indentation of the enclosing Nix
- expression, and that less escaping is typically necessary for
- strings representing languages such as shell scripts and
- configuration files because `''` is much less common than `"`.
- Example:
-
- ```nix
- stdenv.mkDerivation {
- ...
- postInstall =
- ''
- mkdir $out/bin $out/etc
- cp foo $out/bin
- echo "Hello World" > $out/etc/foo.conf
- ${if enableBar then "cp bar $out/bin" else ""}
- '';
- ...
- }
- ```
-
- Finally, as a convenience, *URIs* as defined in appendix B of
- [RFC 2396](http://www.ietf.org/rfc/rfc2396.txt) can be written *as
- is*, without quotes. For instance, the string
- `"http://example.org/foo.tar.bz2"` can also be written as
- `http://example.org/foo.tar.bz2`.
-
- - Numbers, which can be *integers* (like `123`) or *floating point*
- (like `123.43` or `.27e13`).
-
- Numbers are type-compatible: pure integer operations will always
- return integers, whereas any operation involving at least one
- floating point number will have a floating point number as a result.
-
- - *Paths*, e.g., `/bin/sh` or `./builder.sh`. A path must contain at
- least one slash to be recognised as such. For instance, `builder.sh`
- is not a path: it's parsed as an expression that selects the
- attribute `sh` from the variable `builder`. If the file name is
- relative, i.e., if it does not begin with a slash, it is made
- absolute at parse time relative to the directory of the Nix
- expression that contained it. For instance, if a Nix expression in
- `/foo/bar/bla.nix` refers to `../xyzzy/fnord.nix`, the absolute path
- is `/foo/xyzzy/fnord.nix`.
-
- If the first component of a path is a `~`, it is interpreted as if
- the rest of the path were relative to the user's home directory.
- e.g. `~/foo` would be equivalent to `/home/edolstra/foo` for a user
- whose home directory is `/home/edolstra`.
-
- Paths can also be specified between angle brackets, e.g.
- `<nixpkgs>`. This means that the directories listed in the
- environment variable `NIX_PATH` will be searched for the given file
- or directory name.
-
- Antiquotation is supported in any paths except those in angle brackets.
- `./${foo}-${bar}.nix` is a more convenient way of writing
- `./. + "/" + foo + "-" + bar + ".nix"` or `./. + "/${foo}-${bar}.nix"`. At
- least one slash must appear *before* any antiquotations for this to be
- recognized as a path. `a.${foo}/b.${bar}` is a syntactically valid division
- operation. `./a.${foo}/b.${bar}` is a path.
-
- - *Booleans* with values `true` and `false`.
-
- - The null value, denoted as `null`.
-
-## Lists
-
-Lists are formed by enclosing a whitespace-separated list of values
-between square brackets. For example,
-
-```nix
-[ 123 ./foo.nix "abc" (f { x = y; }) ]
-```
-
-defines a list of four elements, the last being the result of a call to
-the function `f`. Note that function calls have to be enclosed in
-parentheses. If they had been omitted, e.g.,
-
-```nix
-[ 123 ./foo.nix "abc" f { x = y; } ]
-```
-
-the result would be a list of five elements, the fourth one being a
-function and the fifth being a set.
-
-Note that lists are only lazy in values, and they are strict in length.
-
-## Sets
-
-Sets are really the core of the language, since ultimately the Nix
-language is all about creating derivations, which are really just sets
-of attributes to be passed to build scripts.
-
-Sets are just a list of name/value pairs (called *attributes*) enclosed
-in curly brackets, where each value is an arbitrary expression
-terminated by a semicolon. For example:
-
-```nix
-{ x = 123;
- text = "Hello";
- y = f { bla = 456; };
-}
-```
-
-This defines a set with attributes named `x`, `text`, `y`. The order of
-the attributes is irrelevant. An attribute name may only occur once.
-
-Attributes can be selected from a set using the `.` operator. For
-instance,
-
-```nix
-{ a = "Foo"; b = "Bar"; }.a
-```
-
-evaluates to `"Foo"`. It is possible to provide a default value in an
-attribute selection using the `or` keyword. For example,
-
-```nix
-{ a = "Foo"; b = "Bar"; }.c or "Xyzzy"
-```
-
-will evaluate to `"Xyzzy"` because there is no `c` attribute in the set.
-
-You can use arbitrary double-quoted strings as attribute names:
-
-```nix
-{ "foo ${bar}" = 123; "nix-1.0" = 456; }."foo ${bar}"
-```
-
-This will evaluate to `123` (Assuming `bar` is antiquotable). In the
-case where an attribute name is just a single antiquotation, the quotes
-can be dropped:
-
-```nix
-{ foo = 123; }.${bar} or 456
-```
-
-This will evaluate to `123` if `bar` evaluates to `"foo"` when coerced
-to a string and `456` otherwise (again assuming `bar` is antiquotable).
-
-In the special case where an attribute name inside of a set declaration
-evaluates to `null` (which is normally an error, as `null` is not
-antiquotable), that attribute is simply not added to the set:
-
-```nix
-{ ${if foo then "bar" else null} = true; }
-```
-
-This will evaluate to `{}` if `foo` evaluates to `false`.
-
-A set that has a `__functor` attribute whose value is callable (i.e. is
-itself a function or a set with a `__functor` attribute whose value is
-callable) can be applied as if it were a function, with the set itself
-passed in first , e.g.,
-
-```nix
-let add = { __functor = self: x: x + self.x; };
- inc = add // { x = 1; };
-in inc 1
-```
-
-evaluates to `2`. This can be used to attach metadata to a function
-without the caller needing to treat it specially, or to implement a form
-of object-oriented programming, for example.
diff --git a/doc/manual/src/expressions/simple-building-testing.md b/doc/manual/src/expressions/simple-building-testing.md
deleted file mode 100644
index 7f0d8f841..000000000
--- a/doc/manual/src/expressions/simple-building-testing.md
+++ /dev/null
@@ -1,61 +0,0 @@
-# Building and Testing
-
-You can now try to build Hello. Of course, you could do `nix-env -f . -iA
-hello`, but you may not want to install a possibly broken package just
-yet. The best way to test the package is by using the command
-`nix-build`, which builds a Nix expression and creates a symlink named
-`result` in the current directory:
-
-```console
-$ nix-build -A hello
-building path `/nix/store/632d2b22514d...-hello-2.1.1'
-hello-2.1.1/
-hello-2.1.1/intl/
-hello-2.1.1/intl/ChangeLog
-...
-
-$ ls -l result
-lrwxrwxrwx ... 2006-09-29 10:43 result -> /nix/store/632d2b22514d...-hello-2.1.1
-
-$ ./result/bin/hello
-Hello, world!
-```
-
-The `-A` option selects the `hello` attribute. This is faster than
-using the symbolic package name specified by the `name` attribute
-(which also happens to be `hello`) and is unambiguous (there can be
-multiple packages with the symbolic name `hello`, but there can be
-only one attribute in a set named `hello`).
-
-`nix-build` registers the `./result` symlink as a garbage collection
-root, so unless and until you delete the `./result` symlink, the output
-of the build will be safely kept on your system. You can use
-`nix-build`’s `-o` switch to give the symlink another name.
-
-Nix has transactional semantics. Once a build finishes successfully, Nix
-makes a note of this in its database: it registers that the path denoted
-by `out` is now “valid”. If you try to build the derivation again, Nix
-will see that the path is already valid and finish immediately. If a
-build fails, either because it returns a non-zero exit code, because Nix
-or the builder are killed, or because the machine crashes, then the
-output paths will not be registered as valid. If you try to build the
-derivation again, Nix will remove the output paths if they exist (e.g.,
-because the builder died half-way through `make
-install`) and try again. Note that there is no “negative caching”: Nix
-doesn't remember that a build failed, and so a failed build can always
-be repeated. This is because Nix cannot distinguish between permanent
-failures (e.g., a compiler error due to a syntax error in the source)
-and transient failures (e.g., a disk full condition).
-
-Nix also performs locking. If you run multiple Nix builds
-simultaneously, and they try to build the same derivation, the first Nix
-instance that gets there will perform the build, while the others block
-(or perform other derivations if available) until the build finishes:
-
-```console
-$ nix-build -A hello
-waiting for lock on `/nix/store/0h5b7hp8d4hqfrw8igvx97x1xawrjnac-hello-2.1.1x'
-```
-
-So it is always safe to run multiple instances of Nix in parallel (which
-isn’t the case with, say, `make`).
diff --git a/doc/manual/src/expressions/simple-expression.md b/doc/manual/src/expressions/simple-expression.md
deleted file mode 100644
index 857f71b9b..000000000
--- a/doc/manual/src/expressions/simple-expression.md
+++ /dev/null
@@ -1,23 +0,0 @@
-# A Simple Nix Expression
-
-This section shows how to add and test the [GNU Hello
-package](http://www.gnu.org/software/hello/hello.html) to the Nix
-Packages collection. Hello is a program that prints out the text “Hello,
-world\!”.
-
-To add a package to the Nix Packages collection, you generally need to
-do three things:
-
-1. Write a Nix expression for the package. This is a file that
- describes all the inputs involved in building the package, such as
- dependencies, sources, and so on.
-
-2. Write a *builder*. This is a shell script that builds the package
- from the inputs. (In fact, it can be written in any language, but
- typically it's a `bash` shell script.)
-
-3. Add the package to the file `pkgs/top-level/all-packages.nix`. The
- Nix expression written in the first step is a *function*; it
- requires other packages in order to build it. In this step you put
- it all together, i.e., you call the function with the right
- arguments to build the actual package.
diff --git a/doc/manual/src/expressions/writing-nix-expressions.md b/doc/manual/src/expressions/writing-nix-expressions.md
deleted file mode 100644
index 5664108e7..000000000
--- a/doc/manual/src/expressions/writing-nix-expressions.md
+++ /dev/null
@@ -1,12 +0,0 @@
-This chapter shows you how to write Nix expressions, which instruct Nix
-how to build packages. It starts with a simple example (a Nix expression
-for GNU Hello), and then moves on to a more in-depth look at the Nix
-expression language.
-
-> **Note**
->
-> This chapter is mostly about the Nix expression language. For more
-> extensive information on adding packages to the Nix Packages
-> collection (such as functions in the standard environment and coding
-> conventions), please consult [its
-> manual](http://nixos.org/nixpkgs/manual/).
diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md
index 3448b971b..70a0eb994 100644
--- a/doc/manual/src/glossary.md
+++ b/doc/manual/src/glossary.md
@@ -3,14 +3,48 @@
- [derivation]{#gloss-derivation}\
A description of a build action. The result of a derivation is a
store object. Derivations are typically specified in Nix expressions
- using the [`derivation` primitive](expressions/derivations.md). These are
+ using the [`derivation` primitive](language/derivations.md). These are
translated into low-level *store derivations* (implicitly by
`nix-env` and `nix-build`, or explicitly by `nix-instantiate`).
+ - [content-addressed derivation]{#gloss-content-addressed-derivation}\
+ A derivation which has the
+ [`__contentAddressed`](language/advanced-attributes.md#adv-attr-__contentAddressed)
+ attribute set to `true`.
+
+ - [fixed-output derivation]{#gloss-fixed-output-derivation}\
+ A derivation which includes the
+ [`outputHash`](language/advanced-attributes.md#adv-attr-outputHash) attribute.
+
- [store]{#gloss-store}\
The location in the file system where store objects live. Typically
`/nix/store`.
+ From the perspective of the location where Nix is
+ invoked, the Nix store can be referred to
+ as a "_local_" or a "_remote_" one:
+
+ + A *local store* exists on the filesystem of
+ the machine where Nix is invoked. You can use other
+ local stores by passing the `--store` flag to the
+ `nix` command. Local stores can be used for building derivations.
+
+ + A *remote store* exists anywhere other than the
+ local filesystem. One example is the `/nix/store`
+ directory on another machine, accessed via `ssh` or
+ served by the `nix-serve` Perl script.
+
+ - [chroot store]{#gloss-chroot-store}\
+ A local store whose canonical path is anything other than `/nix/store`.
+
+ - [binary cache]{#gloss-binary-cache}\
+ A *binary cache* is a Nix store which uses a different format: its
+ metadata and signatures are kept in `.narinfo` files rather than in a
+ Nix database. This different format simplifies serving store objects
+ over the network, but cannot host builds. Examples of binary caches
+ include S3 buckets and the [NixOS binary
+ cache](https://cache.nixos.org).
+
- [store path]{#gloss-store-path}\
The location in the file system of a store object, i.e., an
immediate child of the Nix store directory.
@@ -22,6 +56,19 @@
derivation outputs (objects produced by running a build action), or
derivations (files describing a build action).
+ - [input-addressed store object]{#gloss-input-addressed-store-object}\
+ A store object produced by building a
+ non-[content-addressed](#gloss-content-addressed-derivation),
+ non-[fixed-output](#gloss-fixed-output-derivation)
+ derivation.
+
+ - [output-addressed store object]{#gloss-output-addressed-store-object}\
+ A store object whose store path hashes its content. This
+ includes derivations, the outputs of
+ [content-addressed derivations](#gloss-content-addressed-derivation),
+ and the outputs of
+ [fixed-output derivations](#gloss-fixed-output-derivation).
+
- [substitute]{#gloss-substitute}\
A substitute is a command invocation stored in the Nix database that
describes how to build a store object, bypassing the normal build
@@ -29,6 +76,11 @@
store object by downloading a pre-built version of the store object
from some server.
+ - [substituter]{#gloss-substituter}\
+ A *substituter* is an additional store from which Nix will
+ copy store objects it doesn't have. For details, see the
+ [`substituters` option](command-ref/conf-file.html#conf-substituters).
+
- [purity]{#gloss-purity}\
The assumption that equal Nix derivations when run always produce
the same output. This cannot be guaranteed in general (e.g., a
diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md
index 9fb9c80c3..2d007ca1b 100644
--- a/doc/manual/src/installation/installing-binary.md
+++ b/doc/manual/src/installation/installing-binary.md
@@ -13,7 +13,7 @@ for your platform:
- multi-user on macOS
> **Notes on read-only filesystem root in macOS 10.15 Catalina +**
- >
+ >
> - It took some time to support this cleanly. You may see posts,
> examples, and tutorials using obsolete workarounds.
> - Supporting it cleanly made macOS installs too complex to qualify
@@ -31,8 +31,8 @@ $ sh <(curl -L https://nixos.org/nix/install) --no-daemon
```
This will perform a single-user installation of Nix, meaning that `/nix`
-is owned by the invoking user. You should run this under your usual user
-account, *not* as root. The script will invoke `sudo` to create `/nix`
+is owned by the invoking user. You can run this under your usual user
+account or root. The script will invoke `sudo` to create `/nix`
if it doesn’t already exist. If you don’t have `sudo`, you should
manually create `/nix` first as root, e.g.:
@@ -71,11 +71,11 @@ $ sh <(curl -L https://nixos.org/nix/install) --daemon
The multi-user installation of Nix will create build users between the
user IDs 30001 and 30032, and a group with the group ID 30000. You
-should run this under your usual user account, *not* as root. The script
+can run this under your usual user account or root. The script
will invoke `sudo` as needed.
> **Note**
->
+>
> If you need Nix to use a different group ID or user ID set, you will
> have to download the tarball manually and [edit the install
> script](#installing-from-a-binary-tarball).
@@ -148,7 +148,8 @@ and `/etc/zshrc` which you may remove.
This will remove all the build users that no longer serve a purpose.
4. Edit fstab using `sudo vifs` to remove the line mounting the Nix Store
- volume on `/nix`, which looks like this,
+ volume on `/nix`, which looks like
+ `UUID=<uuid> /nix apfs rw,noauto,nobrowse,suid,owners` or
`LABEL=Nix\040Store /nix apfs rw,nobrowse`. This will prevent automatic
mounting of the Nix Store volume.
@@ -167,7 +168,7 @@ and `/etc/zshrc` which you may remove.
removed next.
7. Remove the Nix Store volume:
-
+
```console
sudo diskutil apfs deleteVolume /nix
```
@@ -175,8 +176,20 @@ and `/etc/zshrc` which you may remove.
This will remove the Nix Store volume and everything that was added to the
store.
+ If the output indicates that the command couldn't remove the volume, you should
+ make sure you don't have an _unmounted_ Nix Store volume. Look for a
+ "Nix Store" volume in the output of the following command:
+
+ ```console
+ diskutil list
+ ```
+
+ If you _do_ see a "Nix Store" volume, delete it by re-running the diskutil
+ deleteVolume command, but replace `/nix` with the store volume's `diskXsY`
+ identifier.
+
> **Note**
->
+>
> After you complete the steps here, you will still have an empty `/nix`
> directory. This is an expected sign of a successful uninstall. The empty
> `/nix` directory will disappear the next time you reboot.
@@ -191,8 +204,7 @@ and `/etc/zshrc` which you may remove.
<!-- Note: anchors above to catch permalinks to old explanations -->
We believe we have ironed out how to cleanly support the read-only root
-on modern macOS. New installs will do this automatically, and you can
-also re-run a new installer to convert your existing setup.
+on modern macOS. New installs will do this automatically.
This section previously detailed the situation, options, and trade-offs,
but it now only outlines what the installer does. You don't need to know
diff --git a/doc/manual/src/expressions/advanced-attributes.md b/doc/manual/src/language/advanced-attributes.md
index 2e7e80ed0..2e7e80ed0 100644
--- a/doc/manual/src/expressions/advanced-attributes.md
+++ b/doc/manual/src/language/advanced-attributes.md
diff --git a/doc/manual/src/expressions/builtin-constants.md b/doc/manual/src/language/builtin-constants.md
index 78d066a82..78d066a82 100644
--- a/doc/manual/src/expressions/builtin-constants.md
+++ b/doc/manual/src/language/builtin-constants.md
diff --git a/doc/manual/src/expressions/builtins-prefix.md b/doc/manual/src/language/builtins-prefix.md
index c631a8453..c631a8453 100644
--- a/doc/manual/src/expressions/builtins-prefix.md
+++ b/doc/manual/src/language/builtins-prefix.md
diff --git a/doc/manual/src/expressions/builtins-suffix.md b/doc/manual/src/language/builtins-suffix.md
index a74db2857..a74db2857 100644
--- a/doc/manual/src/expressions/builtins-suffix.md
+++ b/doc/manual/src/language/builtins-suffix.md
diff --git a/doc/manual/src/expressions/language-constructs.md b/doc/manual/src/language/constructs.md
index 1c01f2cc7..1c01f2cc7 100644
--- a/doc/manual/src/expressions/language-constructs.md
+++ b/doc/manual/src/language/constructs.md
diff --git a/doc/manual/src/expressions/derivations.md b/doc/manual/src/language/derivations.md
index 3391ec0d8..3391ec0d8 100644
--- a/doc/manual/src/expressions/derivations.md
+++ b/doc/manual/src/language/derivations.md
diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md
new file mode 100644
index 000000000..a4b402f8b
--- /dev/null
+++ b/doc/manual/src/language/index.md
@@ -0,0 +1,33 @@
+# Nix Language
+
+The Nix language is
+
+- *domain-specific*
+
+ It only exists for the Nix package manager:
+ to describe packages and configurations as well as their variants and compositions.
+ It is not intended for general purpose use.
+
+- *declarative*
+
+ There is no notion of executing sequential steps.
+ Dependencies between operations are established only through data.
+
+- *pure*
+
+ Values cannot change during computation.
+ Functions always produce the same output if their input does not change.
+
+- *functional*
+
+ Functions are like any other value.
+ Functions can be assigned to names, taken as arguments, or returned by functions.
+
+- *lazy*
+
+ Expressions are only evaluated when their value is needed.
+
+- *dynamically typed*
+
+ Type errors are only detected when expressions are evaluated.
+
diff --git a/doc/manual/src/expressions/language-operators.md b/doc/manual/src/language/operators.md
index 268b44f4c..32398189d 100644
--- a/doc/manual/src/expressions/language-operators.md
+++ b/doc/manual/src/language/operators.md
@@ -1,6 +1,6 @@
# Operators
-The table below lists the operators in the Nix expression language, in
+The table below lists the operators in the Nix language, in
order of precedence (from strongest to weakest binding).
| Name | Syntax | Associativity | Description | Precedence |
diff --git a/doc/manual/src/language/values.md b/doc/manual/src/language/values.md
new file mode 100644
index 000000000..6fc8c0369
--- /dev/null
+++ b/doc/manual/src/language/values.md
@@ -0,0 +1,275 @@
+# Data Types
+
+## Primitives
+
+- <a id="type-string" href="#type-string">String</a>
+
+ *Strings* can be written in three ways.
+
+ The most common way is to enclose the string between double quotes,
+ e.g., `"foo bar"`. Strings can span multiple lines. The special
+ characters `"` and `\` and the character sequence `${` must be
+ escaped by prefixing them with a backslash (`\`). Newlines, carriage
+ returns and tabs can be written as `\n`, `\r` and `\t`,
+ respectively.
+
+ You can include the result of an expression into a string by
+ enclosing it in `${...}`, a feature known as *antiquotation*. The
+ enclosed expression must evaluate to something that can be coerced
+ into a string (meaning that it must be a string, a path, or a
+ derivation). For instance, rather than writing
+
+ ```nix
+ "--with-freetype2-library=" + freetype + "/lib"
+ ```
+
+ (where `freetype` is a derivation), you can instead write the more
+ natural
+
+ ```nix
+ "--with-freetype2-library=${freetype}/lib"
+ ```
+
+ The latter is automatically translated to the former. A more
+ complicated example (from the Nix expression for
+ [Qt](http://www.trolltech.com/products/qt)):
+
+ ```nix
+ configureFlags = "
+ -system-zlib -system-libpng -system-libjpeg
+ ${if openglSupport then "-dlopen-opengl
+ -L${mesa}/lib -I${mesa}/include
+ -L${libXmu}/lib -I${libXmu}/include" else ""}
+ ${if threadSupport then "-thread" else "-no-thread"}
+ ";
+ ```
+
+ Note that Nix expressions and strings can be arbitrarily nested; in
+ this case the outer string contains various antiquotations that
+ themselves contain strings (e.g., `"-thread"`), some of which in
+ turn contain expressions (e.g., `${mesa}`).
+
+ The second way to write string literals is as an *indented string*,
+ which is enclosed between pairs of *double single-quotes*, like so:
+
+ ```nix
+ ''
+ This is the first line.
+ This is the second line.
+ This is the third line.
+ ''
+ ```
+
+ This kind of string literal intelligently strips indentation from
+ the start of each line. To be precise, it strips from each line a
+ number of spaces equal to the minimal indentation of the string as a
+ whole (disregarding the indentation of empty lines). For instance,
+ the first and second line are indented two spaces, while the third
+ line is indented four spaces. Thus, two spaces are stripped from
+ each line, so the resulting string is
+
+ ```nix
+ "This is the first line.\nThis is the second line.\n This is the third line.\n"
+ ```
+
+ Note that the whitespace and newline following the opening `''` is
+ ignored if there is no non-whitespace text on the initial line.
+
+ Antiquotation (`${expr}`) is supported in indented strings.
+
+ Since `${` and `''` have special meaning in indented strings, you
+ need a way to quote them. `$` can be escaped by prefixing it with
+ `''` (that is, two single quotes), i.e., `''$`. `''` can be escaped
+ by prefixing it with `'`, i.e., `'''`. `$` removes any special
+ meaning from the following `$`. Linefeed, carriage-return and tab
+ characters can be written as `''\n`, `''\r`, `''\t`, and `''\`
+ escapes any other character.
+
+ Indented strings are primarily useful in that they allow multi-line
+ string literals to follow the indentation of the enclosing Nix
+ expression, and that less escaping is typically necessary for
+ strings representing languages such as shell scripts and
+ configuration files because `''` is much less common than `"`.
+ Example:
+
+ ```nix
+ stdenv.mkDerivation {
+ ...
+ postInstall =
+ ''
+ mkdir $out/bin $out/etc
+ cp foo $out/bin
+ echo "Hello World" > $out/etc/foo.conf
+ ${if enableBar then "cp bar $out/bin" else ""}
+ '';
+ ...
+ }
+ ```
+
+ Finally, as a convenience, *URIs* as defined in appendix B of
+ [RFC 2396](http://www.ietf.org/rfc/rfc2396.txt) can be written *as
+ is*, without quotes. For instance, the string
+ `"http://example.org/foo.tar.bz2"` can also be written as
+ `http://example.org/foo.tar.bz2`.
+
+- <a id="type-number" href="#type-number">Number</a>
+
+ Numbers, which can be *integers* (like `123`) or *floating point*
+ (like `123.43` or `.27e13`).
+
+ Numbers are type-compatible: pure integer operations will always
+ return integers, whereas any operation involving at least one
+ floating point number will have a floating point number as a result.
+
+- <a id="type-path" href="#type-path">Path</a>
+
+ *Paths*, e.g., `/bin/sh` or `./builder.sh`. A path must contain at
+ least one slash to be recognised as such. For instance, `builder.sh`
+ is not a path: it's parsed as an expression that selects the
+ attribute `sh` from the variable `builder`. If the file name is
+ relative, i.e., if it does not begin with a slash, it is made
+ absolute at parse time relative to the directory of the Nix
+ expression that contained it. For instance, if a Nix expression in
+ `/foo/bar/bla.nix` refers to `../xyzzy/fnord.nix`, the absolute path
+ is `/foo/xyzzy/fnord.nix`.
+
+ If the first component of a path is a `~`, it is interpreted as if
+ the rest of the path were relative to the user's home directory.
+ e.g. `~/foo` would be equivalent to `/home/edolstra/foo` for a user
+ whose home directory is `/home/edolstra`.
+
+ Paths can also be specified between angle brackets, e.g.
+ `<nixpkgs>`. This means that the directories listed in the
+ environment variable `NIX_PATH` will be searched for the given file
+ or directory name.
+
+ Antiquotation is supported in any paths except those in angle brackets.
+ `./${foo}-${bar}.nix` is a more convenient way of writing
+ `./. + "/" + foo + "-" + bar + ".nix"` or `./. + "/${foo}-${bar}.nix"`. At
+ least one slash must appear *before* any antiquotations for this to be
+ recognized as a path. `a.${foo}/b.${bar}` is a syntactically valid division
+ operation. `./a.${foo}/b.${bar}` is a path.
+
+ When a path appears in an antiquotation, and is thus coerced into a string,
+ the path is first copied into the Nix store and the resulting string is
+ the Nix store path. For instance `"${./foo.txt}" will cause `foo.txt` in
+ the current directory to be copied into the Nix store and result in the
+ string `"/nix/store/<HASH>-foo.txt"`.
+
+ Note that the Nix language assumes that all input files will remain
+ _unchanged_ during the course of the Nix expression evaluation.
+ If you for example antiquote a file path during a `nix repl` session, and
+ then later in the same session, after having changed the file contents,
+ evaluate the antiquotation with the file path again, then Nix will still
+ return the first store path. It will _not_ reread the file contents to
+ produce a different Nix store path.
+
+- <a id="type-boolean" href="#type-boolean">Boolean</a>
+
+ *Booleans* with values `true` and `false`.
+
+- <a id="type-null" href="#type-null">Null</a>
+
+ The null value, denoted as `null`.
+
+## List
+
+Lists are formed by enclosing a whitespace-separated list of values
+between square brackets. For example,
+
+```nix
+[ 123 ./foo.nix "abc" (f { x = y; }) ]
+```
+
+defines a list of four elements, the last being the result of a call to
+the function `f`. Note that function calls have to be enclosed in
+parentheses. If they had been omitted, e.g.,
+
+```nix
+[ 123 ./foo.nix "abc" f { x = y; } ]
+```
+
+the result would be a list of five elements, the fourth one being a
+function and the fifth being a set.
+
+Note that lists are only lazy in values, and they are strict in length.
+
+## Attribute Set
+
+An attribute set is a collection of name-value-pairs (called *attributes*) enclosed in curly brackets (`{ }`).
+
+Names and values are separated by an equal sign (`=`).
+Each value is an arbitrary expression terminated by a semicolon (`;`).
+
+Attributes can appear in any order.
+An attribute name may only occur once.
+
+Example:
+
+```nix
+{
+ x = 123;
+ text = "Hello";
+ y = f { bla = 456; };
+}
+```
+
+This defines a set with attributes named `x`, `text`, `y`.
+
+Attributes can be selected from a set using the `.` operator. For
+instance,
+
+```nix
+{ a = "Foo"; b = "Bar"; }.a
+```
+
+evaluates to `"Foo"`. It is possible to provide a default value in an
+attribute selection using the `or` keyword. For example,
+
+```nix
+{ a = "Foo"; b = "Bar"; }.c or "Xyzzy"
+```
+
+will evaluate to `"Xyzzy"` because there is no `c` attribute in the set.
+
+You can use arbitrary double-quoted strings as attribute names:
+
+```nix
+{ "foo ${bar}" = 123; "nix-1.0" = 456; }."foo ${bar}"
+```
+
+This will evaluate to `123` (Assuming `bar` is antiquotable). In the
+case where an attribute name is just a single antiquotation, the quotes
+can be dropped:
+
+```nix
+{ foo = 123; }.${bar} or 456
+```
+
+This will evaluate to `123` if `bar` evaluates to `"foo"` when coerced
+to a string and `456` otherwise (again assuming `bar` is antiquotable).
+
+In the special case where an attribute name inside of a set declaration
+evaluates to `null` (which is normally an error, as `null` is not
+antiquotable), that attribute is simply not added to the set:
+
+```nix
+{ ${if foo then "bar" else null} = true; }
+```
+
+This will evaluate to `{}` if `foo` evaluates to `false`.
+
+A set that has a `__functor` attribute whose value is callable (i.e. is
+itself a function or a set with a `__functor` attribute whose value is
+callable) can be applied as if it were a function, with the set itself
+passed in first , e.g.,
+
+```nix
+let add = { __functor = self: x: x + self.x; };
+ inc = add // { x = 1; };
+in inc 1
+```
+
+evaluates to `2`. This can be used to attach metadata to a function
+without the caller needing to treat it specially, or to implement a form
+of object-oriented programming, for example.
diff --git a/doc/manual/src/package-management/package-management.md b/doc/manual/src/package-management/package-management.md
index bd26a09ab..d528112e2 100644
--- a/doc/manual/src/package-management/package-management.md
+++ b/doc/manual/src/package-management/package-management.md
@@ -1,5 +1,4 @@
This chapter discusses how to do package management with Nix, i.e.,
how to obtain, install, upgrade, and erase packages. This is the
“user’s” perspective of the Nix system — people who want to *create*
-packages should consult the [chapter on writing Nix
-expressions](../expressions/writing-nix-expressions.md).
+packages should consult the chapter on the [Nix language](../language/index.md).
diff --git a/doc/manual/src/release-notes/rl-2.11.md b/doc/manual/src/release-notes/rl-2.11.md
new file mode 100644
index 000000000..b322a4e5e
--- /dev/null
+++ b/doc/manual/src/release-notes/rl-2.11.md
@@ -0,0 +1,5 @@
+# Release 2.11 (2022-08-24)
+
+* `nix copy` now copies the store paths in parallel as much as possible (again).
+ This doesn't apply for the `daemon` and `ssh-ng` stores which copy everything
+ in one batch to avoid latencies issues.
diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md
index 36b759a10..e84b2eff6 100644
--- a/doc/manual/src/release-notes/rl-next.md
+++ b/doc/manual/src/release-notes/rl-next.md
@@ -1,3 +1,8 @@
# Release X.Y (202?-??-??)
-* Allow explicitly selecting outputs with *store derivations* installable syntax too.
+* `<nix/fetchurl.nix>` now accepts an additional argument `impure` which
+ defaults to `false`. If it is set to `true`, the `hash` and `sha256`
+ arguments will be ignored and the resulting derivation will have
+ `__impure` set to `true`, making it an impure derivation.
+
+* Allow explicitly selecting outputs with *store derivations* installable syntax too. \ No newline at end of file
diff --git a/doc/manual/utils.nix b/doc/manual/utils.nix
index d4b18472f..d0643ef46 100644
--- a/doc/manual/utils.nix
+++ b/doc/manual/utils.nix
@@ -5,6 +5,32 @@ rec {
concatStrings = concatStringsSep "";
+ replaceStringsRec = from: to: string:
+ # recursively replace occurrences of `from` with `to` within `string`
+ # example:
+ # replaceStringRec "--" "-" "hello-----world"
+ # => "hello-world"
+ let
+ replaced = replaceStrings [ from ] [ to ] string;
+ in
+ if replaced == string then string else replaceStringsRec from to replaced;
+
+ squash = replaceStringsRec "\n\n\n" "\n\n";
+
+ trim = string:
+ # trim trailing spaces and squash non-leading spaces
+ let
+ trimLine = line:
+ let
+ # separate leading spaces from the rest
+ parts = split "(^ *)" line;
+ spaces = head (elemAt parts 1);
+ rest = elemAt parts 2;
+ # drop trailing spaces
+ body = head (split " *$" rest);
+ in spaces + replaceStringsRec " " " " body;
+ in concatStringsSep "\n" (map trimLine (splitLines string));
+
# FIXME: O(n^2)
unique = foldl' (acc: e: if elem e acc then acc else acc ++ [ e ]) [];
diff --git a/docker.nix b/docker.nix
index ddf6feff5..bb2b4e7ff 100644
--- a/docker.nix
+++ b/docker.nix
@@ -2,10 +2,12 @@
, lib ? pkgs.lib
, name ? "nix"
, tag ? "latest"
+, bundleNixpkgs ? true
, channelName ? "nixpkgs"
, channelURL ? "https://nixos.org/channels/nixpkgs-unstable"
, extraPkgs ? []
, maxLayers ? 100
+, nixConf ? {}
}:
let
defaultPkgs = with pkgs; [
@@ -31,7 +33,7 @@ let
root = {
uid = 0;
- shell = "/bin/bash";
+ shell = "${pkgs.bashInteractive}/bin/bash";
home = "/root";
gid = 0;
};
@@ -123,20 +125,27 @@ let
(lib.attrValues (lib.mapAttrs groupToGroup groups))
);
- nixConf = {
+ defaultNixConf = {
sandbox = "false";
build-users-group = "nixbld";
- trusted-public-keys = "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=";
+ trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
};
- nixConfContents = (lib.concatStringsSep "\n" (lib.mapAttrsFlatten (n: v: "${n} = ${v}") nixConf)) + "\n";
+
+ nixConfContents = (lib.concatStringsSep "\n" (lib.mapAttrsFlatten (n: v:
+ let
+ vStr = if builtins.isList v then lib.concatStringsSep " " v else v;
+ in
+ "${n} = ${vStr}") (defaultNixConf // nixConf))) + "\n";
baseSystem =
let
nixpkgs = pkgs.path;
- channel = pkgs.runCommand "channel-nixos" { } ''
+ channel = pkgs.runCommand "channel-nixos" { inherit bundleNixpkgs; } ''
mkdir $out
- ln -s ${nixpkgs} $out/nixpkgs
- echo "[]" > $out/manifest.nix
+ if [ "$bundleNixpkgs" ]; then
+ ln -s ${nixpkgs} $out/nixpkgs
+ echo "[]" > $out/manifest.nix
+ fi
'';
rootEnv = pkgs.buildPackages.buildEnv {
name = "root-profile-env";
diff --git a/flake.lock b/flake.lock
index 01e4f506a..a66c9cb1b 100644
--- a/flake.lock
+++ b/flake.lock
@@ -18,11 +18,11 @@
},
"nixpkgs": {
"locked": {
- "lastModified": 1653988320,
- "narHash": "sha256-ZaqFFsSDipZ6KVqriwM34T739+KLYJvNmCWzErjAg7c=",
+ "lastModified": 1657693803,
+ "narHash": "sha256-G++2CJ9u0E7NNTAi9n5G8TdDmGJXcIjkJ3NF8cetQB8=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "2fa57ed190fd6c7c746319444f34b5917666e5c1",
+ "rev": "365e1b3a859281cf11b94f87231adeabbdd878a2",
"type": "github"
},
"original": {
diff --git a/flake.nix b/flake.nix
index 1ee040441..cc2a48d9c 100644
--- a/flake.nix
+++ b/flake.nix
@@ -23,7 +23,7 @@
crossSystems = [ "armv6l-linux" "armv7l-linux" ];
- stdenvs = [ "gccStdenv" "clangStdenv" "clang11Stdenv" "stdenv" "libcxxStdenv" ];
+ stdenvs = [ "gccStdenv" "clangStdenv" "clang11Stdenv" "stdenv" "libcxxStdenv" "ccacheStdenv" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
forAllSystemsAndStdenvs = f: forAllSystems (system:
@@ -108,7 +108,7 @@
++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)];
buildDeps =
- [ curl
+ [ (curl.override { patchNetrcRegression = true; })
bzip2 xz brotli editline
openssl sqlite
libarchive
@@ -260,6 +260,7 @@
echo "file binary-dist $fn" >> $out/nix-support/hydra-build-products
tar cvfJ $fn \
--owner=0 --group=0 --mode=u+rw,uga+r \
+ --mtime='1970-01-01' \
--absolute-names \
--hard-dereference \
--transform "s,$TMPDIR/install,$dir/install," \
@@ -363,7 +364,7 @@
buildInputs =
[ nix
- curl
+ (curl.override { patchNetrcRegression = true; })
bzip2
xz
pkgs.perl
@@ -545,6 +546,11 @@
# againstLatestStable = testNixVersions pkgs pkgs.nix pkgs.nixStable;
} "touch $out");
+ installerTests = import ./tests/installer {
+ binaryTarballs = self.hydraJobs.binaryTarball;
+ inherit nixpkgsFor;
+ };
+
};
checks = forAllSystems (system: {
diff --git a/misc/zsh/completion.zsh b/misc/zsh/completion.zsh
index e702c721e..f9b3dca74 100644
--- a/misc/zsh/completion.zsh
+++ b/misc/zsh/completion.zsh
@@ -10,14 +10,15 @@ function _nix() {
local -a suggestions
declare -a suggestions
for suggestion in ${res:1}; do
- # FIXME: This doesn't work properly if the suggestion word contains a `:`
- # itself
- suggestions+="${suggestion/ /:}"
+ suggestions+=("${suggestion%% *}")
done
+ local -a args
if [[ "$tpe" == filenames ]]; then
- compadd -f
+ args+=('-f')
+ elif [[ "$tpe" == attrs ]]; then
+ args+=('-S' '')
fi
- _describe 'nix' suggestions
+ compadd -J nix "${args[@]}" -a suggestions
}
_nix "$@"
diff --git a/scripts/install-darwin-multi-user.sh b/scripts/install-darwin-multi-user.sh
index afaa6783b..5111a5dde 100644
--- a/scripts/install-darwin-multi-user.sh
+++ b/scripts/install-darwin-multi-user.sh
@@ -167,7 +167,7 @@ poly_user_shell_get() {
}
poly_user_shell_set() {
- _sudo "in order to give $1 a safe home directory" \
+ _sudo "in order to give $1 a safe shell" \
/usr/bin/dscl . -create "/Users/$1" "UserShell" "$2"
}
diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh
index 9a18280ef..a39339050 100644
--- a/scripts/install-multi-user.sh
+++ b/scripts/install-multi-user.sh
@@ -37,6 +37,19 @@ readonly PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/zshrc" "/e
readonly PROFILE_BACKUP_SUFFIX=".backup-before-nix"
readonly PROFILE_NIX_FILE="$NIX_ROOT/var/nix/profiles/default/etc/profile.d/nix-daemon.sh"
+# Fish has different syntax than zsh/bash, treat it separate
+readonly PROFILE_FISH_SUFFIX="conf.d/nix.fish"
+readonly PROFILE_FISH_PREFIXES=(
+ # each of these are common values of $__fish_sysconf_dir,
+ # under which Fish will look for a file named
+ # $PROFILE_FISH_SUFFIX.
+ "/etc/fish" # standard
+ "/usr/local/etc/fish" # their installer .pkg for macOS
+ "/opt/homebrew/etc/fish" # homebrew
+ "/opt/local/etc/fish" # macports
+)
+readonly PROFILE_NIX_FILE_FISH="$NIX_ROOT/var/nix/profiles/default/etc/profile.d/nix-daemon.fish"
+
readonly NIX_INSTALLED_NIX="@nix@"
readonly NIX_INSTALLED_CACERT="@cacert@"
#readonly NIX_INSTALLED_NIX="/nix/store/j8dbv5w6jl34caywh2ygdy88knx1mdf7-nix-2.3.6"
@@ -59,6 +72,30 @@ headless() {
fi
}
+is_root() {
+ if [ "$EUID" -eq 0 ]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+is_os_linux() {
+ if [ "$(uname -s)" = "Linux" ]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
+is_os_darwin() {
+ if [ "$(uname -s)" = "Darwin" ]; then
+ return 0
+ else
+ return 1
+ fi
+}
+
contact_us() {
echo "You can open an issue at https://github.com/nixos/nix/issues"
echo ""
@@ -313,14 +350,23 @@ __sudo() {
_sudo() {
local expl="$1"
shift
- if ! headless; then
+ if ! headless || is_root; then
__sudo "$expl" "$*" >&2
fi
- sudo "$@"
+
+ if is_root; then
+ env "$@"
+ else
+ sudo "$@"
+ fi
}
+# Ensure that $TMPDIR exists if defined.
+if [[ -n "${TMPDIR:-}" ]] && [[ ! -d "${TMPDIR:-}" ]]; then
+ mkdir -m 0700 -p "${TMPDIR:-}"
+fi
-readonly SCRATCH=$(mktemp -d "${TMPDIR:-/tmp/}tmp.XXXXXXXXXX")
+readonly SCRATCH=$(mktemp -d)
finish_cleanup() {
rm -rf "$SCRATCH"
}
@@ -329,7 +375,7 @@ finish_fail() {
finish_cleanup
failure <<EOF
-Jeeze, something went wrong. If you can take all the output and open
+Oh no, something went wrong. If you can take all the output and open
an issue, we'd love to fix the problem so nobody else has this issue.
:(
@@ -423,7 +469,7 @@ EOF
fi
done
- if [ "$(uname -s)" = "Linux" ] && [ ! -e /run/systemd/system ]; then
+ if is_os_linux && [ ! -e /run/systemd/system ]; then
warning <<EOF
We did not detect systemd on your system. With a multi-user install
without systemd you will have to manually configure your init system to
@@ -640,7 +686,7 @@ place_channel_configuration() {
check_selinux() {
if command -v getenforce > /dev/null 2>&1; then
- if ! [ "$(getenforce)" = "Disabled" ]; then
+ if [ "$(getenforce)" = "Enforcing" ]; then
failure <<EOF
Nix does not work with selinux enabled yet!
see https://github.com/NixOS/nix/issues/2374
@@ -777,7 +823,7 @@ EOF
fi
_sudo "to load data for the first time in to the Nix Database" \
- "$NIX_INSTALLED_NIX/bin/nix-store" --load-db < ./.reginfo
+ HOME="$ROOT_HOME" "$NIX_INSTALLED_NIX/bin/nix-store" --load-db < ./.reginfo
echo " Just finished getting the nix database ready."
)
@@ -795,6 +841,19 @@ fi
EOF
}
+# Fish has differing syntax
+fish_source_lines() {
+ cat <<EOF
+
+# Nix
+if test -e '$PROFILE_NIX_FILE_FISH'
+ . '$PROFILE_NIX_FILE_FISH'
+end
+# End Nix
+
+EOF
+}
+
configure_shell_profile() {
task "Setting up shell profiles: ${PROFILE_TARGETS[*]}"
for profile_target in "${PROFILE_TARGETS[@]}"; do
@@ -816,6 +875,27 @@ configure_shell_profile() {
tee -a "$profile_target"
fi
done
+
+ task "Setting up shell profiles for Fish with with ${PROFILE_FISH_SUFFIX} inside ${PROFILE_FISH_PREFIXES[*]}"
+ for fish_prefix in "${PROFILE_FISH_PREFIXES[@]}"; do
+ if [ ! -d "$fish_prefix" ]; then
+ # this specific prefix (ie: /etc/fish) is very likely to exist
+ # if Fish is installed with this sysconfdir.
+ continue
+ fi
+
+ profile_target="${fish_prefix}/${PROFILE_FISH_SUFFIX}"
+ conf_dir=$(dirname "$profile_target")
+ if [ ! -d "$conf_dir" ]; then
+ _sudo "create $conf_dir for our Fish hook" \
+ mkdir "$conf_dir"
+ fi
+
+ fish_source_lines \
+ | _sudo "write nix-daemon settings to $profile_target" \
+ tee "$profile_target"
+ done
+
# TODO: should we suggest '. $PROFILE_NIX_FILE'? It would get them on
# their way less disruptively, but a counter-argument is that they won't
# immediately notice if something didn't get set up right?
@@ -865,24 +945,14 @@ EOF
install -m 0664 "$SCRATCH/nix.conf" /etc/nix/nix.conf
}
-main() {
- # TODO: I've moved this out of validate_starting_assumptions so we
- # can fail faster in this case. Sourcing install-darwin... now runs
- # `touch /` to detect Read-only root, but it could update times on
- # pre-Catalina macOS if run as root user.
- if [ "$EUID" -eq 0 ]; then
- failure <<EOF
-Please do not run this script with root privileges. I will call sudo
-when I need to.
-EOF
- fi
+main() {
check_selinux
- if [ "$(uname -s)" = "Darwin" ]; then
+ if is_os_darwin; then
# shellcheck source=./install-darwin-multi-user.sh
. "$EXTRACTED_NIX_PATH/install-darwin-multi-user.sh"
- elif [ "$(uname -s)" = "Linux" ]; then
+ elif is_os_linux; then
# shellcheck source=./install-systemd-multi-user.sh
. "$EXTRACTED_NIX_PATH/install-systemd-multi-user.sh" # most of this works on non-systemd distros also
else
@@ -890,7 +960,10 @@ EOF
fi
welcome_to_nix
- chat_about_sudo
+
+ if ! is_root; then
+ chat_about_sudo
+ fi
cure_artifacts
# TODO: there's a tension between cure and validate. I moved the
diff --git a/scripts/install-nix-from-closure.sh b/scripts/install-nix-from-closure.sh
index cd3cf6670..d4eed2efe 100644
--- a/scripts/install-nix-from-closure.sh
+++ b/scripts/install-nix-from-closure.sh
@@ -209,31 +209,50 @@ if [ -z "$NIX_INSTALLER_NO_CHANNEL_ADD" ]; then
fi
added=
-p=$HOME/.nix-profile/etc/profile.d/nix.sh
+p=
+p_sh=$HOME/.nix-profile/etc/profile.d/nix.sh
+p_fish=$HOME/.nix-profile/etc/profile.d/nix.fish
if [ -z "$NIX_INSTALLER_NO_MODIFY_PROFILE" ]; then
# Make the shell source nix.sh during login.
for i in .bash_profile .bash_login .profile; do
fn="$HOME/$i"
if [ -w "$fn" ]; then
- if ! grep -q "$p" "$fn"; then
+ if ! grep -q "$p_sh" "$fn"; then
echo "modifying $fn..." >&2
- printf '\nif [ -e %s ]; then . %s; fi # added by Nix installer\n' "$p" "$p" >> "$fn"
+ printf '\nif [ -e %s ]; then . %s; fi # added by Nix installer\n' "$p_sh" "$p_sh" >> "$fn"
fi
added=1
+ p=${p_sh}
break
fi
done
for i in .zshenv .zshrc; do
fn="$HOME/$i"
if [ -w "$fn" ]; then
- if ! grep -q "$p" "$fn"; then
+ if ! grep -q "$p_sh" "$fn"; then
echo "modifying $fn..." >&2
- printf '\nif [ -e %s ]; then . %s; fi # added by Nix installer\n' "$p" "$p" >> "$fn"
+ printf '\nif [ -e %s ]; then . %s; fi # added by Nix installer\n' "$p_sh" "$p_sh" >> "$fn"
fi
added=1
+ p=${p_sh}
break
fi
done
+
+ if [ -d "$HOME/.config/fish" ]; then
+ fishdir=$HOME/.config/fish/conf.d
+ if [ ! -d "$fishdir" ]; then
+ mkdir -p "$fishdir"
+ fi
+
+ fn="$fishdir/nix.fish"
+ echo "placing $fn..." >&2
+ printf '\nif test -e %s; . %s; end # added by Nix installer\n' "$p_fish" "$p_fish" > "$fn"
+ added=1
+ p=${p_fish}
+ fi
+else
+ p=${p_sh}
fi
if [ -z "$added" ]; then
diff --git a/scripts/install.in b/scripts/install.in
index af5f71080..7d2e52b26 100755
--- a/scripts/install.in
+++ b/scripts/install.in
@@ -40,12 +40,12 @@ case "$(uname -s).$(uname -m)" in
path=@tarballPath_aarch64-linux@
system=aarch64-linux
;;
- Linux.armv6l_linux)
+ Linux.armv6l)
hash=@tarballHash_armv6l-linux@
path=@tarballPath_armv6l-linux@
system=armv6l-linux
;;
- Linux.armv7l_linux)
+ Linux.armv7l)
hash=@tarballHash_armv7l-linux@
path=@tarballPath_armv7l-linux@
system=armv7l-linux
diff --git a/scripts/local.mk b/scripts/local.mk
index b8477178e..46255e432 100644
--- a/scripts/local.mk
+++ b/scripts/local.mk
@@ -6,6 +6,8 @@ noinst-scripts += $(nix_noinst_scripts)
profiledir = $(sysconfdir)/profile.d
$(eval $(call install-file-as, $(d)/nix-profile.sh, $(profiledir)/nix.sh, 0644))
+$(eval $(call install-file-as, $(d)/nix-profile.fish, $(profiledir)/nix.fish, 0644))
$(eval $(call install-file-as, $(d)/nix-profile-daemon.sh, $(profiledir)/nix-daemon.sh, 0644))
+$(eval $(call install-file-as, $(d)/nix-profile-daemon.fish, $(profiledir)/nix-daemon.fish, 0644))
clean-files += $(nix_noinst_scripts)
diff --git a/scripts/nix-profile-daemon.fish.in b/scripts/nix-profile-daemon.fish.in
new file mode 100644
index 000000000..3d587dd7f
--- /dev/null
+++ b/scripts/nix-profile-daemon.fish.in
@@ -0,0 +1,35 @@
+# Only execute this file once per shell.
+if test -n "$__ETC_PROFILE_NIX_SOURCED"
+ exit
+end
+
+set __ETC_PROFILE_NIX_SOURCED 1
+
+set --export NIX_PROFILES "@localstatedir@/nix/profiles/default $HOME/.nix-profile"
+
+# Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
+if test -n "$NIX_SSH_CERT_FILE"
+ : # Allow users to override the NIX_SSL_CERT_FILE
+else if test -e /etc/ssl/certs/ca-certificates.crt # NixOS, Ubuntu, Debian, Gentoo, Arch
+ set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
+else if test -e /etc/ssl/ca-bundle.pem # openSUSE Tumbleweed
+ set --export NIX_SSL_CERT_FILE /etc/ssl/ca-bundle.pem
+else if test -e /etc/ssl/certs/ca-bundle.crt # Old NixOS
+ set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-bundle.crt
+else if test -e /etc/pki/tls/certs/ca-bundle.crt # Fedora, CentOS
+ set --export NIX_SSL_CERT_FILE /etc/pki/tls/certs/ca-bundle.crt
+else if test -e "$NIX_LINK/etc/ssl/certs/ca-bundle.crt" # fall back to cacert in Nix profile
+ set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ssl/certs/ca-bundle.crt"
+else if test -e "$NIX_LINK/etc/ca-bundle.crt" # old cacert in Nix profile
+ set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ca-bundle.crt"
+else
+ # Fall back to what is in the nix profiles, favouring whatever is defined last.
+ for i in $NIX_PROFILES
+ if test -e "$i/etc/ssl/certs/ca-bundle.crt"
+ set --export NIX_SSL_CERT_FILE "$i/etc/ssl/certs/ca-bundle.crt"
+ end
+ end
+end
+
+fish_add_path --prepend --global "@localstatedir@/nix/profiles/default/bin"
+fish_add_path --prepend --global "$HOME/.nix-profile/bin"
diff --git a/scripts/nix-profile.fish.in b/scripts/nix-profile.fish.in
new file mode 100644
index 000000000..8d783d7c0
--- /dev/null
+++ b/scripts/nix-profile.fish.in
@@ -0,0 +1,37 @@
+if test -n "$HOME" && test -n "$USER"
+
+ # Set up the per-user profile.
+
+ set NIX_LINK $HOME/.nix-profile
+
+ # Set up environment.
+ # This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix
+ set --export NIX_PROFILES "@localstatedir@/nix/profiles/default $HOME/.nix-profile"
+
+ # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work.
+ if test -n "$NIX_SSH_CERT_FILE"
+ : # Allow users to override the NIX_SSL_CERT_FILE
+ else if test -e /etc/ssl/certs/ca-certificates.crt # NixOS, Ubuntu, Debian, Gentoo, Arch
+ set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-certificates.crt
+ else if test -e /etc/ssl/ca-bundle.pem # openSUSE Tumbleweed
+ set --export NIX_SSL_CERT_FILE /etc/ssl/ca-bundle.pem
+ else if test -e /etc/ssl/certs/ca-bundle.crt # Old NixOS
+ set --export NIX_SSL_CERT_FILE /etc/ssl/certs/ca-bundle.crt
+ else if test -e /etc/pki/tls/certs/ca-bundle.crt # Fedora, CentOS
+ set --export NIX_SSL_CERT_FILE /etc/pki/tls/certs/ca-bundle.crt
+ else if test -e "$NIX_LINK/etc/ssl/certs/ca-bundle.crt" # fall back to cacert in Nix profile
+ set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ssl/certs/ca-bundle.crt"
+ else if test -e "$NIX_LINK/etc/ca-bundle.crt" # old cacert in Nix profile
+ set --export NIX_SSL_CERT_FILE "$NIX_LINK/etc/ca-bundle.crt"
+ end
+
+ # Only use MANPATH if it is already set. In general `man` will just simply
+ # pick up `.nix-profile/share/man` because is it close to `.nix-profile/bin`
+ # which is in the $PATH. For more info, run `manpath -d`.
+ if set --query MANPATH
+ set --export --prepend --path MANPATH "$NIX_LINK/share/man"
+ end
+
+ fish_add_path --prepend --global "$NIX_LINK/bin"
+ set --erase NIX_LINK
+end
diff --git a/scripts/nix-profile.sh.in b/scripts/nix-profile.sh.in
index 45cbcbe74..5636085d4 100644
--- a/scripts/nix-profile.sh.in
+++ b/scripts/nix-profile.sh.in
@@ -1,7 +1,6 @@
if [ -n "$HOME" ] && [ -n "$USER" ]; then
# Set up the per-user profile.
- # This part should be kept in sync with nixpkgs:nixos/modules/programs/shell.nix
NIX_LINK=$HOME/.nix-profile
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
index 14bb27936..0740ea960 100644
--- a/src/libcmd/command.cc
+++ b/src/libcmd/command.cc
@@ -88,7 +88,8 @@ EvalCommand::EvalCommand()
{
addFlag({
.longName = "debugger",
- .description = "start an interactive environment if evaluation fails",
+ .description = "Start an interactive environment if evaluation fails.",
+ .category = MixEvalArgs::category,
.handler = {&startReplOnEvalErrors, true},
});
}
@@ -225,7 +226,7 @@ MixProfile::MixProfile()
{
addFlag({
.longName = "profile",
- .description = "The profile to update.",
+ .description = "The profile to operate on.",
.labels = {"path"},
.handler = {&profile},
.completer = completePath
diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc
index 5b6e82388..140ed3b88 100644
--- a/src/libcmd/common-eval-args.cc
+++ b/src/libcmd/common-eval-args.cc
@@ -13,8 +13,6 @@ namespace nix {
MixEvalArgs::MixEvalArgs()
{
- auto category = "Common evaluation options";
-
addFlag({
.longName = "arg",
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
diff --git a/src/libcmd/common-eval-args.hh b/src/libcmd/common-eval-args.hh
index 03fa226aa..1ec800613 100644
--- a/src/libcmd/common-eval-args.hh
+++ b/src/libcmd/common-eval-args.hh
@@ -10,6 +10,8 @@ class Bindings;
struct MixEvalArgs : virtual Args
{
+ static constexpr auto category = "Common evaluation options";
+
MixEvalArgs();
Bindings * getAutoArgs(EvalState & state);
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
index 0641e99ff..76f88db9f 100644
--- a/src/libcmd/installables.cc
+++ b/src/libcmd/installables.cc
@@ -628,6 +628,8 @@ InstallableFlake::InstallableFlake(
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
{
+ Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what()));
+
auto attr = getCursor(*state);
auto attrPath = attr->getAttrPathStr();
diff --git a/src/libcmd/markdown.cc b/src/libcmd/markdown.cc
index 71f9c8dff..668a07763 100644
--- a/src/libcmd/markdown.cc
+++ b/src/libcmd/markdown.cc
@@ -18,7 +18,7 @@ std::string renderMarkdownToTerminal(std::string_view markdown)
.hmargin = 0,
.vmargin = 0,
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
- .oflags = 0,
+ .oflags = LOWDOWN_TERM_NOLINK,
};
auto doc = lowdown_doc_new(&opts);
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 23df40337..df8932087 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -35,6 +35,7 @@ extern "C" {
#include "finally.hh"
#include "markdown.hh"
#include "local-fs-store.hh"
+#include "progress-bar.hh"
#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
@@ -241,7 +242,11 @@ void NixRepl::mainLoop()
// Allow nix-repl specific settings in .inputrc
rl_readline_name = "nix-repl";
- createDirs(dirOf(historyFile));
+ try {
+ createDirs(dirOf(historyFile));
+ } catch (SysError & e) {
+ logWarning(e.info());
+ }
#ifndef READLINE
el_hist_size = 1000;
#endif
@@ -252,6 +257,10 @@ void NixRepl::mainLoop()
rl_set_list_possib_func(listPossibleCallback);
#endif
+ /* Stop the progress bar because it interferes with the display of
+ the repl. */
+ stopProgressBar();
+
std::string input;
while (true) {
@@ -1037,10 +1046,11 @@ void runRepl(
struct CmdRepl : InstallablesCommand
{
- CmdRepl(){
+ CmdRepl() {
evalSettings.pureEval = false;
}
- void prepare()
+
+ void prepare() override
{
if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
warn("future versions of Nix will require using `--file` to load a file");
@@ -1053,12 +1063,15 @@ struct CmdRepl : InstallablesCommand
}
installables = InstallablesCommand::load();
}
+
std::vector<std::string> files;
+
Strings getDefaultFlakeAttrPaths() override
{
return {""};
}
- virtual bool useDefaultInstallables() override
+
+ bool useDefaultInstallables() override
{
return file.has_value() or expr.has_value();
}
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc
index 0d83b6cfe..b259eec63 100644
--- a/src/libexpr/eval-cache.cc
+++ b/src/libexpr/eval-cache.cc
@@ -507,11 +507,6 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
return nullptr;
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
- for (auto & attr : *v.attrs) {
- if (root->db)
- root->db->setPlaceholder({cachedValue->first, attr.name});
- }
-
auto attr = v.attrs->get(name);
if (!attr) {
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index f485e2fed..e3716f217 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -2501,18 +2501,18 @@ void EvalState::printStats()
}
{
auto list = topObj.list("functions");
- for (auto & i : functionCalls) {
+ for (auto & [fun, count] : functionCalls) {
auto obj = list.object();
- if (i.first->name)
- obj.attr("name", (const std::string &) i.first->name);
+ if (fun->name)
+ obj.attr("name", (std::string_view) symbols[fun->name]);
else
obj.attr("name", nullptr);
- if (auto pos = positions[i.first->pos]) {
- obj.attr("file", (const std::string &) pos.file);
+ if (auto pos = positions[fun->pos]) {
+ obj.attr("file", (std::string_view) pos.file);
obj.attr("line", pos.line);
obj.attr("column", pos.column);
}
- obj.attr("count", i.second);
+ obj.attr("count", count);
}
}
{
diff --git a/src/libexpr/fetchurl.nix b/src/libexpr/fetchurl.nix
index 02531103b..9d1b61d7f 100644
--- a/src/libexpr/fetchurl.nix
+++ b/src/libexpr/fetchurl.nix
@@ -12,13 +12,13 @@
, executable ? false
, unpack ? false
, name ? baseNameOf (toString url)
+, impure ? false
}:
-derivation {
+derivation ({
builder = "builtin:fetchurl";
# New-style output content requirements.
- inherit outputHashAlgo outputHash;
outputHashMode = if unpack || executable then "recursive" else "flat";
inherit name url executable unpack;
@@ -38,4 +38,6 @@ derivation {
# To make "nix-prefetch-url" work.
urls = [ url ];
-}
+} // (if impure
+ then { __impure = true; }
+ else { inherit outputHashAlgo outputHash; }))
diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix
index 932ac5e90..8061db3df 100644
--- a/src/libexpr/flake/call-flake.nix
+++ b/src/libexpr/flake/call-flake.nix
@@ -43,7 +43,7 @@ let
outputs = flake.outputs (inputs // { self = result; });
- result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; };
+ result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; _type = "flake"; };
in
if node.flake or true then
assert builtins.isFunction flake.outputs;
diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc
index 3e9d264b4..6df95f1f0 100644
--- a/src/libexpr/flake/config.cc
+++ b/src/libexpr/flake/config.cc
@@ -68,7 +68,7 @@ void ConfigFile::apply()
}
}
if (!trusted) {
- warn("ignoring untrusted flake configuration setting '%s'", name);
+ warn("ignoring untrusted flake configuration setting '%s'.\nPass '%s' to trust it", name, "--accept-flake-config");
continue;
}
}
diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc
index cc9be1336..119c556ac 100644
--- a/src/libexpr/flake/flake.cc
+++ b/src/libexpr/flake/flake.cc
@@ -341,7 +341,6 @@ LockedFlake lockFlake(
debug("old lock file: %s", oldLockFile);
- // FIXME: check whether all overrides are used.
std::map<InputPath, FlakeInput> overrides;
std::set<InputPath> overridesUsed, updatesUsed;
@@ -484,12 +483,12 @@ LockedFlake lockFlake(
} else if (auto follows = std::get_if<1>(&i.second)) {
if (! trustLock) {
// It is possible that the flake has changed,
- // so we must confirm all the follows that are in the lockfile are also in the flake.
+ // so we must confirm all the follows that are in the lock file are also in the flake.
auto overridePath(inputPath);
overridePath.push_back(i.first);
auto o = overrides.find(overridePath);
// If the override disappeared, we have to refetch the flake,
- // since some of the inputs may not be present in the lockfile.
+ // since some of the inputs may not be present in the lock file.
if (o == overrides.end()) {
mustRefetch = true;
// There's no point populating the rest of the fake inputs,
diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh
index a9182f4bf..fe4f67193 100644
--- a/src/libexpr/flake/flakeref.hh
+++ b/src/libexpr/flake/flakeref.hh
@@ -28,7 +28,7 @@ typedef std::string FlakeId;
* object that fetcher generates (usually via
* FlakeRef::fromAttrs(attrs) or parseFlakeRef(url) calls).
*
- * The actual fetch not have been performed yet (i.e. a FlakeRef may
+ * The actual fetch may not have been performed yet (i.e. a FlakeRef may
* be lazy), but the fetcher can be invoked at any time via the
* FlakeRef to ensure the store is populated with this input.
*/
diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc
index 60b52d578..629d2e669 100644
--- a/src/libexpr/flake/lockfile.cc
+++ b/src/libexpr/flake/lockfile.cc
@@ -36,7 +36,7 @@ LockedNode::LockedNode(const nlohmann::json & json)
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
{
if (!lockedRef.input.isLocked())
- throw Error("lockfile contains mutable lock '%s'",
+ throw Error("lock file contains mutable lock '%s'",
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
}
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index bc253d0a3..840bfecef 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -2454,8 +2454,8 @@ static RegisterPrimOp primop_intersectAttrs({
.name = "__intersectAttrs",
.args = {"e1", "e2"},
.doc = R"(
- Return a set consisting of the attributes in the set *e2* that also
- exist in the set *e1*.
+ Return a set consisting of the attributes in the set *e2* which have the
+ same name as some attribute in *e1*.
)",
.fun = prim_intersectAttrs,
});
@@ -3821,8 +3821,8 @@ static RegisterPrimOp primop_parseDrvName({
.args = {"s"},
.doc = R"(
Split the string *s* into a package name and version. The package
- name is everything up to but not including the first dash followed
- by a digit, and the version is everything following that dash. The
+ name is everything up to but not including the first dash not followed
+ by a letter, and the version is everything following that dash. The
result is returned in a set `{ name, version }`. Thus,
`builtins.parseDrvName "nix-0.12pre12876"` returns `{ name =
"nix"; version = "0.12pre12876"; }`.
diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc
index 03504db61..4d63d8b49 100644
--- a/src/libexpr/value-to-json.cc
+++ b/src/libexpr/value-to-json.cc
@@ -10,7 +10,7 @@
namespace nix {
void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context)
+ Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore)
{
checkInterrupt();
@@ -32,7 +32,10 @@ void printValueAsJSON(EvalState & state, bool strict,
break;
case nPath:
- out.write(state.copyPathToStore(context, v.path));
+ if (copyToStore)
+ out.write(state.copyPathToStore(context, v.path));
+ else
+ out.write(v.path);
break;
case nNull:
@@ -54,10 +57,10 @@ void printValueAsJSON(EvalState & state, bool strict,
for (auto & j : names) {
Attr & a(*v.attrs->find(state.symbols.create(j)));
auto placeholder(obj.placeholder(j));
- printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context);
+ printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context, copyToStore);
}
} else
- printValueAsJSON(state, strict, *i->value, i->pos, out, context);
+ printValueAsJSON(state, strict, *i->value, i->pos, out, context, copyToStore);
break;
}
@@ -65,13 +68,13 @@ void printValueAsJSON(EvalState & state, bool strict,
auto list(out.list());
for (auto elem : v.listItems()) {
auto placeholder(list.placeholder());
- printValueAsJSON(state, strict, *elem, pos, placeholder, context);
+ printValueAsJSON(state, strict, *elem, pos, placeholder, context, copyToStore);
}
break;
}
case nExternal:
- v.external->printValueAsJSON(state, strict, out, context);
+ v.external->printValueAsJSON(state, strict, out, context, copyToStore);
break;
case nFloat:
@@ -91,14 +94,14 @@ void printValueAsJSON(EvalState & state, bool strict,
}
void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const PosIdx pos, std::ostream & str, PathSet & context)
+ Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore)
{
JSONPlaceholder out(str);
- printValueAsJSON(state, strict, v, pos, out, context);
+ printValueAsJSON(state, strict, v, pos, out, context, copyToStore);
}
void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
- JSONPlaceholder & out, PathSet & context) const
+ JSONPlaceholder & out, PathSet & context, bool copyToStore) const
{
state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
}
diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh
index c020a817a..7ddc8a5b1 100644
--- a/src/libexpr/value-to-json.hh
+++ b/src/libexpr/value-to-json.hh
@@ -11,9 +11,9 @@ namespace nix {
class JSONPlaceholder;
void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context);
+ Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore = true);
void printValueAsJSON(EvalState & state, bool strict,
- Value & v, const PosIdx pos, std::ostream & str, PathSet & context);
+ Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore = true);
}
diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh
index 2008df74d..590ba7783 100644
--- a/src/libexpr/value.hh
+++ b/src/libexpr/value.hh
@@ -99,7 +99,7 @@ class ExternalValueBase
/* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */
virtual void printValueAsJSON(EvalState & state, bool strict,
- JSONPlaceholder & out, PathSet & context) const;
+ JSONPlaceholder & out, PathSet & context, bool copyToStore = true) const;
/* Print the value as XML. Defaults to unevaluated */
virtual void printValueAsXML(EvalState & state, bool strict, bool location,
diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc
index 7d01aaa7a..7b7a1be35 100644
--- a/src/libfetchers/git.cc
+++ b/src/libfetchers/git.cc
@@ -370,7 +370,7 @@ struct GitInputScheme : InputScheme
auto gitDir = ".git";
runProgram("git", true,
- { "-C", *sourcePath, "--git-dir", gitDir, "add", "--force", "--intent-to-add", "--", std::string(file) });
+ { "-C", *sourcePath, "--git-dir", gitDir, "add", "--intent-to-add", "--", std::string(file) });
if (commitMsg)
runProgram("git", true,
@@ -485,6 +485,10 @@ struct GitInputScheme : InputScheme
}
input.attrs.insert_or_assign("ref", *head);
unlockedAttrs.insert_or_assign("ref", *head);
+ } else {
+ if (!input.getRev()) {
+ unlockedAttrs.insert_or_assign("ref", input.getRef().value());
+ }
}
if (auto res = getCache()->lookup(store, unlockedAttrs)) {
diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc
index 12f5403ea..f92920d18 100644
--- a/src/libmain/common-args.cc
+++ b/src/libmain/common-args.cc
@@ -32,6 +32,7 @@ MixCommonArgs::MixCommonArgs(const std::string & programName)
addFlag({
.longName = "option",
.description = "Set the Nix configuration setting *name* to *value* (overriding `nix.conf`).",
+ .category = miscCategory,
.labels = {"name", "value"},
.handler = {[](std::string name, std::string value) {
try {
diff --git a/src/libmain/common-args.hh b/src/libmain/common-args.hh
index 25453b8c6..f180d83ce 100644
--- a/src/libmain/common-args.hh
+++ b/src/libmain/common-args.hh
@@ -6,6 +6,7 @@ namespace nix {
//static constexpr auto commonArgsCategory = "Miscellaneous common options";
static constexpr auto loggingCategory = "Logging-related options";
+static constexpr auto miscCategory = "Miscellaneous global options";
class MixCommonArgs : public virtual Args
{
diff --git a/src/libmain/loggers.cc b/src/libmain/loggers.cc
index cdf23859b..cda5cb939 100644
--- a/src/libmain/loggers.cc
+++ b/src/libmain/loggers.cc
@@ -30,8 +30,11 @@ Logger * makeDefaultLogger() {
return makeJSONLogger(*makeSimpleLogger(true));
case LogFormat::bar:
return makeProgressBar();
- case LogFormat::barWithLogs:
- return makeProgressBar(true);
+ case LogFormat::barWithLogs: {
+ auto logger = makeProgressBar();
+ logger->setPrintBuildLogs(true);
+ return logger;
+ }
default:
abort();
}
diff --git a/src/libmain/progress-bar.cc b/src/libmain/progress-bar.cc
index f4306ab91..961f4e18a 100644
--- a/src/libmain/progress-bar.cc
+++ b/src/libmain/progress-bar.cc
@@ -8,6 +8,7 @@
#include <map>
#include <thread>
#include <iostream>
+#include <chrono>
namespace nix {
@@ -48,6 +49,7 @@ private:
bool visible = true;
ActivityId parent;
std::optional<std::string> name;
+ std::chrono::time_point<std::chrono::steady_clock> startTime;
};
struct ActivitiesByType
@@ -79,22 +81,22 @@ private:
std::condition_variable quitCV, updateCV;
- bool printBuildLogs;
+ bool printBuildLogs = false;
bool isTTY;
public:
- ProgressBar(bool printBuildLogs, bool isTTY)
- : printBuildLogs(printBuildLogs)
- , isTTY(isTTY)
+ ProgressBar(bool isTTY)
+ : isTTY(isTTY)
{
state_.lock()->active = isTTY;
updateThread = std::thread([&]() {
auto state(state_.lock());
+ auto nextWakeup = std::chrono::milliseconds::max();
while (state->active) {
if (!state->haveUpdate)
- state.wait(updateCV);
- draw(*state);
+ state.wait_for(updateCV, nextWakeup);
+ nextWakeup = draw(*state);
state.wait_for(quitCV, std::chrono::milliseconds(50));
}
});
@@ -118,7 +120,8 @@ public:
updateThread.join();
}
- bool isVerbose() override {
+ bool isVerbose() override
+ {
return printBuildLogs;
}
@@ -159,11 +162,13 @@ public:
if (lvl <= verbosity && !s.empty() && type != actBuildWaiting)
log(*state, lvl, s + "...");
- state->activities.emplace_back(ActInfo());
+ state->activities.emplace_back(ActInfo {
+ .s = s,
+ .type = type,
+ .parent = parent,
+ .startTime = std::chrono::steady_clock::now()
+ });
auto i = std::prev(state->activities.end());
- i->s = s;
- i->type = type;
- i->parent = parent;
state->its.emplace(act, i);
state->activitiesByType[type].its.emplace(act, i);
@@ -327,10 +332,12 @@ public:
updateCV.notify_one();
}
- void draw(State & state)
+ std::chrono::milliseconds draw(State & state)
{
+ auto nextWakeup = std::chrono::milliseconds::max();
+
state.haveUpdate = false;
- if (!state.active) return;
+ if (!state.active) return nextWakeup;
std::string line;
@@ -341,12 +348,25 @@ public:
line += "]";
}
+ auto now = std::chrono::steady_clock::now();
+
if (!state.activities.empty()) {
if (!status.empty()) line += " ";
auto i = state.activities.rbegin();
- while (i != state.activities.rend() && (!i->visible || (i->s.empty() && i->lastLine.empty())))
+ while (i != state.activities.rend()) {
+ if (i->visible && (!i->s.empty() || !i->lastLine.empty())) {
+ /* Don't show activities until some time has
+ passed, to avoid displaying very short
+ activities. */
+ auto delay = std::chrono::milliseconds(10);
+ if (i->startTime + delay < now)
+ break;
+ else
+ nextWakeup = std::min(nextWakeup, std::chrono::duration_cast<std::chrono::milliseconds>(delay - (now - i->startTime)));
+ }
++i;
+ }
if (i != state.activities.rend()) {
line += i->s;
@@ -366,6 +386,8 @@ public:
if (width <= 0) width = std::numeric_limits<decltype(width)>::max();
writeToStderr("\r" + filterANSIEscapes(line, false, width) + ANSI_NORMAL + "\e[K");
+
+ return nextWakeup;
}
std::string getStatus(State & state)
@@ -480,19 +502,21 @@ public:
draw(*state);
return s[0];
}
+
+ void setPrintBuildLogs(bool printBuildLogs) override
+ {
+ this->printBuildLogs = printBuildLogs;
+ }
};
-Logger * makeProgressBar(bool printBuildLogs)
+Logger * makeProgressBar()
{
- return new ProgressBar(
- printBuildLogs,
- shouldANSI()
- );
+ return new ProgressBar(shouldANSI());
}
-void startProgressBar(bool printBuildLogs)
+void startProgressBar()
{
- logger = makeProgressBar(printBuildLogs);
+ logger = makeProgressBar();
}
void stopProgressBar()
diff --git a/src/libmain/progress-bar.hh b/src/libmain/progress-bar.hh
index 7f0dafecf..3a76f8448 100644
--- a/src/libmain/progress-bar.hh
+++ b/src/libmain/progress-bar.hh
@@ -4,9 +4,9 @@
namespace nix {
-Logger * makeProgressBar(bool printBuildLogs = false);
+Logger * makeProgressBar();
-void startProgressBar(bool printBuildLogs = false);
+void startProgressBar();
void stopProgressBar();
diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc
index 31454e49d..c1cf38565 100644
--- a/src/libmain/shared.cc
+++ b/src/libmain/shared.cc
@@ -4,6 +4,7 @@
#include "gc-store.hh"
#include "util.hh"
#include "loggers.hh"
+#include "progress-bar.hh"
#include <algorithm>
#include <cctype>
@@ -181,8 +182,9 @@ void initNix()
/* Reset SIGCHLD to its default. */
struct sigaction act;
sigemptyset(&act.sa_mask);
- act.sa_handler = SIG_DFL;
act.sa_flags = 0;
+
+ act.sa_handler = SIG_DFL;
if (sigaction(SIGCHLD, &act, 0))
throw SysError("resetting SIGCHLD");
@@ -194,9 +196,20 @@ void initNix()
/* HACK: on darwin, we need can’t use sigprocmask with SIGWINCH.
* Instead, add a dummy sigaction handler, and signalHandlerThread
* can handle the rest. */
- struct sigaction sa;
- sa.sa_handler = sigHandler;
- if (sigaction(SIGWINCH, &sa, 0)) throw SysError("handling SIGWINCH");
+ act.sa_handler = sigHandler;
+ if (sigaction(SIGWINCH, &act, 0)) throw SysError("handling SIGWINCH");
+
+ /* Disable SA_RESTART for interrupts, so that system calls on this thread
+ * error with EINTR like they do on Linux.
+ * Most signals on BSD systems default to SA_RESTART on, but Nix
+ * expects EINTR from syscalls to properly exit. */
+ act.sa_handler = SIG_DFL;
+ if (sigaction(SIGINT, &act, 0)) throw SysError("handling SIGINT");
+ if (sigaction(SIGTERM, &act, 0)) throw SysError("handling SIGTERM");
+ if (sigaction(SIGHUP, &act, 0)) throw SysError("handling SIGHUP");
+ if (sigaction(SIGPIPE, &act, 0)) throw SysError("handling SIGPIPE");
+ if (sigaction(SIGQUIT, &act, 0)) throw SysError("handling SIGQUIT");
+ if (sigaction(SIGTRAP, &act, 0)) throw SysError("handling SIGTRAP");
#endif
/* Register a SIGSEGV handler to detect stack overflows. */
@@ -410,6 +423,8 @@ RunPager::RunPager()
if (!pager) pager = getenv("PAGER");
if (pager && ((std::string) pager == "" || (std::string) pager == "cat")) return;
+ stopProgressBar();
+
Pipe toPager;
toPager.create();
diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh
index 0cc56d47d..3c37fd627 100644
--- a/src/libmain/shared.hh
+++ b/src/libmain/shared.hh
@@ -113,5 +113,25 @@ struct PrintFreed
/* Install a SIGSEGV handler to detect stack overflows. */
void detectStackOverflow();
+/* Pluggable behavior to run in case of a stack overflow.
+
+ Default value: defaultStackOverflowHandler.
+
+ This is called by the handler installed by detectStackOverflow().
+
+ This gives Nix library consumers a limit opportunity to report the error
+ condition. The handler should exit the process.
+ See defaultStackOverflowHandler() for a reference implementation.
+
+ NOTE: Use with diligence, because this runs in the signal handler, with very
+ limited stack space and a potentially a corrupted heap, all while the failed
+ thread is blocked indefinitely. All functions called must be reentrant. */
+extern std::function<void(siginfo_t * info, void * ctx)> stackOverflowHandler;
+
+/* The default, robust implementation of stackOverflowHandler.
+
+ Prints an error message directly to stderr using a syscall instead of the
+ logger. Exits the process immediately after. */
+void defaultStackOverflowHandler(siginfo_t * info, void * ctx);
}
diff --git a/src/libmain/stack.cc b/src/libmain/stack.cc
index b0a4a4c5d..10f71c1dc 100644
--- a/src/libmain/stack.cc
+++ b/src/libmain/stack.cc
@@ -1,4 +1,5 @@
#include "error.hh"
+#include "shared.hh"
#include <cstring>
#include <cstddef>
@@ -29,9 +30,7 @@ static void sigsegvHandler(int signo, siginfo_t * info, void * ctx)
ptrdiff_t diff = (char *) info->si_addr - sp;
if (diff < 0) diff = -diff;
if (diff < 4096) {
- char msg[] = "error: stack overflow (possible infinite recursion)\n";
- [[gnu::unused]] auto res = write(2, msg, strlen(msg));
- _exit(1); // maybe abort instead?
+ nix::stackOverflowHandler(info, ctx);
}
}
@@ -67,5 +66,12 @@ void detectStackOverflow()
#endif
}
+std::function<void(siginfo_t * info, void * ctx)> stackOverflowHandler(defaultStackOverflowHandler);
+
+void defaultStackOverflowHandler(siginfo_t * info, void * ctx) {
+ char msg[] = "error: stack overflow (possible infinite recursion)\n";
+ [[gnu::unused]] auto res = write(2, msg, strlen(msg));
+ _exit(1); // maybe abort instead?
+}
}
diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc
index 9226c4e19..a26770c79 100644
--- a/src/libstore/binary-cache-store.cc
+++ b/src/libstore/binary-cache-store.cc
@@ -331,6 +331,17 @@ bool BinaryCacheStore::isValidPathUncached(const StorePath & storePath)
return fileExists(narInfoFileFor(storePath));
}
+std::optional<StorePath> BinaryCacheStore::queryPathFromHashPart(const std::string & hashPart)
+{
+ auto pseudoPath = StorePath(hashPart + "-" + MissingName);
+ try {
+ auto info = queryPathInfo(pseudoPath);
+ return info->path;
+ } catch (InvalidPath &) {
+ return std::nullopt;
+ }
+}
+
void BinaryCacheStore::narFromPath(const StorePath & storePath, Sink & sink)
{
auto info = queryPathInfo(storePath).cast<const NarInfo>();
diff --git a/src/libstore/binary-cache-store.hh b/src/libstore/binary-cache-store.hh
index ca538b3cb..8c82e2387 100644
--- a/src/libstore/binary-cache-store.hh
+++ b/src/libstore/binary-cache-store.hh
@@ -95,8 +95,7 @@ public:
void queryPathInfoUncached(const StorePath & path,
Callback<std::shared_ptr<const ValidPathInfo>> callback) noexcept override;
- std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
- { unsupported("queryPathFromHashPart"); }
+ std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override;
void addToStore(const ValidPathInfo & info, Source & narSource,
RepairFlag repair, CheckSigsFlag checkSigs) override;
diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc
index 3fff2385f..41d2e2a1c 100644
--- a/src/libstore/build/derivation-goal.cc
+++ b/src/libstore/build/derivation-goal.cc
@@ -344,7 +344,7 @@ void DerivationGoal::gaveUpOnSubstitution()
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
/* Ensure that pure, non-fixed-output derivations don't
depend on impure derivations. */
- if (drv->type().isPure() && !drv->type().isFixed()) {
+ if (settings.isExperimentalFeatureEnabled(Xp::ImpureDerivations) && drv->type().isPure() && !drv->type().isFixed()) {
auto inputDrv = worker.evalStore.readDerivation(i.first);
if (!inputDrv.type().isPure())
throw Error("pure derivation '%s' depends on impure derivation '%s'",
@@ -705,8 +705,7 @@ static void movePath(const Path & src, const Path & dst)
if (changePerm)
chmod_(src, st.st_mode | S_IWUSR);
- if (rename(src.c_str(), dst.c_str()))
- throw SysError("renaming '%1%' to '%2%'", src, dst);
+ renameFile(src, dst);
if (changePerm)
chmod_(dst, st.st_mode);
@@ -914,12 +913,6 @@ void DerivationGoal::buildDone()
outputPaths
);
- if (buildMode == bmCheck) {
- cleanupPostOutputsRegisteredModeCheck();
- done(BuildResult::Built, std::move(builtOutputs));
- return;
- }
-
cleanupPostOutputsRegisteredModeNonCheck();
/* Repeat the build if necessary. */
diff --git a/src/libstore/build/hook-instance.cc b/src/libstore/build/hook-instance.cc
index 1f19ddccc..cb58a1f02 100644
--- a/src/libstore/build/hook-instance.cc
+++ b/src/libstore/build/hook-instance.cc
@@ -16,11 +16,11 @@ HookInstance::HookInstance()
buildHookArgs.pop_front();
Strings args;
+ args.push_back(std::string(baseNameOf(buildHook)));
for (auto & arg : buildHookArgs)
args.push_back(arg);
- args.push_back(std::string(baseNameOf(settings.buildHook.get())));
args.push_back(std::to_string(verbosity));
/* Create a pipe to get the output of the child. */
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index d1ec91ed5..5cea3b590 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -223,8 +223,7 @@ static void movePath(const Path & src, const Path & dst)
if (changePerm)
chmod_(src, st.st_mode | S_IWUSR);
- if (rename(src.c_str(), dst.c_str()))
- throw SysError("renaming '%1%' to '%2%'", src, dst);
+ renameFile(src, dst);
if (changePerm)
chmod_(dst, st.st_mode);
@@ -311,7 +310,7 @@ bool LocalDerivationGoal::cleanupDecideWhetherDiskFull()
if (buildMode != bmCheck && status.known->isValid()) continue;
auto p = worker.store.printStorePath(status.known->path);
if (pathExists(chrootRootDir + p))
- rename((chrootRootDir + p).c_str(), p.c_str());
+ renameFile((chrootRootDir + p), p);
}
return diskFull;
@@ -845,18 +844,43 @@ void LocalDerivationGoal::startBuilder()
/* Some distros patch Linux to not allow unprivileged
* user namespaces. If we get EPERM or EINVAL, try
* without CLONE_NEWUSER and see if that works.
+ * Details: https://salsa.debian.org/kernel-team/linux/-/commit/d98e00eda6bea437e39b9e80444eee84a32438a6
*/
usingUserNamespace = false;
flags &= ~CLONE_NEWUSER;
child = clone(childEntry, stack + stackSize, flags, this);
}
- /* Otherwise exit with EPERM so we can handle this in the
- parent. This is only done when sandbox-fallback is set
- to true (the default). */
- if (child == -1 && (errno == EPERM || errno == EINVAL) && settings.sandboxFallback)
- _exit(1);
- if (child == -1) throw SysError("cloning builder process");
-
+ if (child == -1) {
+ switch(errno) {
+ case EPERM:
+ case EINVAL: {
+ int errno_ = errno;
+ if (!userNamespacesEnabled && errno==EPERM)
+ notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/user/max_user_namespaces");
+ if (userNamespacesEnabled) {
+ Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
+ if (pathExists(procSysKernelUnprivilegedUsernsClone)
+ && trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0") {
+ notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/kernel/unprivileged_userns_clone");
+ }
+ }
+ Path procSelfNsUser = "/proc/self/ns/user";
+ if (!pathExists(procSelfNsUser))
+ notice("/proc/self/ns/user does not exist; your kernel was likely built without CONFIG_USER_NS=y, which is required for sandboxing");
+ /* Otherwise exit with EPERM so we can handle this in the
+ parent. This is only done when sandbox-fallback is set
+ to true (the default). */
+ if (settings.sandboxFallback)
+ _exit(1);
+ /* Mention sandbox-fallback in the error message so the user
+ knows that having it disabled contributed to the
+ unrecoverability of this failure */
+ throw SysError(errno_, "creating sandboxed builder process using clone(), without sandbox-fallback");
+ }
+ default:
+ throw SysError("creating sandboxed builder process using clone()");
+ }
+ }
writeFull(builderOut.writeSide.get(),
fmt("%d %d\n", usingUserNamespace, child));
_exit(0);
@@ -1570,6 +1594,8 @@ void LocalDerivationGoal::runChild()
/* Warning: in the child we should absolutely not make any SQLite
calls! */
+ bool sendException = true;
+
try { /* child */
commonChildInit(builderOut);
@@ -2026,6 +2052,8 @@ void LocalDerivationGoal::runChild()
/* Indicate that we managed to set up the build environment. */
writeFull(STDERR_FILENO, std::string("\2\n"));
+ sendException = false;
+
/* Execute the program. This should not return. */
if (drv->isBuiltin()) {
try {
@@ -2079,10 +2107,13 @@ void LocalDerivationGoal::runChild()
throw SysError("executing '%1%'", drv->builder);
} catch (Error & e) {
- writeFull(STDERR_FILENO, "\1\n");
- FdSink sink(STDERR_FILENO);
- sink << e;
- sink.flush();
+ if (sendException) {
+ writeFull(STDERR_FILENO, "\1\n");
+ FdSink sink(STDERR_FILENO);
+ sink << e;
+ sink.flush();
+ } else
+ std::cerr << e.msg();
_exit(1);
}
}
@@ -2350,10 +2381,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
if (*scratchPath != finalPath) {
// Also rewrite the output path
auto source = sinkToSource([&](Sink & nextSink) {
- StringSink sink;
- dumpPath(actualPath, sink);
RewritingSink rsink2(oldHashPart, std::string(finalPath.hashPart()), nextSink);
- rsink2(sink.s);
+ dumpPath(actualPath, rsink2);
rsink2.flush();
});
Path tmpPath = actualPath + ".tmp";
@@ -2600,8 +2629,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
Path prev = path + checkSuffix;
deletePath(prev);
Path dst = path + checkSuffix;
- if (rename(path.c_str(), dst.c_str()))
- throw SysError("renaming '%s' to '%s'", path, dst);
+ renameFile(path, dst);
}
}
diff --git a/src/libstore/builtins/unpack-channel.cc b/src/libstore/builtins/unpack-channel.cc
index 426d58a53..ba04bb16c 100644
--- a/src/libstore/builtins/unpack-channel.cc
+++ b/src/libstore/builtins/unpack-channel.cc
@@ -22,8 +22,7 @@ void builtinUnpackChannel(const BasicDerivation & drv)
auto entries = readDirectory(out);
if (entries.size() != 1)
throw Error("channel tarball '%s' contains more than one file", src);
- if (rename((out + "/" + entries[0].name).c_str(), (out + "/" + channelName).c_str()) == -1)
- throw SysError("renaming channel directory");
+ renameFile((out + "/" + entries[0].name), (out + "/" + channelName));
}
}
diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc
index de69b50ee..48dd5c247 100644
--- a/src/libstore/daemon.cc
+++ b/src/libstore/daemon.cc
@@ -239,6 +239,8 @@ struct ClientSettings
else if (trusted
|| name == settings.buildTimeout.name
|| name == settings.buildRepeat.name
+ || name == settings.maxSilentTime.name
+ || name == settings.pollInterval.name
|| name == "connect-timeout"
|| (name == "builders" && value == ""))
settings.set(name, value);
diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc
index 8454ad7d2..5746c32a3 100644
--- a/src/libstore/filetransfer.cc
+++ b/src/libstore/filetransfer.cc
@@ -308,6 +308,9 @@ struct curlFileTransfer : public FileTransfer
curl_easy_setopt(req, CURLOPT_HTTPHEADER, requestHeaders);
+ if (settings.downloadSpeed.get() > 0)
+ curl_easy_setopt(req, CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t) (settings.downloadSpeed.get() * 1024));
+
if (request.head)
curl_easy_setopt(req, CURLOPT_NOBODY, 1);
@@ -319,7 +322,6 @@ struct curlFileTransfer : public FileTransfer
}
if (request.verifyTLS) {
- debug("verify TLS: Nix CA file = '%s'", settings.caFile);
if (settings.caFile != "")
curl_easy_setopt(req, CURLOPT_CAINFO, settings.caFile.c_str());
} else {
diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc
index d58ed78b1..9ef8972f3 100644
--- a/src/libstore/gc.cc
+++ b/src/libstore/gc.cc
@@ -39,9 +39,7 @@ static void makeSymlink(const Path & link, const Path & target)
createSymlink(target, tempLink);
/* Atomically replace the old one. */
- if (rename(tempLink.c_str(), link.c_str()) == -1)
- throw SysError("cannot rename '%1%' to '%2%'",
- tempLink , link);
+ renameFile(tempLink, link);
}
@@ -621,6 +619,17 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
Path path = storeDir + "/" + std::string(baseName);
Path realPath = realStoreDir + "/" + std::string(baseName);
+ /* There may be temp directories in the store that are still in use
+ by another process. We need to be sure that we can acquire an
+ exclusive lock before deleting them. */
+ if (baseName.find("tmp-", 0) == 0) {
+ AutoCloseFD tmpDirFd = open(realPath.c_str(), O_RDONLY | O_DIRECTORY);
+ if (tmpDirFd.get() == -1 || !lockFile(tmpDirFd.get(), ltWrite, false)) {
+ debug("skipping locked tempdir '%s'", realPath);
+ return;
+ }
+ }
+
printInfo("deleting '%1%'", path);
results.paths.insert(path);
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index 0f2ca4b15..ff658c428 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -114,7 +114,13 @@ std::vector<Path> getUserConfigFiles()
unsigned int Settings::getDefaultCores()
{
- return std::max(1U, std::thread::hardware_concurrency());
+ const unsigned int concurrency = std::max(1U, std::thread::hardware_concurrency());
+ const unsigned int maxCPU = getMaxCPU();
+
+ if (maxCPU > 0)
+ return maxCPU;
+ else
+ return concurrency;
}
StringSet Settings::getDefaultSystemFeatures()
@@ -148,13 +154,9 @@ StringSet Settings::getDefaultExtraPlatforms()
// machines. Note that we can’t force processes from executing
// x86_64 in aarch64 environments or vice versa since they can
// always exec with their own binary preferences.
- if (pathExists("/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist") ||
- pathExists("/System/Library/LaunchDaemons/com.apple.oahd.plist")) {
- if (std::string{SYSTEM} == "x86_64-darwin")
- extraPlatforms.insert("aarch64-darwin");
- else if (std::string{SYSTEM} == "aarch64-darwin")
- extraPlatforms.insert("x86_64-darwin");
- }
+ if (std::string{SYSTEM} == "aarch64-darwin" &&
+ runProgram(RunOptions {.program = "arch", .args = {"-arch", "x86_64", "/usr/bin/true"}, .mergeStderrToStdout = true}).first == 0)
+ extraPlatforms.insert("x86_64-darwin");
#endif
return extraPlatforms;
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index d7f351166..3dcf3d479 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -560,9 +560,15 @@ public:
R"(
If set to `true` (the default), any non-content-addressed path added
or copied to the Nix store (e.g. when substituting from a binary
- cache) must have a valid signature, that is, be signed using one of
- the keys listed in `trusted-public-keys` or `secret-key-files`. Set
- to `false` to disable signature checking.
+ cache) must have a signature by a trusted key. A trusted key is one
+ listed in `trusted-public-keys`, or a public key counterpart to a
+ private key stored in a file listed in `secret-key-files`.
+
+ Set to `false` to disable signature checking and trust all
+ non-content-addressed paths unconditionally.
+
+ (Content-addressed paths are inherently trustworthy and thus
+ unaffected by this configuration option.)
)"};
Setting<StringSet> extraPlatforms{
@@ -613,6 +619,14 @@ public:
are tried based on their Priority value, which each substituter can set
independently. Lower value means higher priority.
The default is `https://cache.nixos.org`, with a Priority of 40.
+
+ Nix will copy a store path from a remote store only if one
+ of the following is true:
+
+ - the store object is signed by one of the [`trusted-public-keys`](#conf-trusted-public-keys)
+ - the substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
+ - the [`require-sigs`](#conf-require-sigs) option has been set to `false`
+ - the store object is [output-addressed](glossary.md#gloss-output-addressed-store-object)
)",
{"binary-caches"}};
@@ -746,6 +760,13 @@ public:
/nix/store/xfghy8ixrhz3kyy6p724iv3cxji088dx-bash-4.4-p23`.
)"};
+ Setting<unsigned int> downloadSpeed {
+ this, 0, "download-speed",
+ R"(
+ Specify the maximum transfer rate in kilobytes per second you want
+ Nix to use for downloads.
+ )"};
+
Setting<std::string> netrcFile{
this, fmt("%s/%s", nixConfDir, "netrc"), "netrc-file",
R"(
diff --git a/src/libstore/local-binary-cache-store.cc b/src/libstore/local-binary-cache-store.cc
index ba4416f6d..f20b1fa02 100644
--- a/src/libstore/local-binary-cache-store.cc
+++ b/src/libstore/local-binary-cache-store.cc
@@ -57,8 +57,7 @@ protected:
AutoDelete del(tmp, false);
StreamToSourceAdapter source(istream);
writeFile(tmp, source);
- if (rename(tmp.c_str(), path2.c_str()))
- throw SysError("renaming '%1%' to '%2%'", tmp, path2);
+ renameFile(tmp, path2);
del.cancel();
}
diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc
index eba3b0fa5..d374d4558 100644
--- a/src/libstore/local-store.cc
+++ b/src/libstore/local-store.cc
@@ -158,7 +158,7 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
txn.commit();
}
- writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
+ writeFile(schemaPath, fmt("%d", nixCASchemaVersion), 0666, true);
lockFile(lockFd.get(), ltRead, true);
}
}
@@ -281,7 +281,7 @@ LocalStore::LocalStore(const Params & params)
else if (curSchema == 0) { /* new store */
curSchema = nixSchemaVersion;
openDB(*state, true);
- writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str());
+ writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str(), 0666, true);
}
else if (curSchema < nixSchemaVersion) {
@@ -329,7 +329,7 @@ LocalStore::LocalStore(const Params & params)
txn.commit();
}
- writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str());
+ writeFile(schemaPath, (format("%1%") % nixSchemaVersion).str(), 0666, true);
lockFile(globalLock.get(), ltRead, true);
}
@@ -751,7 +751,7 @@ void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag check
if (checkSigs == NoCheckSigs || !realisationIsUntrusted(info))
registerDrvOutput(info);
else
- throw Error("cannot register realisation '%s' because it lacks a valid signature", info.outPath.to_string());
+ throw Error("cannot register realisation '%s' because it lacks a signature by a trusted key", info.outPath.to_string());
}
void LocalStore::registerDrvOutput(const Realisation & info)
@@ -1266,7 +1266,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
RepairFlag repair, CheckSigsFlag checkSigs)
{
if (checkSigs && pathInfoIsUntrusted(info))
- throw Error("cannot add path '%s' because it lacks a valid signature", printStorePath(info.path));
+ throw Error("cannot add path '%s' because it lacks a signature by a trusted key", printStorePath(info.path));
addTempRoot(info.path);
@@ -1382,13 +1382,15 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
std::unique_ptr<AutoDelete> delTempDir;
Path tempPath;
+ Path tempDir;
+ AutoCloseFD tempDirFd;
if (!inMemory) {
/* Drain what we pulled so far, and then keep on pulling */
StringSource dumpSource { dump };
ChainSource bothSource { dumpSource, source };
- auto tempDir = createTempDir(realStoreDir, "add");
+ std::tie(tempDir, tempDirFd) = createTempDirInStore();
delTempDir = std::make_unique<AutoDelete>(tempDir);
tempPath = tempDir + "/x";
@@ -1430,8 +1432,7 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
writeFile(realPath, dumpSource);
} else {
/* Move the temporary path we restored above. */
- if (rename(tempPath.c_str(), realPath.c_str()))
- throw Error("renaming '%s' to '%s'", tempPath, realPath);
+ moveFile(tempPath, realPath);
}
/* For computing the nar hash. In recursive SHA-256 mode, this
@@ -1508,18 +1509,24 @@ StorePath LocalStore::addTextToStore(
/* Create a temporary directory in the store that won't be
- garbage-collected. */
-Path LocalStore::createTempDirInStore()
+ garbage-collected until the returned FD is closed. */
+std::pair<Path, AutoCloseFD> LocalStore::createTempDirInStore()
{
- Path tmpDir;
+ Path tmpDirFn;
+ AutoCloseFD tmpDirFd;
+ bool lockedByUs = false;
do {
/* There is a slight possibility that `tmpDir' gets deleted by
- the GC between createTempDir() and addTempRoot(), so repeat
- until `tmpDir' exists. */
- tmpDir = createTempDir(realStoreDir);
- addTempRoot(parseStorePath(tmpDir));
- } while (!pathExists(tmpDir));
- return tmpDir;
+ the GC between createTempDir() and when we acquire a lock on it.
+ We'll repeat until 'tmpDir' exists and we've locked it. */
+ tmpDirFn = createTempDir(realStoreDir, "tmp");
+ tmpDirFd = open(tmpDirFn.c_str(), O_RDONLY | O_DIRECTORY);
+ if (tmpDirFd.get() < 0) {
+ continue;
+ }
+ lockedByUs = lockFile(tmpDirFd.get(), ltWrite, true);
+ } while (!pathExists(tmpDirFn) || !lockedByUs);
+ return {tmpDirFn, std::move(tmpDirFd)};
}
@@ -1942,8 +1949,7 @@ void LocalStore::addBuildLog(const StorePath & drvPath, std::string_view log)
writeFile(tmpFile, compress("bzip2", log));
- if (rename(tmpFile.c_str(), logPath.c_str()) != 0)
- throw SysError("renaming '%1%' to '%2%'", tmpFile, logPath);
+ renameFile(tmpFile, logPath);
}
std::optional<std::string> LocalStore::getVersion()
diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh
index 70d225be3..bd0ce1fe6 100644
--- a/src/libstore/local-store.hh
+++ b/src/libstore/local-store.hh
@@ -256,7 +256,7 @@ private:
void findRuntimeRoots(Roots & roots, bool censor);
- Path createTempDirInStore();
+ std::pair<Path, AutoCloseFD> createTempDirInStore();
void checkDerivationOutputs(const StorePath & drvPath, const Derivation & drv);
diff --git a/src/libstore/nar-accessor.cc b/src/libstore/nar-accessor.cc
index 72d41cc94..398147fc3 100644
--- a/src/libstore/nar-accessor.cc
+++ b/src/libstore/nar-accessor.cc
@@ -75,6 +75,9 @@ struct NarAccessor : public FSAccessor
createMember(path, {FSAccessor::Type::tRegular, false, 0, 0});
}
+ void closeRegularFile() override
+ { }
+
void isExecutable() override
{
parents.top()->isExecutable = true;
diff --git a/src/libstore/optimise-store.cc b/src/libstore/optimise-store.cc
index 8af9b1dde..4d2781180 100644
--- a/src/libstore/optimise-store.cc
+++ b/src/libstore/optimise-store.cc
@@ -229,7 +229,9 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
}
/* Atomically replace the old file with the new hard link. */
- if (rename(tempLink.c_str(), path.c_str()) == -1) {
+ try {
+ renameFile(tempLink, path);
+ } catch (SysError & e) {
if (unlink(tempLink.c_str()) == -1)
printError("unable to unlink '%1%'", tempLink);
if (errno == EMLINK) {
@@ -240,7 +242,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
debug("'%s' has reached maximum number of links", linkPath);
return;
}
- throw SysError("cannot rename '%1%' to '%2%'", tempLink, path);
+ throw;
}
stats.filesLinked++;
diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc
index bc36aef5d..96a29155c 100644
--- a/src/libstore/remote-store.cc
+++ b/src/libstore/remote-store.cc
@@ -580,7 +580,6 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
try {
conn->to.written = 0;
- conn->to.warn = true;
connections->incCapacity();
{
Finally cleanup([&]() { connections->decCapacity(); });
@@ -591,7 +590,6 @@ ref<const ValidPathInfo> RemoteStore::addCAToStore(
dumpString(contents, conn->to);
}
}
- conn->to.warn = false;
conn.processStderr();
} catch (SysError & e) {
/* Daemon closed while we were sending the path. Probably OOM
@@ -674,6 +672,23 @@ void RemoteStore::addToStore(const ValidPathInfo & info, Source & source,
void RemoteStore::addMultipleToStore(
+ PathsSource & pathsToCopy,
+ Activity & act,
+ RepairFlag repair,
+ CheckSigsFlag checkSigs)
+{
+ auto source = sinkToSource([&](Sink & sink) {
+ sink << pathsToCopy.size();
+ for (auto & [pathInfo, pathSource] : pathsToCopy) {
+ pathInfo.write(sink, *this, 16);
+ pathSource->drainInto(sink);
+ }
+ });
+
+ addMultipleToStore(*source, repair, checkSigs);
+}
+
+void RemoteStore::addMultipleToStore(
Source & source,
RepairFlag repair,
CheckSigsFlag checkSigs)
diff --git a/src/libstore/remote-store.hh b/src/libstore/remote-store.hh
index 8493be6fc..11d089cd2 100644
--- a/src/libstore/remote-store.hh
+++ b/src/libstore/remote-store.hh
@@ -88,6 +88,12 @@ public:
RepairFlag repair,
CheckSigsFlag checkSigs) override;
+ void addMultipleToStore(
+ PathsSource & pathsToCopy,
+ Activity & act,
+ RepairFlag repair,
+ CheckSigsFlag checkSigs) override;
+
StorePath addTextToStore(
std::string_view name,
std::string_view s,
diff --git a/src/libstore/sandbox-defaults.sb b/src/libstore/sandbox-defaults.sb
index 56b35c3fe..d9d710559 100644
--- a/src/libstore/sandbox-defaults.sb
+++ b/src/libstore/sandbox-defaults.sb
@@ -98,7 +98,9 @@
(allow file*
(literal "/private/var/select/sh"))
-; Allow Rosetta 2 to run x86_64 binaries on aarch64-darwin.
+; Allow Rosetta 2 to run x86_64 binaries on aarch64-darwin (and vice versa).
(allow file-read*
(subpath "/Library/Apple/usr/libexec/oah")
- (subpath "/System/Library/Apple/usr/libexec/oah"))
+ (subpath "/System/Library/Apple/usr/libexec/oah")
+ (subpath "/System/Library/LaunchDaemons/com.apple.oahd.plist")
+ (subpath "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist"))
diff --git a/src/libstore/sandbox-network.sb b/src/libstore/sandbox-network.sb
index 56beec761..19e9eea9a 100644
--- a/src/libstore/sandbox-network.sb
+++ b/src/libstore/sandbox-network.sb
@@ -14,3 +14,7 @@
; Allow DNS lookups.
(allow network-outbound (remote unix-socket (path-literal "/private/var/run/mDNSResponder")))
+
+; Allow access to trustd.
+(allow mach-lookup (global-name "com.apple.trustd"))
+(allow mach-lookup (global-name "com.apple.trustd.agent"))
diff --git a/src/libstore/ssh.cc b/src/libstore/ssh.cc
index 1bbad71f2..69bfe3418 100644
--- a/src/libstore/ssh.cc
+++ b/src/libstore/ssh.cc
@@ -67,7 +67,7 @@ std::unique_ptr<SSHMaster::Connection> SSHMaster::startCommand(const std::string
if (fakeSSH) {
args = { "bash", "-c" };
} else {
- args = { "ssh", host.c_str(), "-x", "-a" };
+ args = { "ssh", host.c_str(), "-x" };
addCommonSSHOpts(args);
if (socketPath != "")
args.insert(args.end(), {"-S", socketPath});
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 45c53f23e..06a9758fc 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -258,6 +258,84 @@ StorePath Store::addToStore(
return addToStoreFromDump(*source, name, method, hashAlgo, repair, references);
}
+void Store::addMultipleToStore(
+ PathsSource & pathsToCopy,
+ Activity & act,
+ RepairFlag repair,
+ CheckSigsFlag checkSigs)
+{
+ std::atomic<size_t> nrDone{0};
+ std::atomic<size_t> nrFailed{0};
+ std::atomic<uint64_t> bytesExpected{0};
+ std::atomic<uint64_t> nrRunning{0};
+
+ using PathWithInfo = std::pair<ValidPathInfo, std::unique_ptr<Source>>;
+
+ std::map<StorePath, PathWithInfo *> infosMap;
+ StorePathSet storePathsToAdd;
+ for (auto & thingToAdd : pathsToCopy) {
+ infosMap.insert_or_assign(thingToAdd.first.path, &thingToAdd);
+ storePathsToAdd.insert(thingToAdd.first.path);
+ }
+
+ auto showProgress = [&]() {
+ act.progress(nrDone, pathsToCopy.size(), nrRunning, nrFailed);
+ };
+
+ ThreadPool pool;
+
+ processGraph<StorePath>(pool,
+ storePathsToAdd,
+
+ [&](const StorePath & path) {
+
+ auto & [info, _] = *infosMap.at(path);
+
+ if (isValidPath(info.path)) {
+ nrDone++;
+ showProgress();
+ return StorePathSet();
+ }
+
+ bytesExpected += info.narSize;
+ act.setExpected(actCopyPath, bytesExpected);
+
+ return info.references;
+ },
+
+ [&](const StorePath & path) {
+ checkInterrupt();
+
+ auto & [info_, source_] = *infosMap.at(path);
+ auto info = info_;
+ info.ultimate = false;
+
+ /* Make sure that the Source object is destroyed when
+ we're done. In particular, a SinkToSource object must
+ be destroyed to ensure that the destructors on its
+ stack frame are run; this includes
+ LegacySSHStore::narFromPath()'s connection lock. */
+ auto source = std::move(source_);
+
+ if (!isValidPath(info.path)) {
+ MaintainCount<decltype(nrRunning)> mc(nrRunning);
+ showProgress();
+ try {
+ addToStore(info, *source, repair, checkSigs);
+ } catch (Error & e) {
+ nrFailed++;
+ if (!settings.keepGoing)
+ throw e;
+ printMsg(lvlError, "could not copy %s: %s", printStorePath(path), e.what());
+ showProgress();
+ return;
+ }
+ }
+
+ nrDone++;
+ showProgress();
+ });
+}
void Store::addMultipleToStore(
Source & source,
@@ -992,113 +1070,61 @@ std::map<StorePath, StorePath> copyPaths(
for (auto & path : storePaths)
if (!valid.count(path)) missing.insert(path);
+ Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size()));
+
+ // In the general case, `addMultipleToStore` requires a sorted list of
+ // store paths to add, so sort them right now
+ auto sortedMissing = srcStore.topoSortPaths(missing);
+ std::reverse(sortedMissing.begin(), sortedMissing.end());
+
std::map<StorePath, StorePath> pathsMap;
for (auto & path : storePaths)
pathsMap.insert_or_assign(path, path);
- Activity act(*logger, lvlInfo, actCopyPaths, fmt("copying %d paths", missing.size()));
+ Store::PathsSource pathsToCopy;
+
+ auto computeStorePathForDst = [&](const ValidPathInfo & currentPathInfo) -> StorePath {
+ auto storePathForSrc = currentPathInfo.path;
+ auto storePathForDst = storePathForSrc;
+ if (currentPathInfo.ca && currentPathInfo.references.empty()) {
+ storePathForDst = dstStore.makeFixedOutputPathFromCA(storePathForSrc.name(), *currentPathInfo.ca);
+ if (dstStore.storeDir == srcStore.storeDir)
+ assert(storePathForDst == storePathForSrc);
+ if (storePathForDst != storePathForSrc)
+ debug("replaced path '%s' to '%s' for substituter '%s'",
+ srcStore.printStorePath(storePathForSrc),
+ dstStore.printStorePath(storePathForDst),
+ dstStore.getUri());
+ }
+ return storePathForDst;
+ };
- auto sorted = srcStore.topoSortPaths(missing);
- std::reverse(sorted.begin(), sorted.end());
+ for (auto & missingPath : sortedMissing) {
+ auto info = srcStore.queryPathInfo(missingPath);
- auto source = sinkToSource([&](Sink & sink) {
- sink << sorted.size();
- for (auto & storePath : sorted) {
+ auto storePathForDst = computeStorePathForDst(*info);
+ pathsMap.insert_or_assign(missingPath, storePathForDst);
+
+ ValidPathInfo infoForDst = *info;
+ infoForDst.path = storePathForDst;
+
+ auto source = sinkToSource([&](Sink & sink) {
+ // We can reasonably assume that the copy will happen whenever we
+ // read the path, so log something about that at that point
auto srcUri = srcStore.getUri();
auto dstUri = dstStore.getUri();
- auto storePathS = srcStore.printStorePath(storePath);
+ auto storePathS = srcStore.printStorePath(missingPath);
Activity act(*logger, lvlInfo, actCopyPath,
makeCopyPathMessage(srcUri, dstUri, storePathS),
{storePathS, srcUri, dstUri});
PushActivity pact(act.id);
- auto info = srcStore.queryPathInfo(storePath);
- info->write(sink, srcStore, 16);
- srcStore.narFromPath(storePath, sink);
- }
- });
-
- dstStore.addMultipleToStore(*source, repair, checkSigs);
-
- #if 0
- std::atomic<size_t> nrDone{0};
- std::atomic<size_t> nrFailed{0};
- std::atomic<uint64_t> bytesExpected{0};
- std::atomic<uint64_t> nrRunning{0};
-
- auto showProgress = [&]() {
- act.progress(nrDone, missing.size(), nrRunning, nrFailed);
- };
-
- ThreadPool pool;
-
- processGraph<StorePath>(pool,
- StorePathSet(missing.begin(), missing.end()),
-
- [&](const StorePath & storePath) {
- auto info = srcStore.queryPathInfo(storePath);
- auto storePathForDst = storePath;
- if (info->ca && info->references.empty()) {
- storePathForDst = dstStore.makeFixedOutputPathFromCA(storePath.name(), *info->ca);
- if (dstStore.storeDir == srcStore.storeDir)
- assert(storePathForDst == storePath);
- if (storePathForDst != storePath)
- debug("replaced path '%s' to '%s' for substituter '%s'",
- srcStore.printStorePath(storePath),
- dstStore.printStorePath(storePathForDst),
- dstStore.getUri());
- }
- pathsMap.insert_or_assign(storePath, storePathForDst);
-
- if (dstStore.isValidPath(storePath)) {
- nrDone++;
- showProgress();
- return StorePathSet();
- }
-
- bytesExpected += info->narSize;
- act.setExpected(actCopyPath, bytesExpected);
-
- return info->references;
- },
-
- [&](const StorePath & storePath) {
- checkInterrupt();
-
- auto info = srcStore.queryPathInfo(storePath);
-
- auto storePathForDst = storePath;
- if (info->ca && info->references.empty()) {
- storePathForDst = dstStore.makeFixedOutputPathFromCA(storePath.name(), *info->ca);
- if (dstStore.storeDir == srcStore.storeDir)
- assert(storePathForDst == storePath);
- if (storePathForDst != storePath)
- debug("replaced path '%s' to '%s' for substituter '%s'",
- srcStore.printStorePath(storePath),
- dstStore.printStorePath(storePathForDst),
- dstStore.getUri());
- }
- pathsMap.insert_or_assign(storePath, storePathForDst);
-
- if (!dstStore.isValidPath(storePathForDst)) {
- MaintainCount<decltype(nrRunning)> mc(nrRunning);
- showProgress();
- try {
- copyStorePath(srcStore, dstStore, storePath, repair, checkSigs);
- } catch (Error &e) {
- nrFailed++;
- if (!settings.keepGoing)
- throw e;
- printMsg(lvlError, "could not copy %s: %s", dstStore.printStorePath(storePath), e.what());
- showProgress();
- return;
- }
- }
-
- nrDone++;
- showProgress();
+ srcStore.narFromPath(missingPath, sink);
});
- #endif
+ pathsToCopy.push_back(std::pair{infoForDst, std::move(source)});
+ }
+
+ dstStore.addMultipleToStore(pathsToCopy, act, repair, checkSigs);
return pathsMap;
}
@@ -1321,7 +1347,12 @@ std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Para
else if (pathExists(settings.nixDaemonSocketFile))
return std::make_shared<UDSRemoteStore>(params);
#if __linux__
- else if (!pathExists(stateDir) && params.empty() && getuid() != 0 && !getEnv("NIX_STORE_DIR").has_value()) {
+ else if (!pathExists(stateDir)
+ && params.empty()
+ && getuid() != 0
+ && !getEnv("NIX_STORE_DIR").has_value()
+ && !getEnv("NIX_STATE_DIR").has_value())
+ {
/* If /nix doesn't exist, there is no daemon socket, and
we're not root, then automatically set up a chroot
store in ~/.local/share/nix/root. */
@@ -1332,9 +1363,9 @@ std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Para
} catch (Error & e) {
return std::make_shared<LocalStore>(params);
}
- warn("'/nix' does not exist, so Nix will use '%s' as a chroot store", chrootStore);
+ warn("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore);
} else
- debug("'/nix' does not exist, so Nix will use '%s' as a chroot store", chrootStore);
+ debug("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore);
Store::Params params2;
params2["root"] = chrootStore;
return std::make_shared<LocalStore>(params2);
diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh
index 0c8a4db56..c8a667c6d 100644
--- a/src/libstore/store-api.hh
+++ b/src/libstore/store-api.hh
@@ -1,5 +1,6 @@
#pragma once
+#include "nar-info.hh"
#include "realisation.hh"
#include "path.hh"
#include "derived-path.hh"
@@ -359,12 +360,22 @@ public:
virtual void addToStore(const ValidPathInfo & info, Source & narSource,
RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs) = 0;
+ // A list of paths infos along with a source providing the content of the
+ // associated store path
+ using PathsSource = std::vector<std::pair<ValidPathInfo, std::unique_ptr<Source>>>;
+
/* Import multiple paths into the store. */
virtual void addMultipleToStore(
Source & source,
RepairFlag repair = NoRepair,
CheckSigsFlag checkSigs = CheckSigs);
+ virtual void addMultipleToStore(
+ PathsSource & pathsToCopy,
+ Activity & act,
+ RepairFlag repair = NoRepair,
+ CheckSigsFlag checkSigs = CheckSigs);
+
/* Copy the contents of a path to the store and register the
validity the resulting path. The resulting path is returned.
The function object `filter' can be used to exclude files (see
diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc
index 30b471af5..4b0636129 100644
--- a/src/libutil/archive.cc
+++ b/src/libutil/archive.cc
@@ -234,6 +234,7 @@ static void parse(ParseSink & sink, Source & source, const Path & path)
else if (s == "contents" && type == tpRegular) {
parseContents(sink, source, path);
+ sink.closeRegularFile();
}
else if (s == "executable" && type == tpRegular) {
@@ -324,6 +325,12 @@ struct RestoreSink : ParseSink
if (!fd) throw SysError("creating file '%1%'", p);
}
+ void closeRegularFile() override
+ {
+ /* Call close explicitly to make sure the error is checked */
+ fd.close();
+ }
+
void isExecutable() override
{
struct stat st;
diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh
index 79ce08df0..ac4183bf5 100644
--- a/src/libutil/archive.hh
+++ b/src/libutil/archive.hh
@@ -60,6 +60,7 @@ struct ParseSink
virtual void createDirectory(const Path & path) { };
virtual void createRegularFile(const Path & path) { };
+ virtual void closeRegularFile() { };
virtual void isExecutable() { };
virtual void preallocateContents(uint64_t size) { };
virtual void receiveContents(std::string_view data) { };
diff --git a/src/libutil/args.cc b/src/libutil/args.cc
index 44b63f0f6..753980fd4 100644
--- a/src/libutil/args.cc
+++ b/src/libutil/args.cc
@@ -216,7 +216,7 @@ nlohmann::json Args::toJSON()
if (flag->shortName)
j["shortName"] = std::string(1, flag->shortName);
if (flag->description != "")
- j["description"] = flag->description;
+ j["description"] = trim(flag->description);
j["category"] = flag->category;
if (flag->handler.arity != ArityAny)
j["arity"] = flag->handler.arity;
@@ -237,7 +237,7 @@ nlohmann::json Args::toJSON()
}
auto res = nlohmann::json::object();
- res["description"] = description();
+ res["description"] = trim(description());
res["flags"] = std::move(flags);
res["args"] = std::move(args);
auto s = doc();
@@ -379,7 +379,7 @@ nlohmann::json MultiCommand::toJSON()
auto j = command->toJSON();
auto cat = nlohmann::json::object();
cat["id"] = command->category();
- cat["description"] = categories[command->category()];
+ cat["description"] = trim(categories[command->category()]);
j["category"] = std::move(cat);
cmds[name] = std::move(j);
}
diff --git a/src/libutil/error.hh b/src/libutil/error.hh
index a53e9802e..3d1479c54 100644
--- a/src/libutil/error.hh
+++ b/src/libutil/error.hh
@@ -204,13 +204,19 @@ public:
int errNo;
template<typename... Args>
- SysError(const Args & ... args)
+ SysError(int errNo_, const Args & ... args)
: Error("")
{
- errNo = errno;
+ errNo = errNo_;
auto hf = hintfmt(args...);
err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
}
+
+ template<typename... Args>
+ SysError(const Args & ... args)
+ : SysError(errno, args ...)
+ {
+ }
};
}
diff --git a/src/libutil/filesystem.cc b/src/libutil/filesystem.cc
new file mode 100644
index 000000000..403389e60
--- /dev/null
+++ b/src/libutil/filesystem.cc
@@ -0,0 +1,172 @@
+#include <sys/time.h>
+#include <filesystem>
+
+#include "finally.hh"
+#include "util.hh"
+#include "types.hh"
+
+namespace fs = std::filesystem;
+
+namespace nix {
+
+static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
+ int & counter)
+{
+ tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
+ if (includePid)
+ return (format("%1%/%2%-%3%-%4%") % tmpRoot % prefix % getpid() % counter++).str();
+ else
+ return (format("%1%/%2%-%3%") % tmpRoot % prefix % counter++).str();
+}
+
+Path createTempDir(const Path & tmpRoot, const Path & prefix,
+ bool includePid, bool useGlobalCounter, mode_t mode)
+{
+ static int globalCounter = 0;
+ int localCounter = 0;
+ int & counter(useGlobalCounter ? globalCounter : localCounter);
+
+ while (1) {
+ checkInterrupt();
+ Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
+ if (mkdir(tmpDir.c_str(), mode) == 0) {
+#if __FreeBSD__
+ /* Explicitly set the group of the directory. This is to
+ work around around problems caused by BSD's group
+ ownership semantics (directories inherit the group of
+ the parent). For instance, the group of /tmp on
+ FreeBSD is "wheel", so all directories created in /tmp
+ will be owned by "wheel"; but if the user is not in
+ "wheel", then "tar" will fail to unpack archives that
+ have the setgid bit set on directories. */
+ if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
+ throw SysError("setting group of directory '%1%'", tmpDir);
+#endif
+ return tmpDir;
+ }
+ if (errno != EEXIST)
+ throw SysError("creating directory '%1%'", tmpDir);
+ }
+}
+
+
+std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
+{
+ Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
+ // Strictly speaking, this is UB, but who cares...
+ // FIXME: use O_TMPFILE.
+ AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
+ if (!fd)
+ throw SysError("creating temporary file '%s'", tmpl);
+ closeOnExec(fd.get());
+ return {std::move(fd), tmpl};
+}
+
+void createSymlink(const Path & target, const Path & link,
+ std::optional<time_t> mtime)
+{
+ if (symlink(target.c_str(), link.c_str()))
+ throw SysError("creating symlink from '%1%' to '%2%'", link, target);
+ if (mtime) {
+ struct timeval times[2];
+ times[0].tv_sec = *mtime;
+ times[0].tv_usec = 0;
+ times[1].tv_sec = *mtime;
+ times[1].tv_usec = 0;
+ if (lutimes(link.c_str(), times))
+ throw SysError("setting time of symlink '%s'", link);
+ }
+}
+
+void replaceSymlink(const Path & target, const Path & link,
+ std::optional<time_t> mtime)
+{
+ for (unsigned int n = 0; true; n++) {
+ Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
+
+ try {
+ createSymlink(target, tmp, mtime);
+ } catch (SysError & e) {
+ if (e.errNo == EEXIST) continue;
+ throw;
+ }
+
+ renameFile(tmp, link);
+
+ break;
+ }
+}
+
+void setWriteTime(const fs::path & p, const struct stat & st)
+{
+ struct timeval times[2];
+ times[0] = {
+ .tv_sec = st.st_atime,
+ .tv_usec = 0,
+ };
+ times[1] = {
+ .tv_sec = st.st_mtime,
+ .tv_usec = 0,
+ };
+ if (lutimes(p.c_str(), times) != 0)
+ throw SysError("changing modification time of '%s'", p);
+}
+
+void copy(const fs::directory_entry & from, const fs::path & to, bool andDelete)
+{
+ // TODO: Rewrite the `is_*` to use `symlink_status()`
+ auto statOfFrom = lstat(from.path().c_str());
+ auto fromStatus = from.symlink_status();
+
+ // Mark the directory as writable so that we can delete its children
+ if (andDelete && fs::is_directory(fromStatus)) {
+ fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
+ }
+
+
+ if (fs::is_symlink(fromStatus) || fs::is_regular_file(fromStatus)) {
+ fs::copy(from.path(), to, fs::copy_options::copy_symlinks | fs::copy_options::overwrite_existing);
+ } else if (fs::is_directory(fromStatus)) {
+ fs::create_directory(to);
+ for (auto & entry : fs::directory_iterator(from.path())) {
+ copy(entry, to / entry.path().filename(), andDelete);
+ }
+ } else {
+ throw Error("file '%s' has an unsupported type", from.path());
+ }
+
+ setWriteTime(to, statOfFrom);
+ if (andDelete) {
+ if (!fs::is_symlink(fromStatus))
+ fs::permissions(from.path(), fs::perms::owner_write, fs::perm_options::add | fs::perm_options::nofollow);
+ fs::remove(from.path());
+ }
+}
+
+void renameFile(const Path & oldName, const Path & newName)
+{
+ fs::rename(oldName, newName);
+}
+
+void moveFile(const Path & oldName, const Path & newName)
+{
+ try {
+ renameFile(oldName, newName);
+ } catch (fs::filesystem_error & e) {
+ auto oldPath = fs::path(oldName);
+ auto newPath = fs::path(newName);
+ // For the move to be as atomic as possible, copy to a temporary
+ // directory
+ fs::path temp = createTempDir(newPath.parent_path(), "rename-tmp");
+ Finally removeTemp = [&]() { fs::remove(temp); };
+ auto tempCopyTarget = temp / "copy-target";
+ if (e.code().value() == EXDEV) {
+ fs::remove(newPath);
+ warn("Can’t rename %s as %s, copying instead", oldName, newName);
+ copy(fs::directory_entry(oldPath), tempCopyTarget, true);
+ renameFile(tempCopyTarget, newPath);
+ }
+ }
+}
+
+}
diff --git a/src/libutil/json.cc b/src/libutil/json.cc
index b0a5d7e75..2f9e97ff5 100644
--- a/src/libutil/json.cc
+++ b/src/libutil/json.cc
@@ -6,7 +6,8 @@
namespace nix {
-void toJSON(std::ostream & str, const char * start, const char * end)
+template<>
+void toJSON<std::string_view>(std::ostream & str, const std::string_view & s)
{
constexpr size_t BUF_SIZE = 4096;
char buf[BUF_SIZE + 7]; // BUF_SIZE + largest single sequence of puts
@@ -21,7 +22,7 @@ void toJSON(std::ostream & str, const char * start, const char * end)
};
put('"');
- for (auto i = start; i != end; i++) {
+ for (auto i = s.begin(); i != s.end(); i++) {
if (bufPos >= BUF_SIZE) flush();
if (*i == '\"' || *i == '\\') { put('\\'); put(*i); }
else if (*i == '\n') { put('\\'); put('n'); }
@@ -44,7 +45,7 @@ void toJSON(std::ostream & str, const char * start, const char * end)
void toJSON(std::ostream & str, const char * s)
{
- if (!s) str << "null"; else toJSON(str, s, s + strlen(s));
+ if (!s) str << "null"; else toJSON(str, std::string_view(s));
}
template<> void toJSON<int>(std::ostream & str, const int & n) { str << n; }
@@ -55,11 +56,7 @@ template<> void toJSON<long long>(std::ostream & str, const long long & n) { str
template<> void toJSON<unsigned long long>(std::ostream & str, const unsigned long long & n) { str << n; }
template<> void toJSON<float>(std::ostream & str, const float & n) { str << n; }
template<> void toJSON<double>(std::ostream & str, const double & n) { str << n; }
-
-template<> void toJSON<std::string>(std::ostream & str, const std::string & s)
-{
- toJSON(str, s.c_str(), s.c_str() + s.size());
-}
+template<> void toJSON<std::string>(std::ostream & str, const std::string & s) { toJSON(str, (std::string_view) s); }
template<> void toJSON<bool>(std::ostream & str, const bool & b)
{
@@ -154,7 +151,7 @@ JSONObject::~JSONObject()
}
}
-void JSONObject::attr(const std::string & s)
+void JSONObject::attr(std::string_view s)
{
comma();
toJSON(state->str, s);
@@ -162,19 +159,19 @@ void JSONObject::attr(const std::string & s)
if (state->indent) state->str << ' ';
}
-JSONList JSONObject::list(const std::string & name)
+JSONList JSONObject::list(std::string_view name)
{
attr(name);
return JSONList(state);
}
-JSONObject JSONObject::object(const std::string & name)
+JSONObject JSONObject::object(std::string_view name)
{
attr(name);
return JSONObject(state);
}
-JSONPlaceholder JSONObject::placeholder(const std::string & name)
+JSONPlaceholder JSONObject::placeholder(std::string_view name)
{
attr(name);
return JSONPlaceholder(state);
@@ -196,7 +193,11 @@ JSONObject JSONPlaceholder::object()
JSONPlaceholder::~JSONPlaceholder()
{
- assert(!first || std::uncaught_exceptions());
+ if (first) {
+ assert(std::uncaught_exceptions());
+ if (state->stack != 0)
+ write(nullptr);
+ }
}
}
diff --git a/src/libutil/json.hh b/src/libutil/json.hh
index 83213ca66..3790b1a2e 100644
--- a/src/libutil/json.hh
+++ b/src/libutil/json.hh
@@ -6,7 +6,6 @@
namespace nix {
-void toJSON(std::ostream & str, const char * start, const char * end);
void toJSON(std::ostream & str, const char * s);
template<typename T>
@@ -107,7 +106,7 @@ private:
open();
}
- void attr(const std::string & s);
+ void attr(std::string_view s);
public:
@@ -128,18 +127,18 @@ public:
~JSONObject();
template<typename T>
- JSONObject & attr(const std::string & name, const T & v)
+ JSONObject & attr(std::string_view name, const T & v)
{
attr(name);
toJSON(state->str, v);
return *this;
}
- JSONList list(const std::string & name);
+ JSONList list(std::string_view name);
- JSONObject object(const std::string & name);
+ JSONObject object(std::string_view name);
- JSONPlaceholder placeholder(const std::string & name);
+ JSONPlaceholder placeholder(std::string_view name);
};
class JSONPlaceholder : JSONWriter
diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh
index 6f81b92de..d0817b4a9 100644
--- a/src/libutil/logging.hh
+++ b/src/libutil/logging.hh
@@ -111,6 +111,9 @@ public:
virtual std::optional<char> ask(std::string_view s)
{ return {}; }
+
+ virtual void setPrintBuildLogs(bool printBuildLogs)
+ { }
};
ActivityId getCurActivity();
diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc
index 8ff904583..2c3597775 100644
--- a/src/libutil/serialise.cc
+++ b/src/libutil/serialise.cc
@@ -48,24 +48,9 @@ FdSink::~FdSink()
}
-size_t threshold = 256 * 1024 * 1024;
-
-static void warnLargeDump()
-{
- warn("dumping very large path (> 256 MiB); this may run out of memory");
-}
-
-
void FdSink::write(std::string_view data)
{
written += data.size();
- static bool warned = false;
- if (warn && !warned) {
- if (written > threshold) {
- warnLargeDump();
- warned = true;
- }
- }
try {
writeFull(fd, data);
} catch (SysError & e) {
@@ -448,11 +433,6 @@ Error readError(Source & source)
void StringSink::operator () (std::string_view data)
{
- static bool warned = false;
- if (!warned && s.size() > threshold) {
- warnLargeDump();
- warned = true;
- }
s.append(data);
}
diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh
index 13da26c6a..84847835a 100644
--- a/src/libutil/serialise.hh
+++ b/src/libutil/serialise.hh
@@ -97,19 +97,17 @@ protected:
struct FdSink : BufferedSink
{
int fd;
- bool warn = false;
size_t written = 0;
FdSink() : fd(-1) { }
FdSink(int fd) : fd(fd) { }
FdSink(FdSink&&) = default;
- FdSink& operator=(FdSink && s)
+ FdSink & operator=(FdSink && s)
{
flush();
fd = s.fd;
s.fd = -1;
- warn = s.warn;
written = s.written;
return *this;
}
diff --git a/src/libutil/tests/json.cc b/src/libutil/tests/json.cc
index dea73f53a..156286999 100644
--- a/src/libutil/tests/json.cc
+++ b/src/libutil/tests/json.cc
@@ -102,8 +102,8 @@ namespace nix {
TEST(toJSON, substringEscape) {
std::stringstream out;
- const char *s = "foo\t";
- toJSON(out, s+3, s + strlen(s));
+ std::string_view s = "foo\t";
+ toJSON(out, s.substr(3));
ASSERT_EQ(out.str(), "\"\\t\"");
}
diff --git a/src/libutil/util.cc b/src/libutil/util.cc
index 28df30fef..623b74bdd 100644
--- a/src/libutil/util.cc
+++ b/src/libutil/util.cc
@@ -35,6 +35,9 @@
#ifdef __linux__
#include <sys/prctl.h>
#include <sys/resource.h>
+
+#include <mntent.h>
+#include <cmath>
#endif
@@ -350,7 +353,7 @@ void readFile(const Path & path, Sink & sink)
}
-void writeFile(const Path & path, std::string_view s, mode_t mode)
+void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
{
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
if (!fd)
@@ -361,10 +364,16 @@ void writeFile(const Path & path, std::string_view s, mode_t mode)
e.addTrace({}, "writing file '%1%'", path);
throw;
}
+ if (sync)
+ fd.fsync();
+ // Explicitly close to make sure exceptions are propagated.
+ fd.close();
+ if (sync)
+ syncParent(path);
}
-void writeFile(const Path & path, Source & source, mode_t mode)
+void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
{
AutoCloseFD fd = open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC, mode);
if (!fd)
@@ -383,6 +392,20 @@ void writeFile(const Path & path, Source & source, mode_t mode)
e.addTrace({}, "writing file '%1%'", path);
throw;
}
+ if (sync)
+ fd.fsync();
+ // Explicitly close to make sure exceptions are propagated.
+ fd.close();
+ if (sync)
+ syncParent(path);
+}
+
+void syncParent(const Path & path)
+{
+ AutoCloseFD fd = open(dirOf(path).c_str(), O_RDONLY, 0);
+ if (!fd)
+ throw SysError("opening file '%1%'", path);
+ fd.fsync();
}
std::string readLine(int fd)
@@ -505,61 +528,6 @@ void deletePath(const Path & path, uint64_t & bytesFreed)
}
-static Path tempName(Path tmpRoot, const Path & prefix, bool includePid,
- int & counter)
-{
- tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true);
- if (includePid)
- return (format("%1%/%2%-%3%-%4%") % tmpRoot % prefix % getpid() % counter++).str();
- else
- return (format("%1%/%2%-%3%") % tmpRoot % prefix % counter++).str();
-}
-
-
-Path createTempDir(const Path & tmpRoot, const Path & prefix,
- bool includePid, bool useGlobalCounter, mode_t mode)
-{
- static int globalCounter = 0;
- int localCounter = 0;
- int & counter(useGlobalCounter ? globalCounter : localCounter);
-
- while (1) {
- checkInterrupt();
- Path tmpDir = tempName(tmpRoot, prefix, includePid, counter);
- if (mkdir(tmpDir.c_str(), mode) == 0) {
-#if __FreeBSD__
- /* Explicitly set the group of the directory. This is to
- work around around problems caused by BSD's group
- ownership semantics (directories inherit the group of
- the parent). For instance, the group of /tmp on
- FreeBSD is "wheel", so all directories created in /tmp
- will be owned by "wheel"; but if the user is not in
- "wheel", then "tar" will fail to unpack archives that
- have the setgid bit set on directories. */
- if (chown(tmpDir.c_str(), (uid_t) -1, getegid()) != 0)
- throw SysError("setting group of directory '%1%'", tmpDir);
-#endif
- return tmpDir;
- }
- if (errno != EEXIST)
- throw SysError("creating directory '%1%'", tmpDir);
- }
-}
-
-
-std::pair<AutoCloseFD, Path> createTempFile(const Path & prefix)
-{
- Path tmpl(getEnv("TMPDIR").value_or("/tmp") + "/" + prefix + ".XXXXXX");
- // Strictly speaking, this is UB, but who cares...
- // FIXME: use O_TMPFILE.
- AutoCloseFD fd(mkstemp((char *) tmpl.c_str()));
- if (!fd)
- throw SysError("creating temporary file '%s'", tmpl);
- closeOnExec(fd.get());
- return {std::move(fd), tmpl};
-}
-
-
std::string getUserName()
{
auto pw = getpwuid(geteuid());
@@ -574,6 +542,7 @@ Path getHome()
{
static Path homeDir = []()
{
+ std::optional<std::string> unownedUserHomeDir = {};
auto homeDir = getEnv("HOME");
if (homeDir) {
// Only use $HOME if doesn't exist or is owned by the current user.
@@ -585,8 +554,7 @@ Path getHome()
homeDir.reset();
}
} else if (st.st_uid != geteuid()) {
- warn("$HOME ('%s') is not owned by you, falling back to the one defined in the 'passwd' file", *homeDir);
- homeDir.reset();
+ unownedUserHomeDir.swap(homeDir);
}
}
if (!homeDir) {
@@ -597,6 +565,9 @@ Path getHome()
|| !pw || !pw->pw_dir || !pw->pw_dir[0])
throw Error("cannot determine user's home directory");
homeDir = pw->pw_dir;
+ if (unownedUserHomeDir.has_value() && unownedUserHomeDir != homeDir) {
+ warn("$HOME ('%s') is not owned by you, falling back to the one defined in the 'passwd' file ('%s')", *unownedUserHomeDir, *homeDir);
+ }
}
return *homeDir;
}();
@@ -678,44 +649,6 @@ Paths createDirs(const Path & path)
}
-void createSymlink(const Path & target, const Path & link,
- std::optional<time_t> mtime)
-{
- if (symlink(target.c_str(), link.c_str()))
- throw SysError("creating symlink from '%1%' to '%2%'", link, target);
- if (mtime) {
- struct timeval times[2];
- times[0].tv_sec = *mtime;
- times[0].tv_usec = 0;
- times[1].tv_sec = *mtime;
- times[1].tv_usec = 0;
- if (lutimes(link.c_str(), times))
- throw SysError("setting time of symlink '%s'", link);
- }
-}
-
-
-void replaceSymlink(const Path & target, const Path & link,
- std::optional<time_t> mtime)
-{
- for (unsigned int n = 0; true; n++) {
- Path tmp = canonPath(fmt("%s/.%d_%s", dirOf(link), n, baseNameOf(link)));
-
- try {
- createSymlink(target, tmp, mtime);
- } catch (SysError & e) {
- if (e.errNo == EEXIST) continue;
- throw;
- }
-
- if (rename(tmp.c_str(), link.c_str()) != 0)
- throw SysError("renaming '%1%' to '%2%'", tmp, link);
-
- break;
- }
-}
-
-
void readFull(int fd, char * buf, size_t count)
{
while (count) {
@@ -788,7 +721,55 @@ void drainFD(int fd, Sink & sink, bool block)
}
}
+//////////////////////////////////////////////////////////////////////
+
+unsigned int getMaxCPU()
+{
+ #if __linux__
+ try {
+ FILE *fp = fopen("/proc/mounts", "r");
+ if (!fp)
+ return 0;
+
+ Strings cgPathParts;
+
+ struct mntent *ent;
+ while ((ent = getmntent(fp))) {
+ std::string mountType, mountPath;
+
+ mountType = ent->mnt_type;
+ mountPath = ent->mnt_dir;
+ if (mountType == "cgroup2") {
+ cgPathParts.push_back(mountPath);
+ break;
+ }
+ }
+
+ fclose(fp);
+
+ if (cgPathParts.size() > 0 && pathExists("/proc/self/cgroup")) {
+ std::string currentCgroup = readFile("/proc/self/cgroup");
+ Strings cgValues = tokenizeString<Strings>(currentCgroup, ":");
+ cgPathParts.push_back(trim(cgValues.back(), "\n"));
+ cgPathParts.push_back("cpu.max");
+ std::string fullCgPath = canonPath(concatStringsSep("/", cgPathParts));
+
+ if (pathExists(fullCgPath)) {
+ std::string cpuMax = readFile(fullCgPath);
+ std::vector<std::string> cpuMaxParts = tokenizeString<std::vector<std::string>>(cpuMax, " ");
+ std::string quota = cpuMaxParts[0];
+ std::string period = trim(cpuMaxParts[1], "\n");
+
+ if (quota != "max")
+ return std::ceil(std::stoi(quota) / std::stof(period));
+ }
+ }
+ } catch (Error &) { ignoreException(); }
+ #endif
+
+ return 0;
+}
//////////////////////////////////////////////////////////////////////
@@ -880,6 +861,20 @@ void AutoCloseFD::close()
}
}
+void AutoCloseFD::fsync()
+{
+ if (fd != -1) {
+ int result;
+#if __APPLE__
+ result = ::fcntl(fd, F_FULLFSYNC);
+#else
+ result = ::fsync(fd);
+#endif
+ if (result == -1)
+ throw SysError("fsync file descriptor %1%", fd);
+ }
+}
+
AutoCloseFD::operator bool() const
{
diff --git a/src/libutil/util.hh b/src/libutil/util.hh
index d3ed15b0b..e5c678682 100644
--- a/src/libutil/util.hh
+++ b/src/libutil/util.hh
@@ -115,9 +115,12 @@ std::string readFile(const Path & path);
void readFile(const Path & path, Sink & sink);
/* Write a string to a file. */
-void writeFile(const Path & path, std::string_view s, mode_t mode = 0666);
+void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, bool sync = false);
-void writeFile(const Path & path, Source & source, mode_t mode = 0666);
+void writeFile(const Path & path, Source & source, mode_t mode = 0666, bool sync = false);
+
+/* Flush a file's parent directory to disk */
+void syncParent(const Path & path);
/* Read a line from a file descriptor. */
std::string readLine(int fd);
@@ -168,6 +171,17 @@ void createSymlink(const Path & target, const Path & link,
void replaceSymlink(const Path & target, const Path & link,
std::optional<time_t> mtime = {});
+void renameFile(const Path & src, const Path & dst);
+
+/**
+ * Similar to 'renameFile', but fallback to a copy+remove if `src` and `dst`
+ * are on a different filesystem.
+ *
+ * Beware that this might not be atomic because of the copy that happens behind
+ * the scenes
+ */
+void moveFile(const Path & src, const Path & dst);
+
/* Wrappers arount read()/write() that read/write exactly the
requested number of bytes. */
@@ -182,6 +196,9 @@ std::string drainFD(int fd, bool block = true, const size_t reserveSize=0);
void drainFD(int fd, Sink & sink, bool block = true);
+/* If cgroups are active, attempt to calculate the number of CPUs available.
+ If cgroups are unavailable or if cpu.max is set to "max", return 0. */
+unsigned int getMaxCPU();
/* Automatic cleanup of resources. */
@@ -217,6 +234,7 @@ public:
explicit operator bool() const;
int release();
void close();
+ void fsync();
};
diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc
index 7eb8c8f6a..adcaab686 100644
--- a/src/nix-build/nix-build.cc
+++ b/src/nix-build/nix-build.cc
@@ -85,7 +85,6 @@ static void main_nix_build(int argc, char * * argv)
Strings attrPaths;
Strings left;
RepairFlag repair = NoRepair;
- Path gcRoot;
BuildMode buildMode = bmNormal;
bool readStdin = false;
@@ -167,9 +166,6 @@ static void main_nix_build(int argc, char * * argv)
else if (*arg == "--out-link" || *arg == "-o")
outLink = getArg(*arg, arg, end);
- else if (*arg == "--add-root")
- gcRoot = getArg(*arg, arg, end);
-
else if (*arg == "--dry-run")
dryRun = true;
@@ -401,7 +397,7 @@ static void main_nix_build(int argc, char * * argv)
auto bashDrv = drv->requireDrvPath();
pathsToBuild.push_back(DerivedPath::Built {
.drvPath = bashDrv,
- .outputs = {},
+ .outputs = {"out"},
});
pathsToCopy.insert(bashDrv);
shellDrv = bashDrv;
diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc
index a69d3700d..fdd66220a 100644
--- a/src/nix-env/nix-env.cc
+++ b/src/nix-env/nix-env.cc
@@ -940,12 +940,12 @@ static void queryJSON(Globals & globals, std::vector<DrvInfo> & elems, bool prin
JSONObject metaObj = pkgObj.object("meta");
StringSet metaNames = i.queryMetaNames();
for (auto & j : metaNames) {
- auto placeholder = metaObj.placeholder(j);
Value * v = i.queryMeta(j);
if (!v) {
printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j);
- placeholder.write(nullptr);
+ metaObj.attr(j, nullptr);
} else {
+ auto placeholder = metaObj.placeholder(j);
PathSet context;
printValueAsJSON(*globals.state, true, *v, noPos, placeholder, context);
}
diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc
index d3144e131..6b5ba595d 100644
--- a/src/nix-instantiate/nix-instantiate.cc
+++ b/src/nix-instantiate/nix-instantiate.cc
@@ -52,9 +52,10 @@ void processExpr(EvalState & state, const Strings & attrPaths,
state.autoCallFunction(autoArgs, v, vRes);
if (output == okXML)
printValueAsXML(state, strict, location, vRes, std::cout, context, noPos);
- else if (output == okJSON)
+ else if (output == okJSON) {
printValueAsJSON(state, strict, vRes, v.determinePos(noPos), std::cout, context);
- else {
+ std::cout << std::endl;
+ } else {
if (strict) state.forceValueDeep(vRes);
vRes.print(state.symbols, std::cout);
std::cout << std::endl;
diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc
index b453ea1ca..23f2ad3cf 100644
--- a/src/nix-store/nix-store.cc
+++ b/src/nix-store/nix-store.cc
@@ -922,7 +922,7 @@ static void opServe(Strings opFlags, Strings opArgs)
if (GET_PROTOCOL_MINOR(clientVersion) >= 3)
out << status.timesBuilt << status.isNonDeterministic << status.startTime << status.stopTime;
- if (GET_PROTOCOL_MINOR(clientVersion >= 6)) {
+ if (GET_PROTOCOL_MINOR(clientVersion) >= 6) {
worker_proto::write(*store, out, status.builtOutputs);
}
diff --git a/src/nix/app.cc b/src/nix/app.cc
index 821964f86..48de8fb82 100644
--- a/src/nix/app.cc
+++ b/src/nix/app.cc
@@ -66,7 +66,9 @@ UnresolvedApp Installable::toApp(EvalState & state)
auto type = cursor->getAttr("type")->getString();
- std::string expected = !attrPath.empty() && state.symbols[attrPath[0]] == "apps" ? "app" : "derivation";
+ std::string expected = !attrPath.empty() &&
+ (state.symbols[attrPath[0]] == "apps" || state.symbols[attrPath[0]] == "defaultApp")
+ ? "app" : "derivation";
if (type != expected)
throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected);
diff --git a/src/nix/bundle.md b/src/nix/bundle.md
index 2bb70711f..a18161a3c 100644
--- a/src/nix/bundle.md
+++ b/src/nix/bundle.md
@@ -44,7 +44,7 @@ flake output attributes:
* `bundlers.<system>.default`
-If an attribute *name* is given, `nix run` tries the following flake
+If an attribute *name* is given, `nix bundle` tries the following flake
output attributes:
* `bundlers.<system>.<name>`
diff --git a/src/nix/develop.cc b/src/nix/develop.cc
index 6d9ad9942..4de109754 100644
--- a/src/nix/develop.cc
+++ b/src/nix/develop.cc
@@ -246,6 +246,7 @@ struct Common : InstallableCommand, MixProfile
"NIX_LOG_FD",
"NIX_REMOTE",
"PPID",
+ "SHELL",
"SHELLOPTS",
"SSL_CERT_FILE", // FIXME: only want to ignore /no-cert-file.crt
"TEMP",
@@ -288,8 +289,10 @@ struct Common : InstallableCommand, MixProfile
out << "unset shellHook\n";
- for (auto & var : savedVars)
+ for (auto & var : savedVars) {
+ out << fmt("%s=${%s:-}\n", var, var);
out << fmt("nix_saved_%s=\"$%s\"\n", var, var);
+ }
buildEnvironment.toBash(out, ignoreVars);
diff --git a/src/nix/develop.md b/src/nix/develop.md
index e036ec6b9..4e8542d1b 100644
--- a/src/nix/develop.md
+++ b/src/nix/develop.md
@@ -66,6 +66,12 @@ R""(
`nixpkgs#glibc` in `~/my-glibc` and want to compile another package
against it.
+* Run a series of script commands:
+
+ ```console
+ # nix develop --command bash -c "mkdir build && cmake .. && make"
+ ```
+
# Description
`nix develop` starts a `bash` shell that provides an interactive build
diff --git a/src/nix/eval.cc b/src/nix/eval.cc
index 967dc8519..ddd2790c6 100644
--- a/src/nix/eval.cc
+++ b/src/nix/eval.cc
@@ -116,7 +116,8 @@ struct CmdEval : MixJSON, InstallableCommand
else if (json) {
JSONPlaceholder jsonOut(std::cout);
- printValueAsJSON(*state, true, *v, pos, jsonOut, context);
+ printValueAsJSON(*state, true, *v, pos, jsonOut, context, false);
+ std::cout << std::endl;
}
else {
diff --git a/src/nix/flake-update.md b/src/nix/flake-update.md
index 03b50e38e..2ee8a707d 100644
--- a/src/nix/flake-update.md
+++ b/src/nix/flake-update.md
@@ -6,7 +6,7 @@ R""(
lock file:
```console
- # nix flake update
+ # nix flake update --commit-lock-file
* Updated 'nix': 'github:NixOS/nix/9fab14adbc3810d5cc1f88672fde1eee4358405c' -> 'github:NixOS/nix/8927cba62f5afb33b01016d5c4f7f8b7d0adde3c'
* Updated 'nixpkgs': 'github:NixOS/nixpkgs/3d2d8f281a27d466fa54b469b5993f7dde198375' -> 'github:NixOS/nixpkgs/a3a3dda3bacf61e8a39258a0ed9c924eeca8e293'
diff --git a/src/nix/flake.cc b/src/nix/flake.cc
index e01bc6d10..3967f1102 100644
--- a/src/nix/flake.cc
+++ b/src/nix/flake.cc
@@ -212,7 +212,8 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON
ANSI_BOLD "Last modified:" ANSI_NORMAL " %s",
std::put_time(std::localtime(&*lastModified), "%F %T"));
- logger->cout(ANSI_BOLD "Inputs:" ANSI_NORMAL);
+ if (!lockedFlake.lockFile.root->inputs.empty())
+ logger->cout(ANSI_BOLD "Inputs:" ANSI_NORMAL);
std::unordered_set<std::shared_ptr<Node>> visited;
diff --git a/src/nix/get-env.sh b/src/nix/get-env.sh
index 42c806450..a7a8a01b9 100644
--- a/src/nix/get-env.sh
+++ b/src/nix/get-env.sh
@@ -43,6 +43,7 @@ __dumpEnv() {
local __var_name="${BASH_REMATCH[2]}"
if [[ $__var_name =~ ^BASH_ || \
+ $__var_name =~ ^COMP_ || \
$__var_name = _ || \
$__var_name = DIRSTACK || \
$__var_name = EUID || \
@@ -54,7 +55,9 @@ __dumpEnv() {
$__var_name = PWD || \
$__var_name = RANDOM || \
$__var_name = SHLVL || \
- $__var_name = SECONDS \
+ $__var_name = SECONDS || \
+ $__var_name = EPOCHREALTIME || \
+ $__var_name = EPOCHSECONDS \
]]; then continue; fi
if [[ -z $__first ]]; then printf ',\n'; else __first=; fi
diff --git a/src/nix/main.cc b/src/nix/main.cc
index a8404a2ea..f8e93e367 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -74,6 +74,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({
.longName = "help",
.description = "Show usage information.",
+ .category = miscCategory,
.handler = {[&]() { throw HelpRequested(); }},
});
@@ -82,12 +83,13 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.shortName = 'L',
.description = "Print full build logs on standard error.",
.category = loggingCategory,
- .handler = {[&]() {setLogFormat(LogFormat::barWithLogs); }},
+ .handler = {[&]() { logger->setPrintBuildLogs(true); }},
});
addFlag({
.longName = "version",
.description = "Show version information.",
+ .category = miscCategory,
.handler = {[&]() { showVersion = true; }},
});
@@ -95,12 +97,14 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.longName = "offline",
.aliases = {"no-net"}, // FIXME: remove
.description = "Disable substituters and consider all previously downloaded files up-to-date.",
+ .category = miscCategory,
.handler = {[&]() { useNet = false; }},
});
addFlag({
.longName = "refresh",
.description = "Consider all previously downloaded files out-of-date.",
+ .category = miscCategory,
.handler = {[&]() { refresh = true; }},
});
}
@@ -187,7 +191,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
*vUtils);
auto attrs = state.buildBindings(16);
- attrs.alloc("command").mkString(toplevel.toJSON().dump());
+ attrs.alloc("toplevel").mkString(toplevel.toJSON().dump());
auto vRes = state.allocValue();
state.callFunction(*vGenerateManpage, state.allocValue()->mkAttrs(attrs), *vRes, noPos);
@@ -266,7 +270,7 @@ void mainWrapped(int argc, char * * argv)
programPath = argv[0];
auto programName = std::string(baseNameOf(programPath));
- if (argc > 0 && std::string_view(argv[0]) == "__build-remote") {
+ if (argc > 1 && std::string_view(argv[1]) == "__build-remote") {
programName = "build-remote";
argv++; argc--;
}
@@ -325,7 +329,7 @@ void mainWrapped(int argc, char * * argv)
std::cout << "attrs\n"; break;
}
for (auto & s : *completions)
- std::cout << s.completion << "\t" << s.description << "\n";
+ std::cout << s.completion << "\t" << trim(s.description) << "\n";
}
});
diff --git a/src/nix/make-content-addressed.md b/src/nix/make-content-addressed.md
index 215683e6d..32eecc880 100644
--- a/src/nix/make-content-addressed.md
+++ b/src/nix/make-content-addressed.md
@@ -22,7 +22,7 @@ R""(
```console
# nix copy --to /tmp/nix --trusted-public-keys '' nixpkgs#hello
- cannot add path '/nix/store/zy9wbxwcygrwnh8n2w9qbbcr6zk87m26-libunistring-0.9.10' because it lacks a valid signature
+ cannot add path '/nix/store/zy9wbxwcygrwnh8n2w9qbbcr6zk87m26-libunistring-0.9.10' because it lacks a signature by a trusted key
```
* Create a content-addressed representation of the current NixOS
diff --git a/src/nix/path-from-hash-part.cc b/src/nix/path-from-hash-part.cc
new file mode 100644
index 000000000..7f7cda8d3
--- /dev/null
+++ b/src/nix/path-from-hash-part.cc
@@ -0,0 +1,39 @@
+#include "command.hh"
+#include "store-api.hh"
+
+using namespace nix;
+
+struct CmdPathFromHashPart : StoreCommand
+{
+ std::string hashPart;
+
+ CmdPathFromHashPart()
+ {
+ expectArgs({
+ .label = "hash-part",
+ .handler = {&hashPart},
+ });
+ }
+
+ std::string description() override
+ {
+ return "get a store path from its hash part";
+ }
+
+ std::string doc() override
+ {
+ return
+ #include "path-from-hash-part.md"
+ ;
+ }
+
+ void run(ref<Store> store) override
+ {
+ if (auto storePath = store->queryPathFromHashPart(hashPart))
+ logger->cout(store->printStorePath(*storePath));
+ else
+ throw Error("there is no store path corresponding to '%s'", hashPart);
+ }
+};
+
+static auto rCmdPathFromHashPart = registerCommand2<CmdPathFromHashPart>({"store", "path-from-hash-part"});
diff --git a/src/nix/path-from-hash-part.md b/src/nix/path-from-hash-part.md
new file mode 100644
index 000000000..788e13ab6
--- /dev/null
+++ b/src/nix/path-from-hash-part.md
@@ -0,0 +1,20 @@
+R""(
+
+# Examples
+
+* Return the full store path with the given hash part:
+
+ ```console
+ # nix store path-from-hash-part --store https://cache.nixos.org/ 0i2jd68mp5g6h2sa5k9c85rb80sn8hi9
+ /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10
+ ```
+
+# Description
+
+Given the hash part of a store path (that is, the 32 characters
+following `/nix/store/`), return the full store path. This is
+primarily useful in the implementation of binary caches, where a
+request for a `.narinfo` file only supplies the hash part
+(e.g. `https://cache.nixos.org/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9.narinfo`).
+
+)""
diff --git a/src/nix/profile.md b/src/nix/profile.md
index 8dade051d..be3c5ba1a 100644
--- a/src/nix/profile.md
+++ b/src/nix/profile.md
@@ -11,7 +11,7 @@ them to be rolled back easily.
The default profile used by `nix profile` is `$HOME/.nix-profile`,
which, if it does not exist, is created as a symlink to
-`/nix/var/nix/profiles/per-user/default` if Nix is invoked by the
+`/nix/var/nix/profiles/default` if Nix is invoked by the
`root` user, or `/nix/var/nix/profiles/per-user/`*username* otherwise.
You can specify another profile location using `--profile` *path*.
diff --git a/src/nix/repl.md b/src/nix/repl.md
index 23ef0f4e6..c5113be61 100644
--- a/src/nix/repl.md
+++ b/src/nix/repl.md
@@ -36,7 +36,7 @@ R""(
Loading Installable ''...
Added 1 variables.
- # nix repl --extra_experimental_features 'flakes repl-flake' nixpkgs
+ # nix repl --extra-experimental-features 'flakes repl-flake' nixpkgs
Loading Installable 'flake:nixpkgs#'...
Added 5 variables.
diff --git a/src/nix/shell.md b/src/nix/shell.md
index 90b81fb2f..9fa1031f5 100644
--- a/src/nix/shell.md
+++ b/src/nix/shell.md
@@ -23,6 +23,12 @@ R""(
Hi everybody!
```
+* Run multiple commands in a shell environment:
+
+ ```console
+ # nix shell nixpkgs#gnumake -c sh -c "cd src && make"
+ ```
+
* Run GNU Hello in a chroot store:
```console
diff --git a/src/nix/verify.cc b/src/nix/verify.cc
index e92df1303..efa2434dc 100644
--- a/src/nix/verify.cc
+++ b/src/nix/verify.cc
@@ -41,7 +41,7 @@ struct CmdVerify : StorePathsCommand
addFlag({
.longName = "sigs-needed",
.shortName = 'n',
- .description = "Require that each path has at least *n* valid signatures.",
+ .description = "Require that each path is signed by at least *n* different keys.",
.labels = {"n"},
.handler = {&sigsNeeded}
});
diff --git a/tests/build-dry.sh b/tests/build-dry.sh
index f0f38e9a0..5f29239dc 100644
--- a/tests/build-dry.sh
+++ b/tests/build-dry.sh
@@ -18,9 +18,6 @@ nix-build --no-out-link dependencies.nix --dry-run 2>&1 | grep "will be built"
# Now new command:
nix build -f dependencies.nix --dry-run 2>&1 | grep "will be built"
-# TODO: XXX: FIXME: #1793
-# Disable this part of the test until the problem is resolved:
-if [ -n "$ISSUE_1795_IS_FIXED" ]; then
clearStore
clearCache
@@ -28,7 +25,6 @@ clearCache
nix build -f dependencies.nix --dry-run 2>&1 | grep "will be built"
# Now old command:
nix-build --no-out-link dependencies.nix --dry-run 2>&1 | grep "will be built"
-fi
###################################################
# Check --dry-run doesn't create links with --dry-run
diff --git a/tests/check.sh b/tests/check.sh
index ab48ff865..495202781 100644
--- a/tests/check.sh
+++ b/tests/check.sh
@@ -40,6 +40,14 @@ nix-build check.nix -A deterministic --argstr checkBuildId $checkBuildId \
if grep -q 'may not be deterministic' $TEST_ROOT/log; then false; fi
checkBuildTempDirRemoved $TEST_ROOT/log
+nix build -f check.nix deterministic --rebuild --repeat 1 \
+ --argstr checkBuildId $checkBuildId --keep-failed --no-link \
+ 2> $TEST_ROOT/log
+if grep -q 'checking is not possible' $TEST_ROOT/log; then false; fi
+# Repeat is set to 1, ie. nix should build deterministic twice.
+if [ "$(grep "checking outputs" $TEST_ROOT/log | wc -l)" -ne 2 ]; then false; fi
+checkBuildTempDirRemoved $TEST_ROOT/log
+
nix-build check.nix -A nondeterministic --argstr checkBuildId $checkBuildId \
--no-out-link 2> $TEST_ROOT/log
checkBuildTempDirRemoved $TEST_ROOT/log
@@ -50,6 +58,12 @@ grep 'may not be deterministic' $TEST_ROOT/log
[ "$status" = "104" ]
checkBuildTempDirRemoved $TEST_ROOT/log
+nix build -f check.nix nondeterministic --rebuild --repeat 1 \
+ --argstr checkBuildId $checkBuildId --keep-failed --no-link \
+ 2> $TEST_ROOT/log || status=$?
+grep 'may not be deterministic' $TEST_ROOT/log
+checkBuildTempDirRemoved $TEST_ROOT/log
+
nix-build check.nix -A nondeterministic --argstr checkBuildId $checkBuildId \
--no-out-link --check --keep-failed 2> $TEST_ROOT/log || status=$?
grep 'may not be deterministic' $TEST_ROOT/log
diff --git a/tests/common.sh.in b/tests/common.sh.in
index 79da10199..73c2d2309 100644
--- a/tests/common.sh.in
+++ b/tests/common.sh.in
@@ -193,7 +193,7 @@ fi
onError() {
set +x
echo "$0: test failed at:" >&2
- for ((i = 1; i < 16; i++)); do
+ for ((i = 1; i < ${#BASH_SOURCE[@]}; i++)); do
if [[ -z ${BASH_SOURCE[i]} ]]; then break; fi
echo " ${FUNCNAME[i]} in ${BASH_SOURCE[i]}:${BASH_LINENO[i-1]}" >&2
done
diff --git a/tests/completions.sh b/tests/completions.sh
new file mode 100644
index 000000000..522aa1c86
--- /dev/null
+++ b/tests/completions.sh
@@ -0,0 +1,62 @@
+source common.sh
+
+cd "$TEST_ROOT"
+
+mkdir -p dep
+cat <<EOF > dep/flake.nix
+{
+ outputs = i: { };
+}
+EOF
+mkdir -p foo
+cat <<EOF > foo/flake.nix
+{
+ inputs.a.url = "path:$(realpath dep)";
+
+ outputs = i: {
+ sampleOutput = 1;
+ };
+}
+EOF
+mkdir -p bar
+cat <<EOF > bar/flake.nix
+{
+ inputs.b.url = "path:$(realpath dep)";
+
+ outputs = i: {
+ sampleOutput = 1;
+ };
+}
+EOF
+
+# Test the completion of a subcommand
+[[ "$(NIX_GET_COMPLETIONS=1 nix buil)" == $'normal\nbuild\t' ]]
+[[ "$(NIX_GET_COMPLETIONS=2 nix flake metad)" == $'normal\nmetadata\t' ]]
+
+# Filename completion
+[[ "$(NIX_GET_COMPLETIONS=2 nix build ./f)" == $'filenames\n./foo\t' ]]
+[[ "$(NIX_GET_COMPLETIONS=2 nix build ./nonexistent)" == $'filenames' ]]
+
+# Input override completion
+[[ "$(NIX_GET_COMPLETIONS=4 nix build ./foo --override-input '')" == $'normal\na\t' ]]
+[[ "$(NIX_GET_COMPLETIONS=5 nix flake show ./foo --override-input '')" == $'normal\na\t' ]]
+## With multiple input flakes
+[[ "$(NIX_GET_COMPLETIONS=5 nix build ./foo ./bar --override-input '')" == $'normal\na\t\nb\t' ]]
+## With tilde expansion
+[[ "$(HOME=$PWD NIX_GET_COMPLETIONS=4 nix build '~/foo' --override-input '')" == $'normal\na\t' ]]
+## Out of order
+[[ "$(NIX_GET_COMPLETIONS=3 nix build --update-input '' ./foo)" == $'normal\na\t' ]]
+[[ "$(NIX_GET_COMPLETIONS=4 nix build ./foo --update-input '' ./bar)" == $'normal\na\t\nb\t' ]]
+
+# Cli flag completion
+NIX_GET_COMPLETIONS=2 nix build --log-form | grep -- "--log-format"
+
+# Config option completion
+## With `--option`
+NIX_GET_COMPLETIONS=3 nix build --option allow-import-from | grep -- "allow-import-from-derivation"
+## As a cli flag – not working atm
+# NIX_GET_COMPLETIONS=2 nix build --allow-import-from | grep -- "allow-import-from-derivation"
+
+# Attr path completions
+[[ "$(NIX_GET_COMPLETIONS=2 nix eval ./foo\#sam)" == $'attrs\n./foo#sampleOutput\t' ]]
+[[ "$(NIX_GET_COMPLETIONS=4 nix eval --file ./foo/flake.nix outp)" == $'attrs\noutputs\t' ]]
diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh
index 166bccfc7..4ceba0293 100644
--- a/tests/fetchGit.sh
+++ b/tests/fetchGit.sh
@@ -24,12 +24,14 @@ touch $repo/.gitignore
git -C $repo add hello .gitignore
git -C $repo commit -m 'Bla1'
rev1=$(git -C $repo rev-parse HEAD)
+git -C $repo tag -a tag1 -m tag1
echo world > $repo/hello
git -C $repo commit -m 'Bla2' -a
git -C $repo worktree add $TEST_ROOT/worktree
echo hello >> $TEST_ROOT/worktree/hello
rev2=$(git -C $repo rev-parse HEAD)
+git -C $repo tag -a tag2 -m tag2
# Fetch a worktree
unset _NIX_FORCE_HTTP
@@ -217,6 +219,16 @@ rev4_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$
path9=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; name = \"foo\"; }).outPath")
[[ $path9 =~ -foo$ ]]
+# Specifying a ref without a rev shouldn't pick a cached rev for a different ref
+export _NIX_FORCE_HTTP=1
+rev_tag1_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"refs/tags/tag1\"; }).rev")
+rev_tag1=$(git -C $repo rev-parse refs/tags/tag1)
+[[ $rev_tag1_nix = $rev_tag1 ]]
+rev_tag2_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"refs/tags/tag2\"; }).rev")
+rev_tag2=$(git -C $repo rev-parse refs/tags/tag2)
+[[ $rev_tag2_nix = $rev_tag2 ]]
+unset _NIX_FORCE_HTTP
+
# should fail if there is no repo
rm -rf $repo/.git
(! nix eval --impure --raw --expr "(builtins.fetchGit \"file://$repo\").outPath")
diff --git a/tests/installer/default.nix b/tests/installer/default.nix
new file mode 100644
index 000000000..32aa7889a
--- /dev/null
+++ b/tests/installer/default.nix
@@ -0,0 +1,220 @@
+{ binaryTarballs
+, nixpkgsFor
+}:
+
+let
+
+ installScripts = {
+ install-default = {
+ script = ''
+ tar -xf ./nix.tar.xz
+ mv ./nix-* nix
+ ./nix/install --no-channel-add
+ '';
+ };
+
+ install-force-no-daemon = {
+ script = ''
+ tar -xf ./nix.tar.xz
+ mv ./nix-* nix
+ ./nix/install --no-daemon
+ '';
+ };
+
+ install-force-daemon = {
+ script = ''
+ tar -xf ./nix.tar.xz
+ mv ./nix-* nix
+ ./nix/install --daemon --no-channel-add
+ '';
+ };
+ };
+
+ disableSELinux = "sudo setenforce 0";
+
+ images = {
+
+ /*
+ "ubuntu-14-04" = {
+ image = import <nix/fetchurl.nix> {
+ url = "https://app.vagrantup.com/ubuntu/boxes/trusty64/versions/20190514.0.0/providers/virtualbox.box";
+ hash = "sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8=";
+ };
+ rootDisk = "box-disk1.vmdk";
+ system = "x86_64-linux";
+ };
+ */
+
+ "ubuntu-16-04" = {
+ image = import <nix/fetchurl.nix> {
+ url = "https://app.vagrantup.com/generic/boxes/ubuntu1604/versions/4.1.12/providers/libvirt.box";
+ hash = "sha256-lO4oYQR2tCh5auxAYe6bPOgEqOgv3Y3GC1QM1tEEEU8=";
+ };
+ rootDisk = "box.img";
+ system = "x86_64-linux";
+ };
+
+ "ubuntu-22-04" = {
+ image = import <nix/fetchurl.nix> {
+ url = "https://app.vagrantup.com/generic/boxes/ubuntu2204/versions/4.1.12/providers/libvirt.box";
+ hash = "sha256-HNll0Qikw/xGIcogni5lz01vUv+R3o8xowP2EtqjuUQ=";
+ };
+ rootDisk = "box.img";
+ system = "x86_64-linux";
+ };
+
+ "fedora-36" = {
+ image = import <nix/fetchurl.nix> {
+ url = "https://app.vagrantup.com/generic/boxes/fedora36/versions/4.1.12/providers/libvirt.box";
+ hash = "sha256-rxPgnDnFkTDwvdqn2CV3ZUo3re9AdPtSZ9SvOHNvaks=";
+ };
+ rootDisk = "box.img";
+ system = "x86_64-linux";
+ postBoot = disableSELinux;
+ };
+
+ # Currently fails with 'error while loading shared libraries:
+ # libsodium.so.23: cannot stat shared object: Invalid argument'.
+ /*
+ "rhel-6" = {
+ image = import <nix/fetchurl.nix> {
+ url = "https://app.vagrantup.com/generic/boxes/rhel6/versions/4.1.12/providers/libvirt.box";
+ hash = "sha256-QwzbvRoRRGqUCQptM7X/InRWFSP2sqwRt2HaaO6zBGM=";
+ };
+ rootDisk = "box.img";
+ system = "x86_64-linux";
+ };
+ */
+
+ "rhel-7" = {
+ image = import <nix/fetchurl.nix> {
+ url = "https://app.vagrantup.com/generic/boxes/rhel7/versions/4.1.12/providers/libvirt.box";
+ hash = "sha256-b4afnqKCO9oWXgYHb9DeQ2berSwOjS27rSd9TxXDc/U=";
+ };
+ rootDisk = "box.img";
+ system = "x86_64-linux";
+ };
+
+ "rhel-8" = {
+ image = import <nix/fetchurl.nix> {
+ url = "https://app.vagrantup.com/generic/boxes/rhel8/versions/4.1.12/providers/libvirt.box";
+ hash = "sha256-zFOPjSputy1dPgrQRixBXmlyN88cAKjJ21VvjSWUCUY=";
+ };
+ rootDisk = "box.img";
+ system = "x86_64-linux";
+ postBoot = disableSELinux;
+ };
+
+ "rhel-9" = {
+ image = import <nix/fetchurl.nix> {
+ url = "https://app.vagrantup.com/generic/boxes/rhel9/versions/4.1.12/providers/libvirt.box";
+ hash = "sha256-vL/FbB3kK1rcSaR627nWmScYGKGk4seSmAdq6N5diMg=";
+ };
+ rootDisk = "box.img";
+ system = "x86_64-linux";
+ postBoot = disableSELinux;
+ extraQemuOpts = "-cpu Westmere-v2";
+ };
+
+ };
+
+ makeTest = imageName: testName:
+ let image = images.${imageName}; in
+ with nixpkgsFor.${image.system};
+ runCommand
+ "installer-test-${imageName}-${testName}"
+ { buildInputs = [ qemu_kvm openssh ];
+ image = image.image;
+ postBoot = image.postBoot or "";
+ installScript = installScripts.${testName}.script;
+ binaryTarball = binaryTarballs.${system};
+ }
+ ''
+ shopt -s nullglob
+
+ echo "Unpacking Vagrant box $image..."
+ tar xvf $image
+
+ image_type=$(qemu-img info ${image.rootDisk} | sed 's/file format: \(.*\)/\1/; t; d')
+
+ qemu-img create -b ./${image.rootDisk} -F "$image_type" -f qcow2 ./disk.qcow2
+
+ extra_qemu_opts="${image.extraQemuOpts or ""}"
+
+ # Add the config disk, required by the Ubuntu images.
+ config_drive=$(echo *configdrive.vmdk || true)
+ if [[ -n $config_drive ]]; then
+ extra_qemu_opts+=" -drive id=disk2,file=$config_drive,if=virtio"
+ fi
+
+ echo "Starting qemu..."
+ qemu-kvm -m 4096 -nographic \
+ -drive id=disk1,file=./disk.qcow2,if=virtio \
+ -netdev user,id=net0,restrict=yes,hostfwd=tcp::20022-:22 -device virtio-net-pci,netdev=net0 \
+ $extra_qemu_opts &
+ qemu_pid=$!
+ trap "kill $qemu_pid" EXIT
+
+ if ! [ -e ./vagrant_insecure_key ]; then
+ cp ${./vagrant_insecure_key} vagrant_insecure_key
+ fi
+
+ chmod 0400 ./vagrant_insecure_key
+
+ ssh_opts="-o StrictHostKeyChecking=no -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedKeyTypes=+ssh-rsa -i ./vagrant_insecure_key"
+ ssh="ssh -p 20022 -q $ssh_opts vagrant@localhost"
+
+ echo "Waiting for SSH..."
+ for ((i = 0; i < 120; i++)); do
+ echo "[ssh] Trying to connect..."
+ if $ssh -- true; then
+ echo "[ssh] Connected!"
+ break
+ fi
+ if ! kill -0 $qemu_pid; then
+ echo "qemu died unexpectedly"
+ exit 1
+ fi
+ sleep 1
+ done
+
+ if [[ -n $postBoot ]]; then
+ echo "Running post-boot commands..."
+ $ssh "set -ex; $postBoot"
+ fi
+
+ echo "Copying installer..."
+ scp -P 20022 $ssh_opts $binaryTarball/nix-*.tar.xz vagrant@localhost:nix.tar.xz
+
+ echo "Running installer..."
+ $ssh "set -eux; $installScript"
+
+ echo "Testing Nix installation..."
+ $ssh <<EOF
+ set -ex
+
+ # FIXME: get rid of this; ideally ssh should just work.
+ source ~/.bash_profile || true
+ source ~/.bash_login || true
+ source ~/.profile || true
+ source /etc/bashrc || true
+
+ nix-env --version
+ nix --extra-experimental-features nix-command store ping
+
+ out=\$(nix-build --no-substitute -E 'derivation { name = "foo"; system = "x86_64-linux"; builder = "/bin/sh"; args = ["-c" "echo foobar > \$out"]; }')
+ [[ \$(cat \$out) = foobar ]]
+ EOF
+
+ echo "Done!"
+ touch $out
+ '';
+
+in
+
+builtins.mapAttrs (imageName: image:
+ { ${image.system} = builtins.mapAttrs (testName: test:
+ makeTest imageName testName
+ ) installScripts;
+ }
+) images
diff --git a/tests/installer/vagrant_insecure_key b/tests/installer/vagrant_insecure_key
new file mode 100644
index 000000000..7d6a08390
--- /dev/null
+++ b/tests/installer/vagrant_insecure_key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEogIBAAKCAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzI
+w+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoP
+kcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2
+hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NO
+Td0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcW
+yLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQIBIwKCAQEA4iqWPJXtzZA68mKd
+ELs4jJsdyky+ewdZeNds5tjcnHU5zUYE25K+ffJED9qUWICcLZDc81TGWjHyAqD1
+Bw7XpgUwFgeUJwUlzQurAv+/ySnxiwuaGJfhFM1CaQHzfXphgVml+fZUvnJUTvzf
+TK2Lg6EdbUE9TarUlBf/xPfuEhMSlIE5keb/Zz3/LUlRg8yDqz5w+QWVJ4utnKnK
+iqwZN0mwpwU7YSyJhlT4YV1F3n4YjLswM5wJs2oqm0jssQu/BT0tyEXNDYBLEF4A
+sClaWuSJ2kjq7KhrrYXzagqhnSei9ODYFShJu8UWVec3Ihb5ZXlzO6vdNQ1J9Xsf
+4m+2ywKBgQD6qFxx/Rv9CNN96l/4rb14HKirC2o/orApiHmHDsURs5rUKDx0f9iP
+cXN7S1uePXuJRK/5hsubaOCx3Owd2u9gD6Oq0CsMkE4CUSiJcYrMANtx54cGH7Rk
+EjFZxK8xAv1ldELEyxrFqkbE4BKd8QOt414qjvTGyAK+OLD3M2QdCQKBgQDtx8pN
+CAxR7yhHbIWT1AH66+XWN8bXq7l3RO/ukeaci98JfkbkxURZhtxV/HHuvUhnPLdX
+3TwygPBYZFNo4pzVEhzWoTtnEtrFueKxyc3+LjZpuo+mBlQ6ORtfgkr9gBVphXZG
+YEzkCD3lVdl8L4cw9BVpKrJCs1c5taGjDgdInQKBgHm/fVvv96bJxc9x1tffXAcj
+3OVdUN0UgXNCSaf/3A/phbeBQe9xS+3mpc4r6qvx+iy69mNBeNZ0xOitIjpjBo2+
+dBEjSBwLk5q5tJqHmy/jKMJL4n9ROlx93XS+njxgibTvU6Fp9w+NOFD/HvxB3Tcz
+6+jJF85D5BNAG3DBMKBjAoGBAOAxZvgsKN+JuENXsST7F89Tck2iTcQIT8g5rwWC
+P9Vt74yboe2kDT531w8+egz7nAmRBKNM751U/95P9t88EDacDI/Z2OwnuFQHCPDF
+llYOUI+SpLJ6/vURRbHSnnn8a/XG+nzedGH5JGqEJNQsz+xT2axM0/W/CRknmGaJ
+kda/AoGANWrLCz708y7VYgAtW2Uf1DPOIYMdvo6fxIB5i9ZfISgcJ/bbCUkFrhoH
++vq/5CIWxCPp0f85R4qxxQ5ihxJ0YDQT9Jpx4TMss4PSavPaBH3RXow5Ohe+bYoQ
+NE5OgEXk2wVfZczCZpigBKbKZHNYcelXtTt/nP3rsCuGcM4h53s=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/lang/eval-okay-eq.exp b/tests/lang/eval-okay-eq.exp
new file mode 100644
index 000000000..27ba77dda
--- /dev/null
+++ b/tests/lang/eval-okay-eq.exp
@@ -0,0 +1 @@
+true
diff --git a/tests/lang/eval-okay-eq.exp.disabled b/tests/lang/eval-okay-eq.exp.disabled
deleted file mode 100644
index 2015847b6..000000000
--- a/tests/lang/eval-okay-eq.exp.disabled
+++ /dev/null
@@ -1 +0,0 @@
-Bool(True)
diff --git a/tests/lang/eval-okay-versions.nix b/tests/lang/eval-okay-versions.nix
index e63c36586..e9111f5f4 100644
--- a/tests/lang/eval-okay-versions.nix
+++ b/tests/lang/eval-okay-versions.nix
@@ -4,6 +4,7 @@ let
name2 = "hello";
name3 = "915resolution-0.5.2";
name4 = "xf86-video-i810-1.7.4";
+ name5 = "name-that-ends-with-dash--1.0";
eq = 0;
lt = builtins.sub 0 1;
@@ -23,6 +24,8 @@ let
((builtins.parseDrvName name3).version == "0.5.2")
((builtins.parseDrvName name4).name == "xf86-video-i810")
((builtins.parseDrvName name4).version == "1.7.4")
+ ((builtins.parseDrvName name5).name == "name-that-ends-with-dash")
+ ((builtins.parseDrvName name5).version == "-1.0")
(versionTest "1.0" "2.3" lt)
(versionTest "2.1" "2.3" lt)
(versionTest "2.3" "2.3" eq)
diff --git a/tests/local.mk b/tests/local.mk
index 9734df08b..aff595d3b 100644
--- a/tests/local.mk
+++ b/tests/local.mk
@@ -109,7 +109,9 @@ nix_tests = \
suggestions.sh \
store-ping.sh \
fetchClosure.sh \
- impure-derivations.sh
+ completions.sh \
+ impure-derivations.sh \
+ path-from-hash-part.sh
ifeq ($(HAVE_LIBCPUID), 1)
nix_tests += compute-levels.sh
diff --git a/tests/path-from-hash-part.sh b/tests/path-from-hash-part.sh
new file mode 100644
index 000000000..bdd104434
--- /dev/null
+++ b/tests/path-from-hash-part.sh
@@ -0,0 +1,10 @@
+source common.sh
+
+path=$(nix build --no-link --print-out-paths -f simple.nix)
+
+hash_part=$(basename $path)
+hash_part=${hash_part:0:32}
+
+path2=$(nix store path-from-hash-part $hash_part)
+
+[[ $path = $path2 ]]
diff --git a/tests/signing.sh b/tests/signing.sh
index 6aafbeb91..9b673c609 100644
--- a/tests/signing.sh
+++ b/tests/signing.sh
@@ -81,7 +81,7 @@ info=$(nix path-info --store file://$cacheDir --json $outPath2)
[[ $info =~ 'cache1.example.org' ]]
[[ $info =~ 'cache2.example.org' ]]
-# Copying to a diverted store should fail due to a lack of valid signatures.
+# Copying to a diverted store should fail due to a lack of signatures by trusted keys.
chmod -R u+w $TEST_ROOT/store0 || true
rm -rf $TEST_ROOT/store0
(! nix copy --to $TEST_ROOT/store0 $outPath)