diff options
author | John Ericson <John.Ericson@Obsidian.Systems> | 2023-02-28 12:46:00 -0500 |
---|---|---|
committer | John Ericson <John.Ericson@Obsidian.Systems> | 2023-02-28 12:46:00 -0500 |
commit | 5abd643c6d10f2cfa6e26652a9688a0263310094 (patch) | |
tree | 2fdb8bf147cb93430ba3ba79a473568e2584e497 | |
parent | e68e8e3cee53ce7debd7c54b0d122d94d1b102a2 (diff) | |
parent | d381248ec0847cacd918480e83a99287f814456a (diff) |
Merge branch 'path-info' into ca-drv-exotic
199 files changed, 3856 insertions, 1523 deletions
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d58577551..ab5908649 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -5,7 +5,7 @@ # For documentation on this mechanism, see https://help.github.com/articles/about-codeowners/ # Default reviewers if nothing else matches -* @edolstra @thufschmitt +* @edolstra # This file .github/CODEOWNERS @edolstra @@ -13,3 +13,6 @@ # Public documentation /doc @fricklerhandwerk *.md @fricklerhandwerk + +# Libstore layer +/src/libstore @thufschmitt diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a268d7caf..db69e51db 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -21,8 +21,8 @@ Maintainers: tick if completed or explain if not relevant - [ ] tests, as appropriate - functional tests - `tests/**.sh` - unit tests - `src/*/tests` - - integration tests + - integration tests - `tests/nixos/*` - [ ] documentation in the manual - [ ] code and comments are self-explanatory - [ ] commit message explains why the change was made - - [ ] new feature or bug fix: updated release notes + - [ ] new feature or incompatible change: updated release notes diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 9f8d14509..b04723b95 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -21,12 +21,12 @@ jobs: fetch-depth: 0 - name: Create backport PRs # should be kept in sync with `version` - uses: zeebe-io/backport-action@v1.0.1 + uses: zeebe-io/backport-action@v1.2.0 with: # Config README: https://github.com/zeebe-io/backport-action#backport-action github_token: ${{ secrets.GITHUB_TOKEN }} github_workspace: ${{ github.workspace }} pull_description: |- - Bot-based backport to `${target_branch}`, triggered by a label in #${pull_number}. + Automatic backport to `${target_branch}`, triggered by a label in #${pull_number}. # should be kept in sync with `uses` version: v0.0.5 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dafba6d85..325579a5b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: cachix/install-nix-action@v18 + - uses: cachix/install-nix-action@v19 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - uses: cachix/cachix-action@v12 if: needs.check_secrets.outputs.cachix == 'true' @@ -58,7 +58,7 @@ 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@v18 + - uses: cachix/install-nix-action@v19 - uses: cachix/cachix-action@v12 with: name: '${{ env.CACHIX_NAME }}' @@ -77,7 +77,7 @@ 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@v18 + - uses: cachix/install-nix-action@v19 with: install_url: '${{needs.installer.outputs.installerURL}}' install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve" @@ -102,7 +102,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: cachix/install-nix-action@v18 + - uses: cachix/install-nix-action@v19 - 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@v12 diff --git a/.gitignore b/.gitignore index 8e0db013f..e326966d6 100644 --- a/.gitignore +++ b/.gitignore @@ -37,14 +37,14 @@ perl/Makefile.config /src/libexpr/parser-tab.hh /src/libexpr/parser-tab.output /src/libexpr/nix.tbl -/src/libexpr/tests/libexpr-tests +/src/libexpr/tests/libnixexpr-tests # /src/libstore/ *.gen.* -/src/libstore/tests/libstore-tests +/src/libstore/tests/libnixstore-tests # /src/libutil/ -/src/libutil/tests/libutil-tests +/src/libutil/tests/libnixutil-tests /src/nix/nix @@ -75,7 +75,7 @@ perl/Makefile.config # /tests/ /tests/test-tmp -/tests/common.sh +/tests/common/vars-and-functions.sh /tests/result* /tests/restricted-innocent /tests/shell @@ -103,6 +103,7 @@ outputs/ *.a *.o +*.o.tmp *.so *.dylib *.dll @@ -36,4 +36,4 @@ endif include mk/lib.mk -GLOBAL_CXXFLAGS += -g -Wall -include config.h -std=c++17 -I src +GLOBAL_CXXFLAGS += -g -Wall -include config.h -std=c++2a -I src @@ -20,8 +20,8 @@ Information on additional installation methods is available on the [Nix download ## Building And Developing -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. +See our [Hacking guide](https://nixos.org/manual/nix/unstable/contributing/hacking.html) in our manual for instruction on how to +to set up a development environment and build Nix from source. ## Additional Resources diff --git a/boehmgc-coroutine-sp-fallback.diff b/boehmgc-coroutine-sp-fallback.diff new file mode 100644 index 000000000..2826486fb --- /dev/null +++ b/boehmgc-coroutine-sp-fallback.diff @@ -0,0 +1,77 @@ +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 b5d71e62..aed7b0bf 100644 +--- a/pthread_stop_world.c ++++ b/pthread_stop_world.c +@@ -768,6 +768,8 @@ STATIC void GC_restart_handler(int sig) + /* world is stopped. Should not fail if it isn't. */ + GC_INNER void GC_push_all_stacks(void) + { ++ size_t stack_limit; ++ pthread_attr_t pattr; + GC_bool found_me = FALSE; + size_t nthreads = 0; + int i; +@@ -851,6 +853,31 @@ GC_INNER void GC_push_all_stacks(void) + hi = p->altstack + p->altstack_size; + /* FIXME: Need to scan the normal stack too, but how ? */ + /* FIXME: Assume stack grows down */ ++ } else { ++ if (pthread_getattr_np(p->id, &pattr)) { ++ ABORT("GC_push_all_stacks: pthread_getattr_np failed!"); ++ } ++ if (pthread_attr_getstacksize(&pattr, &stack_limit)) { ++ ABORT("GC_push_all_stacks: pthread_attr_getstacksize failed!"); ++ } ++ if (pthread_attr_destroy(&pattr)) { ++ ABORT("GC_push_all_stacks: pthread_attr_destroy failed!"); ++ } ++ // 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. ++ #ifndef STACK_GROWS_UP ++ if (lo >= hi || lo < hi - stack_limit) { // sp outside stack ++ lo = hi - stack_limit; ++ } ++ #else ++ #error "STACK_GROWS_UP not supported in boost_coroutine2 (as of june 2021), so we don't support it in Nix." ++ #endif + } + GC_push_all_stack_sections(lo, hi, traced_stack_sect); + # ifdef STACK_GROWS_UP diff --git a/configure.ac b/configure.ac index 0066bc389..09b3651b9 100644 --- a/configure.ac +++ b/configure.ac @@ -276,8 +276,11 @@ PKG_CHECK_MODULES([GTEST], [gtest_main]) # Look for rapidcheck. # No pkg-config yet, https://github.com/emil-e/rapidcheck/issues/302 +AC_LANG_PUSH(C++) AC_CHECK_HEADERS([rapidcheck/gtest.h], [], [], [#include <gtest/gtest.h>]) -AC_CHECK_LIB([rapidcheck], []) +dnl No good for C++ libs with mangled symbols +dnl AC_CHECK_LIB([rapidcheck], []) +AC_LANG_POP(C++) # Look for nlohmann/json. diff --git a/doc/manual/custom.css b/doc/manual/custom.css index 69d48d4a7..b90f5423f 100644 --- a/doc/manual/custom.css +++ b/doc/manual/custom.css @@ -5,3 +5,7 @@ h1:not(:first-of-type) { h2 { margin-top: 1em; } + +.hljs-meta { + user-select: none; +} diff --git a/doc/manual/local.mk b/doc/manual/local.mk index 190f0258a..f43510b6d 100644 --- a/doc/manual/local.mk +++ b/doc/manual/local.mk @@ -51,13 +51,13 @@ $(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 $@.tmp --expr 'import doc/manual/generate-manpage.nix { toplevel = builtins.readFile $<; }' - # @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable + @# @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable $(trace-gen) sed -i $@.tmp/*.md -e 's^@docroot@^../..^g' @mv $@.tmp $@ $(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 - # @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable + @# @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable $(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-options.nix (builtins.fromJSON (builtins.readFile $<))' \ | sed -e 's^@docroot@^..^g'>> $@.tmp @mv $@.tmp $@ @@ -72,7 +72,7 @@ $(d)/conf-file.json: $(bindir)/nix $(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 - # @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable + @# @docroot@: https://nixos.org/manual/nix/unstable/contributing/hacking.html#docroot-variable $(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' \ | sed -e 's^@docroot@^..^g' >> $@.tmp @cat doc/manual/src/language/builtins-suffix.md >> $@.tmp diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in index b1c551969..964091285 100644 --- a/doc/manual/src/SUMMARY.md.in +++ b/doc/manual/src/SUMMARY.md.in @@ -67,6 +67,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.14 (2023-02-28)](release-notes/rl-2.14.md) - [Release 2.13 (2023-01-17)](release-notes/rl-2.13.md) - [Release 2.12 (2022-12-06)](release-notes/rl-2.12.md) - [Release 2.11 (2022-08-25)](release-notes/rl-2.11.md) diff --git a/doc/manual/src/command-ref/env-common.md b/doc/manual/src/command-ref/env-common.md index bb85a6b07..c5d38db47 100644 --- a/doc/manual/src/command-ref/env-common.md +++ b/doc/manual/src/command-ref/env-common.md @@ -91,3 +91,16 @@ Most Nix commands interpret the following environment variables: variable sets the initial size of the heap in bytes. It defaults to 384 MiB. Setting it to a low value reduces memory consumption, but will increase runtime due to the overhead of garbage collection. + +## XDG Base Directory + +New Nix commands conform to the [XDG Base Directory Specification], and use the following environment variables to determine locations of various state and configuration files: + +- [`XDG_CONFIG_HOME`]{#env-XDG_CONFIG_HOME} (default `~/.config`) +- [`XDG_STATE_HOME`]{#env-XDG_STATE_HOME} (default `~/.local/state`) +- [`XDG_CACHE_HOME`]{#env-XDG_CACHE_HOME} (default `~/.cache`) + +Classic Nix commands can also be made to follow this standard using the [`use-xdg-base-directories`] configuration option. + +[XDG Base Directory Specification]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html +[`use-xdg-base-directories`]: ../command-ref/conf-file.md#conf-use-xdg-base-directories
\ No newline at end of file diff --git a/doc/manual/src/command-ref/nix-store.md b/doc/manual/src/command-ref/nix-store.md index f6017481c..31fdd7806 100644 --- a/doc/manual/src/command-ref/nix-store.md +++ b/doc/manual/src/command-ref/nix-store.md @@ -82,8 +82,8 @@ paths. Realisation is a somewhat overloaded term: produced through substitutes. If there are no (successful) substitutes, realisation fails. -[valid]: ../glossary.md#validity -[substitutes]: ../glossary.md#substitute +[valid]: ../glossary.md#gloss-validity +[substitutes]: ../glossary.md#gloss-substitute The output path of each derivation is printed on standard output. (For non-derivations argument, the argument itself is printed.) @@ -633,7 +633,7 @@ written to standard output. A NAR archive is like a TAR or Zip archive, but it contains only the information that Nix considers important. For instance, timestamps are -elided because all files in the Nix store have their timestamp set to 0 +elided because all files in the Nix store have their timestamp set to 1 anyway. Likewise, all permissions are left out except for the execute bit, because all files in the Nix store have 444 or 555 permission. diff --git a/doc/manual/src/contributing/cli-guideline.md b/doc/manual/src/contributing/cli-guideline.md index 01a1b1e73..e53d2d178 100644 --- a/doc/manual/src/contributing/cli-guideline.md +++ b/doc/manual/src/contributing/cli-guideline.md @@ -389,6 +389,88 @@ colors, no emojis and using ASCII instead of Unicode symbols). The same should happen when TTY is not detected on STDERR. We should not display progress / status section, but only print warnings and errors. +## Returning future proof JSON + +The schema of JSON output should allow for backwards compatible extension. This section explains how to achieve this. + +Two definitions are helpful here, because while JSON only defines one "key-value" +object type, we use it to cover two use cases: + + - **dictionary**: a map from names to value that all have the same type. In + C++ this would be a `std::map` with string keys. + - **record**: a fixed set of attributes each with their own type. In C++, this + would be represented by a `struct`. + +It is best not to mix these use cases, as that may lead to incompatibilities when the schema changes. For example, adding a record field to a dictionary breaks consumers that assume all JSON object fields to have the same meaning and type. + +This leads to the following guidelines: + + - The top-level (root) value must be a record. + + Otherwise, one can not change the structure of a command's output. + + - The value of a dictionary item must be a record. + + Otherwise, the item type can not be extended. + + - List items should be records. + + Otherwise, one can not change the structure of the list items. + + If the order of the items does not matter, and each item has a unique key that is a string, consider representing the list as a dictionary instead. If the order of the items needs to be preserved, return a list of records. + + - Streaming JSON should return records. + + An example of a streaming JSON format is [JSON lines](https://jsonlines.org/), where each line represents a JSON value. These JSON values can be considered top-level values or list items, and they must be records. + +### Examples + + +This is bad, because all keys must be assumed to be store implementations: + +```json +{ + "local": { ... }, + "remote": { ... }, + "http": { ... } +} +``` + +This is good, because the it is extensible at the root, and is somewhat self-documenting: + +```json +{ + "storeTypes": { "local": { ... }, ... }, + "pluginSupport": true +} +``` + +While the dictionary of store types seems like a very complete response at first, a use case may arise that warrants returning additional information. +For example, the presence of plugin support may be crucial information for a client to proceed when their desired store type is missing. + + + +The following representation is bad because it is not extensible: + +```json +{ "outputs": [ "out" "bin" ] } +``` + +However, simply converting everything to records is not enough, because the order of outputs must be preserved: + +```json +{ "outputs": { "bin": {}, "out": {} } } +``` + +The first item is the default output. Deriving this information from the outputs ordering is not great, but this is how Nix currently happens to work. +While it is possible for a JSON parser to preserve the order of fields, we can not rely on this capability to be present in all JSON libraries. + +This representation is extensible and preserves the ordering: + +```json +{ "outputs": [ { "outputName": "out" }, { "outputName": "bin" } ] } +``` + ## Dialog with the user CLIs don't always make it clear when an action has taken place. For every diff --git a/doc/manual/src/contributing/hacking.md b/doc/manual/src/contributing/hacking.md index 9dbafcc0a..3869c37a4 100644 --- a/doc/manual/src/contributing/hacking.md +++ b/doc/manual/src/contributing/hacking.md @@ -8,54 +8,82 @@ $ git clone https://github.com/NixOS/nix.git $ cd nix ``` -To build Nix for the current operating system/architecture use +The following instructions assume you already have some version of Nix installed locally, so that you can use it to set up the development environment. If you don't have it installed, follow the [installation instructions]. + +[installation instructions]: ../installation/installation.md + +## Nix with flakes + +This section assumes you are using Nix with [flakes] enabled. See the [next section](#classic-nix) for equivalent instructions which don't require flakes. + +[flakes]: ../command-ref/new-cli/nix3-flake.md#description + +To build all dependencies and start a shell in which all environment +variables are set up so that those dependencies can be found: ```console -$ nix-build +$ nix develop ``` -or if you have a flake-enabled nix: +This shell also adds `./outputs/bin/nix` to your `$PATH` so you can run `nix` immediately after building it. + +To get a shell with one of the other [supported compilation environments](#compilation-environments): ```console -$ nix build +$ nix develop .#native-clang11StdenvPackages ``` -This will build `defaultPackage` attribute defined in the `flake.nix` -file. To build for other platforms add one of the following suffixes to -it: aarch64-linux, i686-linux, x86\_64-darwin, x86\_64-linux. i.e. +> **Note** +> +> Use `ccacheStdenv` to drastically improve rebuild time. +> By default, [ccache](https://ccache.dev) keeps artifacts in `~/.cache/ccache/`. + +To build Nix itself in this shell: ```console -$ nix-build -A defaultPackage.x86_64-linux +[nix-shell]$ ./bootstrap.sh +[nix-shell]$ ./configure $configureFlags --prefix=$(pwd)/outputs/out +[nix-shell]$ make -j $NIX_BUILD_CORES ``` -To build all dependencies and start a shell in which all environment -variables are set up so that those dependencies can be found: +To install it in `$(pwd)/outputs` and test it: ```console -$ nix-shell +[nix-shell]$ make install +[nix-shell]$ make installcheck -j $NIX_BUILD_CORES +[nix-shell]$ nix --version +nix (Nix) 2.12 ``` -or if you have a flake-enabled nix: +To build a release version of Nix: ```console -$ nix develop +$ nix build ``` -To get a shell with a different compilation environment (e.g. stdenv, -gccStdenv, clangStdenv, clang11Stdenv, ccacheStdenv): +You can also build Nix for one of the [supported target platforms](#target-platforms). + +## Classic Nix + +This section is for Nix without [flakes]. + +To build all dependencies and start a shell in which all environment +variables are set up so that those dependencies can be found: ```console -$ nix-shell -A devShells.x86_64-linux.clang11StdenvPackages +$ nix-shell ``` -or if you have a flake-enabled nix: +To get a shell with one of the other [supported compilation environments](#compilation-environments): ```console -$ nix develop .#clang11StdenvPackages +$ nix-shell -A devShells.x86_64-linux.native-clang11StdenvPackages ``` -Note: you can use `ccacheStdenv` to drastically improve rebuild -time. By default, ccache keeps artifacts in `~/.cache/ccache/`. +> **Note** +> +> You can use `native-ccacheStdenvPackages` to drastically improve rebuild time. +> By default, [ccache](https://ccache.dev) keeps artifacts in `~/.cache/ccache/`. To build Nix itself in this shell: @@ -71,21 +99,99 @@ To install it in `$(pwd)/outputs` and test it: [nix-shell]$ make install [nix-shell]$ make installcheck -j $NIX_BUILD_CORES [nix-shell]$ ./outputs/out/bin/nix --version -nix (Nix) 3.0 +nix (Nix) 2.12 ``` -If you have a flakes-enabled Nix you can replace: +To build Nix for the current operating system and CPU architecture use ```console -$ nix-shell +$ nix-build +``` + +You can also build Nix for one of the [supported target platforms](#target-platforms). + +## Platforms + +As specified in [`flake.nix`], Nix can be built for various platforms: + +- `aarch64-linux` +- `i686-linux` +- `x86_64-darwin` +- `x86_64-linux` + +[`flake.nix`]: https://github.com/nixos/nix/blob/master/flake.nix + +In order to build Nix for a different platform than the one you're currently +on, you need to have some way for your system Nix to build code for that +platform. Common solutions include [remote builders] and [binfmt emulation] +(only supported on NixOS). + +[remote builders]: ../advanced-topics/distributed-builds.md +[binfmt emulation]: https://nixos.org/manual/nixos/stable/options.html#opt-boot.binfmt.emulatedSystems + +These solutions let Nix perform builds as if you're on the native platform, so +executing the build is as simple as + +```console +$ nix build .#packages.aarch64-linux.default ``` -by: +for flake-enabled Nix, or ```console -$ nix develop +$ nix-build -A packages.aarch64-linux.default +``` + +for classic Nix. + +You can use any of the other supported platforms in place of `aarch64-linux`. + +Cross-compiled builds are available for ARMv6 and ARMv7, and Nix on unsupported platforms can be bootstrapped by adding more `crossSystems` in `flake.nix`. + +## Compilation environments + +Nix can be compiled using multiple environments: + +- `stdenv`: default; +- `gccStdenv`: force the use of `gcc` compiler; +- `clangStdenv`: force the use of `clang` compiler; +- `ccacheStdenv`: enable [ccache], a compiler cache to speed up compilation. + +To build with one of those environments, you can use + +```console +$ nix build .#nix-ccacheStdenv ``` +for flake-enabled Nix, or + +```console +$ nix-build -A nix-ccacheStdenv +``` + +for classic Nix. + +You can use any of the other supported environments in place of `nix-ccacheStdenv`. + +## Editor integration + +The `clangd` LSP server is installed by default on the `clang`-based `devShell`s. +See [supported compilation environments](#compilation-environments) and instructions how to set up a shell [with flakes](#nix-with-flakes) or in [classic Nix](#classic-nix). + +To use the LSP with your editor, you first need to [set up `clangd`](https://clangd.llvm.org/installation#project-setup) by running: + +```console +make clean && bear -- make -j$NIX_BUILD_CORES install +``` + +Configure your editor to use the `clangd` from the shell, either by running it inside the development shell, or by using [nix-direnv](https://github.com/nix-community/nix-direnv) and [the appropriate editor plugin](https://github.com/direnv/direnv/wiki#editor-integration). + +> **Note** +> +> For some editors (e.g. Visual Studio Code), you may need to install a [special extension](https://open-vsx.org/extension/llvm-vs-code-extensions/vscode-clangd) for the editor to interact with `clangd`. +> Some other editors (e.g. Emacs, Vim) need a plugin to support LSP servers in general (e.g. [lsp-mode](https://github.com/emacs-lsp/lsp-mode) for Emacs and [vim-lsp](https://github.com/prabirshrestha/vim-lsp) for vim). +> Editor-specific setup is typically opinionated, so we will not cover it here in more detail. + ## Running tests ### Unit-tests @@ -219,7 +325,7 @@ After the CI run completes, you can check the output to extract the installer UR 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 + curl -L <install_url> | sh -s -- --tarball-url-prefix https://<github-username>-nix-install-tests.cachix.org/serve ``` <!-- #### Manually generating test installers diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index 5a49af8dc..d0aff34e2 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -19,6 +19,13 @@ [store derivation]: #gloss-store-derivation + - [instantiate]{#gloss-instantiate}, instantiation\ + Translate a [derivation] into a [store derivation]. + + See [`nix-instantiate`](./command-ref/nix-instantiate.md). + + [instantiate]: #gloss-instantiate + - [realise]{#gloss-realise}, realisation\ Ensure a [store path] is [valid][validity]. @@ -156,6 +163,8 @@ to path `Q`, then `Q` is in the closure of `P`. Further, if `Q` references `R` then `R` is also in the closure of `P`. + [closure]: #gloss-closure + - [output path]{#gloss-output-path}\ A [store path] produced by a [derivation]. @@ -172,6 +181,8 @@ - The store path is listed in the Nix database as being valid. - All paths in the store path's [closure] are valid. + [validity]: #gloss-validity + - [user environment]{#gloss-user-env}\ An automatically generated store object that consists of a set of symlinks to “active” applications, i.e., other store paths. These diff --git a/doc/manual/src/installation/env-variables.md b/doc/manual/src/installation/env-variables.md index bb35c0e9f..fb8155a80 100644 --- a/doc/manual/src/installation/env-variables.md +++ b/doc/manual/src/installation/env-variables.md @@ -27,7 +27,7 @@ Set the environment variable and install Nix ```console $ export NIX_SSL_CERT_FILE=/etc/ssl/my-certificate-bundle.crt -$ sh <(curl -L https://nixos.org/nix/install) +$ curl -L https://nixos.org/nix/install | sh ``` In the shell profile and rc files (for example, `/etc/bashrc`, @@ -38,7 +38,7 @@ export NIX_SSL_CERT_FILE=/etc/ssl/my-certificate-bundle.crt ``` > **Note** -> +> > You must not add the export and then do the install, as the Nix > installer will detect the presence of Nix configuration, and abort. diff --git a/doc/manual/src/installation/installation.md b/doc/manual/src/installation/installation.md index b40c5b95f..dafdeb667 100644 --- a/doc/manual/src/installation/installation.md +++ b/doc/manual/src/installation/installation.md @@ -1,2 +1,38 @@ -This section describes how to install and configure Nix for first-time -use. +# Installation + +This section describes how to install and configure Nix for first-time use. + +The current recommended option on Linux and MacOS is [multi-user](#multi-user). + +## Multi-user + +This installation offers better sharing, improved isolation, and more security +over a single user installation. + +This option requires either: + +* Linux running systemd, with SELinux disabled +* MacOS + +```console +$ bash <(curl -L https://nixos.org/nix/install) --daemon +``` + +## Single-user + +> Single-user is not supported on Mac. + +This installation has less requirements than the multi-user install, however it +cannot offer equivalent sharing, isolation, or security. + +This option is suitable for systems without systemd. + +```console +$ bash <(curl -L https://nixos.org/nix/install) --no-daemon +``` + +## Distributions + +The Nix community maintains installers for several distributions. + +They can be found in the [`nix-community/nix-installers`](https://github.com/nix-community/nix-installers) repository. diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index 53fdbe31a..e3fd962bd 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -3,7 +3,7 @@ The easiest way to install Nix is to run the following command: ```console -$ sh <(curl -L https://nixos.org/nix/install) +$ curl -L https://nixos.org/nix/install | sh ``` This will run the installer interactively (causing it to explain what @@ -27,7 +27,7 @@ you can authenticate with `sudo`. To explicitly select a single-user installation on your system: ```console -$ sh <(curl -L https://nixos.org/nix/install) --no-daemon +$ curl -L https://nixos.org/nix/install | sh -s -- --no-daemon ``` This will perform a single-user installation of Nix, meaning that `/nix` @@ -66,7 +66,7 @@ You can instruct the installer to perform a multi-user installation on your system: ```console -$ sh <(curl -L https://nixos.org/nix/install) --daemon +$ curl -L https://nixos.org/nix/install | sh -s -- --daemon ``` The multi-user installation of Nix will create build users between the @@ -287,7 +287,7 @@ These install scripts can be used the same as the main NixOS.org installation script: ```console -$ sh <(curl -L https://nixos.org/nix/install) +$ curl -L https://nixos.org/nix/install | sh ``` In the same directory of the install script are sha256 sums, and gpg diff --git a/doc/manual/src/language/advanced-attributes.md b/doc/manual/src/language/advanced-attributes.md index 2e7e80ed0..5a63236e5 100644 --- a/doc/manual/src/language/advanced-attributes.md +++ b/doc/manual/src/language/advanced-attributes.md @@ -207,13 +207,13 @@ Derivations can declare some infrequently used optional attributes. the hash in either hexadecimal or base-32 notation. (See the [`nix-hash` command](../command-ref/nix-hash.md) for information about converting to and from base-32 notation.) - + - [`__contentAddressed`]{#adv-attr-__contentAddressed} If this **experimental** attribute is set to true, then the derivation outputs will be stored in a content-addressed location rather than the traditional input-addressed one. - This only has an effect if the `ca-derivation` experimental feature is enabled. - + This only has an effect if the `ca-derivations` experimental feature is enabled. + Setting this attribute also requires setting `outputHashMode` and `outputHashAlgo` like for *fixed-output derivations* (see above). - [`passAsFile`]{#adv-attr-passAsFile}\ @@ -255,3 +255,78 @@ Derivations can declare some infrequently used optional attributes. > substituted. Thus it is usually a good idea to align `system` with > `builtins.currentSystem` when setting `allowSubstitutes` to > `false`. For most trivial derivations this should be the case. + + - [`__structuredAttrs`]{#adv-attr-structuredAttrs}\ + If the special attribute `__structuredAttrs` is set to `true`, the other derivation + attributes are serialised in JSON format and made available to the + builder via the file `.attrs.json` in the builder’s temporary + directory. This obviates the need for [`passAsFile`](#adv-attr-passAsFile) since JSON files + have no size restrictions, unlike process environments. + + It also makes it possible to tweak derivation settings in a structured way; see + [`outputChecks`](#adv-attr-outputChecks) for example. + + As a convenience to Bash builders, + Nix writes a script named `.attrs.sh` to the builder’s directory + that initialises shell variables corresponding to all attributes + that are representable in Bash. This includes non-nested + (associative) arrays. For example, the attribute `hardening.format = true` + ends up as the Bash associative array element `${hardening[format]}`. + + - [`outputChecks`]{#adv-attr-outputChecks}\ + When using [structured attributes](#adv-attr-structuredAttrs), the `outputChecks` + attribute allows defining checks per-output. + + In addition to + [`allowedReferences`](#adv-attr-allowedReferences), [`allowedRequisites`](#adv-attr-allowedRequisites), + [`disallowedReferences`](#adv-attr-disallowedReferences) and [`disallowedRequisites`](#adv-attr-disallowedRequisites), + the following attributes are available: + + - `maxSize` defines the maximum size of the resulting [store object](../glossary.md#gloss-store-object). + - `maxClosureSize` defines the maximum size of the output's closure. + - `ignoreSelfRefs` controls whether self-references should be considered when + checking for allowed references/requisites. + + Example: + + ```nix + __structuredAttrs = true; + + outputChecks.out = { + # The closure of 'out' must not be larger than 256 MiB. + maxClosureSize = 256 * 1024 * 1024; + + # It must not refer to the C compiler or to the 'dev' output. + disallowedRequisites = [ stdenv.cc "dev" ]; + }; + + outputChecks.dev = { + # The 'dev' output must not be larger than 128 KiB. + maxSize = 128 * 1024; + }; + ``` + + - [`unsafeDiscardReferences`]{#adv-attr-unsafeDiscardReferences}\ + > **Warning** + > This is an experimental feature. + > + > To enable it, add the following to [nix.conf](../command-ref/conf-file.md): + > + > ``` + > extra-experimental-features = discard-references + > ``` + + When using [structured attributes](#adv-attr-structuredAttrs), the + attribute `unsafeDiscardReferences` is an attribute set with a boolean value for each output name. + If set to `true`, it disables scanning the output for runtime dependencies. + + Example: + + ```nix + __structuredAttrs = true; + unsafeDiscardReferences.out = true; + ``` + + This is useful, for example, when generating self-contained filesystem images with + their own embedded Nix store: hashes found inside such an image refer + to the embedded store and not to the host's Nix store. diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 31300631c..3eabe1a02 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -91,7 +91,7 @@ This is an incomplete overview of language features, by example. <tr> <td> - `"hello ${ { a = "world" }.a }"` + `"hello ${ { a = "world"; }.a }"` `"1 2 ${toString 3}"` diff --git a/doc/manual/src/language/operators.md b/doc/manual/src/language/operators.md index 1f918bd4d..90b325597 100644 --- a/doc/manual/src/language/operators.md +++ b/doc/manual/src/language/operators.md @@ -24,7 +24,7 @@ | [Equality] | *expr* `==` *expr* | none | 11 | | Inequality | *expr* `!=` *expr* | none | 11 | | Logical conjunction (`AND`) | *bool* `&&` *bool* | left | 12 | -| Logical disjunction (`OR`) | *bool* `\|\|` *bool* | left | 13 | +| Logical disjunction (`OR`) | *bool* <code>\|\|</code> *bool* | left | 13 | | [Logical implication] | *bool* `->` *bool* | none | 14 | [string]: ./values.md#type-string @@ -116,7 +116,7 @@ The result is a string. [store path]: ../glossary.md#gloss-store-path [store]: ../glossary.md#gloss-store -[Path and string concatenation]: #path-and-string-concatenation +[String and path concatenation]: #string-and-path-concatenation ## Update @@ -133,7 +133,7 @@ If an attribute name is present in both, the attribute value from the latter is Comparison is -- [arithmetic] for [number]s +- [arithmetic] for [number]s - lexicographic for [string]s and [path]s - item-wise lexicographic for [list]s: elements at the same index in both lists are compared according to their type and skipped if they are equal. diff --git a/doc/manual/src/quick-start.md b/doc/manual/src/quick-start.md index b54e73500..651134c25 100644 --- a/doc/manual/src/quick-start.md +++ b/doc/manual/src/quick-start.md @@ -4,16 +4,16 @@ This chapter is for impatient people who don't like reading documentation. For more in-depth information you are kindly referred to subsequent chapters. -1. Install single-user Nix by running the following: +1. Install Nix by running the following: ```console - $ bash <(curl -L https://nixos.org/nix/install) + $ curl -L https://nixos.org/nix/install | sh ``` - This will install Nix in `/nix`. The install script will create - `/nix` using `sudo`, so make sure you have sufficient rights. (For - other installation methods, see - [here](installation/installation.md).) + The install script will use `sudo`, so make sure you have sufficient rights. + On Linux, `--daemon` can be omitted for a single-user install. + + For other installation methods, see [here](installation/installation.md). 1. See what installable packages are currently available in the channel: diff --git a/doc/manual/src/release-notes/rl-2.13.md b/doc/manual/src/release-notes/rl-2.13.md index 2ebf19f60..168708113 100644 --- a/doc/manual/src/release-notes/rl-2.13.md +++ b/doc/manual/src/release-notes/rl-2.13.md @@ -25,11 +25,11 @@ * Allow explicitly selecting outputs in a store derivation installable, just like we can do with other sorts of installables. For example, ```shell-session - # nix-build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev + # nix build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev ``` now works just as ```shell-session - # nix-build glibc^dev + # nix build nixpkgs#glibc^dev ``` does already. diff --git a/doc/manual/src/release-notes/rl-2.14.md b/doc/manual/src/release-notes/rl-2.14.md new file mode 100644 index 000000000..705c118bb --- /dev/null +++ b/doc/manual/src/release-notes/rl-2.14.md @@ -0,0 +1,22 @@ +# Release 2.14 (2023-02-28) + +* A new function `builtins.readFileType` is available. It is similar to + `builtins.readDir` but acts on a single file or directory. + +* In flakes, the `.outPath` attribute of a flake now always refers to + the directory containing the `flake.nix`. This was not the case for + when `flake.nix` was in a subdirectory of e.g. a Git repository. + The root of the source of a flake in a subdirectory is still + available in `.sourceInfo.outPath`. + +* In derivations that use structured attributes, you can now use `unsafeDiscardReferences` + to disable scanning a given output for runtime dependencies: + ```nix + __structuredAttrs = true; + unsafeDiscardReferences.out = true; + ``` + This is useful e.g. when generating self-contained filesystem images with + their own embedded Nix store: hashes found inside such an image refer + to the embedded store and not to the host's Nix store. + + This requires the `discard-references` experimental feature. diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 8a79703ab..78ae99f4b 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1,10 +1,2 @@ # Release X.Y (202?-??-??) -* A new function `builtins.readFileType` is available. It is similar to - `builtins.readDir` but acts on a single file or directory. - -* The `builtins.readDir` function has been optimized when encountering not-yet-known - file types from POSIX's `readdir`. In such cases the type of each file is/was - discovered by making multiple syscalls. This change makes these operations - lazy such that these lookups will only be performed if the attribute is used. - This optimization affects a minority of filesystems and operating systems. @@ -8,10 +8,11 @@ outputs = { self, nixpkgs, nixpkgs-regression, lowdown-src }: let + inherit (nixpkgs) lib; officialRelease = false; - version = nixpkgs.lib.fileContents ./.version + versionSuffix; + version = lib.fileContents ./.version + versionSuffix; versionSuffix = if officialRelease then "" @@ -25,36 +26,42 @@ stdenvs = [ "gccStdenv" "clangStdenv" "clang11Stdenv" "stdenv" "libcxxStdenv" "ccacheStdenv" ]; - forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); - forAllSystemsAndStdenvs = f: forAllSystems (system: - nixpkgs.lib.listToAttrs + forAllSystems = lib.genAttrs systems; + + forAllCrossSystems = lib.genAttrs crossSystems; + + forAllStdenvs = f: + lib.listToAttrs (map - (n: - nixpkgs.lib.nameValuePair "${n}Packages" ( - f system n - )) stdenvs - ) - ); + (stdenvName: { + name = "${stdenvName}Packages"; + value = f stdenvName; + }) + stdenvs); - forAllStdenvs = f: nixpkgs.lib.genAttrs stdenvs (stdenv: f stdenv); # Memoize nixpkgs for different platforms for efficiency. - nixpkgsFor = - let stdenvsPackages = forAllSystemsAndStdenvs - (system: stdenv: - import nixpkgs { - inherit system; - overlays = [ - (overlayFor (p: p.${stdenv})) - ]; - } - ); - in - # Add the `stdenvPackages` at toplevel, both because these are the ones - # we want most of the time and for backwards compatibility - forAllSystems (system: stdenvsPackages.${system} // stdenvsPackages.${system}.stdenvPackages); + nixpkgsFor = forAllSystems + (system: let + make-pkgs = crossSystem: stdenv: import nixpkgs { + inherit system crossSystem; + overlays = [ + (overlayFor (p: p.${stdenv})) + ]; + }; + stdenvs = forAllStdenvs (make-pkgs null); + native = stdenvs.stdenvPackages; + in { + inherit stdenvs native; + static = native.pkgsStatic; + cross = forAllCrossSystems (crossSystem: make-pkgs crossSystem "stdenv"); + }); - commonDeps = { pkgs, isStatic ? false }: with pkgs; rec { + commonDeps = + { pkgs + , isStatic ? pkgs.stdenv.hostPlatform.isStatic + }: + with pkgs; rec { # Use "busybox-sandbox-shell" if present, # if not (legacy) fallback and hope it's sufficient. sh = pkgs.busybox-sandbox-shell or (busybox.override { @@ -131,15 +138,20 @@ }); propagatedDeps = - [ (boehmgc.override { + [ ((boehmgc.override { enableLargeConfig = true; + }).overrideAttrs(o: { + patches = (o.patches or []) ++ [ + ./boehmgc-coroutine-sp-fallback.diff + ]; }) + ) nlohmann_json ]; }; installScriptFor = systems: - with nixpkgsFor.x86_64-linux; + with nixpkgsFor.x86_64-linux.native; runCommand "installer-script" { buildInputs = [ nix ]; } @@ -203,8 +215,9 @@ installCheckPhase = "make installcheck -j$NIX_BUILD_CORES -l$NIX_BUILD_CORES"; }; - binaryTarball = buildPackages: nix: pkgs: + binaryTarball = nix: pkgs: let + inherit (pkgs) buildPackages; inherit (pkgs) cacert; installerClosureInfo = buildPackages.closureInfo { rootPaths = [ nix cacert ]; }; in @@ -284,7 +297,15 @@ # Forward from the previous stage as we don’t want it to pick the lowdown override nixUnstable = prev.nixUnstable; - nix = with final; with commonDeps { inherit pkgs; }; currentStdenv.mkDerivation { + nix = + with final; + with commonDeps { + inherit pkgs; + inherit (currentStdenv.hostPlatform) isStatic; + }; + let + canRunInstalled = currentStdenv.buildPlatform.canExecute currentStdenv.hostPlatform; + in currentStdenv.mkDerivation { name = "nix-${version}"; inherit version; @@ -295,24 +316,26 @@ outputs = [ "out" "dev" "doc" ]; nativeBuildInputs = nativeBuildDeps; - buildInputs = buildDeps ++ awsDeps; + buildInputs = buildDeps + # There have been issues building these dependencies + ++ lib.optionals (currentStdenv.hostPlatform == currentStdenv.buildPlatform) awsDeps; propagatedBuildInputs = propagatedDeps; disallowedReferences = [ boost ]; - preConfigure = + preConfigure = lib.optionalString (! currentStdenv.hostPlatform.isStatic) '' # Copy libboost_context so we don't get all of Boost in our closure. # https://github.com/NixOS/nixpkgs/issues/45462 mkdir -p $out/lib cp -pd ${boost}/lib/{libboost_context*,libboost_thread*,libboost_system*} $out/lib rm -f $out/lib/*.a - ${lib.optionalString currentStdenv.isLinux '' + ${lib.optionalString currentStdenv.hostPlatform.isLinux '' chmod u+w $out/lib/*.so.* patchelf --set-rpath $out/lib:${currentStdenv.cc.cc.lib}/lib $out/lib/libboost_thread.so.* ''} - ${lib.optionalString currentStdenv.isDarwin '' + ${lib.optionalString currentStdenv.hostPlatform.isDarwin '' for LIB in $out/lib/*.dylib; do chmod u+w $LIB install_name_tool -id $LIB $LIB @@ -323,7 +346,9 @@ ''; configureFlags = configureFlags ++ - [ "--sysconfdir=/etc" ]; + [ "--sysconfdir=/etc" ] ++ + lib.optional stdenv.hostPlatform.isStatic "--enable-embedded-sandbox-shell" ++ + lib.optional (!canRunInstalled) "--disable-doc-gen"; enableParallelBuilding = true; @@ -347,10 +372,12 @@ doInstallCheck = true; installCheckFlags = "sysconfdir=$(out)/etc"; - separateDebugInfo = true; + separateDebugInfo = !currentStdenv.hostPlatform.isStatic; strictDeps = true; + hardeningDisable = lib.optional stdenv.hostPlatform.isStatic "pie"; + passthru.perl-bindings = with final; perl.pkgs.toPerlModule (currentStdenv.mkDerivation { name = "nix-perl-${version}"; @@ -383,7 +410,7 @@ postUnpack = "sourceRoot=$sourceRoot/perl"; }); - meta.platforms = systems; + meta.platforms = lib.platforms.unix; }; lowdown-nix = with final; currentStdenv.mkDerivation rec { @@ -404,7 +431,20 @@ }; }; + nixos-lib = import (nixpkgs + "/nixos/lib") { }; + + # https://nixos.org/manual/nixos/unstable/index.html#sec-calling-nixos-tests + runNixOSTestFor = system: test: nixos-lib.runTest { + imports = [ test ]; + hostPkgs = nixpkgsFor.${system}.native; + defaults = { + nixpkgs.pkgs = nixpkgsFor.${system}.native; + }; + _module.args.nixpkgs = nixpkgs; + }; + in { + inherit nixpkgsFor; # A Nixpkgs overlay that overrides the 'nix' and # 'nix.perl-bindings' packages. @@ -413,32 +453,28 @@ hydraJobs = { # Binary package for various platforms. - build = nixpkgs.lib.genAttrs systems (system: self.packages.${system}.nix); + build = forAllSystems (system: self.packages.${system}.nix); - buildStatic = nixpkgs.lib.genAttrs linux64BitSystems (system: self.packages.${system}.nix-static); + buildStatic = lib.genAttrs linux64BitSystems (system: self.packages.${system}.nix-static); - buildCross = nixpkgs.lib.genAttrs crossSystems (crossSystem: - nixpkgs.lib.genAttrs ["x86_64-linux"] (system: self.packages.${system}."nix-${crossSystem}")); + buildCross = forAllCrossSystems (crossSystem: + lib.genAttrs ["x86_64-linux"] (system: self.packages.${system}."nix-${crossSystem}")); - buildNoGc = nixpkgs.lib.genAttrs systems (system: self.packages.${system}.nix.overrideAttrs (a: { configureFlags = (a.configureFlags or []) ++ ["--enable-gc=no"];})); + buildNoGc = forAllSystems (system: self.packages.${system}.nix.overrideAttrs (a: { configureFlags = (a.configureFlags or []) ++ ["--enable-gc=no"];})); # Perl bindings for various platforms. - perlBindings = nixpkgs.lib.genAttrs systems (system: self.packages.${system}.nix.perl-bindings); + perlBindings = forAllSystems (system: nixpkgsFor.${system}.native.nix.perl-bindings); # Binary tarball for various platforms, containing a Nix store # with the closure of 'nix' package, and the second half of # the installation script. - binaryTarball = nixpkgs.lib.genAttrs systems (system: binaryTarball nixpkgsFor.${system} nixpkgsFor.${system}.nix nixpkgsFor.${system}); - - binaryTarballCross = nixpkgs.lib.genAttrs ["x86_64-linux"] (system: builtins.listToAttrs (map (crossSystem: { - name = crossSystem; - value = let - nixpkgsCross = import nixpkgs { - inherit system crossSystem; - overlays = [ self.overlays.default ]; - }; - in binaryTarball nixpkgsFor.${system} self.packages.${system}."nix-${crossSystem}" nixpkgsCross; - }) crossSystems)); + binaryTarball = forAllSystems (system: binaryTarball nixpkgsFor.${system}.native.nix nixpkgsFor.${system}.native); + + binaryTarballCross = lib.genAttrs ["x86_64-linux"] (system: + forAllCrossSystems (crossSystem: + binaryTarball + self.packages.${system}."nix-${crossSystem}" + nixpkgsFor.${system}.cross.${crossSystem})); # The first half of the installation script. This is uploaded # to https://nixos.org/nix/install. It downloads the binary @@ -448,11 +484,11 @@ installerScriptForGHA = installScriptFor [ "x86_64-linux" "x86_64-darwin" "armv6l-linux" "armv7l-linux"]; # docker image with Nix inside - dockerImage = nixpkgs.lib.genAttrs linux64BitSystems (system: self.packages.${system}.dockerImage); + dockerImage = lib.genAttrs linux64BitSystems (system: self.packages.${system}.dockerImage); # Line coverage analysis. coverage = - with nixpkgsFor.x86_64-linux; + with nixpkgsFor.x86_64-linux.native; with commonDeps { inherit pkgs; }; releaseTools.coverageAnalysis { @@ -460,6 +496,10 @@ src = self; + configureFlags = [ + "CXXFLAGS=-I${lib.getDev pkgs.rapidcheck}/extras/gtest/include" + ]; + enableParallelBuilding = true; nativeBuildInputs = nativeBuildDeps; @@ -478,54 +518,29 @@ }; # System tests. - tests.remoteBuilds = import ./tests/remote-builds.nix { - system = "x86_64-linux"; - inherit nixpkgs; - overlay = self.overlays.default; - }; + tests.authorization = runNixOSTestFor "x86_64-linux" ./tests/nixos/authorization.nix; - tests.nix-copy-closure = import ./tests/nix-copy-closure.nix { - system = "x86_64-linux"; - inherit nixpkgs; - overlay = self.overlays.default; - }; + tests.remoteBuilds = runNixOSTestFor "x86_64-linux" ./tests/nixos/remote-builds.nix; - tests.nssPreload = (import ./tests/nss-preload.nix rec { - system = "x86_64-linux"; - inherit nixpkgs; - overlay = self.overlays.default; - }); + tests.nix-copy-closure = runNixOSTestFor "x86_64-linux" ./tests/nixos/nix-copy-closure.nix; - tests.githubFlakes = (import ./tests/github-flakes.nix rec { - system = "x86_64-linux"; - inherit nixpkgs; - overlay = self.overlays.default; - }); + tests.nssPreload = runNixOSTestFor "x86_64-linux" ./tests/nixos/nss-preload.nix; - tests.sourcehutFlakes = (import ./tests/sourcehut-flakes.nix rec { - system = "x86_64-linux"; - inherit nixpkgs; - overlay = self.overlays.default; - }); + tests.githubFlakes = runNixOSTestFor "x86_64-linux" ./tests/nixos/github-flakes.nix; - tests.containers = (import ./tests/containers.nix rec { - system = "x86_64-linux"; - inherit nixpkgs; - overlay = self.overlays.default; - }); + tests.sourcehutFlakes = runNixOSTestFor "x86_64-linux" ./tests/nixos/sourcehut-flakes.nix; - tests.setuid = nixpkgs.lib.genAttrs + tests.containers = runNixOSTestFor "x86_64-linux" ./tests/nixos/containers/containers.nix; + + tests.setuid = lib.genAttrs ["i686-linux" "x86_64-linux"] - (system: - import ./tests/setuid.nix rec { - inherit nixpkgs system; - overlay = self.overlays.default; - }); + (system: runNixOSTestFor system ./tests/nixos/setuid.nix); + # Make sure that nix-env still produces the exact same result # on a particular version of Nixpkgs. tests.evalNixpkgs = - with nixpkgsFor.x86_64-linux; + with nixpkgsFor.x86_64-linux.native; runCommand "eval-nixos" { buildInputs = [ nix ]; } '' type -p nix-env @@ -536,18 +551,18 @@ ''; tests.nixpkgsLibTests = - nixpkgs.lib.genAttrs systems (system: + forAllSystems (system: import (nixpkgs + "/lib/tests/release.nix") - { pkgs = nixpkgsFor.${system}; } + { pkgs = nixpkgsFor.${system}.native; } ); metrics.nixpkgs = import "${nixpkgs-regression}/pkgs/top-level/metrics.nix" { - pkgs = nixpkgsFor.x86_64-linux; + pkgs = nixpkgsFor.x86_64-linux.native; nixpkgs = nixpkgs-regression; }; installTests = forAllSystems (system: - let pkgs = nixpkgsFor.${system}; in + let pkgs = nixpkgsFor.${system}.native; in pkgs.runCommand "install-tests" { againstSelf = testNixVersions pkgs pkgs.nix pkgs.pkgs.nix; againstCurrentUnstable = @@ -572,67 +587,18 @@ perlBindings = self.hydraJobs.perlBindings.${system}; installTests = self.hydraJobs.installTests.${system}; nixpkgsLibTests = self.hydraJobs.tests.nixpkgsLibTests.${system}; - } // (nixpkgs.lib.optionalAttrs (builtins.elem system linux64BitSystems)) { + } // (lib.optionalAttrs (builtins.elem system linux64BitSystems)) { dockerImage = self.hydraJobs.dockerImage.${system}; }); packages = forAllSystems (system: rec { - inherit (nixpkgsFor.${system}) nix; + inherit (nixpkgsFor.${system}.native) nix; default = nix; - } // (nixpkgs.lib.optionalAttrs (builtins.elem system linux64BitSystems) { - nix-static = let - nixpkgs = nixpkgsFor.${system}.pkgsStatic; - in with commonDeps { pkgs = nixpkgs; isStatic = true; }; nixpkgs.stdenv.mkDerivation { - name = "nix-${version}"; - - src = self; - - VERSION_SUFFIX = versionSuffix; - - outputs = [ "out" "dev" "doc" ]; - - nativeBuildInputs = nativeBuildDeps; - buildInputs = buildDeps ++ propagatedDeps; - - # Work around pkgsStatic disabling all tests. - # Remove in NixOS 22.11, see https://github.com/NixOS/nixpkgs/pull/140271. - preHook = - '' - doCheck=1 - doInstallCheck=1 - ''; - - configureFlags = - configureFlags ++ - [ "--sysconfdir=/etc" - "--enable-embedded-sandbox-shell" - ]; - - enableParallelBuilding = true; - - makeFlags = "profiledir=$(out)/etc/profile.d"; - - installFlags = "sysconfdir=$(out)/etc"; - - postInstall = '' - mkdir -p $doc/nix-support - echo "doc manual $doc/share/doc/nix/manual" >> $doc/nix-support/hydra-build-products - mkdir -p $out/nix-support - echo "file binary-dist $out/bin/nix" >> $out/nix-support/hydra-build-products - ''; - - installCheckFlags = "sysconfdir=$(out)/etc"; - - stripAllList = ["bin"]; - - strictDeps = true; - - hardeningDisable = [ "pie" ]; - }; - + } // (lib.optionalAttrs (builtins.elem system linux64BitSystems) { + nix-static = nixpkgsFor.${system}.static.nix; dockerImage = let - pkgs = nixpkgsFor.${system}; + pkgs = nixpkgsFor.${system}.native; image = import ./docker.nix { inherit pkgs; tag = version; }; in pkgs.runCommand @@ -644,65 +610,30 @@ ln -s ${image} $image echo "file binary-dist $image" >> $out/nix-support/hydra-build-products ''; - } - - // builtins.listToAttrs (map (crossSystem: { - name = "nix-${crossSystem}"; - value = let - nixpkgsCross = import nixpkgs { - inherit system crossSystem; - overlays = [ self.overlays.default ]; - }; - in with commonDeps { pkgs = nixpkgsCross; }; nixpkgsCross.stdenv.mkDerivation { - name = "nix-${version}"; - - src = self; - - VERSION_SUFFIX = versionSuffix; - - outputs = [ "out" "dev" "doc" ]; - - nativeBuildInputs = nativeBuildDeps; - buildInputs = buildDeps ++ propagatedDeps; - - configureFlags = [ "--sysconfdir=/etc" "--disable-doc-gen" ]; - - enableParallelBuilding = true; - - makeFlags = "profiledir=$(out)/etc/profile.d"; - - doCheck = true; - - installFlags = "sysconfdir=$(out)/etc"; - - postInstall = '' - mkdir -p $doc/nix-support - echo "doc manual $doc/share/doc/nix/manual" >> $doc/nix-support/hydra-build-products - mkdir -p $out/nix-support - echo "file binary-dist $out/bin/nix" >> $out/nix-support/hydra-build-products - ''; - - doInstallCheck = true; - installCheckFlags = "sysconfdir=$(out)/etc"; - }; - }) (if system == "x86_64-linux" then crossSystems else []))) - - // (builtins.listToAttrs (map (stdenvName: - nixpkgsFor.${system}.lib.nameValuePair - "nix-${stdenvName}" - nixpkgsFor.${system}."${stdenvName}Packages".nix - ) stdenvs))); - - devShells = forAllSystems (system: - forAllStdenvs (stdenv: - with nixpkgsFor.${system}; + } // builtins.listToAttrs (map + (crossSystem: { + name = "nix-${crossSystem}"; + value = nixpkgsFor.${system}.cross.${crossSystem}.nix; + }) + crossSystems) + // builtins.listToAttrs (map + (stdenvName: { + name = "nix-${stdenvName}"; + value = nixpkgsFor.${system}.stdenvs."${stdenvName}Packages".nix; + }) + stdenvs))); + + devShells = let + makeShell = pkgs: stdenv: with commonDeps { inherit pkgs; }; - nixpkgsFor.${system}.${stdenv}.mkDerivation { + stdenv.mkDerivation { name = "nix"; outputs = [ "out" "dev" "doc" ]; - nativeBuildInputs = nativeBuildDeps; + nativeBuildInputs = nativeBuildDeps + ++ (lib.optionals stdenv.cc.isClang [ pkgs.bear pkgs.clang-tools ]); + buildInputs = buildDeps ++ propagatedDeps ++ awsDeps; inherit configureFlags; @@ -720,10 +651,21 @@ # Make bash completion work. XDG_DATA_DIRS+=:$out/share ''; - } - ) - // { default = self.devShells.${system}.stdenv; } - ); - + }; + in + forAllSystems (system: + let + makeShells = prefix: pkgs: + lib.mapAttrs' + (k: v: lib.nameValuePair "${prefix}-${k}" v) + (forAllStdenvs (stdenvName: makeShell pkgs pkgs.${stdenvName})); + in + (makeShells "native" nixpkgsFor.${system}.native) // + (makeShells "static" nixpkgsFor.${system}.static) // + (forAllCrossSystems (crossSystem: let pkgs = nixpkgsFor.${system}.cross.${crossSystem}; in makeShell pkgs pkgs.stdenv)) // + { + default = self.devShells.${system}.native-stdenvPackages; + } + ); }; } diff --git a/maintainers/backporting.md b/maintainers/backporting.md new file mode 100644 index 000000000..2424050c8 --- /dev/null +++ b/maintainers/backporting.md @@ -0,0 +1,12 @@ + +# Backporting + +To [automatically backport a pull request](https://github.com/NixOS/nix/blob/master/.github/workflows/backport.yml) to a release branch once it's merged, assign it a label of the form [`backport <branch>`](https://github.com/NixOS/nix/labels?q=backport). + +Since [GitHub Actions workflows will not trigger other workflows](https://docs.github.com/en/actions/using-workflows/triggering-a-workflow#triggering-a-workflow-from-a-workflow), checks on the automatic backport need to be triggered by another actor. +This is achieved by closing and reopening the backport pull request. + +This specifically affects the [`installer_test`] check. +Note that it only runs after the other tests, so it may take a while to appear. + +[`installer_test`]: https://github.com/NixOS/nix/blob/895dfc656a21f6252ddf48df0d1f215effa04ecb/.github/workflows/ci.yml#L70-L91 diff --git a/maintainers/release-process.md b/maintainers/release-process.md new file mode 100644 index 000000000..b52d218f5 --- /dev/null +++ b/maintainers/release-process.md @@ -0,0 +1,181 @@ +# Nix release process + +## Release artifacts + +The release process is intended to create the following for each +release: + +* A Git tag + +* Binary tarballs in https://releases.nixos.org/?prefix=nix/ + +* Docker images + +* Closures in https://cache.nixos.org + +* (Optionally) Updated `fallback-paths.nix` in Nixpkgs + +* An updated manual on https://nixos.org/manual/nix/stable/ + +## Creating a new release from the `master` branch + +* Make sure that the [Hydra `master` jobset](https://hydra.nixos.org/jobset/nix/master) succeeds. + +* In a checkout of the Nix repo, make sure you're on `master` and run + `git pull`. + +* Move the contents of `doc/manual/src/release-notes/rl-next.md` + (except the first line) to + `doc/manual/src/release-notes/rl-$VERSION.md` (where `$VERSION` is + the contents of `.version` *without* the patch level, e.g. `2.12` + rather than `2.12.0`). + +* Add a header to `doc/manual/src/release-notes/rl-$VERSION.md` like + + ``` + # Release 2.12 (2022-12-06) + ``` + +* Proof-read / edit / rearrange the release notes. Breaking changes + and highlights should go to the top. + +* Add a link to the release notes to `doc/manual/src/SUMMARY.md.in` + (*not* `SUMMARY.md`), e.g. + + ``` + - [Release 2.12 (2022-12-06)](release-notes/rl-2.12.md) + ``` + +* Run + + ```console + $ git checkout -b release-notes + $ git add doc/manual/src/release-notes/rl-$VERSION.md + $ git commit -a -m 'Release notes' + $ git push --set-upstream $REMOTE release-notes + ``` + +* Create a PR for `release-notes`. + +* Wait for the PR to be merged. + +* Create a branch for the release: + + ```console + $ git checkout master + $ git pull + $ git checkout -b $VERSION-maintenance + ``` + +* Mark the release as stable: + + ```console + $ git cherry-pick f673551e71942a52b6d7ae66af8b67140904a76a + ``` + + This removes the link to `rl-next.md` from the manual and sets + `officialRelease = true` in `flake.nix`. + +* Push the release branch: + + ```console + $ git push --set-upstream origin $VERSION-maintenance + ``` + +* Create a jobset for the release branch on Hydra as follows: + + * Go to the jobset of the previous release + (e.g. https://hydra.nixos.org/jobset/nix/maintenance-2.11). + + * Select `Actions -> Clone this jobset`. + + * Set identifier to `maintenance-$VERSION`. + + * Set description to `$VERSION release branch`. + + * Set flake URL to `github:NixOS/nix/$VERSION-maintenance`. + + * Hit `Create jobset`. + +* Wait for the new jobset to evaluate and build. If impatient, go to + the evaluation and select `Actions -> Bump builds to front of + queue`. + +* When the jobset evaluation has succeeded building, take note of the + evaluation ID (e.g. `1780832` in + `https://hydra.nixos.org/eval/1780832`). + +* Tag the release and upload the release artifacts to + [`releases.nixos.org`](https://releases.nixos.org/) and [Docker Hub](https://hub.docker.com/): + + ```console + $ IS_LATEST=1 ./maintainers/upload-release.pl <EVAL-ID> + ``` + + Note: `IS_LATEST=1` causes the `latest-release` branch to be + force-updated. This is used by the `nixos.org` website to get the + [latest Nix manual](https://nixos.org/manual/nixpkgs/unstable/). + + TODO: This script requires the right AWS credentials. Document. + + TODO: This script currently requires a + `/home/eelco/Dev/nix-pristine` and + `/home/eelco/Dev/nixpkgs-pristine`. + + TODO: trigger nixos.org netlify: https://docs.netlify.com/configure-builds/build-hooks/ +* Prepare for the next point release by editing `.version` to + e.g. + + ```console + $ echo 2.12.1 > .version + $ git commit -a -m 'Bump version' + $ git push + ``` + + Commit and push this to the maintenance branch. + +* Bump the version of `master`: + + ```console + $ git checkout master + $ git pull + $ NEW_VERSION=2.13.0 + $ echo -n $NEW_VERSION > .version + $ git checkout -b bump-$NEW_VERSION + $ git commit -a -m 'Bump version' + $ git push --set-upstream origin bump-$NEW_VERSION + ``` + + Make a pull request and auto-merge it. + +* Create a milestone for the next release, move all unresolved issues + from the previous milestone, and close the previous milestone. Set + the date for the next milestone 6 weeks from now. + +* Create a backport label + +* Post an [announcement on Discourse](https://discourse.nixos.org/c/announcements/8), including the contents of + `rl-$VERSION.md`. + +## Creating a point release + +* Wait for the desired evaluation of the maintenance jobset to finish + building. + +* Run + + ```console + $ IS_LATEST=1 ./maintainers/upload-release.pl <EVAL-ID> + ``` + + Omit `IS_LATEST=1` when creating a point release that is not on the + most recent stable branch. This prevents `nixos.org` to going back + to an older release. + +* Bump the version number of the release branch as above (e.g. to + `2.12.2`). + +## Recovering from mistakes + +`upload-release.pl` should be idempotent. For instance a wrong `IS_LATEST` value can be fixed that way, by running the script on the actual latest release. + diff --git a/mk/libraries.mk b/mk/libraries.mk index 6541775f3..02e4d47f9 100644 --- a/mk/libraries.mk +++ b/mk/libraries.mk @@ -67,6 +67,7 @@ define build-library $(1)_LDFLAGS_USE := $(1)_LDFLAGS_USE_INSTALLED := + $(1)_LIB_CLOSURE := $(1) $$(eval $$(call create-dir, $$(_d))) @@ -128,10 +129,12 @@ define build-library +$$(trace-ld) $(LD) -Ur -o $$(_d)/$$($(1)_NAME).o $$^ $$(trace-ar) $(AR) crs $$@ $$(_d)/$$($(1)_NAME).o - $(1)_LDFLAGS_USE += $$($(1)_PATH) $$($(1)_LDFLAGS) + $(1)_LDFLAGS_USE += $$($(1)_PATH) $$($(1)_LDFLAGS) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE)) $(1)_INSTALL_PATH := $$(libdir)/$$($(1)_NAME).a + $(1)_LIB_CLOSURE += $$($(1)_LIBS) + endif $(1)_LDFLAGS_USE += $$($(1)_LDFLAGS_PROPAGATED) diff --git a/mk/programs.mk b/mk/programs.mk index 0fc1990f7..1ee1d3fa5 100644 --- a/mk/programs.mk +++ b/mk/programs.mk @@ -3,6 +3,9 @@ programs-list := # Build a program with symbolic name $(1). The program is defined by # various variables prefixed by ‘$(1)_’: # +# - $(1)_NAME: the name of the program (e.g. ‘foo’); defaults to +# $(1). +# # - $(1)_DIR: the directory where the (non-installed) program will be # placed. # @@ -23,11 +26,12 @@ programs-list := # - $(1)_INSTALL_DIR: the directory where the program will be # installed; defaults to $(bindir). define build-program + $(1)_NAME ?= $(1) _d := $(buildprefix)$$($(1)_DIR) _srcs := $$(sort $$(foreach src, $$($(1)_SOURCES), $$(src))) $(1)_OBJS := $$(addprefix $(buildprefix), $$(addsuffix .o, $$(basename $$(_srcs)))) - _libs := $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_PATH)) - $(1)_PATH := $$(_d)/$(1) + _libs := $$(foreach lib, $$($(1)_LIBS), $$(foreach lib2, $$($$(lib)_LIB_CLOSURE), $$($$(lib2)_PATH))) + $(1)_PATH := $$(_d)/$$($(1)_NAME) $$(eval $$(call create-dir, $$(_d))) @@ -38,7 +42,7 @@ define build-program ifdef $(1)_INSTALL_DIR - $(1)_INSTALL_PATH := $$($(1)_INSTALL_DIR)/$(1) + $(1)_INSTALL_PATH := $$($(1)_INSTALL_DIR)/$$($(1)_NAME) $$(eval $$(call create-dir, $$($(1)_INSTALL_DIR))) @@ -54,7 +58,7 @@ define build-program else $(DESTDIR)$$($(1)_INSTALL_PATH): $$($(1)_PATH) | $(DESTDIR)$$($(1)_INSTALL_DIR)/ - install -t $(DESTDIR)$$($(1)_INSTALL_DIR) $$< + +$$(trace-install) install -t $(DESTDIR)$$($(1)_INSTALL_DIR) $$< endif endif diff --git a/perl/Makefile b/perl/Makefile index 708f86882..c2c95f255 100644 --- a/perl/Makefile +++ b/perl/Makefile @@ -1,6 +1,6 @@ makefiles = local.mk -GLOBAL_CXXFLAGS += -g -Wall -std=c++17 -I ../src +GLOBAL_CXXFLAGS += -g -Wall -std=c++2a -I ../src -include Makefile.config diff --git a/perl/lib/Nix/Store.xs b/perl/lib/Nix/Store.xs index f19fb20bf..bfe00d3e2 100644 --- a/perl/lib/Nix/Store.xs +++ b/perl/lib/Nix/Store.xs @@ -26,6 +26,7 @@ static ref<Store> store() static std::shared_ptr<Store> _store; if (!_store) { try { + initLibStore(); loadConfFile(); settings.lockCPU = false; _store = openStore(); @@ -295,7 +296,7 @@ SV * makeFixedOutputPath(int recursive, char * algo, char * hash, char * name) auto h = Hash::parseAny(hash, parseHashType(algo)); auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat; auto path = store()->makeFixedOutputPath(name, FixedOutputInfo { - { + .hash = { .method = method, .hash = h, }, diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh index f149ea0d7..7c66538b0 100644 --- a/scripts/install-multi-user.sh +++ b/scripts/install-multi-user.sh @@ -136,7 +136,7 @@ EOF cat <<EOF $step. Delete the files Nix added to your system: - sudo rm -rf /etc/nix $NIX_ROOT $ROOT_HOME/.nix-profile $ROOT_HOME/.nix-defexpr $ROOT_HOME/.nix-channels $HOME/.nix-profile $HOME/.nix-defexpr $HOME/.nix-channels + sudo rm -rf "/etc/nix" "$NIX_ROOT" "$ROOT_HOME/.nix-profile" "$ROOT_HOME/.nix-defexpr" "$ROOT_HOME/.nix-channels" "$ROOT_HOME/.local/state/nix" "$ROOT_HOME/.cache/nix" "$HOME/.nix-profile" "$HOME/.nix-defexpr" "$HOME/.nix-channels" "$HOME/.local/state/nix" "$HOME/.cache/nix" and that is it. diff --git a/scripts/install-nix-from-closure.sh b/scripts/install-nix-from-closure.sh index ccd19e1a8..794622530 100644 --- a/scripts/install-nix-from-closure.sh +++ b/scripts/install-nix-from-closure.sh @@ -188,6 +188,8 @@ fi # shellcheck source=./nix-profile.sh.in . "$nix/etc/profile.d/nix.sh" +NIX_LINK="$HOME/.nix-profile" + if ! "$nix/bin/nix-env" -i "$nix"; then echo "$0: unable to install Nix into your default profile" >&2 exit 1 @@ -196,7 +198,7 @@ fi # Install an SSL certificate bundle. if [ -z "$NIX_SSL_CERT_FILE" ] || ! [ -f "$NIX_SSL_CERT_FILE" ]; then "$nix/bin/nix-env" -i "$cacert" - export NIX_SSL_CERT_FILE="$HOME/.nix-profile/etc/ssl/certs/ca-bundle.crt" + export NIX_SSL_CERT_FILE="$NIX_LINK/etc/ssl/certs/ca-bundle.crt" fi # Subscribe the user to the Nixpkgs channel and fetch it. @@ -214,8 +216,8 @@ fi added= p= -p_sh=$HOME/.nix-profile/etc/profile.d/nix.sh -p_fish=$HOME/.nix-profile/etc/profile.d/nix.fish +p_sh=$NIX_LINK/etc/profile.d/nix.sh +p_fish=$NIX_LINK/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 diff --git a/scripts/nix-profile-daemon.sh.in b/scripts/nix-profile-daemon.sh.in index 0a47571ac..235536c65 100644 --- a/scripts/nix-profile-daemon.sh.in +++ b/scripts/nix-profile-daemon.sh.in @@ -2,7 +2,33 @@ if [ -n "${__ETC_PROFILE_NIX_SOURCED:-}" ]; then return; fi __ETC_PROFILE_NIX_SOURCED=1 -export NIX_PROFILES="@localstatedir@/nix/profiles/default $HOME/.nix-profile" +NIX_LINK=$HOME/.nix-profile +if [ -n "$XDG_STATE_HOME" ]; then + NIX_LINK_NEW="$XDG_STATE_HOME/nix/profile" +else + NIX_LINK_NEW=$HOME/.local/state/nix/profile +fi +if ! [ -e "$NIX_LINK" ]; then + NIX_LINK="$NIX_LINK_NEW" +else + if [ -t 2 ] && [ -e "$NIX_LINK_NEW" ]; then + warning="\033[1;35mwarning:\033[0m" + printf "$warning Both %s and legacy %s exist; using the latter.\n" "$NIX_LINK_NEW" "$NIX_LINK" 1>&2 + if [ "$(realpath "$NIX_LINK")" = "$(realpath "$NIX_LINK_NEW")" ]; then + printf " Since the profiles match, you can safely delete either of them.\n" 1>&2 + else + # This should be an exceptionally rare occasion: the only way to get it would be to + # 1. Update to newer Nix; + # 2. Remove .nix-profile; + # 3. Set the $NIX_LINK_NEW to something other than the default user profile; + # 4. Roll back to older Nix. + # If someone did all that, they can probably figure out how to migrate the profile. + printf "$warning Profiles do not match. You should manually migrate from %s to %s.\n" "$NIX_LINK" "$NIX_LINK_NEW" 1>&2 + fi + fi +fi + +export NIX_PROFILES="@localstatedir@/nix/profiles/default $NIX_LINK" # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. if [ -n "${NIX_SSL_CERT_FILE:-}" ]; then @@ -34,4 +60,5 @@ else unset -f check_nix_profiles fi -export PATH="$HOME/.nix-profile/bin:@localstatedir@/nix/profiles/default/bin:$PATH" +export PATH="$NIX_LINK/bin:@localstatedir@/nix/profiles/default/bin:$PATH" +unset NIX_LINK diff --git a/scripts/nix-profile.sh.in b/scripts/nix-profile.sh.in index 5636085d4..264d9a8e2 100644 --- a/scripts/nix-profile.sh.in +++ b/scripts/nix-profile.sh.in @@ -2,11 +2,35 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then # Set up the per-user profile. - NIX_LINK=$HOME/.nix-profile + NIX_LINK="$HOME/.nix-profile" + if [ -n "$XDG_STATE_HOME" ]; then + NIX_LINK_NEW="$XDG_STATE_HOME/nix/profile" + else + NIX_LINK_NEW="$HOME/.local/state/nix/profile" + fi + if ! [ -e "$NIX_LINK" ]; then + NIX_LINK="$NIX_LINK_NEW" + else + if [ -t 2 ] && [ -e "$NIX_LINK_NEW" ]; then + warning="\033[1;35mwarning:\033[0m" + printf "$warning Both %s and legacy %s exist; using the latter.\n" "$NIX_LINK_NEW" "$NIX_LINK" 1>&2 + if [ "$(realpath "$NIX_LINK")" = "$(realpath "$NIX_LINK_NEW")" ]; then + printf " Since the profiles match, you can safely delete either of them.\n" 1>&2 + else + # This should be an exceptionally rare occasion: the only way to get it would be to + # 1. Update to newer Nix; + # 2. Remove .nix-profile; + # 3. Set the $NIX_LINK_NEW to something other than the default user profile; + # 4. Roll back to older Nix. + # If someone did all that, they can probably figure out how to migrate the profile. + printf "$warning Profiles do not match. You should manually migrate from %s to %s.\n" "$NIX_LINK" "$NIX_LINK_NEW" 1>&2 + fi + fi + fi # Set up environment. # This part should be kept in sync with nixpkgs:nixos/modules/programs/environment.nix - export NIX_PROFILES="@localstatedir@/nix/profiles/default $HOME/.nix-profile" + export NIX_PROFILES="@localstatedir@/nix/profiles/default $NIX_LINK" # Set $NIX_SSL_CERT_FILE so that Nixpkgs applications like curl work. if [ -e /etc/ssl/certs/ca-certificates.crt ]; then # NixOS, Ubuntu, Debian, Gentoo, Arch @@ -31,5 +55,5 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then fi export PATH="$NIX_LINK/bin:$PATH" - unset NIX_LINK + unset NIX_LINK NIX_LINK_NEW fi diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc index 6b81ecc49..174435e7c 100644 --- a/src/build-remote/build-remote.cc +++ b/src/build-remote/build-remote.cc @@ -72,6 +72,7 @@ static int main_build_remote(int argc, char * * argv) settings.set(name, value); } + auto maxBuildJobs = settings.maxBuildJobs; settings.maxBuildJobs.set("1"); // hack to make tests with local?root= work initPlugins(); @@ -112,10 +113,14 @@ static int main_build_remote(int argc, char * * argv) drvPath = store->parseStorePath(readString(source)); auto requiredFeatures = readStrings<std::set<std::string>>(source); - auto canBuildLocally = amWilling + /* It would be possible to build locally after some builds clear out, + so don't show the warning now: */ + bool couldBuildLocally = maxBuildJobs > 0 && ( neededSystem == settings.thisSystem || settings.extraPlatforms.get().count(neededSystem) > 0) && allSupportedLocally(*store, requiredFeatures); + /* It's possible to build this locally right now: */ + bool canBuildLocally = amWilling && couldBuildLocally; /* Error ignored here, will be caught later */ mkdir(currentLoad.c_str(), 0777); @@ -214,7 +219,7 @@ static int main_build_remote(int argc, char * * argv) % concatStringsSep<StringSet>(", ", m.supportedFeatures) % concatStringsSep<StringSet>(", ", m.mandatoryFeatures); - printMsg(canBuildLocally ? lvlChatty : lvlWarn, error); + printMsg(couldBuildLocally ? lvlChatty : lvlWarn, error); std::cerr << "# decline\n"; } diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index 0740ea960..ab51c229d 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -4,6 +4,7 @@ #include "derivations.hh" #include "nixexpr.hh" #include "profiles.hh" +#include "repl.hh" #include <nlohmann/json.hpp> @@ -121,12 +122,22 @@ ref<EvalState> EvalCommand::getEvalState() ; if (startReplOnEvalErrors) { - evalState->debugRepl = &runRepl; + evalState->debugRepl = &AbstractNixRepl::runSimple; }; } return ref<EvalState>(evalState); } +MixOperateOnOptions::MixOperateOnOptions() +{ + addFlag({ + .longName = "derivation", + .description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.", + .category = installablesCategory, + .handler = {&operateOn, OperateOn::Derivation}, + }); +} + BuiltPathsCommand::BuiltPathsCommand(bool recursive) : recursive(recursive) { @@ -208,20 +219,6 @@ void StorePathCommand::run(ref<Store> store, std::vector<StorePath> && storePath run(store, *storePaths.begin()); } -Strings editorFor(const Path & file, uint32_t line) -{ - auto editor = getEnv("EDITOR").value_or("cat"); - auto args = tokenizeString<Strings>(editor); - if (line > 0 && ( - editor.find("emacs") != std::string::npos || - editor.find("nano") != std::string::npos || - editor.find("vim") != std::string::npos || - editor.find("kak") != std::string::npos)) - args.push_back(fmt("+%d", line)); - args.push_back(file); - return args; -} - MixProfile::MixProfile() { addFlag({ diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 3b4b40981..b6d554aab 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -94,12 +94,8 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions { std::optional<Path> file; std::optional<std::string> expr; - bool readOnlyMode = false; - // FIXME: move this; not all commands (e.g. 'nix run') use it. - OperateOn operateOn = OperateOn::Output; - - SourceExprCommand(bool supportReadOnlyMode = false); + SourceExprCommand(); std::vector<std::shared_ptr<Installable>> parseInstallables( ref<Store> store, std::vector<std::string> ss); @@ -114,6 +110,11 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions void completeInstallable(std::string_view prefix); }; +struct MixReadOnlyOption : virtual Args +{ + MixReadOnlyOption(); +}; + /* A command that operates on a list of "installables", which can be store paths, attribute paths, Nix expressions, etc. */ struct InstallablesCommand : virtual Args, SourceExprCommand @@ -139,7 +140,7 @@ struct InstallableCommand : virtual Args, SourceExprCommand { std::shared_ptr<Installable> installable; - InstallableCommand(bool supportReadOnlyMode = false); + InstallableCommand(); void prepare() override; @@ -153,8 +154,15 @@ private: std::string _installable{"."}; }; +struct MixOperateOnOptions : virtual Args +{ + OperateOn operateOn = OperateOn::Output; + + MixOperateOnOptions(); +}; + /* A command that operates on zero or more store paths. */ -struct BuiltPathsCommand : public InstallablesCommand +struct BuiltPathsCommand : InstallablesCommand, virtual MixOperateOnOptions { private: @@ -227,10 +235,6 @@ static RegisterCommand registerCommand2(std::vector<std::string> && name) return RegisterCommand(std::move(name), [](){ return make_ref<T>(); }); } -/* Helper function to generate args that invoke $EDITOR on - filename:lineno. */ -Strings editorFor(const Path & file, uint32_t line); - struct MixProfile : virtual StoreCommand { std::optional<Path> profile; @@ -280,8 +284,4 @@ void printClosureDiff( const StorePath & afterPath, std::string_view indent); - -void runRepl( - ref<EvalState> evalState, - const ValMap & extraEnv); } diff --git a/src/libcmd/editor-for.cc b/src/libcmd/editor-for.cc new file mode 100644 index 000000000..f674f32bd --- /dev/null +++ b/src/libcmd/editor-for.cc @@ -0,0 +1,20 @@ +#include "util.hh" +#include "editor-for.hh" + +namespace nix { + +Strings editorFor(const Path & file, uint32_t line) +{ + auto editor = getEnv("EDITOR").value_or("cat"); + auto args = tokenizeString<Strings>(editor); + if (line > 0 && ( + editor.find("emacs") != std::string::npos || + editor.find("nano") != std::string::npos || + editor.find("vim") != std::string::npos || + editor.find("kak") != std::string::npos)) + args.push_back(fmt("+%d", line)); + args.push_back(file); + return args; +} + +} diff --git a/src/libcmd/editor-for.hh b/src/libcmd/editor-for.hh new file mode 100644 index 000000000..8fbd08792 --- /dev/null +++ b/src/libcmd/editor-for.hh @@ -0,0 +1,11 @@ +#pragma once + +#include "types.hh" + +namespace nix { + +/* Helper function to generate args that invoke $EDITOR on + filename:lineno. */ +Strings editorFor(const Path & file, uint32_t line); + +} diff --git a/src/libcmd/installable-attr-path.cc b/src/libcmd/installable-attr-path.cc new file mode 100644 index 000000000..d9377f0d6 --- /dev/null +++ b/src/libcmd/installable-attr-path.cc @@ -0,0 +1,109 @@ +#include "globals.hh" +#include "installable-attr-path.hh" +#include "outputs-spec.hh" +#include "util.hh" +#include "command.hh" +#include "attr-path.hh" +#include "common-eval-args.hh" +#include "derivations.hh" +#include "eval-inline.hh" +#include "eval.hh" +#include "get-drvs.hh" +#include "store-api.hh" +#include "shared.hh" +#include "flake/flake.hh" +#include "eval-cache.hh" +#include "url.hh" +#include "registry.hh" +#include "build-result.hh" + +#include <regex> +#include <queue> + +#include <nlohmann/json.hpp> + +namespace nix { + +InstallableAttrPath::InstallableAttrPath( + ref<EvalState> state, + SourceExprCommand & cmd, + Value * v, + const std::string & attrPath, + ExtendedOutputsSpec extendedOutputsSpec) + : InstallableValue(state) + , cmd(cmd) + , v(allocRootValue(v)) + , attrPath(attrPath) + , extendedOutputsSpec(std::move(extendedOutputsSpec)) +{ } + +std::pair<Value *, PosIdx> InstallableAttrPath::toValue(EvalState & state) +{ + auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v); + state.forceValue(*vRes, pos); + return {vRes, pos}; +} + +DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths() +{ + auto v = toValue(*state).first; + + Bindings & autoArgs = *cmd.getAutoArgs(*state); + + DrvInfos drvInfos; + getDerivations(*state, *v, "", autoArgs, drvInfos, false); + + // Backward compatibility hack: group results by drvPath. This + // helps keep .all output together. + std::map<StorePath, OutputsSpec> byDrvPath; + + for (auto & drvInfo : drvInfos) { + auto drvPath = drvInfo.queryDrvPath(); + if (!drvPath) + throw Error("'%s' is not a derivation", what()); + + auto newOutputs = std::visit(overloaded { + [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec { + std::set<std::string> outputsToInstall; + for (auto & output : drvInfo.queryOutputs(false, true)) + outputsToInstall.insert(output.first); + return OutputsSpec::Names { std::move(outputsToInstall) }; + }, + [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec { + return e; + }, + }, extendedOutputsSpec.raw()); + + auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs); + + if (!didInsert) + iter->second = iter->second.union_(newOutputs); + } + + DerivedPathsWithInfo res; + for (auto & [drvPath, outputs] : byDrvPath) + res.push_back({ + .path = DerivedPath::Built { + .drvPath = drvPath, + .outputs = outputs, + }, + }); + + return res; +} + +InstallableAttrPath InstallableAttrPath::parse( + ref<EvalState> state, + SourceExprCommand & cmd, + Value * v, + std::string_view prefix, + ExtendedOutputsSpec extendedOutputsSpec) +{ + return { + state, cmd, v, + prefix == "." ? "" : std::string { prefix }, + extendedOutputsSpec + }; +} + +} diff --git a/src/libcmd/installable-attr-path.hh b/src/libcmd/installable-attr-path.hh new file mode 100644 index 000000000..c06132ec8 --- /dev/null +++ b/src/libcmd/installable-attr-path.hh @@ -0,0 +1,56 @@ +#include "globals.hh" +#include "installable-value.hh" +#include "outputs-spec.hh" +#include "util.hh" +#include "command.hh" +#include "attr-path.hh" +#include "common-eval-args.hh" +#include "derivations.hh" +#include "eval-inline.hh" +#include "eval.hh" +#include "get-drvs.hh" +#include "store-api.hh" +#include "shared.hh" +#include "eval-cache.hh" +#include "url.hh" +#include "registry.hh" +#include "build-result.hh" + +#include <regex> +#include <queue> + +#include <nlohmann/json.hpp> + +namespace nix { + +class InstallableAttrPath : public InstallableValue +{ + SourceExprCommand & cmd; + RootValue v; + std::string attrPath; + ExtendedOutputsSpec extendedOutputsSpec; + + InstallableAttrPath( + ref<EvalState> state, + SourceExprCommand & cmd, + Value * v, + const std::string & attrPath, + ExtendedOutputsSpec extendedOutputsSpec); + + std::string what() const override { return attrPath; }; + + std::pair<Value *, PosIdx> toValue(EvalState & state) override; + + DerivedPathsWithInfo toDerivedPaths() override; + +public: + + static InstallableAttrPath parse( + ref<EvalState> state, + SourceExprCommand & cmd, + Value * v, + std::string_view prefix, + ExtendedOutputsSpec extendedOutputsSpec); +}; + +} diff --git a/src/libcmd/installable-derived-path.cc b/src/libcmd/installable-derived-path.cc new file mode 100644 index 000000000..a9921b901 --- /dev/null +++ b/src/libcmd/installable-derived-path.cc @@ -0,0 +1,70 @@ +#include "installable-derived-path.hh" +#include "derivations.hh" + +namespace nix { + +std::string InstallableDerivedPath::what() const +{ + return derivedPath.to_string(*store); +} + +DerivedPathsWithInfo InstallableDerivedPath::toDerivedPaths() +{ + return {{.path = derivedPath, .info = {} }}; +} + +std::optional<StorePath> InstallableDerivedPath::getStorePath() +{ + return std::visit(overloaded { + [&](const DerivedPath::Built & bfd) { + return bfd.drvPath; + }, + [&](const DerivedPath::Opaque & bo) { + return bo.path; + }, + }, derivedPath.raw()); +} + +InstallableDerivedPath InstallableDerivedPath::parse( + ref<Store> store, + std::string_view prefix, + ExtendedOutputsSpec extendedOutputsSpec) +{ + auto derivedPath = std::visit(overloaded { + // If the user did not use ^, we treat the output more liberally. + [&](const ExtendedOutputsSpec::Default &) -> DerivedPath { + // First, we accept a symlink chain or an actual store path. + auto storePath = store->followLinksToStorePath(prefix); + // Second, we see if the store path ends in `.drv` to decide what sort + // of derived path they want. + // + // This handling predates the `^` syntax. The `^*` in + // `/nix/store/hash-foo.drv^*` unambiguously means "do the + // `DerivedPath::Built` case", so plain `/nix/store/hash-foo.drv` could + // also unambiguously mean "do the DerivedPath::Opaque` case". + // + // Issue #7261 tracks reconsidering this `.drv` dispatching. + return storePath.isDerivation() + ? (DerivedPath) DerivedPath::Built { + .drvPath = std::move(storePath), + .outputs = OutputsSpec::All {}, + } + : (DerivedPath) DerivedPath::Opaque { + .path = std::move(storePath), + }; + }, + // If the user did use ^, we just do exactly what is written. + [&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath { + return DerivedPath::Built { + .drvPath = store->parseStorePath(prefix), + .outputs = outputSpec, + }; + }, + }, extendedOutputsSpec.raw()); + return InstallableDerivedPath { + store, + std::move(derivedPath), + }; +} + +} diff --git a/src/libcmd/installable-derived-path.hh b/src/libcmd/installable-derived-path.hh new file mode 100644 index 000000000..042878b91 --- /dev/null +++ b/src/libcmd/installable-derived-path.hh @@ -0,0 +1,28 @@ +#pragma once + +#include "installables.hh" + +namespace nix { + +struct InstallableDerivedPath : Installable +{ + ref<Store> store; + DerivedPath derivedPath; + + InstallableDerivedPath(ref<Store> store, DerivedPath && derivedPath) + : store(store), derivedPath(std::move(derivedPath)) + { } + + std::string what() const override; + + DerivedPathsWithInfo toDerivedPaths() override; + + std::optional<StorePath> getStorePath() override; + + static InstallableDerivedPath parse( + ref<Store> store, + std::string_view prefix, + ExtendedOutputsSpec extendedOutputsSpec); +}; + +} diff --git a/src/libcmd/installable-flake.cc b/src/libcmd/installable-flake.cc new file mode 100644 index 000000000..60a97deaf --- /dev/null +++ b/src/libcmd/installable-flake.cc @@ -0,0 +1,236 @@ +#include "globals.hh" +#include "installable-flake.hh" +#include "installable-derived-path.hh" +#include "outputs-spec.hh" +#include "util.hh" +#include "command.hh" +#include "attr-path.hh" +#include "common-eval-args.hh" +#include "derivations.hh" +#include "eval-inline.hh" +#include "eval.hh" +#include "get-drvs.hh" +#include "store-api.hh" +#include "shared.hh" +#include "flake/flake.hh" +#include "eval-cache.hh" +#include "url.hh" +#include "registry.hh" +#include "build-result.hh" + +#include <regex> +#include <queue> + +#include <nlohmann/json.hpp> + +namespace nix { + +std::vector<std::string> InstallableFlake::getActualAttrPaths() +{ + std::vector<std::string> res; + + for (auto & prefix : prefixes) + res.push_back(prefix + *attrPaths.begin()); + + for (auto & s : attrPaths) + res.push_back(s); + + return res; +} + +Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake) +{ + auto vFlake = state.allocValue(); + + callFlake(state, lockedFlake, *vFlake); + + auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); + assert(aOutputs); + + state.forceValue(*aOutputs->value, [&]() { return aOutputs->value->determinePos(noPos); }); + + return aOutputs->value; +} + +static std::string showAttrPaths(const std::vector<std::string> & paths) +{ + std::string s; + for (const auto & [n, i] : enumerate(paths)) { + if (n > 0) s += n + 1 == paths.size() ? " or " : ", "; + s += '\''; s += i; s += '\''; + } + return s; +} + +InstallableFlake::InstallableFlake( + SourceExprCommand * cmd, + ref<EvalState> state, + FlakeRef && flakeRef, + std::string_view fragment, + ExtendedOutputsSpec extendedOutputsSpec, + Strings attrPaths, + Strings prefixes, + const flake::LockFlags & lockFlags) + : InstallableValue(state), + flakeRef(flakeRef), + attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}), + prefixes(fragment == "" ? Strings{} : prefixes), + extendedOutputsSpec(std::move(extendedOutputsSpec)), + lockFlags(lockFlags) +{ + if (cmd && cmd->getAutoArgs(*state)->size()) + throw UsageError("'--arg' and '--argstr' are incompatible with flakes"); +} + +DerivedPathsWithInfo InstallableFlake::toDerivedPaths() +{ + Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what())); + + auto attr = getCursor(*state); + + auto attrPath = attr->getAttrPathStr(); + + if (!attr->isDerivation()) { + + // FIXME: use eval cache? + auto v = attr->forceValue(); + + if (v.type() == nPath) { + PathSet context; + auto storePath = state->copyPathToStore(context, Path(v.path)); + return {{ + .path = DerivedPath::Opaque { + .path = std::move(storePath), + } + }}; + } + + else if (v.type() == nString) { + PathSet context; + auto s = state->forceString(v, context, noPos, fmt("while evaluating the flake output attribute '%s'", attrPath)); + auto storePath = state->store->maybeParseStorePath(s); + if (storePath && context.count(std::string(s))) { + return {{ + .path = DerivedPath::Opaque { + .path = std::move(*storePath), + } + }}; + } else + throw Error("flake output attribute '%s' evaluates to the string '%s' which is not a store path", attrPath, s); + } + + else + throw Error("flake output attribute '%s' is not a derivation or path", attrPath); + } + + auto drvPath = attr->forceDerivation(); + + std::optional<NixInt> priority; + + if (attr->maybeGetAttr(state->sOutputSpecified)) { + } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) { + if (auto aPriority = aMeta->maybeGetAttr("priority")) + priority = aPriority->getInt(); + } + + return {{ + .path = DerivedPath::Built { + .drvPath = std::move(drvPath), + .outputs = std::visit(overloaded { + [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec { + std::set<std::string> outputsToInstall; + if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) { + if (aOutputSpecified->getBool()) { + if (auto aOutputName = attr->maybeGetAttr("outputName")) + outputsToInstall = { aOutputName->getString() }; + } + } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) { + if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall")) + for (auto & s : aOutputsToInstall->getListOfStrings()) + outputsToInstall.insert(s); + } + + if (outputsToInstall.empty()) + outputsToInstall.insert("out"); + + return OutputsSpec::Names { std::move(outputsToInstall) }; + }, + [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec { + return e; + }, + }, extendedOutputsSpec.raw()), + }, + .info = { + .priority = priority, + .originalRef = flakeRef, + .resolvedRef = getLockedFlake()->flake.lockedRef, + .attrPath = attrPath, + .extendedOutputsSpec = extendedOutputsSpec, + } + }}; +} + +std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state) +{ + return {&getCursor(state)->forceValue(), noPos}; +} + +std::vector<ref<eval_cache::AttrCursor>> +InstallableFlake::getCursors(EvalState & state) +{ + auto evalCache = openEvalCache(state, + std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags))); + + auto root = evalCache->getRoot(); + + std::vector<ref<eval_cache::AttrCursor>> res; + + Suggestions suggestions; + auto attrPaths = getActualAttrPaths(); + + for (auto & attrPath : attrPaths) { + debug("trying flake output attribute '%s'", attrPath); + + auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath)); + if (attr) { + res.push_back(ref(*attr)); + } else { + suggestions += attr.getSuggestions(); + } + } + + if (res.size() == 0) + throw Error( + suggestions, + "flake '%s' does not provide attribute %s", + flakeRef, + showAttrPaths(attrPaths)); + + return res; +} + +std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const +{ + if (!_lockedFlake) { + flake::LockFlags lockFlagsApplyConfig = lockFlags; + lockFlagsApplyConfig.applyNixConfig = true; + _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig)); + } + return _lockedFlake; +} + +FlakeRef InstallableFlake::nixpkgsFlakeRef() const +{ + auto lockedFlake = getLockedFlake(); + + if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) { + if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) { + debug("using nixpkgs flake '%s'", lockedNode->lockedRef); + return std::move(lockedNode->lockedRef); + } + } + + return Installable::nixpkgsFlakeRef(); +} + +} diff --git a/src/libcmd/installable-flake.hh b/src/libcmd/installable-flake.hh new file mode 100644 index 000000000..c75765086 --- /dev/null +++ b/src/libcmd/installable-flake.hh @@ -0,0 +1,50 @@ +#pragma once + +#include "installable-value.hh" + +namespace nix { + +struct InstallableFlake : InstallableValue +{ + FlakeRef flakeRef; + Strings attrPaths; + Strings prefixes; + ExtendedOutputsSpec extendedOutputsSpec; + const flake::LockFlags & lockFlags; + mutable std::shared_ptr<flake::LockedFlake> _lockedFlake; + + InstallableFlake( + SourceExprCommand * cmd, + ref<EvalState> state, + FlakeRef && flakeRef, + std::string_view fragment, + ExtendedOutputsSpec extendedOutputsSpec, + Strings attrPaths, + Strings prefixes, + const flake::LockFlags & lockFlags); + + std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); } + + std::vector<std::string> getActualAttrPaths(); + + Value * getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake); + + DerivedPathsWithInfo toDerivedPaths() override; + + std::pair<Value *, PosIdx> toValue(EvalState & state) override; + + /* Get a cursor to every attrpath in getActualAttrPaths() + that exists. However if none exists, throw an exception. */ + std::vector<ref<eval_cache::AttrCursor>> + getCursors(EvalState & state) override; + + std::shared_ptr<flake::LockedFlake> getLockedFlake() const; + + FlakeRef nixpkgsFlakeRef() const override; +}; + +ref<eval_cache::EvalCache> openEvalCache( + EvalState & state, + std::shared_ptr<flake::LockedFlake> lockedFlake); + +} diff --git a/src/libcmd/installable-value.hh b/src/libcmd/installable-value.hh new file mode 100644 index 000000000..c6cdc4797 --- /dev/null +++ b/src/libcmd/installable-value.hh @@ -0,0 +1,14 @@ +#pragma once + +#include "installables.hh" + +namespace nix { + +struct InstallableValue : Installable +{ + ref<EvalState> state; + + InstallableValue(ref<EvalState> state) : state(state) {} +}; + +} diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 5090ea6d2..00c6f9516 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -1,5 +1,8 @@ #include "globals.hh" #include "installables.hh" +#include "installable-derived-path.hh" +#include "installable-attr-path.hh" +#include "installable-flake.hh" #include "outputs-spec.hh" #include "util.hh" #include "command.hh" @@ -144,7 +147,7 @@ void MixFlakeOptions::completionHook() completeFlakeInput(*prefix); } -SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode) +SourceExprCommand::SourceExprCommand() { addFlag({ .longName = "file", @@ -166,24 +169,18 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode) .labels = {"expr"}, .handler = {&expr} }); +} +MixReadOnlyOption::MixReadOnlyOption() +{ addFlag({ - .longName = "derivation", - .description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.", - .category = installablesCategory, - .handler = {&operateOn, OperateOn::Derivation}, + .longName = "read-only", + .description = + "Do not instantiate each evaluated derivation. " + "This improves performance, but can cause errors when accessing " + "store paths of derivations during evaluation.", + .handler = {&settings.readOnlyMode, true}, }); - - if (supportReadOnlyMode) { - addFlag({ - .longName = "read-only", - .description = - "Do not instantiate each evaluated derivation. " - "This improves performance, but can cause errors when accessing " - "store paths of derivations during evaluation.", - .handler = {&readOnlyMode, true}, - }); - } } Strings SourceExprCommand::getDefaultFlakeAttrPaths() @@ -379,10 +376,9 @@ Installable::getCursors(EvalState & state) ref<eval_cache::AttrCursor> Installable::getCursor(EvalState & state) { - auto cursors = getCursors(state); - if (cursors.empty()) - throw Error("cannot find flake attribute '%s'", what()); - return cursors[0]; + /* Although getCursors should return at least one element, in case it doesn't, + bound check to avoid an undefined behavior for vector[0] */ + return getCursors(state).at(0); } static StorePath getDeriver( @@ -397,143 +393,6 @@ static StorePath getDeriver( return *derivers.begin(); } -struct InstallableStorePath : Installable -{ - ref<Store> store; - DerivedPath req; - - InstallableStorePath(ref<Store> store, DerivedPath && req) - : store(store), req(std::move(req)) - { } - - std::string what() const override - { - return req.to_string(*store); - } - - DerivedPathsWithInfo toDerivedPaths() override - { - return {{.path = req, .info = {} }}; - } - - std::optional<StorePath> getStorePath() override - { - return std::visit(overloaded { - [&](const DerivedPath::Built & bfd) { - return bfd.drvPath; - }, - [&](const DerivedPath::Opaque & bo) { - return bo.path; - }, - }, req.raw()); - } -}; - -struct InstallableAttrPath : InstallableValue -{ - SourceExprCommand & cmd; - RootValue v; - std::string attrPath; - ExtendedOutputsSpec extendedOutputsSpec; - - InstallableAttrPath( - ref<EvalState> state, - SourceExprCommand & cmd, - Value * v, - const std::string & attrPath, - ExtendedOutputsSpec extendedOutputsSpec) - : InstallableValue(state) - , cmd(cmd) - , v(allocRootValue(v)) - , attrPath(attrPath) - , extendedOutputsSpec(std::move(extendedOutputsSpec)) - { } - - std::string what() const override { return attrPath; } - - std::pair<Value *, PosIdx> toValue(EvalState & state) override - { - auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v); - state.forceValue(*vRes, pos); - return {vRes, pos}; - } - - DerivedPathsWithInfo toDerivedPaths() override - { - auto v = toValue(*state).first; - - Bindings & autoArgs = *cmd.getAutoArgs(*state); - - DrvInfos drvInfos; - getDerivations(*state, *v, "", autoArgs, drvInfos, false); - - // Backward compatibility hack: group results by drvPath. This - // helps keep .all output together. - std::map<StorePath, OutputsSpec> byDrvPath; - - for (auto & drvInfo : drvInfos) { - auto drvPath = drvInfo.queryDrvPath(); - if (!drvPath) - throw Error("'%s' is not a derivation", what()); - - auto newOutputs = std::visit(overloaded { - [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec { - std::set<std::string> outputsToInstall; - for (auto & output : drvInfo.queryOutputs(false, true)) - outputsToInstall.insert(output.first); - return OutputsSpec::Names { std::move(outputsToInstall) }; - }, - [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec { - return e; - }, - }, extendedOutputsSpec.raw()); - - auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs); - - if (!didInsert) - iter->second = iter->second.union_(newOutputs); - } - - DerivedPathsWithInfo res; - for (auto & [drvPath, outputs] : byDrvPath) - res.push_back({ - .path = DerivedPath::Built { - .drvPath = drvPath, - .outputs = outputs, - }, - }); - - return res; - } -}; - -std::vector<std::string> InstallableFlake::getActualAttrPaths() -{ - std::vector<std::string> res; - - for (auto & prefix : prefixes) - res.push_back(prefix + *attrPaths.begin()); - - for (auto & s : attrPaths) - res.push_back(s); - - return res; -} - -Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake) -{ - auto vFlake = state.allocValue(); - - callFlake(state, lockedFlake, *vFlake); - - auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); - assert(aOutputs); - - state.forceValue(*aOutputs->value, [&]() { return aOutputs->value->determinePos(noPos); }); - - return aOutputs->value; -} - ref<eval_cache::EvalCache> openEvalCache( EvalState & state, std::shared_ptr<flake::LockedFlake> lockedFlake) @@ -563,214 +422,11 @@ ref<eval_cache::EvalCache> openEvalCache( }); } -static std::string showAttrPaths(const std::vector<std::string> & paths) -{ - std::string s; - for (const auto & [n, i] : enumerate(paths)) { - if (n > 0) s += n + 1 == paths.size() ? " or " : ", "; - s += '\''; s += i; s += '\''; - } - return s; -} - -InstallableFlake::InstallableFlake( - SourceExprCommand * cmd, - ref<EvalState> state, - FlakeRef && flakeRef, - std::string_view fragment, - ExtendedOutputsSpec extendedOutputsSpec, - Strings attrPaths, - Strings prefixes, - const flake::LockFlags & lockFlags) - : InstallableValue(state), - flakeRef(flakeRef), - attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}), - prefixes(fragment == "" ? Strings{} : prefixes), - extendedOutputsSpec(std::move(extendedOutputsSpec)), - lockFlags(lockFlags) -{ - if (cmd && cmd->getAutoArgs(*state)->size()) - throw UsageError("'--arg' and '--argstr' are incompatible with flakes"); -} - -DerivedPathsWithInfo InstallableFlake::toDerivedPaths() -{ - Activity act(*logger, lvlTalkative, actUnknown, fmt("evaluating derivation '%s'", what())); - - auto attr = getCursor(*state); - - auto attrPath = attr->getAttrPathStr(); - - if (!attr->isDerivation()) { - - // FIXME: use eval cache? - auto v = attr->forceValue(); - - if (v.type() == nPath) { - PathSet context; - auto storePath = state->copyPathToStore(context, Path(v.path)); - return {{ - .path = DerivedPath::Opaque { - .path = std::move(storePath), - } - }}; - } - - else if (v.type() == nString) { - PathSet context; - auto s = state->forceString(v, context, noPos, fmt("while evaluating the flake output attribute '%s'", attrPath)); - auto storePath = state->store->maybeParseStorePath(s); - if (storePath && context.count(std::string(s))) { - return {{ - .path = DerivedPath::Opaque { - .path = std::move(*storePath), - } - }}; - } else - throw Error("flake output attribute '%s' evaluates to the string '%s' which is not a store path", attrPath, s); - } - - else - throw Error("flake output attribute '%s' is not a derivation or path", attrPath); - } - - auto drvPath = attr->forceDerivation(); - - std::optional<NixInt> priority; - - if (attr->maybeGetAttr(state->sOutputSpecified)) { - } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) { - if (auto aPriority = aMeta->maybeGetAttr("priority")) - priority = aPriority->getInt(); - } - - return {{ - .path = DerivedPath::Built { - .drvPath = std::move(drvPath), - .outputs = std::visit(overloaded { - [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec { - std::set<std::string> outputsToInstall; - if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) { - if (aOutputSpecified->getBool()) { - if (auto aOutputName = attr->maybeGetAttr("outputName")) - outputsToInstall = { aOutputName->getString() }; - } - } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) { - if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall")) - for (auto & s : aOutputsToInstall->getListOfStrings()) - outputsToInstall.insert(s); - } - - if (outputsToInstall.empty()) - outputsToInstall.insert("out"); - - return OutputsSpec::Names { std::move(outputsToInstall) }; - }, - [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec { - return e; - }, - }, extendedOutputsSpec.raw()), - }, - .info = { - .priority = priority, - .originalRef = flakeRef, - .resolvedRef = getLockedFlake()->flake.lockedRef, - .attrPath = attrPath, - .extendedOutputsSpec = extendedOutputsSpec, - } - }}; -} - -std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state) -{ - return {&getCursor(state)->forceValue(), noPos}; -} - -std::vector<ref<eval_cache::AttrCursor>> -InstallableFlake::getCursors(EvalState & state) -{ - auto evalCache = openEvalCache(state, - std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags))); - - auto root = evalCache->getRoot(); - - std::vector<ref<eval_cache::AttrCursor>> res; - - for (auto & attrPath : getActualAttrPaths()) { - auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath)); - if (attr) res.push_back(ref(*attr)); - } - - return res; -} - -ref<eval_cache::AttrCursor> InstallableFlake::getCursor(EvalState & state) -{ - auto lockedFlake = getLockedFlake(); - - auto cache = openEvalCache(state, lockedFlake); - auto root = cache->getRoot(); - - Suggestions suggestions; - - auto attrPaths = getActualAttrPaths(); - - for (auto & attrPath : attrPaths) { - debug("trying flake output attribute '%s'", attrPath); - - auto attrOrSuggestions = root->findAlongAttrPath( - parseAttrPath(state, attrPath), - true - ); - - if (!attrOrSuggestions) { - suggestions += attrOrSuggestions.getSuggestions(); - continue; - } - - return *attrOrSuggestions; - } - - throw Error( - suggestions, - "flake '%s' does not provide attribute %s", - flakeRef, - showAttrPaths(attrPaths)); -} - -std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const -{ - if (!_lockedFlake) { - flake::LockFlags lockFlagsApplyConfig = lockFlags; - lockFlagsApplyConfig.applyNixConfig = true; - _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig)); - } - return _lockedFlake; -} - -FlakeRef InstallableFlake::nixpkgsFlakeRef() const -{ - auto lockedFlake = getLockedFlake(); - - if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) { - if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) { - debug("using nixpkgs flake '%s'", lockedNode->lockedRef); - return std::move(lockedNode->lockedRef); - } - } - - return Installable::nixpkgsFlakeRef(); -} - std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables( ref<Store> store, std::vector<std::string> ss) { std::vector<std::shared_ptr<Installable>> result; - if (readOnlyMode) { - settings.readOnlyMode = true; - } - if (file || expr) { if (file && expr) throw UsageError("'--file' and '--expr' are exclusive"); @@ -796,9 +452,8 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables( auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s); result.push_back( std::make_shared<InstallableAttrPath>( - state, *this, vFile, - prefix == "." ? "" : std::string { prefix }, - extendedOutputsSpec)); + InstallableAttrPath::parse( + state, *this, vFile, prefix, extendedOutputsSpec))); } } else { @@ -811,41 +466,10 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables( auto prefix = std::move(prefix_); auto extendedOutputsSpec = std::move(extendedOutputsSpec_); - auto found = prefix.find('/'); - if (found != std::string::npos) { + if (prefix.find('/') != std::string::npos) { try { - auto derivedPath = std::visit(overloaded { - // If the user did not use ^, we treat the output more liberally. - [&](const ExtendedOutputsSpec::Default &) -> DerivedPath { - // First, we accept a symlink chain or an actual store path. - auto storePath = store->followLinksToStorePath(prefix); - // Second, we see if the store path ends in `.drv` to decide what sort - // of derived path they want. - // - // This handling predates the `^` syntax. The `^*` in - // `/nix/store/hash-foo.drv^*` unambiguously means "do the - // `DerivedPath::Built` case", so plain `/nix/store/hash-foo.drv` could - // also unambiguously mean "do the DerivedPath::Opaque` case". - // - // Issue #7261 tracks reconsidering this `.drv` dispatching. - return storePath.isDerivation() - ? (DerivedPath) DerivedPath::Built { - .drvPath = std::move(storePath), - .outputs = OutputsSpec::All {}, - } - : (DerivedPath) DerivedPath::Opaque { - .path = std::move(storePath), - }; - }, - // If the user did use ^, we just do exactly what is written. - [&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath { - return DerivedPath::Built { - .drvPath = store->parseStorePath(prefix), - .outputs = outputSpec, - }; - }, - }, extendedOutputsSpec.raw()); - result.push_back(std::make_shared<InstallableStorePath>(store, std::move(derivedPath))); + result.push_back(std::make_shared<InstallableDerivedPath>( + InstallableDerivedPath::parse(store, prefix, extendedOutputsSpec))); continue; } catch (BadStorePath &) { } catch (...) { @@ -1081,8 +705,8 @@ void InstallablesCommand::prepare() installables = load(); } -Installables InstallablesCommand::load() { - Installables installables; +Installables InstallablesCommand::load() +{ if (_installables.empty() && useDefaultInstallables()) // FIXME: commands like "nix profile install" should not have a // default, probably. @@ -1092,16 +716,13 @@ Installables InstallablesCommand::load() { std::vector<std::string> InstallablesCommand::getFlakesForCompletion() { - if (_installables.empty()) { - if (useDefaultInstallables()) - return {"."}; - return {}; - } + if (_installables.empty() && useDefaultInstallables()) + return {"."}; return _installables; } -InstallableCommand::InstallableCommand(bool supportReadOnlyMode) - : SourceExprCommand(supportReadOnlyMode) +InstallableCommand::InstallableCommand() + : SourceExprCommand() { expectArgs({ .label = "installable", diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index 3d12639b0..be77fdc81 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -103,9 +103,13 @@ struct Installable return {}; } + /* Get a cursor to each value this Installable could refer to. However + if none exists, throw exception instead of returning empty vector. */ virtual std::vector<ref<eval_cache::AttrCursor>> getCursors(EvalState & state); + /* Get the first and most preferred cursor this Installable could refer + to, or throw an exception if none exists. */ virtual ref<eval_cache::AttrCursor> getCursor(EvalState & state); @@ -157,58 +161,4 @@ struct Installable typedef std::vector<std::shared_ptr<Installable>> Installables; -struct InstallableValue : Installable -{ - ref<EvalState> state; - - InstallableValue(ref<EvalState> state) : state(state) {} -}; - -struct InstallableFlake : InstallableValue -{ - FlakeRef flakeRef; - Strings attrPaths; - Strings prefixes; - ExtendedOutputsSpec extendedOutputsSpec; - const flake::LockFlags & lockFlags; - mutable std::shared_ptr<flake::LockedFlake> _lockedFlake; - - InstallableFlake( - SourceExprCommand * cmd, - ref<EvalState> state, - FlakeRef && flakeRef, - std::string_view fragment, - ExtendedOutputsSpec extendedOutputsSpec, - Strings attrPaths, - Strings prefixes, - const flake::LockFlags & lockFlags); - - std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); } - - std::vector<std::string> getActualAttrPaths(); - - Value * getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake); - - DerivedPathsWithInfo toDerivedPaths() override; - - std::pair<Value *, PosIdx> toValue(EvalState & state) override; - - /* Get a cursor to every attrpath in getActualAttrPaths() that - exists. */ - std::vector<ref<eval_cache::AttrCursor>> - getCursors(EvalState & state) override; - - /* Get a cursor to the first attrpath in getActualAttrPaths() that - exists, or throw an exception with suggestions if none exists. */ - ref<eval_cache::AttrCursor> getCursor(EvalState & state) override; - - std::shared_ptr<flake::LockedFlake> getLockedFlake() const; - - FlakeRef nixpkgsFlakeRef() const override; -}; - -ref<eval_cache::EvalCache> openEvalCache( - EvalState & state, - std::shared_ptr<flake::LockedFlake> lockedFlake); - } diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk index 152bc388d..541a7d2ba 100644 --- a/src/libcmd/local.mk +++ b/src/libcmd/local.mk @@ -6,7 +6,7 @@ libcmd_DIR := $(d) libcmd_SOURCES := $(wildcard $(d)/*.cc) -libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -I src/nix +libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) -pthread diff --git a/src/libcmd/nix-cmd.pc.in b/src/libcmd/nix-cmd.pc.in index 1761a9f41..39575f222 100644 --- a/src/libcmd/nix-cmd.pc.in +++ b/src/libcmd/nix-cmd.pc.in @@ -6,4 +6,4 @@ Name: Nix Description: Nix Package Manager Version: @PACKAGE_VERSION@ Libs: -L${libdir} -lnixcmd -Cflags: -I${includedir}/nix -std=c++17 +Cflags: -I${includedir}/nix -std=c++2a diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index 4158439b6..e3afb1531 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -19,6 +19,8 @@ extern "C" { } #endif +#include "repl.hh" + #include "ansicolor.hh" #include "shared.hh" #include "eval.hh" @@ -31,7 +33,9 @@ extern "C" { #include "get-drvs.hh" #include "derivations.hh" #include "globals.hh" -#include "command.hh" +#include "flake/flake.hh" +#include "flake/lockfile.hh" +#include "editor-for.hh" #include "finally.hh" #include "markdown.hh" #include "local-fs-store.hh" @@ -45,18 +49,16 @@ extern "C" { namespace nix { struct NixRepl + : AbstractNixRepl #if HAVE_BOEHMGC - : gc + , gc #endif { std::string curDir; - ref<EvalState> state; - Bindings * autoArgs; size_t debugTraceIndex; Strings loadedFiles; - typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues; std::function<AnnotatedValues()> getValues; const static int envSize = 32768; @@ -69,8 +71,11 @@ struct NixRepl NixRepl(const Strings & searchPath, nix::ref<Store> store,ref<EvalState> state, std::function<AnnotatedValues()> getValues); - ~NixRepl(); - void mainLoop(); + virtual ~NixRepl(); + + void mainLoop() override; + void initEnv() override; + StringSet completePrefix(const std::string & prefix); bool getLine(std::string & input, const std::string & prompt); StorePath getDerivationPath(Value & v); @@ -78,7 +83,6 @@ struct NixRepl void loadFile(const Path & path); void loadFlake(const std::string & flakeRef); - void initEnv(); void loadFiles(); void reloadFiles(); void addAttrsToScope(Value & attrs); @@ -92,7 +96,6 @@ struct NixRepl std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen); }; - std::string removeWhitespace(std::string s) { s = chomp(s); @@ -104,7 +107,7 @@ std::string removeWhitespace(std::string s) NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state, std::function<NixRepl::AnnotatedValues()> getValues) - : state(state) + : AbstractNixRepl(state) , debugTraceIndex(0) , getValues(getValues) , staticEnv(new StaticEnv(false, state->staticBaseEnv.get())) @@ -1029,8 +1032,22 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m return str; } -void runRepl( - ref<EvalState>evalState, + +std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create( + const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state, + std::function<AnnotatedValues()> getValues) +{ + return std::make_unique<NixRepl>( + searchPath, + openStore(), + state, + getValues + ); +} + + +void AbstractNixRepl::runSimple( + ref<EvalState> evalState, const ValMap & extraEnv) { auto getValues = [&]()->NixRepl::AnnotatedValues{ @@ -1054,91 +1071,4 @@ void runRepl( repl->mainLoop(); } -struct CmdRepl : InstallablesCommand -{ - CmdRepl() { - evalSettings.pureEval = false; - } - - 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"); - if (this->_installables.size() > 1) - warn("more than one input file is not currently supported"); - auto filePath = this->_installables[0].data(); - file = std::optional(filePath); - _installables.front() = _installables.back(); - _installables.pop_back(); - } - installables = InstallablesCommand::load(); - } - - std::vector<std::string> files; - - Strings getDefaultFlakeAttrPaths() override - { - return {""}; - } - - bool useDefaultInstallables() override - { - return file.has_value() or expr.has_value(); - } - - bool forceImpureByDefault() override - { - return true; - } - - std::string description() override - { - return "start an interactive environment for evaluating Nix expressions"; - } - - std::string doc() override - { - return - #include "repl.md" - ; - } - - void run(ref<Store> store) override - { - auto state = getEvalState(); - auto getValues = [&]()->NixRepl::AnnotatedValues{ - auto installables = load(); - NixRepl::AnnotatedValues values; - for (auto & installable: installables){ - auto what = installable->what(); - if (file){ - auto [val, pos] = installable->toValue(*state); - auto what = installable->what(); - state->forceValue(*val, pos); - auto autoArgs = getAutoArgs(*state); - auto valPost = state->allocValue(); - state->autoCallFunction(*autoArgs, *val, *valPost); - state->forceValue(*valPost, pos); - values.push_back( {valPost, what }); - } else { - auto [val, pos] = installable->toValue(*state); - values.push_back( {val, what} ); - } - } - return values; - }; - auto repl = std::make_unique<NixRepl>( - searchPath, - openStore(), - state, - getValues - ); - repl->autoArgs = getAutoArgs(*repl->state); - repl->initEnv(); - repl->mainLoop(); - } -}; - -static auto rCmdRepl = registerCommand<CmdRepl>("repl"); - } diff --git a/src/libcmd/repl.hh b/src/libcmd/repl.hh new file mode 100644 index 000000000..dfccc93e7 --- /dev/null +++ b/src/libcmd/repl.hh @@ -0,0 +1,39 @@ +#pragma once + +#include "eval.hh" + +#if HAVE_BOEHMGC +#define GC_INCLUDE_NEW +#include <gc/gc_cpp.h> +#endif + +namespace nix { + +struct AbstractNixRepl +{ + ref<EvalState> state; + Bindings * autoArgs; + + AbstractNixRepl(ref<EvalState> state) + : state(state) + { } + + virtual ~AbstractNixRepl() + { } + + typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues; + + static std::unique_ptr<AbstractNixRepl> create( + const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state, + std::function<AnnotatedValues()> getValues); + + static void runSimple( + ref<EvalState> evalState, + const ValMap & extraEnv); + + virtual void initEnv() = 0; + + virtual void mainLoop() = 0; +}; + +} diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 1828b8c2e..21fc4d0fe 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -2490,7 +2490,7 @@ Strings EvalSettings::getDefaultNixPath() }; if (!evalSettings.restrictEval && !evalSettings.pureEval) { - add(getHome() + "/.nix-defexpr/channels"); + add(settings.useXDGBaseDirectories ? getStateDir() + "/nix/defexpr/channels" : getHome() + "/.nix-defexpr/channels"); add(settings.nixStateDir + "/profiles/per-user/root/channels/nixpkgs", "nixpkgs"); add(settings.nixStateDir + "/profiles/per-user/root/channels"); } diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix index 8061db3df..4beb0b0fe 100644 --- a/src/libexpr/flake/call-flake.nix +++ b/src/libexpr/flake/call-flake.nix @@ -16,7 +16,9 @@ let subdir = if key == lockFile.root then rootSubdir else node.locked.dir or ""; - flake = import (sourceInfo + (if subdir != "" then "/" else "") + subdir + "/flake.nix"); + outPath = sourceInfo + ((if subdir == "" then "" else "/") + subdir); + + flake = import (outPath + "/flake.nix"); inputs = builtins.mapAttrs (inputName: inputSpec: allNodes.${resolveInput inputSpec}) @@ -43,7 +45,21 @@ let outputs = flake.outputs (inputs // { self = result; }); - result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; _type = "flake"; }; + result = + outputs + # We add the sourceInfo attribute for its metadata, as they are + # relevant metadata for the flake. However, the outPath of the + # sourceInfo does not necessarily match the outPath of the flake, + # as the flake may be in a subdirectory of a source. + # This is shadowed in the next // + // sourceInfo + // { + # This shadows the sourceInfo.outPath + inherit outPath; + + 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/lockfile.cc b/src/libexpr/flake/lockfile.cc index a3ed90e1f..a74e68c9c 100644 --- a/src/libexpr/flake/lockfile.cc +++ b/src/libexpr/flake/lockfile.cc @@ -219,7 +219,7 @@ std::optional<FlakeRef> LockFile::isUnlocked() const visit(root); for (auto & i : nodes) { - if (i == root) continue; + if (i == ref<const Node>(root)) continue; auto node = i.dynamic_pointer_cast<const LockedNode>(); if (node && !node->lockedRef.input.isLocked()) return node->lockedRef; diff --git a/src/libexpr/nix-expr.pc.in b/src/libexpr/nix-expr.pc.in index 80f7a492b..60ffb5dba 100644 --- a/src/libexpr/nix-expr.pc.in +++ b/src/libexpr/nix-expr.pc.in @@ -7,4 +7,4 @@ Description: Nix Package Manager Version: @PACKAGE_VERSION@ Requires: nix-store bdw-gc Libs: -L${libdir} -lnixexpr -Cflags: -I${includedir}/nix -std=c++17 +Cflags: -I${includedir}/nix -std=c++2a diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index ffe67f97d..4a81eaa47 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -186,7 +186,7 @@ struct ExprString : Expr { std::string s; Value v; - ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); }; + ExprString(std::string &&s) : s(std::move(s)) { v.mkString(this->s.data()); }; Value * maybeThunk(EvalState & state, Env & env) override; COMMON_METHODS }; @@ -233,7 +233,7 @@ struct ExprSelect : Expr PosIdx pos; Expr * e, * def; AttrPath attrPath; - ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; + ExprSelect(const PosIdx & pos, Expr * e, const AttrPath && attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { }; ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; PosIdx getPos() const override { return pos; } COMMON_METHODS @@ -243,7 +243,7 @@ struct ExprOpHasAttr : Expr { Expr * e; AttrPath attrPath; - ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { }; + ExprOpHasAttr(Expr * e, const AttrPath && attrPath) : e(e), attrPath(std::move(attrPath)) { }; PosIdx getPos() const override { return e->getPos(); } COMMON_METHODS }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index ffb364a90..dec5818fc 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -90,7 +90,7 @@ static void dupAttr(const EvalState & state, Symbol attr, const PosIdx pos, cons } -static void addAttr(ExprAttrs * attrs, AttrPath & attrPath, +static void addAttr(ExprAttrs * attrs, AttrPath && attrPath, Expr * e, const PosIdx pos, const nix::EvalState & state) { AttrPath::iterator i; @@ -188,7 +188,7 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals, static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols, - std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> & es) + std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es) { if (es.empty()) return new ExprString(""); @@ -268,7 +268,7 @@ static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols, s2 = std::string(s2, 0, p + 1); } - es2->emplace_back(i->first, new ExprString(s2)); + es2->emplace_back(i->first, new ExprString(std::move(s2))); }; for (; i != es.end(); ++i, --n) { std::visit(overloaded { trimExpr, trimString }, i->second); @@ -408,7 +408,7 @@ expr_op | expr_op OR expr_op { $$ = new ExprOpOr(makeCurPos(@2, data), $1, $3); } | expr_op IMPL expr_op { $$ = new ExprOpImpl(makeCurPos(@2, data), $1, $3); } | expr_op UPDATE expr_op { $$ = new ExprOpUpdate(makeCurPos(@2, data), $1, $3); } - | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); } + | expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, std::move(*$3)); delete $3; } | expr_op '+' expr_op { $$ = new ExprConcatStrings(makeCurPos(@2, data), false, new std::vector<std::pair<PosIdx, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); } | expr_op '-' expr_op { $$ = new ExprCall(makeCurPos(@2, data), new ExprVar(data->symbols.create("__sub")), {$1, $3}); } @@ -431,14 +431,14 @@ expr_app expr_select : expr_simple '.' attrpath - { $$ = new ExprSelect(CUR_POS, $1, *$3, 0); } + { $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), nullptr); delete $3; } | expr_simple '.' attrpath OR_KW expr_select - { $$ = new ExprSelect(CUR_POS, $1, *$3, $5); } + { $$ = new ExprSelect(CUR_POS, $1, std::move(*$3), $5); delete $3; } | /* Backwards compatibility: because Nixpkgs has a rarely used function named ‘or’, allow stuff like ‘map or [...]’. */ expr_simple OR_KW { $$ = new ExprCall(CUR_POS, $1, {new ExprVar(CUR_POS, data->symbols.create("or"))}); } - | expr_simple { $$ = $1; } + | expr_simple ; expr_simple @@ -453,9 +453,10 @@ expr_simple | FLOAT { $$ = new ExprFloat($1); } | '"' string_parts '"' { $$ = $2; } | IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE { - $$ = stripIndentation(CUR_POS, data->symbols, *$2); + $$ = stripIndentation(CUR_POS, data->symbols, std::move(*$2)); + delete $2; } - | path_start PATH_END { $$ = $1; } + | path_start PATH_END | path_start string_parts_interpolated PATH_END { $2->insert($2->begin(), {makeCurPos(@1, data), $1}); $$ = new ExprConcatStrings(CUR_POS, false, $2); @@ -465,7 +466,7 @@ expr_simple $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__findFile")), {new ExprVar(data->symbols.create("__nixPath")), - new ExprString(path)}); + new ExprString(std::move(path))}); } | URI { static bool noURLLiterals = settings.isExperimentalFeatureEnabled(Xp::NoUrlLiterals); @@ -533,7 +534,7 @@ ind_string_parts ; binds - : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data), data->state); } + : binds attrpath '=' expr ';' { $$ = $1; addAttr($$, std::move(*$2), $4, makeCurPos(@2, data), data->state); delete $2; } | binds INHERIT attrs ';' { $$ = $1; for (auto & i : *$3) { @@ -542,6 +543,7 @@ binds auto pos = makeCurPos(@3, data); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true)); } + delete $3; } | binds INHERIT '(' expr ')' attrs ';' { $$ = $1; @@ -551,6 +553,7 @@ binds dupAttr(data->state, i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos); $$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data))); } + delete $6; } | { $$ = new ExprAttrs(makeCurPos(@0, data)); } ; @@ -596,7 +599,7 @@ attrpath ; attr - : ID { $$ = $1; } + : ID | OR_KW { $$ = {"or", 2}; } ; @@ -612,9 +615,9 @@ expr_list formals : formal ',' formals - { $$ = $3; $$->formals.push_back(*$1); } + { $$ = $3; $$->formals.emplace_back(*$1); delete $1; } | formal - { $$ = new ParserFormals; $$->formals.push_back(*$1); $$->ellipsis = false; } + { $$ = new ParserFormals; $$->formals.emplace_back(*$1); $$->ellipsis = false; delete $1; } | { $$ = new ParserFormals; $$->ellipsis = false; } | ELLIPSIS diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index db33749a1..f4ae3b537 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2093,7 +2093,7 @@ static void addPath( std::optional<StorePath> expectedStorePath; if (expectedHash) expectedStorePath = state.store->makeFixedOutputPath(name, FixedOutputInfo { - { + .hash = { .method = method, .hash = *expectedHash, }, @@ -3038,9 +3038,9 @@ static RegisterPrimOp primop_foldlStrict({ .doc = R"( Reduce a list by applying a binary operator, from left to right, e.g. `foldl' op nul [x0 x1 x2 ...] = op (op (op nul x0) x1) x2) - ...`. The operator is applied strictly, i.e., its arguments are - evaluated first. For example, `foldl' (x: y: x + y) 0 [1 2 3]` - evaluates to 6. + ...`. For example, `foldl' (x: y: x + y) 0 [1 2 3]` evaluates to 6. + The return value of each application of `op` is evaluated immediately, + even for intermediate values. )", .fun = prim_foldlStrict, }); diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index e194462e4..f3dce2214 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -4,6 +4,7 @@ #include "fetchers.hh" #include "filetransfer.hh" #include "registry.hh" +#include "url.hh" #include <ctime> #include <iomanip> @@ -68,7 +69,16 @@ void emitTreeAttrs( std::string fixURI(std::string uri, EvalState & state, const std::string & defaultScheme = "file") { state.checkURI(uri); - return uri.find("://") != std::string::npos ? uri : defaultScheme + "://" + uri; + if (uri.find("://") == std::string::npos) { + const auto p = ParsedURL { + .scheme = defaultScheme, + .authority = "", + .path = uri + }; + return p.to_string(); + } else { + return uri; + } } std::string fixURIForGit(std::string uri, EvalState & state) @@ -236,7 +246,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v auto expectedPath = state.store->makeFixedOutputPath( name, FixedOutputInfo { - { + .hash = { .method = unpack ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat, .hash = *expectedHash, }, @@ -461,6 +471,17 @@ static RegisterPrimOp primop_fetchGit({ > **Note** > > This behavior is disabled in *Pure evaluation mode*. + + - To fetch the content of a checked-out work directory: + + ```nix + builtins.fetchGit ./work-dir + ``` + + If the URL points to a local directory, and no `ref` or `rev` is + given, `fetchGit` will use the current content of the checked-out + files, even if they are not committed or added to Git's index. It will + only consider files added to the Git repository, as listed by `git ls-files`. )", .fun = prim_fetchGit, }); diff --git a/src/libexpr/tests/error_traces.cc b/src/libexpr/tests/error_traces.cc index 5e2213f69..24e95ac39 100644 --- a/src/libexpr/tests/error_traces.cc +++ b/src/libexpr/tests/error_traces.cc @@ -1,7 +1,7 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> -#include "libexprtests.hh" +#include "tests/libexpr.hh" namespace nix { diff --git a/src/libexpr/tests/json.cc b/src/libexpr/tests/json.cc index f1ea1b197..411bc0ac3 100644 --- a/src/libexpr/tests/json.cc +++ b/src/libexpr/tests/json.cc @@ -1,4 +1,4 @@ -#include "libexprtests.hh" +#include "tests/libexpr.hh" #include "value-to-json.hh" namespace nix { diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexpr.hh index 03e468fbb..8534d9567 100644 --- a/src/libexpr/tests/libexprtests.hh +++ b/src/libexpr/tests/libexpr.hh @@ -7,18 +7,19 @@ #include "eval-inline.hh" #include "store-api.hh" +#include "tests/libstore.hh" namespace nix { - class LibExprTest : public ::testing::Test { + class LibExprTest : public LibStoreTest { public: static void SetUpTestSuite() { - initLibStore(); + LibStoreTest::SetUpTestSuite(); initGC(); } protected: LibExprTest() - : store(openStore("dummy://")) + : LibStoreTest() , state({}, store) { } @@ -36,7 +37,6 @@ namespace nix { return state.symbols.create(value); } - ref<Store> store; EvalState state; }; diff --git a/src/libexpr/tests/local.mk b/src/libexpr/tests/local.mk index e483575a4..3e5504f71 100644 --- a/src/libexpr/tests/local.mk +++ b/src/libexpr/tests/local.mk @@ -2,6 +2,8 @@ check: libexpr-tests_RUN programs += libexpr-tests +libexpr-tests_NAME := libnixexpr-tests + libexpr-tests_DIR := $(d) libexpr-tests_INSTALL_DIR := @@ -12,6 +14,6 @@ libexpr-tests_SOURCES := \ libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests -libexpr-tests_LIBS = libexpr libutil libstore libfetchers +libexpr-tests_LIBS = libstore-tests libutils-tests libexpr libutil libstore libfetchers libexpr-tests_LDFLAGS := $(GTEST_LIBS) -lgmock diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc index 9cdcf64a1..e1d3ac503 100644 --- a/src/libexpr/tests/primops.cc +++ b/src/libexpr/tests/primops.cc @@ -1,7 +1,7 @@ #include <gmock/gmock.h> #include <gtest/gtest.h> -#include "libexprtests.hh" +#include "tests/libexpr.hh" namespace nix { class CaptureLogger : public Logger diff --git a/src/libexpr/tests/trivial.cc b/src/libexpr/tests/trivial.cc index 8ce276e52..171727ac7 100644 --- a/src/libexpr/tests/trivial.cc +++ b/src/libexpr/tests/trivial.cc @@ -1,4 +1,4 @@ -#include "libexprtests.hh" +#include "tests/libexpr.hh" namespace nix { // Testing of trivial expressions diff --git a/src/libexpr/tests/value/context.cc b/src/libexpr/tests/value/context.cc index d5c9d3bce..083359b7a 100644 --- a/src/libexpr/tests/value/context.cc +++ b/src/libexpr/tests/value/context.cc @@ -1,6 +1,10 @@ -#include "value/context.hh" +#include <nlohmann/json.hpp> +#include <gtest/gtest.h> +#include <rapidcheck/gtest.h> -#include "libexprtests.hh" +#include "tests/path.hh" +#include "tests/libexpr.hh" +#include "tests/value/context.hh" namespace nix { @@ -70,3 +74,54 @@ TEST_F(NixStringContextElemTest, built) { } } + +namespace rc { +using namespace nix; + +Gen<NixStringContextElem::Opaque> Arbitrary<NixStringContextElem::Opaque>::arbitrary() +{ + return gen::just(NixStringContextElem::Opaque { + .path = *gen::arbitrary<StorePath>(), + }); +} + +Gen<NixStringContextElem::DrvDeep> Arbitrary<NixStringContextElem::DrvDeep>::arbitrary() +{ + return gen::just(NixStringContextElem::DrvDeep { + .drvPath = *gen::arbitrary<StorePath>(), + }); +} + +Gen<NixStringContextElem::Built> Arbitrary<NixStringContextElem::Built>::arbitrary() +{ + return gen::just(NixStringContextElem::Built { + .drvPath = *gen::arbitrary<StorePath>(), + .output = (*gen::arbitrary<StorePathName>()).name, + }); +} + +Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary() +{ + switch (*gen::inRange<uint8_t>(0, 2)) { + case 0: + return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::Opaque>()); + case 1: + return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::DrvDeep>()); + default: + return gen::just<NixStringContextElem>(*gen::arbitrary<NixStringContextElem::Built>()); + } +} + +} + +namespace nix { + +RC_GTEST_FIXTURE_PROP( + NixStringContextElemTest, + prop_round_rip, + (const NixStringContextElem & o)) +{ + RC_ASSERT(o == NixStringContextElem::parse(store(), o.to_string(store()))); +} + +} diff --git a/src/libexpr/tests/value/context.hh b/src/libexpr/tests/value/context.hh new file mode 100644 index 000000000..54d21760e --- /dev/null +++ b/src/libexpr/tests/value/context.hh @@ -0,0 +1,30 @@ +#pragma once + +#include <rapidcheck/gen/Arbitrary.h> + +#include <value/context.hh> + +namespace rc { +using namespace nix; + +template<> +struct Arbitrary<NixStringContextElem::Opaque> { + static Gen<NixStringContextElem::Opaque> arbitrary(); +}; + +template<> +struct Arbitrary<NixStringContextElem::Built> { + static Gen<NixStringContextElem::Built> arbitrary(); +}; + +template<> +struct Arbitrary<NixStringContextElem::DrvDeep> { + static Gen<NixStringContextElem::DrvDeep> arbitrary(); +}; + +template<> +struct Arbitrary<NixStringContextElem> { + static Gen<NixStringContextElem> arbitrary(); +}; + +} diff --git a/src/libexpr/value/context.hh b/src/libexpr/value/context.hh index d8008c436..721563cba 100644 --- a/src/libexpr/value/context.hh +++ b/src/libexpr/value/context.hh @@ -1,9 +1,10 @@ #pragma once #include "util.hh" +#include "comparator.hh" #include "path.hh" -#include <optional> +#include <variant> #include <nlohmann/json_fwd.hpp> @@ -31,7 +32,9 @@ class Store; Encoded as just the path: ‘<path>’. */ struct NixStringContextElem_Opaque { - StorePath path; + StorePath path; + + GENERATE_CMP(NixStringContextElem_Opaque, me->path); }; /* Path to a derivation and its entire build closure. @@ -43,7 +46,9 @@ struct NixStringContextElem_Opaque { Encoded in the form ‘=<drvPath>’. */ struct NixStringContextElem_DrvDeep { - StorePath drvPath; + StorePath drvPath; + + GENERATE_CMP(NixStringContextElem_DrvDeep, me->drvPath); }; /* Derivation output. @@ -51,8 +56,10 @@ struct NixStringContextElem_DrvDeep { Encoded in the form ‘!<output>!<drvPath>’. */ struct NixStringContextElem_Built { - StorePath drvPath; - std::string output; + StorePath drvPath; + std::string output; + + GENERATE_CMP(NixStringContextElem_Built, me->drvPath, me->output); }; using _NixStringContextElem_Raw = std::variant< diff --git a/src/libfetchers/fetch-settings.hh b/src/libfetchers/fetch-settings.hh index f33cbdcfc..7049dea30 100644 --- a/src/libfetchers/fetch-settings.hh +++ b/src/libfetchers/fetch-settings.hh @@ -57,7 +57,7 @@ struct FetchSettings : public Config ``` This example specifies three tokens, one each for accessing - github.com, gitlab.mycompany.com, and sourceforge.net. + github.com, gitlab.mycompany.com, and gitlab.com. The `input.foo` uses the "gitlab" fetcher, which might requires specifying the token type along with the token diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index 3936eadfe..91db3a9eb 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -211,7 +211,7 @@ StorePath Input::computeStorePath(Store & store) const if (!narHash) throw Error("cannot compute store path for unlocked input '%s'", to_string()); return store.makeFixedOutputPath(getName(), FixedOutputInfo { - { + .hash = { .method = FileIngestionMethod::Recursive, .hash = *narHash, }, diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 1f7d7c07d..309a143f5 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -615,15 +615,42 @@ struct GitInputScheme : InputScheme AutoDelete delTmpGitDir(tmpGitDir, true); runProgram("git", true, { "-c", "init.defaultBranch=" + gitInitialBranch, "init", tmpDir, "--separate-git-dir", tmpGitDir }); - // TODO: repoDir might lack the ref (it only checks if rev - // exists, see FIXME above) so use a big hammer and fetch - // everything to ensure we get the rev. - runProgram("git", true, { "-C", tmpDir, "fetch", "--quiet", "--force", - "--update-head-ok", "--", repoDir, "refs/*:refs/*" }); + + { + // TODO: repoDir might lack the ref (it only checks if rev + // exists, see FIXME above) so use a big hammer and fetch + // everything to ensure we get the rev. + Activity act(*logger, lvlTalkative, actUnknown, fmt("making temporary clone of '%s'", repoDir)); + runProgram("git", true, { "-C", tmpDir, "fetch", "--quiet", "--force", + "--update-head-ok", "--", repoDir, "refs/*:refs/*" }); + } runProgram("git", true, { "-C", tmpDir, "checkout", "--quiet", input.getRev()->gitRev() }); - runProgram("git", true, { "-C", tmpDir, "remote", "add", "origin", actualUrl }); - runProgram("git", true, { "-C", tmpDir, "submodule", "--quiet", "update", "--init", "--recursive" }); + + /* Ensure that we use the correct origin for fetching + submodules. This matters for submodules with relative + URLs. */ + if (isLocal) { + writeFile(tmpGitDir + "/config", readFile(repoDir + "/" + gitDir + "/config")); + + /* Restore the config.bare setting we may have just + copied erroneously from the user's repo. */ + runProgram("git", true, { "-C", tmpDir, "config", "core.bare", "false" }); + } else + runProgram("git", true, { "-C", tmpDir, "config", "remote.origin.url", actualUrl }); + + /* As an optimisation, copy the modules directory of the + source repo if it exists. */ + auto modulesPath = repoDir + "/" + gitDir + "/modules"; + if (pathExists(modulesPath)) { + Activity act(*logger, lvlTalkative, actUnknown, fmt("copying submodules of '%s'", actualUrl)); + runProgram("cp", true, { "-R", "--", modulesPath, tmpGitDir + "/modules" }); + } + + { + Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching submodules of '%s'", actualUrl)); + runProgram("git", true, { "-C", tmpDir, "submodule", "--quiet", "update", "--init", "--recursive" }); + } filter = isNotDotGitDirectory; } else { diff --git a/src/libfetchers/tarball.cc b/src/libfetchers/tarball.cc index 302046610..96fe5faca 100644 --- a/src/libfetchers/tarball.cc +++ b/src/libfetchers/tarball.cc @@ -74,7 +74,7 @@ DownloadFileResult downloadFile( *store, name, FixedOutputInfo { - { + .hash = { .method = FileIngestionMethod::Flat, .hash = hash, }, diff --git a/src/libmain/nix-main.pc.in b/src/libmain/nix-main.pc.in index 37b03dcd4..fb3ead6fa 100644 --- a/src/libmain/nix-main.pc.in +++ b/src/libmain/nix-main.pc.in @@ -6,4 +6,4 @@ Name: Nix Description: Nix Package Manager Version: @PACKAGE_VERSION@ Libs: -L${libdir} -lnixmain -Cflags: -I${includedir}/nix -std=c++17 +Cflags: -I${includedir}/nix -std=c++2a diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc index 9058bb8b1..9eae8d534 100644 --- a/src/libstore/binary-cache-store.cc +++ b/src/libstore/binary-cache-store.cc @@ -309,7 +309,7 @@ StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, std::string_view n *this, name, FixedOutputInfo { - { + .hash = { .method = method, .hash = nar.first, }, @@ -380,7 +380,7 @@ void BinaryCacheStore::queryPathInfoUncached(const StorePath & storePath, auto callbackPtr = std::make_shared<decltype(callback)>(std::move(callback)); getFile(narInfoFile, - {[=](std::future<std::optional<std::string>> fut) { + {[=,this](std::future<std::optional<std::string>> fut) { try { auto data = fut.get(); @@ -427,7 +427,7 @@ StorePath BinaryCacheStore::addToStore( *this, name, FixedOutputInfo { - { + .hash = { .method = method, .hash = h, }, diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 6f3048b63..85e9e16ff 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -16,6 +16,7 @@ #include "json-utils.hh" #include "cgroup.hh" #include "personality.hh" +#include "namespaces.hh" #include <regex> #include <queue> @@ -167,7 +168,8 @@ void LocalDerivationGoal::killSandbox(bool getStats) } -void LocalDerivationGoal::tryLocalBuild() { +void LocalDerivationGoal::tryLocalBuild() +{ unsigned int curBuilds = worker.getNrLocalBuilds(); if (curBuilds >= settings.maxBuildJobs) { state = &DerivationGoal::tryToBuild; @@ -205,6 +207,17 @@ void LocalDerivationGoal::tryLocalBuild() { #endif } + #if __linux__ + if (useChroot) { + if (!mountAndPidNamespacesSupported()) { + if (!settings.sandboxFallback) + throw Error("this system does not support the kernel namespaces that are required for sandboxing; use '--no-sandbox' to disable sandboxing"); + debug("auto-disabling sandboxing because the prerequisite namespaces are not available"); + useChroot = false; + } + } + #endif + if (useBuildUsers()) { if (!buildUser) buildUser = acquireUserLock(parsedDrv->useUidRange() ? 65536 : 1, useChroot); @@ -372,12 +385,6 @@ void LocalDerivationGoal::cleanupPostOutputsRegisteredModeNonCheck() } -int childEntry(void * arg) -{ - ((LocalDerivationGoal *) arg)->runChild(); - return 1; -} - #if __linux__ static void linkOrCopy(const Path & from, const Path & to) { @@ -663,7 +670,8 @@ void LocalDerivationGoal::startBuilder() nobody account. The latter is kind of a hack to support Samba-in-QEMU. */ createDirs(chrootRootDir + "/etc"); - chownToBuilder(chrootRootDir + "/etc"); + if (parsedDrv->useUidRange()) + chownToBuilder(chrootRootDir + "/etc"); if (parsedDrv->useUidRange() && (!buildUser || buildUser->getUIDCount() < 65536)) throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name); @@ -888,12 +896,7 @@ void LocalDerivationGoal::startBuilder() userNamespaceSync.create(); - Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces"; - static bool userNamespacesEnabled = - pathExists(maxUserNamespaces) - && trim(readFile(maxUserNamespaces)) != "0"; - - usingUserNamespace = userNamespacesEnabled; + usingUserNamespace = userNamespacesSupported(); Pid helper = startProcess([&]() { @@ -908,76 +911,21 @@ void LocalDerivationGoal::startBuilder() if (getuid() == 0 && setgroups(0, 0) == -1) throw SysError("setgroups failed"); - size_t stackSize = 1 * 1024 * 1024; - char * stack = (char *) mmap(0, stackSize, - PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); - if (stack == MAP_FAILED) throw SysError("allocating stack"); - - int flags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD; + ProcessOptions options; + options.cloneFlags = CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | CLONE_NEWUTS | CLONE_PARENT | SIGCHLD; if (privateNetwork) - flags |= CLONE_NEWNET; + options.cloneFlags |= CLONE_NEWNET; if (usingUserNamespace) - flags |= CLONE_NEWUSER; - - pid_t child = clone(childEntry, stack + stackSize, flags, this); - if (child == -1 && errno == EINVAL) { - /* Fallback for Linux < 2.13 where CLONE_NEWPID and - CLONE_PARENT are not allowed together. */ - flags &= ~CLONE_NEWPID; - child = clone(childEntry, stack + stackSize, flags, this); - } - if (usingUserNamespace && child == -1 && (errno == EPERM || errno == EINVAL)) { - /* 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); - } - 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()"); - } - } + options.cloneFlags |= CLONE_NEWUSER; + + pid_t child = startProcess([&]() { runChild(); }, options); + writeFull(builderOut.writeSide.get(), fmt("%d %d\n", usingUserNamespace, child)); _exit(0); }); - int res = helper.wait(); - if (res != 0 && settings.sandboxFallback) { - useChroot = false; - initTmpDir(); - goto fallback; - } else if (res != 0) + if (helper.wait() != 0) throw Error("unable to start build process"); userNamespaceSync.readSide = -1; @@ -1045,9 +993,6 @@ void LocalDerivationGoal::startBuilder() } else #endif { -#if __linux__ - fallback: -#endif pid = startProcess([&]() { runChild(); }); @@ -1516,8 +1461,7 @@ void LocalDerivationGoal::startDaemon() FdSink to(remote.get()); try { daemon::processConnection(store, from, to, - daemon::NotTrusted, daemon::Recursive, - [&](Store & store) { store.createUser("nobody", 65535); }); + daemon::NotTrusted, daemon::Recursive); debug("terminated daemon connection"); } catch (SysError &) { ignoreException(); @@ -1907,6 +1851,10 @@ void LocalDerivationGoal::runChild() } } + /* Make /etc unwritable */ + if (!parsedDrv->useUidRange()) + chmod_(chrootRootDir + "/etc", 0555); + /* Unshare this mount namespace. This is necessary because pivot_root() below changes the root of the mount namespace. This means that the call to setns() in @@ -2323,11 +2271,28 @@ DrvOutputs LocalDerivationGoal::registerOutputs() buildUser ? std::optional(buildUser->getUIDRange()) : std::nullopt, inodesSeen); - debug("scanning for references for output '%s' in temp location '%s'", outputName, actualPath); + bool discardReferences = false; + if (auto structuredAttrs = parsedDrv->getStructuredAttrs()) { + if (auto udr = get(*structuredAttrs, "unsafeDiscardReferences")) { + settings.requireExperimentalFeature(Xp::DiscardReferences); + if (auto output = get(*udr, outputName)) { + if (!output->is_boolean()) + throw Error("attribute 'unsafeDiscardReferences.\"%s\"' of derivation '%s' must be a Boolean", outputName, drvPath.to_string()); + discardReferences = output->get<bool>(); + } + } + } + + StorePathSet references; + if (discardReferences) + debug("discarding references of output '%s'", outputName); + else { + debug("scanning for references for output '%s' in temp location '%s'", outputName, actualPath); - /* Pass blank Sink as we are not ready to hash data at this stage. */ - NullSink blank; - auto references = scanForReferences(blank, actualPath, referenceablePaths); + /* Pass blank Sink as we are not ready to hash data at this stage. */ + NullSink blank; + references = scanForReferences(blank, actualPath, referenceablePaths); + } outputReferencesIfUnregistered.insert_or_assign( outputName, diff --git a/src/libstore/build/worker.cc b/src/libstore/build/worker.cc index b94fb8416..f775f8486 100644 --- a/src/libstore/build/worker.cc +++ b/src/libstore/build/worker.cc @@ -276,7 +276,7 @@ void Worker::run(const Goals & _topGoals) if (!children.empty() || !waitingForAWhile.empty()) waitForInput(); else { - if (awake.empty() && 0 == settings.maxBuildJobs) + if (awake.empty() && 0U == settings.maxBuildJobs) { if (getMachines().empty()) throw Error("unable to start any build; either increase '--max-jobs' " diff --git a/src/libstore/content-address.cc b/src/libstore/content-address.cc index 5452758b3..034a9485b 100644 --- a/src/libstore/content-address.cc +++ b/src/libstore/content-address.cc @@ -17,8 +17,9 @@ std::string makeFileIngestionPrefix(FileIngestionMethod m) return ""; case FileIngestionMethod::Recursive: return "r:"; + default: + throw Error("impossible, caught both cases"); } - assert(false); } std::string makeContentAddressingPrefix(ContentAddressMethod m) { @@ -168,13 +169,13 @@ ContentAddressWithReferences contentAddressFromMethodHashAndRefs( if (refs.self) throw UsageError("Cannot have a self reference with text hashing scheme"); return TextInfo { - { .hash = std::move(hash) }, + .hash = { .hash = std::move(hash) }, .references = std::move(refs.others), }; }, [&](FileIngestionMethod m2) -> ContentAddressWithReferences { return FixedOutputInfo { - { + .hash = { .method = m2, .hash = std::move(hash), }, @@ -191,7 +192,7 @@ ContentAddressMethod getContentAddressMethod(const ContentAddressWithReferences return TextHashMethod {}; }, [](const FixedOutputInfo & fsh) -> ContentAddressMethod { - return fsh.method; + return fsh.hash.method; }, }, ca); } @@ -222,13 +223,13 @@ ContentAddressWithReferences caWithoutRefs(const ContentAddress & ca) { return std::visit(overloaded { [&](const TextHash & h) -> ContentAddressWithReferences { return TextInfo { - h, + .hash = h, .references = {}, }; }, [&](const FixedOutputHash & h) -> ContentAddressWithReferences { return FixedOutputInfo { - h, + .hash = h, .references = {}, }; }, @@ -239,10 +240,10 @@ Hash getContentAddressHash(const ContentAddressWithReferences & ca) { return std::visit(overloaded { [](const TextInfo & th) { - return th.hash; + return th.hash.hash; }, [](const FixedOutputInfo & fsh) { - return fsh.hash; + return fsh.hash.hash; }, }, ca); } diff --git a/src/libstore/content-address.hh b/src/libstore/content-address.hh index 729b1078d..962a50c6b 100644 --- a/src/libstore/content-address.hh +++ b/src/libstore/content-address.hh @@ -118,18 +118,20 @@ struct StoreReferences { */ // This matches the additional info that we need for makeTextPath -struct TextInfo : TextHash { +struct TextInfo { + TextHash hash; // References for the paths, self references disallowed StorePathSet references; - GENERATE_CMP(TextInfo, *(const TextHash *)me, me->references); + GENERATE_CMP(TextInfo, me->hash, me->references); }; -struct FixedOutputInfo : FixedOutputHash { +struct FixedOutputInfo { + FixedOutputHash hash; // References for the paths StoreReferences references; - GENERATE_CMP(FixedOutputInfo, *(const FixedOutputHash *)me, me->references); + GENERATE_CMP(FixedOutputInfo, me->hash, me->references); }; typedef std::variant< diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc index 0694f5b90..1745b1e57 100644 --- a/src/libstore/daemon.cc +++ b/src/libstore/daemon.cc @@ -222,7 +222,8 @@ struct ClientSettings else if (!hasSuffix(s, "/") && trusted.count(s + "/")) subs.push_back(s + "/"); else - warn("ignoring untrusted substituter '%s'", s); + warn("ignoring untrusted substituter '%s', you are not a trusted user.\n" + "Run `man nix.conf` for more information on the `substituters` configuration option.", s); res = subs; return true; }; @@ -235,6 +236,10 @@ struct ClientSettings // the daemon, as that could cause some pretty weird stuff if (parseFeatures(tokenizeString<StringSet>(value)) != settings.experimentalFeatures.get()) debug("Ignoring the client-specified experimental features"); + } else if (name == settings.pluginFiles.name) { + if (tokenizeString<Paths>(value) != settings.pluginFiles.get()) + warn("Ignoring the client-specified plugin-files.\n" + "The client specifying plugins to the daemon never made sense, and was removed in Nix >=2.14."); } else if (trusted || name == settings.buildTimeout.name @@ -528,7 +533,14 @@ static void performOp(TunnelLogger * logger, ref<Store> store, mode = (BuildMode) readInt(from); /* Repairing is not atomic, so disallowed for "untrusted" - clients. */ + clients. + + FIXME: layer violation in this message: the daemon code (i.e. + this file) knows whether a client/connection is trusted, but it + does not how how the client was authenticated. The mechanism + need not be getting the UID of the other end of a Unix Domain + Socket. + */ if (mode == bmRepair && !trusted) throw Error("repairing is not allowed because you are not in 'trusted-users'"); } @@ -545,7 +557,9 @@ static void performOp(TunnelLogger * logger, ref<Store> store, mode = (BuildMode) readInt(from); /* Repairing is not atomic, so disallowed for "untrusted" - clients. */ + clients. + + FIXME: layer violation; see above. */ if (mode == bmRepair && !trusted) throw Error("repairing is not allowed because you are not in 'trusted-users'"); @@ -984,8 +998,7 @@ void processConnection( FdSource & from, FdSink & to, TrustedFlag trusted, - RecursiveFlag recursive, - std::function<void(Store &)> authHook) + RecursiveFlag recursive) { auto monitor = !recursive ? std::make_unique<MonitorFdHup>(from.fd) : nullptr; @@ -1028,10 +1041,6 @@ void processConnection( try { - /* If we can't accept clientVersion, then throw an error - *here* (not above). */ - authHook(*store); - tunnelLogger->stopWork(); to.flush(); diff --git a/src/libstore/daemon.hh b/src/libstore/daemon.hh index 67755d54e..8c765615c 100644 --- a/src/libstore/daemon.hh +++ b/src/libstore/daemon.hh @@ -13,11 +13,6 @@ void processConnection( FdSource & from, FdSink & to, TrustedFlag trusted, - RecursiveFlag recursive, - /* Arbitrary hook to check authorization / initialize user data / whatever - after the protocol has been negotiated. The idea is that this function - and everything it calls doesn't know about this stuff, and the - `nix-daemon` handles that instead. */ - std::function<void(Store &)> authHook); + RecursiveFlag recursive); } diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index b19ee5ed1..5461f4a9c 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -6,6 +6,7 @@ #include "worker-protocol.hh" #include "fs-accessor.hh" #include <boost/container/small_vector.hpp> +#include <nlohmann/json.hpp> namespace nix { @@ -887,4 +888,64 @@ std::optional<BasicDerivation> Derivation::tryResolve( const Hash impureOutputHash = hashString(htSHA256, "impure"); +nlohmann::json DerivationOutput::toJSON( + const Store & store, std::string_view drvName, std::string_view outputName) const +{ + nlohmann::json res = nlohmann::json::object(); + std::visit(overloaded { + [&](const DerivationOutput::InputAddressed & doi) { + res["path"] = store.printStorePath(doi.path); + }, + [&](const DerivationOutput::CAFixed & dof) { + res["path"] = store.printStorePath(dof.path(store, drvName, outputName)); + res["hashAlgo"] = printMethodAlgo(dof.ca); + res["hash"] = getContentAddressHash(dof.ca).to_string(Base16, false); + // FIXME print refs? + }, + [&](const DerivationOutput::CAFloating & dof) { + res["hashAlgo"] = makeContentAddressingPrefix(dof.method) + printHashType(dof.hashType); + }, + [&](const DerivationOutput::Deferred &) {}, + [&](const DerivationOutput::Impure & doi) { + res["hashAlgo"] = makeContentAddressingPrefix(doi.method) + printHashType(doi.hashType); + res["impure"] = true; + }, + }, raw()); + return res; +} + +nlohmann::json Derivation::toJSON(const Store & store) const +{ + nlohmann::json res = nlohmann::json::object(); + + { + nlohmann::json & outputsObj = res["outputs"]; + outputsObj = nlohmann::json::object(); + for (auto & [outputName, output] : outputs) { + outputsObj[outputName] = output.toJSON(store, name, outputName); + } + } + + { + auto& inputsList = res["inputSrcs"]; + inputsList = nlohmann::json ::array(); + for (auto & input : inputSrcs) + inputsList.emplace_back(store.printStorePath(input)); + } + + { + auto& inputDrvsObj = res["inputDrvs"]; + inputDrvsObj = nlohmann::json ::object(); + for (auto & input : inputDrvs) + inputDrvsObj[store.printStorePath(input.first)] = input.second; + } + + res["system"] = platform; + res["builder"] = builder; + res["args"] = args; + res["env"] = env; + + return res; +} + } diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index 803ab3cd6..80629dadb 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -83,6 +83,11 @@ struct DerivationOutput : _DerivationOutputRaw inline const Raw & raw() const { return static_cast<const Raw &>(*this); } + + nlohmann::json toJSON( + const Store & store, + std::string_view drvName, + std::string_view outputName) const; }; typedef std::map<std::string, DerivationOutput> DerivationOutputs; @@ -210,6 +215,8 @@ struct Derivation : BasicDerivation Derivation() = default; Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { } Derivation(BasicDerivation && bd) : BasicDerivation(std::move(bd)) { } + + nlohmann::json toJSON(const Store & store) const; }; diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh index 5f2dfa4f1..9e0cce377 100644 --- a/src/libstore/derived-path.hh +++ b/src/libstore/derived-path.hh @@ -4,8 +4,8 @@ #include "path.hh" #include "realisation.hh" #include "outputs-spec.hh" +#include "comparator.hh" -#include <optional> #include <variant> #include <nlohmann/json_fwd.hpp> @@ -28,8 +28,7 @@ struct DerivedPathOpaque { std::string to_string(const Store & store) const; static DerivedPathOpaque parse(const Store & store, std::string_view); - bool operator < (const DerivedPathOpaque & b) const - { return path < b.path; } + GENERATE_CMP(DerivedPathOpaque, me->path); }; /** @@ -52,8 +51,7 @@ struct DerivedPathBuilt { static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view); nlohmann::json toJSON(ref<Store> store) const; - bool operator < (const DerivedPathBuilt & b) const - { return std::make_pair(drvPath, outputs) < std::make_pair(b.drvPath, b.outputs); } + GENERATE_CMP(DerivedPathBuilt, me->drvPath, me->outputs); }; using _DerivedPathRaw = std::variant< @@ -97,6 +95,8 @@ struct BuiltPathBuilt { nlohmann::json toJSON(ref<Store> store) const; static BuiltPathBuilt parse(const Store & store, std::string_view); + + GENERATE_CMP(BuiltPathBuilt, me->drvPath, me->outputs); }; using _BuiltPathRaw = std::variant< diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index 756bd4423..1c8676a59 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -101,6 +101,7 @@ struct curlFileTransfer : public FileTransfer this->result.data.append(data); }) { + requestHeaders = curl_slist_append(requestHeaders, "Accept-Encoding: zstd, br, gzip, deflate, bzip2, xz"); if (!request.expectedETag.empty()) requestHeaders = curl_slist_append(requestHeaders, ("If-None-Match: " + request.expectedETag).c_str()); if (!request.mimeType.empty()) @@ -828,7 +829,7 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink) { auto state(_state->lock()); - while (state->data.empty()) { + if (state->data.empty()) { if (state->quit) { if (state->exc) std::rethrow_exception(state->exc); @@ -836,6 +837,8 @@ void FileTransfer::download(FileTransferRequest && request, Sink & sink) } state.wait(state->avail); + + if (state->data.empty()) continue; } chunk = std::move(state->data); diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index 130c5b670..8e33a3dec 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -222,19 +222,19 @@ template<> void BaseSetting<SandboxMode>::convertToArg(Args & args, const std::s .longName = name, .description = "Enable sandboxing.", .category = category, - .handler = {[=]() { override(smEnabled); }} + .handler = {[this]() { override(smEnabled); }} }); args.addFlag({ .longName = "no-" + name, .description = "Disable sandboxing.", .category = category, - .handler = {[=]() { override(smDisabled); }} + .handler = {[this]() { override(smDisabled); }} }); args.addFlag({ .longName = "relaxed-" + name, .description = "Enable sandboxing, but allow builds to disable it.", .category = category, - .handler = {[=]() { override(smRelaxed); }} + .handler = {[this]() { override(smRelaxed); }} }); } diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 7111def92..93086eaf8 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -201,7 +201,16 @@ public: {"build-timeout"}}; PathSetting buildHook{this, true, "", "build-hook", - "The path of the helper program that executes builds to remote machines."}; + R"( + The path to the helper program that executes remote builds. + + Nix communicates with the build hook over `stdio` using a custom protocol to request builds that cannot be performed directly by the Nix daemon. + The default value is the internal Nix binary that implements remote building. + + > **Important** + > + > Change this setting only if you really know what you’re doing. + )"}; Setting<std::string> builders{ this, "@" + nixConfDir + "/machines", "builders", @@ -279,8 +288,8 @@ public: If the build users group is empty, builds will be performed under the uid of the Nix process (that is, the uid of the caller if `NIX_REMOTE` is empty, the uid under which the Nix daemon runs if - `NIX_REMOTE` is `daemon`). Obviously, this should not be used in - multi-user settings with untrusted users. + `NIX_REMOTE` is `daemon`). Obviously, this should not be used + with a nix daemon accessible to untrusted clients. Defaults to `nixbld` when running as root, *empty* otherwise. )", @@ -570,11 +579,15 @@ public: {"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="}, "trusted-public-keys", R"( - A whitespace-separated list of public keys. When paths are copied - from another Nix store (such as a binary cache), they must be - signed with one of these keys. For example: - `cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= - hydra.nixos.org-1:CNHJZBh9K4tP3EKF6FkkgeVYsS3ohTl+oS0Qa8bezVs=`. + A whitespace-separated list of public keys. + + At least one of the following condition must be met + for Nix to accept copying a store object from another + Nix store (such as a substituter): + + - the store object has been signed using a key in the trusted keys list + - the [`require-sigs`](#conf-require-sigs) option has been set to `false` + - the store object is [output-addressed](@docroot@/glossary.md#gloss-output-addressed-store-object) )", {"binary-cache-public-keys"}}; @@ -670,13 +683,14 @@ public: 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: + At least one of the following conditions must be met for Nix to use + a substituter: - - 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](@docroot@/glossary.md#gloss-output-addressed-store-object) + - the user calling Nix is in the [`trusted-users`](#conf-trusted-users) list + + In addition, each store path should be trusted as described + in [`trusted-public-keys`](#conf-trusted-public-keys) )", {"binary-caches"}}; @@ -691,24 +705,6 @@ public: )", {"trusted-binary-caches"}}; - Setting<Strings> trustedUsers{ - this, {"root"}, "trusted-users", - R"( - A list of names of users (separated by whitespace) that have - additional rights when connecting to the Nix daemon, such as the - ability to specify additional binary caches, or to import unsigned - NARs. You can also specify groups by prefixing them with `@`; for - instance, `@wheel` means all users in the `wheel` group. The default - is `root`. - - > **Warning** - > - > Adding a user to `trusted-users` is essentially equivalent to - > giving that user root access to the system. For example, the user - > can set `sandbox-paths` and thereby obtain read access to - > directories that are otherwise inacessible to them. - )"}; - Setting<unsigned int> ttlNegativeNarInfoCache{ this, 3600, "narinfo-cache-negative-ttl", R"( @@ -731,18 +727,6 @@ public: mismatch if the build isn't reproducible. )"}; - /* ?Who we trust to use the daemon in safe ways */ - Setting<Strings> allowedUsers{ - this, {"*"}, "allowed-users", - R"( - A list of names of users (separated by whitespace) that are allowed - to connect to the Nix daemon. As with the `trusted-users` option, - you can specify groups by prefixing them with `@`. Also, you can - allow all users by specifying `*`. The default is `*`. - - Note that trusted users are always allowed to connect. - )"}; - Setting<bool> printMissing{this, true, "print-missing", "Whether to print what paths need to be built or downloaded."}; @@ -970,6 +954,27 @@ public: resolves to a different location from that of the build machine. You can enable this setting if you are sure you're not going to do that. )"}; + + Setting<bool> useXDGBaseDirectories{ + this, false, "use-xdg-base-directories", + R"( + If set to `true`, Nix will conform to the [XDG Base Directory Specification] for files in `$HOME`. + The environment variables used to implement this are documented in the [Environment Variables section](@docroot@/installation/env-variables.md). + + [XDG Base Directory Specification]: https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html + + > **Warning** + > This changes the location of some well-known symlinks that Nix creates, which might break tools that rely on the old, non-XDG-conformant locations. + + In particular, the following locations change: + + | Old | New | + |-------------------|--------------------------------| + | `~/.nix-profile` | `$XDG_STATE_HOME/nix/profile` | + | `~/.nix-defexpr` | `$XDG_STATE_HOME/nix/defexpr` | + | `~/.nix-channels` | `$XDG_STATE_HOME/nix/channels` | + )" + }; }; diff --git a/src/libstore/http-binary-cache-store.cc b/src/libstore/http-binary-cache-store.cc index 73bcd6e81..1479822a9 100644 --- a/src/libstore/http-binary-cache-store.cc +++ b/src/libstore/http-binary-cache-store.cc @@ -56,7 +56,7 @@ public: void init() override { // FIXME: do this lazily? - if (auto cacheInfo = diskCache->cacheExists(cacheUri)) { + if (auto cacheInfo = diskCache->upToDateCacheExists(cacheUri)) { wantMassQuery.setDefault(cacheInfo->wantMassQuery); priority.setDefault(cacheInfo->priority); } else { diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 94c005130..72ba12e0d 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -201,8 +201,6 @@ LocalStore::LocalStore(const Params & params) throw SysError("could not set permissions on '%s' to 755", perUserDir); } - createUser(getUserName(), getuid()); - /* Optionally, create directories and set permissions for a multi-user install. */ if (getuid() == 0 && settings.buildUsersGroup != "") { @@ -1417,7 +1415,7 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name auto [hash, size] = hashSink->finish(); ContentAddressWithReferences desc = FixedOutputInfo { - { + .hash = { .method = method, .hash = hash, }, @@ -1844,20 +1842,6 @@ void LocalStore::signPathInfo(ValidPathInfo & info) } -void LocalStore::createUser(const std::string & userName, uid_t userId) -{ - for (auto & dir : { - fmt("%s/profiles/per-user/%s", stateDir, userName), - fmt("%s/gcroots/per-user/%s", stateDir, userName) - }) { - createDirs(dir); - if (chmod(dir.c_str(), 0755) == -1) - throw SysError("changing permissions of directory '%s'", dir); - if (chown(dir.c_str(), userId, getgid()) == -1) - throw SysError("changing owner of directory '%s'", dir); - } -} - std::optional<std::pair<int64_t, Realisation>> LocalStore::queryRealisationCore_( LocalStore::State & state, const DrvOutput & id) diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index 06d36a7d5..a84eb7c26 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -281,8 +281,6 @@ private: void signPathInfo(ValidPathInfo & info); void signRealisation(Realisation &); - void createUser(const std::string & userName, uid_t userId) override; - // XXX: Make a generic `Store` method FixedOutputHash hashCAPath( const FileIngestionMethod & method, diff --git a/src/libstore/make-content-addressed.cc b/src/libstore/make-content-addressed.cc index 3ee64c77a..53fe04704 100644 --- a/src/libstore/make-content-addressed.cc +++ b/src/libstore/make-content-addressed.cc @@ -31,11 +31,12 @@ std::map<StorePath, StorePath> makeContentAddressed( for (auto & ref : oldInfo->references) { if (ref == path) refs.self = true; - auto i = remappings.find(ref); - auto replacement = i != remappings.end() ? i->second : ref; - // FIXME: warn about unremapped paths? - if (replacement != ref) { - rewrites.insert_or_assign(srcStore.printStorePath(ref), srcStore.printStorePath(replacement)); + else { + auto i = remappings.find(ref); + auto replacement = i != remappings.end() ? i->second : ref; + // FIXME: warn about unremapped paths? + if (replacement != ref) + rewrites.insert_or_assign(srcStore.printStorePath(ref), srcStore.printStorePath(replacement)); refs.others.insert(std::move(replacement)); } } @@ -51,7 +52,7 @@ std::map<StorePath, StorePath> makeContentAddressed( dstStore, path.name(), FixedOutputInfo { - { + .hash = { .method = FileIngestionMethod::Recursive, .hash = narModuloHash, }, diff --git a/src/libstore/misc.cc b/src/libstore/misc.cc index acd4e0929..e16b60b88 100644 --- a/src/libstore/misc.cc +++ b/src/libstore/misc.cc @@ -21,16 +21,16 @@ void Store::computeFSClosure(const StorePathSet & startPaths, StorePathSet res; StorePathSet referrers; queryReferrers(path, referrers); - for (auto & ref : referrers) + for (auto& ref : referrers) if (ref != path) res.insert(ref); if (includeOutputs) - for (auto & i : queryValidDerivers(path)) + for (auto& i : queryValidDerivers(path)) res.insert(i); if (includeDerivers && path.isDerivation()) - for (auto & [_, maybeOutPath] : queryPartialDerivationOutputMap(path)) + for (auto& [_, maybeOutPath] : queryPartialDerivationOutputMap(path)) if (maybeOutPath && isValidPath(*maybeOutPath)) res.insert(*maybeOutPath); return res; @@ -40,12 +40,12 @@ void Store::computeFSClosure(const StorePathSet & startPaths, std::future<ref<const ValidPathInfo>> & fut) { StorePathSet res; auto info = fut.get(); - for (auto & ref : info->references) + for (auto& ref : info->references) if (ref != path) res.insert(ref); if (includeOutputs && path.isDerivation()) - for (auto & [_, maybeOutPath] : queryPartialDerivationOutputMap(path)) + for (auto& [_, maybeOutPath] : queryPartialDerivationOutputMap(path)) if (maybeOutPath && isValidPath(*maybeOutPath)) res.insert(*maybeOutPath); @@ -93,12 +93,12 @@ std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv) [&](const TextInfo & ti) -> std::optional<ContentAddress> { if (!ti.references.empty()) return std::nullopt; - return static_cast<TextHash>(ti); + return ti.hash; }, [&](const FixedOutputInfo & fi) -> std::optional<ContentAddress> { if (!fi.references.empty()) return std::nullopt; - return static_cast<FixedOutputHash>(fi); + return fi.hash; }, }, dof->ca); } diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc index 3e0689534..2645f468b 100644 --- a/src/libstore/nar-info-disk-cache.cc +++ b/src/libstore/nar-info-disk-cache.cc @@ -84,11 +84,10 @@ public: Sync<State> _state; - NarInfoDiskCacheImpl() + NarInfoDiskCacheImpl(Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite") { auto state(_state.lock()); - Path dbPath = getCacheDir() + "/nix/binary-cache-v6.sqlite"; createDirs(dirOf(dbPath)); state->db = SQLite(dbPath); @@ -98,7 +97,7 @@ public: state->db.exec(schema); state->insertCache.create(state->db, - "insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?)"); + "insert into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?1, ?2, ?3, ?4, ?5) on conflict (url) do update set timestamp = ?2, storeDir = ?3, wantMassQuery = ?4, priority = ?5 returning id;"); state->queryCache.create(state->db, "select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ? and timestamp > ?"); @@ -166,6 +165,8 @@ public: return i->second; } +private: + std::optional<Cache> queryCacheRaw(State & state, const std::string & uri) { auto i = state.caches.find(uri); @@ -173,15 +174,21 @@ public: auto queryCache(state.queryCache.use()(uri)(time(0) - cacheInfoTtl)); if (!queryCache.next()) return std::nullopt; - state.caches.emplace(uri, - Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)}); + auto cache = Cache { + .id = (int) queryCache.getInt(0), + .storeDir = queryCache.getStr(1), + .wantMassQuery = queryCache.getInt(2) != 0, + .priority = (int) queryCache.getInt(3), + }; + state.caches.emplace(uri, cache); } return getCache(state, uri); } - void createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override +public: + int createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override { - retrySQLite<void>([&]() { + return retrySQLite<int>([&]() { auto state(_state.lock()); SQLiteTxn txn(state->db); @@ -190,17 +197,29 @@ public: auto cache(queryCacheRaw(*state, uri)); if (cache) - return; + return cache->id; - state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority).exec(); - assert(sqlite3_changes(state->db) == 1); - state->caches[uri] = Cache{(int) sqlite3_last_insert_rowid(state->db), storeDir, wantMassQuery, priority}; + Cache ret { + .id = -1, // set below + .storeDir = storeDir, + .wantMassQuery = wantMassQuery, + .priority = priority, + }; + + { + auto r(state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority)); + assert(r.next()); + ret.id = (int) r.getInt(0); + } + + state->caches[uri] = ret; txn.commit(); + return ret.id; }); } - std::optional<CacheInfo> cacheExists(const std::string & uri) override + std::optional<CacheInfo> upToDateCacheExists(const std::string & uri) override { return retrySQLite<std::optional<CacheInfo>>([&]() -> std::optional<CacheInfo> { auto state(_state.lock()); @@ -208,6 +227,7 @@ public: if (!cache) return std::nullopt; return CacheInfo { + .id = cache->id, .wantMassQuery = cache->wantMassQuery, .priority = cache->priority }; @@ -371,4 +391,9 @@ ref<NarInfoDiskCache> getNarInfoDiskCache() return cache; } +ref<NarInfoDiskCache> getTestNarInfoDiskCache(Path dbPath) +{ + return make_ref<NarInfoDiskCacheImpl>(dbPath); +} + } diff --git a/src/libstore/nar-info-disk-cache.hh b/src/libstore/nar-info-disk-cache.hh index 2dcaa76a4..4877f56d8 100644 --- a/src/libstore/nar-info-disk-cache.hh +++ b/src/libstore/nar-info-disk-cache.hh @@ -13,16 +13,17 @@ public: virtual ~NarInfoDiskCache() { } - virtual void createCache(const std::string & uri, const Path & storeDir, + virtual int createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) = 0; struct CacheInfo { + int id; bool wantMassQuery; int priority; }; - virtual std::optional<CacheInfo> cacheExists(const std::string & uri) = 0; + virtual std::optional<CacheInfo> upToDateCacheExists(const std::string & uri) = 0; virtual std::pair<Outcome, std::shared_ptr<NarInfo>> lookupNarInfo( const std::string & uri, const std::string & hashPart) = 0; @@ -45,4 +46,6 @@ public: multiple threads. */ ref<NarInfoDiskCache> getNarInfoDiskCache(); +ref<NarInfoDiskCache> getTestNarInfoDiskCache(Path dbPath); + } diff --git a/src/libstore/nix-store.pc.in b/src/libstore/nix-store.pc.in index 6d67b1e03..dc42d0bca 100644 --- a/src/libstore/nix-store.pc.in +++ b/src/libstore/nix-store.pc.in @@ -6,4 +6,4 @@ Name: Nix Description: Nix Package Manager Version: @PACKAGE_VERSION@ Libs: -L${libdir} -lnixstore -lnixutil -Cflags: -I${includedir}/nix -std=c++17 +Cflags: -I${includedir}/nix -std=c++2a diff --git a/src/libstore/path-info.cc b/src/libstore/path-info.cc index 5944afd06..76cab63e0 100644 --- a/src/libstore/path-info.cc +++ b/src/libstore/path-info.cc @@ -21,7 +21,7 @@ void ValidPathInfo::sign(const Store & store, const SecretKey & secretKey) sigs.insert(secretKey.signDetached(fingerprint(store))); } -std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithReferenences() const +std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithReferences() const { if (! ca) return std::nullopt; @@ -30,7 +30,7 @@ std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithRef [&](const TextHash & th) -> ContentAddressWithReferences { assert(references.count(path) == 0); return TextInfo { - th, + .hash = th, .references = references, }; }, @@ -42,7 +42,7 @@ std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithRef refs.erase(path); } return FixedOutputInfo { - foh, + .hash = foh, .references = { .others = std::move(refs), .self = hasSelfReference, @@ -54,7 +54,7 @@ std::optional<ContentAddressWithReferences> ValidPathInfo::contentAddressWithRef bool ValidPathInfo::isContentAddressed(const Store & store) const { - auto fullCaOpt = contentAddressWithReferenences(); + auto fullCaOpt = contentAddressWithReferences(); if (! fullCaOpt) return false; diff --git a/src/libstore/path-info.hh b/src/libstore/path-info.hh index 663d94540..97eb6638b 100644 --- a/src/libstore/path-info.hh +++ b/src/libstore/path-info.hh @@ -29,6 +29,7 @@ struct ValidPathInfo { StorePath path; std::optional<StorePath> deriver; + // TODO document this Hash narHash; StorePathSet references; time_t registrationTime = 0; @@ -77,7 +78,7 @@ struct ValidPathInfo void sign(const Store & store, const SecretKey & secretKey); - std::optional<ContentAddressWithReferences> contentAddressWithReferenences() const; + std::optional<ContentAddressWithReferences> contentAddressWithReferences() const; /* Return true iff the path is verifiably content-addressed. */ bool isContentAddressed(const Store & store) const; diff --git a/src/libstore/profiles.cc b/src/libstore/profiles.cc index 3e4188188..c551c5f3e 100644 --- a/src/libstore/profiles.cc +++ b/src/libstore/profiles.cc @@ -280,16 +280,24 @@ std::string optimisticLockProfile(const Path & profile) } +Path profilesDir() +{ + auto profileRoot = createNixStateDir() + "/profiles"; + createDirs(profileRoot); + return profileRoot; +} + + Path getDefaultProfile() { - Path profileLink = getHome() + "/.nix-profile"; + Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile"; try { + auto profile = + getuid() == 0 + ? settings.nixStateDir + "/profiles/default" + : profilesDir() + "/profile"; if (!pathExists(profileLink)) { - replaceSymlink( - getuid() == 0 - ? settings.nixStateDir + "/profiles/default" - : fmt("%s/profiles/per-user/%s/profile", settings.nixStateDir, getUserName()), - profileLink); + replaceSymlink(profile, profileLink); } return absPath(readLink(profileLink), dirOf(profileLink)); } catch (Error &) { diff --git a/src/libstore/profiles.hh b/src/libstore/profiles.hh index 408ca039c..fbf95b850 100644 --- a/src/libstore/profiles.hh +++ b/src/libstore/profiles.hh @@ -68,8 +68,13 @@ void lockProfile(PathLocks & lock, const Path & profile); rebuilt. */ std::string optimisticLockProfile(const Path & profile); -/* Resolve ~/.nix-profile. If ~/.nix-profile doesn't exist yet, create - it. */ +/* Creates and returns the path to a directory suitable for storing the user’s + profiles. */ +Path profilesDir(); + +/* Resolve the default profile (~/.nix-profile by default, $XDG_STATE_HOME/ + nix/profile if XDG Base Directory Support is enabled), and create if doesn't + exist */ Path getDefaultProfile(); } diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index 5f1bb73ac..e19d512c0 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -266,6 +266,7 @@ void RemoteStore::setOptions(Connection & conn) overrides.erase(settings.useSubstitutes.name); overrides.erase(loggerSettings.showTrace.name); overrides.erase(settings.experimentalFeatures.name); + overrides.erase(settings.pluginFiles.name); conn.to << overrides.size(); for (auto & i : overrides) conn.to << i.first << i.second.value; diff --git a/src/libstore/s3-binary-cache-store.cc b/src/libstore/s3-binary-cache-store.cc index 844553ad3..8d76eee99 100644 --- a/src/libstore/s3-binary-cache-store.cc +++ b/src/libstore/s3-binary-cache-store.cc @@ -238,7 +238,7 @@ struct S3BinaryCacheStoreImpl : virtual S3BinaryCacheStoreConfig, public virtual void init() override { - if (auto cacheInfo = diskCache->cacheExists(getUri())) { + if (auto cacheInfo = diskCache->upToDateCacheExists(getUri())) { wantMassQuery.setDefault(cacheInfo->wantMassQuery); priority.setDefault(cacheInfo->priority); } else { diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index 353dff9fa..871f2f3be 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -41,6 +41,15 @@ SQLiteError::SQLiteError(const char *path, const char *errMsg, int errNo, int ex throw SQLiteError(path, errMsg, err, exterr, offset, std::move(hf)); } +static void traceSQL(void * x, const char * sql) +{ + // wacky delimiters: + // so that we're quite unambiguous without escaping anything + // notice instead of trace: + // so that this can be enabled without getting the firehose in our face. + notice("SQL<[%1%]>", sql); +}; + SQLite::SQLite(const Path & path, bool create) { // useSQLiteWAL also indicates what virtual file system we need. Using @@ -58,6 +67,11 @@ SQLite::SQLite(const Path & path, bool create) if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK) SQLiteError::throw_(db, "setting timeout"); + if (getEnv("NIX_DEBUG_SQLITE_TRACES") == "1") { + // To debug sqlite statements; trace all of them + sqlite3_trace(db, &traceSQL, nullptr); + } + exec("pragma foreign_keys = 1"); } diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 3c0c26706..b8a77b324 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -99,10 +99,12 @@ StorePath Store::followLinksToStorePath(std::string_view path) const silly, but it's done that way for compatibility). <id> is the name of the output (usually, "out"). - <h2> = base-16 representation of a SHA-256 hash of: + <h2> = base-16 representation of a SHA-256 hash of <s2> + + <s2> = if <type> = "text:...": the string written to the resulting store path - if <type> = "source": + if <type> = "source:...": the serialisation of the path from which this store path is copied, as returned by hashPath() if <type> = "output:<id>": @@ -164,8 +166,8 @@ StorePath Store::makeOutputPath(std::string_view id, /* Stuff the references (if any) into the type. This is a bit - hacky, but we can't put them in `s' since that would be - ambiguous. */ + hacky, but we can't put them in, say, <s2> (per the grammar above) + since that would be ambiguous. */ static std::string makeType( const Store & store, std::string && type, @@ -182,15 +184,15 @@ static std::string makeType( StorePath Store::makeFixedOutputPath(std::string_view name, const FixedOutputInfo & info) const { - if (info.hash.type == htSHA256 && info.method == FileIngestionMethod::Recursive) { - return makeStorePath(makeType(*this, "source", info.references), info.hash, name); + if (info.hash.hash.type == htSHA256 && info.hash.method == FileIngestionMethod::Recursive) { + return makeStorePath(makeType(*this, "source", info.references), info.hash.hash, name); } else { assert(info.references.size() == 0); return makeStorePath("output:out", hashString(htSHA256, "fixed:out:" - + makeFileIngestionPrefix(info.method) - + info.hash.to_string(Base16, true) + ":"), + + makeFileIngestionPrefix(info.hash.method) + + info.hash.hash.to_string(Base16, true) + ":"), name); } } @@ -198,13 +200,13 @@ StorePath Store::makeFixedOutputPath(std::string_view name, const FixedOutputInf StorePath Store::makeTextPath(std::string_view name, const TextInfo & info) const { - assert(info.hash.type == htSHA256); + assert(info.hash.hash.type == htSHA256); return makeStorePath( makeType(*this, "text", StoreReferences { .others = info.references, .self = false, }), - info.hash, + info.hash.hash, name); } @@ -230,7 +232,7 @@ std::pair<StorePath, Hash> Store::computeStorePathForPath(std::string_view name, ? hashPath(hashAlgo, srcPath, filter).first : hashFile(hashAlgo, srcPath); FixedOutputInfo caInfo { - { + .hash = { .method = method, .hash = h, }, @@ -439,7 +441,7 @@ ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath, *this, name, FixedOutputInfo { - { + .hash = { .method = method, .hash = hash, }, @@ -762,7 +764,7 @@ StorePathSet Store::queryValidPaths(const StorePathSet & paths, SubstituteFlag m auto doQuery = [&](const StorePath & path) { checkInterrupt(); - queryPathInfo(path, {[path, this, &state_, &wakeup](std::future<ref<const ValidPathInfo>> fut) { + queryPathInfo(path, {[path, &state_, &wakeup](std::future<ref<const ValidPathInfo>> fut) { auto state(state_.lock()); try { auto info = fut.get(); @@ -996,7 +998,7 @@ void copyStorePath( auto info2 = make_ref<ValidPathInfo>(*info); info2->path = dstStore.makeFixedOutputPathFromCA( info->path.name(), - info->contentAddressWithReferenences().value()); + info->contentAddressWithReferences().value()); if (dstStore.storeDir == srcStore.storeDir) assert(info->path == info2->path); info = info2; @@ -1110,7 +1112,7 @@ std::map<StorePath, StorePath> copyPaths( if (currentPathInfo.ca && currentPathInfo.references.empty()) { storePathForDst = dstStore.makeFixedOutputPathFromCA( currentPathInfo.path.name(), - currentPathInfo.contentAddressWithReferenences().value()); + currentPathInfo.contentAddressWithReferences().value()); if (dstStore.storeDir == srcStore.storeDir) assert(storePathForDst == storePathForSrc); if (storePathForDst != storePathForSrc) diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index 2d252db84..72517c6e4 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -653,9 +653,6 @@ public: return toRealPath(printStorePath(storePath)); } - virtual void createUser(const std::string & userName, uid_t userId) - { } - /* * Synchronises the options of the client with those of the daemon * (a no-op when there’s no daemon) diff --git a/src/libstore/tests/derivation.cc b/src/libstore/tests/derivation.cc new file mode 100644 index 000000000..4dd14dcce --- /dev/null +++ b/src/libstore/tests/derivation.cc @@ -0,0 +1,143 @@ +#include <nlohmann/json.hpp> +#include <gtest/gtest.h> + +#include "derivations.hh" + +#include "tests/libstore.hh" + +namespace nix { + +class DerivationTest : public LibStoreTest +{ +}; + +#define TEST_JSON(TYPE, NAME, STR, VAL, ...) \ + TEST_F(DerivationTest, TYPE ## _ ## NAME ## _to_json) { \ + using nlohmann::literals::operator "" _json; \ + ASSERT_EQ( \ + STR ## _json, \ + (TYPE { VAL }).toJSON(*store __VA_OPT__(,) __VA_ARGS__)); \ + } + +TEST_JSON(DerivationOutput, inputAddressed, + R"({ + "path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name" + })", + (DerivationOutput::InputAddressed { + .path = store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name"), + }), + "drv-name", "output-name") + +TEST_JSON(DerivationOutput, caFixed, + R"({ + "hashAlgo": "r:sha256", + "hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f", + "path": "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-drv-name-output-name" + })", + (DerivationOutput::CAFixed { + .ca = FixedOutputInfo { + .hash = { + .method = FileIngestionMethod::Recursive, + .hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="), + }, + .references = {}, + }, + }), + "drv-name", "output-name") + +TEST_JSON(DerivationOutput, caFixedText, + R"({ + "hashAlgo": "text:sha256", + "hash": "894517c9163c896ec31a2adbd33c0681fd5f45b2c0ef08a64c92a03fb97f390f", + "path": "/nix/store/6s1zwabh956jvhv4w9xcdb5jiyanyxg1-drv-name-output-name" + })", + (DerivationOutput::CAFixed { + .ca = TextInfo { + .hash = { + .hash = Hash::parseAnyPrefixed("sha256-iUUXyRY8iW7DGirb0zwGgf1fRbLA7wimTJKgP7l/OQ8="), + }, + .references = {}, + }, + }), + "drv-name", "output-name") + +TEST_JSON(DerivationOutput, caFloating, + R"({ + "hashAlgo": "r:sha256" + })", + (DerivationOutput::CAFloating { + .method = FileIngestionMethod::Recursive, + .hashType = htSHA256, + }), + "drv-name", "output-name") + +TEST_JSON(DerivationOutput, deferred, + R"({ })", + DerivationOutput::Deferred { }, + "drv-name", "output-name") + +TEST_JSON(DerivationOutput, impure, + R"({ + "hashAlgo": "r:sha256", + "impure": true + })", + (DerivationOutput::Impure { + .method = FileIngestionMethod::Recursive, + .hashType = htSHA256, + }), + "drv-name", "output-name") + +TEST_JSON(Derivation, impure, + R"({ + "inputSrcs": [ + "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1" + ], + "inputDrvs": { + "/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": [ + "cat", + "dog" + ] + }, + "system": "wasm-sel4", + "builder": "foo", + "args": [ + "bar", + "baz" + ], + "env": { + "BIG_BAD": "WOLF" + }, + "outputs": {} + })", + ({ + Derivation drv; + drv.inputSrcs = { + store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"), + }; + drv.inputDrvs = { + { + store->parseStorePath("/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv"), + { + "cat", + "dog", + }, + } + }; + drv.platform = "wasm-sel4"; + drv.builder = "foo"; + drv.args = { + "bar", + "baz", + }; + drv.env = { + { + "BIG_BAD", + "WOLF", + }, + }; + drv; + })) + +#undef TEST_JSON + +} diff --git a/src/libstore/tests/derived-path.cc b/src/libstore/tests/derived-path.cc new file mode 100644 index 000000000..d1ac2c5e7 --- /dev/null +++ b/src/libstore/tests/derived-path.cc @@ -0,0 +1,62 @@ +#include <regex> + +#include <nlohmann/json.hpp> +#include <gtest/gtest.h> +#include <rapidcheck/gtest.h> + +#include "tests/derived-path.hh" +#include "tests/libstore.hh" + +namespace rc { +using namespace nix; + +Gen<DerivedPath::Opaque> Arbitrary<DerivedPath::Opaque>::arbitrary() +{ + return gen::just(DerivedPath::Opaque { + .path = *gen::arbitrary<StorePath>(), + }); +} + +Gen<DerivedPath::Built> Arbitrary<DerivedPath::Built>::arbitrary() +{ + return gen::just(DerivedPath::Built { + .drvPath = *gen::arbitrary<StorePath>(), + .outputs = *gen::arbitrary<OutputsSpec>(), + }); +} + +Gen<DerivedPath> Arbitrary<DerivedPath>::arbitrary() +{ + switch (*gen::inRange<uint8_t>(0, 1)) { + case 0: + return gen::just<DerivedPath>(*gen::arbitrary<DerivedPath::Opaque>()); + default: + return gen::just<DerivedPath>(*gen::arbitrary<DerivedPath::Built>()); + } +} + +} + +namespace nix { + +class DerivedPathTest : public LibStoreTest +{ +}; + +// FIXME: `RC_GTEST_FIXTURE_PROP` isn't calling `SetUpTestSuite` because it is +// no a real fixture. +// +// See https://github.com/emil-e/rapidcheck/blob/master/doc/gtest.md#rc_gtest_fixture_propfixture-name-args +TEST_F(DerivedPathTest, force_init) +{ +} + +RC_GTEST_FIXTURE_PROP( + DerivedPathTest, + prop_round_rip, + (const DerivedPath & o)) +{ + RC_ASSERT(o == DerivedPath::parse(*store, o.to_string(*store))); +} + +} diff --git a/src/libstore/tests/derived-path.hh b/src/libstore/tests/derived-path.hh new file mode 100644 index 000000000..3bc812440 --- /dev/null +++ b/src/libstore/tests/derived-path.hh @@ -0,0 +1,28 @@ +#pragma once + +#include <rapidcheck/gen/Arbitrary.h> + +#include <derived-path.hh> + +#include "tests/path.hh" +#include "tests/outputs-spec.hh" + +namespace rc { +using namespace nix; + +template<> +struct Arbitrary<DerivedPath::Opaque> { + static Gen<DerivedPath::Opaque> arbitrary(); +}; + +template<> +struct Arbitrary<DerivedPath::Built> { + static Gen<DerivedPath::Built> arbitrary(); +}; + +template<> +struct Arbitrary<DerivedPath> { + static Gen<DerivedPath> arbitrary(); +}; + +} diff --git a/src/libstore/tests/libstoretests.hh b/src/libstore/tests/libstore.hh index 05397659b..05397659b 100644 --- a/src/libstore/tests/libstoretests.hh +++ b/src/libstore/tests/libstore.hh diff --git a/src/libstore/tests/local.mk b/src/libstore/tests/local.mk index a2cf8a0cf..03becc7d1 100644 --- a/src/libstore/tests/local.mk +++ b/src/libstore/tests/local.mk @@ -1,6 +1,20 @@ -check: libstore-tests_RUN +check: libstore-tests-exe_RUN -programs += libstore-tests +programs += libstore-tests-exe + +libstore-tests-exe_NAME = libnixstore-tests + +libstore-tests-exe_DIR := $(d) + +libstore-tests-exe_INSTALL_DIR := + +libstore-tests-exe_LIBS = libstore-tests + +libstore-tests-exe_LDFLAGS := $(GTEST_LIBS) + +libraries += libstore-tests + +libstore-tests_NAME = libnixstore-tests libstore-tests_DIR := $(d) @@ -10,6 +24,6 @@ libstore-tests_SOURCES := $(wildcard $(d)/*.cc) libstore-tests_CXXFLAGS += -I src/libstore -I src/libutil -libstore-tests_LIBS = libstore libutil +libstore-tests_LIBS = libutil-tests libstore libutil libstore-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) diff --git a/src/libstore/tests/nar-info-disk-cache.cc b/src/libstore/tests/nar-info-disk-cache.cc new file mode 100644 index 000000000..b4bdb8329 --- /dev/null +++ b/src/libstore/tests/nar-info-disk-cache.cc @@ -0,0 +1,123 @@ +#include "nar-info-disk-cache.hh" + +#include <gtest/gtest.h> +#include <rapidcheck/gtest.h> +#include "sqlite.hh" +#include <sqlite3.h> + + +namespace nix { + +TEST(NarInfoDiskCacheImpl, create_and_read) { + // This is a large single test to avoid some setup overhead. + + int prio = 12345; + bool wantMassQuery = true; + + Path tmpDir = createTempDir(); + AutoDelete delTmpDir(tmpDir); + Path dbPath(tmpDir + "/test-narinfo-disk-cache.sqlite"); + + int savedId; + int barId; + SQLite db; + SQLiteStmt getIds; + + { + auto cache = getTestNarInfoDiskCache(dbPath); + + // Set up "background noise" and check that different caches receive different ids + { + auto bc1 = cache->createCache("https://bar", "/nix/storedir", wantMassQuery, prio); + auto bc2 = cache->createCache("https://xyz", "/nix/storedir", false, 12); + ASSERT_NE(bc1, bc2); + barId = bc1; + } + + // Check that the fields are saved and returned correctly. This does not test + // the select statement yet, because of in-memory caching. + savedId = cache->createCache("http://foo", "/nix/storedir", wantMassQuery, prio);; + { + auto r = cache->upToDateCacheExists("http://foo"); + ASSERT_TRUE(r); + ASSERT_EQ(r->priority, prio); + ASSERT_EQ(r->wantMassQuery, wantMassQuery); + ASSERT_EQ(savedId, r->id); + } + + // We're going to pay special attention to the id field because we had a bug + // that changed it. + db = SQLite(dbPath); + getIds.create(db, "select id from BinaryCaches where url = 'http://foo'"); + + { + auto q(getIds.use()); + ASSERT_TRUE(q.next()); + ASSERT_EQ(savedId, q.getInt(0)); + ASSERT_FALSE(q.next()); + } + + // Pretend that the caches are older, but keep one up to date, as "background noise" + db.exec("update BinaryCaches set timestamp = timestamp - 1 - 7 * 24 * 3600 where url <> 'https://xyz';"); + + // This shows that the in-memory cache works + { + auto r = cache->upToDateCacheExists("http://foo"); + ASSERT_TRUE(r); + ASSERT_EQ(r->priority, prio); + ASSERT_EQ(r->wantMassQuery, wantMassQuery); + } + } + + { + // We can't clear the in-memory cache, so we use a new cache object. This is + // more realistic anyway. + auto cache2 = getTestNarInfoDiskCache(dbPath); + + { + auto r = cache2->upToDateCacheExists("http://foo"); + ASSERT_FALSE(r); + } + + // "Update", same data, check that the id number is reused + cache2->createCache("http://foo", "/nix/storedir", wantMassQuery, prio); + + { + auto r = cache2->upToDateCacheExists("http://foo"); + ASSERT_TRUE(r); + ASSERT_EQ(r->priority, prio); + ASSERT_EQ(r->wantMassQuery, wantMassQuery); + ASSERT_EQ(r->id, savedId); + } + + { + auto q(getIds.use()); + ASSERT_TRUE(q.next()); + auto currentId = q.getInt(0); + ASSERT_FALSE(q.next()); + ASSERT_EQ(currentId, savedId); + } + + // Check that the fields can be modified, and the id remains the same + { + auto r0 = cache2->upToDateCacheExists("https://bar"); + ASSERT_FALSE(r0); + + cache2->createCache("https://bar", "/nix/storedir", !wantMassQuery, prio + 10); + auto r = cache2->upToDateCacheExists("https://bar"); + ASSERT_EQ(r->wantMassQuery, !wantMassQuery); + ASSERT_EQ(r->priority, prio + 10); + ASSERT_EQ(r->id, barId); + } + + // // Force update (no use case yet; we only retrieve cache metadata when stale based on timestamp) + // { + // cache2->createCache("https://bar", "/nix/storedir", wantMassQuery, prio + 20); + // auto r = cache2->upToDateCacheExists("https://bar"); + // ASSERT_EQ(r->wantMassQuery, wantMassQuery); + // ASSERT_EQ(r->priority, prio + 20); + // } + } +} + +} diff --git a/src/libstore/tests/outputs-spec.cc b/src/libstore/tests/outputs-spec.cc index 06e4cabbd..984d1d963 100644 --- a/src/libstore/tests/outputs-spec.cc +++ b/src/libstore/tests/outputs-spec.cc @@ -2,6 +2,7 @@ #include <nlohmann/json.hpp> #include <gtest/gtest.h> +#include <rapidcheck/gtest.h> namespace nix { @@ -199,3 +200,34 @@ TEST_JSON(ExtendedOutputsSpec, names, R"(["a","b"])", (ExtendedOutputsSpec::Expl #undef TEST_JSON } + +namespace rc { +using namespace nix; + +Gen<OutputsSpec> Arbitrary<OutputsSpec>::arbitrary() +{ + switch (*gen::inRange<uint8_t>(0, 1)) { + case 0: + return gen::just((OutputsSpec) OutputsSpec::All { }); + default: + return gen::just((OutputsSpec) OutputsSpec::Names { + *gen::nonEmpty(gen::container<StringSet>(gen::map( + gen::arbitrary<StorePathName>(), + [](StorePathName n) { return n.name; }))), + }); + } +} + +} + +namespace nix { + +RC_GTEST_PROP( + OutputsSpec, + prop_round_rip, + (const OutputsSpec & o)) +{ + RC_ASSERT(o == OutputsSpec::parse(o.to_string())); +} + +} diff --git a/src/libstore/tests/outputs-spec.hh b/src/libstore/tests/outputs-spec.hh new file mode 100644 index 000000000..2d455c817 --- /dev/null +++ b/src/libstore/tests/outputs-spec.hh @@ -0,0 +1,17 @@ +#pragma once + +#include <rapidcheck/gen/Arbitrary.h> + +#include <outputs-spec.hh> + +#include <tests/path.hh> + +namespace rc { +using namespace nix; + +template<> +struct Arbitrary<OutputsSpec> { + static Gen<OutputsSpec> arbitrary(); +}; + +} diff --git a/src/libstore/tests/path.cc b/src/libstore/tests/path.cc index 8ea252c92..430aa0099 100644 --- a/src/libstore/tests/path.cc +++ b/src/libstore/tests/path.cc @@ -7,7 +7,9 @@ #include "path-regex.hh" #include "store-api.hh" -#include "libstoretests.hh" +#include "tests/hash.hh" +#include "tests/libstore.hh" +#include "tests/path.hh" namespace nix { @@ -73,17 +75,14 @@ void showValue(const StorePath & p, std::ostream & os) { namespace rc { using namespace nix; -template<> -struct Arbitrary<StorePath> { - static Gen<StorePath> arbitrary(); -}; - -Gen<StorePath> Arbitrary<StorePath>::arbitrary() +Gen<StorePathName> Arbitrary<StorePathName>::arbitrary() { - auto len = *gen::inRange<size_t>(1, StorePath::MaxPathLen); + auto len = *gen::inRange<size_t>( + 1, + StorePath::MaxPathLen - std::string_view { HASH_PART }.size()); - std::string pre { HASH_PART "-" }; - pre.reserve(pre.size() + len); + std::string pre; + pre.reserve(len); for (size_t c = 0; c < len; ++c) { switch (auto i = *gen::inRange<uint8_t>(0, 10 + 2 * 26 + 6)) { @@ -118,7 +117,17 @@ Gen<StorePath> Arbitrary<StorePath>::arbitrary() } } - return gen::just(StorePath { pre }); + return gen::just(StorePathName { + .name = std::move(pre), + }); +} + +Gen<StorePath> Arbitrary<StorePath>::arbitrary() +{ + return gen::just(StorePath { + *gen::arbitrary<Hash>(), + (*gen::arbitrary<StorePathName>()).name, + }); } } // namespace rc diff --git a/src/libstore/tests/path.hh b/src/libstore/tests/path.hh new file mode 100644 index 000000000..d7f1a8988 --- /dev/null +++ b/src/libstore/tests/path.hh @@ -0,0 +1,28 @@ +#pragma once + +#include <rapidcheck/gen/Arbitrary.h> + +#include <path.hh> + +namespace nix { + +struct StorePathName { + std::string name; +}; + +} + +namespace rc { +using namespace nix; + +template<> +struct Arbitrary<StorePathName> { + static Gen<StorePathName> arbitrary(); +}; + +template<> +struct Arbitrary<StorePath> { + static Gen<StorePath> arbitrary(); +}; + +} diff --git a/src/libutil/args.cc b/src/libutil/args.cc index 753980fd4..35686a8aa 100644 --- a/src/libutil/args.cc +++ b/src/libutil/args.cc @@ -29,7 +29,15 @@ void Args::removeFlag(const std::string & longName) void Completions::add(std::string completion, std::string description) { - assert(description.find('\n') == std::string::npos); + description = trim(description); + // ellipsize overflowing content on the back of the description + auto end_index = description.find_first_of(".\n"); + if (end_index != std::string::npos) { + auto needs_ellipsis = end_index != description.size() - 1; + description.resize(end_index); + if (needs_ellipsis) + description.append(" [...]"); + } insert(Completion { .completion = completion, .description = description @@ -324,7 +332,7 @@ MultiCommand::MultiCommand(const Commands & commands_) expectArgs({ .label = "subcommand", .optional = true, - .handler = {[=](std::string s) { + .handler = {[=,this](std::string s) { assert(!command); auto i = commands.find(s); if (i == commands.end()) { diff --git a/src/libutil/config.cc b/src/libutil/config.cc index 9bb412b4f..b349f2d80 100644 --- a/src/libutil/config.cc +++ b/src/libutil/config.cc @@ -209,7 +209,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category) .description = fmt("Set the `%s` setting.", name), .category = category, .labels = {"value"}, - .handler = {[=](std::string s) { overridden = true; set(s); }}, + .handler = {[this](std::string s) { overridden = true; set(s); }}, }); if (isAppendable()) @@ -218,7 +218,7 @@ void BaseSetting<T>::convertToArg(Args & args, const std::string & category) .description = fmt("Append to the `%s` setting.", name), .category = category, .labels = {"value"}, - .handler = {[=](std::string s) { overridden = true; set(s, true); }}, + .handler = {[this](std::string s) { overridden = true; set(s, true); }}, }); } @@ -270,13 +270,13 @@ template<> void BaseSetting<bool>::convertToArg(Args & args, const std::string & .longName = name, .description = fmt("Enable the `%s` setting.", name), .category = category, - .handler = {[=]() { override(true); }} + .handler = {[this]() { override(true); }} }); args.addFlag({ .longName = "no-" + name, .description = fmt("Disable the `%s` setting.", name), .category = category, - .handler = {[=]() { override(false); }} + .handler = {[this]() { override(false); }} }); } diff --git a/src/libutil/config.hh b/src/libutil/config.hh index 79ec0f9cf..7ac43c854 100644 --- a/src/libutil/config.hh +++ b/src/libutil/config.hh @@ -250,11 +250,15 @@ public: operator const T &() const { return value; } operator T &() { return value; } const T & get() const { return value; } - bool operator ==(const T & v2) const { return value == v2; } - bool operator !=(const T & v2) const { return value != v2; } - void operator =(const T & v) { assign(v); } + template<typename U> + bool operator ==(const U & v2) const { return value == v2; } + template<typename U> + bool operator !=(const U & v2) const { return value != v2; } + template<typename U> + void operator =(const U & v) { assign(v); } virtual void assign(const T & v) { value = v; } - void setDefault(const T & v) { if (!overridden) value = v; } + template<typename U> + void setDefault(const U & v) { if (!overridden) value = v; } void set(const std::string & str, bool append = false) override; diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 7d236028c..0ebeaba61 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -74,6 +74,8 @@ struct AbstractPos virtual void print(std::ostream & out) const = 0; std::optional<LinesOfCode> getCodeLines() const; + + virtual ~AbstractPos() = default; }; std::ostream & operator << (std::ostream & str, const AbstractPos & pos); diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index e0902971e..58d762ebb 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -16,6 +16,7 @@ std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = { { Xp::ReplFlake, "repl-flake" }, { Xp::AutoAllocateUids, "auto-allocate-uids" }, { Xp::Cgroups, "cgroups" }, + { Xp::DiscardReferences, "discard-references" }, }; const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name) diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index af775feb0..ac372e03e 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -25,6 +25,7 @@ enum struct ExperimentalFeature ReplFlake, AutoAllocateUids, Cgroups, + DiscardReferences, }; /** diff --git a/src/libutil/namespaces.cc b/src/libutil/namespaces.cc new file mode 100644 index 000000000..f66accb10 --- /dev/null +++ b/src/libutil/namespaces.cc @@ -0,0 +1,97 @@ +#if __linux__ + +#include "namespaces.hh" +#include "util.hh" +#include "finally.hh" + +#include <sys/mount.h> + +namespace nix { + +bool userNamespacesSupported() +{ + static auto res = [&]() -> bool + { + if (!pathExists("/proc/self/ns/user")) { + debug("'/proc/self/ns/user' does not exist; your kernel was likely built without CONFIG_USER_NS=y"); + return false; + } + + Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces"; + if (!pathExists(maxUserNamespaces) || + trim(readFile(maxUserNamespaces)) == "0") + { + debug("user namespaces appear to be disabled; check '/proc/sys/user/max_user_namespaces'"); + return false; + } + + Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone"; + if (pathExists(procSysKernelUnprivilegedUsernsClone) + && trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0") + { + debug("user namespaces appear to be disabled; check '/proc/sys/kernel/unprivileged_userns_clone'"); + return false; + } + + try { + Pid pid = startProcess([&]() + { + _exit(0); + }, { + .cloneFlags = CLONE_NEWUSER + }); + + auto r = pid.wait(); + assert(!r); + } catch (SysError & e) { + debug("user namespaces do not work on this system: %s", e.msg()); + return false; + } + + return true; + }(); + return res; +} + +bool mountAndPidNamespacesSupported() +{ + static auto res = [&]() -> bool + { + try { + + Pid pid = startProcess([&]() + { + /* Make sure we don't remount the parent's /proc. */ + if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1) + _exit(1); + + /* Test whether we can remount /proc. The kernel disallows + this if /proc is not fully visible, i.e. if there are + filesystems mounted on top of files inside /proc. See + https://lore.kernel.org/lkml/87tvsrjai0.fsf@xmission.com/T/. */ + if (mount("none", "/proc", "proc", 0, 0) == -1) + _exit(2); + + _exit(0); + }, { + .cloneFlags = CLONE_NEWNS | CLONE_NEWPID | (userNamespacesSupported() ? CLONE_NEWUSER : 0) + }); + + if (pid.wait()) { + debug("PID namespaces do not work on this system: cannot remount /proc"); + return false; + } + + } catch (SysError & e) { + debug("mount namespaces do not work on this system: %s", e.msg()); + return false; + } + + return true; + }(); + return res; +} + +} + +#endif diff --git a/src/libutil/namespaces.hh b/src/libutil/namespaces.hh new file mode 100644 index 000000000..e82379b9c --- /dev/null +++ b/src/libutil/namespaces.hh @@ -0,0 +1,13 @@ +#pragma once + +namespace nix { + +#if __linux__ + +bool userNamespacesSupported(); + +bool mountAndPidNamespacesSupported(); + +#endif + +} diff --git a/src/libutil/tests/hash.cc b/src/libutil/tests/hash.cc index 412c03030..e4e928b3b 100644 --- a/src/libutil/tests/hash.cc +++ b/src/libutil/tests/hash.cc @@ -1,5 +1,12 @@ -#include "hash.hh" +#include <regex> + +#include <nlohmann/json.hpp> #include <gtest/gtest.h> +#include <rapidcheck/gtest.h> + +#include <hash.hh> + +#include "tests/hash.hh" namespace nix { @@ -73,3 +80,16 @@ namespace nix { "c7d329eeb6dd26545e96e55b874be909"); } } + +namespace rc { +using namespace nix; + +Gen<Hash> Arbitrary<Hash>::arbitrary() +{ + Hash hash(htSHA1); + for (size_t i = 0; i < hash.hashSize; ++i) + hash.hash[i] = *gen::arbitrary<uint8_t>(); + return gen::just(hash); +} + +} diff --git a/src/libutil/tests/hash.hh b/src/libutil/tests/hash.hh new file mode 100644 index 000000000..9e9650e6e --- /dev/null +++ b/src/libutil/tests/hash.hh @@ -0,0 +1,15 @@ +#pragma once + +#include <rapidcheck/gen/Arbitrary.h> + +#include <hash.hh> + +namespace rc { +using namespace nix; + +template<> +struct Arbitrary<Hash> { + static Gen<Hash> arbitrary(); +}; + +} diff --git a/src/libutil/tests/local.mk b/src/libutil/tests/local.mk index 815e18560..167915439 100644 --- a/src/libutil/tests/local.mk +++ b/src/libutil/tests/local.mk @@ -2,14 +2,28 @@ check: libutil-tests_RUN programs += libutil-tests +libutil-tests-exe_NAME = libnixutil-tests + +libutil-tests-exe_DIR := $(d) + +libutil-tests-exe_INSTALL_DIR := + +libutil-tests-exe_LIBS = libutil-tests + +libutil-tests-exe_LDFLAGS := $(GTEST_LIBS) + +libraries += libutil-tests + +libutil-tests_NAME = libnixutil-tests + libutil-tests_DIR := $(d) libutil-tests_INSTALL_DIR := libutil-tests_SOURCES := $(wildcard $(d)/*.cc) -libutil-tests_CXXFLAGS += -I src/libutil -I src/libexpr +libutil-tests_CXXFLAGS += -I src/libutil libutil-tests_LIBS = libutil -libutil-tests_LDFLAGS := $(GTEST_LIBS) +libutil-tests_LDFLAGS := -lrapidcheck $(GTEST_LIBS) diff --git a/src/libutil/tests/url.cc b/src/libutil/tests/url.cc index c3b233797..a908631e6 100644 --- a/src/libutil/tests/url.cc +++ b/src/libutil/tests/url.cc @@ -99,6 +99,27 @@ namespace nix { ASSERT_EQ(parsed, expected); } + TEST(parseURL, parsesFilePlusHttpsUrl) { + auto s = "file+https://www.example.org/video.mp4"; + auto parsed = parseURL(s); + + ParsedURL expected { + .url = "file+https://www.example.org/video.mp4", + .base = "https://www.example.org/video.mp4", + .scheme = "file+https", + .authority = "www.example.org", + .path = "/video.mp4", + .query = (StringMap) { }, + .fragment = "", + }; + + ASSERT_EQ(parsed, expected); + } + + TEST(parseURL, rejectsAuthorityInUrlsWithFileTransportation) { + auto s = "file://www.example.org/video.mp4"; + ASSERT_THROW(parseURL(s), Error); + } TEST(parseURL, parseIPv4Address) { auto s = "http://127.0.0.1:8080/file.tar.gz?download=fast&when=now#hello"; @@ -281,4 +302,37 @@ namespace nix { ASSERT_EQ(d, s); } + + /* ---------------------------------------------------------------------------- + * percentEncode + * --------------------------------------------------------------------------*/ + + TEST(percentEncode, encodesUrlEncodedString) { + std::string s = percentEncode("==@=="); + std::string d = "%3D%3D%40%3D%3D"; + ASSERT_EQ(d, s); + } + + TEST(percentEncode, keepArgument) { + std::string a = percentEncode("abd / def"); + std::string b = percentEncode("abd / def", "/"); + ASSERT_EQ(a, "abd%20%2F%20def"); + ASSERT_EQ(b, "abd%20/%20def"); + } + + TEST(percentEncode, inverseOfDecode) { + std::string original = "%3D%3D%40%3D%3D"; + std::string once = percentEncode(original); + std::string back = percentDecode(once); + + ASSERT_EQ(back, original); + } + + TEST(percentEncode, trailingPercent) { + std::string s = percentEncode("==@==%"); + std::string d = "%3D%3D%40%3D%3D%25"; + + ASSERT_EQ(d, s); + } + } diff --git a/src/libutil/url.cc b/src/libutil/url.cc index 5b7abeb49..9e44241ac 100644 --- a/src/libutil/url.cc +++ b/src/libutil/url.cc @@ -30,13 +30,13 @@ ParsedURL parseURL(const std::string & url) auto & query = match[6]; auto & fragment = match[7]; - auto isFile = scheme.find("file") != std::string::npos; + auto transportIsFile = parseUrlScheme(scheme).transport == "file"; - if (authority && *authority != "" && isFile) + if (authority && *authority != "" && transportIsFile) throw BadURL("file:// URL '%s' has unexpected authority '%s'", url, *authority); - if (isFile && path.empty()) + if (transportIsFile && path.empty()) path = "/"; return ParsedURL{ @@ -88,17 +88,22 @@ std::map<std::string, std::string> decodeQuery(const std::string & query) return result; } -std::string percentEncode(std::string_view s) +const static std::string allowedInQuery = ":@/?"; +const static std::string allowedInPath = ":@/"; + +std::string percentEncode(std::string_view s, std::string_view keep) { std::string res; for (auto & c : s) + // unreserved + keep if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') - || strchr("-._~!$&'()*+,;=:@", c)) + || strchr("-._~", c) + || keep.find(c) != std::string::npos) res += c; else - res += fmt("%%%02x", (unsigned int) c); + res += fmt("%%%02X", (unsigned int) c); return res; } @@ -109,9 +114,9 @@ std::string encodeQuery(const std::map<std::string, std::string> & ss) for (auto & [name, value] : ss) { if (!first) res += '&'; first = false; - res += percentEncode(name); + res += percentEncode(name, allowedInQuery); res += '='; - res += percentEncode(value); + res += percentEncode(value, allowedInQuery); } return res; } @@ -122,7 +127,7 @@ std::string ParsedURL::to_string() const scheme + ":" + (authority ? "//" + *authority : "") - + path + + percentEncode(path, allowedInPath) + (query.empty() ? "" : "?" + encodeQuery(query)) + (fragment.empty() ? "" : "#" + percentEncode(fragment)); } diff --git a/src/libutil/url.hh b/src/libutil/url.hh index 2a9fb34c1..ddd673d65 100644 --- a/src/libutil/url.hh +++ b/src/libutil/url.hh @@ -22,6 +22,7 @@ struct ParsedURL MakeError(BadURL, Error); std::string percentDecode(std::string_view in); +std::string percentEncode(std::string_view s, std::string_view keep=""); std::map<std::string, std::string> decodeQuery(const std::string & query); diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 993dc1cb6..885bae69c 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -36,6 +36,7 @@ #ifdef __linux__ #include <sys/prctl.h> #include <sys/resource.h> +#include <sys/mman.h> #include <cmath> #endif @@ -537,6 +538,16 @@ std::string getUserName() return name; } +Path getHomeOf(uid_t userId) +{ + std::vector<char> buf(16384); + struct passwd pwbuf; + struct passwd * pw; + if (getpwuid_r(userId, &pwbuf, buf.data(), buf.size(), &pw) != 0 + || !pw || !pw->pw_dir || !pw->pw_dir[0]) + throw Error("cannot determine user's home directory"); + return pw->pw_dir; +} Path getHome() { @@ -558,13 +569,7 @@ Path getHome() } } if (!homeDir) { - std::vector<char> buf(16384); - struct passwd pwbuf; - struct passwd * pw; - if (getpwuid_r(geteuid(), &pwbuf, buf.data(), buf.size(), &pw) != 0 - || !pw || !pw->pw_dir || !pw->pw_dir[0]) - throw Error("cannot determine user's home directory"); - homeDir = pw->pw_dir; + homeDir = getHomeOf(geteuid()); 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); } @@ -604,6 +609,19 @@ Path getDataDir() return dataDir ? *dataDir : getHome() + "/.local/share"; } +Path getStateDir() +{ + auto stateDir = getEnv("XDG_STATE_HOME"); + return stateDir ? *stateDir : getHome() + "/.local/state"; +} + +Path createNixStateDir() +{ + Path dir = getStateDir() + "/nix"; + createDirs(dir); + return dir; +} + std::optional<Path> getSelfExe() { @@ -1047,9 +1065,17 @@ static pid_t doFork(bool allowVfork, std::function<void()> fun) } +static int childEntry(void * arg) +{ + auto main = (std::function<void()> *) arg; + (*main)(); + return 1; +} + + pid_t startProcess(std::function<void()> fun, const ProcessOptions & options) { - auto wrapper = [&]() { + std::function<void()> wrapper = [&]() { if (!options.allowVfork) logger = makeSimpleLogger(); try { @@ -1069,7 +1095,27 @@ pid_t startProcess(std::function<void()> fun, const ProcessOptions & options) _exit(1); }; - pid_t pid = doFork(options.allowVfork, wrapper); + pid_t pid = -1; + + if (options.cloneFlags) { + #ifdef __linux__ + // Not supported, since then we don't know when to free the stack. + assert(!(options.cloneFlags & CLONE_VM)); + + size_t stackSize = 1 * 1024 * 1024; + auto stack = (char *) mmap(0, stackSize, + PROT_WRITE | PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); + if (stack == MAP_FAILED) throw SysError("allocating stack"); + + Finally freeStack([&]() { munmap(stack, stackSize); }); + + pid = clone(childEntry, stack + stackSize, options.cloneFlags | SIGCHLD, &wrapper); + #else + throw Error("clone flags are only supported on Linux"); + #endif + } else + pid = doFork(options.allowVfork, wrapper); + if (pid == -1) throw SysError("unable to fork"); return pid; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 9b149de80..b5625ecef 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -137,6 +137,9 @@ void deletePath(const Path & path, uint64_t & bytesFreed); std::string getUserName(); +/* Return the given user's home directory from /etc/passwd. */ +Path getHomeOf(uid_t userId); + /* Return $HOME or the user's home directory from /etc/passwd. */ Path getHome(); @@ -155,6 +158,12 @@ Path getDataDir(); /* Return the path of the current executable. */ std::optional<Path> getSelfExe(); +/* Return $XDG_STATE_HOME or $HOME/.local/state. */ +Path getStateDir(); + +/* Create the Nix state directory and return the path to it. */ +Path createNixStateDir(); + /* Create a directory and all its parents, if necessary. Returns the list of created directories, in order of creation. */ Paths createDirs(const Path & path); @@ -298,6 +307,7 @@ struct ProcessOptions bool dieWithParent = true; bool runExitHandlers = false; bool allowVfork = false; + int cloneFlags = 0; // use clone() with the specified flags (Linux only) }; pid_t startProcess(std::function<void()> fun, const ProcessOptions & options = ProcessOptions()); diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc index 049838bb1..da76c2ace 100644 --- a/src/nix-build/nix-build.cc +++ b/src/nix-build/nix-build.cc @@ -536,7 +536,9 @@ static void main_nix_build(int argc, char * * argv) "SHELL=%5%; " "BASH=%5%; " "set +e; " - R"s([ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] '; )s" + R"s([ -n "$PS1" -a -z "$NIX_SHELL_PRESERVE_PROMPT" ] && )s" + + (getuid() == 0 ? R"s(PS1='\n\[\033[1;31m\][nix-shell:\w]\$\[\033[0m\] '; )s" + : R"s(PS1='\n\[\033[1;32m\][nix-shell:\w]\$\[\033[0m\] '; )s") + "if [ \"$(type -t runHook)\" = function ]; then runHook shellHook; fi; " "unset NIX_ENFORCE_PURITY; " "shopt -u nullglob; " diff --git a/src/nix-channel/nix-channel.cc b/src/nix-channel/nix-channel.cc index cf52b03b4..338a7d18e 100755 --- a/src/nix-channel/nix-channel.cc +++ b/src/nix-channel/nix-channel.cc @@ -1,9 +1,11 @@ +#include "profiles.hh" #include "shared.hh" #include "globals.hh" #include "filetransfer.hh" #include "store-api.hh" #include "legacy.hh" #include "fetchers.hh" +#include "util.hh" #include <fcntl.h> #include <regex> @@ -162,11 +164,11 @@ static int main_nix_channel(int argc, char ** argv) { // Figure out the name of the `.nix-channels' file to use auto home = getHome(); - channelsList = home + "/.nix-channels"; - nixDefExpr = home + "/.nix-defexpr"; + channelsList = settings.useXDGBaseDirectories ? createNixStateDir() + "/channels" : home + "/.nix-channels"; + nixDefExpr = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : home + "/.nix-defexpr"; // Figure out the name of the channels profile. - profile = fmt("%s/profiles/per-user/%s/channels", settings.nixStateDir, getUserName()); + profile = profilesDir() + "/channels"; enum { cNone, diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 406e548c0..0daf374de 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -1289,7 +1289,7 @@ static void opSwitchProfile(Globals & globals, Strings opFlags, Strings opArgs) throw UsageError("exactly one argument expected"); Path profile = absPath(opArgs.front()); - Path profileLink = getHome() + "/.nix-profile"; + Path profileLink = settings.useXDGBaseDirectories ? createNixStateDir() + "/profile" : getHome() + "/.nix-profile"; switchLink(profileLink, profile); } @@ -1393,7 +1393,10 @@ static int main_nix_env(int argc, char * * argv) Globals globals; globals.instSource.type = srcUnknown; - globals.instSource.nixExprPath = getHome() + "/.nix-defexpr"; + { + Path nixExprPath = settings.useXDGBaseDirectories ? createNixStateDir() + "/defexpr" : getHome() + "/.nix-defexpr"; + globals.instSource.nixExprPath = nixExprPath; + } globals.instSource.systemFilter = "*"; if (!pathExists(globals.instSource.nixExprPath)) { diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 5b261ecc6..735d6a592 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -216,7 +216,7 @@ static void opPrintFixedPath(Strings opFlags, Strings opArgs) std::string name = *i++; cout << fmt("%s\n", store->printStorePath(store->makeFixedOutputPath(name, FixedOutputInfo { - { + .hash = { .method = method, .hash = Hash::parseAny(hash, hashAlgo), }, diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc index 81dbc09a6..16e48a39b 100644 --- a/src/nix/add-to-store.cc +++ b/src/nix/add-to-store.cc @@ -45,7 +45,7 @@ struct CmdAddToStore : MixDryRun, StoreCommand *store, std::move(*namePart), FixedOutputInfo { - { + .hash = { .method = std::move(ingestionMethod), .hash = std::move(hash), }, diff --git a/src/nix/app.cc b/src/nix/app.cc index 08cd0ccd4..5cd65136f 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -1,4 +1,5 @@ #include "installables.hh" +#include "installable-derived-path.hh" #include "store-api.hh" #include "eval-inline.hh" #include "eval-cache.hh" @@ -8,30 +9,6 @@ namespace nix { -struct InstallableDerivedPath : Installable -{ - ref<Store> store; - const DerivedPath derivedPath; - - InstallableDerivedPath(ref<Store> store, const DerivedPath & derivedPath) - : store(store) - , derivedPath(derivedPath) - { - } - - std::string what() const override { return derivedPath.to_string(*store); } - - DerivedPathsWithInfo toDerivedPaths() override - { - return {{derivedPath}}; - } - - std::optional<StorePath> getStorePath() override - { - return std::nullopt; - } -}; - /** * Return the rewrites that are needed to resolve a string whose context is * included in `dependencies`. @@ -146,7 +123,7 @@ App UnresolvedApp::resolve(ref<Store> evalStore, ref<Store> store) for (auto & ctxElt : unresolved.context) installableContext.push_back( - std::make_shared<InstallableDerivedPath>(store, ctxElt)); + std::make_shared<InstallableDerivedPath>(store, DerivedPath { ctxElt })); auto builtContext = Installable::build(evalStore, store, Realise::Outputs, installableContext); res.program = resolveString(*store, unresolved.program, builtContext); diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc index 6ae9460f6..dcf9a6f2d 100644 --- a/src/nix/bundle.cc +++ b/src/nix/bundle.cc @@ -1,4 +1,5 @@ #include "command.hh" +#include "installable-flake.hh" #include "common-args.hh" #include "shared.hh" #include "store-api.hh" diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc index c527fdb0a..a22bccba1 100644 --- a/src/nix/daemon.cc +++ b/src/nix/daemon.cc @@ -34,6 +34,43 @@ using namespace nix; using namespace nix::daemon; +struct AuthorizationSettings : Config { + + Setting<Strings> trustedUsers{ + this, {"root"}, "trusted-users", + R"( + A list of names of users (separated by whitespace) that have + additional rights when connecting to the Nix daemon, such as the + ability to specify additional binary caches, or to import unsigned + NARs. You can also specify groups by prefixing them with `@`; for + instance, `@wheel` means all users in the `wheel` group. The default + is `root`. + + > **Warning** + > + > Adding a user to `trusted-users` is essentially equivalent to + > giving that user root access to the system. For example, the user + > can set `sandbox-paths` and thereby obtain read access to + > directories that are otherwise inacessible to them. + )"}; + + /* ?Who we trust to use the daemon in safe ways */ + Setting<Strings> allowedUsers{ + this, {"*"}, "allowed-users", + R"( + A list of names of users (separated by whitespace) that are allowed + to connect to the Nix daemon. As with the `trusted-users` option, + you can specify groups by prefixing them with `@`. Also, you can + allow all users by specifying `*`. The default is `*`. + + Note that trusted users are always allowed to connect. + )"}; +}; + +AuthorizationSettings authorizationSettings; + +static GlobalConfig::Register rSettings(&authorizationSettings); + #ifndef __linux__ #define SPLICE_F_MOVE 0 static ssize_t splice(int fd_in, void *off_in, int fd_out, void *off_out, size_t len, unsigned int flags) @@ -203,8 +240,8 @@ static void daemonLoop() struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0; std::string group = gr ? gr->gr_name : std::to_string(peer.gid); - Strings trustedUsers = settings.trustedUsers; - Strings allowedUsers = settings.allowedUsers; + Strings trustedUsers = authorizationSettings.trustedUsers; + Strings allowedUsers = authorizationSettings.allowedUsers; if (matchUser(user, group, trustedUsers)) trusted = Trusted; @@ -241,15 +278,7 @@ static void daemonLoop() // Handle the connection. FdSource from(remote.get()); FdSink to(remote.get()); - processConnection(openUncachedStore(), from, to, trusted, NotRecursive, [&](Store & store) { -#if 0 - /* Prevent users from doing something very dangerous. */ - if (geteuid() == 0 && - querySetting("build-users-group", "") == "") - throw Error("if you run 'nix-daemon' as root, then you MUST set 'build-users-group'!"); -#endif - store.createUser(user, peer.uid); - }); + processConnection(openUncachedStore(), from, to, trusted, NotRecursive); exit(0); }, options); @@ -302,7 +331,7 @@ static void runDaemon(bool stdio) /* Auth hook is empty because in this mode we blindly trust the standard streams. Limiting access to those is explicitly not `nix-daemon`'s responsibility. */ - processConnection(openUncachedStore(), from, to, Trusted, NotRecursive, [&](Store & _){}); + processConnection(openUncachedStore(), from, to, Trusted, NotRecursive); } } else daemonLoop(); diff --git a/src/nix/develop.cc b/src/nix/develop.cc index 16bbd8613..9d07a7a85 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -1,5 +1,6 @@ #include "eval.hh" #include "command.hh" +#include "installable-flake.hh" #include "common-args.hh" #include "shared.hh" #include "store-api.hh" diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc index 0621d662c..3489cc132 100644 --- a/src/nix/diff-closures.cc +++ b/src/nix/diff-closures.cc @@ -106,7 +106,7 @@ void printClosureDiff( using namespace nix; -struct CmdDiffClosures : SourceExprCommand +struct CmdDiffClosures : SourceExprCommand, MixOperateOnOptions { std::string _before, _after; diff --git a/src/nix/edit.cc b/src/nix/edit.cc index 76a134b1f..dfe75fbdf 100644 --- a/src/nix/edit.cc +++ b/src/nix/edit.cc @@ -3,6 +3,7 @@ #include "eval.hh" #include "attr-path.hh" #include "progress-bar.hh" +#include "editor-for.hh" #include <unistd.h> diff --git a/src/nix/eval.cc b/src/nix/eval.cc index ccee074e9..a579213fd 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -11,13 +11,13 @@ using namespace nix; -struct CmdEval : MixJSON, InstallableCommand +struct CmdEval : MixJSON, InstallableCommand, MixReadOnlyOption { bool raw = false; std::optional<std::string> apply; std::optional<Path> writeTo; - CmdEval() : InstallableCommand(true /* supportReadOnlyMode */) + CmdEval() : InstallableCommand() { addFlag({ .longName = "raw", diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 020c1b182..053a9c9e1 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -1,4 +1,5 @@ #include "command.hh" +#include "installable-flake.hh" #include "common-args.hh" #include "shared.hh" #include "eval.hh" @@ -966,6 +967,7 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun struct CmdFlakeShow : FlakeCommand, MixJSON { bool showLegacy = false; + bool showAllSystems = false; CmdFlakeShow() { @@ -974,6 +976,11 @@ struct CmdFlakeShow : FlakeCommand, MixJSON .description = "Show the contents of the `legacyPackages` output.", .handler = {&showLegacy, true} }); + addFlag({ + .longName = "all-systems", + .description = "Show the contents of outputs for all systems.", + .handler = {&showAllSystems, true} + }); } std::string description() override @@ -994,6 +1001,62 @@ struct CmdFlakeShow : FlakeCommand, MixJSON auto state = getEvalState(); auto flake = std::make_shared<LockedFlake>(lockFlake()); + auto localSystem = std::string(settings.thisSystem.get()); + + std::function<bool( + eval_cache::AttrCursor & visitor, + const std::vector<Symbol> &attrPath, + const Symbol &attr)> hasContent; + + // For frameworks it's important that structures are as lazy as possible + // to prevent infinite recursions, performance issues and errors that + // aren't related to the thing to evaluate. As a consequence, they have + // to emit more attributes than strictly (sic) necessary. + // However, these attributes with empty values are not useful to the user + // so we omit them. + hasContent = [&]( + eval_cache::AttrCursor & visitor, + const std::vector<Symbol> &attrPath, + const Symbol &attr) -> bool + { + auto attrPath2(attrPath); + attrPath2.push_back(attr); + auto attrPathS = state->symbols.resolve(attrPath2); + const auto & attrName = state->symbols[attr]; + + auto visitor2 = visitor.getAttr(attrName); + + if ((attrPathS[0] == "apps" + || attrPathS[0] == "checks" + || attrPathS[0] == "devShells" + || attrPathS[0] == "legacyPackages" + || attrPathS[0] == "packages") + && (attrPathS.size() == 1 || attrPathS.size() == 2)) { + for (const auto &subAttr : visitor2->getAttrs()) { + if (hasContent(*visitor2, attrPath2, subAttr)) { + return true; + } + } + return false; + } + + if ((attrPathS.size() == 1) + && (attrPathS[0] == "formatter" + || attrPathS[0] == "nixosConfigurations" + || attrPathS[0] == "nixosModules" + || attrPathS[0] == "overlays" + )) { + for (const auto &subAttr : visitor2->getAttrs()) { + if (hasContent(*visitor2, attrPath2, subAttr)) { + return true; + } + } + return false; + } + + // If we don't recognize it, it's probably content + return true; + }; std::function<nlohmann::json( eval_cache::AttrCursor & visitor, @@ -1020,7 +1083,12 @@ struct CmdFlakeShow : FlakeCommand, MixJSON { if (!json) logger->cout("%s", headerPrefix); - auto attrs = visitor.getAttrs(); + std::vector<Symbol> attrs; + for (const auto &attr : visitor.getAttrs()) { + if (hasContent(visitor, attrPath, attr)) + attrs.push_back(attr); + } + for (const auto & [i, attr] : enumerate(attrs)) { const auto & attrName = state->symbols[attr]; bool last = i + 1 == attrs.size(); @@ -1084,10 +1152,18 @@ struct CmdFlakeShow : FlakeCommand, MixJSON || (attrPath.size() == 3 && (attrPathS[0] == "checks" || attrPathS[0] == "packages" || attrPathS[0] == "devShells")) ) { - if (visitor.isDerivation()) - showDerivation(); - else - throw Error("expected a derivation"); + if (!showAllSystems && std::string(attrPathS[1]) != localSystem) { + if (!json) + logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--all-systems' to show)", headerPrefix)); + else { + logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPathS))); + } + } else { + if (visitor.isDerivation()) + showDerivation(); + else + throw Error("expected a derivation"); + } } else if (attrPath.size() > 0 && attrPathS[0] == "hydraJobs") { @@ -1106,6 +1182,12 @@ struct CmdFlakeShow : FlakeCommand, MixJSON else { logger->warn(fmt("%s omitted (use '--legacy' to show)", concatStringsSep(".", attrPathS))); } + } else if (!showAllSystems && std::string(attrPathS[1]) != localSystem) { + if (!json) + logger->cout(fmt("%s " ANSI_WARNING "omitted" ANSI_NORMAL " (use '--all-systems' to show)", headerPrefix)); + else { + logger->warn(fmt("%s omitted (use '--all-systems' to show)", concatStringsSep(".", attrPathS))); + } } else { if (visitor.isDerivation()) showDerivation(); diff --git a/src/nix/ping-store.cc b/src/nix/ping-store.cc index 3c3b7bb45..5c44510ab 100644 --- a/src/nix/ping-store.cc +++ b/src/nix/ping-store.cc @@ -1,10 +1,13 @@ #include "command.hh" #include "shared.hh" #include "store-api.hh" +#include "finally.hh" + +#include <nlohmann/json.hpp> using namespace nix; -struct CmdPingStore : StoreCommand +struct CmdPingStore : StoreCommand, MixJSON { std::string description() override { @@ -20,10 +23,21 @@ struct CmdPingStore : StoreCommand void run(ref<Store> store) override { - notice("Store URL: %s", store->getUri()); - store->connect(); - if (auto version = store->getVersion()) - notice("Version: %s", *version); + if (!json) { + notice("Store URL: %s", store->getUri()); + store->connect(); + if (auto version = store->getVersion()) + notice("Version: %s", *version); + } else { + nlohmann::json res; + Finally printRes([&]() { + logger->cout("%s", res); + }); + res["url"] = store->getUri(); + store->connect(); + if (auto version = store->getVersion()) + res["version"] = *version; + } } }; diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index df9933d29..209517b21 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -68,7 +68,7 @@ std::tuple<StorePath, Hash> prefetchFile( if (expectedHash) { hashType = expectedHash->type; storePath = store->makeFixedOutputPath(*name, FixedOutputInfo { - { + .hash = { .method = ingestionMethod, .hash = *expectedHash, }, diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 345505532..04ac48f00 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -1,4 +1,5 @@ #include "command.hh" +#include "installable-flake.hh" #include "common-args.hh" #include "shared.hh" #include "store-api.hh" @@ -202,7 +203,7 @@ struct ProfileManifest *store, "profile", FixedOutputInfo { - { + .hash = { .method = FileIngestionMethod::Recursive, .hash = narHash, }, diff --git a/src/nix/repl.cc b/src/nix/repl.cc new file mode 100644 index 000000000..679bdea77 --- /dev/null +++ b/src/nix/repl.cc @@ -0,0 +1,95 @@ +#include "eval.hh" +#include "globals.hh" +#include "command.hh" +#include "repl.hh" + +namespace nix { + +struct CmdRepl : InstallablesCommand +{ + CmdRepl() { + evalSettings.pureEval = false; + } + + 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"); + if (this->_installables.size() > 1) + warn("more than one input file is not currently supported"); + auto filePath = this->_installables[0].data(); + file = std::optional(filePath); + _installables.front() = _installables.back(); + _installables.pop_back(); + } + installables = InstallablesCommand::load(); + } + + std::vector<std::string> files; + + Strings getDefaultFlakeAttrPaths() override + { + return {""}; + } + + bool useDefaultInstallables() override + { + return file.has_value() or expr.has_value(); + } + + bool forceImpureByDefault() override + { + return true; + } + + std::string description() override + { + return "start an interactive environment for evaluating Nix expressions"; + } + + std::string doc() override + { + return + #include "repl.md" + ; + } + + void run(ref<Store> store) override + { + auto state = getEvalState(); + auto getValues = [&]()->AbstractNixRepl::AnnotatedValues{ + auto installables = load(); + AbstractNixRepl::AnnotatedValues values; + for (auto & installable: installables){ + auto what = installable->what(); + if (file){ + auto [val, pos] = installable->toValue(*state); + auto what = installable->what(); + state->forceValue(*val, pos); + auto autoArgs = getAutoArgs(*state); + auto valPost = state->allocValue(); + state->autoCallFunction(*autoArgs, *val, *valPost); + state->forceValue(*valPost, pos); + values.push_back( {valPost, what }); + } else { + auto [val, pos] = installable->toValue(*state); + values.push_back( {val, what} ); + } + } + return values; + }; + auto repl = AbstractNixRepl::create( + searchPath, + openStore(), + state, + getValues + ); + repl->autoArgs = getAutoArgs(*repl->state); + repl->initEnv(); + repl->mainLoop(); + } +}; + +static auto rCmdRepl = registerCommand<CmdRepl>("repl"); + +} diff --git a/src/nix/search.cc b/src/nix/search.cc index d2a31607d..4fa1e7837 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -56,8 +56,8 @@ struct CmdSearch : InstallableCommand, MixJSON Strings getDefaultFlakeAttrPaths() override { return { - "packages." + settings.thisSystem.get() + ".", - "legacyPackages." + settings.thisSystem.get() + "." + "packages." + settings.thisSystem.get(), + "legacyPackages." + settings.thisSystem.get() }; } diff --git a/src/nix/show-config.cc b/src/nix/show-config.cc index 29944e748..3530584f9 100644 --- a/src/nix/show-config.cc +++ b/src/nix/show-config.cc @@ -9,15 +9,44 @@ using namespace nix; struct CmdShowConfig : Command, MixJSON { + std::optional<std::string> name; + + CmdShowConfig() { + expectArgs({ + .label = {"name"}, + .optional = true, + .handler = {&name}, + }); + } + std::string description() override { - return "show the Nix configuration"; + return "show the Nix configuration or the value of a specific setting"; } Category category() override { return catUtility; } void run() override { + if (name) { + if (json) { + throw UsageError("'--json' is not supported when specifying a setting name"); + } + + std::map<std::string, Config::SettingInfo> settings; + globalConfig.getSettings(settings); + auto setting = settings.find(*name); + + if (setting == settings.end()) { + throw Error("could not find setting '%1%'", *name); + } else { + const auto & value = setting->second.value; + logger->cout("%s", value); + } + + return; + } + if (json) { // FIXME: use appropriate JSON types (bool, ints, etc). logger->cout("%s", globalConfig.toJSON().dump()); diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc index 9d69c19f2..d1a516cad 100644 --- a/src/nix/show-derivation.cc +++ b/src/nix/show-derivation.cc @@ -54,57 +54,8 @@ struct CmdShowDerivation : InstallablesCommand for (auto & drvPath : drvPaths) { if (!drvPath.isDerivation()) continue; - json& drvObj = jsonRoot[store->printStorePath(drvPath)]; - - auto drv = store->readDerivation(drvPath); - - { - json& outputsObj = drvObj["outputs"]; - outputsObj = json::object(); - for (auto & [_outputName, output] : drv.outputs) { - auto & outputName = _outputName; // work around clang bug - auto& outputObj = outputsObj[outputName]; - outputObj = json::object(); - std::visit(overloaded { - [&](const DerivationOutput::InputAddressed & doi) { - outputObj["path"] = store->printStorePath(doi.path); - }, - [&](const DerivationOutput::CAFixed & dof) { - outputObj["path"] = store->printStorePath(dof.path(*store, drv.name, outputName)); - outputObj["hashAlgo"] = printMethodAlgo(dof.ca); - outputObj["hash"] = getContentAddressHash(dof.ca).to_string(Base16, false); - // FIXME print refs? - }, - [&](const DerivationOutput::CAFloating & dof) { - outputObj["hashAlgo"] = makeContentAddressingPrefix(dof.method) + printHashType(dof.hashType); - }, - [&](const DerivationOutput::Deferred &) {}, - [&](const DerivationOutput::Impure & doi) { - outputObj["hashAlgo"] = makeContentAddressingPrefix(doi.method) + printHashType(doi.hashType); - outputObj["impure"] = true; - }, - }, output.raw()); - } - } - - { - auto& inputsList = drvObj["inputSrcs"]; - inputsList = json::array(); - for (auto & input : drv.inputSrcs) - inputsList.emplace_back(store->printStorePath(input)); - } - - { - auto& inputDrvsObj = drvObj["inputDrvs"]; - inputDrvsObj = json::object(); - for (auto & input : drv.inputDrvs) - inputDrvsObj[store->printStorePath(input.first)] = input.second; - } - - drvObj["system"] = drv.platform; - drvObj["builder"] = drv.builder; - drvObj["args"] = drv.args; - drvObj["env"] = drv.env; + jsonRoot[store->printStorePath(drvPath)] = + store->readDerivation(drvPath).toJSON(*store); } std::cout << jsonRoot.dump(2) << std::endl; } diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc index 76125e5e4..a3a9dc698 100644 --- a/src/nix/why-depends.cc +++ b/src/nix/why-depends.cc @@ -27,7 +27,7 @@ static std::string filterPrintable(const std::string & s) return res; } -struct CmdWhyDepends : SourceExprCommand +struct CmdWhyDepends : SourceExprCommand, MixOperateOnOptions { std::string _package, _dependency; bool all = false; diff --git a/tests/build-delete.sh b/tests/build-delete.sh new file mode 100644 index 000000000..636681f64 --- /dev/null +++ b/tests/build-delete.sh @@ -0,0 +1,56 @@ +source common.sh + +clearStore + +set -o pipefail + +# https://github.com/NixOS/nix/issues/6572 +issue_6572_independent_outputs() { + nix build -f multiple-outputs.nix --json independent --no-link > $TEST_ROOT/independent.json + + # Make sure that 'nix build' can build a derivation that depends on both outputs of another derivation. + p=$(nix build -f multiple-outputs.nix use-independent --no-link --print-out-paths) + nix-store --delete "$p" # Clean up for next test + + # Make sure that 'nix build' tracks input-outputs correctly when a single output is already present. + nix-store --delete "$(jq -r <$TEST_ROOT/independent.json .[0].outputs.first)" + p=$(nix build -f multiple-outputs.nix use-independent --no-link --print-out-paths) + cmp $p <<EOF +first +second +EOF + nix-store --delete "$p" # Clean up for next test + + # Make sure that 'nix build' tracks input-outputs correctly when a single output is already present. + nix-store --delete "$(jq -r <$TEST_ROOT/independent.json .[0].outputs.second)" + p=$(nix build -f multiple-outputs.nix use-independent --no-link --print-out-paths) + cmp $p <<EOF +first +second +EOF + nix-store --delete "$p" # Clean up for next test +} +issue_6572_independent_outputs + + +# https://github.com/NixOS/nix/issues/6572 +issue_6572_dependent_outputs() { + + nix build -f multiple-outputs.nix --json a --no-link > $TEST_ROOT/a.json + + # # Make sure that 'nix build' can build a derivation that depends on both outputs of another derivation. + p=$(nix build -f multiple-outputs.nix use-a --no-link --print-out-paths) + nix-store --delete "$p" # Clean up for next test + + # Make sure that 'nix build' tracks input-outputs correctly when a single output is already present. + nix-store --delete "$(jq -r <$TEST_ROOT/a.json .[0].outputs.second)" + p=$(nix build -f multiple-outputs.nix use-a --no-link --print-out-paths) + cmp $p <<EOF +first +second +EOF + nix-store --delete "$p" # Clean up for next test +} +if isDaemonNewer "2.12pre0"; then + issue_6572_dependent_outputs +fi diff --git a/tests/build.sh b/tests/build.sh index a00fb5232..2dfd43b65 100644 --- a/tests/build.sh +++ b/tests/build.sh @@ -107,62 +107,3 @@ nix build --impure -f multiple-outputs.nix --json e --no-link | jq --exit-status (.drvPath | match(".*multiple-outputs-e.drv")) and (.outputs | keys == ["a_a", "b"])) ' - -testNormalization () { - clearStore - outPath=$(nix-build ./simple.nix --no-out-link) - test "$(stat -c %Y $outPath)" -eq 1 -} - -testNormalization - -# https://github.com/NixOS/nix/issues/6572 -issue_6572_independent_outputs() { - nix build -f multiple-outputs.nix --json independent --no-link > $TEST_ROOT/independent.json - - # Make sure that 'nix build' can build a derivation that depends on both outputs of another derivation. - p=$(nix build -f multiple-outputs.nix use-independent --no-link --print-out-paths) - nix-store --delete "$p" # Clean up for next test - - # Make sure that 'nix build' tracks input-outputs correctly when a single output is already present. - nix-store --delete "$(jq -r <$TEST_ROOT/independent.json .[0].outputs.first)" - p=$(nix build -f multiple-outputs.nix use-independent --no-link --print-out-paths) - cmp $p <<EOF -first -second -EOF - nix-store --delete "$p" # Clean up for next test - - # Make sure that 'nix build' tracks input-outputs correctly when a single output is already present. - nix-store --delete "$(jq -r <$TEST_ROOT/independent.json .[0].outputs.second)" - p=$(nix build -f multiple-outputs.nix use-independent --no-link --print-out-paths) - cmp $p <<EOF -first -second -EOF - nix-store --delete "$p" # Clean up for next test -} -issue_6572_independent_outputs - - -# https://github.com/NixOS/nix/issues/6572 -issue_6572_dependent_outputs() { - - nix build -f multiple-outputs.nix --json a --no-link > $TEST_ROOT/a.json - - # # Make sure that 'nix build' can build a derivation that depends on both outputs of another derivation. - p=$(nix build -f multiple-outputs.nix use-a --no-link --print-out-paths) - nix-store --delete "$p" # Clean up for next test - - # Make sure that 'nix build' tracks input-outputs correctly when a single output is already present. - nix-store --delete "$(jq -r <$TEST_ROOT/a.json .[0].outputs.second)" - p=$(nix build -f multiple-outputs.nix use-a --no-link --print-out-paths) - cmp $p <<EOF -first -second -EOF - nix-store --delete "$p" # Clean up for next test -} -if isDaemonNewer "2.12pre0"; then - issue_6572_dependent_outputs -fi diff --git a/tests/ca/build.sh b/tests/ca/build.sh index 92f8b429a..cc225c6c8 100644 --- a/tests/ca/build.sh +++ b/tests/ca/build.sh @@ -3,7 +3,7 @@ source common.sh drv=$(nix-instantiate --experimental-features ca-derivations ./content-addressed.nix -A rootCA --arg seed 1) -nix --experimental-features 'nix-command ca-derivations' show-derivation --derivation "$drv" --arg seed 1 +nix --experimental-features 'nix-command ca-derivations' show-derivation "$drv" --arg seed 1 buildAttr () { local derivationPath=$1 diff --git a/tests/ca/new-build-cmd.sh b/tests/ca/new-build-cmd.sh new file mode 100644 index 000000000..432d4d132 --- /dev/null +++ b/tests/ca/new-build-cmd.sh @@ -0,0 +1,5 @@ +source common.sh + +export NIX_TESTS_CA_BY_DEFAULT=1 +cd .. +source ./build.sh diff --git a/tests/ca/recursive.sh b/tests/ca/recursive.sh index 0354d23b4..cd6736b24 100755 --- a/tests/ca/recursive.sh +++ b/tests/ca/recursive.sh @@ -7,5 +7,3 @@ requireDaemonNewerThan "2.4pre20210623" export NIX_TESTS_CA_BY_DEFAULT=1 cd .. source ./recursive.sh - - diff --git a/tests/ca/text-hashed-output.sh b/tests/ca/text-hashed-output.sh index 1c4d3a438..bbe5de763 100644 --- a/tests/ca/text-hashed-output.sh +++ b/tests/ca/text-hashed-output.sh @@ -16,10 +16,10 @@ sed -i 's/experimental-features = .*/& ca-derivations ca-references/' "$NIX_CONF # - check that the path of the output coincides with that of the original derivation drv=$(nix-instantiate --experimental-features ca-derivations ./text-hashed-output.nix -A root) -nix show-derivation --derivation "$drv" +nix show-derivation "$drv" drvDep=$(nix-instantiate --experimental-features ca-derivations ./text-hashed-output.nix -A dependent) -nix show-derivation --derivation "$drvDep" +nix show-derivation "$drvDep" out1=$(nix-build --experimental-features ca-derivations ./text-hashed-output.nix -A dependent --no-out-link) diff --git a/tests/check-refs.nix b/tests/check-refs.nix index 9d90b0920..99d69a226 100644 --- a/tests/check-refs.nix +++ b/tests/check-refs.nix @@ -67,4 +67,11 @@ rec { disallowedReferences = [test5]; }; + test11 = makeTest 11 { + __structuredAttrs = true; + unsafeDiscardReferences.out = true; + outputChecks.out.allowedReferences = []; + buildCommand = ''echo ${dep} > "''${outputs[out]}"''; + }; + } diff --git a/tests/check-refs.sh b/tests/check-refs.sh index 16bbabc40..65a72552a 100644 --- a/tests/check-refs.sh +++ b/tests/check-refs.sh @@ -40,3 +40,12 @@ nix-build -o $RESULT check-refs.nix -A test7 # test10 should succeed (no disallowed references). nix-build -o $RESULT check-refs.nix -A test10 + +if isDaemonNewer 2.12pre20230103; then + enableFeatures discard-references + restartDaemon + + # test11 should succeed. + test11=$(nix-build -o $RESULT check-refs.nix -A test11) + [[ -z $(nix-store -q --references "$test11") ]] +fi diff --git a/tests/common.sh b/tests/common.sh new file mode 100644 index 000000000..68b90a85f --- /dev/null +++ b/tests/common.sh @@ -0,0 +1,12 @@ +set -e + +if [[ -z "${COMMON_SH_SOURCED-}" ]]; then + +COMMON_SH_SOURCED=1 + +source "$(readlink -f "$(dirname "${BASH_SOURCE[0]}")")/common/vars-and-functions.sh" +if [[ -n "${NIX_DAEMON_PACKAGE:-}" ]]; then + startDaemon +fi + +fi # COMMON_SH_SOURCED diff --git a/tests/common.sh.in b/tests/common/vars-and-functions.sh.in index 73c2d2309..0deef4c1c 100644 --- a/tests/common.sh.in +++ b/tests/common/vars-and-functions.sh.in @@ -1,8 +1,10 @@ set -e -if [[ -z "$COMMON_SH_SOURCED" ]]; then +if [[ -z "${COMMON_VARS_AND_FUNCTIONS_SH_SOURCED-}" ]]; then -COMMON_SH_SOURCED=1 +COMMON_VARS_AND_FUNCTIONS_SH_SOURCED=1 + +export PS4='+(${BASH_SOURCE[0]}:$LINENO) ' export TEST_ROOT=$(realpath ${TMPDIR:-/tmp}/nix-test)/${TEST_NAME:-default} export NIX_STORE_DIR @@ -23,10 +25,12 @@ if [[ -n $NIX_STORE ]]; then fi export _NIX_IN_TEST=$TEST_ROOT/shared export _NIX_TEST_NO_LSOF=1 -export NIX_REMOTE=$NIX_REMOTE_ +export NIX_REMOTE=${NIX_REMOTE_-} unset NIX_PATH export TEST_HOME=$TEST_ROOT/test-home export HOME=$TEST_HOME +unset XDG_STATE_HOME +unset XDG_DATA_HOME unset XDG_CONFIG_HOME unset XDG_CONFIG_DIRS unset XDG_CACHE_HOME @@ -62,8 +66,8 @@ readLink() { } clearProfiles() { - profiles="$NIX_STATE_DIR"/profiles - rm -rf $profiles + profiles="$HOME"/.local/state/nix/profiles + rm -rf "$profiles" } clearStore() { @@ -86,13 +90,14 @@ clearCacheCache() { startDaemon() { # Don’t start the daemon twice, as this would just make it loop indefinitely - if [[ "$NIX_REMOTE" == daemon ]]; then - return + if [[ "${_NIX_TEST_DAEMON_PID-}" != '' ]]; then + return fi # Start the daemon, wait for the socket to appear. rm -f $NIX_DAEMON_SOCKET_PATH - PATH=$DAEMON_PATH nix-daemon& - pidDaemon=$! + PATH=$DAEMON_PATH nix-daemon & + _NIX_TEST_DAEMON_PID=$! + export _NIX_TEST_DAEMON_PID for ((i = 0; i < 300; i++)); do if [[ -S $NIX_DAEMON_SOCKET_PATH ]]; then DAEMON_STARTED=1 @@ -104,25 +109,35 @@ startDaemon() { fail "Didn’t manage to start the daemon" fi trap "killDaemon" EXIT + # Save for if daemon is killed + NIX_REMOTE_OLD=$NIX_REMOTE export NIX_REMOTE=daemon } killDaemon() { - kill $pidDaemon + # Don’t fail trying to stop a non-existant daemon twice + if [[ "${_NIX_TEST_DAEMON_PID-}" == '' ]]; then + return + fi + kill $_NIX_TEST_DAEMON_PID for i in {0..100}; do - kill -0 $pidDaemon 2> /dev/null || break + kill -0 $_NIX_TEST_DAEMON_PID 2> /dev/null || break sleep 0.1 done - kill -9 $pidDaemon 2> /dev/null || true - wait $pidDaemon || true + kill -9 $_NIX_TEST_DAEMON_PID 2> /dev/null || true + wait $_NIX_TEST_DAEMON_PID || true + rm -f $NIX_DAEMON_SOCKET_PATH + # Indicate daemon is stopped + unset _NIX_TEST_DAEMON_PID + # Restore old nix remote + NIX_REMOTE=$NIX_REMOTE_OLD trap "" EXIT } restartDaemon() { - [[ -z "${pidDaemon:-}" ]] && return 0 + [[ -z "${_NIX_TEST_DAEMON_PID:-}" ]] && return 0 killDaemon - unset NIX_REMOTE startDaemon } @@ -186,10 +201,6 @@ enableFeatures() { set -x -if [[ -n "${NIX_DAEMON_PACKAGE:-}" ]]; then - startDaemon -fi - onError() { set +x echo "$0: test failed at:" >&2 @@ -201,4 +212,4 @@ onError() { trap onError ERR -fi # COMMON_SH_SOURCED +fi # COMMON_VARS_AND_FUNCTIONS_SH_SOURCED diff --git a/tests/config.sh b/tests/config.sh index 3d0da3cef..723f575ed 100644 --- a/tests/config.sh +++ b/tests/config.sh @@ -51,3 +51,8 @@ exp_features=$(nix show-config | grep '^experimental-features' | cut -d '=' -f 2 [[ $prev != $exp_cores ]] [[ $exp_cores == "4242" ]] [[ $exp_features == "flakes nix-command" ]] + +# Test that it's possible to retrieve a single setting's value +val=$(nix show-config | grep '^warn-dirty' | cut -d '=' -f 2 | xargs) +val2=$(nix show-config warn-dirty) +[[ $val == $val2 ]] diff --git a/tests/db-migration.sh b/tests/db-migration.sh index 3f9dc8972..92dd4f3ba 100644 --- a/tests/db-migration.sh +++ b/tests/db-migration.sh @@ -9,7 +9,6 @@ fi source common.sh killDaemon -unset NIX_REMOTE # Fill the db using the older Nix PATH_WITH_NEW_NIX="$PATH" diff --git a/tests/export-graph.sh b/tests/export-graph.sh index a1449b34e..4954a6cbc 100644 --- a/tests/export-graph.sh +++ b/tests/export-graph.sh @@ -4,7 +4,7 @@ clearStore clearProfiles checkRef() { - nix-store -q --references $TEST_ROOT/result | grep -q "$1" || fail "missing reference $1" + nix-store -q --references $TEST_ROOT/result | grep -q "$1"'$' || fail "missing reference $1" } # Test the export of the runtime dependency graph. diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh index da09c3f37..a7a8df186 100644 --- a/tests/fetchGit.sh +++ b/tests/fetchGit.sh @@ -237,3 +237,17 @@ rm -rf $repo/.git # should succeed for a repo without commits git init $repo path10=$(nix eval --impure --raw --expr "(builtins.fetchGit \"file://$repo\").outPath") + +# should succeed for a path with a space +# regression test for #7707 +repo="$TEST_ROOT/a b" +git init "$repo" +git -C "$repo" config user.email "foobar@example.com" +git -C "$repo" config user.name "Foobar" + +echo utrecht > "$repo/hello" +touch "$repo/.gitignore" +git -C "$repo" add hello .gitignore +git -C "$repo" commit -m 'Bla1' +cd "$repo" +path11=$(nix eval --impure --raw --expr "(builtins.fetchGit ./.).outPath") diff --git a/tests/fetchGitSubmodules.sh b/tests/fetchGitSubmodules.sh index 50da4cb97..08ccaa3cd 100644 --- a/tests/fetchGitSubmodules.sh +++ b/tests/fetchGitSubmodules.sh @@ -104,3 +104,28 @@ noSubmoduleRepoBaseline=$(nix eval --raw --expr "(builtins.fetchGit { url = file noSubmoduleRepo=$(nix eval --raw --expr "(builtins.fetchGit { url = file://$subRepo; rev = \"$subRev\"; submodules = true; }).outPath") [[ $noSubmoduleRepoBaseline == $noSubmoduleRepo ]] + +# Test relative submodule URLs. +rm $TEST_HOME/.cache/nix/fetcher-cache* +rm -rf $rootRepo/.git $rootRepo/.gitmodules $rootRepo/sub +initGitRepo $rootRepo +git -C $rootRepo submodule add ../gitSubmodulesSub sub +git -C $rootRepo commit -m "Add submodule" +rev2=$(git -C $rootRepo rev-parse HEAD) +pathWithRelative=$(nix eval --raw --expr "(builtins.fetchGit { url = file://$rootRepo; rev = \"$rev2\"; submodules = true; }).outPath") +diff -r -x .gitmodules $pathWithSubmodules $pathWithRelative + +# Test clones that have an upstream with relative submodule URLs. +rm $TEST_HOME/.cache/nix/fetcher-cache* +cloneRepo=$TEST_ROOT/a/b/gitSubmodulesClone # NB /a/b to make the relative path not work relative to $cloneRepo +git clone $rootRepo $cloneRepo +pathIndirect=$(nix eval --raw --expr "(builtins.fetchGit { url = file://$cloneRepo; rev = \"$rev2\"; submodules = true; }).outPath") +[[ $pathIndirect = $pathWithRelative ]] + +# Test that if the clone has the submodule already, we're not fetching +# it again. +git -C $cloneRepo submodule update --init +rm $TEST_HOME/.cache/nix/fetcher-cache* +rm -rf $subRepo +pathSubmoduleGone=$(nix eval --raw --expr "(builtins.fetchGit { url = file://$cloneRepo; rev = \"$rev2\"; submodules = true; }).outPath") +[[ $pathSubmoduleGone = $pathWithRelative ]] diff --git a/tests/flakes/common.sh b/tests/flakes/common.sh index c333733c2..9d79080cd 100644 --- a/tests/flakes/common.sh +++ b/tests/flakes/common.sh @@ -20,9 +20,13 @@ writeSimpleFlake() { foo = import ./simple.nix; default = foo; }; + packages.someOtherSystem = rec { + foo = import ./simple.nix; + default = foo; + }; # To test "nix flake init". - legacyPackages.x86_64-linux.hello = import ./simple.nix; + legacyPackages.$system.hello = import ./simple.nix; }; } EOF diff --git a/tests/flakes/init.sh b/tests/flakes/init.sh index 36cb9956a..2d4c77ba1 100644 --- a/tests/flakes/init.sh +++ b/tests/flakes/init.sh @@ -41,8 +41,8 @@ cat > $templatesDir/trivial/flake.nix <<EOF description = "A flake for building Hello World"; outputs = { self, nixpkgs }: { - packages.x86_64-linux = rec { - hello = nixpkgs.legacyPackages.x86_64-linux.hello; + packages.$system = rec { + hello = nixpkgs.legacyPackages.$system.hello; default = hello; }; }; diff --git a/tests/flakes/inputs.sh b/tests/flakes/inputs.sh new file mode 100644 index 000000000..80620488a --- /dev/null +++ b/tests/flakes/inputs.sh @@ -0,0 +1,80 @@ +source ./common.sh + +requireGit + + +test_subdir_self_path() { + baseDir=$TEST_ROOT/$RANDOM + flakeDir=$baseDir/b-low + mkdir -p $flakeDir + writeSimpleFlake $baseDir + writeSimpleFlake $flakeDir + + echo all good > $flakeDir/message + cat > $flakeDir/flake.nix <<EOF +{ + outputs = inputs: rec { + packages.$system = rec { + default = + assert builtins.readFile ./message == "all good\n"; + assert builtins.readFile (inputs.self + "/message") == "all good\n"; + import ./simple.nix; + }; + }; +} +EOF + ( + nix build $baseDir?dir=b-low --no-link + ) +} +test_subdir_self_path + + +test_git_subdir_self_path() { + repoDir=$TEST_ROOT/repo-$RANDOM + createGitRepo $repoDir + flakeDir=$repoDir/b-low + mkdir -p $flakeDir + writeSimpleFlake $repoDir + writeSimpleFlake $flakeDir + + echo all good > $flakeDir/message + cat > $flakeDir/flake.nix <<EOF +{ + outputs = inputs: rec { + packages.$system = rec { + default = + assert builtins.readFile ./message == "all good\n"; + assert builtins.readFile (inputs.self + "/message") == "all good\n"; + assert inputs.self.outPath == inputs.self.sourceInfo.outPath + "/b-low"; + import ./simple.nix; + }; + }; +} +EOF + ( + cd $flakeDir + git add . + git commit -m init + # nix build + ) + + clientDir=$TEST_ROOT/client-$RANDOM + mkdir -p $clientDir + cat > $clientDir/flake.nix <<EOF +{ + inputs.inp = { + type = "git"; + url = "file://$repoDir"; + dir = "b-low"; + }; + + outputs = inputs: rec { + packages = inputs.inp.packages; + }; +} +EOF + nix build $clientDir --no-link + +} +test_git_subdir_self_path diff --git a/tests/flakes/show.sh b/tests/flakes/show.sh new file mode 100644 index 000000000..dd13264b9 --- /dev/null +++ b/tests/flakes/show.sh @@ -0,0 +1,66 @@ +source ./common.sh + +flakeDir=$TEST_ROOT/flake +mkdir -p "$flakeDir" + +writeSimpleFlake "$flakeDir" +cd "$flakeDir" + + +# By default: Only show the packages content for the current system and no +# legacyPackages at all +nix flake show --json > show-output.json +nix eval --impure --expr ' +let show_output = builtins.fromJSON (builtins.readFile ./show-output.json); +in +assert show_output.packages.someOtherSystem.default == {}; +assert show_output.packages.${builtins.currentSystem}.default.name == "simple"; +assert show_output.legacyPackages.${builtins.currentSystem} == {}; +true +' + +# With `--all-systems`, show the packages for all systems +nix flake show --json --all-systems > show-output.json +nix eval --impure --expr ' +let show_output = builtins.fromJSON (builtins.readFile ./show-output.json); +in +assert show_output.packages.someOtherSystem.default.name == "simple"; +assert show_output.legacyPackages.${builtins.currentSystem} == {}; +true +' + +# With `--legacy`, show the legacy packages +nix flake show --json --legacy > show-output.json +nix eval --impure --expr ' +let show_output = builtins.fromJSON (builtins.readFile ./show-output.json); +in +assert show_output.legacyPackages.${builtins.currentSystem}.hello.name == "simple"; +true +' + +# Test that attributes are only reported when they have actual content +cat >flake.nix <<EOF +{ + description = "Bla bla"; + + outputs = inputs: rec { + apps.$system = { }; + checks.$system = { }; + devShells.$system = { }; + legacyPackages.$system = { }; + packages.$system = { }; + packages.someOtherSystem = { }; + + formatter = { }; + nixosConfigurations = { }; + nixosModules = { }; + }; +} +EOF +nix flake show --json --all-systems > show-output.json +nix eval --impure --expr ' +let show_output = builtins.fromJSON (builtins.readFile ./show-output.json); +in +assert show_output == { }; +true +' diff --git a/tests/init.sh b/tests/init.sh index 3c6d5917d..fea659516 100644..100755 --- a/tests/init.sh +++ b/tests/init.sh @@ -1,8 +1,13 @@ -source common.sh +set -eu -o pipefail + +# Don't start the daemon +source common/vars-and-functions.sh test -n "$TEST_ROOT" if test -d "$TEST_ROOT"; then chmod -R u+w "$TEST_ROOT" + # We would delete any daemon socket, so let's stop the daemon first. + killDaemon rm -rf "$TEST_ROOT" fi mkdir "$TEST_ROOT" diff --git a/tests/installer/default.nix b/tests/installer/default.nix index 32aa7889a..31d83699d 100644 --- a/tests/installer/default.nix +++ b/tests/installer/default.nix @@ -120,7 +120,7 @@ let makeTest = imageName: testName: let image = images.${imageName}; in - with nixpkgsFor.${image.system}; + with nixpkgsFor.${image.system}.native; runCommand "installer-test-${imageName}-${testName}" { buildInputs = [ qemu_kvm openssh ]; diff --git a/tests/lang/eval-fail-foldlStrict-strict-op-application.nix b/tests/lang/eval-fail-foldlStrict-strict-op-application.nix new file mode 100644 index 000000000..1620cc76e --- /dev/null +++ b/tests/lang/eval-fail-foldlStrict-strict-op-application.nix @@ -0,0 +1,5 @@ +# Tests that the result of applying op is forced even if the value is never used +builtins.foldl' + (_: f: f null) + null + [ (_: throw "Not the final value, but is still forced!") (_: 23) ] diff --git a/tests/lang/eval-okay-foldlStrict-lazy-elements.exp b/tests/lang/eval-okay-foldlStrict-lazy-elements.exp new file mode 100644 index 000000000..d81cc0710 --- /dev/null +++ b/tests/lang/eval-okay-foldlStrict-lazy-elements.exp @@ -0,0 +1 @@ +42 diff --git a/tests/lang/eval-okay-foldlStrict-lazy-elements.nix b/tests/lang/eval-okay-foldlStrict-lazy-elements.nix new file mode 100644 index 000000000..c666e07f3 --- /dev/null +++ b/tests/lang/eval-okay-foldlStrict-lazy-elements.nix @@ -0,0 +1,9 @@ +# Tests that the rhs argument of op is not forced unconditionally +let + lst = builtins.foldl' + (acc: x: acc ++ [ x ]) + [ ] + [ 42 (throw "this shouldn't be evaluated") ]; +in + +builtins.head lst diff --git a/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.exp b/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.exp new file mode 100644 index 000000000..d81cc0710 --- /dev/null +++ b/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.exp @@ -0,0 +1 @@ +42 diff --git a/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.nix b/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.nix new file mode 100644 index 000000000..abcd5366a --- /dev/null +++ b/tests/lang/eval-okay-foldlStrict-lazy-initial-accumulator.nix @@ -0,0 +1,6 @@ +# Checks that the nul value for the accumulator is not forced unconditionally. +# Some languages provide a foldl' that is strict in this argument, but Nix does not. +builtins.foldl' + (_: x: x) + (throw "This is never forced") + [ "but the results of applying op are" 42 ] diff --git a/tests/linux-sandbox.sh b/tests/linux-sandbox.sh index 3f304ac2f..e62039567 100644 --- a/tests/linux-sandbox.sh +++ b/tests/linux-sandbox.sh @@ -37,3 +37,6 @@ nix-build check.nix -A nondeterministic --sandbox-paths /nix/store --no-out-link (! nix-build check.nix -A nondeterministic --sandbox-paths /nix/store --no-out-link --check -K 2> $TEST_ROOT/log) if grep -q 'error: renaming' $TEST_ROOT/log; then false; fi grep -q 'may not be deterministic' $TEST_ROOT/log + +# Test that sandboxed builds cannot write to /etc easily +(! nix-build -E 'with import ./config.nix; mkDerivation { name = "etc-write"; buildCommand = "echo > /etc/test"; }' --no-out-link --sandbox-paths /nix/store) diff --git a/tests/local.mk b/tests/local.mk index e4f1c4e89..4003d9447 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -1,9 +1,11 @@ nix_tests = \ + init.sh \ flakes/flakes.sh \ flakes/run.sh \ flakes/mercurial.sh \ flakes/circular.sh \ flakes/init.sh \ + flakes/inputs.sh \ flakes/follow-paths.sh \ flakes/bundle.sh \ flakes/check.sh \ @@ -17,9 +19,11 @@ nix_tests = \ fetchMercurial.sh \ gc-auto.sh \ user-envs.sh \ + user-envs-migration.sh \ binary-cache.sh \ multiple-outputs.sh \ ca/build.sh \ + ca/new-build-cmd.sh \ nix-build.sh \ gc-concurrent.sh \ repair.sh \ @@ -103,6 +107,8 @@ nix_tests = \ ssh-relay.sh \ plugins.sh \ build.sh \ + build-delete.sh \ + output-normalization.sh \ ca/nix-run.sh \ selfref-gc.sh ca/selfref-gc.sh \ db-migration.sh \ @@ -114,6 +120,7 @@ nix_tests = \ store-ping.sh \ fetchClosure.sh \ completions.sh \ + flakes/show.sh \ impure-derivations.sh \ path-from-hash-part.sh \ toString-path.sh @@ -124,9 +131,9 @@ endif install-tests += $(foreach x, $(nix_tests), tests/$(x)) -clean-files += $(d)/common.sh $(d)/config.nix $(d)/ca/config.nix +clean-files += $(d)/tests/common/vars-and-functions.sh $(d)/config.nix $(d)/ca/config.nix -test-deps += tests/common.sh tests/config.nix tests/ca/config.nix +test-deps += tests/common/vars-and-functions.sh tests/config.nix tests/ca/config.nix tests/plugins/libplugintest.$(SO_EXT) ifeq ($(BUILD_SHARED_LIBS), 1) test-deps += tests/plugins/libplugintest.$(SO_EXT) diff --git a/tests/nix-channel.sh b/tests/nix-channel.sh index 54b8f5979..b64283f48 100644 --- a/tests/nix-channel.sh +++ b/tests/nix-channel.sh @@ -12,6 +12,19 @@ nix-channel --remove xyzzy [ -e $TEST_HOME/.nix-channels ] [ "$(cat $TEST_HOME/.nix-channels)" = '' ] +# Test the XDG Base Directories support + +export NIX_CONFIG="use-xdg-base-directories = true" + +nix-channel --add http://foo/bar xyzzy +nix-channel --list | grep -q http://foo/bar +nix-channel --remove xyzzy + +unset NIX_CONFIG + +[ -e $TEST_HOME/.local/state/nix/channels ] +[ "$(cat $TEST_HOME/.local/state/nix/channels)" = '' ] + # Create a channel. rm -rf $TEST_ROOT/foo mkdir -p $TEST_ROOT/foo diff --git a/tests/nix-profile.sh b/tests/nix-profile.sh index 7ba3235fa..266dc9e49 100644 --- a/tests/nix-profile.sh +++ b/tests/nix-profile.sh @@ -56,6 +56,14 @@ nix profile history nix profile history | grep "packages.$system.default: ∅ -> 1.0" nix profile diff-closures | grep 'env-manifest.nix: ε → ∅' +# Test XDG Base Directories support + +export NIX_CONFIG="use-xdg-base-directories = true" +nix profile remove 1 +nix profile install $flake1Dir +[[ $($TEST_HOME/.local/state/nix/profile/bin/hello) = "Hello World" ]] +unset NIX_CONFIG + # Test upgrading a package. printf NixOS > $flake1Dir/who printf 2.0 > $flake1Dir/version diff --git a/tests/nixos/authorization.nix b/tests/nixos/authorization.nix new file mode 100644 index 000000000..7e8744dd9 --- /dev/null +++ b/tests/nixos/authorization.nix @@ -0,0 +1,79 @@ +{ + name = "authorization"; + + nodes.machine = { + virtualisation.writableStore = true; + # TODO add a test without allowed-users setting. allowed-users is uncommon among NixOS users. + nix.settings.allowed-users = ["alice" "bob"]; + nix.settings.trusted-users = ["alice"]; + + users.users.alice.isNormalUser = true; + users.users.bob.isNormalUser = true; + users.users.mallory.isNormalUser = true; + + nix.settings.experimental-features = "nix-command"; + }; + + testScript = + let + pathFour = "/nix/store/20xfy868aiic0r0flgzq4n5dq1yvmxkn-four"; + in + '' + machine.wait_for_unit("multi-user.target") + machine.succeed(""" + exec 1>&2 + echo kSELDhobKaF8/VdxIxdP7EQe+Q > one + diff $(nix store add-file one) one + """) + machine.succeed(""" + su --login alice -c ' + set -x + cd ~ + echo ehHtmfuULXYyBV6NBk6QUi8iE0 > two + ls + diff $(echo $(nix store add-file two)) two' 1>&2 + """) + machine.succeed(""" + su --login bob -c ' + set -x + cd ~ + echo 0Jw8RNp7cK0W2AdNbcquofcOVk > three + diff $(nix store add-file three) three + ' 1>&2 + """) + + # We're going to check that a path is not created + machine.succeed(""" + ! [[ -e ${pathFour} ]] + """) + machine.succeed(""" + su --login mallory -c ' + set -x + cd ~ + echo 5mgtDj0ohrWkT50TLR0f4tIIxY > four; + (! nix store add-file four 2>&1) | grep -F "cannot open connection to remote store" + (! nix store add-file four 2>&1) | grep -F "Connection reset by peer" + ! [[ -e ${pathFour} ]] + ' 1>&2 + """) + + # Check that the file _can_ be added, and matches the expected path we were checking + machine.succeed(""" + exec 1>&2 + echo 5mgtDj0ohrWkT50TLR0f4tIIxY > four + four="$(nix store add-file four)" + diff $four four + diff <(echo $four) <(echo ${pathFour}) + """) + + machine.succeed(""" + su --login alice -c 'nix-store --verify --repair' + """) + + machine.succeed(""" + set -x + su --login bob -c '(! nix-store --verify --repair 2>&1)' | tee diag 1>&2 + grep -F "you are not privileged to repair paths" diag + """) + ''; +} diff --git a/tests/containers.nix b/tests/nixos/containers/containers.nix index a4856b2df..c8ee78a4a 100644 --- a/tests/containers.nix +++ b/tests/nixos/containers/containers.nix @@ -1,12 +1,7 @@ # Test whether we can run a NixOS container inside a Nix build using systemd-nspawn. -{ nixpkgs, system, overlay }: +{ lib, nixpkgs, ... }: -with import (nixpkgs + "/nixos/lib/testing-python.nix") { - inherit system; - extraConfigurations = [ { nixpkgs.overlays = [ overlay ]; } ]; -}; - -makeTest ({ +{ name = "containers"; nodes = @@ -65,4 +60,4 @@ makeTest ({ host.succeed("[[ $(cat ./result/msg) = 'Hello World' ]]") ''; -}) +} diff --git a/tests/id-test.nix b/tests/nixos/containers/id-test.nix index 8eb9d38f9..8eb9d38f9 100644 --- a/tests/id-test.nix +++ b/tests/nixos/containers/id-test.nix diff --git a/tests/systemd-nspawn.nix b/tests/nixos/containers/systemd-nspawn.nix index 424436b3f..f54f32f2a 100644 --- a/tests/systemd-nspawn.nix +++ b/tests/nixos/containers/systemd-nspawn.nix @@ -56,12 +56,12 @@ runCommand "test" # Make /run a tmpfs to shut up a systemd warning. mkdir /run mount -t tmpfs none /run - chmod 0700 /run mount -t cgroup2 none /sys/fs/cgroup mkdir -p $out + chmod +w /etc touch /etc/os-release echo a5ea3f98dedc0278b6f3cc8c37eeaeac > /etc/machine-id diff --git a/tests/github-flakes.nix b/tests/nixos/github-flakes.nix index a8b036b17..e4d347691 100644 --- a/tests/github-flakes.nix +++ b/tests/nixos/github-flakes.nix @@ -1,14 +1,9 @@ -{ nixpkgs, system, overlay }: - -with import (nixpkgs + "/nixos/lib/testing-python.nix") { - inherit system; - extraConfigurations = [ { nixpkgs.overlays = [ overlay ]; } ]; -}; - +{ lib, config, nixpkgs, ... }: let + pkgs = config.nodes.client.nixpkgs.pkgs; # Generate a fake root CA and a fake api.github.com / github.com / channels.nixos.org certificate. - cert = pkgs.runCommand "cert" { buildInputs = [ pkgs.openssl ]; } + cert = pkgs.runCommand "cert" { nativeBuildInputs = [ pkgs.openssl ]; } '' mkdir -p $out @@ -92,8 +87,6 @@ let ''; in -makeTest ( - { name = "github-flakes"; @@ -207,4 +200,4 @@ makeTest ( client.succeed("nix build nixpkgs#fuse --tarball-ttl 0") ''; -}) +} diff --git a/tests/nix-copy-closure.nix b/tests/nixos/nix-copy-closure.nix index 2dc164ae4..66cbfb033 100644 --- a/tests/nix-copy-closure.nix +++ b/tests/nixos/nix-copy-closure.nix @@ -1,13 +1,16 @@ # Test ‘nix-copy-closure’. -{ nixpkgs, system, overlay }: +{ lib, config, nixpkgs, hostPkgs, ... }: -with import (nixpkgs + "/nixos/lib/testing-python.nix") { - inherit system; - extraConfigurations = [ { nixpkgs.overlays = [ overlay ]; } ]; -}; +let + pkgs = config.nodes.client.nixpkgs.pkgs; -makeTest (let pkgA = pkgs.cowsay; pkgB = pkgs.wget; pkgC = pkgs.hello; pkgD = pkgs.tmux; in { + pkgA = pkgs.cowsay; + pkgB = pkgs.wget; + pkgC = pkgs.hello; + pkgD = pkgs.tmux; + +in { name = "nix-copy-closure"; nodes = @@ -74,4 +77,4 @@ makeTest (let pkgA = pkgs.cowsay; pkgB = pkgs.wget; pkgC = pkgs.hello; pkgD = pk # ) # client.succeed("nix-store --check-validity ${pkgC}") ''; -}) +} diff --git a/tests/nss-preload.nix b/tests/nixos/nss-preload.nix index 5a6ff3f68..cef62e95b 100644 --- a/tests/nss-preload.nix +++ b/tests/nixos/nss-preload.nix @@ -1,11 +1,9 @@ -{ nixpkgs, system, overlay }: - -with import (nixpkgs + "/nixos/lib/testing-python.nix") { - inherit system; - extraConfigurations = [ { nixpkgs.overlays = [ overlay ]; } ]; -}; +{ lib, config, nixpkgs, ... }: let + + pkgs = config.nodes.client.nixpkgs.pkgs; + nix-fetch = pkgs.writeText "fetch.nix" '' derivation { # This derivation is an copy from what is available over at @@ -41,9 +39,7 @@ let ''; in -makeTest ( - -rec { +{ name = "nss-preload"; nodes = { @@ -122,4 +118,4 @@ rec { nix-build ${nix-fetch} >&2 """) ''; -}) +} diff --git a/tests/remote-builds.nix b/tests/nixos/remote-builds.nix index 9f88217fe..1c96cc787 100644 --- a/tests/remote-builds.nix +++ b/tests/nixos/remote-builds.nix @@ -1,15 +1,9 @@ # Test Nix's remote build feature. -{ nixpkgs, system, overlay }: - -with import (nixpkgs + "/nixos/lib/testing-python.nix") { - inherit system; - extraConfigurations = [ { nixpkgs.overlays = [ overlay ]; } ]; -}; - -makeTest ( +{ config, lib, hostPkgs, ... }: let + pkgs = config.nodes.client.nixpkgs.pkgs; # The configuration of the remote builders. builder = @@ -17,6 +11,11 @@ let { services.openssh.enable = true; virtualisation.writableStore = true; nix.settings.sandbox = true; + + # Regression test for use of PID namespaces when /proc has + # filesystems mounted on top of it + # (i.e. /proc/sys/fs/binfmt_misc). + boot.binfmt.emulatedSystems = [ "aarch64-linux" ]; }; # Trivial Nix expression to build remotely. @@ -75,7 +74,7 @@ in # Create an SSH key on the client. subprocess.run([ - "${pkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", "" + "${hostPkgs.openssh}/bin/ssh-keygen", "-t", "ed25519", "-f", "key", "-N", "" ], capture_output=True, check=True) client.succeed("mkdir -p -m 700 /root/.ssh") client.copy_from_host("key", "/root/.ssh/id_ed25519") @@ -109,4 +108,4 @@ in builder1.block() client.succeed("nix-build ${expr nodes.client.config 4}") ''; -}) +} diff --git a/tests/setuid.nix b/tests/nixos/setuid.nix index 6784615e4..2b66320dd 100644 --- a/tests/setuid.nix +++ b/tests/nixos/setuid.nix @@ -1,13 +1,12 @@ # Verify that Linux builds cannot create setuid or setgid binaries. -{ nixpkgs, system, overlay }: +{ lib, config, nixpkgs, ... }: -with import (nixpkgs + "/nixos/lib/testing-python.nix") { - inherit system; - extraConfigurations = [ { nixpkgs.overlays = [ overlay ]; } ]; -}; +let + pkgs = config.nodes.machine.nixpkgs.pkgs; -makeTest { +in +{ name = "setuid"; nodes.machine = diff --git a/tests/sourcehut-flakes.nix b/tests/nixos/sourcehut-flakes.nix index b77496ab6..a76fed020 100644 --- a/tests/sourcehut-flakes.nix +++ b/tests/nixos/sourcehut-flakes.nix @@ -1,12 +1,8 @@ -{ nixpkgs, system, overlay }: - -with import (nixpkgs + "/nixos/lib/testing-python.nix") -{ - inherit system; - extraConfigurations = [{ nixpkgs.overlays = [ overlay ]; }]; -}; +{ lib, config, hostPkgs, nixpkgs, ... }: let + pkgs = config.nodes.sourcehut.nixpkgs.pkgs; + # Generate a fake root CA and a fake git.sr.ht certificate. cert = pkgs.runCommand "cert" { buildInputs = [ pkgs.openssl ]; } '' @@ -64,8 +60,6 @@ let in -makeTest ( - { name = "sourcehut-flakes"; @@ -164,4 +158,4 @@ makeTest ( client.succeed("nix build nixpkgs#fuse --tarball-ttl 0") ''; - }) +} diff --git a/tests/output-normalization.sh b/tests/output-normalization.sh new file mode 100644 index 000000000..0f6df5e31 --- /dev/null +++ b/tests/output-normalization.sh @@ -0,0 +1,9 @@ +source common.sh + +testNormalization () { + clearStore + outPath=$(nix-build ./simple.nix --no-out-link) + test "$(stat -c %Y $outPath)" -eq 1 +} + +testNormalization diff --git a/tests/remote-store.sh b/tests/remote-store.sh index 31210ab47..1ae126794 100644 --- a/tests/remote-store.sh +++ b/tests/remote-store.sh @@ -30,7 +30,3 @@ NIX_REMOTE= nix-store --dump-db > $TEST_ROOT/d2 cmp $TEST_ROOT/d1 $TEST_ROOT/d2 killDaemon - -user=$(whoami) -[ -e $NIX_STATE_DIR/gcroots/per-user/$user ] -[ -e $NIX_STATE_DIR/profiles/per-user/$user ] diff --git a/tests/store-ping.sh b/tests/store-ping.sh index f9427cf0a..9846c7d3d 100644 --- a/tests/store-ping.sh +++ b/tests/store-ping.sh @@ -1,13 +1,17 @@ source common.sh STORE_INFO=$(nix store ping 2>&1) +STORE_INFO_JSON=$(nix store ping --json) echo "$STORE_INFO" | grep "Store URL: ${NIX_REMOTE}" if [[ -v NIX_DAEMON_PACKAGE ]] && isDaemonNewer "2.7.0pre20220126"; then DAEMON_VERSION=$($NIX_DAEMON_PACKAGE/bin/nix-daemon --version | cut -d' ' -f3) echo "$STORE_INFO" | grep "Version: $DAEMON_VERSION" + [[ "$(echo "$STORE_INFO_JSON" | jq -r ".version")" == "$DAEMON_VERSION" ]] fi expect 127 NIX_REMOTE=unix:$PWD/store nix store ping || \ fail "nix store ping on a non-existent store should fail" + +[[ "$(echo "$STORE_INFO_JSON" | jq -r ".url")" == "${NIX_REMOTE:-local}" ]] diff --git a/tests/user-envs-migration.sh b/tests/user-envs-migration.sh new file mode 100644 index 000000000..467c28fbb --- /dev/null +++ b/tests/user-envs-migration.sh @@ -0,0 +1,35 @@ +# Test that the migration of user environments +# (https://github.com/NixOS/nix/pull/5226) does preserve everything + +source common.sh + +if isDaemonNewer "2.4pre20211005"; then + exit 99 +fi + + +killDaemon +unset NIX_REMOTE + +clearStore +clearProfiles +rm -rf ~/.nix-profile + +# Fill the environment using the older Nix +PATH_WITH_NEW_NIX="$PATH" +export PATH="$NIX_DAEMON_PACKAGE/bin:$PATH" + +nix-env -f user-envs.nix -i foo-1.0 +nix-env -f user-envs.nix -i bar-0.1 + +# Migrate to the new profile dir, and ensure that everything’s there +export PATH="$PATH_WITH_NEW_NIX" +nix-env -q # Trigger the migration +( [[ -L ~/.nix-profile ]] && \ + [[ $(readlink ~/.nix-profile) == ~/.local/share/nix/profiles/profile ]] ) || \ + fail "The nix profile should point to the new location" + +(nix-env -q | grep foo && nix-env -q | grep bar && \ + [[ -e ~/.nix-profile/bin/foo ]] && \ + [[ $(nix-env --list-generations | wc -l) == 2 ]]) || + fail "The nix profile should have the same content as before the migration" |