1
0
Fork 0
mirror of https://github.com/NixOS/nix synced 2025-06-25 10:41:16 +02:00
This commit is contained in:
Graham Christensen 2025-05-22 15:24:55 -04:00 committed by GitHub
commit 5eb4e6aecd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
447 changed files with 22760 additions and 23309 deletions

2
.git-blame-ignore-revs Normal file
View file

@ -0,0 +1,2 @@
# bulk initial re-formatting with clang-format
9535f8bdc34e8e62d53608a027bc95fd0f07e55f # !autorebase ./maintainers/format.sh --until-stable

View file

@ -77,469 +77,6 @@
# Don't format vendored code # Don't format vendored code
''^doc/manual/redirects\.js$'' ''^doc/manual/redirects\.js$''
''^doc/manual/theme/highlight\.js$'' ''^doc/manual/theme/highlight\.js$''
# We haven't applied formatting to these files yet
''^doc/manual/redirects\.js$''
''^doc/manual/theme/highlight\.js$''
''^precompiled-headers\.h$''
''^src/build-remote/build-remote\.cc$''
''^src/libcmd/built-path\.cc$''
''^src/libcmd/include/nix/cmd/built-path\.hh$''
''^src/libcmd/common-eval-args\.cc$''
''^src/libcmd/include/nix/cmd/common-eval-args\.hh$''
''^src/libcmd/editor-for\.cc$''
''^src/libcmd/installable-attr-path\.cc$''
''^src/libcmd/include/nix/cmd/installable-attr-path\.hh$''
''^src/libcmd/installable-derived-path\.cc$''
''^src/libcmd/include/nix/cmd/installable-derived-path\.hh$''
''^src/libcmd/installable-flake\.cc$''
''^src/libcmd/include/nix/cmd/installable-flake\.hh$''
''^src/libcmd/installable-value\.cc$''
''^src/libcmd/include/nix/cmd/installable-value\.hh$''
''^src/libcmd/installables\.cc$''
''^src/libcmd/include/nix/cmd/installables\.hh$''
''^src/libcmd/include/nix/cmd/legacy\.hh$''
''^src/libcmd/markdown\.cc$''
''^src/libcmd/misc-store-flags\.cc$''
''^src/libcmd/repl-interacter\.cc$''
''^src/libcmd/include/nix/cmd/repl-interacter\.hh$''
''^src/libcmd/repl\.cc$''
''^src/libcmd/include/nix/cmd/repl\.hh$''
''^src/libexpr-c/nix_api_expr\.cc$''
''^src/libexpr-c/nix_api_external\.cc$''
''^src/libexpr/attr-path\.cc$''
''^src/libexpr/include/nix/expr/attr-path\.hh$''
''^src/libexpr/attr-set\.cc$''
''^src/libexpr/include/nix/expr/attr-set\.hh$''
''^src/libexpr/eval-cache\.cc$''
''^src/libexpr/include/nix/expr/eval-cache\.hh$''
''^src/libexpr/eval-error\.cc$''
''^src/libexpr/include/nix/expr/eval-inline\.hh$''
''^src/libexpr/eval-settings\.cc$''
''^src/libexpr/include/nix/expr/eval-settings\.hh$''
''^src/libexpr/eval\.cc$''
''^src/libexpr/include/nix/expr/eval\.hh$''
''^src/libexpr/function-trace\.cc$''
''^src/libexpr/include/nix/expr/gc-small-vector\.hh$''
''^src/libexpr/get-drvs\.cc$''
''^src/libexpr/include/nix/expr/get-drvs\.hh$''
''^src/libexpr/json-to-value\.cc$''
''^src/libexpr/nixexpr\.cc$''
''^src/libexpr/include/nix/expr/nixexpr\.hh$''
''^src/libexpr/include/nix/expr/parser-state\.hh$''
''^src/libexpr/primops\.cc$''
''^src/libexpr/include/nix/expr/primops\.hh$''
''^src/libexpr/primops/context\.cc$''
''^src/libexpr/primops/fetchClosure\.cc$''
''^src/libexpr/primops/fetchMercurial\.cc$''
''^src/libexpr/primops/fetchTree\.cc$''
''^src/libexpr/primops/fromTOML\.cc$''
''^src/libexpr/print-ambiguous\.cc$''
''^src/libexpr/include/nix/expr/print-ambiguous\.hh$''
''^src/libexpr/include/nix/expr/print-options\.hh$''
''^src/libexpr/print\.cc$''
''^src/libexpr/include/nix/expr/print\.hh$''
''^src/libexpr/search-path\.cc$''
''^src/libexpr/include/nix/expr/symbol-table\.hh$''
''^src/libexpr/value-to-json\.cc$''
''^src/libexpr/include/nix/expr/value-to-json\.hh$''
''^src/libexpr/value-to-xml\.cc$''
''^src/libexpr/include/nix/expr/value-to-xml\.hh$''
''^src/libexpr/include/nix/expr/value\.hh$''
''^src/libexpr/value/context\.cc$''
''^src/libexpr/include/nix/expr/value/context\.hh$''
''^src/libfetchers/attrs\.cc$''
''^src/libfetchers/cache\.cc$''
''^src/libfetchers/include/nix/fetchers/cache\.hh$''
''^src/libfetchers/fetch-settings\.cc$''
''^src/libfetchers/include/nix/fetchers/fetch-settings\.hh$''
''^src/libfetchers/fetch-to-store\.cc$''
''^src/libfetchers/fetchers\.cc$''
''^src/libfetchers/include/nix/fetchers/fetchers\.hh$''
''^src/libfetchers/filtering-source-accessor\.cc$''
''^src/libfetchers/include/nix/fetchers/filtering-source-accessor\.hh$''
''^src/libfetchers/fs-source-accessor\.cc$''
''^src/libfetchers/include/nix/fs-source-accessor\.hh$''
''^src/libfetchers/git-utils\.cc$''
''^src/libfetchers/include/nix/fetchers/git-utils\.hh$''
''^src/libfetchers/github\.cc$''
''^src/libfetchers/indirect\.cc$''
''^src/libfetchers/memory-source-accessor\.cc$''
''^src/libfetchers/path\.cc$''
''^src/libfetchers/registry\.cc$''
''^src/libfetchers/include/nix/fetchers/registry\.hh$''
''^src/libfetchers/tarball\.cc$''
''^src/libfetchers/include/nix/fetchers/tarball\.hh$''
''^src/libfetchers/git\.cc$''
''^src/libfetchers/mercurial\.cc$''
''^src/libflake/config\.cc$''
''^src/libflake/flake\.cc$''
''^src/libflake/include/nix/flake/flake\.hh$''
''^src/libflake/flakeref\.cc$''
''^src/libflake/include/nix/flake/flakeref\.hh$''
''^src/libflake/lockfile\.cc$''
''^src/libflake/include/nix/flake/lockfile\.hh$''
''^src/libflake/url-name\.cc$''
''^src/libmain/common-args\.cc$''
''^src/libmain/include/nix/main/common-args\.hh$''
''^src/libmain/loggers\.cc$''
''^src/libmain/include/nix/main/loggers\.hh$''
''^src/libmain/progress-bar\.cc$''
''^src/libmain/shared\.cc$''
''^src/libmain/include/nix/main/shared\.hh$''
''^src/libmain/unix/stack\.cc$''
''^src/libstore/binary-cache-store\.cc$''
''^src/libstore/include/nix/store/binary-cache-store\.hh$''
''^src/libstore/include/nix/store/build-result\.hh$''
''^src/libstore/include/nix/store/builtins\.hh$''
''^src/libstore/builtins/buildenv\.cc$''
''^src/libstore/include/nix/store/builtins/buildenv\.hh$''
''^src/libstore/include/nix/store/common-protocol-impl\.hh$''
''^src/libstore/common-protocol\.cc$''
''^src/libstore/include/nix/store/common-protocol\.hh$''
''^src/libstore/include/nix/store/common-ssh-store-config\.hh$''
''^src/libstore/content-address\.cc$''
''^src/libstore/include/nix/store/content-address\.hh$''
''^src/libstore/daemon\.cc$''
''^src/libstore/include/nix/store/daemon\.hh$''
''^src/libstore/derivations\.cc$''
''^src/libstore/include/nix/store/derivations\.hh$''
''^src/libstore/derived-path-map\.cc$''
''^src/libstore/include/nix/store/derived-path-map\.hh$''
''^src/libstore/derived-path\.cc$''
''^src/libstore/include/nix/store/derived-path\.hh$''
''^src/libstore/downstream-placeholder\.cc$''
''^src/libstore/include/nix/store/downstream-placeholder\.hh$''
''^src/libstore/dummy-store\.cc$''
''^src/libstore/export-import\.cc$''
''^src/libstore/filetransfer\.cc$''
''^src/libstore/include/nix/store/filetransfer\.hh$''
''^src/libstore/include/nix/store/gc-store\.hh$''
''^src/libstore/globals\.cc$''
''^src/libstore/include/nix/store/globals\.hh$''
''^src/libstore/http-binary-cache-store\.cc$''
''^src/libstore/legacy-ssh-store\.cc$''
''^src/libstore/include/nix/store/legacy-ssh-store\.hh$''
''^src/libstore/include/nix/store/length-prefixed-protocol-helper\.hh$''
''^src/libstore/linux/personality\.cc$''
''^src/libstore/linux/include/nix/store/personality\.hh$''
''^src/libstore/local-binary-cache-store\.cc$''
''^src/libstore/local-fs-store\.cc$''
''^src/libstore/include/nix/store/local-fs-store\.hh$''
''^src/libstore/log-store\.cc$''
''^src/libstore/include/nix/store/log-store\.hh$''
''^src/libstore/machines\.cc$''
''^src/libstore/include/nix/store/machines\.hh$''
''^src/libstore/make-content-addressed\.cc$''
''^src/libstore/include/nix/store/make-content-addressed\.hh$''
''^src/libstore/misc\.cc$''
''^src/libstore/names\.cc$''
''^src/libstore/include/nix/store/names\.hh$''
''^src/libstore/nar-accessor\.cc$''
''^src/libstore/include/nix/store/nar-accessor\.hh$''
''^src/libstore/nar-info-disk-cache\.cc$''
''^src/libstore/include/nix/store/nar-info-disk-cache\.hh$''
''^src/libstore/nar-info\.cc$''
''^src/libstore/include/nix/store/nar-info\.hh$''
''^src/libstore/outputs-spec\.cc$''
''^src/libstore/include/nix/store/outputs-spec\.hh$''
''^src/libstore/parsed-derivations\.cc$''
''^src/libstore/path-info\.cc$''
''^src/libstore/include/nix/store/path-info\.hh$''
''^src/libstore/path-references\.cc$''
''^src/libstore/include/nix/store/path-regex\.hh$''
''^src/libstore/path-with-outputs\.cc$''
''^src/libstore/path\.cc$''
''^src/libstore/include/nix/store/path\.hh$''
''^src/libstore/pathlocks\.cc$''
''^src/libstore/include/nix/store/pathlocks\.hh$''
''^src/libstore/profiles\.cc$''
''^src/libstore/include/nix/store/profiles\.hh$''
''^src/libstore/realisation\.cc$''
''^src/libstore/include/nix/store/realisation\.hh$''
''^src/libstore/remote-fs-accessor\.cc$''
''^src/libstore/include/nix/store/remote-fs-accessor\.hh$''
''^src/libstore/include/nix/store/remote-store-connection\.hh$''
''^src/libstore/remote-store\.cc$''
''^src/libstore/include/nix/store/remote-store\.hh$''
''^src/libstore/s3-binary-cache-store\.cc$''
''^src/libstore/include/nix/store/s3\.hh$''
''^src/libstore/serve-protocol-impl\.cc$''
''^src/libstore/include/nix/store/serve-protocol-impl\.hh$''
''^src/libstore/serve-protocol\.cc$''
''^src/libstore/include/nix/store/serve-protocol\.hh$''
''^src/libstore/sqlite\.cc$''
''^src/libstore/include/nix/store/sqlite\.hh$''
''^src/libstore/ssh-store\.cc$''
''^src/libstore/ssh\.cc$''
''^src/libstore/include/nix/store/ssh\.hh$''
''^src/libstore/store-api\.cc$''
''^src/libstore/include/nix/store/store-api\.hh$''
''^src/libstore/include/nix/store/store-dir-config\.hh$''
''^src/libstore/build/derivation-building-goal\.cc$''
''^src/libstore/include/nix/store/build/derivation-building-goal\.hh$''
''^src/libstore/build/derivation-goal\.cc$''
''^src/libstore/include/nix/store/build/derivation-goal\.hh$''
''^src/libstore/build/drv-output-substitution-goal\.cc$''
''^src/libstore/include/nix/store/build/drv-output-substitution-goal\.hh$''
''^src/libstore/build/entry-points\.cc$''
''^src/libstore/build/goal\.cc$''
''^src/libstore/include/nix/store/build/goal\.hh$''
''^src/libstore/unix/build/hook-instance\.cc$''
''^src/libstore/unix/build/derivation-builder\.cc$''
''^src/libstore/unix/include/nix/store/build/derivation-builder\.hh$''
''^src/libstore/build/substitution-goal\.cc$''
''^src/libstore/include/nix/store/build/substitution-goal\.hh$''
''^src/libstore/build/worker\.cc$''
''^src/libstore/include/nix/store/build/worker\.hh$''
''^src/libstore/builtins/fetchurl\.cc$''
''^src/libstore/builtins/unpack-channel\.cc$''
''^src/libstore/gc\.cc$''
''^src/libstore/local-overlay-store\.cc$''
''^src/libstore/include/nix/store/local-overlay-store\.hh$''
''^src/libstore/local-store\.cc$''
''^src/libstore/include/nix/store/local-store\.hh$''
''^src/libstore/unix/user-lock\.cc$''
''^src/libstore/unix/include/nix/store/user-lock\.hh$''
''^src/libstore/optimise-store\.cc$''
''^src/libstore/unix/pathlocks\.cc$''
''^src/libstore/posix-fs-canonicalise\.cc$''
''^src/libstore/include/nix/store/posix-fs-canonicalise\.hh$''
''^src/libstore/uds-remote-store\.cc$''
''^src/libstore/include/nix/store/uds-remote-store\.hh$''
''^src/libstore/windows/build\.cc$''
''^src/libstore/include/nix/store/worker-protocol-impl\.hh$''
''^src/libstore/worker-protocol\.cc$''
''^src/libstore/include/nix/store/worker-protocol\.hh$''
''^src/libutil-c/nix_api_util_internal\.h$''
''^src/libutil/archive\.cc$''
''^src/libutil/include/nix/util/archive\.hh$''
''^src/libutil/args\.cc$''
''^src/libutil/include/nix/util/args\.hh$''
''^src/libutil/include/nix/util/args/root\.hh$''
''^src/libutil/include/nix/util/callback\.hh$''
''^src/libutil/canon-path\.cc$''
''^src/libutil/include/nix/util/canon-path\.hh$''
''^src/libutil/include/nix/util/chunked-vector\.hh$''
''^src/libutil/include/nix/util/closure\.hh$''
''^src/libutil/include/nix/util/comparator\.hh$''
''^src/libutil/compute-levels\.cc$''
''^src/libutil/include/nix/util/config-impl\.hh$''
''^src/libutil/configuration\.cc$''
''^src/libutil/include/nix/util/configuration\.hh$''
''^src/libutil/current-process\.cc$''
''^src/libutil/include/nix/util/current-process\.hh$''
''^src/libutil/english\.cc$''
''^src/libutil/include/nix/util/english\.hh$''
''^src/libutil/error\.cc$''
''^src/libutil/include/nix/util/error\.hh$''
''^src/libutil/include/nix/util/exit\.hh$''
''^src/libutil/experimental-features\.cc$''
''^src/libutil/include/nix/util/experimental-features\.hh$''
''^src/libutil/file-content-address\.cc$''
''^src/libutil/include/nix/util/file-content-address\.hh$''
''^src/libutil/file-descriptor\.cc$''
''^src/libutil/include/nix/util/file-descriptor\.hh$''
''^src/libutil/include/nix/util/file-path-impl\.hh$''
''^src/libutil/include/nix/util/file-path\.hh$''
''^src/libutil/file-system\.cc$''
''^src/libutil/include/nix/util/file-system\.hh$''
''^src/libutil/include/nix/util/finally\.hh$''
''^src/libutil/include/nix/util/fmt\.hh$''
''^src/libutil/fs-sink\.cc$''
''^src/libutil/include/nix/util/fs-sink\.hh$''
''^src/libutil/git\.cc$''
''^src/libutil/include/nix/util/git\.hh$''
''^src/libutil/hash\.cc$''
''^src/libutil/include/nix/util/hash\.hh$''
''^src/libutil/hilite\.cc$''
''^src/libutil/include/nix/util/hilite\.hh$''
''^src/libutil/source-accessor\.hh$''
''^src/libutil/include/nix/util/json-impls\.hh$''
''^src/libutil/json-utils\.cc$''
''^src/libutil/include/nix/util/json-utils\.hh$''
''^src/libutil/linux/cgroup\.cc$''
''^src/libutil/linux/namespaces\.cc$''
''^src/libutil/logging\.cc$''
''^src/libutil/include/nix/util/logging\.hh$''
''^src/libutil/memory-source-accessor\.cc$''
''^src/libutil/include/nix/util/memory-source-accessor\.hh$''
''^src/libutil/include/nix/util/pool\.hh$''
''^src/libutil/position\.cc$''
''^src/libutil/include/nix/util/position\.hh$''
''^src/libutil/posix-source-accessor\.cc$''
''^src/libutil/include/nix/util/posix-source-accessor\.hh$''
''^src/libutil/include/nix/util/processes\.hh$''
''^src/libutil/include/nix/util/ref\.hh$''
''^src/libutil/references\.cc$''
''^src/libutil/include/nix/util/references\.hh$''
''^src/libutil/regex-combinators\.hh$''
''^src/libutil/serialise\.cc$''
''^src/libutil/include/nix/util/serialise\.hh$''
''^src/libutil/include/nix/util/signals\.hh$''
''^src/libutil/signature/local-keys\.cc$''
''^src/libutil/include/nix/util/signature/local-keys\.hh$''
''^src/libutil/signature/signer\.cc$''
''^src/libutil/include/nix/util/signature/signer\.hh$''
''^src/libutil/source-accessor\.cc$''
''^src/libutil/include/nix/util/source-accessor\.hh$''
''^src/libutil/source-path\.cc$''
''^src/libutil/include/nix/util/source-path\.hh$''
''^src/libutil/include/nix/util/split\.hh$''
''^src/libutil/suggestions\.cc$''
''^src/libutil/include/nix/util/suggestions\.hh$''
''^src/libutil/include/nix/util/sync\.hh$''
''^src/libutil/terminal\.cc$''
''^src/libutil/include/nix/util/terminal\.hh$''
''^src/libutil/thread-pool\.cc$''
''^src/libutil/include/nix/util/thread-pool\.hh$''
''^src/libutil/include/nix/util/topo-sort\.hh$''
''^src/libutil/include/nix/util/types\.hh$''
''^src/libutil/unix/file-descriptor\.cc$''
''^src/libutil/unix/file-path\.cc$''
''^src/libutil/unix/processes\.cc$''
''^src/libutil/unix/include/nix/util/signals-impl\.hh$''
''^src/libutil/unix/signals\.cc$''
''^src/libutil/unix-domain-socket\.cc$''
''^src/libutil/unix/users\.cc$''
''^src/libutil/include/nix/util/url-parts\.hh$''
''^src/libutil/url\.cc$''
''^src/libutil/include/nix/util/url\.hh$''
''^src/libutil/users\.cc$''
''^src/libutil/include/nix/util/users\.hh$''
''^src/libutil/util\.cc$''
''^src/libutil/include/nix/util/util\.hh$''
''^src/libutil/include/nix/util/variant-wrapper\.hh$''
''^src/libutil/widecharwidth/widechar_width\.h$'' # vendored source
''^src/libutil/windows/file-descriptor\.cc$''
''^src/libutil/windows/file-path\.cc$''
''^src/libutil/windows/processes\.cc$''
''^src/libutil/windows/users\.cc$''
''^src/libutil/windows/windows-error\.cc$''
''^src/libutil/windows/include/nix/util/windows-error\.hh$''
''^src/libutil/xml-writer\.cc$''
''^src/libutil/include/nix/util/xml-writer\.hh$''
''^src/nix-build/nix-build\.cc$''
''^src/nix-channel/nix-channel\.cc$''
''^src/nix-collect-garbage/nix-collect-garbage\.cc$''
''^src/nix-env/buildenv.nix$''
''^src/nix-env/nix-env\.cc$''
''^src/nix-env/user-env\.cc$''
''^src/nix-env/user-env\.hh$''
''^src/nix-instantiate/nix-instantiate\.cc$''
''^src/nix-store/dotgraph\.cc$''
''^src/nix-store/graphml\.cc$''
''^src/nix-store/nix-store\.cc$''
''^src/nix/add-to-store\.cc$''
''^src/nix/app\.cc$''
''^src/nix/build\.cc$''
''^src/nix/bundle\.cc$''
''^src/nix/cat\.cc$''
''^src/nix/config-check\.cc$''
''^src/nix/config\.cc$''
''^src/nix/copy\.cc$''
''^src/nix/derivation-add\.cc$''
''^src/nix/derivation-show\.cc$''
''^src/nix/derivation\.cc$''
''^src/nix/develop\.cc$''
''^src/nix/diff-closures\.cc$''
''^src/nix/dump-path\.cc$''
''^src/nix/edit\.cc$''
''^src/nix/eval\.cc$''
''^src/nix/flake\.cc$''
''^src/nix/fmt\.cc$''
''^src/nix/hash\.cc$''
''^src/nix/log\.cc$''
''^src/nix/ls\.cc$''
''^src/nix/main\.cc$''
''^src/nix/make-content-addressed\.cc$''
''^src/nix/nar\.cc$''
''^src/nix/optimise-store\.cc$''
''^src/nix/path-from-hash-part\.cc$''
''^src/nix/path-info\.cc$''
''^src/nix/prefetch\.cc$''
''^src/nix/profile\.cc$''
''^src/nix/realisation\.cc$''
''^src/nix/registry\.cc$''
''^src/nix/repl\.cc$''
''^src/nix/run\.cc$''
''^src/nix/run\.hh$''
''^src/nix/search\.cc$''
''^src/nix/sigs\.cc$''
''^src/nix/store-copy-log\.cc$''
''^src/nix/store-delete\.cc$''
''^src/nix/store-gc\.cc$''
''^src/nix/store-info\.cc$''
''^src/nix/store-repair\.cc$''
''^src/nix/store\.cc$''
''^src/nix/unix/daemon\.cc$''
''^src/nix/upgrade-nix\.cc$''
''^src/nix/verify\.cc$''
''^src/nix/why-depends\.cc$''
''^tests/functional/plugins/plugintest\.cc''
''^tests/functional/test-libstoreconsumer/main\.cc''
''^tests/nixos/ca-fd-leak/sender\.c''
''^tests/nixos/ca-fd-leak/smuggler\.c''
''^tests/nixos/user-sandboxing/attacker\.c''
''^src/libexpr-test-support/include/nix/expr/tests/libexpr\.hh''
''^src/libexpr-test-support/tests/value/context\.cc''
''^src/libexpr-test-support/include/nix/expr/tests/value/context\.hh''
''^src/libexpr-tests/derived-path\.cc''
''^src/libexpr-tests/error_traces\.cc''
''^src/libexpr-tests/eval\.cc''
''^src/libexpr-tests/json\.cc''
''^src/libexpr-tests/main\.cc''
''^src/libexpr-tests/primops\.cc''
''^src/libexpr-tests/search-path\.cc''
''^src/libexpr-tests/trivial\.cc''
''^src/libexpr-tests/value/context\.cc''
''^src/libexpr-tests/value/print\.cc''
''^src/libfetchers-tests/public-key\.cc''
''^src/libflake-tests/flakeref\.cc''
''^src/libflake-tests/url-name\.cc''
''^src/libstore-test-support/tests/derived-path\.cc''
''^src/libstore-test-support/include/nix/store/tests/derived-path\.hh''
''^src/libstore-test-support/include/nix/store/tests/nix_api_store\.hh''
''^src/libstore-test-support/tests/outputs-spec\.cc''
''^src/libstore-test-support/include/nix/store/tests/outputs-spec\.hh''
''^src/libstore-test-support/path\.cc''
''^src/libstore-test-support/include/nix/store/tests/path\.hh''
''^src/libstore-test-support/include/nix/store/tests/protocol\.hh''
''^src/libstore-tests/common-protocol\.cc''
''^src/libstore-tests/content-address\.cc''
''^src/libstore-tests/derivation\.cc''
''^src/libstore-tests/derived-path\.cc''
''^src/libstore-tests/downstream-placeholder\.cc''
''^src/libstore-tests/machines\.cc''
''^src/libstore-tests/nar-info-disk-cache\.cc''
''^src/libstore-tests/nar-info\.cc''
''^src/libstore-tests/outputs-spec\.cc''
''^src/libstore-tests/path-info\.cc''
''^src/libstore-tests/path\.cc''
''^src/libstore-tests/serve-protocol\.cc''
''^src/libstore-tests/worker-protocol\.cc''
''^src/libutil-test-support/include/nix/util/tests/characterization\.hh''
''^src/libutil-test-support/hash\.cc''
''^src/libutil-test-support/include/nix/util/tests/hash\.hh''
''^src/libutil-tests/args\.cc''
''^src/libutil-tests/canon-path\.cc''
''^src/libutil-tests/chunked-vector\.cc''
''^src/libutil-tests/closure\.cc''
''^src/libutil-tests/compression\.cc''
''^src/libutil-tests/config\.cc''
''^src/libutil-tests/file-content-address\.cc''
''^src/libutil-tests/git\.cc''
''^src/libutil-tests/hash\.cc''
''^src/libutil-tests/hilite\.cc''
''^src/libutil-tests/json-utils\.cc''
''^src/libutil-tests/logging\.cc''
''^src/libutil-tests/lru-cache\.cc''
''^src/libutil-tests/pool\.cc''
''^src/libutil-tests/references\.cc''
''^src/libutil-tests/suggestions\.cc''
''^src/libutil-tests/url\.cc''
''^src/libutil-tests/xml-writer\.cc''
]; ];
}; };
shellcheck = { shellcheck = {

View file

@ -1,11 +1,16 @@
#!/usr/bin/env bash #!/usr/bin/env bash
if ! type -p pre-commit &>/dev/null; then if ! type -p pre-commit &>/dev/null; then
echo "format.sh: pre-commit not found. Please use \`nix develop\`."; echo "format.sh: pre-commit not found. Please use \`nix develop -c ./maintainers/format.sh\`.";
exit 1; exit 1;
fi; fi;
if test -z "$_NIX_PRE_COMMIT_HOOKS_CONFIG"; then if test -z "$_NIX_PRE_COMMIT_HOOKS_CONFIG"; then
echo "format.sh: _NIX_PRE_COMMIT_HOOKS_CONFIG not set. Please use \`nix develop\`."; echo "format.sh: _NIX_PRE_COMMIT_HOOKS_CONFIG not set. Please use \`nix develop -c ./maintainers/format.sh\`.";
exit 1; exit 1;
fi; fi;
pre-commit run --config "$_NIX_PRE_COMMIT_HOOKS_CONFIG" --all-files
while ! pre-commit run --config "$_NIX_PRE_COMMIT_HOOKS_CONFIG" --all-files; do
if [ "${1:-}" != "--until-stable" ]; then
exit 1
fi
done

View file

@ -113,6 +113,7 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
) pkgs.buildPackages.mesonEmulatorHook ) pkgs.buildPackages.mesonEmulatorHook
++ [ ++ [
pkgs.buildPackages.cmake pkgs.buildPackages.cmake
pkgs.buildPackages.gnused
pkgs.buildPackages.shellcheck pkgs.buildPackages.shellcheck
pkgs.buildPackages.changelog-d pkgs.buildPackages.changelog-d
modular.pre-commit.settings.package modular.pre-commit.settings.package

View file

@ -26,8 +26,7 @@
using namespace nix; using namespace nix;
using std::cin; using std::cin;
static void handleAlarm(int sig) { static void handleAlarm(int sig) {}
}
std::string escapeUri(std::string uri) std::string escapeUri(std::string uri)
{ {
@ -42,9 +41,11 @@ static AutoCloseFD openSlotLock(const Machine & m, uint64_t slot)
return openLockFile(fmt("%s/%s-%d", currentLoad, escapeUri(m.storeUri.render()), slot), true); return openLockFile(fmt("%s/%s-%d", currentLoad, escapeUri(m.storeUri.render()), slot), true);
} }
static bool allSupportedLocally(Store & store, const StringSet& requiredFeatures) { static bool allSupportedLocally(Store & store, const StringSet & requiredFeatures)
{
for (auto & feature : requiredFeatures) for (auto & feature : requiredFeatures)
if (!store.config.systemFeatures.get().count(feature)) return false; if (!store.config.systemFeatures.get().count(feature))
return false;
return true; return true;
} }
@ -107,8 +108,11 @@ static int main_build_remote(int argc, char * * argv)
try { try {
auto s = readString(source); auto s = readString(source);
if (s != "try") return 0; if (s != "try")
} catch (EndOfFile &) { return 0; } return 0;
} catch (EndOfFile &) {
return 0;
}
auto amWilling = readInt(source); auto amWilling = readInt(source);
auto neededSystem = readString(source); auto neededSystem = readString(source);
@ -117,9 +121,9 @@ static int main_build_remote(int argc, char * * argv)
/* It would be possible to build locally after some builds clear out, /* It would be possible to build locally after some builds clear out,
so don't show the warning now: */ so don't show the warning now: */
bool couldBuildLocally = maxBuildJobs > 0 bool couldBuildLocally =
&& ( neededSystem == settings.thisSystem maxBuildJobs > 0
|| settings.extraPlatforms.get().count(neededSystem) > 0) && (neededSystem == settings.thisSystem || settings.extraPlatforms.get().count(neededSystem) > 0)
&& allSupportedLocally(*store, requiredFeatures); && allSupportedLocally(*store, requiredFeatures);
/* It's possible to build this locally right now: */ /* It's possible to build this locally right now: */
bool canBuildLocally = amWilling && couldBuildLocally; bool canBuildLocally = amWilling && couldBuildLocally;
@ -139,11 +143,8 @@ static int main_build_remote(int argc, char * * argv)
for (auto & m : machines) { for (auto & m : machines) {
debug("considering building on remote machine '%s'", m.storeUri.render()); debug("considering building on remote machine '%s'", m.storeUri.render());
if (m.enabled && if (m.enabled && m.systemSupported(neededSystem) && m.allSupported(requiredFeatures)
m.systemSupported(neededSystem) && && m.mandatoryMet(requiredFeatures)) {
m.allSupported(requiredFeatures) &&
m.mandatoryMet(requiredFeatures))
{
rightType = true; rightType = true;
AutoCloseFD free; AutoCloseFD free;
uint64_t load = 0; uint64_t load = 0;
@ -185,8 +186,7 @@ static int main_build_remote(int argc, char * * argv)
if (!bestSlotLock) { if (!bestSlotLock) {
if (rightType && !canBuildLocally) if (rightType && !canBuildLocally)
std::cerr << "# postpone\n"; std::cerr << "# postpone\n";
else else {
{
// build the hint template. // build the hint template.
std::string errorText = std::string errorText =
"Failed to find a machine for remote build!\n" "Failed to find a machine for remote build!\n"
@ -205,16 +205,11 @@ static int main_build_remote(int argc, char * * argv)
drvstr = "<unknown>"; drvstr = "<unknown>";
auto error = HintFmt::fromFormatString(errorText); auto error = HintFmt::fromFormatString(errorText);
error error % drvstr % neededSystem % concatStringsSep<StringSet>(", ", requiredFeatures)
% drvstr
% neededSystem
% concatStringsSep<StringSet>(", ", requiredFeatures)
% machines.size(); % machines.size();
for (auto & m : machines) for (auto & m : machines)
error error % concatStringsSep<StringSet>(", ", m.systemTypes) % m.maxJobs
% concatStringsSep<StringSet>(", ", m.systemTypes)
% m.maxJobs
% concatStringsSep<StringSet>(", ", m.supportedFeatures) % concatStringsSep<StringSet>(", ", m.supportedFeatures)
% concatStringsSep<StringSet>(", ", m.mandatoryFeatures); % concatStringsSep<StringSet>(", ", m.mandatoryFeatures);
@ -242,9 +237,7 @@ static int main_build_remote(int argc, char * * argv)
sshStore->connect(); sshStore->connect();
} catch (std::exception & e) { } catch (std::exception & e) {
auto msg = chomp(drainFD(5, false)); auto msg = chomp(drainFD(5, false));
printError("cannot build on '%s': %s%s", printError("cannot build on '%s': %s%s", storeUri, e.what(), msg.empty() ? "" : ": " + msg);
storeUri, e.what(),
msg.empty() ? "" : ": " + msg);
bestMachine->enabled = false; bestMachine->enabled = false;
continue; continue;
} }
@ -271,7 +264,8 @@ connected:
try { try {
setUpdateLock(storeUri); setUpdateLock(storeUri);
} catch (SysError & e) { } catch (SysError & e) {
if (e.errNo != ENAMETOOLONG) throw; if (e.errNo != ENAMETOOLONG)
throw;
// Try again hashing the store URL so we have a shorter path // Try again hashing the store URL so we have a shorter path
auto h = hashString(HashAlgorithm::MD5, storeUri); auto h = hashString(HashAlgorithm::MD5, storeUri);
setUpdateLock(h.to_string(HashFormat::Base64, false)); setUpdateLock(h.to_string(HashFormat::Base64, false));
@ -330,21 +324,19 @@ connected:
optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv); optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv);
auto & result = *optResult; auto & result = *optResult;
if (!result.success()) if (!result.success())
throw Error("build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg); throw Error(
"build of '%s' on '%s' failed: %s", store->printStorePath(*drvPath), storeUri, result.errorMsg);
} else { } else {
copyClosure(*store, *sshStore, StorePathSet{*drvPath}, NoRepair, NoCheckSigs, substitute); copyClosure(*store, *sshStore, StorePathSet{*drvPath}, NoRepair, NoCheckSigs, substitute);
auto res = sshStore->buildPathsWithResults({ auto res = sshStore->buildPathsWithResults({DerivedPath::Built{
DerivedPath::Built {
.drvPath = makeConstantStorePathRef(*drvPath), .drvPath = makeConstantStorePathRef(*drvPath),
.outputs = OutputsSpec::All{}, .outputs = OutputsSpec::All{},
} }});
});
// One path to build should produce exactly one build result // One path to build should produce exactly one build result
assert(res.size() == 1); assert(res.size() == 1);
optResult = std::move(res[0]); optResult = std::move(res[0]);
} }
auto outputHashes = staticOutputHashes(*store, drv); auto outputHashes = staticOutputHashes(*store, drv);
std::set<Realisation> missingRealisations; std::set<Realisation> missingRealisations;
StorePathSet missingPaths; StorePathSet missingPaths;

View file

@ -10,23 +10,13 @@
namespace nix { namespace nix {
// Custom implementation to avoid `ref` ptr equality // Custom implementation to avoid `ref` ptr equality
GENERATE_CMP_EXT( GENERATE_CMP_EXT(, std::strong_ordering, SingleBuiltPathBuilt, *me->drvPath, me->output);
,
std::strong_ordering,
SingleBuiltPathBuilt,
*me->drvPath,
me->output);
// Custom implementation to avoid `ref` ptr equality // Custom implementation to avoid `ref` ptr equality
// TODO no `GENERATE_CMP_EXT` because no `std::set::operator<=>` on // TODO no `GENERATE_CMP_EXT` because no `std::set::operator<=>` on
// Darwin, per header. // Darwin, per header.
GENERATE_EQUAL( GENERATE_EQUAL(, BuiltPathBuilt ::, BuiltPathBuilt, *me->drvPath, me->outputs);
,
BuiltPathBuilt ::,
BuiltPathBuilt,
*me->drvPath,
me->outputs);
StorePath SingleBuiltPath::outPath() const StorePath SingleBuiltPath::outPath() const
{ {
@ -34,8 +24,8 @@ StorePath SingleBuiltPath::outPath() const
overloaded{ overloaded{
[](const SingleBuiltPath::Opaque & p) { return p.path; }, [](const SingleBuiltPath::Opaque & p) { return p.path; },
[](const SingleBuiltPath::Built & b) { return b.output.second; }, [](const SingleBuiltPath::Built & b) { return b.output.second; },
}, raw() },
); raw());
} }
StorePathSet BuiltPath::outPaths() const StorePathSet BuiltPath::outPaths() const
@ -49,8 +39,8 @@ StorePathSet BuiltPath::outPaths() const
res.insert(path); res.insert(path);
return res; return res;
}, },
}, raw() },
); raw());
} }
SingleDerivedPath::Built SingleBuiltPath::Built::discardOutputPath() const SingleDerivedPath::Built SingleBuiltPath::Built::discardOutputPath() const
@ -65,14 +55,10 @@ SingleDerivedPath SingleBuiltPath::discardOutputPath() const
{ {
return std::visit( return std::visit(
overloaded{ overloaded{
[](const SingleBuiltPath::Opaque & p) -> SingleDerivedPath { [](const SingleBuiltPath::Opaque & p) -> SingleDerivedPath { return p; },
return p; [](const SingleBuiltPath::Built & b) -> SingleDerivedPath { return b.discardOutputPath(); },
}, },
[](const SingleBuiltPath::Built & b) -> SingleDerivedPath { raw());
return b.discardOutputPath();
},
}, raw()
);
} }
nlohmann::json BuiltPath::Built::toJSON(const StoreDirConfig & store) const nlohmann::json BuiltPath::Built::toJSON(const StoreDirConfig & store) const
@ -97,16 +83,12 @@ nlohmann::json SingleBuiltPath::Built::toJSON(const StoreDirConfig & store) cons
nlohmann::json SingleBuiltPath::toJSON(const StoreDirConfig & store) const nlohmann::json SingleBuiltPath::toJSON(const StoreDirConfig & store) const
{ {
return std::visit([&](const auto & buildable) { return std::visit([&](const auto & buildable) { return buildable.toJSON(store); }, raw());
return buildable.toJSON(store);
}, raw());
} }
nlohmann::json BuiltPath::toJSON(const StoreDirConfig & store) const nlohmann::json BuiltPath::toJSON(const StoreDirConfig & store) const
{ {
return std::visit([&](const auto & buildable) { return std::visit([&](const auto & buildable) { return buildable.toJSON(store); }, raw());
return buildable.toJSON(store);
}, raw());
} }
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
@ -116,18 +98,16 @@ RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
overloaded{ overloaded{
[&](const BuiltPath::Opaque & p) { res.insert(p.path); }, [&](const BuiltPath::Opaque & p) { res.insert(p.path); },
[&](const BuiltPath::Built & p) { [&](const BuiltPath::Built & p) {
auto drvHashes = auto drvHashes = staticOutputHashes(store, store.readDerivation(p.drvPath->outPath()));
staticOutputHashes(store, store.readDerivation(p.drvPath->outPath()));
for (auto & [outputName, outputPath] : p.outputs) { for (auto & [outputName, outputPath] : p.outputs) {
if (experimentalFeatureSettings.isEnabled( if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) {
Xp::CaDerivations)) {
auto drvOutput = get(drvHashes, outputName); auto drvOutput = get(drvHashes, outputName);
if (!drvOutput) if (!drvOutput)
throw Error( throw Error(
"the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)", "the derivation '%s' has unrealised output '%s' (derived-path.cc/toRealisedPaths)",
store.printStorePath(p.drvPath->outPath()), outputName); store.printStorePath(p.drvPath->outPath()),
auto thisRealisation = store.queryRealisation( outputName);
DrvOutput{*drvOutput, outputName}); auto thisRealisation = store.queryRealisation(DrvOutput{*drvOutput, outputName});
assert(thisRealisation); // Weve built it, so we must assert(thisRealisation); // Weve built it, so we must
// have the realisation // have the realisation
res.insert(*thisRealisation); res.insert(*thisRealisation);

View file

@ -18,7 +18,6 @@
namespace nix { namespace nix {
fetchers::Settings fetchSettings; fetchers::Settings fetchSettings;
static GlobalConfig::Register rFetchSettings(&fetchSettings); static GlobalConfig::Register rFetchSettings(&fetchSettings);
@ -49,17 +48,14 @@ EvalSettings evalSettings {
static GlobalConfig::Register rEvalSettings(&evalSettings); static GlobalConfig::Register rEvalSettings(&evalSettings);
flake::Settings flakeSettings; flake::Settings flakeSettings;
static GlobalConfig::Register rFlakeSettings(&flakeSettings); static GlobalConfig::Register rFlakeSettings(&flakeSettings);
CompatibilitySettings compatibilitySettings{}; CompatibilitySettings compatibilitySettings{};
static GlobalConfig::Register rCompatibilitySettings(&compatibilitySettings); static GlobalConfig::Register rCompatibilitySettings(&compatibilitySettings);
MixEvalArgs::MixEvalArgs() MixEvalArgs::MixEvalArgs()
{ {
addFlag({ addFlag({
@ -67,7 +63,9 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the value *expr* as the argument *name* to Nix functions.", .description = "Pass the value *expr* as the argument *name* to Nix functions.",
.category = category, .category = category,
.labels = {"name", "expr"}, .labels = {"name", "expr"},
.handler = {[&](std::string name, std::string expr) { autoArgs.insert_or_assign(name, AutoArg{AutoArgExpr{expr}}); }}, .handler = {[&](std::string name, std::string expr) {
autoArgs.insert_or_assign(name, AutoArg{AutoArgExpr{expr}});
}},
}); });
addFlag({ addFlag({
@ -75,7 +73,9 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the string *string* as the argument *name* to Nix functions.", .description = "Pass the string *string* as the argument *name* to Nix functions.",
.category = category, .category = category,
.labels = {"name", "string"}, .labels = {"name", "string"},
.handler = {[&](std::string name, std::string s) { autoArgs.insert_or_assign(name, AutoArg{AutoArgString{s}}); }}, .handler = {[&](std::string name, std::string s) {
autoArgs.insert_or_assign(name, AutoArg{AutoArgString{s}});
}},
}); });
addFlag({ addFlag({
@ -83,7 +83,9 @@ MixEvalArgs::MixEvalArgs()
.description = "Pass the contents of file *path* as the argument *name* to Nix functions.", .description = "Pass the contents of file *path* as the argument *name* to Nix functions.",
.category = category, .category = category,
.labels = {"name", "path"}, .labels = {"name", "path"},
.handler = {[&](std::string name, std::string path) { autoArgs.insert_or_assign(name, AutoArg{AutoArgFile{path}}); }}, .handler = {[&](std::string name, std::string path) {
autoArgs.insert_or_assign(name, AutoArg{AutoArgFile{path}});
}},
.completer = completePath, .completer = completePath,
}); });
@ -107,18 +109,14 @@ MixEvalArgs::MixEvalArgs()
)", )",
.category = category, .category = category,
.labels = {"path"}, .labels = {"path"},
.handler = {[&](std::string s) { .handler = {[&](std::string s) { lookupPath.elements.emplace_back(LookupPath::Elem::parse(s)); }},
lookupPath.elements.emplace_back(LookupPath::Elem::parse(s));
}},
}); });
addFlag({ addFlag({
.longName = "impure", .longName = "impure",
.description = "Allow access to mutable paths and repositories.", .description = "Allow access to mutable paths and repositories.",
.category = category, .category = category,
.handler = {[&]() { .handler = {[&]() { evalSettings.pureEval = false; }},
evalSettings.pureEval = false;
}},
}); });
addFlag({ addFlag({
@ -130,7 +128,8 @@ MixEvalArgs::MixEvalArgs()
auto from = parseFlakeRef(fetchSettings, _from, std::filesystem::current_path().string()); auto from = parseFlakeRef(fetchSettings, _from, std::filesystem::current_path().string());
auto to = parseFlakeRef(fetchSettings, _to, std::filesystem::current_path().string()); auto to = parseFlakeRef(fetchSettings, _to, std::filesystem::current_path().string());
fetchers::Attrs extraAttrs; fetchers::Attrs extraAttrs;
if (to.subdir != "") extraAttrs["dir"] = to.subdir; if (to.subdir != "")
extraAttrs["dir"] = to.subdir;
fetchers::overrideRegistry(from.input, to.input, extraAttrs); fetchers::overrideRegistry(from.input, to.input, extraAttrs);
}}, }},
.completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) { .completer = {[&](AddCompletions & completions, size_t, std::string_view prefix) {
@ -156,20 +155,21 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
auto res = state.buildBindings(autoArgs.size()); auto res = state.buildBindings(autoArgs.size());
for (auto & [name, arg] : autoArgs) { for (auto & [name, arg] : autoArgs) {
auto v = state.allocValue(); auto v = state.allocValue();
std::visit(overloaded { std::visit(
overloaded{
[&](const AutoArgExpr & arg) { [&](const AutoArgExpr & arg) {
state.mkThunk_(*v, state.parseExprFromString(arg.expr, compatibilitySettings.nixShellShebangArgumentsRelativeToScript ? state.rootPath(absPath(getCommandBaseDir())) : state.rootPath("."))); state.mkThunk_(
*v,
state.parseExprFromString(
arg.expr,
compatibilitySettings.nixShellShebangArgumentsRelativeToScript
? state.rootPath(absPath(getCommandBaseDir()))
: state.rootPath(".")));
}, },
[&](const AutoArgString & arg) { [&](const AutoArgString & arg) { v->mkString(arg.s); },
v->mkString(arg.s); [&](const AutoArgFile & arg) { v->mkString(readFile(arg.path.string())); },
}, [&](const AutoArgStdin & arg) { v->mkString(readFile(STDIN_FILENO)); }},
[&](const AutoArgFile & arg) { arg);
v->mkString(readFile(arg.path.string()));
},
[&](const AutoArgStdin & arg) {
v->mkString(readFile(STDIN_FILENO));
}
}, arg);
res.insert(state.symbols.create(name), v); res.insert(state.symbols.create(name), v);
} }
return res.finish(); return res.finish();
@ -178,15 +178,8 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * baseDir) SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * baseDir)
{ {
if (EvalSettings::isPseudoUrl(s)) { if (EvalSettings::isPseudoUrl(s)) {
auto accessor = fetchers::downloadTarball( auto accessor = fetchers::downloadTarball(state.store, state.fetchSettings, EvalSettings::resolvePseudoUrl(s));
state.store, auto storePath = fetchToStore(state.fetchSettings, *state.store, SourcePath(accessor), FetchMode::Copy);
state.fetchSettings,
EvalSettings::resolvePseudoUrl(s));
auto storePath = fetchToStore(
state.fetchSettings,
*state.store,
SourcePath(accessor),
FetchMode::Copy);
return state.storePath(storePath); return state.storePath(storePath);
} }
@ -195,11 +188,7 @@ SourcePath lookupFileArg(EvalState & state, std::string_view s, const Path * bas
auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false); auto flakeRef = parseFlakeRef(fetchSettings, std::string(s.substr(6)), {}, true, false);
auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store); auto [accessor, lockedRef] = flakeRef.resolve(state.store).lazyFetch(state.store);
auto storePath = nix::fetchToStore( auto storePath = nix::fetchToStore(
state.fetchSettings, state.fetchSettings, *state.store, SourcePath(accessor), FetchMode::Copy, lockedRef.input.getName());
*state.store,
SourcePath(accessor),
FetchMode::Copy,
lockedRef.input.getName());
state.allowPath(storePath); state.allowPath(storePath);
return state.storePath(storePath); return state.storePath(storePath);
} }

View file

@ -11,11 +11,9 @@ Strings editorFor(const SourcePath & file, uint32_t line)
throw Error("cannot open '%s' in an editor because it has no physical path", file); throw Error("cannot open '%s' in an editor because it has no physical path", file);
auto editor = getEnv("EDITOR").value_or("cat"); auto editor = getEnv("EDITOR").value_or("cat");
auto args = tokenizeString<Strings>(editor); auto args = tokenizeString<Strings>(editor);
if (line > 0 && ( if (line > 0
editor.find("emacs") != std::string::npos || && (editor.find("emacs") != std::string::npos || editor.find("nano") != std::string::npos
editor.find("nano") != std::string::npos || || editor.find("vim") != std::string::npos || editor.find("kak") != std::string::npos))
editor.find("vim") != std::string::npos ||
editor.find("kak") != std::string::npos))
args.push_back(fmt("+%d", line)); args.push_back(fmt("+%d", line));
args.push_back(path->string()); args.push_back(path->string());
return args; return args;

View file

@ -8,7 +8,8 @@ namespace nix {
struct SingleBuiltPath; struct SingleBuiltPath;
struct SingleBuiltPathBuilt { struct SingleBuiltPathBuilt
{
ref<SingleBuiltPath> drvPath; ref<SingleBuiltPath> drvPath;
std::pair<std::string, StorePath> output; std::pair<std::string, StorePath> output;
@ -22,12 +23,10 @@ struct SingleBuiltPathBuilt {
std::strong_ordering operator<=>(const SingleBuiltPathBuilt &) const noexcept; std::strong_ordering operator<=>(const SingleBuiltPathBuilt &) const noexcept;
}; };
using _SingleBuiltPathRaw = std::variant< using _SingleBuiltPathRaw = std::variant<DerivedPathOpaque, SingleBuiltPathBuilt>;
DerivedPathOpaque,
SingleBuiltPathBuilt
>;
struct SingleBuiltPath : _SingleBuiltPathRaw { struct SingleBuiltPath : _SingleBuiltPathRaw
{
using Raw = _SingleBuiltPathRaw; using Raw = _SingleBuiltPathRaw;
using Raw::Raw; using Raw::Raw;
@ -37,7 +36,8 @@ struct SingleBuiltPath : _SingleBuiltPathRaw {
bool operator==(const SingleBuiltPath &) const = default; bool operator==(const SingleBuiltPath &) const = default;
auto operator<=>(const SingleBuiltPath &) const = default; auto operator<=>(const SingleBuiltPath &) const = default;
inline const Raw & raw() const { inline const Raw & raw() const
{
return static_cast<const Raw &>(*this); return static_cast<const Raw &>(*this);
} }
@ -59,7 +59,8 @@ static inline ref<SingleBuiltPath> staticDrv(StorePath drvPath)
* *
* See 'BuiltPath' for more an explanation. * See 'BuiltPath' for more an explanation.
*/ */
struct BuiltPathBuilt { struct BuiltPathBuilt
{
ref<SingleBuiltPath> drvPath; ref<SingleBuiltPath> drvPath;
std::map<std::string, StorePath> outputs; std::map<std::string, StorePath> outputs;
@ -72,16 +73,14 @@ struct BuiltPathBuilt {
nlohmann::json toJSON(const StoreDirConfig & store) const; nlohmann::json toJSON(const StoreDirConfig & store) const;
}; };
using _BuiltPathRaw = std::variant< using _BuiltPathRaw = std::variant<DerivedPath::Opaque, BuiltPathBuilt>;
DerivedPath::Opaque,
BuiltPathBuilt
>;
/** /**
* A built path. Similar to a DerivedPath, but enriched with the corresponding * A built path. Similar to a DerivedPath, but enriched with the corresponding
* output path(s). * output path(s).
*/ */
struct BuiltPath : _BuiltPathRaw { struct BuiltPath : _BuiltPathRaw
{
using Raw = _BuiltPathRaw; using Raw = _BuiltPathRaw;
using Raw::Raw; using Raw::Raw;
@ -92,7 +91,8 @@ struct BuiltPath : _BuiltPathRaw {
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet. // TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
// auto operator <=> (const BuiltPath &) const = default; // auto operator <=> (const BuiltPath &) const = default;
inline const Raw & raw() const { inline const Raw & raw() const
{
return static_cast<const Raw &>(*this); return static_cast<const Raw &>(*this);
} }

View file

@ -12,7 +12,9 @@ namespace nix {
class Store; class Store;
namespace fetchers { struct Settings; } namespace fetchers {
struct Settings;
}
class EvalState; class EvalState;
struct EvalSettings; struct EvalSettings;
@ -20,7 +22,9 @@ struct CompatibilitySettings;
class Bindings; class Bindings;
struct SourcePath; struct SourcePath;
namespace flake { struct Settings; } namespace flake {
struct Settings;
}
/** /**
* @todo Get rid of global setttings variables * @todo Get rid of global setttings variables
@ -55,10 +59,20 @@ struct MixEvalArgs : virtual Args, virtual MixRepair
std::optional<std::string> evalStoreUrl; std::optional<std::string> evalStoreUrl;
private: private:
struct AutoArgExpr { std::string expr; }; struct AutoArgExpr
struct AutoArgString { std::string s; }; {
struct AutoArgFile { std::filesystem::path path; }; std::string expr;
struct AutoArgStdin { }; };
struct AutoArgString
{
std::string s;
};
struct AutoArgFile
{
std::filesystem::path path;
};
struct AutoArgStdin
{};
using AutoArg = std::variant<AutoArgExpr, AutoArgString, AutoArgFile, AutoArgStdin>; using AutoArg = std::variant<AutoArgExpr, AutoArgString, AutoArgFile, AutoArgStdin>;

View file

@ -39,7 +39,10 @@ class InstallableAttrPath : public InstallableValue
const std::string & attrPath, const std::string & attrPath,
ExtendedOutputsSpec extendedOutputsSpec); ExtendedOutputsSpec extendedOutputsSpec);
std::string what() const override { return attrPath; }; std::string what() const override
{
return attrPath;
};
std::pair<Value *, PosIdx> toValue(EvalState & state) override; std::pair<Value *, PosIdx> toValue(EvalState & state) override;

View file

@ -11,8 +11,10 @@ struct InstallableDerivedPath : Installable
DerivedPath derivedPath; DerivedPath derivedPath;
InstallableDerivedPath(ref<Store> store, DerivedPath && derivedPath) InstallableDerivedPath(ref<Store> store, DerivedPath && derivedPath)
: store(store), derivedPath(std::move(derivedPath)) : store(store)
{ } , derivedPath(std::move(derivedPath))
{
}
std::string what() const override; std::string what() const override;
@ -20,10 +22,8 @@ struct InstallableDerivedPath : Installable
std::optional<StorePath> getStorePath() override; std::optional<StorePath> getStorePath() override;
static InstallableDerivedPath parse( static InstallableDerivedPath
ref<Store> store, parse(ref<Store> store, std::string_view prefix, ExtendedOutputsSpec extendedOutputsSpec);
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec);
}; };
} }

View file

@ -18,7 +18,8 @@ struct ExtraPathInfoFlake : ExtraPathInfoValue
/** /**
* Extra struct to get around C++ designated initializer limitations * Extra struct to get around C++ designated initializer limitations
*/ */
struct Flake { struct Flake
{
FlakeRef originalRef; FlakeRef originalRef;
FlakeRef lockedRef; FlakeRef lockedRef;
}; };
@ -26,8 +27,10 @@ struct ExtraPathInfoFlake : ExtraPathInfoValue
Flake flake; Flake flake;
ExtraPathInfoFlake(Value && v, Flake && f) ExtraPathInfoFlake(Value && v, Flake && f)
: ExtraPathInfoValue(std::move(v)), flake(std::move(f)) : ExtraPathInfoValue(std::move(v))
{ } , flake(std::move(f))
{
}
}; };
struct InstallableFlake : InstallableValue struct InstallableFlake : InstallableValue
@ -49,7 +52,10 @@ struct InstallableFlake : InstallableValue
Strings prefixes, Strings prefixes,
const flake::LockFlags & lockFlags); const flake::LockFlags & lockFlags);
std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); } std::string what() const override
{
return flakeRef.to_string() + "#" + *attrPaths.begin();
}
std::vector<std::string> getActualAttrPaths(); std::vector<std::string> getActualAttrPaths();
@ -61,8 +67,7 @@ struct InstallableFlake : InstallableValue
* Get a cursor to every attrpath in getActualAttrPaths() that * Get a cursor to every attrpath in getActualAttrPaths() that
* exists. However if none exists, throw an exception. * exists. However if none exists, throw an exception.
*/ */
std::vector<ref<eval_cache::AttrCursor>> std::vector<ref<eval_cache::AttrCursor>> getCursors(EvalState & state) override;
getCursors(EvalState & state) override;
std::shared_ptr<flake::LockedFlake> getLockedFlake() const; std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
@ -82,8 +87,6 @@ static inline FlakeRef defaultNixpkgsFlakeRef()
return FlakeRef::fromAttrs(fetchSettings, {{"type", "indirect"}, {"id", "nixpkgs"}}); return FlakeRef::fromAttrs(fetchSettings, {{"type", "indirect"}, {"id", "nixpkgs"}});
} }
ref<eval_cache::EvalCache> openEvalCache( ref<eval_cache::EvalCache> openEvalCache(EvalState & state, std::shared_ptr<flake::LockedFlake> lockedFlake);
EvalState & state,
std::shared_ptr<flake::LockedFlake> lockedFlake);
} }

View file

@ -9,7 +9,10 @@ namespace nix {
struct PackageInfo; struct PackageInfo;
struct SourceExprCommand; struct SourceExprCommand;
namespace eval_cache { class EvalCache; class AttrCursor; } namespace eval_cache {
class EvalCache;
class AttrCursor;
}
struct App struct App
{ {
@ -37,7 +40,8 @@ struct ExtraPathInfoValue : ExtraPathInfo
/** /**
* Extra struct to get around C++ designated initializer limitations * Extra struct to get around C++ designated initializer limitations
*/ */
struct Value { struct Value
{
/** /**
* An optional priority for use with "build envs". See Package * An optional priority for use with "build envs". See Package
*/ */
@ -61,7 +65,8 @@ struct ExtraPathInfoValue : ExtraPathInfo
ExtraPathInfoValue(Value && v) ExtraPathInfoValue(Value && v)
: value(std::move(v)) : value(std::move(v))
{ } {
}
virtual ~ExtraPathInfoValue() = default; virtual ~ExtraPathInfoValue() = default;
}; };
@ -74,7 +79,10 @@ struct InstallableValue : Installable
{ {
ref<EvalState> state; ref<EvalState> state;
InstallableValue(ref<EvalState> state) : state(state) {} InstallableValue(ref<EvalState> state)
: state(state)
{
}
virtual ~InstallableValue() {} virtual ~InstallableValue() {}
@ -85,15 +93,13 @@ struct InstallableValue : Installable
* However if none exists, throw exception instead of returning * However if none exists, throw exception instead of returning
* empty vector. * empty vector.
*/ */
virtual std::vector<ref<eval_cache::AttrCursor>> virtual std::vector<ref<eval_cache::AttrCursor>> getCursors(EvalState & state);
getCursors(EvalState & state);
/** /**
* Get the first and most preferred cursor this Installable could * Get the first and most preferred cursor this Installable could
* refer to, or throw an exception if none exists. * refer to, or throw an exception if none exists.
*/ */
virtual ref<eval_cache::AttrCursor> virtual ref<eval_cache::AttrCursor> getCursor(EvalState & state);
getCursor(EvalState & state);
UnresolvedApp toApp(EvalState & state); UnresolvedApp toApp(EvalState & state);
@ -116,7 +122,8 @@ protected:
* @result A derived path (with empty info, for now) if the value * @result A derived path (with empty info, for now) if the value
* matched the above criteria. * matched the above criteria.
*/ */
std::optional<DerivedPathWithInfo> trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx); std::optional<DerivedPathWithInfo>
trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx);
}; };
} }

View file

@ -168,37 +168,19 @@ struct Installable
BuildMode bMode = bmNormal); BuildMode bMode = bmNormal);
static std::set<StorePath> toStorePathSet( static std::set<StorePath> toStorePathSet(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, OperateOn operateOn, const Installables & installables);
ref<Store> store,
Realise mode,
OperateOn operateOn,
const Installables & installables);
static std::vector<StorePath> toStorePaths( static std::vector<StorePath> toStorePaths(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, OperateOn operateOn, const Installables & installables);
ref<Store> store,
Realise mode,
OperateOn operateOn,
const Installables & installables);
static StorePath toStorePath( static StorePath toStorePath(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, OperateOn operateOn, ref<Installable> installable);
ref<Store> store,
Realise mode,
OperateOn operateOn,
ref<Installable> installable);
static std::set<StorePath> toDerivations( static std::set<StorePath>
ref<Store> store, toDerivations(ref<Store> store, const Installables & installables, bool useDeriver = false);
const Installables & installables,
bool useDeriver = false);
static BuiltPaths toBuiltPaths( static BuiltPaths toBuiltPaths(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, OperateOn operateOn, const Installables & installables);
ref<Store> store,
Realise mode,
OperateOn operateOn,
const Installables & installables);
}; };
} }

View file

@ -13,7 +13,8 @@ struct RegisterLegacyCommand
{ {
typedef std::map<std::string, MainFunction> Commands; typedef std::map<std::string, MainFunction> Commands;
static Commands & commands() { static Commands & commands()
{
static Commands commands; static Commands commands;
return commands; return commands;
} }

View file

@ -11,7 +11,8 @@ namespace nix {
namespace detail { namespace detail {
/** Provides the completion hooks for the repl, without exposing its complete /** Provides the completion hooks for the repl, without exposing its complete
* internals. */ * internals. */
struct ReplCompleterMixin { struct ReplCompleterMixin
{
virtual StringSet completePrefix(const std::string & prefix) = 0; virtual StringSet completePrefix(const std::string & prefix) = 0;
}; };
}; };

View file

@ -12,10 +12,10 @@ struct AbstractNixRepl
AbstractNixRepl(ref<EvalState> state) AbstractNixRepl(ref<EvalState> state)
: state(state) : state(state)
{ } {
}
virtual ~AbstractNixRepl() virtual ~AbstractNixRepl() {}
{ }
typedef std::vector<std::pair<Value *, std::string>> AnnotatedValues; typedef std::vector<std::pair<Value *, std::string>> AnnotatedValues;
@ -33,9 +33,7 @@ struct AbstractNixRepl
std::function<AnnotatedValues()> getValues, std::function<AnnotatedValues()> getValues,
RunNix * runNix = nullptr); RunNix * runNix = nullptr);
static ReplExitStatus runSimple( static ReplExitStatus runSimple(ref<EvalState> evalState, const ValMap & extraEnv);
ref<EvalState> evalState,
const ValMap & extraEnv);
virtual void initEnv() = 0; virtual void initEnv() = 0;

View file

@ -35,7 +35,8 @@ InstallableAttrPath::InstallableAttrPath(
, v(allocRootValue(v)) , v(allocRootValue(v))
, attrPath(attrPath) , attrPath(attrPath)
, extendedOutputsSpec(std::move(extendedOutputsSpec)) , extendedOutputsSpec(std::move(extendedOutputsSpec))
{ } {
}
std::pair<Value *, PosIdx> InstallableAttrPath::toValue(EvalState & state) std::pair<Value *, PosIdx> InstallableAttrPath::toValue(EvalState & state)
{ {
@ -48,11 +49,8 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
{ {
auto [v, pos] = toValue(*state); auto [v, pos] = toValue(*state);
if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths( if (std::optional derivedPathWithInfo =
*v, trySinglePathToDerivedPaths(*v, pos, fmt("while evaluating the attribute '%s'", attrPath))) {
pos,
fmt("while evaluating the attribute '%s'", attrPath)))
{
return {*derivedPathWithInfo}; return {*derivedPathWithInfo};
} }
@ -70,7 +68,8 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
if (!drvPath) if (!drvPath)
throw Error("'%s' is not a derivation", what()); throw Error("'%s' is not a derivation", what());
auto newOutputs = std::visit(overloaded { auto newOutputs = std::visit(
overloaded{
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec { [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
StringSet outputsToInstall; StringSet outputsToInstall;
for (auto & output : packageInfo.queryOutputs(false, true)) for (auto & output : packageInfo.queryOutputs(false, true))
@ -79,10 +78,9 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
outputsToInstall.insert("out"); outputsToInstall.insert("out");
return OutputsSpec::Names{std::move(outputsToInstall)}; return OutputsSpec::Names{std::move(outputsToInstall)};
}, },
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec { [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec { return e; },
return e;
}, },
}, extendedOutputsSpec.raw); extendedOutputsSpec.raw);
auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs); auto [iter, didInsert] = byDrvPath.emplace(*drvPath, newOutputs);
@ -93,7 +91,8 @@ DerivedPathsWithInfo InstallableAttrPath::toDerivedPaths()
DerivedPathsWithInfo res; DerivedPathsWithInfo res;
for (auto & [drvPath, outputs] : byDrvPath) for (auto & [drvPath, outputs] : byDrvPath)
res.push_back({ res.push_back({
.path = DerivedPath::Built { .path =
DerivedPath::Built{
.drvPath = makeConstantStorePathRef(drvPath), .drvPath = makeConstantStorePathRef(drvPath),
.outputs = outputs, .outputs = outputs,
}, },
@ -115,7 +114,9 @@ InstallableAttrPath InstallableAttrPath::parse(
ExtendedOutputsSpec extendedOutputsSpec) ExtendedOutputsSpec extendedOutputsSpec)
{ {
return { return {
state, cmd, v, state,
cmd,
v,
prefix == "." ? "" : std::string{prefix}, prefix == "." ? "" : std::string{prefix},
std::move(extendedOutputsSpec), std::move(extendedOutputsSpec),
}; };

View file

@ -21,12 +21,11 @@ std::optional<StorePath> InstallableDerivedPath::getStorePath()
return derivedPath.getBaseStorePath(); return derivedPath.getBaseStorePath();
} }
InstallableDerivedPath InstallableDerivedPath::parse( InstallableDerivedPath
ref<Store> store, InstallableDerivedPath::parse(ref<Store> store, std::string_view prefix, ExtendedOutputsSpec extendedOutputsSpec)
std::string_view prefix,
ExtendedOutputsSpec extendedOutputsSpec)
{ {
auto derivedPath = std::visit(overloaded { auto derivedPath = std::visit(
overloaded{
// If the user did not use ^, we treat the output more // If the user did not use ^, we treat the output more
// liberally: we accept a symlink chain or an actual // liberally: we accept a symlink chain or an actual
// store path. // store path.
@ -45,7 +44,8 @@ InstallableDerivedPath InstallableDerivedPath::parse(
.outputs = outputSpec, .outputs = outputSpec,
}; };
}, },
}, extendedOutputsSpec.raw); },
extendedOutputsSpec.raw);
return InstallableDerivedPath{ return InstallableDerivedPath{
store, store,
std::move(derivedPath), std::move(derivedPath),

View file

@ -47,8 +47,11 @@ static std::string showAttrPaths(const std::vector<std::string> & paths)
{ {
std::string s; std::string s;
for (const auto & [n, i] : enumerate(paths)) { for (const auto & [n, i] : enumerate(paths)) {
if (n > 0) s += n + 1 == paths.size() ? " or " : ", "; if (n > 0)
s += '\''; s += i; s += '\''; s += n + 1 == paths.size() ? " or " : ", ";
s += '\'';
s += i;
s += '\'';
} }
return s; return s;
} }
@ -62,12 +65,12 @@ InstallableFlake::InstallableFlake(
Strings attrPaths, Strings attrPaths,
Strings prefixes, Strings prefixes,
const flake::LockFlags & lockFlags) const flake::LockFlags & lockFlags)
: InstallableValue(state), : InstallableValue(state)
flakeRef(flakeRef), , flakeRef(flakeRef)
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}), , attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment})
prefixes(fragment == "" ? Strings{} : prefixes), , prefixes(fragment == "" ? Strings{} : prefixes)
extendedOutputsSpec(std::move(extendedOutputsSpec)), , extendedOutputsSpec(std::move(extendedOutputsSpec))
lockFlags(lockFlags) , lockFlags(lockFlags)
{ {
if (cmd && cmd->getAutoArgs(*state)->size()) if (cmd && cmd->getAutoArgs(*state)->size())
throw UsageError("'--arg' and '--argstr' are incompatible with flakes"); throw UsageError("'--arg' and '--argstr' are incompatible with flakes");
@ -87,18 +90,14 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
auto v = attr->forceValue(); auto v = attr->forceValue();
if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths( if (std::optional derivedPathWithInfo = trySinglePathToDerivedPaths(
v, v, noPos, fmt("while evaluating the flake output attribute '%s'", attrPath))) {
noPos,
fmt("while evaluating the flake output attribute '%s'", attrPath)))
{
return {*derivedPathWithInfo}; return {*derivedPathWithInfo};
} else { } else {
throw Error( throw Error(
"expected flake output attribute '%s' to be a derivation or path but found %s: %s", "expected flake output attribute '%s' to be a derivation or path but found %s: %s",
attrPath, attrPath,
showType(v), showType(v),
ValuePrinter(*this->state, v, errorPrintOptions) ValuePrinter(*this->state, v, errorPrintOptions));
);
} }
} }
@ -113,9 +112,11 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
} }
return {{ return {{
.path = DerivedPath::Built { .path =
DerivedPath::Built{
.drvPath = makeConstantStorePathRef(std::move(drvPath)), .drvPath = makeConstantStorePathRef(std::move(drvPath)),
.outputs = std::visit(overloaded { .outputs = std::visit(
overloaded{
[&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec { [&](const ExtendedOutputsSpec::Default & d) -> OutputsSpec {
StringSet outputsToInstall; StringSet outputsToInstall;
if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) { if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
@ -134,10 +135,9 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
return OutputsSpec::Names{std::move(outputsToInstall)}; return OutputsSpec::Names{std::move(outputsToInstall)};
}, },
[&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec { [&](const ExtendedOutputsSpec::Explicit & e) -> OutputsSpec { return e; },
return e;
}, },
}, extendedOutputsSpec.raw), extendedOutputsSpec.raw),
}, },
.info = make_ref<ExtraPathInfoFlake>( .info = make_ref<ExtraPathInfoFlake>(
ExtraPathInfoValue::Value{ ExtraPathInfoValue::Value{
@ -157,8 +157,7 @@ std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
return {&getCursor(state)->forceValue(), noPos}; return {&getCursor(state)->forceValue(), noPos};
} }
std::vector<ref<eval_cache::AttrCursor>> std::vector<ref<eval_cache::AttrCursor>> InstallableFlake::getCursors(EvalState & state)
InstallableFlake::getCursors(EvalState & state)
{ {
auto evalCache = openEvalCache(state, getLockedFlake()); auto evalCache = openEvalCache(state, getLockedFlake());
@ -181,11 +180,7 @@ InstallableFlake::getCursors(EvalState & state)
} }
if (res.size() == 0) if (res.size() == 0)
throw Error( throw Error(suggestions, "flake '%s' does not provide attribute %s", flakeRef, showAttrPaths(attrPaths));
suggestions,
"flake '%s' does not provide attribute %s",
flakeRef,
showAttrPaths(attrPaths));
return res; return res;
} }
@ -196,8 +191,8 @@ std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
flake::LockFlags lockFlagsApplyConfig = lockFlags; flake::LockFlags lockFlagsApplyConfig = lockFlags;
// FIXME why this side effect? // FIXME why this side effect?
lockFlagsApplyConfig.applyNixConfig = true; lockFlagsApplyConfig.applyNixConfig = true;
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake( _lockedFlake =
flakeSettings, *state, flakeRef, lockFlagsApplyConfig)); std::make_shared<flake::LockedFlake>(lockFlake(flakeSettings, *state, flakeRef, lockFlagsApplyConfig));
} }
return _lockedFlake; return _lockedFlake;
} }

View file

@ -4,17 +4,14 @@
namespace nix { namespace nix {
std::vector<ref<eval_cache::AttrCursor>> std::vector<ref<eval_cache::AttrCursor>> InstallableValue::getCursors(EvalState & state)
InstallableValue::getCursors(EvalState & state)
{ {
auto evalCache = auto evalCache =
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state, std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state, [&]() { return toValue(state).first; });
[&]() { return toValue(state).first; });
return {evalCache->getRoot()}; return {evalCache->getRoot()};
} }
ref<eval_cache::AttrCursor> ref<eval_cache::AttrCursor> InstallableValue::getCursor(EvalState & state)
InstallableValue::getCursor(EvalState & state)
{ {
/* Although getCursors should return at least one element, in case it doesn't, /* Although getCursors should return at least one element, in case it doesn't,
bound check to avoid an undefined behavior for vector[0] */ bound check to avoid an undefined behavior for vector[0] */
@ -42,12 +39,14 @@ ref<InstallableValue> InstallableValue::require(ref<Installable> installable)
return ref{castedInstallable}; return ref{castedInstallable};
} }
std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx) std::optional<DerivedPathWithInfo>
InstallableValue::trySinglePathToDerivedPaths(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
if (v.type() == nPath) { if (v.type() == nPath) {
auto storePath = fetchToStore(state->fetchSettings, *state->store, v.path(), FetchMode::Copy); auto storePath = fetchToStore(state->fetchSettings, *state->store, v.path(), FetchMode::Copy);
return {{ return {{
.path = DerivedPath::Opaque { .path =
DerivedPath::Opaque{
.path = std::move(storePath), .path = std::move(storePath),
}, },
.info = make_ref<ExtraPathInfo>(), .info = make_ref<ExtraPathInfo>(),
@ -56,13 +55,13 @@ std::optional<DerivedPathWithInfo> InstallableValue::trySinglePathToDerivedPaths
else if (v.type() == nString) { else if (v.type() == nString) {
return {{ return {{
.path = DerivedPath::fromSingle( .path = DerivedPath::fromSingle(state->coerceToSingleDerivedPath(pos, v, errorCtx)),
state->coerceToSingleDerivedPath(pos, v, errorCtx)),
.info = make_ref<ExtraPathInfo>(), .info = make_ref<ExtraPathInfo>(),
}}; }};
} }
else return std::nullopt; else
return std::nullopt;
} }
} }

View file

@ -61,7 +61,8 @@ MixFlakeOptions::MixFlakeOptions()
.category = category, .category = category,
.handler = {[&]() { .handler = {[&]() {
lockFlags.recreateLockFile = true; lockFlags.recreateLockFile = true;
warn("'--recreate-lock-file' is deprecated and will be removed in a future version; use 'nix flake update' instead."); warn(
"'--recreate-lock-file' is deprecated and will be removed in a future version; use 'nix flake update' instead.");
}}, }},
}); });
@ -158,9 +159,7 @@ MixFlakeOptions::MixFlakeOptions()
.description = "Write the given lock file instead of `flake.lock` within the top-level flake.", .description = "Write the given lock file instead of `flake.lock` within the top-level flake.",
.category = category, .category = category,
.labels = {"flake-lock-path"}, .labels = {"flake-lock-path"},
.handler = {[&](std::string lockFilePath) { .handler = {[&](std::string lockFilePath) { lockFlags.outputLockFilePath = lockFilePath; }},
lockFlags.outputLockFilePath = lockFilePath;
}},
.completer = completePath, .completer = completePath,
}); });
@ -209,7 +208,8 @@ SourceExprCommand::SourceExprCommand()
addFlag({ addFlag({
.longName = "expr", .longName = "expr",
.description = "Interpret [*installables*](@docroot@/command-ref/new-cli/nix.md#installables) as attribute paths relative to the Nix expression *expr*.", .description =
"Interpret [*installables*](@docroot@/command-ref/new-cli/nix.md#installables) as attribute paths relative to the Nix expression *expr*.",
.category = installablesCategory, .category = installablesCategory,
.labels = {"expr"}, .labels = {"expr"},
.handler = {&expr}, .handler = {&expr},
@ -220,8 +220,7 @@ MixReadOnlyOption::MixReadOnlyOption()
{ {
addFlag({ addFlag({
.longName = "read-only", .longName = "read-only",
.description = .description = "Do not instantiate each evaluated derivation. "
"Do not instantiate each evaluated derivation. "
"This improves performance, but can cause errors when accessing " "This improves performance, but can cause errors when accessing "
"store paths of derivations during evaluation.", "store paths of derivations during evaluation.",
.handler = {&settings.readOnlyMode, true}, .handler = {&settings.readOnlyMode, true},
@ -230,22 +229,17 @@ MixReadOnlyOption::MixReadOnlyOption()
Strings SourceExprCommand::getDefaultFlakeAttrPaths() Strings SourceExprCommand::getDefaultFlakeAttrPaths()
{ {
return { return {"packages." + settings.thisSystem.get() + ".default", "defaultPackage." + settings.thisSystem.get()};
"packages." + settings.thisSystem.get() + ".default",
"defaultPackage." + settings.thisSystem.get()
};
} }
Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
{ {
return { return {// As a convenience, look for the attribute in
// As a convenience, look for the attribute in
// 'outputs.packages'. // 'outputs.packages'.
"packages." + settings.thisSystem.get() + ".", "packages." + settings.thisSystem.get() + ".",
// As a temporary hack until Nixpkgs is properly converted // As a temporary hack until Nixpkgs is properly converted
// to provide a clean 'packages' set, look in 'legacyPackages'. // to provide a clean 'packages' set, look in 'legacyPackages'.
"legacyPackages." + settings.thisSystem.get() + "." "legacyPackages." + settings.thisSystem.get() + "."};
};
} }
Args::CompleterClosure SourceExprCommand::getCompleteInstallable() Args::CompleterClosure SourceExprCommand::getCompleteInstallable()
@ -263,10 +257,7 @@ void SourceExprCommand::completeInstallable(AddCompletions & completions, std::s
evalSettings.pureEval = false; evalSettings.pureEval = false;
auto state = getEvalState(); auto state = getEvalState();
auto e = auto e = state->parseExprFromFile(resolveExprPath(lookupFileArg(*state, *file)));
state->parseExprFromFile(
resolveExprPath(
lookupFileArg(*state, *file)));
Value root; Value root;
state->eval(e, root); state->eval(e, root);
@ -341,11 +332,12 @@ void completeFlakeRefWithFragment(
auto flakeRefS = std::string(prefix.substr(0, hash)); auto flakeRefS = std::string(prefix.substr(0, hash));
// TODO: ideally this would use the command base directory instead of assuming ".". // TODO: ideally this would use the command base directory instead of assuming ".".
auto flakeRef = parseFlakeRef(fetchSettings, expandTilde(flakeRefS), std::filesystem::current_path().string()); auto flakeRef =
parseFlakeRef(fetchSettings, expandTilde(flakeRefS), std::filesystem::current_path().string());
auto evalCache = openEvalCache(*evalState, auto evalCache = openEvalCache(
std::make_shared<flake::LockedFlake>(lockFlake( *evalState,
flakeSettings, *evalState, flakeRef, lockFlags))); std::make_shared<flake::LockedFlake>(lockFlake(flakeSettings, *evalState, flakeRef, lockFlags)));
auto root = evalCache->getRoot(); auto root = evalCache->getRoot();
@ -369,7 +361,8 @@ void completeFlakeRefWithFragment(
} }
auto attr = root->findAlongAttrPath(attrPath); auto attr = root->findAlongAttrPath(attrPath);
if (!attr) continue; if (!attr)
continue;
for (auto & attr2 : (*attr)->getAttrs()) { for (auto & attr2 : (*attr)->getAttrs()) {
if (hasPrefix(evalState->symbols[attr2], lastAttr)) { if (hasPrefix(evalState->symbols[attr2], lastAttr)) {
@ -377,7 +370,9 @@ void completeFlakeRefWithFragment(
/* Strip the attrpath prefix. */ /* Strip the attrpath prefix. */
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size()); attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
// FIXME: handle names with dots // FIXME: handle names with dots
completions.add(flakeRefS + "#" + prefixRoot + concatStringsSep(".", evalState->symbols.resolve(attrPath2))); completions.add(
flakeRefS + "#" + prefixRoot
+ concatStringsSep(".", evalState->symbols.resolve(attrPath2)));
} }
} }
} }
@ -387,7 +382,8 @@ void completeFlakeRefWithFragment(
if (fragment.empty()) { if (fragment.empty()) {
for (auto & attrPath : defaultFlakeAttrPaths) { for (auto & attrPath : defaultFlakeAttrPaths) {
auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath)); auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath));
if (!attr) continue; if (!attr)
continue;
completions.add(flakeRefS + "#" + prefixRoot); completions.add(flakeRefS + "#" + prefixRoot);
} }
} }
@ -427,14 +423,12 @@ DerivedPathWithInfo Installable::toDerivedPath()
{ {
auto buildables = toDerivedPaths(); auto buildables = toDerivedPaths();
if (buildables.size() != 1) if (buildables.size() != 1)
throw Error("installable '%s' evaluates to %d derivations, where only one is expected", what(), buildables.size()); throw Error(
"installable '%s' evaluates to %d derivations, where only one is expected", what(), buildables.size());
return std::move(buildables[0]); return std::move(buildables[0]);
} }
static StorePath getDeriver( static StorePath getDeriver(ref<Store> store, const Installable & i, const StorePath & drvPath)
ref<Store> store,
const Installable & i,
const StorePath & drvPath)
{ {
auto derivers = store->queryValidDerivers(drvPath); auto derivers = store->queryValidDerivers(drvPath);
if (derivers.empty()) if (derivers.empty())
@ -443,15 +437,12 @@ static StorePath getDeriver(
return *derivers.begin(); return *derivers.begin();
} }
ref<eval_cache::EvalCache> openEvalCache( ref<eval_cache::EvalCache> openEvalCache(EvalState & state, std::shared_ptr<flake::LockedFlake> lockedFlake)
EvalState & state,
std::shared_ptr<flake::LockedFlake> lockedFlake)
{ {
auto fingerprint = evalSettings.useEvalCache && evalSettings.pureEval auto fingerprint = evalSettings.useEvalCache && evalSettings.pureEval
? lockedFlake->getFingerprint(state.store, state.fetchSettings) ? lockedFlake->getFingerprint(state.store, state.fetchSettings)
: std::nullopt; : std::nullopt;
auto rootLoader = [&state, lockedFlake]() auto rootLoader = [&state, lockedFlake]() {
{
/* For testing whether the evaluation cache is /* For testing whether the evaluation cache is
complete. */ complete. */
if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0") if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
@ -471,7 +462,10 @@ ref<eval_cache::EvalCache> openEvalCache(
if (fingerprint) { if (fingerprint) {
auto search = state.evalCaches.find(fingerprint.value()); auto search = state.evalCaches.find(fingerprint.value());
if (search == state.evalCaches.end()) { if (search == state.evalCaches.end()) {
search = state.evalCaches.emplace(fingerprint.value(), make_ref<nix::eval_cache::EvalCache>(fingerprint, state, rootLoader)).first; search =
state.evalCaches
.emplace(fingerprint.value(), make_ref<nix::eval_cache::EvalCache>(fingerprint, state, rootLoader))
.first;
} }
return search->second; return search->second;
} else { } else {
@ -479,8 +473,7 @@ ref<eval_cache::EvalCache> openEvalCache(
} }
} }
Installables SourceExprCommand::parseInstallables( Installables SourceExprCommand::parseInstallables(ref<Store> store, std::vector<std::string> ss)
ref<Store> store, std::vector<std::string> ss)
{ {
Installables result; Installables result;
@ -501,12 +494,10 @@ Installables SourceExprCommand::parseInstallables(
if (file == "-") { if (file == "-") {
auto e = state->parseStdin(); auto e = state->parseStdin();
state->eval(e, *vFile); state->eval(e, *vFile);
} } else if (file) {
else if (file) {
auto dir = absPath(getCommandBaseDir()); auto dir = absPath(getCommandBaseDir());
state->evalFile(lookupFileArg(*state, *file, &dir), *vFile); state->evalFile(lookupFileArg(*state, *file, &dir), *vFile);
} } else {
else {
Path dir = absPath(getCommandBaseDir()); Path dir = absPath(getCommandBaseDir());
auto e = state->parseExprFromString(*expr, state->rootPath(dir)); auto e = state->parseExprFromString(*expr, state->rootPath(dir));
state->eval(e, *vFile); state->eval(e, *vFile);
@ -515,8 +506,7 @@ Installables SourceExprCommand::parseInstallables(
for (auto & s : ss) { for (auto & s : ss) {
auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s); auto [prefix, extendedOutputsSpec] = ExtendedOutputsSpec::parse(s);
result.push_back( result.push_back(
make_ref<InstallableAttrPath>( make_ref<InstallableAttrPath>(InstallableAttrPath::parse(
InstallableAttrPath::parse(
state, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec)))); state, *this, vFile, std::move(prefix), std::move(extendedOutputsSpec))));
} }
@ -532,7 +522,8 @@ Installables SourceExprCommand::parseInstallables(
if (prefix.find('/') != std::string::npos) { if (prefix.find('/') != std::string::npos) {
try { try {
result.push_back(make_ref<InstallableDerivedPath>( result.push_back(
make_ref<InstallableDerivedPath>(
InstallableDerivedPath::parse(store, prefix, extendedOutputsSpec.raw))); InstallableDerivedPath::parse(store, prefix, extendedOutputsSpec.raw)));
continue; continue;
} catch (BadStorePath &) { } catch (BadStorePath &) {
@ -543,9 +534,10 @@ Installables SourceExprCommand::parseInstallables(
} }
try { try {
auto [flakeRef, fragment] = parseFlakeRefWithFragment( auto [flakeRef, fragment] =
fetchSettings, std::string { prefix }, absPath(getCommandBaseDir())); parseFlakeRefWithFragment(fetchSettings, std::string{prefix}, absPath(getCommandBaseDir()));
result.push_back(make_ref<InstallableFlake>( result.push_back(
make_ref<InstallableFlake>(
this, this,
getEvalState(), getEvalState(),
std::move(flakeRef), std::move(flakeRef),
@ -566,8 +558,7 @@ Installables SourceExprCommand::parseInstallables(
return result; return result;
} }
ref<Installable> SourceExprCommand::parseInstallable( ref<Installable> SourceExprCommand::parseInstallable(ref<Store> store, const std::string & installable)
ref<Store> store, const std::string & installable)
{ {
auto installables = parseInstallables(store, {installable}); auto installables = parseInstallables(store, {installable});
assert(installables.size() == 1); assert(installables.size() == 1);
@ -578,9 +569,7 @@ static SingleBuiltPath getBuiltPath(ref<Store> evalStore, ref<Store> store, cons
{ {
return std::visit( return std::visit(
overloaded{ overloaded{
[&](const SingleDerivedPath::Opaque & bo) -> SingleBuiltPath { [&](const SingleDerivedPath::Opaque & bo) -> SingleBuiltPath { return SingleBuiltPath::Opaque{bo.path}; },
return SingleBuiltPath::Opaque { bo.path };
},
[&](const SingleDerivedPath::Built & bfd) -> SingleBuiltPath { [&](const SingleDerivedPath::Built & bfd) -> SingleBuiltPath {
auto drvPath = getBuiltPath(evalStore, store, *bfd.drvPath); auto drvPath = getBuiltPath(evalStore, store, *bfd.drvPath);
// Resolving this instead of `bfd` will yield the same result, but avoid duplicative work. // Resolving this instead of `bfd` will yield the same result, but avoid duplicative work.
@ -599,11 +588,7 @@ static SingleBuiltPath getBuiltPath(ref<Store> evalStore, ref<Store> store, cons
} }
std::vector<BuiltPathWithResult> Installable::build( std::vector<BuiltPathWithResult> Installable::build(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, const Installables & installables, BuildMode bMode)
ref<Store> store,
Realise mode,
const Installables & installables,
BuildMode bMode)
{ {
std::vector<BuiltPathWithResult> res; std::vector<BuiltPathWithResult> res;
for (auto & [_, builtPathWithResult] : build2(evalStore, store, mode, installables, bMode)) for (auto & [_, builtPathWithResult] : build2(evalStore, store, mode, installables, bMode))
@ -611,9 +596,7 @@ std::vector<BuiltPathWithResult> Installable::build(
return res; return res;
} }
static void throwBuildErrors( static void throwBuildErrors(std::vector<KeyedBuildResult> & buildResults, const Store & store)
std::vector<KeyedBuildResult> & buildResults,
const Store & store)
{ {
std::vector<KeyedBuildResult> failed; std::vector<KeyedBuildResult> failed;
for (auto & buildResult : buildResults) { for (auto & buildResult : buildResults) {
@ -630,7 +613,8 @@ static void throwBuildErrors(
StringSet failedPaths; StringSet failedPaths;
for (; failedResult != failed.end(); failedResult++) { for (; failedResult != failed.end(); failedResult++) {
if (!failedResult->errorMsg.empty()) { if (!failedResult->errorMsg.empty()) {
logError(ErrorInfo{ logError(
ErrorInfo{
.level = lvlError, .level = lvlError,
.msg = failedResult->errorMsg, .msg = failedResult->errorMsg,
}); });
@ -643,11 +627,7 @@ static void throwBuildErrors(
} }
std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build2( std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build2(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, const Installables & installables, BuildMode bMode)
ref<Store> store,
Realise mode,
const Installables & installables,
BuildMode bMode)
{ {
if (mode == Realise::Nothing) if (mode == Realise::Nothing)
settings.readOnlyMode = true; settings.readOnlyMode = true;
@ -678,22 +658,25 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
for (auto & path : pathsToBuild) { for (auto & path : pathsToBuild) {
for (auto & aux : backmap[path]) { for (auto & aux : backmap[path]) {
std::visit(overloaded { std::visit(
overloaded{
[&](const DerivedPath::Built & bfd) { [&](const DerivedPath::Built & bfd) {
auto outputs = resolveDerivedPath(*store, bfd, &*evalStore); auto outputs = resolveDerivedPath(*store, bfd, &*evalStore);
res.push_back({aux.installable, { res.push_back(
.path = BuiltPath::Built { {aux.installable,
.drvPath = make_ref<SingleBuiltPath>(getBuiltPath(evalStore, store, *bfd.drvPath)), {.path =
BuiltPath::Built{
.drvPath =
make_ref<SingleBuiltPath>(getBuiltPath(evalStore, store, *bfd.drvPath)),
.outputs = outputs, .outputs = outputs,
}, },
.info = aux.info}}); .info = aux.info}});
}, },
[&](const DerivedPath::Opaque & bo) { [&](const DerivedPath::Opaque & bo) {
res.push_back({aux.installable, { res.push_back({aux.installable, {.path = BuiltPath::Opaque{bo.path}, .info = aux.info}});
.path = BuiltPath::Opaque { bo.path },
.info = aux.info}});
}, },
}, path.raw()); },
path.raw());
} }
} }
@ -707,26 +690,30 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
throwBuildErrors(buildResults, *store); throwBuildErrors(buildResults, *store);
for (auto & buildResult : buildResults) { for (auto & buildResult : buildResults) {
for (auto & aux : backmap[buildResult.path]) { for (auto & aux : backmap[buildResult.path]) {
std::visit(overloaded { std::visit(
overloaded{
[&](const DerivedPath::Built & bfd) { [&](const DerivedPath::Built & bfd) {
std::map<std::string, StorePath> outputs; std::map<std::string, StorePath> outputs;
for (auto & [outputName, realisation] : buildResult.builtOutputs) for (auto & [outputName, realisation] : buildResult.builtOutputs)
outputs.emplace(outputName, realisation.outPath); outputs.emplace(outputName, realisation.outPath);
res.push_back({aux.installable, { res.push_back(
.path = BuiltPath::Built { {aux.installable,
.drvPath = make_ref<SingleBuiltPath>(getBuiltPath(evalStore, store, *bfd.drvPath)), {.path =
BuiltPath::Built{
.drvPath =
make_ref<SingleBuiltPath>(getBuiltPath(evalStore, store, *bfd.drvPath)),
.outputs = outputs, .outputs = outputs,
}, },
.info = aux.info, .info = aux.info,
.result = buildResult}}); .result = buildResult}});
}, },
[&](const DerivedPath::Opaque & bo) { [&](const DerivedPath::Opaque & bo) {
res.push_back({aux.installable, { res.push_back(
.path = BuiltPath::Opaque { bo.path }, {aux.installable,
.info = aux.info, {.path = BuiltPath::Opaque{bo.path}, .info = aux.info, .result = buildResult}});
.result = buildResult}});
}, },
}, buildResult.path.raw()); },
buildResult.path.raw());
} }
} }
@ -741,11 +728,7 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
} }
BuiltPaths Installable::toBuiltPaths( BuiltPaths Installable::toBuiltPaths(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, OperateOn operateOn, const Installables & installables)
ref<Store> store,
Realise mode,
OperateOn operateOn,
const Installables & installables)
{ {
if (operateOn == OperateOn::Output) { if (operateOn == OperateOn::Output) {
BuiltPaths res; BuiltPaths res;
@ -764,10 +747,7 @@ BuiltPaths Installable::toBuiltPaths(
} }
StorePathSet Installable::toStorePathSet( StorePathSet Installable::toStorePathSet(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, OperateOn operateOn, const Installables & installables)
ref<Store> store,
Realise mode, OperateOn operateOn,
const Installables & installables)
{ {
StorePathSet outPaths; StorePathSet outPaths;
for (auto & path : toBuiltPaths(evalStore, store, mode, operateOn, installables)) { for (auto & path : toBuiltPaths(evalStore, store, mode, operateOn, installables)) {
@ -778,10 +758,7 @@ StorePathSet Installable::toStorePathSet(
} }
StorePaths Installable::toStorePaths( StorePaths Installable::toStorePaths(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, OperateOn operateOn, const Installables & installables)
ref<Store> store,
Realise mode, OperateOn operateOn,
const Installables & installables)
{ {
StorePaths outPaths; StorePaths outPaths;
for (auto & path : toBuiltPaths(evalStore, store, mode, operateOn, installables)) { for (auto & path : toBuiltPaths(evalStore, store, mode, operateOn, installables)) {
@ -792,10 +769,7 @@ StorePaths Installable::toStorePaths(
} }
StorePath Installable::toStorePath( StorePath Installable::toStorePath(
ref<Store> evalStore, ref<Store> evalStore, ref<Store> store, Realise mode, OperateOn operateOn, ref<Installable> installable)
ref<Store> store,
Realise mode, OperateOn operateOn,
ref<Installable> installable)
{ {
auto paths = toStorePathSet(evalStore, store, mode, operateOn, {installable}); auto paths = toStorePathSet(evalStore, store, mode, operateOn, {installable});
@ -805,28 +779,23 @@ StorePath Installable::toStorePath(
return *paths.begin(); return *paths.begin();
} }
StorePathSet Installable::toDerivations( StorePathSet Installable::toDerivations(ref<Store> store, const Installables & installables, bool useDeriver)
ref<Store> store,
const Installables & installables,
bool useDeriver)
{ {
StorePathSet drvPaths; StorePathSet drvPaths;
for (const auto & i : installables) for (const auto & i : installables)
for (const auto & b : i->toDerivedPaths()) for (const auto & b : i->toDerivedPaths())
std::visit(overloaded { std::visit(
overloaded{
[&](const DerivedPath::Opaque & bo) { [&](const DerivedPath::Opaque & bo) {
drvPaths.insert( drvPaths.insert(
bo.path.isDerivation() bo.path.isDerivation() ? bo.path
? bo.path : useDeriver ? getDeriver(store, *i, bo.path)
: useDeriver
? getDeriver(store, *i, bo.path)
: throw Error("argument '%s' did not evaluate to a derivation", i->what())); : throw Error("argument '%s' did not evaluate to a derivation", i->what()));
}, },
[&](const DerivedPath::Built & bfd) { [&](const DerivedPath::Built & bfd) { drvPaths.insert(resolveDerivedPath(*store, *bfd.drvPath)); },
drvPaths.insert(resolveDerivedPath(*store, *bfd.drvPath));
}, },
}, b.path.raw()); b.path.raw());
return drvPaths; return drvPaths;
} }
@ -861,10 +830,7 @@ std::vector<FlakeRef> RawInstallablesCommand::getFlakeRefsForCompletion()
std::vector<FlakeRef> res; std::vector<FlakeRef> res;
res.reserve(rawInstallables.size()); res.reserve(rawInstallables.size());
for (const auto & i : rawInstallables) for (const auto & i : rawInstallables)
res.push_back(parseFlakeRefWithFragment( res.push_back(parseFlakeRefWithFragment(fetchSettings, expandTilde(i), absPath(getCommandBaseDir())).first);
fetchSettings,
expandTilde(i),
absPath(getCommandBaseDir())).first);
return res; return res;
} }
@ -883,12 +849,7 @@ void RawInstallablesCommand::run(ref<Store> store)
std::vector<FlakeRef> InstallableCommand::getFlakeRefsForCompletion() std::vector<FlakeRef> InstallableCommand::getFlakeRefsForCompletion()
{ {
return { return {parseFlakeRefWithFragment(fetchSettings, expandTilde(_installable), absPath(getCommandBaseDir())).first};
parseFlakeRefWithFragment(
fetchSettings,
expandTilde(_installable),
absPath(getCommandBaseDir())).first
};
} }
void InstallablesCommand::run(ref<Store> store, std::vector<std::string> && rawInstallables) void InstallablesCommand::run(ref<Store> store, std::vector<std::string> && rawInstallables)

View file

@ -25,8 +25,7 @@ static std::string doRenderMarkdownToTerminal(std::string_view markdown)
.vmargin = 0, .vmargin = 0,
}; };
# endif # endif
struct lowdown_opts opts struct lowdown_opts opts{
{
.type = LOWDOWN_TERM, .type = LOWDOWN_TERM,
# if HAVE_LOWDOWN_1_4 # if HAVE_LOWDOWN_1_4
.term = opts_term, .term = opts_term,

View file

@ -1,7 +1,6 @@
#include "nix/cmd/misc-store-flags.hh" #include "nix/cmd/misc-store-flags.hh"
namespace nix::flag namespace nix::flag {
{
static void hashFormatCompleter(AddCompletions & completions, size_t index, std::string_view prefix) static void hashFormatCompleter(AddCompletions & completions, size_t index, std::string_view prefix)
{ {
@ -19,9 +18,7 @@ Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf)
.longName = std::move(longName), .longName = std::move(longName),
.description = "Hash format (`base16`, `nix32`, `base64`, `sri`). Default: `sri`.", .description = "Hash format (`base16`, `nix32`, `base64`, `sri`). Default: `sri`.",
.labels = {"hash-format"}, .labels = {"hash-format"},
.handler = {[hf](std::string s) { .handler = {[hf](std::string s) { *hf = parseHashFormat(s); }},
*hf = parseHashFormat(s);
}},
.completer = hashFormatCompleter, .completer = hashFormatCompleter,
}; };
} }
@ -32,9 +29,7 @@ Args::Flag hashFormatOpt(std::string && longName, std::optional<HashFormat> * oh
.longName = std::move(longName), .longName = std::move(longName),
.description = "Hash format (`base16`, `nix32`, `base64`, `sri`).", .description = "Hash format (`base16`, `nix32`, `base64`, `sri`).",
.labels = {"hash-format"}, .labels = {"hash-format"},
.handler = {[ohf](std::string s) { .handler = {[ohf](std::string s) { *ohf = std::optional<HashFormat>{parseHashFormat(s)}; }},
*ohf = std::optional<HashFormat>{parseHashFormat(s)};
}},
.completer = hashFormatCompleter, .completer = hashFormatCompleter,
}; };
} }
@ -52,9 +47,7 @@ Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha)
.longName = std::move(longName), .longName = std::move(longName),
.description = "Hash algorithm (`blake3`, `md5`, `sha1`, `sha256`, or `sha512`).", .description = "Hash algorithm (`blake3`, `md5`, `sha1`, `sha256`, or `sha512`).",
.labels = {"hash-algo"}, .labels = {"hash-algo"},
.handler = {[ha](std::string s) { .handler = {[ha](std::string s) { *ha = parseHashAlgo(s); }},
*ha = parseHashAlgo(s);
}},
.completer = hashAlgoCompleter, .completer = hashAlgoCompleter,
}; };
} }
@ -63,11 +56,10 @@ Args::Flag hashAlgoOpt(std::string && longName, std::optional<HashAlgorithm> * o
{ {
return Args::Flag{ return Args::Flag{
.longName = std::move(longName), .longName = std::move(longName),
.description = "Hash algorithm (`blake3`, `md5`, `sha1`, `sha256`, or `sha512`). Can be omitted for SRI hashes.", .description =
"Hash algorithm (`blake3`, `md5`, `sha1`, `sha256`, or `sha512`). Can be omitted for SRI hashes.",
.labels = {"hash-algo"}, .labels = {"hash-algo"},
.handler = {[oha](std::string s) { .handler = {[oha](std::string s) { *oha = std::optional<HashAlgorithm>{parseHashAlgo(s)}; }},
*oha = std::optional<HashAlgorithm>{parseHashAlgo(s)};
}},
.completer = hashAlgoCompleter, .completer = hashAlgoCompleter,
}; };
} }
@ -92,9 +84,7 @@ Args::Flag fileIngestionMethod(FileIngestionMethod * method)
it to the hash function. it to the hash function.
)", )",
.labels = {"file-ingestion-method"}, .labels = {"file-ingestion-method"},
.handler = {[method](std::string s) { .handler = {[method](std::string s) { *method = parseFileIngestionMethod(s); }},
*method = parseFileIngestionMethod(s);
}},
}; };
} }
@ -126,9 +116,7 @@ Args::Flag contentAddressMethod(ContentAddressMethod * method)
for regular usage prefer `nar` and `flat`. for regular usage prefer `nar` and `flat`.
)", )",
.labels = {"content-address-method"}, .labels = {"content-address-method"},
.handler = {[method](std::string s) { .handler = {[method](std::string s) { *method = ContentAddressMethod::parse(s); }},
*method = ContentAddressMethod::parse(s);
}},
}; };
} }

View file

@ -183,8 +183,7 @@ bool ReadlineLikeInteracter::getLine(std::string & input, ReplPromptType promptT
// editline doesn't echo the input to the output when non-interactive, unlike readline // editline doesn't echo the input to the output when non-interactive, unlike readline
// this results in a different behavior when running tests. The echoing is // this results in a different behavior when running tests. The echoing is
// quite useful for reading the test output, so we add it here. // quite useful for reading the test output, so we add it here.
if (auto e = getEnv("_NIX_TEST_REPL_ECHO"); s && e && *e == "1") if (auto e = getEnv("_NIX_TEST_REPL_ECHO"); s && e && *e == "1") {
{
#if !USE_READLINE #if !USE_READLINE
// This is probably not right for multi-line input, but we don't use that // This is probably not right for multi-line input, but we don't use that
// in the characterisation tests, so it's fine. // in the characterisation tests, so it's fine.

View file

@ -54,10 +54,7 @@ enum class ProcessLineResult {
PromptAgain, PromptAgain,
}; };
struct NixRepl struct NixRepl : AbstractNixRepl, detail::ReplCompleterMixin, gc
: AbstractNixRepl
, detail::ReplCompleterMixin
, gc
{ {
size_t debugTraceIndex; size_t debugTraceIndex;
@ -79,8 +76,12 @@ struct NixRepl
std::unique_ptr<ReplInteracter> interacter; std::unique_ptr<ReplInteracter> interacter;
NixRepl(const LookupPath & lookupPath, nix::ref<Store> store,ref<EvalState> state, NixRepl(
std::function<AnnotatedValues()> getValues, RunNix * runNix); const LookupPath & lookupPath,
nix::ref<Store> store,
ref<EvalState> state,
std::function<AnnotatedValues()> getValues,
RunNix * runNix);
virtual ~NixRepl() = default; virtual ~NixRepl() = default;
ReplExitStatus mainLoop() override; ReplExitStatus mainLoop() override;
@ -101,13 +102,15 @@ struct NixRepl
void evalString(std::string s, Value & v); void evalString(std::string s, Value & v);
void loadDebugTraceEnv(DebugTrace & dt); void loadDebugTraceEnv(DebugTrace & dt);
void printValue(std::ostream & str, void printValue(std::ostream & str, Value & v, unsigned int maxDepth = std::numeric_limits<unsigned int>::max())
Value & v,
unsigned int maxDepth = std::numeric_limits<unsigned int>::max())
{ {
// Hide the progress bar during printing because it might interfere // Hide the progress bar during printing because it might interfere
auto suspension = logger->suspend(); auto suspension = logger->suspend();
::nix::printValue(*state, str, v, PrintOptions { ::nix::printValue(
*state,
str,
v,
PrintOptions{
.ansiColors = true, .ansiColors = true,
.force = true, .force = true,
.derivationPaths = true, .derivationPaths = true,
@ -122,13 +125,17 @@ std::string removeWhitespace(std::string s)
{ {
s = chomp(s); s = chomp(s);
size_t n = s.find_first_not_of(" \n\r\t"); size_t n = s.find_first_not_of(" \n\r\t");
if (n != std::string::npos) s = std::string(s, n); if (n != std::string::npos)
s = std::string(s, n);
return s; return s;
} }
NixRepl::NixRepl(
NixRepl::NixRepl(const LookupPath & lookupPath, nix::ref<Store> store, ref<EvalState> state, const LookupPath & lookupPath,
std::function<NixRepl::AnnotatedValues()> getValues, RunNix * runNix) nix::ref<Store> store,
ref<EvalState> state,
std::function<NixRepl::AnnotatedValues()> getValues,
RunNix * runNix)
: AbstractNixRepl(state) : AbstractNixRepl(state)
, debugTraceIndex(0) , debugTraceIndex(0)
, getValues(getValues) , getValues(getValues)
@ -184,7 +191,8 @@ ReplExitStatus NixRepl::mainLoop()
auto suspension = logger->suspend(); auto suspension = logger->suspend();
// When continuing input from previous lines, don't print a prompt, just align to the same // When continuing input from previous lines, don't print a prompt, just align to the same
// number of chars as the prompt. // number of chars as the prompt.
if (!interacter->getLine(input, input.empty() ? ReplPromptType::ReplPrompt : ReplPromptType::ContinuationPrompt)) { if (!interacter->getLine(
input, input.empty() ? ReplPromptType::ReplPrompt : ReplPromptType::ContinuationPrompt)) {
// Ctrl-D should exit the debugger. // Ctrl-D should exit the debugger.
state->debugStop = false; state->debugStop = false;
logger->cout(""); logger->cout("");
@ -260,7 +268,8 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
/* This is a variable name; look it up in the current scope. */ /* This is a variable name; look it up in the current scope. */
StringSet::iterator i = varNames.lower_bound(cur); StringSet::iterator i = varNames.lower_bound(cur);
while (i != varNames.end()) { while (i != varNames.end()) {
if (i->substr(0, cur.size()) != cur) break; if (i->substr(0, cur.size()) != cur)
break;
completions.insert(prev + *i); completions.insert(prev + *i);
i++; i++;
} }
@ -279,11 +288,15 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
Expr * e = parseString(expr); Expr * e = parseString(expr);
Value v; Value v;
e->eval(*state, *env, v); e->eval(*state, *env, v);
state->forceAttrs(v, noPos, "while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)"); state->forceAttrs(
v,
noPos,
"while evaluating an attrset for the purpose of completion (this error should not be displayed; file an issue?)");
for (auto & i : *v.attrs()) { for (auto & i : *v.attrs()) {
std::string_view name = state->symbols[i.name]; std::string_view name = state->symbols[i.name];
if (name.substr(0, cur2.size()) != cur2) continue; if (name.substr(0, cur2.size()) != cur2)
continue;
completions.insert(concatStrings(prev, expr, ".", name)); completions.insert(concatStrings(prev, expr, ".", name));
} }
@ -301,24 +314,23 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
return completions; return completions;
} }
// FIXME: DRY and match or use the parser // FIXME: DRY and match or use the parser
static bool isVarName(std::string_view s) static bool isVarName(std::string_view s)
{ {
if (s.size() == 0) return false; if (s.size() == 0)
return false;
char c = s[0]; char c = s[0];
if ((c >= '0' && c <= '9') || c == '-' || c == '\'') return false; if ((c >= '0' && c <= '9') || c == '-' || c == '\'')
return false;
for (auto & i : s) for (auto & i : s)
if (!((i >= 'a' && i <= 'z') || if (!((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == '-'
(i >= 'A' && i <= 'Z') || || i == '\''))
(i >= '0' && i <= '9') ||
i == '_' || i == '-' || i == '\''))
return false; return false;
return true; return true;
} }
StorePath NixRepl::getDerivationPath(Value & v)
StorePath NixRepl::getDerivationPath(Value & v) { {
auto packageInfo = getDerivation(*state, v, false); auto packageInfo = getDerivation(*state, v, false);
if (!packageInfo) if (!packageInfo)
throw Error("expression does not evaluate to a derivation, so I can't build it"); throw Error("expression does not evaluate to a derivation, so I can't build it");
@ -357,15 +369,15 @@ ProcessLineResult NixRepl::processLine(std::string line)
if (line[0] == ':') { if (line[0] == ':') {
size_t p = line.find_first_of(" \n\r\t"); size_t p = line.find_first_of(" \n\r\t");
command = line.substr(0, p); command = line.substr(0, p);
if (p != std::string::npos) arg = removeWhitespace(line.substr(p)); if (p != std::string::npos)
arg = removeWhitespace(line.substr(p));
} else { } else {
arg = line; arg = line;
} }
if (command == ":?" || command == ":help") { if (command == ":?" || command == ":help") {
// FIXME: convert to Markdown, include in the 'nix repl' manpage. // FIXME: convert to Markdown, include in the 'nix repl' manpage.
std::cout std::cout << "The following commands are available:\n"
<< "The following commands are available:\n"
<< "\n" << "\n"
<< " <expr> Evaluate and print expression\n" << " <expr> Evaluate and print expression\n"
<< " <x> = <expr> Bind expression to variable\n" << " <x> = <expr> Bind expression to variable\n"
@ -390,19 +402,16 @@ ProcessLineResult NixRepl::processLine(std::string line)
<< " :log <expr> Show logs for a derivation\n" << " :log <expr> Show logs for a derivation\n"
<< " :te, :trace-enable [bool] Enable, disable or toggle showing traces for\n" << " :te, :trace-enable [bool] Enable, disable or toggle showing traces for\n"
<< " errors\n" << " errors\n"
<< " :?, :help Brings up this help menu\n" << " :?, :help Brings up this help menu\n";
;
if (state->debugRepl) { if (state->debugRepl) {
std::cout std::cout << "\n"
<< "\n"
<< " Debug mode commands\n" << " Debug mode commands\n"
<< " :env Show env stack\n" << " :env Show env stack\n"
<< " :bt, :backtrace Show trace stack\n" << " :bt, :backtrace Show trace stack\n"
<< " :st Show current trace\n" << " :st Show current trace\n"
<< " :st <idx> Change to another trace in the stack\n" << " :st <idx> Change to another trace in the stack\n"
<< " :c, :continue Go until end of program, exception, or builtins.break\n" << " :c, :continue Go until end of program, exception, or builtins.break\n"
<< " :s, :step Go one step\n" << " :s, :step Go one step\n";
;
} }
} }
@ -427,7 +436,8 @@ ProcessLineResult NixRepl::processLine(std::string line)
try { try {
// change the DebugTrace index. // change the DebugTrace index.
debugTraceIndex = stoi(arg); debugTraceIndex = stoi(arg);
} catch (...) { } } catch (...) {
}
for (const auto & [idx, i] : enumerate(state->debugTraces)) { for (const auto & [idx, i] : enumerate(state->debugTraces)) {
if (idx == debugTraceIndex) { if (idx == debugTraceIndex) {
@ -553,9 +563,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
runNix("nix-env", {"-i", drvPathRaw}); runNix("nix-env", {"-i", drvPathRaw});
} else if (command == ":log") { } else if (command == ":log") {
settings.readOnlyMode = true; settings.readOnlyMode = true;
Finally roModeReset([&]() { Finally roModeReset([&]() { settings.readOnlyMode = false; });
settings.readOnlyMode = false;
});
auto subs = getDefaultSubstituters(); auto subs = getDefaultSubstituters();
subs.push_front(state->store); subs.push_front(state->store);
@ -578,7 +586,8 @@ ProcessLineResult NixRepl::processLine(std::string line)
break; break;
} }
} }
if (!foundLog) throw Error("build log of '%s' is not available", drvPathRaw); if (!foundLog)
throw Error("build log of '%s' is not available", drvPathRaw);
} else { } else {
runNix("nix-shell", {drvPathRaw}); runNix("nix-shell", {drvPathRaw});
} }
@ -641,9 +650,8 @@ ProcessLineResult NixRepl::processLine(std::string line)
for (auto & arg : args) for (auto & arg : args)
arg = "*" + arg + "*"; arg = "*" + arg + "*";
markdown += markdown += "**Synopsis:** `builtins." + (std::string) (*doc->name) + "` " + concatStringsSep(" ", args)
"**Synopsis:** `builtins." + (std::string) (*doc->name) + "` " + "\n\n";
+ concatStringsSep(" ", args) + "\n\n";
} }
markdown += stripIndentation(doc->doc); markdown += stripIndentation(doc->doc);
@ -684,11 +692,8 @@ ProcessLineResult NixRepl::processLine(std::string line)
else { else {
size_t p = line.find('='); size_t p = line.find('=');
std::string name; std::string name;
if (p != std::string::npos && if (p != std::string::npos && p < line.size() && line[p + 1] != '='
p < line.size() && && isVarName(name = removeWhitespace(line.substr(0, p)))) {
line[p + 1] != '=' &&
isVarName(name = removeWhitespace(line.substr(0, p))))
{
Expr * e = parseString(line.substr(p + 1)); Expr * e = parseString(line.substr(p + 1));
Value & v(*state->allocValue()); Value & v(*state->allocValue());
v.mkThunk(env, e); v.mkThunk(env, e);
@ -736,8 +741,12 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
Value v; Value v;
flake::callFlake(*state, flake::callFlake(
flake::lockFlake(flakeSettings, *state, flakeRef, *state,
flake::lockFlake(
flakeSettings,
*state,
flakeRef,
flake::LockFlags{ flake::LockFlags{
.updateLockFile = false, .updateLockFile = false,
.useRegistries = !evalSettings.pureEval, .useRegistries = !evalSettings.pureEval,
@ -747,7 +756,6 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
addAttrsToScope(v); addAttrsToScope(v);
} }
void NixRepl::initEnv() void NixRepl::initEnv()
{ {
env = &state->allocEnv(envSize); env = &state->allocEnv(envSize);
@ -760,7 +768,6 @@ void NixRepl::initEnv()
varNames.emplace(state->symbols[i.first]); varNames.emplace(state->symbols[i.first]);
} }
void NixRepl::reloadFilesAndFlakes() void NixRepl::reloadFilesAndFlakes()
{ {
initEnv(); initEnv();
@ -769,7 +776,6 @@ void NixRepl::reloadFilesAndFlakes()
loadFlakes(); loadFlakes();
} }
void NixRepl::loadFiles() void NixRepl::loadFiles()
{ {
Strings old = loadedFiles; Strings old = loadedFiles;
@ -786,7 +792,6 @@ void NixRepl::loadFiles()
} }
} }
void NixRepl::loadFlakes() void NixRepl::loadFlakes()
{ {
Strings old = loadedFlakes; Strings old = loadedFlakes;
@ -798,10 +803,12 @@ void NixRepl::loadFlakes()
} }
} }
void NixRepl::addAttrsToScope(Value & attrs) void NixRepl::addAttrsToScope(Value & attrs)
{ {
state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }, "while evaluating an attribute set to be merged in the global scope"); state->forceAttrs(
attrs,
[&]() { return attrs.determinePos(noPos); },
"while evaluating an attribute set to be merged in the global scope");
if (displ + attrs.attrs()->size() >= envSize) if (displ + attrs.attrs()->size() >= envSize)
throw Error("environment full; cannot add more variables"); throw Error("environment full; cannot add more variables");
@ -815,7 +822,6 @@ void NixRepl::addAttrsToScope(Value & attrs)
notice("Added %1% variables.", attrs.attrs()->size()); notice("Added %1% variables.", attrs.attrs()->size());
} }
void NixRepl::addVarToScope(const Symbol name, Value & v) void NixRepl::addVarToScope(const Symbol name, Value & v)
{ {
if (displ >= envSize) if (displ >= envSize)
@ -828,13 +834,11 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
varNames.emplace(state->symbols[name]); varNames.emplace(state->symbols[name]);
} }
Expr * NixRepl::parseString(std::string s) Expr * NixRepl::parseString(std::string s)
{ {
return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv); return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv);
} }
void NixRepl::evalString(std::string s, Value & v) void NixRepl::evalString(std::string s, Value & v)
{ {
Expr * e = parseString(s); Expr * e = parseString(s);
@ -842,33 +846,27 @@ void NixRepl::evalString(std::string s, Value & v)
state->forceValue(v, v.determinePos(noPos)); state->forceValue(v, v.determinePos(noPos));
} }
void NixRepl::runNix(Path program, const Strings & args, const std::optional<std::string> & input) void NixRepl::runNix(Path program, const Strings & args, const std::optional<std::string> & input)
{ {
if (runNixPtr) if (runNixPtr)
(*runNixPtr)(program, args, input); (*runNixPtr)(program, args, input);
else else
throw Error("Cannot run '%s' because no method of calling the Nix CLI was provided. This is a configuration problem pertaining to how this program was built. See Nix 2.25 release notes", program); throw Error(
"Cannot run '%s' because no method of calling the Nix CLI was provided. This is a configuration problem pertaining to how this program was built. See Nix 2.25 release notes",
program);
} }
std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create( std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
const LookupPath & lookupPath, nix::ref<Store> store, ref<EvalState> state, const LookupPath & lookupPath,
std::function<AnnotatedValues()> getValues, RunNix * runNix) nix::ref<Store> store,
ref<EvalState> state,
std::function<AnnotatedValues()> getValues,
RunNix * runNix)
{ {
return std::make_unique<NixRepl>( return std::make_unique<NixRepl>(lookupPath, std::move(store), state, getValues, runNix);
lookupPath,
std::move(store),
state,
getValues,
runNix
);
} }
ReplExitStatus AbstractNixRepl::runSimple(ref<EvalState> evalState, const ValMap & extraEnv)
ReplExitStatus AbstractNixRepl::runSimple(
ref<EvalState> evalState,
const ValMap & extraEnv)
{ {
auto getValues = [&]() -> NixRepl::AnnotatedValues { auto getValues = [&]() -> NixRepl::AnnotatedValues {
NixRepl::AnnotatedValues values; NixRepl::AnnotatedValues values;
@ -880,8 +878,7 @@ ReplExitStatus AbstractNixRepl::runSimple(
openStore(), openStore(),
evalState, evalState,
getValues, getValues,
/*runNix=*/nullptr /*runNix=*/nullptr);
);
repl->initEnv(); repl->initEnv();

View file

@ -35,9 +35,7 @@ template <typename T, typename F>
static T * unsafe_new_with_self(F && init) static T * unsafe_new_with_self(F && init)
{ {
// Allocate // Allocate
void * p = ::operator new( void * p = ::operator new(sizeof(T), static_cast<std::align_val_t>(alignof(T)));
sizeof(T),
static_cast<std::align_val_t>(alignof(T)));
// Initialize with placement new // Initialize with placement new
return new (p) T(init(static_cast<T *>(p))); return new (p) T(init(static_cast<T *>(p)));
} }
@ -86,7 +84,8 @@ nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, n
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }
nix_err nix_value_call_multi(nix_c_context * context, EvalState * state, nix_value * fn, size_t nargs, nix_value ** args, nix_value * value) nix_err nix_value_call_multi(
nix_c_context * context, EvalState * state, nix_value * fn, size_t nargs, nix_value ** args, nix_value * value)
{ {
if (context) if (context)
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
@ -152,7 +151,8 @@ nix_err nix_eval_state_builder_load(nix_c_context * context, nix_eval_state_buil
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }
nix_err nix_eval_state_builder_set_lookup_path(nix_c_context * context, nix_eval_state_builder * builder, const char ** lookupPath_c) nix_err nix_eval_state_builder_set_lookup_path(
nix_c_context * context, nix_eval_state_builder * builder, const char ** lookupPath_c)
{ {
if (context) if (context)
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
@ -175,11 +175,7 @@ EvalState * nix_eval_state_build(nix_c_context * context, nix_eval_state_builder
return EvalState{ return EvalState{
.fetchSettings = std::move(builder->fetchSettings), .fetchSettings = std::move(builder->fetchSettings),
.settings = std::move(builder->settings), .settings = std::move(builder->settings),
.state = nix::EvalState( .state = nix::EvalState(builder->lookupPath, builder->store, self->fetchSettings, self->settings),
builder->lookupPath,
builder->store,
self->fetchSettings,
self->settings),
}; };
}); });
} }
@ -195,8 +191,7 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c
if (nix_eval_state_builder_load(context, builder) != NIX_OK) if (nix_eval_state_builder_load(context, builder) != NIX_OK)
return nullptr; return nullptr;
if (nix_eval_state_builder_set_lookup_path(context, builder, lookupPath_c) if (nix_eval_state_builder_set_lookup_path(context, builder, lookupPath_c) != NIX_OK)
!= NIX_OK)
return nullptr; return nullptr;
auto * state = nix_eval_state_build(context, builder); auto * state = nix_eval_state_build(context, builder);

View file

@ -155,7 +155,13 @@ public:
} }
nix_string_context ctx{context}; nix_string_context ctx{context};
desc.printValueAsXML( desc.printValueAsXML(
v, (EvalState *) &state, strict, location, &doc, &ctx, &drvsSeen, v,
(EvalState *) &state,
strict,
location,
&doc,
&ctx,
&drvsSeen,
*reinterpret_cast<const uint32_t *>(&pos)); *reinterpret_cast<const uint32_t *>(&pos));
} }

View file

@ -16,9 +16,11 @@
#include "nix/store/tests/libstore.hh" #include "nix/store/tests/libstore.hh"
namespace nix { namespace nix {
class LibExprTest : public LibStoreTest { class LibExprTest : public LibStoreTest
{
public: public:
static void SetUpTestSuite() { static void SetUpTestSuite()
{
LibStoreTest::SetUpTestSuite(); LibStoreTest::SetUpTestSuite();
initGC(); initGC();
} }
@ -30,7 +32,8 @@ namespace nix {
{ {
evalSettings.nixPath = {}; evalSettings.nixPath = {};
} }
Value eval(std::string input, bool forceValue = true) { Value eval(std::string input, bool forceValue = true)
{
Value v; Value v;
Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root)); Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root));
assert(e); assert(e);
@ -40,13 +43,15 @@ namespace nix {
return v; return v;
} }
Value * maybeThunk(std::string input, bool forceValue = true) { Value * maybeThunk(std::string input, bool forceValue = true)
{
Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root)); Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root));
assert(e); assert(e);
return e->maybeThunk(state, state.baseEnv); return e->maybeThunk(state, state.baseEnv);
} }
Symbol createSymbol(const char * value) { Symbol createSymbol(const char * value)
{
return state.symbols.create(value); return state.symbols.create(value);
} }
@ -56,66 +61,78 @@ namespace nix {
EvalState state; EvalState state;
}; };
MATCHER(IsListType, "") { MATCHER(IsListType, "")
{
return arg != nList; return arg != nList;
} }
MATCHER(IsList, "") { MATCHER(IsList, "")
{
return arg.type() == nList; return arg.type() == nList;
} }
MATCHER(IsString, "") { MATCHER(IsString, "")
{
return arg.type() == nString; return arg.type() == nString;
} }
MATCHER(IsNull, "") { MATCHER(IsNull, "")
{
return arg.type() == nNull; return arg.type() == nNull;
} }
MATCHER(IsThunk, "") { MATCHER(IsThunk, "")
{
return arg.type() == nThunk; return arg.type() == nThunk;
} }
MATCHER(IsAttrs, "") { MATCHER(IsAttrs, "")
{
return arg.type() == nAttrs; return arg.type() == nAttrs;
} }
MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s)) { MATCHER_P(IsStringEq, s, fmt("The string is equal to \"%1%\"", s))
{
if (arg.type() != nString) { if (arg.type() != nString) {
return false; return false;
} }
return std::string_view(arg.c_str()) == s; return std::string_view(arg.c_str()) == s;
} }
MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v)) { MATCHER_P(IsIntEq, v, fmt("The string is equal to \"%1%\"", v))
{
if (arg.type() != nInt) { if (arg.type() != nInt) {
return false; return false;
} }
return arg.integer().value == v; return arg.integer().value == v;
} }
MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v)) { MATCHER_P(IsFloatEq, v, fmt("The float is equal to \"%1%\"", v))
{
if (arg.type() != nFloat) { if (arg.type() != nFloat) {
return false; return false;
} }
return arg.fpoint() == v; return arg.fpoint() == v;
} }
MATCHER(IsTrue, "") { MATCHER(IsTrue, "")
{
if (arg.type() != nBool) { if (arg.type() != nBool) {
return false; return false;
} }
return arg.boolean() == true; return arg.boolean() == true;
} }
MATCHER(IsFalse, "") { MATCHER(IsFalse, "")
{
if (arg.type() != nBool) { if (arg.type() != nBool) {
return false; return false;
} }
return arg.boolean() == false; return arg.boolean() == false;
} }
MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p)) { MATCHER_P(IsPathEq, p, fmt("Is a path equal to \"%1%\"", p))
{
if (arg.type() != nPath) { if (arg.type() != nPath) {
*result_listener << "Expected a path got " << arg.type(); *result_listener << "Expected a path got " << arg.type();
return false; return false;
@ -129,8 +146,8 @@ namespace nix {
return true; return true;
} }
MATCHER_P(IsListOfSize, n, fmt("Is a list of size [%1%]", n))
MATCHER_P(IsListOfSize, n, fmt("Is a list of size [%1%]", n)) { {
if (arg.type() != nList) { if (arg.type() != nList) {
*result_listener << "Expected list got " << arg.type(); *result_listener << "Expected list got " << arg.type();
return false; return false;
@ -141,7 +158,8 @@ namespace nix {
return true; return true;
} }
MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) { MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n))
{
if (arg.type() != nAttrs) { if (arg.type() != nAttrs) {
*result_listener << "Expected set got " << arg.type(); *result_listener << "Expected set got " << arg.type();
return false; return false;
@ -152,5 +170,4 @@ namespace nix {
return true; return true;
} }
} /* namespace nix */ } /* namespace nix */

View file

@ -9,22 +9,26 @@ namespace rc {
using namespace nix; using namespace nix;
template<> template<>
struct Arbitrary<NixStringContextElem::Opaque> { struct Arbitrary<NixStringContextElem::Opaque>
{
static Gen<NixStringContextElem::Opaque> arbitrary(); static Gen<NixStringContextElem::Opaque> arbitrary();
}; };
template<> template<>
struct Arbitrary<NixStringContextElem::Built> { struct Arbitrary<NixStringContextElem::Built>
{
static Gen<NixStringContextElem::Built> arbitrary(); static Gen<NixStringContextElem::Built> arbitrary();
}; };
template<> template<>
struct Arbitrary<NixStringContextElem::DrvDeep> { struct Arbitrary<NixStringContextElem::DrvDeep>
{
static Gen<NixStringContextElem::DrvDeep> arbitrary(); static Gen<NixStringContextElem::DrvDeep> arbitrary();
}; };
template<> template<>
struct Arbitrary<NixStringContextElem> { struct Arbitrary<NixStringContextElem>
{
static Gen<NixStringContextElem> arbitrary(); static Gen<NixStringContextElem> arbitrary();
}; };

View file

@ -8,22 +8,18 @@
namespace nix { namespace nix {
// Testing of trivial expressions // Testing of trivial expressions
class DerivedPathExpressionTest : public LibExprTest {}; class DerivedPathExpressionTest : public LibExprTest
{};
// FIXME: `RC_GTEST_FIXTURE_PROP` isn't calling `SetUpTestSuite` because it is // FIXME: `RC_GTEST_FIXTURE_PROP` isn't calling `SetUpTestSuite` because it is
// no a real fixture. // no a real fixture.
// //
// See https://github.com/emil-e/rapidcheck/blob/master/doc/gtest.md#rc_gtest_fixture_propfixture-name-args // See https://github.com/emil-e/rapidcheck/blob/master/doc/gtest.md#rc_gtest_fixture_propfixture-name-args
TEST_F(DerivedPathExpressionTest, force_init) TEST_F(DerivedPathExpressionTest, force_init) {}
{
}
#ifndef COVERAGE #ifndef COVERAGE
RC_GTEST_FIXTURE_PROP( RC_GTEST_FIXTURE_PROP(DerivedPathExpressionTest, prop_opaque_path_round_trip, (const SingleDerivedPath::Opaque & o))
DerivedPathExpressionTest,
prop_opaque_path_round_trip,
(const SingleDerivedPath::Opaque & o))
{ {
auto * v = state.allocValue(); auto * v = state.allocValue();
state.mkStorePathString(o.path, *v); state.mkStorePathString(o.path, *v);
@ -35,9 +31,7 @@ RC_GTEST_FIXTURE_PROP(
// path only. // path only.
RC_GTEST_FIXTURE_PROP( RC_GTEST_FIXTURE_PROP(
DerivedPathExpressionTest, DerivedPathExpressionTest, prop_derived_path_built_placeholder_round_trip, (const SingleDerivedPath::Built & b))
prop_derived_path_built_placeholder_round_trip,
(const SingleDerivedPath::Built & b))
{ {
/** /**
* We set these in tests rather than the regular globals so we don't have * We set these in tests rather than the regular globals so we don't have

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,8 @@
namespace nix { namespace nix {
TEST(nix_isAllowedURI, http_example_com) { TEST(nix_isAllowedURI, http_example_com)
{
Strings allowed; Strings allowed;
allowed.push_back("http://example.com"); allowed.push_back("http://example.com");
@ -20,7 +21,8 @@ TEST(nix_isAllowedURI, http_example_com) {
ASSERT_FALSE(isAllowedURI("http://example.org/foo", allowed)); ASSERT_FALSE(isAllowedURI("http://example.org/foo", allowed));
} }
TEST(nix_isAllowedURI, http_example_com_foo) { TEST(nix_isAllowedURI, http_example_com_foo)
{
Strings allowed; Strings allowed;
allowed.push_back("http://example.com/foo"); allowed.push_back("http://example.com/foo");
@ -34,7 +36,8 @@ TEST(nix_isAllowedURI, http_example_com_foo) {
// ASSERT_TRUE(isAllowedURI("http://example.com/foo?ok=1", allowed)); // ASSERT_TRUE(isAllowedURI("http://example.com/foo?ok=1", allowed));
} }
TEST(nix_isAllowedURI, http) { TEST(nix_isAllowedURI, http)
{
Strings allowed; Strings allowed;
allowed.push_back("http://"); allowed.push_back("http://");
@ -48,7 +51,8 @@ TEST(nix_isAllowedURI, http) {
ASSERT_FALSE(isAllowedURI("http:foo", allowed)); ASSERT_FALSE(isAllowedURI("http:foo", allowed));
} }
TEST(nix_isAllowedURI, https) { TEST(nix_isAllowedURI, https)
{
Strings allowed; Strings allowed;
allowed.push_back("https://"); allowed.push_back("https://");
@ -58,7 +62,8 @@ TEST(nix_isAllowedURI, https) {
ASSERT_FALSE(isAllowedURI("http://example.com/https:", allowed)); ASSERT_FALSE(isAllowedURI("http://example.com/https:", allowed));
} }
TEST(nix_isAllowedURI, absolute_path) { TEST(nix_isAllowedURI, absolute_path)
{
Strings allowed; Strings allowed;
allowed.push_back("/var/evil"); // bad idea allowed.push_back("/var/evil"); // bad idea
@ -76,7 +81,8 @@ TEST(nix_isAllowedURI, absolute_path) {
ASSERT_FALSE(isAllowedURI("http://example.com//var/evil/foo", allowed)); ASSERT_FALSE(isAllowedURI("http://example.com//var/evil/foo", allowed));
} }
TEST(nix_isAllowedURI, file_url) { TEST(nix_isAllowedURI, file_url)
{
Strings allowed; Strings allowed;
allowed.push_back("file:///var/evil"); // bad idea allowed.push_back("file:///var/evil"); // bad idea
@ -103,7 +109,8 @@ TEST(nix_isAllowedURI, file_url) {
ASSERT_FALSE(isAllowedURI("file://", allowed)); ASSERT_FALSE(isAllowedURI("file://", allowed));
} }
TEST(nix_isAllowedURI, github_all) { TEST(nix_isAllowedURI, github_all)
{
Strings allowed; Strings allowed;
allowed.push_back("github:"); allowed.push_back("github:");
ASSERT_TRUE(isAllowedURI("github:", allowed)); ASSERT_TRUE(isAllowedURI("github:", allowed));
@ -117,7 +124,8 @@ TEST(nix_isAllowedURI, github_all) {
ASSERT_FALSE(isAllowedURI("github", allowed)); ASSERT_FALSE(isAllowedURI("github", allowed));
} }
TEST(nix_isAllowedURI, github_org) { TEST(nix_isAllowedURI, github_org)
{
Strings allowed; Strings allowed;
allowed.push_back("github:foo"); allowed.push_back("github:foo");
ASSERT_FALSE(isAllowedURI("github:", allowed)); ASSERT_FALSE(isAllowedURI("github:", allowed));
@ -130,7 +138,8 @@ TEST(nix_isAllowedURI, github_org) {
ASSERT_FALSE(isAllowedURI("file:///github:foo/bar/archive/master.tar.gz", allowed)); ASSERT_FALSE(isAllowedURI("file:///github:foo/bar/archive/master.tar.gz", allowed));
} }
TEST(nix_isAllowedURI, non_scheme_colon) { TEST(nix_isAllowedURI, non_scheme_colon)
{
Strings allowed; Strings allowed;
allowed.push_back("https://foo/bar:"); allowed.push_back("https://foo/bar:");
ASSERT_TRUE(isAllowedURI("https://foo/bar:", allowed)); ASSERT_TRUE(isAllowedURI("https://foo/bar:", allowed));
@ -138,16 +147,19 @@ TEST(nix_isAllowedURI, non_scheme_colon) {
ASSERT_FALSE(isAllowedURI("https://foo/bar:baz", allowed)); ASSERT_FALSE(isAllowedURI("https://foo/bar:baz", allowed));
} }
class EvalStateTest : public LibExprTest {}; class EvalStateTest : public LibExprTest
{};
TEST_F(EvalStateTest, getBuiltins_ok) { TEST_F(EvalStateTest, getBuiltins_ok)
{
auto evaled = maybeThunk("builtins"); auto evaled = maybeThunk("builtins");
auto & builtins = state.getBuiltins(); auto & builtins = state.getBuiltins();
ASSERT_TRUE(builtins.type() == nAttrs); ASSERT_TRUE(builtins.type() == nAttrs);
ASSERT_EQ(evaled, &builtins); ASSERT_EQ(evaled, &builtins);
} }
TEST_F(EvalStateTest, getBuiltin_ok) { TEST_F(EvalStateTest, getBuiltin_ok)
{
auto & builtin = state.getBuiltin("toString"); auto & builtin = state.getBuiltin("toString");
ASSERT_TRUE(builtin.type() == nFunction); ASSERT_TRUE(builtin.type() == nFunction);
// FIXME // FIXME
@ -157,7 +169,8 @@ TEST_F(EvalStateTest, getBuiltin_ok) {
ASSERT_EQ(state.forceBool(builtin2, noPos, "in unit test"), true); ASSERT_EQ(state.forceBool(builtin2, noPos, "in unit test"), true);
} }
TEST_F(EvalStateTest, getBuiltin_fail) { TEST_F(EvalStateTest, getBuiltin_fail)
{
ASSERT_THROW(state.getBuiltin("nonexistent"), EvalError); ASSERT_THROW(state.getBuiltin("nonexistent"), EvalError);
} }

View file

@ -4,9 +4,11 @@
namespace nix { namespace nix {
// Testing the conversion to JSON // Testing the conversion to JSON
class JSONValueTest : public LibExprTest { class JSONValueTest : public LibExprTest
{
protected: protected:
std::string getJSONValue(Value& value) { std::string getJSONValue(Value & value)
{
std::stringstream ss; std::stringstream ss;
NixStringContext ps; NixStringContext ps;
printValueAsJSON(state, true, value, noPos, ss, ps); printValueAsJSON(state, true, value, noPos, ss, ps);
@ -14,43 +16,50 @@ namespace nix {
} }
}; };
TEST_F(JSONValueTest, null) { TEST_F(JSONValueTest, null)
{
Value v; Value v;
v.mkNull(); v.mkNull();
ASSERT_EQ(getJSONValue(v), "null"); ASSERT_EQ(getJSONValue(v), "null");
} }
TEST_F(JSONValueTest, BoolFalse) { TEST_F(JSONValueTest, BoolFalse)
{
Value v; Value v;
v.mkBool(false); v.mkBool(false);
ASSERT_EQ(getJSONValue(v), "false"); ASSERT_EQ(getJSONValue(v), "false");
} }
TEST_F(JSONValueTest, BoolTrue) { TEST_F(JSONValueTest, BoolTrue)
{
Value v; Value v;
v.mkBool(true); v.mkBool(true);
ASSERT_EQ(getJSONValue(v), "true"); ASSERT_EQ(getJSONValue(v), "true");
} }
TEST_F(JSONValueTest, IntPositive) { TEST_F(JSONValueTest, IntPositive)
{
Value v; Value v;
v.mkInt(100); v.mkInt(100);
ASSERT_EQ(getJSONValue(v), "100"); ASSERT_EQ(getJSONValue(v), "100");
} }
TEST_F(JSONValueTest, IntNegative) { TEST_F(JSONValueTest, IntNegative)
{
Value v; Value v;
v.mkInt(-100); v.mkInt(-100);
ASSERT_EQ(getJSONValue(v), "-100"); ASSERT_EQ(getJSONValue(v), "-100");
} }
TEST_F(JSONValueTest, String) { TEST_F(JSONValueTest, String)
{
Value v; Value v;
v.mkString("test"); v.mkString("test");
ASSERT_EQ(getJSONValue(v), "\"test\""); ASSERT_EQ(getJSONValue(v), "\"test\"");
} }
TEST_F(JSONValueTest, StringQuotes) { TEST_F(JSONValueTest, StringQuotes)
{
Value v; Value v;
v.mkString("test\""); v.mkString("test\"");
@ -60,7 +69,8 @@ namespace nix {
// The dummy store doesn't support writing files. Fails with this exception message: // The dummy store doesn't support writing files. Fails with this exception message:
// C++ exception with description "error: operation 'addToStoreFromDump' is // C++ exception with description "error: operation 'addToStoreFromDump' is
// not supported by store 'dummy'" thrown in the test body. // not supported by store 'dummy'" thrown in the test body.
TEST_F(JSONValueTest, DISABLED_Path) { TEST_F(JSONValueTest, DISABLED_Path)
{
Value v; Value v;
v.mkPath(state.rootPath(CanonPath("/test"))); v.mkPath(state.rootPath(CanonPath("/test")));
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\""); ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");

View file

@ -5,7 +5,8 @@
using namespace nix; using namespace nix;
int main (int argc, char **argv) { int main(int argc, char ** argv)
{
if (argc > 1 && std::string_view(argv[1]) == "__build-remote") { if (argc > 1 && std::string_view(argv[1]) == "__build-remote") {
printError("test-build-remote: not supported in libexpr unit tests"); printError("test-build-remote: not supported in libexpr unit tests");
return 1; return 1;
@ -16,14 +17,15 @@ int main (int argc, char **argv) {
#ifdef __linux__ // should match the conditional around sandboxBuildDir declaration. #ifdef __linux__ // should match the conditional around sandboxBuildDir declaration.
// When building and testing nix within the host's Nix sandbox, our store dir will be located in the host's sandboxBuildDir, e.g.: // When building and testing nix within the host's Nix sandbox, our store dir will be located in the host's
// Host // sandboxBuildDir, e.g.: Host
// storeDir = /nix/store // storeDir = /nix/store
// sandboxBuildDir = /build // sandboxBuildDir = /build
// This process // This process
// storeDir = /build/foo/bar/store // storeDir = /build/foo/bar/store
// sandboxBuildDir = /build // sandboxBuildDir = /build
// However, we have a rule that the store dir must not be inside the storeDir, so we need to pick a different sandboxBuildDir. // However, we have a rule that the store dir must not be inside the storeDir, so we need to pick a different
// sandboxBuildDir.
settings.sandboxBuildDir = "/test-build-dir-instead-of-usual-build-dir"; settings.sandboxBuildDir = "/test-build-dir-instead-of-usual-build-dir";
#endif #endif

View file

@ -14,46 +14,54 @@ namespace nix {
public: public:
CaptureLogger() {} CaptureLogger() {}
std::string get() const { std::string get() const
{
return oss.str(); return oss.str();
} }
void log(Verbosity lvl, std::string_view s) override { void log(Verbosity lvl, std::string_view s) override
{
oss << s << std::endl; oss << s << std::endl;
} }
void logEI(const ErrorInfo & ei) override { void logEI(const ErrorInfo & ei) override
{
showErrorInfo(oss, ei, loggerSettings.showTrace.get()); showErrorInfo(oss, ei, loggerSettings.showTrace.get());
} }
}; };
class CaptureLogging { class CaptureLogging
{
std::unique_ptr<Logger> oldLogger; std::unique_ptr<Logger> oldLogger;
public: public:
CaptureLogging() { CaptureLogging()
{
oldLogger = std::move(logger); oldLogger = std::move(logger);
logger = std::make_unique<CaptureLogger>(); logger = std::make_unique<CaptureLogger>();
} }
~CaptureLogging() { ~CaptureLogging()
{
logger = std::move(oldLogger); logger = std::move(oldLogger);
} }
}; };
// Testing eval of PrimOp's // Testing eval of PrimOp's
class PrimOpTest : public LibExprTest {}; class PrimOpTest : public LibExprTest
{};
TEST_F(PrimOpTest, throw)
TEST_F(PrimOpTest, throw) { {
ASSERT_THROW(eval("throw \"foo\""), ThrownError); ASSERT_THROW(eval("throw \"foo\""), ThrownError);
} }
TEST_F(PrimOpTest, abort) { TEST_F(PrimOpTest, abort)
{
ASSERT_THROW(eval("abort \"abort\""), Abort); ASSERT_THROW(eval("abort \"abort\""), Abort);
} }
TEST_F(PrimOpTest, ceil) { TEST_F(PrimOpTest, ceil)
{
auto v = eval("builtins.ceil 1.9"); auto v = eval("builtins.ceil 1.9");
ASSERT_THAT(v, IsIntEq(2)); ASSERT_THAT(v, IsIntEq(2));
auto intMin = eval("builtins.ceil (-4611686018427387904 - 4611686018427387904)"); auto intMin = eval("builtins.ceil (-4611686018427387904 - 4611686018427387904)");
@ -68,7 +76,8 @@ namespace nix {
ASSERT_THROW(eval("builtins.ceil (-4611686018427387904 - 4611686018427387903)"), EvalError); ASSERT_THROW(eval("builtins.ceil (-4611686018427387904 - 4611686018427387903)"), EvalError);
} }
TEST_F(PrimOpTest, floor) { TEST_F(PrimOpTest, floor)
{
auto v = eval("builtins.floor 1.9"); auto v = eval("builtins.floor 1.9");
ASSERT_THAT(v, IsIntEq(1)); ASSERT_THAT(v, IsIntEq(1));
auto intMin = eval("builtins.ceil (-4611686018427387904 - 4611686018427387904)"); auto intMin = eval("builtins.ceil (-4611686018427387904 - 4611686018427387904)");
@ -83,7 +92,8 @@ namespace nix {
ASSERT_THROW(eval("builtins.ceil (-4611686018427387904 - 4611686018427387903)"), EvalError); ASSERT_THROW(eval("builtins.ceil (-4611686018427387904 - 4611686018427387903)"), EvalError);
} }
TEST_F(PrimOpTest, tryEvalFailure) { TEST_F(PrimOpTest, tryEvalFailure)
{
auto v = eval("builtins.tryEval (throw \"\")"); auto v = eval("builtins.tryEval (throw \"\")");
ASSERT_THAT(v, IsAttrsOfSize(2)); ASSERT_THAT(v, IsAttrsOfSize(2));
auto s = createSymbol("success"); auto s = createSymbol("success");
@ -92,7 +102,8 @@ namespace nix {
ASSERT_THAT(*p->value, IsFalse()); ASSERT_THAT(*p->value, IsFalse());
} }
TEST_F(PrimOpTest, tryEvalSuccess) { TEST_F(PrimOpTest, tryEvalSuccess)
{
auto v = eval("builtins.tryEval 123"); auto v = eval("builtins.tryEval 123");
ASSERT_THAT(v, IsAttrs()); ASSERT_THAT(v, IsAttrs());
auto s = createSymbol("success"); auto s = createSymbol("success");
@ -105,26 +116,31 @@ namespace nix {
ASSERT_THAT(*p->value, IsIntEq(123)); ASSERT_THAT(*p->value, IsIntEq(123));
} }
TEST_F(PrimOpTest, getEnv) { TEST_F(PrimOpTest, getEnv)
{
setEnv("_NIX_UNIT_TEST_ENV_VALUE", "test value"); setEnv("_NIX_UNIT_TEST_ENV_VALUE", "test value");
auto v = eval("builtins.getEnv \"_NIX_UNIT_TEST_ENV_VALUE\""); auto v = eval("builtins.getEnv \"_NIX_UNIT_TEST_ENV_VALUE\"");
ASSERT_THAT(v, IsStringEq("test value")); ASSERT_THAT(v, IsStringEq("test value"));
} }
TEST_F(PrimOpTest, seq) { TEST_F(PrimOpTest, seq)
{
ASSERT_THROW(eval("let x = throw \"test\"; in builtins.seq x { }"), ThrownError); ASSERT_THROW(eval("let x = throw \"test\"; in builtins.seq x { }"), ThrownError);
} }
TEST_F(PrimOpTest, seqNotDeep) { TEST_F(PrimOpTest, seqNotDeep)
{
auto v = eval("let x = { z = throw \"test\"; }; in builtins.seq x { }"); auto v = eval("let x = { z = throw \"test\"; }; in builtins.seq x { }");
ASSERT_THAT(v, IsAttrs()); ASSERT_THAT(v, IsAttrs());
} }
TEST_F(PrimOpTest, deepSeq) { TEST_F(PrimOpTest, deepSeq)
{
ASSERT_THROW(eval("let x = { z = throw \"test\"; }; in builtins.deepSeq x { }"), ThrownError); ASSERT_THROW(eval("let x = { z = throw \"test\"; }; in builtins.deepSeq x { }"), ThrownError);
} }
TEST_F(PrimOpTest, trace) { TEST_F(PrimOpTest, trace)
{
CaptureLogging l; CaptureLogging l;
auto v = eval("builtins.trace \"test string 123\" 123"); auto v = eval("builtins.trace \"test string 123\" 123");
ASSERT_THAT(v, IsIntEq(123)); ASSERT_THAT(v, IsIntEq(123));
@ -132,40 +148,47 @@ namespace nix {
ASSERT_NE(text.find("test string 123"), std::string::npos); ASSERT_NE(text.find("test string 123"), std::string::npos);
} }
TEST_F(PrimOpTest, placeholder) { TEST_F(PrimOpTest, placeholder)
{
auto v = eval("builtins.placeholder \"out\""); auto v = eval("builtins.placeholder \"out\"");
ASSERT_THAT(v, IsStringEq("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9")); ASSERT_THAT(v, IsStringEq("/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"));
} }
TEST_F(PrimOpTest, baseNameOf) { TEST_F(PrimOpTest, baseNameOf)
{
auto v = eval("builtins.baseNameOf /some/path"); auto v = eval("builtins.baseNameOf /some/path");
ASSERT_THAT(v, IsStringEq("path")); ASSERT_THAT(v, IsStringEq("path"));
} }
TEST_F(PrimOpTest, dirOf) { TEST_F(PrimOpTest, dirOf)
{
auto v = eval("builtins.dirOf /some/path"); auto v = eval("builtins.dirOf /some/path");
ASSERT_THAT(v, IsPathEq("/some")); ASSERT_THAT(v, IsPathEq("/some"));
} }
TEST_F(PrimOpTest, attrValues) { TEST_F(PrimOpTest, attrValues)
{
auto v = eval("builtins.attrValues { x = \"foo\"; a = 1; }"); auto v = eval("builtins.attrValues { x = \"foo\"; a = 1; }");
ASSERT_THAT(v, IsListOfSize(2)); ASSERT_THAT(v, IsListOfSize(2));
ASSERT_THAT(*v.listElems()[0], IsIntEq(1)); ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
ASSERT_THAT(*v.listElems()[1], IsStringEq("foo")); ASSERT_THAT(*v.listElems()[1], IsStringEq("foo"));
} }
TEST_F(PrimOpTest, getAttr) { TEST_F(PrimOpTest, getAttr)
{
auto v = eval("builtins.getAttr \"x\" { x = \"foo\"; }"); auto v = eval("builtins.getAttr \"x\" { x = \"foo\"; }");
ASSERT_THAT(v, IsStringEq("foo")); ASSERT_THAT(v, IsStringEq("foo"));
} }
TEST_F(PrimOpTest, getAttrNotFound) { TEST_F(PrimOpTest, getAttrNotFound)
{
// FIXME: TypeError is really bad here, also the error wording is worse // FIXME: TypeError is really bad here, also the error wording is worse
// than on Nix <=2.3 // than on Nix <=2.3
ASSERT_THROW(eval("builtins.getAttr \"y\" { }"), TypeError); ASSERT_THROW(eval("builtins.getAttr \"y\" { }"), TypeError);
} }
TEST_F(PrimOpTest, unsafeGetAttrPos) { TEST_F(PrimOpTest, unsafeGetAttrPos)
{
state.corepkgsFS->addFile(CanonPath("foo.nix"), "\n\r\n\r{ y = \"x\"; }"); state.corepkgsFS->addFile(CanonPath("foo.nix"), "\n\r\n\r{ y = \"x\"; }");
auto expr = "builtins.unsafeGetAttrPos \"y\" (import <nix/foo.nix>)"; auto expr = "builtins.unsafeGetAttrPos \"y\" (import <nix/foo.nix>)";
@ -189,49 +212,58 @@ namespace nix {
ASSERT_THAT(*column->value, IsIntEq(3)); ASSERT_THAT(*column->value, IsIntEq(3));
} }
TEST_F(PrimOpTest, hasAttr) { TEST_F(PrimOpTest, hasAttr)
{
auto v = eval("builtins.hasAttr \"x\" { x = 1; }"); auto v = eval("builtins.hasAttr \"x\" { x = 1; }");
ASSERT_THAT(v, IsTrue()); ASSERT_THAT(v, IsTrue());
} }
TEST_F(PrimOpTest, hasAttrNotFound) { TEST_F(PrimOpTest, hasAttrNotFound)
{
auto v = eval("builtins.hasAttr \"x\" { }"); auto v = eval("builtins.hasAttr \"x\" { }");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(PrimOpTest, isAttrs) { TEST_F(PrimOpTest, isAttrs)
{
auto v = eval("builtins.isAttrs {}"); auto v = eval("builtins.isAttrs {}");
ASSERT_THAT(v, IsTrue()); ASSERT_THAT(v, IsTrue());
} }
TEST_F(PrimOpTest, isAttrsFalse) { TEST_F(PrimOpTest, isAttrsFalse)
{
auto v = eval("builtins.isAttrs null"); auto v = eval("builtins.isAttrs null");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(PrimOpTest, removeAttrs) { TEST_F(PrimOpTest, removeAttrs)
{
auto v = eval("builtins.removeAttrs { x = 1; } [\"x\"]"); auto v = eval("builtins.removeAttrs { x = 1; } [\"x\"]");
ASSERT_THAT(v, IsAttrsOfSize(0)); ASSERT_THAT(v, IsAttrsOfSize(0));
} }
TEST_F(PrimOpTest, removeAttrsRetains) { TEST_F(PrimOpTest, removeAttrsRetains)
{
auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]"); auto v = eval("builtins.removeAttrs { x = 1; y = 2; } [\"x\"]");
ASSERT_THAT(v, IsAttrsOfSize(1)); ASSERT_THAT(v, IsAttrsOfSize(1));
ASSERT_NE(v.attrs()->find(createSymbol("y")), nullptr); ASSERT_NE(v.attrs()->find(createSymbol("y")), nullptr);
} }
TEST_F(PrimOpTest, listToAttrsEmptyList) { TEST_F(PrimOpTest, listToAttrsEmptyList)
{
auto v = eval("builtins.listToAttrs []"); auto v = eval("builtins.listToAttrs []");
ASSERT_THAT(v, IsAttrsOfSize(0)); ASSERT_THAT(v, IsAttrsOfSize(0));
ASSERT_EQ(v.type(), nAttrs); ASSERT_EQ(v.type(), nAttrs);
ASSERT_EQ(v.attrs()->size(), 0u); ASSERT_EQ(v.attrs()->size(), 0u);
} }
TEST_F(PrimOpTest, listToAttrsNotFieldName) { TEST_F(PrimOpTest, listToAttrsNotFieldName)
{
ASSERT_THROW(eval("builtins.listToAttrs [{}]"), Error); ASSERT_THROW(eval("builtins.listToAttrs [{}]"), Error);
} }
TEST_F(PrimOpTest, listToAttrs) { TEST_F(PrimOpTest, listToAttrs)
{
auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]"); auto v = eval("builtins.listToAttrs [ { name = \"key\"; value = 123; } ]");
ASSERT_THAT(v, IsAttrsOfSize(1)); ASSERT_THAT(v, IsAttrsOfSize(1));
auto key = v.attrs()->find(createSymbol("key")); auto key = v.attrs()->find(createSymbol("key"));
@ -239,7 +271,8 @@ namespace nix {
ASSERT_THAT(*key->value, IsIntEq(123)); ASSERT_THAT(*key->value, IsIntEq(123));
} }
TEST_F(PrimOpTest, intersectAttrs) { TEST_F(PrimOpTest, intersectAttrs)
{
auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }"); auto v = eval("builtins.intersectAttrs { a = 1; b = 2; } { b = 3; c = 4; }");
ASSERT_THAT(v, IsAttrsOfSize(1)); ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs()->find(createSymbol("b")); auto b = v.attrs()->find(createSymbol("b"));
@ -247,14 +280,16 @@ namespace nix {
ASSERT_THAT(*b->value, IsIntEq(3)); ASSERT_THAT(*b->value, IsIntEq(3));
} }
TEST_F(PrimOpTest, catAttrs) { TEST_F(PrimOpTest, catAttrs)
{
auto v = eval("builtins.catAttrs \"a\" [{a = 1;} {b = 0;} {a = 2;}]"); auto v = eval("builtins.catAttrs \"a\" [{a = 1;} {b = 0;} {a = 2;}]");
ASSERT_THAT(v, IsListOfSize(2)); ASSERT_THAT(v, IsListOfSize(2));
ASSERT_THAT(*v.listElems()[0], IsIntEq(1)); ASSERT_THAT(*v.listElems()[0], IsIntEq(1));
ASSERT_THAT(*v.listElems()[1], IsIntEq(2)); ASSERT_THAT(*v.listElems()[1], IsIntEq(2));
} }
TEST_F(PrimOpTest, functionArgs) { TEST_F(PrimOpTest, functionArgs)
{
auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)"); auto v = eval("builtins.functionArgs ({ x, y ? 123}: 1)");
ASSERT_THAT(v, IsAttrsOfSize(2)); ASSERT_THAT(v, IsAttrsOfSize(2));
@ -267,7 +302,8 @@ namespace nix {
ASSERT_THAT(*y->value, IsTrue()); ASSERT_THAT(*y->value, IsTrue());
} }
TEST_F(PrimOpTest, mapAttrs) { TEST_F(PrimOpTest, mapAttrs)
{
auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }"); auto v = eval("builtins.mapAttrs (name: value: value * 10) { a = 1; b = 2; }");
ASSERT_THAT(v, IsAttrsOfSize(2)); ASSERT_THAT(v, IsAttrsOfSize(2));
@ -284,50 +320,60 @@ namespace nix {
ASSERT_THAT(*b->value, IsIntEq(20)); ASSERT_THAT(*b->value, IsIntEq(20));
} }
TEST_F(PrimOpTest, isList) { TEST_F(PrimOpTest, isList)
{
auto v = eval("builtins.isList []"); auto v = eval("builtins.isList []");
ASSERT_THAT(v, IsTrue()); ASSERT_THAT(v, IsTrue());
} }
TEST_F(PrimOpTest, isListFalse) { TEST_F(PrimOpTest, isListFalse)
{
auto v = eval("builtins.isList null"); auto v = eval("builtins.isList null");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(PrimOpTest, elemtAt) { TEST_F(PrimOpTest, elemtAt)
{
auto v = eval("builtins.elemAt [0 1 2 3] 3"); auto v = eval("builtins.elemAt [0 1 2 3] 3");
ASSERT_THAT(v, IsIntEq(3)); ASSERT_THAT(v, IsIntEq(3));
} }
TEST_F(PrimOpTest, elemtAtOutOfBounds) { TEST_F(PrimOpTest, elemtAtOutOfBounds)
{
ASSERT_THROW(eval("builtins.elemAt [0 1 2 3] 5"), Error); ASSERT_THROW(eval("builtins.elemAt [0 1 2 3] 5"), Error);
} }
TEST_F(PrimOpTest, head) { TEST_F(PrimOpTest, head)
{
auto v = eval("builtins.head [ 3 2 1 0 ]"); auto v = eval("builtins.head [ 3 2 1 0 ]");
ASSERT_THAT(v, IsIntEq(3)); ASSERT_THAT(v, IsIntEq(3));
} }
TEST_F(PrimOpTest, headEmpty) { TEST_F(PrimOpTest, headEmpty)
{
ASSERT_THROW(eval("builtins.head [ ]"), Error); ASSERT_THROW(eval("builtins.head [ ]"), Error);
} }
TEST_F(PrimOpTest, headWrongType) { TEST_F(PrimOpTest, headWrongType)
{
ASSERT_THROW(eval("builtins.head { }"), Error); ASSERT_THROW(eval("builtins.head { }"), Error);
} }
TEST_F(PrimOpTest, tail) { TEST_F(PrimOpTest, tail)
{
auto v = eval("builtins.tail [ 3 2 1 0 ]"); auto v = eval("builtins.tail [ 3 2 1 0 ]");
ASSERT_THAT(v, IsListOfSize(3)); ASSERT_THAT(v, IsListOfSize(3));
for (const auto [n, elem] : enumerate(v.listItems())) for (const auto [n, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(2 - static_cast<int>(n))); ASSERT_THAT(*elem, IsIntEq(2 - static_cast<int>(n)));
} }
TEST_F(PrimOpTest, tailEmpty) { TEST_F(PrimOpTest, tailEmpty)
{
ASSERT_THROW(eval("builtins.tail []"), Error); ASSERT_THROW(eval("builtins.tail []"), Error);
} }
TEST_F(PrimOpTest, map) { TEST_F(PrimOpTest, map)
{
auto v = eval("map (x: \"foo\" + x) [ \"bar\" \"bla\" \"abc\" ]"); auto v = eval("map (x: \"foo\" + x) [ \"bar\" \"bla\" \"abc\" ]");
ASSERT_THAT(v, IsListOfSize(3)); ASSERT_THAT(v, IsListOfSize(3));
auto elem = v.listElems()[0]; auto elem = v.listElems()[0];
@ -346,61 +392,72 @@ namespace nix {
ASSERT_THAT(*elem, IsStringEq("fooabc")); ASSERT_THAT(*elem, IsStringEq("fooabc"));
} }
TEST_F(PrimOpTest, filter) { TEST_F(PrimOpTest, filter)
{
auto v = eval("builtins.filter (x: x == 2) [ 3 2 3 2 3 2 ]"); auto v = eval("builtins.filter (x: x == 2) [ 3 2 3 2 3 2 ]");
ASSERT_THAT(v, IsListOfSize(3)); ASSERT_THAT(v, IsListOfSize(3));
for (const auto elem : v.listItems()) for (const auto elem : v.listItems())
ASSERT_THAT(*elem, IsIntEq(2)); ASSERT_THAT(*elem, IsIntEq(2));
} }
TEST_F(PrimOpTest, elemTrue) { TEST_F(PrimOpTest, elemTrue)
{
auto v = eval("builtins.elem 3 [ 1 2 3 4 5 ]"); auto v = eval("builtins.elem 3 [ 1 2 3 4 5 ]");
ASSERT_THAT(v, IsTrue()); ASSERT_THAT(v, IsTrue());
} }
TEST_F(PrimOpTest, elemFalse) { TEST_F(PrimOpTest, elemFalse)
{
auto v = eval("builtins.elem 6 [ 1 2 3 4 5 ]"); auto v = eval("builtins.elem 6 [ 1 2 3 4 5 ]");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(PrimOpTest, concatLists) { TEST_F(PrimOpTest, concatLists)
{
auto v = eval("builtins.concatLists [[1 2] [3 4]]"); auto v = eval("builtins.concatLists [[1 2] [3 4]]");
ASSERT_THAT(v, IsListOfSize(4)); ASSERT_THAT(v, IsListOfSize(4));
for (const auto [i, elem] : enumerate(v.listItems())) for (const auto [i, elem] : enumerate(v.listItems()))
ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i) + 1)); ASSERT_THAT(*elem, IsIntEq(static_cast<int>(i) + 1));
} }
TEST_F(PrimOpTest, length) { TEST_F(PrimOpTest, length)
{
auto v = eval("builtins.length [ 1 2 3 ]"); auto v = eval("builtins.length [ 1 2 3 ]");
ASSERT_THAT(v, IsIntEq(3)); ASSERT_THAT(v, IsIntEq(3));
} }
TEST_F(PrimOpTest, foldStrict) { TEST_F(PrimOpTest, foldStrict)
{
auto v = eval("builtins.foldl' (a: b: a + b) 0 [1 2 3]"); auto v = eval("builtins.foldl' (a: b: a + b) 0 [1 2 3]");
ASSERT_THAT(v, IsIntEq(6)); ASSERT_THAT(v, IsIntEq(6));
} }
TEST_F(PrimOpTest, anyTrue) { TEST_F(PrimOpTest, anyTrue)
{
auto v = eval("builtins.any (x: x == 2) [ 1 2 3 ]"); auto v = eval("builtins.any (x: x == 2) [ 1 2 3 ]");
ASSERT_THAT(v, IsTrue()); ASSERT_THAT(v, IsTrue());
} }
TEST_F(PrimOpTest, anyFalse) { TEST_F(PrimOpTest, anyFalse)
{
auto v = eval("builtins.any (x: x == 5) [ 1 2 3 ]"); auto v = eval("builtins.any (x: x == 5) [ 1 2 3 ]");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(PrimOpTest, allTrue) { TEST_F(PrimOpTest, allTrue)
{
auto v = eval("builtins.all (x: x > 0) [ 1 2 3 ]"); auto v = eval("builtins.all (x: x > 0) [ 1 2 3 ]");
ASSERT_THAT(v, IsTrue()); ASSERT_THAT(v, IsTrue());
} }
TEST_F(PrimOpTest, allFalse) { TEST_F(PrimOpTest, allFalse)
{
auto v = eval("builtins.all (x: x <= 0) [ 1 2 3 ]"); auto v = eval("builtins.all (x: x <= 0) [ 1 2 3 ]");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(PrimOpTest, genList) { TEST_F(PrimOpTest, genList)
{
auto v = eval("builtins.genList (x: x + 1) 3"); auto v = eval("builtins.genList (x: x + 1) 3");
ASSERT_EQ(v.type(), nList); ASSERT_EQ(v.type(), nList);
ASSERT_EQ(v.listSize(), 3u); ASSERT_EQ(v.listSize(), 3u);
@ -411,7 +468,8 @@ namespace nix {
} }
} }
TEST_F(PrimOpTest, sortLessThan) { TEST_F(PrimOpTest, sortLessThan)
{
auto v = eval("builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]"); auto v = eval("builtins.sort builtins.lessThan [ 483 249 526 147 42 77 ]");
ASSERT_EQ(v.type(), nList); ASSERT_EQ(v.type(), nList);
ASSERT_EQ(v.listSize(), 6u); ASSERT_EQ(v.listSize(), 6u);
@ -421,7 +479,8 @@ namespace nix {
ASSERT_THAT(*elem, IsIntEq(numbers[n])); ASSERT_THAT(*elem, IsIntEq(numbers[n]));
} }
TEST_F(PrimOpTest, partition) { TEST_F(PrimOpTest, partition)
{
auto v = eval("builtins.partition (x: x > 10) [1 23 9 3 42]"); auto v = eval("builtins.partition (x: x > 10) [1 23 9 3 42]");
ASSERT_THAT(v, IsAttrsOfSize(2)); ASSERT_THAT(v, IsAttrsOfSize(2));
@ -441,7 +500,8 @@ namespace nix {
ASSERT_THAT(*wrong->value->listElems()[2], IsIntEq(3)); ASSERT_THAT(*wrong->value->listElems()[2], IsIntEq(3));
} }
TEST_F(PrimOpTest, concatMap) { TEST_F(PrimOpTest, concatMap)
{
auto v = eval("builtins.concatMap (x: x ++ [0]) [ [1 2] [3 4] ]"); auto v = eval("builtins.concatMap (x: x ++ [0]) [ [1 2] [3 4] ]");
ASSERT_EQ(v.type(), nList); ASSERT_EQ(v.type(), nList);
ASSERT_EQ(v.listSize(), 6u); ASSERT_EQ(v.listSize(), 6u);
@ -451,17 +511,20 @@ namespace nix {
ASSERT_THAT(*elem, IsIntEq(numbers[n])); ASSERT_THAT(*elem, IsIntEq(numbers[n]));
} }
TEST_F(PrimOpTest, addInt) { TEST_F(PrimOpTest, addInt)
{
auto v = eval("builtins.add 3 5"); auto v = eval("builtins.add 3 5");
ASSERT_THAT(v, IsIntEq(8)); ASSERT_THAT(v, IsIntEq(8));
} }
TEST_F(PrimOpTest, addFloat) { TEST_F(PrimOpTest, addFloat)
{
auto v = eval("builtins.add 3.0 5.0"); auto v = eval("builtins.add 3.0 5.0");
ASSERT_THAT(v, IsFloatEq(8.0)); ASSERT_THAT(v, IsFloatEq(8.0));
} }
TEST_F(PrimOpTest, addFloatToInt) { TEST_F(PrimOpTest, addFloatToInt)
{
auto v = eval("builtins.add 3.0 5"); auto v = eval("builtins.add 3.0 5");
ASSERT_THAT(v, IsFloatEq(8.0)); ASSERT_THAT(v, IsFloatEq(8.0));
@ -469,17 +532,20 @@ namespace nix {
ASSERT_THAT(v, IsFloatEq(8.0)); ASSERT_THAT(v, IsFloatEq(8.0));
} }
TEST_F(PrimOpTest, subInt) { TEST_F(PrimOpTest, subInt)
{
auto v = eval("builtins.sub 5 2"); auto v = eval("builtins.sub 5 2");
ASSERT_THAT(v, IsIntEq(3)); ASSERT_THAT(v, IsIntEq(3));
} }
TEST_F(PrimOpTest, subFloat) { TEST_F(PrimOpTest, subFloat)
{
auto v = eval("builtins.sub 5.0 2.0"); auto v = eval("builtins.sub 5.0 2.0");
ASSERT_THAT(v, IsFloatEq(3.0)); ASSERT_THAT(v, IsFloatEq(3.0));
} }
TEST_F(PrimOpTest, subFloatFromInt) { TEST_F(PrimOpTest, subFloatFromInt)
{
auto v = eval("builtins.sub 5.0 2"); auto v = eval("builtins.sub 5.0 2");
ASSERT_THAT(v, IsFloatEq(3.0)); ASSERT_THAT(v, IsFloatEq(3.0));
@ -487,17 +553,20 @@ namespace nix {
ASSERT_THAT(v, IsFloatEq(2.0)); ASSERT_THAT(v, IsFloatEq(2.0));
} }
TEST_F(PrimOpTest, mulInt) { TEST_F(PrimOpTest, mulInt)
{
auto v = eval("builtins.mul 3 5"); auto v = eval("builtins.mul 3 5");
ASSERT_THAT(v, IsIntEq(15)); ASSERT_THAT(v, IsIntEq(15));
} }
TEST_F(PrimOpTest, mulFloat) { TEST_F(PrimOpTest, mulFloat)
{
auto v = eval("builtins.mul 3.0 5.0"); auto v = eval("builtins.mul 3.0 5.0");
ASSERT_THAT(v, IsFloatEq(15.0)); ASSERT_THAT(v, IsFloatEq(15.0));
} }
TEST_F(PrimOpTest, mulFloatMixed) { TEST_F(PrimOpTest, mulFloatMixed)
{
auto v = eval("builtins.mul 3 5.0"); auto v = eval("builtins.mul 3 5.0");
ASSERT_THAT(v, IsFloatEq(15.0)); ASSERT_THAT(v, IsFloatEq(15.0));
@ -505,58 +574,68 @@ namespace nix {
ASSERT_THAT(v, IsFloatEq(10.0)); ASSERT_THAT(v, IsFloatEq(10.0));
} }
TEST_F(PrimOpTest, divInt) { TEST_F(PrimOpTest, divInt)
{
auto v = eval("builtins.div 5 (-1)"); auto v = eval("builtins.div 5 (-1)");
ASSERT_THAT(v, IsIntEq(-5)); ASSERT_THAT(v, IsIntEq(-5));
} }
TEST_F(PrimOpTest, divIntZero) { TEST_F(PrimOpTest, divIntZero)
{
ASSERT_THROW(eval("builtins.div 5 0"), EvalError); ASSERT_THROW(eval("builtins.div 5 0"), EvalError);
} }
TEST_F(PrimOpTest, divFloat) { TEST_F(PrimOpTest, divFloat)
{
auto v = eval("builtins.div 5.0 (-1)"); auto v = eval("builtins.div 5.0 (-1)");
ASSERT_THAT(v, IsFloatEq(-5.0)); ASSERT_THAT(v, IsFloatEq(-5.0));
} }
TEST_F(PrimOpTest, divFloatZero) { TEST_F(PrimOpTest, divFloatZero)
{
ASSERT_THROW(eval("builtins.div 5.0 0.0"), EvalError); ASSERT_THROW(eval("builtins.div 5.0 0.0"), EvalError);
} }
TEST_F(PrimOpTest, bitOr) { TEST_F(PrimOpTest, bitOr)
{
auto v = eval("builtins.bitOr 1 2"); auto v = eval("builtins.bitOr 1 2");
ASSERT_THAT(v, IsIntEq(3)); ASSERT_THAT(v, IsIntEq(3));
} }
TEST_F(PrimOpTest, bitXor) { TEST_F(PrimOpTest, bitXor)
{
auto v = eval("builtins.bitXor 3 2"); auto v = eval("builtins.bitXor 3 2");
ASSERT_THAT(v, IsIntEq(1)); ASSERT_THAT(v, IsIntEq(1));
} }
TEST_F(PrimOpTest, lessThanFalse) { TEST_F(PrimOpTest, lessThanFalse)
{
auto v = eval("builtins.lessThan 3 1"); auto v = eval("builtins.lessThan 3 1");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(PrimOpTest, lessThanTrue) { TEST_F(PrimOpTest, lessThanTrue)
{
auto v = eval("builtins.lessThan 1 3"); auto v = eval("builtins.lessThan 1 3");
ASSERT_THAT(v, IsTrue()); ASSERT_THAT(v, IsTrue());
} }
TEST_F(PrimOpTest, toStringAttrsThrows) { TEST_F(PrimOpTest, toStringAttrsThrows)
{
ASSERT_THROW(eval("builtins.toString {}"), EvalError); ASSERT_THROW(eval("builtins.toString {}"), EvalError);
} }
TEST_F(PrimOpTest, toStringLambdaThrows) { TEST_F(PrimOpTest, toStringLambdaThrows)
{
ASSERT_THROW(eval("builtins.toString (x: x)"), EvalError); ASSERT_THROW(eval("builtins.toString (x: x)"), EvalError);
} }
class ToStringPrimOpTest : class ToStringPrimOpTest : public PrimOpTest,
public PrimOpTest,
public testing::WithParamInterface<std::tuple<std::string, std::string_view>> public testing::WithParamInterface<std::tuple<std::string, std::string_view>>
{}; {};
TEST_P(ToStringPrimOpTest, toString) { TEST_P(ToStringPrimOpTest, toString)
{
const auto [input, output] = GetParam(); const auto [input, output] = GetParam();
auto v = eval(input); auto v = eval(input);
ASSERT_THAT(v, IsStringEq(output)); ASSERT_THAT(v, IsStringEq(output));
@ -577,82 +656,98 @@ namespace nix {
CASE(R"({ v = "bar"; __toString = self: self.v; })", "bar"), CASE(R"({ v = "bar"; __toString = self: self.v; })", "bar"),
CASE(R"({ v = "bar"; __toString = self: self.v; outPath = "foo"; })", "bar"), CASE(R"({ v = "bar"; __toString = self: self.v; outPath = "foo"; })", "bar"),
CASE(R"({ outPath = "foo"; })", "foo"), CASE(R"({ outPath = "foo"; })", "foo"),
CASE(R"(./test)", "/test") CASE(R"(./test)", "/test")));
)
);
#undef CASE #undef CASE
TEST_F(PrimOpTest, substring){ TEST_F(PrimOpTest, substring)
{
auto v = eval("builtins.substring 0 3 \"nixos\""); auto v = eval("builtins.substring 0 3 \"nixos\"");
ASSERT_THAT(v, IsStringEq("nix")); ASSERT_THAT(v, IsStringEq("nix"));
} }
TEST_F(PrimOpTest, substringSmallerString){ TEST_F(PrimOpTest, substringSmallerString)
{
auto v = eval("builtins.substring 0 3 \"n\""); auto v = eval("builtins.substring 0 3 \"n\"");
ASSERT_THAT(v, IsStringEq("n")); ASSERT_THAT(v, IsStringEq("n"));
} }
TEST_F(PrimOpTest, substringEmptyString){ TEST_F(PrimOpTest, substringEmptyString)
{
auto v = eval("builtins.substring 1 3 \"\""); auto v = eval("builtins.substring 1 3 \"\"");
ASSERT_THAT(v, IsStringEq("")); ASSERT_THAT(v, IsStringEq(""));
} }
TEST_F(PrimOpTest, stringLength) { TEST_F(PrimOpTest, stringLength)
{
auto v = eval("builtins.stringLength \"123\""); auto v = eval("builtins.stringLength \"123\"");
ASSERT_THAT(v, IsIntEq(3)); ASSERT_THAT(v, IsIntEq(3));
} }
TEST_F(PrimOpTest, hashStringMd5) { TEST_F(PrimOpTest, hashStringMd5)
{
auto v = eval("builtins.hashString \"md5\" \"asdf\""); auto v = eval("builtins.hashString \"md5\" \"asdf\"");
ASSERT_THAT(v, IsStringEq("912ec803b2ce49e4a541068d495ab570")); ASSERT_THAT(v, IsStringEq("912ec803b2ce49e4a541068d495ab570"));
} }
TEST_F(PrimOpTest, hashStringSha1) { TEST_F(PrimOpTest, hashStringSha1)
{
auto v = eval("builtins.hashString \"sha1\" \"asdf\""); auto v = eval("builtins.hashString \"sha1\" \"asdf\"");
ASSERT_THAT(v, IsStringEq("3da541559918a808c2402bba5012f6c60b27661c")); ASSERT_THAT(v, IsStringEq("3da541559918a808c2402bba5012f6c60b27661c"));
} }
TEST_F(PrimOpTest, hashStringSha256) { TEST_F(PrimOpTest, hashStringSha256)
{
auto v = eval("builtins.hashString \"sha256\" \"asdf\""); auto v = eval("builtins.hashString \"sha256\" \"asdf\"");
ASSERT_THAT(v, IsStringEq("f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b")); ASSERT_THAT(v, IsStringEq("f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b"));
} }
TEST_F(PrimOpTest, hashStringSha512) { TEST_F(PrimOpTest, hashStringSha512)
{
auto v = eval("builtins.hashString \"sha512\" \"asdf\""); auto v = eval("builtins.hashString \"sha512\" \"asdf\"");
ASSERT_THAT(v, IsStringEq("401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1")); ASSERT_THAT(
v,
IsStringEq(
"401b09eab3c013d4ca54922bb802bec8fd5318192b0a75f201d8b3727429080fb337591abd3e44453b954555b7a0812e1081c39b740293f765eae731f5a65ed1"));
} }
TEST_F(PrimOpTest, hashStringInvalidHashAlgorithm) { TEST_F(PrimOpTest, hashStringInvalidHashAlgorithm)
{
ASSERT_THROW(eval("builtins.hashString \"foobar\" \"asdf\""), Error); ASSERT_THROW(eval("builtins.hashString \"foobar\" \"asdf\""), Error);
} }
TEST_F(PrimOpTest, nixPath) { TEST_F(PrimOpTest, nixPath)
{
auto v = eval("builtins.nixPath"); auto v = eval("builtins.nixPath");
ASSERT_EQ(v.type(), nList); ASSERT_EQ(v.type(), nList);
// We can't test much more as currently the EvalSettings are a global // We can't test much more as currently the EvalSettings are a global
// that we can't easily swap / replace // that we can't easily swap / replace
} }
TEST_F(PrimOpTest, langVersion) { TEST_F(PrimOpTest, langVersion)
{
auto v = eval("builtins.langVersion"); auto v = eval("builtins.langVersion");
ASSERT_EQ(v.type(), nInt); ASSERT_EQ(v.type(), nInt);
} }
TEST_F(PrimOpTest, storeDir) { TEST_F(PrimOpTest, storeDir)
{
auto v = eval("builtins.storeDir"); auto v = eval("builtins.storeDir");
ASSERT_THAT(v, IsStringEq(settings.nixStore)); ASSERT_THAT(v, IsStringEq(settings.nixStore));
} }
TEST_F(PrimOpTest, nixVersion) { TEST_F(PrimOpTest, nixVersion)
{
auto v = eval("builtins.nixVersion"); auto v = eval("builtins.nixVersion");
ASSERT_THAT(v, IsStringEq(nixVersion)); ASSERT_THAT(v, IsStringEq(nixVersion));
} }
TEST_F(PrimOpTest, currentSystem) { TEST_F(PrimOpTest, currentSystem)
{
auto v = eval("builtins.currentSystem"); auto v = eval("builtins.currentSystem");
ASSERT_THAT(v, IsStringEq(evalSettings.getCurrentSystem())); ASSERT_THAT(v, IsStringEq(evalSettings.getCurrentSystem()));
} }
TEST_F(PrimOpTest, derivation) { TEST_F(PrimOpTest, derivation)
{
auto v = eval("derivation"); auto v = eval("derivation");
ASSERT_EQ(v.type(), nFunction); ASSERT_EQ(v.type(), nFunction);
ASSERT_TRUE(v.isLambda()); ASSERT_TRUE(v.isLambda());
@ -660,13 +755,15 @@ namespace nix {
ASSERT_TRUE(v.payload.lambda.fun->hasFormals()); ASSERT_TRUE(v.payload.lambda.fun->hasFormals());
} }
TEST_F(PrimOpTest, currentTime) { TEST_F(PrimOpTest, currentTime)
{
auto v = eval("builtins.currentTime"); auto v = eval("builtins.currentTime");
ASSERT_EQ(v.type(), nInt); ASSERT_EQ(v.type(), nInt);
ASSERT_TRUE(v.integer() > 0); ASSERT_TRUE(v.integer() > 0);
} }
TEST_F(PrimOpTest, splitVersion) { TEST_F(PrimOpTest, splitVersion)
{
auto v = eval("builtins.splitVersion \"1.2.3git\""); auto v = eval("builtins.splitVersion \"1.2.3git\"");
ASSERT_THAT(v, IsListOfSize(4)); ASSERT_THAT(v, IsListOfSize(4));
@ -675,12 +772,12 @@ namespace nix {
ASSERT_THAT(*p, IsStringEq(strings[n])); ASSERT_THAT(*p, IsStringEq(strings[n]));
} }
class CompareVersionsPrimOpTest : class CompareVersionsPrimOpTest : public PrimOpTest,
public PrimOpTest,
public testing::WithParamInterface<std::tuple<std::string, const int>> public testing::WithParamInterface<std::tuple<std::string, const int>>
{}; {};
TEST_P(CompareVersionsPrimOpTest, compareVersions) { TEST_P(CompareVersionsPrimOpTest, compareVersions)
{
auto [expression, expectation] = GetParam(); auto [expression, expectation] = GetParam();
auto v = eval(expression); auto v = eval(expression);
ASSERT_THAT(v, IsIntEq(expectation)); ASSERT_THAT(v, IsIntEq(expectation));
@ -707,18 +804,16 @@ namespace nix {
CASE(2.3pre3, 2.3pre12, -1), CASE(2.3pre3, 2.3pre12, -1),
CASE(2.3a, 2.3c, -1), CASE(2.3a, 2.3c, -1),
CASE(2.3pre1, 2.3c, -1), CASE(2.3pre1, 2.3c, -1),
CASE(2.3pre1, 2.3q, -1) CASE(2.3pre1, 2.3q, -1)));
)
);
#undef CASE #undef CASE
class ParseDrvNamePrimOpTest
class ParseDrvNamePrimOpTest : : public PrimOpTest,
public PrimOpTest,
public testing::WithParamInterface<std::tuple<std::string, std::string_view, std::string_view>> public testing::WithParamInterface<std::tuple<std::string, std::string_view, std::string_view>>
{}; {};
TEST_P(ParseDrvNamePrimOpTest, parseDrvName) { TEST_P(ParseDrvNamePrimOpTest, parseDrvName)
{
auto [input, expectedName, expectedVersion] = GetParam(); auto [input, expectedName, expectedVersion] = GetParam();
const auto expr = fmt("builtins.parseDrvName \"%1%\"", input); const auto expr = fmt("builtins.parseDrvName \"%1%\"", input);
auto v = eval(expr); auto v = eval(expr);
@ -738,25 +833,26 @@ namespace nix {
ParseDrvNamePrimOpTest, ParseDrvNamePrimOpTest,
testing::Values( testing::Values(
std::make_tuple("nix-0.12pre12876", "nix", "0.12pre12876"), std::make_tuple("nix-0.12pre12876", "nix", "0.12pre12876"),
std::make_tuple("a-b-c-1234pre5+git", "a-b-c", "1234pre5+git") std::make_tuple("a-b-c-1234pre5+git", "a-b-c", "1234pre5+git")));
)
);
TEST_F(PrimOpTest, replaceStrings) { TEST_F(PrimOpTest, replaceStrings)
{
// FIXME: add a test that verifies the string context is as expected // FIXME: add a test that verifies the string context is as expected
auto v = eval("builtins.replaceStrings [\"oo\" \"a\"] [\"a\" \"i\"] \"foobar\""); auto v = eval("builtins.replaceStrings [\"oo\" \"a\"] [\"a\" \"i\"] \"foobar\"");
ASSERT_EQ(v.type(), nString); ASSERT_EQ(v.type(), nString);
ASSERT_EQ(v.string_view(), "fabir"); ASSERT_EQ(v.string_view(), "fabir");
} }
TEST_F(PrimOpTest, concatStringsSep) { TEST_F(PrimOpTest, concatStringsSep)
{
// FIXME: add a test that verifies the string context is as expected // FIXME: add a test that verifies the string context is as expected
auto v = eval("builtins.concatStringsSep \"%\" [\"foo\" \"bar\" \"baz\"]"); auto v = eval("builtins.concatStringsSep \"%\" [\"foo\" \"bar\" \"baz\"]");
ASSERT_EQ(v.type(), nString); ASSERT_EQ(v.type(), nString);
ASSERT_EQ(v.string_view(), "foo%bar%baz"); ASSERT_EQ(v.string_view(), "foo%bar%baz");
} }
TEST_F(PrimOpTest, split1) { TEST_F(PrimOpTest, split1)
{
// v = [ "" [ "a" ] "c" ] // v = [ "" [ "a" ] "c" ]
auto v = eval("builtins.split \"(a)b\" \"abc\""); auto v = eval("builtins.split \"(a)b\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(3)); ASSERT_THAT(v, IsListOfSize(3));
@ -769,7 +865,8 @@ namespace nix {
ASSERT_THAT(*v.listElems()[2], IsStringEq("c")); ASSERT_THAT(*v.listElems()[2], IsStringEq("c"));
} }
TEST_F(PrimOpTest, split2) { TEST_F(PrimOpTest, split2)
{
// v is expected to be a list [ "" [ "a" ] "b" [ "c"] "" ] // v is expected to be a list [ "" [ "a" ] "b" [ "c"] "" ]
auto v = eval("builtins.split \"([ac])\" \"abc\""); auto v = eval("builtins.split \"([ac])\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(5)); ASSERT_THAT(v, IsListOfSize(5));
@ -787,7 +884,8 @@ namespace nix {
ASSERT_THAT(*v.listElems()[4], IsStringEq("")); ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
} }
TEST_F(PrimOpTest, split3) { TEST_F(PrimOpTest, split3)
{
auto v = eval("builtins.split \"(a)|(c)\" \"abc\""); auto v = eval("builtins.split \"(a)|(c)\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(5)); ASSERT_THAT(v, IsListOfSize(5));
@ -811,7 +909,8 @@ namespace nix {
ASSERT_THAT(*v.listElems()[4], IsStringEq("")); ASSERT_THAT(*v.listElems()[4], IsStringEq(""));
} }
TEST_F(PrimOpTest, split4) { TEST_F(PrimOpTest, split4)
{
auto v = eval("builtins.split \"([[:upper:]]+)\" \" FOO \""); auto v = eval("builtins.split \"([[:upper:]]+)\" \" FOO \"");
ASSERT_THAT(v, IsListOfSize(3)); ASSERT_THAT(v, IsListOfSize(3));
auto first = v.listElems()[0]; auto first = v.listElems()[0];
@ -826,30 +925,35 @@ namespace nix {
ASSERT_THAT(*third, IsStringEq(" ")); ASSERT_THAT(*third, IsStringEq(" "));
} }
TEST_F(PrimOpTest, match1) { TEST_F(PrimOpTest, match1)
{
auto v = eval("builtins.match \"ab\" \"abc\""); auto v = eval("builtins.match \"ab\" \"abc\"");
ASSERT_THAT(v, IsNull()); ASSERT_THAT(v, IsNull());
} }
TEST_F(PrimOpTest, match2) { TEST_F(PrimOpTest, match2)
{
auto v = eval("builtins.match \"abc\" \"abc\""); auto v = eval("builtins.match \"abc\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(0)); ASSERT_THAT(v, IsListOfSize(0));
} }
TEST_F(PrimOpTest, match3) { TEST_F(PrimOpTest, match3)
{
auto v = eval("builtins.match \"a(b)(c)\" \"abc\""); auto v = eval("builtins.match \"a(b)(c)\" \"abc\"");
ASSERT_THAT(v, IsListOfSize(2)); ASSERT_THAT(v, IsListOfSize(2));
ASSERT_THAT(*v.listElems()[0], IsStringEq("b")); ASSERT_THAT(*v.listElems()[0], IsStringEq("b"));
ASSERT_THAT(*v.listElems()[1], IsStringEq("c")); ASSERT_THAT(*v.listElems()[1], IsStringEq("c"));
} }
TEST_F(PrimOpTest, match4) { TEST_F(PrimOpTest, match4)
{
auto v = eval("builtins.match \"[[:space:]]+([[:upper:]]+)[[:space:]]+\" \" FOO \""); auto v = eval("builtins.match \"[[:space:]]+([[:upper:]]+)[[:space:]]+\" \" FOO \"");
ASSERT_THAT(v, IsListOfSize(1)); ASSERT_THAT(v, IsListOfSize(1));
ASSERT_THAT(*v.listElems()[0], IsStringEq("FOO")); ASSERT_THAT(*v.listElems()[0], IsStringEq("FOO"));
} }
TEST_F(PrimOpTest, match5) { TEST_F(PrimOpTest, match5)
{
// The regex "\\{}" is valid and matches the string "{}". // The regex "\\{}" is valid and matches the string "{}".
// Caused a regression before when trying to switch from std::regex to boost::regex. // Caused a regression before when trying to switch from std::regex to boost::regex.
// See https://github.com/NixOS/nix/pull/7762#issuecomment-1834303659 // See https://github.com/NixOS/nix/pull/7762#issuecomment-1834303659
@ -857,7 +961,8 @@ namespace nix {
ASSERT_THAT(v, IsListOfSize(0)); ASSERT_THAT(v, IsListOfSize(0));
} }
TEST_F(PrimOpTest, attrNames) { TEST_F(PrimOpTest, attrNames)
{
auto v = eval("builtins.attrNames { x = 1; y = 2; z = 3; a = 2; }"); auto v = eval("builtins.attrNames { x = 1; y = 2; z = 3; a = 2; }");
ASSERT_THAT(v, IsListOfSize(4)); ASSERT_THAT(v, IsListOfSize(4));
@ -867,7 +972,8 @@ namespace nix {
ASSERT_THAT(*elem, IsStringEq(expected[n])); ASSERT_THAT(*elem, IsStringEq(expected[n]));
} }
TEST_F(PrimOpTest, genericClosure_not_strict) { TEST_F(PrimOpTest, genericClosure_not_strict)
{
// Operator should not be used when startSet is empty // Operator should not be used when startSet is empty
auto v = eval("builtins.genericClosure { startSet = []; }"); auto v = eval("builtins.genericClosure { startSet = []; }");
ASSERT_THAT(v, IsListOfSize(0)); ASSERT_THAT(v, IsListOfSize(0));

View file

@ -5,7 +5,8 @@
namespace nix { namespace nix {
TEST(LookupPathElem, parse_justPath) { TEST(LookupPathElem, parse_justPath)
{
ASSERT_EQ( ASSERT_EQ(
LookupPath::Elem::parse("foo"), LookupPath::Elem::parse("foo"),
(LookupPath::Elem{ (LookupPath::Elem{
@ -14,7 +15,8 @@ TEST(LookupPathElem, parse_justPath) {
})); }));
} }
TEST(LookupPathElem, parse_emptyPrefix) { TEST(LookupPathElem, parse_emptyPrefix)
{
ASSERT_EQ( ASSERT_EQ(
LookupPath::Elem::parse("=foo"), LookupPath::Elem::parse("=foo"),
(LookupPath::Elem{ (LookupPath::Elem{
@ -23,7 +25,8 @@ TEST(LookupPathElem, parse_emptyPrefix) {
})); }));
} }
TEST(LookupPathElem, parse_oneEq) { TEST(LookupPathElem, parse_oneEq)
{
ASSERT_EQ( ASSERT_EQ(
LookupPath::Elem::parse("foo=bar"), LookupPath::Elem::parse("foo=bar"),
(LookupPath::Elem{ (LookupPath::Elem{
@ -32,7 +35,8 @@ TEST(LookupPathElem, parse_oneEq) {
})); }));
} }
TEST(LookupPathElem, parse_twoEqs) { TEST(LookupPathElem, parse_twoEqs)
{
ASSERT_EQ( ASSERT_EQ(
LookupPath::Elem::parse("foo=bar=baz"), LookupPath::Elem::parse("foo=bar=baz"),
(LookupPath::Elem{ (LookupPath::Elem{
@ -41,48 +45,56 @@ TEST(LookupPathElem, parse_twoEqs) {
})); }));
} }
TEST(LookupPathElem, suffixIfPotentialMatch_justPath)
TEST(LookupPathElem, suffixIfPotentialMatch_justPath) { {
LookupPath::Prefix prefix{.s = ""}; LookupPath::Prefix prefix{.s = ""};
ASSERT_EQ(prefix.suffixIfPotentialMatch("any/thing"), std::optional{"any/thing"}); ASSERT_EQ(prefix.suffixIfPotentialMatch("any/thing"), std::optional{"any/thing"});
} }
TEST(LookupPathElem, suffixIfPotentialMatch_misleadingPrefix1) { TEST(LookupPathElem, suffixIfPotentialMatch_misleadingPrefix1)
{
LookupPath::Prefix prefix{.s = "foo"}; LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX"), std::nullopt); ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX"), std::nullopt);
} }
TEST(LookupPathElem, suffixIfPotentialMatch_misleadingPrefix2) { TEST(LookupPathElem, suffixIfPotentialMatch_misleadingPrefix2)
{
LookupPath::Prefix prefix{.s = "foo"}; LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX/bar"), std::nullopt); ASSERT_EQ(prefix.suffixIfPotentialMatch("fooX/bar"), std::nullopt);
} }
TEST(LookupPathElem, suffixIfPotentialMatch_partialPrefix) { TEST(LookupPathElem, suffixIfPotentialMatch_partialPrefix)
{
LookupPath::Prefix prefix{.s = "fooX"}; LookupPath::Prefix prefix{.s = "fooX"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::nullopt); ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::nullopt);
} }
TEST(LookupPathElem, suffixIfPotentialMatch_exactPrefix) { TEST(LookupPathElem, suffixIfPotentialMatch_exactPrefix)
{
LookupPath::Prefix prefix{.s = "foo"}; LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::optional{""}); ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::optional{""});
} }
TEST(LookupPathElem, suffixIfPotentialMatch_multiKey) { TEST(LookupPathElem, suffixIfPotentialMatch_multiKey)
{
LookupPath::Prefix prefix{.s = "foo/bar"}; LookupPath::Prefix prefix{.s = "foo/bar"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional{"baz"}); ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional{"baz"});
} }
TEST(LookupPathElem, suffixIfPotentialMatch_trailingSlash) { TEST(LookupPathElem, suffixIfPotentialMatch_trailingSlash)
{
LookupPath::Prefix prefix{.s = "foo"}; LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/"), std::optional{""}); ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/"), std::optional{""});
} }
TEST(LookupPathElem, suffixIfPotentialMatch_trailingDoubleSlash) { TEST(LookupPathElem, suffixIfPotentialMatch_trailingDoubleSlash)
{
LookupPath::Prefix prefix{.s = "foo"}; LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo//"), std::optional{"/"}); ASSERT_EQ(prefix.suffixIfPotentialMatch("foo//"), std::optional{"/"});
} }
TEST(LookupPathElem, suffixIfPotentialMatch_trailingPath) { TEST(LookupPathElem, suffixIfPotentialMatch_trailingPath)
{
LookupPath::Prefix prefix{.s = "foo"}; LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional{"bar/baz"}); ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional{"bar/baz"});
} }

View file

@ -2,64 +2,77 @@
namespace nix { namespace nix {
// Testing of trivial expressions // Testing of trivial expressions
class TrivialExpressionTest : public LibExprTest {}; class TrivialExpressionTest : public LibExprTest
{};
TEST_F(TrivialExpressionTest, true) { TEST_F(TrivialExpressionTest, true)
{
auto v = eval("true"); auto v = eval("true");
ASSERT_THAT(v, IsTrue()); ASSERT_THAT(v, IsTrue());
} }
TEST_F(TrivialExpressionTest, false) { TEST_F(TrivialExpressionTest, false)
{
auto v = eval("false"); auto v = eval("false");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(TrivialExpressionTest, null) { TEST_F(TrivialExpressionTest, null)
{
auto v = eval("null"); auto v = eval("null");
ASSERT_THAT(v, IsNull()); ASSERT_THAT(v, IsNull());
} }
TEST_F(TrivialExpressionTest, 1) { TEST_F(TrivialExpressionTest, 1)
{
auto v = eval("1"); auto v = eval("1");
ASSERT_THAT(v, IsIntEq(1)); ASSERT_THAT(v, IsIntEq(1));
} }
TEST_F(TrivialExpressionTest, 1plus1) { TEST_F(TrivialExpressionTest, 1plus1)
{
auto v = eval("1+1"); auto v = eval("1+1");
ASSERT_THAT(v, IsIntEq(2)); ASSERT_THAT(v, IsIntEq(2));
} }
TEST_F(TrivialExpressionTest, minus1) { TEST_F(TrivialExpressionTest, minus1)
{
auto v = eval("-1"); auto v = eval("-1");
ASSERT_THAT(v, IsIntEq(-1)); ASSERT_THAT(v, IsIntEq(-1));
} }
TEST_F(TrivialExpressionTest, 1minus1) { TEST_F(TrivialExpressionTest, 1minus1)
{
auto v = eval("1-1"); auto v = eval("1-1");
ASSERT_THAT(v, IsIntEq(0)); ASSERT_THAT(v, IsIntEq(0));
} }
TEST_F(TrivialExpressionTest, lambdaAdd) { TEST_F(TrivialExpressionTest, lambdaAdd)
{
auto v = eval("let add = a: b: a + b; in add 1 2"); auto v = eval("let add = a: b: a + b; in add 1 2");
ASSERT_THAT(v, IsIntEq(3)); ASSERT_THAT(v, IsIntEq(3));
} }
TEST_F(TrivialExpressionTest, list) { TEST_F(TrivialExpressionTest, list)
{
auto v = eval("[]"); auto v = eval("[]");
ASSERT_THAT(v, IsListOfSize(0)); ASSERT_THAT(v, IsListOfSize(0));
} }
TEST_F(TrivialExpressionTest, attrs) { TEST_F(TrivialExpressionTest, attrs)
{
auto v = eval("{}"); auto v = eval("{}");
ASSERT_THAT(v, IsAttrsOfSize(0)); ASSERT_THAT(v, IsAttrsOfSize(0));
} }
TEST_F(TrivialExpressionTest, float) { TEST_F(TrivialExpressionTest, float)
{
auto v = eval("1.234"); auto v = eval("1.234");
ASSERT_THAT(v, IsFloatEq(1.234)); ASSERT_THAT(v, IsFloatEq(1.234));
} }
TEST_F(TrivialExpressionTest, updateAttrs) { TEST_F(TrivialExpressionTest, updateAttrs)
{
auto v = eval("{ a = 1; } // { b = 2; a = 3; }"); auto v = eval("{ a = 1; } // { b = 2; a = 3; }");
ASSERT_THAT(v, IsAttrsOfSize(2)); ASSERT_THAT(v, IsAttrsOfSize(2));
auto a = v.attrs()->find(createSymbol("a")); auto a = v.attrs()->find(createSymbol("a"));
@ -71,75 +84,87 @@ namespace nix {
ASSERT_THAT(*b->value, IsIntEq(2)); ASSERT_THAT(*b->value, IsIntEq(2));
} }
TEST_F(TrivialExpressionTest, hasAttrOpFalse) { TEST_F(TrivialExpressionTest, hasAttrOpFalse)
{
auto v = eval("{} ? a"); auto v = eval("{} ? a");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(TrivialExpressionTest, hasAttrOpTrue) { TEST_F(TrivialExpressionTest, hasAttrOpTrue)
{
auto v = eval("{ a = 123; } ? a"); auto v = eval("{ a = 123; } ? a");
ASSERT_THAT(v, IsTrue()); ASSERT_THAT(v, IsTrue());
} }
TEST_F(TrivialExpressionTest, withFound) { TEST_F(TrivialExpressionTest, withFound)
{
auto v = eval("with { a = 23; }; a"); auto v = eval("with { a = 23; }; a");
ASSERT_THAT(v, IsIntEq(23)); ASSERT_THAT(v, IsIntEq(23));
} }
TEST_F(TrivialExpressionTest, withNotFound) { TEST_F(TrivialExpressionTest, withNotFound)
{
ASSERT_THROW(eval("with {}; a"), Error); ASSERT_THROW(eval("with {}; a"), Error);
} }
TEST_F(TrivialExpressionTest, withOverride) { TEST_F(TrivialExpressionTest, withOverride)
{
auto v = eval("with { a = 23; }; with { a = 42; }; a"); auto v = eval("with { a = 23; }; with { a = 42; }; a");
ASSERT_THAT(v, IsIntEq(42)); ASSERT_THAT(v, IsIntEq(42));
} }
TEST_F(TrivialExpressionTest, letOverWith) { TEST_F(TrivialExpressionTest, letOverWith)
{
auto v = eval("let a = 23; in with { a = 1; }; a"); auto v = eval("let a = 23; in with { a = 1; }; a");
ASSERT_THAT(v, IsIntEq(23)); ASSERT_THAT(v, IsIntEq(23));
} }
TEST_F(TrivialExpressionTest, multipleLet) { TEST_F(TrivialExpressionTest, multipleLet)
{
auto v = eval("let a = 23; in let a = 42; in a"); auto v = eval("let a = 23; in let a = 42; in a");
ASSERT_THAT(v, IsIntEq(42)); ASSERT_THAT(v, IsIntEq(42));
} }
TEST_F(TrivialExpressionTest, defaultFunctionArgs) { TEST_F(TrivialExpressionTest, defaultFunctionArgs)
{
auto v = eval("({ a ? 123 }: a) {}"); auto v = eval("({ a ? 123 }: a) {}");
ASSERT_THAT(v, IsIntEq(123)); ASSERT_THAT(v, IsIntEq(123));
} }
TEST_F(TrivialExpressionTest, defaultFunctionArgsOverride) { TEST_F(TrivialExpressionTest, defaultFunctionArgsOverride)
{
auto v = eval("({ a ? 123 }: a) { a = 5; }"); auto v = eval("({ a ? 123 }: a) { a = 5; }");
ASSERT_THAT(v, IsIntEq(5)); ASSERT_THAT(v, IsIntEq(5));
} }
TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureBack) { TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureBack)
{
auto v = eval("({ a ? 123 }@args: args) {}"); auto v = eval("({ a ? 123 }@args: args) {}");
ASSERT_THAT(v, IsAttrsOfSize(0)); ASSERT_THAT(v, IsAttrsOfSize(0));
} }
TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureFront) { TEST_F(TrivialExpressionTest, defaultFunctionArgsCaptureFront)
{
auto v = eval("(args@{ a ? 123 }: args) {}"); auto v = eval("(args@{ a ? 123 }: args) {}");
ASSERT_THAT(v, IsAttrsOfSize(0)); ASSERT_THAT(v, IsAttrsOfSize(0));
} }
TEST_F(TrivialExpressionTest, assertThrows) { TEST_F(TrivialExpressionTest, assertThrows)
{
ASSERT_THROW(eval("let x = arg: assert arg == 1; 123; in x 2"), Error); ASSERT_THROW(eval("let x = arg: assert arg == 1; 123; in x 2"), Error);
} }
TEST_F(TrivialExpressionTest, assertPassed) { TEST_F(TrivialExpressionTest, assertPassed)
{
auto v = eval("let x = arg: assert arg == 1; 123; in x 1"); auto v = eval("let x = arg: assert arg == 1; 123; in x 1");
ASSERT_THAT(v, IsIntEq(123)); ASSERT_THAT(v, IsIntEq(123));
} }
class AttrSetMergeTrvialExpressionTest : class AttrSetMergeTrvialExpressionTest : public TrivialExpressionTest, public testing::WithParamInterface<const char *>
public TrivialExpressionTest,
public testing::WithParamInterface<const char*>
{}; {};
TEST_P(AttrSetMergeTrvialExpressionTest, attrsetMergeLazy) { TEST_P(AttrSetMergeTrvialExpressionTest, attrsetMergeLazy)
{
// Usually Nix rejects duplicate keys in an attrset but it does allow // Usually Nix rejects duplicate keys in an attrset but it does allow
// so if it is an attribute set that contains disjoint sets of keys. // so if it is an attribute set that contains disjoint sets of keys.
// The below is equivalent to `{a.b = 1; a.c = 2; }`. // The below is equivalent to `{a.b = 1; a.c = 2; }`.
@ -171,11 +196,7 @@ namespace nix {
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(
attrsetMergeLazy, attrsetMergeLazy,
AttrSetMergeTrvialExpressionTest, AttrSetMergeTrvialExpressionTest,
testing::Values( testing::Values("{ a.b = 1; a.c = 2; }", "{ a = { b = 1; }; a = { c = 2; }; }"));
"{ a.b = 1; a.c = 2; }",
"{ a = { b = 1; }; a = { c = 2; }; }"
)
);
// The following macros ultimately define 48 tests (16 variations on three // The following macros ultimately define 48 tests (16 variations on three
// templates). Each template tests an expression that can be written in 2^4 // templates). Each template tests an expression that can be written in 2^4
@ -200,27 +221,33 @@ namespace nix {
#define X_EXPAND_IF0(k, v) k "." v #define X_EXPAND_IF0(k, v) k "." v
#define X_EXPAND_IF1(k, v) k " = { " v " };" #define X_EXPAND_IF1(k, v) k " = { " v " };"
#define X4(w, x, y, z) \ #define X4(w, x, y, z) \
TEST_F(TrivialExpressionTest, nestedAttrsetMerge##w##x##y##z) { \ TEST_F(TrivialExpressionTest, nestedAttrsetMerge##w##x##y##z) \
auto v = eval("{ a.b = { c = 1; d = 2; }; } == { " \ { \
X_EXPAND_IF##w("a", X_EXPAND_IF##x("b", "c = 1;")) " " \ auto v = eval( \
X_EXPAND_IF##y("a", X_EXPAND_IF##z("b", "d = 2;")) " }"); \ "{ a.b = { c = 1; d = 2; }; } == { " X_EXPAND_IF##w( \
"a", X_EXPAND_IF##x("b", "c = 1;")) " " X_EXPAND_IF##y("a", X_EXPAND_IF##z("b", "d = 2;")) " }"); \
ASSERT_THAT(v, IsTrue()); \ ASSERT_THAT(v, IsTrue()); \
}; \ }; \
TEST_F(TrivialExpressionTest, nestedAttrsetMergeDup##w##x##y##z) { \ TEST_F(TrivialExpressionTest, nestedAttrsetMergeDup##w##x##y##z) \
ASSERT_THROW(eval("{ " \ { \
X_EXPAND_IF##w("a", X_EXPAND_IF##x("b", "c = 1;")) " " \ ASSERT_THROW( \
X_EXPAND_IF##y("a", X_EXPAND_IF##z("b", "c = 2;")) " }"), Error); \ eval( \
"{ " X_EXPAND_IF##w("a", X_EXPAND_IF##x("b", "c = 1;")) " " X_EXPAND_IF##y( \
"a", X_EXPAND_IF##z("b", "c = 2;")) " }"), \
Error); \
}; \ }; \
TEST_F(TrivialExpressionTest, nestedAttrsetMergeLet##w##x##y##z) { \ TEST_F(TrivialExpressionTest, nestedAttrsetMergeLet##w##x##y##z) \
auto v = eval("{ b = { c = 1; d = 2; }; } == (let " \ { \
X_EXPAND_IF##w("a", X_EXPAND_IF##x("b", "c = 1;")) " " \ auto v = eval( \
X_EXPAND_IF##y("a", X_EXPAND_IF##z("b", "d = 2;")) " in a)"); \ "{ b = { c = 1; d = 2; }; } == (let " X_EXPAND_IF##w( \
"a", X_EXPAND_IF##x("b", "c = 1;")) " " X_EXPAND_IF##y("a", X_EXPAND_IF##z("b", "d = 2;")) " in a)"); \
ASSERT_THAT(v, IsTrue()); \ ASSERT_THAT(v, IsTrue()); \
}; };
#define X3(...) X4(__VA_ARGS__, 0) X4(__VA_ARGS__, 1) #define X3(...) X4(__VA_ARGS__, 0) X4(__VA_ARGS__, 1)
#define X2(...) X3(__VA_ARGS__, 0) X3(__VA_ARGS__, 1) #define X2(...) X3(__VA_ARGS__, 0) X3(__VA_ARGS__, 1)
#define X1(...) X2(__VA_ARGS__, 0) X2(__VA_ARGS__, 1) #define X1(...) X2(__VA_ARGS__, 0) X2(__VA_ARGS__, 1)
X1(0) X1(1) X1(0)
X1(1)
#undef X_EXPAND_IF0 #undef X_EXPAND_IF0
#undef X_EXPAND_IF1 #undef X_EXPAND_IF1
#undef X1 #undef X1
@ -228,66 +255,79 @@ namespace nix {
#undef X3 #undef X3
#undef X4 #undef X4
TEST_F(TrivialExpressionTest, functor) { TEST_F(TrivialExpressionTest, functor)
{
auto v = eval("{ __functor = self: arg: self.v + arg; v = 10; } 5"); auto v = eval("{ __functor = self: arg: self.v + arg; v = 10; } 5");
ASSERT_THAT(v, IsIntEq(15)); ASSERT_THAT(v, IsIntEq(15));
} }
TEST_F(TrivialExpressionTest, forwardPipe) { TEST_F(TrivialExpressionTest, forwardPipe)
{
auto v = eval("1 |> builtins.add 2 |> builtins.mul 3"); auto v = eval("1 |> builtins.add 2 |> builtins.mul 3");
ASSERT_THAT(v, IsIntEq(9)); ASSERT_THAT(v, IsIntEq(9));
} }
TEST_F(TrivialExpressionTest, backwardPipe) { TEST_F(TrivialExpressionTest, backwardPipe)
{
auto v = eval("builtins.add 1 <| builtins.mul 2 <| 3"); auto v = eval("builtins.add 1 <| builtins.mul 2 <| 3");
ASSERT_THAT(v, IsIntEq(7)); ASSERT_THAT(v, IsIntEq(7));
} }
TEST_F(TrivialExpressionTest, forwardPipeEvaluationOrder) { TEST_F(TrivialExpressionTest, forwardPipeEvaluationOrder)
{
auto v = eval("1 |> null |> (x: 2)"); auto v = eval("1 |> null |> (x: 2)");
ASSERT_THAT(v, IsIntEq(2)); ASSERT_THAT(v, IsIntEq(2));
} }
TEST_F(TrivialExpressionTest, backwardPipeEvaluationOrder) { TEST_F(TrivialExpressionTest, backwardPipeEvaluationOrder)
{
auto v = eval("(x: 1) <| null <| 2"); auto v = eval("(x: 1) <| null <| 2");
ASSERT_THAT(v, IsIntEq(1)); ASSERT_THAT(v, IsIntEq(1));
} }
TEST_F(TrivialExpressionTest, differentPipeOperatorsDoNotAssociate) { TEST_F(TrivialExpressionTest, differentPipeOperatorsDoNotAssociate)
{
ASSERT_THROW(eval("(x: 1) <| 2 |> (x: 3)"), ParseError); ASSERT_THROW(eval("(x: 1) <| 2 |> (x: 3)"), ParseError);
} }
TEST_F(TrivialExpressionTest, differentPipeOperatorsParensLeft) { TEST_F(TrivialExpressionTest, differentPipeOperatorsParensLeft)
{
auto v = eval("((x: 1) <| 2) |> (x: 3)"); auto v = eval("((x: 1) <| 2) |> (x: 3)");
ASSERT_THAT(v, IsIntEq(3)); ASSERT_THAT(v, IsIntEq(3));
} }
TEST_F(TrivialExpressionTest, differentPipeOperatorsParensRight) { TEST_F(TrivialExpressionTest, differentPipeOperatorsParensRight)
{
auto v = eval("(x: 1) <| (2 |> (x: 3))"); auto v = eval("(x: 1) <| (2 |> (x: 3))");
ASSERT_THAT(v, IsIntEq(1)); ASSERT_THAT(v, IsIntEq(1));
} }
TEST_F(TrivialExpressionTest, forwardPipeLowestPrecedence) { TEST_F(TrivialExpressionTest, forwardPipeLowestPrecedence)
{
auto v = eval("false -> true |> (x: !x)"); auto v = eval("false -> true |> (x: !x)");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(TrivialExpressionTest, backwardPipeLowestPrecedence) { TEST_F(TrivialExpressionTest, backwardPipeLowestPrecedence)
{
auto v = eval("(x: !x) <| false -> true"); auto v = eval("(x: !x) <| false -> true");
ASSERT_THAT(v, IsFalse()); ASSERT_THAT(v, IsFalse());
} }
TEST_F(TrivialExpressionTest, forwardPipeStrongerThanElse) { TEST_F(TrivialExpressionTest, forwardPipeStrongerThanElse)
{
auto v = eval("if true then 1 else 2 |> 3"); auto v = eval("if true then 1 else 2 |> 3");
ASSERT_THAT(v, IsIntEq(1)); ASSERT_THAT(v, IsIntEq(1));
} }
TEST_F(TrivialExpressionTest, backwardPipeStrongerThanElse) { TEST_F(TrivialExpressionTest, backwardPipeStrongerThanElse)
{
auto v = eval("if true then 1 else 2 <| 3"); auto v = eval("if true then 1 else 2 <| 3");
ASSERT_THAT(v, IsIntEq(1)); ASSERT_THAT(v, IsIntEq(1));
} }
TEST_F(TrivialExpressionTest, bindOr) { TEST_F(TrivialExpressionTest, bindOr)
{
auto v = eval("{ or = 1; }"); auto v = eval("{ or = 1; }");
ASSERT_THAT(v, IsAttrsOfSize(1)); ASSERT_THAT(v, IsAttrsOfSize(1));
auto b = v.attrs()->find(createSymbol("or")); auto b = v.attrs()->find(createSymbol("or"));
@ -295,7 +335,8 @@ namespace nix {
ASSERT_THAT(*b->value, IsIntEq(1)); ASSERT_THAT(*b->value, IsIntEq(1));
} }
TEST_F(TrivialExpressionTest, orCantBeUsed) { TEST_F(TrivialExpressionTest, orCantBeUsed)
{
ASSERT_THROW(eval("let or = 1; in or"), Error); ASSERT_THROW(eval("let or = 1; in or"), Error);
} }
} /* namespace nix */ } /* namespace nix */

View file

@ -10,41 +10,37 @@ namespace nix {
// Test a few cases of invalid string context elements. // Test a few cases of invalid string context elements.
TEST(NixStringContextElemTest, empty_invalid) { TEST(NixStringContextElemTest, empty_invalid)
EXPECT_THROW( {
NixStringContextElem::parse(""), EXPECT_THROW(NixStringContextElem::parse(""), BadNixStringContextElem);
BadNixStringContextElem);
} }
TEST(NixStringContextElemTest, single_bang_invalid) { TEST(NixStringContextElemTest, single_bang_invalid)
EXPECT_THROW( {
NixStringContextElem::parse("!"), EXPECT_THROW(NixStringContextElem::parse("!"), BadNixStringContextElem);
BadNixStringContextElem);
} }
TEST(NixStringContextElemTest, double_bang_invalid) { TEST(NixStringContextElemTest, double_bang_invalid)
EXPECT_THROW( {
NixStringContextElem::parse("!!/"), EXPECT_THROW(NixStringContextElem::parse("!!/"), BadStorePath);
BadStorePath);
} }
TEST(NixStringContextElemTest, eq_slash_invalid) { TEST(NixStringContextElemTest, eq_slash_invalid)
EXPECT_THROW( {
NixStringContextElem::parse("=/"), EXPECT_THROW(NixStringContextElem::parse("=/"), BadStorePath);
BadStorePath);
} }
TEST(NixStringContextElemTest, slash_invalid) { TEST(NixStringContextElemTest, slash_invalid)
EXPECT_THROW( {
NixStringContextElem::parse("/"), EXPECT_THROW(NixStringContextElem::parse("/"), BadStorePath);
BadStorePath);
} }
/** /**
* Round trip (string <-> data structure) test for * Round trip (string <-> data structure) test for
* `NixStringContextElem::Opaque`. * `NixStringContextElem::Opaque`.
*/ */
TEST(NixStringContextElemTest, opaque) { TEST(NixStringContextElemTest, opaque)
{
std::string_view opaque = "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x"; std::string_view opaque = "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
auto elem = NixStringContextElem::parse(opaque); auto elem = NixStringContextElem::parse(opaque);
auto * p = std::get_if<NixStringContextElem::Opaque>(&elem.raw); auto * p = std::get_if<NixStringContextElem::Opaque>(&elem.raw);
@ -57,7 +53,8 @@ TEST(NixStringContextElemTest, opaque) {
* Round trip (string <-> data structure) test for * Round trip (string <-> data structure) test for
* `NixStringContextElem::DrvDeep`. * `NixStringContextElem::DrvDeep`.
*/ */
TEST(NixStringContextElemTest, drvDeep) { TEST(NixStringContextElemTest, drvDeep)
{
std::string_view drvDeep = "=g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv"; std::string_view drvDeep = "=g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(drvDeep); auto elem = NixStringContextElem::parse(drvDeep);
auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem.raw); auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem.raw);
@ -70,13 +67,16 @@ TEST(NixStringContextElemTest, drvDeep) {
* Round trip (string <-> data structure) test for a simpler * Round trip (string <-> data structure) test for a simpler
* `NixStringContextElem::Built`. * `NixStringContextElem::Built`.
*/ */
TEST(NixStringContextElemTest, built_opaque) { TEST(NixStringContextElemTest, built_opaque)
{
std::string_view built = "!foo!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv"; std::string_view built = "!foo!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
auto elem = NixStringContextElem::parse(built); auto elem = NixStringContextElem::parse(built);
auto * p = std::get_if<NixStringContextElem::Built>(&elem.raw); auto * p = std::get_if<NixStringContextElem::Built>(&elem.raw);
ASSERT_TRUE(p); ASSERT_TRUE(p);
ASSERT_EQ(p->output, "foo"); ASSERT_EQ(p->output, "foo");
ASSERT_EQ(*p->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque { ASSERT_EQ(
*p->drvPath,
((SingleDerivedPath) SingleDerivedPath::Opaque{
.path = StorePath{built.substr(5)}, .path = StorePath{built.substr(5)},
})); }));
ASSERT_EQ(elem.to_string(), built); ASSERT_EQ(elem.to_string(), built);
@ -86,7 +86,8 @@ TEST(NixStringContextElemTest, built_opaque) {
* Round trip (string <-> data structure) test for a more complex, * Round trip (string <-> data structure) test for a more complex,
* inductive `NixStringContextElem::Built`. * inductive `NixStringContextElem::Built`.
*/ */
TEST(NixStringContextElemTest, built_built) { TEST(NixStringContextElemTest, built_built)
{
/** /**
* We set these in tests rather than the regular globals so we don't have * We set these in tests rather than the regular globals so we don't have
* to worry about race conditions if the tests run concurrently. * to worry about race conditions if the tests run concurrently.
@ -102,7 +103,9 @@ TEST(NixStringContextElemTest, built_built) {
auto * drvPath = std::get_if<SingleDerivedPath::Built>(&*p->drvPath); auto * drvPath = std::get_if<SingleDerivedPath::Built>(&*p->drvPath);
ASSERT_TRUE(drvPath); ASSERT_TRUE(drvPath);
ASSERT_EQ(drvPath->output, "bar"); ASSERT_EQ(drvPath->output, "bar");
ASSERT_EQ(*drvPath->drvPath, ((SingleDerivedPath) SingleDerivedPath::Opaque { ASSERT_EQ(
*drvPath->drvPath,
((SingleDerivedPath) SingleDerivedPath::Opaque{
.path = StorePath{built.substr(9)}, .path = StorePath{built.substr(9)},
})); }));
ASSERT_EQ(elem.to_string(), built); ASSERT_EQ(elem.to_string(), built);
@ -112,17 +115,15 @@ TEST(NixStringContextElemTest, built_built) {
* Without the right experimental features enabled, we cannot parse a * Without the right experimental features enabled, we cannot parse a
* complex inductive string context element. * complex inductive string context element.
*/ */
TEST(NixStringContextElemTest, built_built_xp) { TEST(NixStringContextElemTest, built_built_xp)
{
ASSERT_THROW( ASSERT_THROW(
NixStringContextElem::parse("!foo!bar!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv"), MissingExperimentalFeature); NixStringContextElem::parse("!foo!bar!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv"), MissingExperimentalFeature);
} }
#ifndef COVERAGE #ifndef COVERAGE
RC_GTEST_PROP( RC_GTEST_PROP(NixStringContextElemTest, prop_round_rip, (const NixStringContextElem & o))
NixStringContextElemTest,
prop_round_rip,
(const NixStringContextElem & o))
{ {
ExperimentalFeatureSettings xpSettings; ExperimentalFeatureSettings xpSettings;
xpSettings.set("experimental-features", "dynamic-derivations"); xpSettings.set("experimental-features", "dynamic-derivations");

View file

@ -106,10 +106,7 @@ TEST_F(ValuePrintingTests, vApp)
TEST_F(ValuePrintingTests, vLambda) TEST_F(ValuePrintingTests, vLambda)
{ {
Env env { Env env{.up = nullptr, .values = {}};
.up = nullptr,
.values = { }
};
PosTable::Origin origin = state.positions.addOrigin(std::monostate(), 1); PosTable::Origin origin = state.positions.addOrigin(std::monostate(), 1);
auto posIdx = state.positions.add(origin, 0); auto posIdx = state.positions.add(origin, 0);
auto body = ExprInt(0); auto body = ExprInt(0);
@ -130,9 +127,7 @@ TEST_F(ValuePrintingTests, vLambda)
TEST_F(ValuePrintingTests, vPrimOp) TEST_F(ValuePrintingTests, vPrimOp)
{ {
Value vPrimOp; Value vPrimOp;
PrimOp primOp{ PrimOp primOp{.name = "puppy"};
.name = "puppy"
};
vPrimOp.mkPrimOp(&primOp); vPrimOp.mkPrimOp(&primOp);
test(vPrimOp, "«primop puppy»"); test(vPrimOp, "«primop puppy»");
@ -140,9 +135,7 @@ TEST_F(ValuePrintingTests, vPrimOp)
TEST_F(ValuePrintingTests, vPrimOpApp) TEST_F(ValuePrintingTests, vPrimOpApp)
{ {
PrimOp primOp{ PrimOp primOp{.name = "puppy"};
.name = "puppy"
};
Value vPrimOp; Value vPrimOp;
vPrimOp.mkPrimOp(&primOp); vPrimOp.mkPrimOp(&primOp);
@ -221,7 +214,10 @@ TEST_F(ValuePrintingTests, depthAttrs)
vNested.mkAttrs(builder2.finish()); vNested.mkAttrs(builder2.finish());
test(vNested, "{ nested = { ... }; one = 1; two = 2; }", PrintOptions{.maxDepth = 1}); test(vNested, "{ nested = { ... }; one = 1; two = 2; }", PrintOptions{.maxDepth = 1});
test(vNested, "{ nested = { nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 2 }); test(
vNested,
"{ nested = { nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }",
PrintOptions{.maxDepth = 2});
test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions{.maxDepth = 3}); test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions{.maxDepth = 3});
test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions{.maxDepth = 4}); test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions{.maxDepth = 4});
} }
@ -272,9 +268,7 @@ struct StringPrintingTests : LibExprTest
v.mkString(literal); v.mkString(literal);
std::stringstream out; std::stringstream out;
printValue(state, out, v, PrintOptions { printValue(state, out, v, PrintOptions{.maxStringLength = maxLength});
.maxStringLength = maxLength
});
ASSERT_EQ(out.str(), expected); ASSERT_EQ(out.str(), expected);
} }
}; };
@ -305,15 +299,9 @@ TEST_F(ValuePrintingTests, attrsTypeFirst)
Value vAttrs; Value vAttrs;
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(vAttrs, "{ type = \"puppy\"; apple = \"apple\"; }", PrintOptions{.maxAttrs = 100});
"{ type = \"puppy\"; apple = \"apple\"; }",
PrintOptions {
.maxAttrs = 100
});
test(vAttrs, test(vAttrs, "{ apple = \"apple\"; type = \"puppy\"; }", PrintOptions{});
"{ apple = \"apple\"; type = \"puppy\"; }",
PrintOptions { });
} }
TEST_F(ValuePrintingTests, ansiColorsInt) TEST_F(ValuePrintingTests, ansiColorsInt)
@ -321,11 +309,7 @@ TEST_F(ValuePrintingTests, ansiColorsInt)
Value v; Value v;
v.mkInt(10); v.mkInt(10);
test(v, test(v, ANSI_CYAN "10" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_CYAN "10" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsFloat) TEST_F(ValuePrintingTests, ansiColorsFloat)
@ -333,11 +317,7 @@ TEST_F(ValuePrintingTests, ansiColorsFloat)
Value v; Value v;
v.mkFloat(1.6); v.mkFloat(1.6);
test(v, test(v, ANSI_CYAN "1.6" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_CYAN "1.6" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsBool) TEST_F(ValuePrintingTests, ansiColorsBool)
@ -345,11 +325,7 @@ TEST_F(ValuePrintingTests, ansiColorsBool)
Value v; Value v;
v.mkBool(true); v.mkBool(true);
test(v, test(v, ANSI_CYAN "true" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_CYAN "true" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsString) TEST_F(ValuePrintingTests, ansiColorsString)
@ -357,11 +333,7 @@ TEST_F(ValuePrintingTests, ansiColorsString)
Value v; Value v;
v.mkString("puppy"); v.mkString("puppy");
test(v, test(v, ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsStringElided) TEST_F(ValuePrintingTests, ansiColorsStringElided)
@ -369,12 +341,10 @@ TEST_F(ValuePrintingTests, ansiColorsStringElided)
Value v; Value v;
v.mkString("puppy"); v.mkString("puppy");
test(v, test(
v,
ANSI_MAGENTA "\"pup\" " ANSI_FAINT "«2 bytes elided»" ANSI_NORMAL, ANSI_MAGENTA "\"pup\" " ANSI_FAINT "«2 bytes elided»" ANSI_NORMAL,
PrintOptions { PrintOptions{.ansiColors = true, .maxStringLength = 3});
.ansiColors = true,
.maxStringLength = 3
});
} }
TEST_F(ValuePrintingTests, ansiColorsPath) TEST_F(ValuePrintingTests, ansiColorsPath)
@ -382,11 +352,7 @@ TEST_F(ValuePrintingTests, ansiColorsPath)
Value v; Value v;
v.mkPath(state.rootPath(CanonPath("puppy"))); v.mkPath(state.rootPath(CanonPath("puppy")));
test(v, test(v, ANSI_GREEN "/puppy" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_GREEN "/puppy" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsNull) TEST_F(ValuePrintingTests, ansiColorsNull)
@ -394,11 +360,7 @@ TEST_F(ValuePrintingTests, ansiColorsNull)
Value v; Value v;
v.mkNull(); v.mkNull();
test(v, test(v, ANSI_CYAN "null" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_CYAN "null" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsAttrs) TEST_F(ValuePrintingTests, ansiColorsAttrs)
@ -416,11 +378,10 @@ TEST_F(ValuePrintingTests, ansiColorsAttrs)
Value vAttrs; Value vAttrs;
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(
vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; two = " ANSI_CYAN "2" ANSI_NORMAL "; }", "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; two = " ANSI_CYAN "2" ANSI_NORMAL "; }",
PrintOptions { PrintOptions{.ansiColors = true});
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsDerivation) TEST_F(ValuePrintingTests, ansiColorsDerivation)
@ -434,20 +395,15 @@ TEST_F(ValuePrintingTests, ansiColorsDerivation)
Value vAttrs; Value vAttrs;
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(
vAttrs,
ANSI_GREEN "«derivation»" ANSI_NORMAL, ANSI_GREEN "«derivation»" ANSI_NORMAL,
PrintOptions { PrintOptions{.ansiColors = true, .force = true, .derivationPaths = true});
.ansiColors = true,
.force = true,
.derivationPaths = true
});
test(vAttrs, test(
vAttrs,
"{ type = " ANSI_MAGENTA "\"derivation\"" ANSI_NORMAL "; }", "{ type = " ANSI_MAGENTA "\"derivation\"" ANSI_NORMAL "; }",
PrintOptions { PrintOptions{.ansiColors = true, .force = true});
.ansiColors = true,
.force = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsError) TEST_F(ValuePrintingTests, ansiColorsError)
@ -458,10 +414,9 @@ TEST_F(ValuePrintingTests, ansiColorsError)
Value vError; Value vError;
vError.mkApp(&throw_, &message); vError.mkApp(&throw_, &message);
test(vError, test(
ANSI_RED vError,
"«error: uh oh!»" ANSI_RED "«error: uh oh!»" ANSI_NORMAL,
ANSI_NORMAL,
PrintOptions{ PrintOptions{
.ansiColors = true, .ansiColors = true,
.force = true, .force = true,
@ -486,25 +441,15 @@ TEST_F(ValuePrintingTests, ansiColorsDerivationError)
Value vAttrs; Value vAttrs;
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(
"{ drvPath = " vAttrs,
ANSI_RED "{ drvPath = " ANSI_RED "«error: uh oh!»" ANSI_NORMAL "; type = " ANSI_MAGENTA "\"derivation\"" ANSI_NORMAL
"«error: uh oh!»"
ANSI_NORMAL
"; type = "
ANSI_MAGENTA
"\"derivation\""
ANSI_NORMAL
"; }", "; }",
PrintOptions { PrintOptions{.ansiColors = true, .force = true});
.ansiColors = true,
.force = true
});
test(vAttrs, test(
ANSI_RED vAttrs,
"«error: uh oh!»" ANSI_RED "«error: uh oh!»" ANSI_NORMAL,
ANSI_NORMAL,
PrintOptions{ PrintOptions{
.ansiColors = true, .ansiColors = true,
.force = true, .force = true,
@ -523,12 +468,7 @@ TEST_F(ValuePrintingTests, ansiColorsAssert)
Value v; Value v;
state.mkThunk_(v, &expr); state.mkThunk_(v, &expr);
test(v, test(v, ANSI_RED "«error: assertion 'false' failed»" ANSI_NORMAL, PrintOptions{.ansiColors = true, .force = true});
ANSI_RED "«error: assertion 'false' failed»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsList) TEST_F(ValuePrintingTests, ansiColorsList)
@ -545,19 +485,15 @@ TEST_F(ValuePrintingTests, ansiColorsList)
Value vList; Value vList;
vList.mkList(list); vList.mkList(list);
test(vList, test(
vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA "«nullptr»" ANSI_NORMAL " ]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA "«nullptr»" ANSI_NORMAL " ]",
PrintOptions { PrintOptions{.ansiColors = true});
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsLambda) TEST_F(ValuePrintingTests, ansiColorsLambda)
{ {
Env env { Env env{.up = nullptr, .values = {}};
.up = nullptr,
.values = { }
};
PosTable::Origin origin = state.positions.addOrigin(std::monostate(), 1); PosTable::Origin origin = state.positions.addOrigin(std::monostate(), 1);
auto posIdx = state.positions.add(origin, 0); auto posIdx = state.positions.add(origin, 0);
auto body = ExprInt(0); auto body = ExprInt(0);
@ -568,54 +504,32 @@ TEST_F(ValuePrintingTests, ansiColorsLambda)
Value vLambda; Value vLambda;
vLambda.mkLambda(&env, &eLambda); vLambda.mkLambda(&env, &eLambda);
test(vLambda, test(vLambda, ANSI_BLUE "«lambda @ «none»:1:1»" ANSI_NORMAL, PrintOptions{.ansiColors = true, .force = true});
ANSI_BLUE "«lambda @ «none»:1:1»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
eLambda.setName(createSymbol("puppy")); eLambda.setName(createSymbol("puppy"));
test(vLambda, test(vLambda, ANSI_BLUE "«lambda puppy @ «none»:1:1»" ANSI_NORMAL, PrintOptions{.ansiColors = true, .force = true});
ANSI_BLUE "«lambda puppy @ «none»:1:1»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsPrimOp) TEST_F(ValuePrintingTests, ansiColorsPrimOp)
{ {
PrimOp primOp{ PrimOp primOp{.name = "puppy"};
.name = "puppy"
};
Value v; Value v;
v.mkPrimOp(&primOp); v.mkPrimOp(&primOp);
test(v, test(v, ANSI_BLUE "«primop puppy»" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_BLUE "«primop puppy»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsPrimOpApp) TEST_F(ValuePrintingTests, ansiColorsPrimOpApp)
{ {
PrimOp primOp{ PrimOp primOp{.name = "puppy"};
.name = "puppy"
};
Value vPrimOp; Value vPrimOp;
vPrimOp.mkPrimOp(&primOp); vPrimOp.mkPrimOp(&primOp);
Value v; Value v;
v.mkPrimOpApp(&vPrimOp, nullptr); v.mkPrimOpApp(&vPrimOp, nullptr);
test(v, test(v, ANSI_BLUE "«partially applied primop puppy»" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_BLUE "«partially applied primop puppy»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsThunk) TEST_F(ValuePrintingTests, ansiColorsThunk)
@ -623,11 +537,7 @@ TEST_F(ValuePrintingTests, ansiColorsThunk)
Value v; Value v;
v.mkThunk(nullptr, nullptr); v.mkThunk(nullptr, nullptr);
test(v, test(v, ANSI_MAGENTA "«thunk»" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_MAGENTA "«thunk»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsBlackhole) TEST_F(ValuePrintingTests, ansiColorsBlackhole)
@ -635,11 +545,7 @@ TEST_F(ValuePrintingTests, ansiColorsBlackhole)
Value v; Value v;
v.mkBlackhole(); v.mkBlackhole();
test(v, test(v, ANSI_RED "«potential infinite recursion»" ANSI_NORMAL, PrintOptions{.ansiColors = true});
ANSI_RED "«potential infinite recursion»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated) TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated)
@ -656,11 +562,7 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated)
Value vAttrs; Value vAttrs;
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(vAttrs, "{ a = { }; b = " ANSI_MAGENTA "«repeated»" ANSI_NORMAL "; }", PrintOptions{.ansiColors = true});
"{ a = { }; b = " ANSI_MAGENTA "«repeated»" ANSI_NORMAL "; }",
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, ansiColorsListRepeated) TEST_F(ValuePrintingTests, ansiColorsListRepeated)
@ -676,11 +578,7 @@ TEST_F(ValuePrintingTests, ansiColorsListRepeated)
Value vList; Value vList;
vList.mkList(list); vList.mkList(list);
test(vList, test(vList, "[ { } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]", PrintOptions{.ansiColors = true});
"[ { } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]",
PrintOptions {
.ansiColors = true
});
} }
TEST_F(ValuePrintingTests, listRepeated) TEST_F(ValuePrintingTests, listRepeated)
@ -697,11 +595,7 @@ TEST_F(ValuePrintingTests, listRepeated)
vList.mkList(list); vList.mkList(list);
test(vList, "[ { } «repeated» ]", PrintOptions{}); test(vList, "[ { } «repeated» ]", PrintOptions{});
test(vList, test(vList, "[ { } { } ]", PrintOptions{.trackRepeated = false});
"[ { } { } ]",
PrintOptions {
.trackRepeated = false
});
} }
TEST_F(ValuePrintingTests, ansiColorsAttrsElided) TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
@ -719,12 +613,10 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
Value vAttrs; Value vAttrs;
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(
vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«1 attribute elided»" ANSI_NORMAL " }", "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«1 attribute elided»" ANSI_NORMAL " }",
PrintOptions { PrintOptions{.ansiColors = true, .maxAttrs = 1});
.ansiColors = true,
.maxAttrs = 1
});
Value vThree; Value vThree;
vThree.mkInt(3); vThree.mkInt(3);
@ -732,12 +624,10 @@ TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
builder.insert(state.symbols.create("three"), &vThree); builder.insert(state.symbols.create("three"), &vThree);
vAttrs.mkAttrs(builder.finish()); vAttrs.mkAttrs(builder.finish());
test(vAttrs, test(
vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL " }", "{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT "«2 attributes elided»" ANSI_NORMAL " }",
PrintOptions { PrintOptions{.ansiColors = true, .maxAttrs = 1});
.ansiColors = true,
.maxAttrs = 1
});
} }
TEST_F(ValuePrintingTests, ansiColorsListElided) TEST_F(ValuePrintingTests, ansiColorsListElided)
@ -757,12 +647,10 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
Value vList; Value vList;
vList.mkList(list); vList.mkList(list);
test(vList, test(
vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«1 item elided»" ANSI_NORMAL " ]",
PrintOptions { PrintOptions{.ansiColors = true, .maxListItems = 1});
.ansiColors = true,
.maxListItems = 1
});
} }
Value vThree; Value vThree;
@ -776,12 +664,10 @@ TEST_F(ValuePrintingTests, ansiColorsListElided)
Value vList; Value vList;
vList.mkList(list); vList.mkList(list);
test(vList, test(
vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]", "[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT "«2 items elided»" ANSI_NORMAL " ]",
PrintOptions { PrintOptions{.ansiColors = true, .maxListItems = 1});
.ansiColors = true,
.maxListItems = 1
});
} }
} }

View file

@ -1,10 +1,8 @@
#include "nix/expr/attr-path.hh" #include "nix/expr/attr-path.hh"
#include "nix/expr/eval-inline.hh" #include "nix/expr/eval-inline.hh"
namespace nix { namespace nix {
static Strings parseAttrPath(std::string_view s) static Strings parseAttrPath(std::string_view s)
{ {
Strings res; Strings res;
@ -19,18 +17,19 @@ static Strings parseAttrPath(std::string_view s)
while (1) { while (1) {
if (i == s.end()) if (i == s.end())
throw ParseError("missing closing quote in selection path '%1%'", s); throw ParseError("missing closing quote in selection path '%1%'", s);
if (*i == '"') break; if (*i == '"')
break;
cur.push_back(*i++); cur.push_back(*i++);
} }
} else } else
cur.push_back(*i); cur.push_back(*i);
++i; ++i;
} }
if (!cur.empty()) res.push_back(cur); if (!cur.empty())
res.push_back(cur);
return res; return res;
} }
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s) std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s)
{ {
std::vector<Symbol> res; std::vector<Symbol> res;
@ -39,9 +38,8 @@ std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s)
return res; return res;
} }
std::pair<Value *, PosIdx>
std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::string & attrPath, findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn)
Bindings & autoArgs, Value & vIn)
{ {
Strings tokens = parseAttrPath(attrPath); Strings tokens = parseAttrPath(attrPath);
@ -65,10 +63,12 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
if (!attrIndex) { if (!attrIndex) {
if (v->type() != nAttrs) if (v->type() != nAttrs)
state.error<TypeError>( state
.error<TypeError>(
"the expression selected by the selection path '%1%' should be a set but is %2%", "the expression selected by the selection path '%1%' should be a set but is %2%",
attrPath, attrPath,
showType(*v)).debugThrow(); showType(*v))
.debugThrow();
if (attr.empty()) if (attr.empty())
throw Error("empty attribute name in selection path '%1%'", attrPath); throw Error("empty attribute name in selection path '%1%'", attrPath);
@ -79,7 +79,8 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
attrNames.insert(std::string(state.symbols[attr.name])); attrNames.insert(std::string(state.symbols[attr.name]));
auto suggestions = Suggestions::bestMatches(attrNames, attr); auto suggestions = Suggestions::bestMatches(attrNames, attr);
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath); throw AttrPathNotFound(
suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
} }
v = &*a->value; v = &*a->value;
pos = a->pos; pos = a->pos;
@ -88,23 +89,23 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
else { else {
if (!v->isList()) if (!v->isList())
state.error<TypeError>( state
.error<TypeError>(
"the expression selected by the selection path '%1%' should be a list but is %2%", "the expression selected by the selection path '%1%' should be a list but is %2%",
attrPath, attrPath,
showType(*v)).debugThrow(); showType(*v))
.debugThrow();
if (*attrIndex >= v->listSize()) if (*attrIndex >= v->listSize())
throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath); throw AttrPathNotFound("list index %1% in selection path '%2%' is out of range", *attrIndex, attrPath);
v = v->listElems()[*attrIndex]; v = v->listElems()[*attrIndex];
pos = noPos; pos = noPos;
} }
} }
return {v, pos}; return {v, pos};
} }
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what) std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
{ {
Value * v2; Value * v2;
@ -118,17 +119,17 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
// FIXME: is it possible to extract the Pos object instead of doing this // FIXME: is it possible to extract the Pos object instead of doing this
// toString + parsing? // toString + parsing?
NixStringContext context; NixStringContext context;
auto path = state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation"); auto path =
state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation");
auto fn = path.path.abs(); auto fn = path.path.abs();
auto fail = [fn]() { auto fail = [fn]() { throw ParseError("cannot parse 'meta.position' attribute '%s'", fn); };
throw ParseError("cannot parse 'meta.position' attribute '%s'", fn);
};
try { try {
auto colon = fn.rfind(':'); auto colon = fn.rfind(':');
if (colon == std::string::npos) fail(); if (colon == std::string::npos)
fail();
auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos)); auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos));
return {SourcePath{path.accessor, CanonPath(fn.substr(0, colon))}, lineno}; return {SourcePath{path.accessor, CanonPath(fn.substr(0, colon))}, lineno};
} catch (std::invalid_argument & e) { } catch (std::invalid_argument & e) {
@ -137,5 +138,4 @@ std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v
} }
} }
} }

View file

@ -3,11 +3,8 @@
#include <algorithm> #include <algorithm>
namespace nix { namespace nix {
/* Allocate a new array of attributes for an attribute set with a specific /* Allocate a new array of attributes for an attribute set with a specific
capacity. The space is implicitly reserved after the Bindings capacity. The space is implicitly reserved after the Bindings
structure. */ structure. */
@ -22,7 +19,6 @@ Bindings * EvalState::allocBindings(size_t capacity)
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings((Bindings::size_t) capacity); return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings((Bindings::size_t) capacity);
} }
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos) Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
{ {
auto value = state.allocValue(); auto value = state.allocValue();
@ -30,24 +26,21 @@ Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
return *value; return *value;
} }
Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos) Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos)
{ {
return alloc(state.symbols.create(name), pos); return alloc(state.symbols.create(name), pos);
} }
void Bindings::sort() void Bindings::sort()
{ {
if (size_) std::sort(begin(), end()); if (size_)
std::sort(begin(), end());
} }
Value & Value::mkAttrs(BindingsBuilder & bindings) Value & Value::mkAttrs(BindingsBuilder & bindings)
{ {
mkAttrs(bindings.finish()); mkAttrs(bindings.finish());
return *this; return *this;
} }
} }

View file

@ -11,8 +11,10 @@ namespace nix::eval_cache {
CachedEvalError::CachedEvalError(ref<AttrCursor> cursor, Symbol attr) CachedEvalError::CachedEvalError(ref<AttrCursor> cursor, Symbol attr)
: EvalError(cursor->root->state, "cached failure of attribute '%s'", cursor->getAttrPathStr(attr)) : EvalError(cursor->root->state, "cached failure of attribute '%s'", cursor->getAttrPathStr(attr))
, cursor(cursor), attr(attr) , cursor(cursor)
{ } , attr(attr)
{
}
void CachedEvalError::force() void CachedEvalError::force()
{ {
@ -25,7 +27,8 @@ void CachedEvalError::force()
} }
// Shouldn't happen. // Shouldn't happen.
throw EvalError(state, "evaluation of cached failed attribute '%s' unexpectedly succeeded", cursor->getAttrPathStr(attr)); throw EvalError(
state, "evaluation of cached failed attribute '%s' unexpectedly succeeded", cursor->getAttrPathStr(attr));
} }
static const char * schema = R"sql( static const char * schema = R"sql(
@ -59,10 +62,7 @@ struct AttrDb
SymbolTable & symbols; SymbolTable & symbols;
AttrDb( AttrDb(const StoreDirConfig & cfg, const Hash & fingerprint, SymbolTable & symbols)
const StoreDirConfig & cfg,
const Hash & fingerprint,
SymbolTable & symbols)
: cfg(cfg) : cfg(cfg)
, _state(std::make_unique<Sync<State>>()) , _state(std::make_unique<Sync<State>>())
, symbols(symbols) , symbols(symbols)
@ -78,17 +78,16 @@ struct AttrDb
state->db.isCache(); state->db.isCache();
state->db.exec(schema); state->db.exec(schema);
state->insertAttribute.create(state->db, state->insertAttribute.create(
"insert or replace into Attributes(parent, name, type, value) values (?, ?, ?, ?)"); state->db, "insert or replace into Attributes(parent, name, type, value) values (?, ?, ?, ?)");
state->insertAttributeWithContext.create(state->db, state->insertAttributeWithContext.create(
"insert or replace into Attributes(parent, name, type, value, context) values (?, ?, ?, ?, ?)"); state->db, "insert or replace into Attributes(parent, name, type, value, context) values (?, ?, ?, ?, ?)");
state->queryAttribute.create(state->db, state->queryAttribute.create(
"select rowid, type, value, context from Attributes where parent = ? and name = ?"); state->db, "select rowid, type, value, context from Attributes where parent = ? and name = ?");
state->queryAttributes.create(state->db, state->queryAttributes.create(state->db, "select name from Attributes where parent = ?");
"select name from Attributes where parent = ?");
state->txn = std::make_unique<SQLiteTxn>(state->db); state->txn = std::make_unique<SQLiteTxn>(state->db);
} }
@ -108,7 +107,8 @@ struct AttrDb
template<typename F> template<typename F>
AttrId doSQLite(F && fun) AttrId doSQLite(F && fun)
{ {
if (failed) return 0; if (failed)
return 0;
try { try {
return fun(); return fun();
} catch (SQLiteError &) { } catch (SQLiteError &) {
@ -118,116 +118,76 @@ struct AttrDb
} }
} }
AttrId setAttrs( AttrId setAttrs(AttrKey key, const std::vector<Symbol> & attrs)
AttrKey key,
const std::vector<Symbol> & attrs)
{
return doSQLite([&]()
{ {
return doSQLite([&]() {
auto state(_state->lock()); auto state(_state->lock());
state->insertAttribute.use() state->insertAttribute.use()(key.first)(symbols[key.second])(AttrType::FullAttrs) (0, false).exec();
(key.first)
(symbols[key.second])
(AttrType::FullAttrs)
(0, false).exec();
AttrId rowId = state->db.getLastInsertedRowId(); AttrId rowId = state->db.getLastInsertedRowId();
assert(rowId); assert(rowId);
for (auto & attr : attrs) for (auto & attr : attrs)
state->insertAttribute.use() state->insertAttribute.use()(rowId)(symbols[attr])(AttrType::Placeholder) (0, false).exec();
(rowId)
(symbols[attr])
(AttrType::Placeholder)
(0, false).exec();
return rowId; return rowId;
}); });
} }
AttrId setString( AttrId setString(AttrKey key, std::string_view s, const char ** context = nullptr)
AttrKey key,
std::string_view s,
const char * * context = nullptr)
{
return doSQLite([&]()
{ {
return doSQLite([&]() {
auto state(_state->lock()); auto state(_state->lock());
if (context) { if (context) {
std::string ctx; std::string ctx;
for (const char ** p = context; *p; ++p) { for (const char ** p = context; *p; ++p) {
if (p != context) ctx.push_back(' '); if (p != context)
ctx.push_back(' ');
ctx.append(*p); ctx.append(*p);
} }
state->insertAttributeWithContext.use() state->insertAttributeWithContext.use()(key.first)(symbols[key.second])(AttrType::String) (s) (ctx)
(key.first) .exec();
(symbols[key.second])
(AttrType::String)
(s)
(ctx).exec();
} else { } else {
state->insertAttribute.use() state->insertAttribute.use()(key.first)(symbols[key.second])(AttrType::String) (s).exec();
(key.first)
(symbols[key.second])
(AttrType::String)
(s).exec();
} }
return state->db.getLastInsertedRowId(); return state->db.getLastInsertedRowId();
}); });
} }
AttrId setBool( AttrId setBool(AttrKey key, bool b)
AttrKey key,
bool b)
{
return doSQLite([&]()
{ {
return doSQLite([&]() {
auto state(_state->lock()); auto state(_state->lock());
state->insertAttribute.use() state->insertAttribute.use()(key.first)(symbols[key.second])(AttrType::Bool) (b ? 1 : 0).exec();
(key.first)
(symbols[key.second])
(AttrType::Bool)
(b ? 1 : 0).exec();
return state->db.getLastInsertedRowId(); return state->db.getLastInsertedRowId();
}); });
} }
AttrId setInt( AttrId setInt(AttrKey key, int n)
AttrKey key,
int n)
{
return doSQLite([&]()
{ {
return doSQLite([&]() {
auto state(_state->lock()); auto state(_state->lock());
state->insertAttribute.use() state->insertAttribute.use()(key.first)(symbols[key.second])(AttrType::Int) (n).exec();
(key.first)
(symbols[key.second])
(AttrType::Int)
(n).exec();
return state->db.getLastInsertedRowId(); return state->db.getLastInsertedRowId();
}); });
} }
AttrId setListOfStrings( AttrId setListOfStrings(AttrKey key, const std::vector<std::string> & l)
AttrKey key,
const std::vector<std::string> & l)
{
return doSQLite([&]()
{ {
return doSQLite([&]() {
auto state(_state->lock()); auto state(_state->lock());
state->insertAttribute.use() state->insertAttribute
(key.first) .use()(key.first)(symbols[key.second])(
(symbols[key.second]) AttrType::ListOfStrings) (dropEmptyInitThenConcatStringsSep("\t", l))
(AttrType::ListOfStrings) .exec();
(dropEmptyInitThenConcatStringsSep("\t", l)).exec();
return state->db.getLastInsertedRowId(); return state->db.getLastInsertedRowId();
}); });
@ -235,15 +195,10 @@ struct AttrDb
AttrId setPlaceholder(AttrKey key) AttrId setPlaceholder(AttrKey key)
{ {
return doSQLite([&]() return doSQLite([&]() {
{
auto state(_state->lock()); auto state(_state->lock());
state->insertAttribute.use() state->insertAttribute.use()(key.first)(symbols[key.second])(AttrType::Placeholder) (0, false).exec();
(key.first)
(symbols[key.second])
(AttrType::Placeholder)
(0, false).exec();
return state->db.getLastInsertedRowId(); return state->db.getLastInsertedRowId();
}); });
@ -251,15 +206,10 @@ struct AttrDb
AttrId setMissing(AttrKey key) AttrId setMissing(AttrKey key)
{ {
return doSQLite([&]() return doSQLite([&]() {
{
auto state(_state->lock()); auto state(_state->lock());
state->insertAttribute.use() state->insertAttribute.use()(key.first)(symbols[key.second])(AttrType::Missing) (0, false).exec();
(key.first)
(symbols[key.second])
(AttrType::Missing)
(0, false).exec();
return state->db.getLastInsertedRowId(); return state->db.getLastInsertedRowId();
}); });
@ -267,15 +217,10 @@ struct AttrDb
AttrId setMisc(AttrKey key) AttrId setMisc(AttrKey key)
{ {
return doSQLite([&]() return doSQLite([&]() {
{
auto state(_state->lock()); auto state(_state->lock());
state->insertAttribute.use() state->insertAttribute.use()(key.first)(symbols[key.second])(AttrType::Misc) (0, false).exec();
(key.first)
(symbols[key.second])
(AttrType::Misc)
(0, false).exec();
return state->db.getLastInsertedRowId(); return state->db.getLastInsertedRowId();
}); });
@ -283,15 +228,10 @@ struct AttrDb
AttrId setFailed(AttrKey key) AttrId setFailed(AttrKey key)
{ {
return doSQLite([&]() return doSQLite([&]() {
{
auto state(_state->lock()); auto state(_state->lock());
state->insertAttribute.use() state->insertAttribute.use()(key.first)(symbols[key.second])(AttrType::Failed) (0, false).exec();
(key.first)
(symbols[key.second])
(AttrType::Failed)
(0, false).exec();
return state->db.getLastInsertedRowId(); return state->db.getLastInsertedRowId();
}); });
@ -302,7 +242,8 @@ struct AttrDb
auto state(_state->lock()); auto state(_state->lock());
auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second])); auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second]));
if (!queryAttribute.next()) return {}; if (!queryAttribute.next())
return {};
auto rowId = (AttrId) queryAttribute.getInt(0); auto rowId = (AttrId) queryAttribute.getInt(0);
auto type = (AttrType) queryAttribute.getInt(1); auto type = (AttrType) queryAttribute.getInt(1);
@ -343,10 +284,7 @@ struct AttrDb
} }
}; };
static std::shared_ptr<AttrDb> makeAttrDb( static std::shared_ptr<AttrDb> makeAttrDb(const StoreDirConfig & cfg, const Hash & fingerprint, SymbolTable & symbols)
const StoreDirConfig & cfg,
const Hash & fingerprint,
SymbolTable & symbols)
{ {
try { try {
return std::make_shared<AttrDb>(cfg, fingerprint, symbols); return std::make_shared<AttrDb>(cfg, fingerprint, symbols);
@ -357,9 +295,7 @@ static std::shared_ptr<AttrDb> makeAttrDb(
} }
EvalCache::EvalCache( EvalCache::EvalCache(
std::optional<std::reference_wrapper<const Hash>> useCache, std::optional<std::reference_wrapper<const Hash>> useCache, EvalState & state, RootLoader rootLoader)
EvalState & state,
RootLoader rootLoader)
: db(useCache ? makeAttrDb(*state.store, *useCache, state.symbols) : nullptr) : db(useCache ? makeAttrDb(*state.store, *useCache, state.symbols) : nullptr)
, state(state) , state(state)
, rootLoader(rootLoader) , rootLoader(rootLoader)
@ -381,11 +317,10 @@ ref<AttrCursor> EvalCache::getRoot()
} }
AttrCursor::AttrCursor( AttrCursor::AttrCursor(
ref<EvalCache> root, ref<EvalCache> root, Parent parent, Value * value, std::optional<std::pair<AttrId, AttrValue>> && cachedValue)
Parent parent, : root(root)
Value * value, , parent(parent)
std::optional<std::pair<AttrId, AttrValue>> && cachedValue) , cachedValue(std::move(cachedValue))
: root(root), parent(parent), cachedValue(std::move(cachedValue))
{ {
if (value) if (value)
_value = allocRootValue(value); _value = allocRootValue(value);
@ -470,13 +405,11 @@ Value & AttrCursor::forceValue()
if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) { if (root->db && (!cachedValue || std::get_if<placeholder_t>(&cachedValue->second))) {
if (v.type() == nString) if (v.type() == nString)
cachedValue = {root->db->setString(getKey(), v.c_str(), v.context()), cachedValue = {root->db->setString(getKey(), v.c_str(), v.context()), string_t{v.c_str(), {}}};
string_t{v.c_str(), {}}};
else if (v.type() == nPath) { else if (v.type() == nPath) {
auto path = v.path().path; auto path = v.path().path;
cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}}; cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}};
} } else if (v.type() == nBool)
else if (v.type() == nBool)
cachedValue = {root->db->setBool(getKey(), v.boolean()), v.boolean()}; cachedValue = {root->db->setBool(getKey(), v.boolean()), v.boolean()};
else if (v.type() == nInt) else if (v.type() == nInt)
cachedValue = {root->db->setInt(getKey(), v.integer().value), int_t{v.integer()}}; cachedValue = {root->db->setInt(getKey(), v.integer().value), int_t{v.integer()}};
@ -518,8 +451,8 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
else if (std::get_if<failed_t>(&attr->second)) else if (std::get_if<failed_t>(&attr->second))
throw CachedEvalError(ref(shared_from_this()), name); throw CachedEvalError(ref(shared_from_this()), name);
else else
return std::make_shared<AttrCursor>(root, return std::make_shared<AttrCursor>(
std::make_pair(ref(shared_from_this()), name), nullptr, std::move(attr)); root, std::make_pair(ref(shared_from_this()), name), nullptr, std::move(attr));
} }
// Incomplete attrset, so need to fall thru and // Incomplete attrset, so need to fall thru and
// evaluate to see whether 'name' exists // evaluate to see whether 'name' exists
@ -618,17 +551,15 @@ string_t AttrCursor::getStringWithContext()
if (auto s = std::get_if<string_t>(&cachedValue->second)) { if (auto s = std::get_if<string_t>(&cachedValue->second)) {
bool valid = true; bool valid = true;
for (auto & c : s->second) { for (auto & c : s->second) {
const StorePath & path = std::visit(overloaded { const StorePath & path = std::visit(
[&](const NixStringContextElem::DrvDeep & d) -> const StorePath & { overloaded{
return d.drvPath; [&](const NixStringContextElem::DrvDeep & d) -> const StorePath & { return d.drvPath; },
},
[&](const NixStringContextElem::Built & b) -> const StorePath & { [&](const NixStringContextElem::Built & b) -> const StorePath & {
return b.drvPath->getBaseStorePath(); return b.drvPath->getBaseStorePath();
}, },
[&](const NixStringContextElem::Opaque & o) -> const StorePath & { [&](const NixStringContextElem::Opaque & o) -> const StorePath & { return o.path; },
return o.path;
}, },
}, c.raw); c.raw);
if (!root->state.store->isValidPath(path)) { if (!root->state.store->isValidPath(path)) {
valid = false; valid = false;
break; break;
@ -649,8 +580,7 @@ string_t AttrCursor::getStringWithContext()
NixStringContext context; NixStringContext context;
copyContext(v, context); copyContext(v, context);
return {v.c_str(), std::move(context)}; return {v.c_str(), std::move(context)};
} } else if (v.type() == nPath)
else if (v.type() == nPath)
return {v.path().to_string(), {}}; return {v.path().to_string(), {}};
else else
root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), showType(v)).debugThrow(); root->state.error<TypeError>("'%s' is not a string but %s", getAttrPathStr(), showType(v)).debugThrow();
@ -722,7 +652,8 @@ std::vector<std::string> AttrCursor::getListOfStrings()
std::vector<std::string> res; std::vector<std::string> res;
for (auto & elem : v.listItems()) for (auto & elem : v.listItems())
res.push_back(std::string(root->state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching"))); res.push_back(
std::string(root->state.forceStringNoCtx(*elem, noPos, "while evaluating an attribute for caching")));
if (root->db) if (root->db)
cachedValue = {root->db->setListOfStrings(getKey(), res), res}; cachedValue = {root->db->setListOfStrings(getKey(), res), res};
@ -778,8 +709,8 @@ StorePath AttrCursor::forceDerivation()
been garbage-collected. So force it to be regenerated. */ been garbage-collected. So force it to be regenerated. */
aDrvPath->forceValue(); aDrvPath->forceValue();
if (!root->state.store->isValidPath(drvPath)) if (!root->state.store->isValidPath(drvPath))
throw Error("don't know how to recreate store derivation '%s'!", throw Error(
root->state.store->printStorePath(drvPath)); "don't know how to recreate store derivation '%s'!", root->state.store->printStorePath(drvPath));
} }
return drvPath; return drvPath;
} }

View file

@ -44,7 +44,8 @@ EvalErrorBuilder<T> & EvalErrorBuilder<T>::withFrame(const Env & env, const Expr
// NOTE: This is abusing side-effects. // NOTE: This is abusing side-effects.
// TODO: check compatibility with nested debugger calls. // TODO: check compatibility with nested debugger calls.
// TODO: What side-effects?? // TODO: What side-effects??
error.state.debugTraces.push_front(DebugTrace{ error.state.debugTraces.push_front(
DebugTrace{
.pos = expr.getPos(), .pos = expr.getPos(),
.expr = expr, .expr = expr,
.env = env, .env = env,
@ -96,7 +97,8 @@ template<class T>
void EvalErrorBuilder<T>::panic() void EvalErrorBuilder<T>::panic()
{ {
logError(error.info()); logError(error.info());
printError("This is a bug! An unexpected condition occurred, causing the Nix evaluator to have to stop. If you could share a reproducible example or a core dump, please open an issue at https://github.com/NixOS/nix/issues"); printError(
"This is a bug! An unexpected condition occurred, causing the Nix evaluator to have to stop. If you could share a reproducible example or a core dump, please open an issue at https://github.com/NixOS/nix/issues");
abort(); abort();
} }

View file

@ -19,12 +19,14 @@ Strings EvalSettings::parseNixPath(const std::string & s)
auto start2 = p; auto start2 = p;
while (p != s.end() && *p != ':') { while (p != s.end() && *p != ':') {
if (*p == '=') start2 = p + 1; if (*p == '=')
start2 = p + 1;
++p; ++p;
} }
if (p == s.end()) { if (p == s.end()) {
if (p != start) res.push_back(std::string(start, p)); if (p != start)
res.push_back(std::string(start, p));
break; break;
} }
@ -32,10 +34,12 @@ Strings EvalSettings::parseNixPath(const std::string & s)
auto prefix = std::string(start2, s.end()); auto prefix = std::string(start2, s.end());
if (EvalSettings::isPseudoUrl(prefix) || hasPrefix(prefix, "flake:")) { if (EvalSettings::isPseudoUrl(prefix) || hasPrefix(prefix, "flake:")) {
++p; ++p;
while (p != s.end() && *p != ':') ++p; while (p != s.end() && *p != ':')
++p;
} }
res.push_back(std::string(start, p)); res.push_back(std::string(start, p));
if (p == s.end()) break; if (p == s.end())
break;
} }
++p; ++p;
@ -75,11 +79,14 @@ Strings EvalSettings::getDefaultNixPath()
bool EvalSettings::isPseudoUrl(std::string_view s) bool EvalSettings::isPseudoUrl(std::string_view s)
{ {
if (s.compare(0, 8, "channel:") == 0) return true; if (s.compare(0, 8, "channel:") == 0)
return true;
size_t pos = s.find("://"); size_t pos = s.find("://");
if (pos == std::string::npos) return false; if (pos == std::string::npos)
return false;
std::string scheme(s, 0, pos); std::string scheme(s, 0, pos);
return scheme == "http" || scheme == "https" || scheme == "file" || scheme == "channel" || scheme == "git" || scheme == "s3" || scheme == "ssh"; return scheme == "http" || scheme == "https" || scheme == "file" || scheme == "channel" || scheme == "git"
|| scheme == "s3" || scheme == "ssh";
} }
std::string EvalSettings::resolvePseudoUrl(std::string_view url) std::string EvalSettings::resolvePseudoUrl(std::string_view url)
@ -98,9 +105,7 @@ const std::string & EvalSettings::getCurrentSystem() const
Path getNixDefExpr() Path getNixDefExpr()
{ {
return settings.useXDGBaseDirectories return settings.useXDGBaseDirectories ? getStateDir() + "/defexpr" : getHome() + "/.nix-defexpr";
? getStateDir() + "/defexpr"
: getHome() + "/.nix-defexpr";
} }
} // namespace nix } // namespace nix

File diff suppressed because it is too large Load diff

View file

@ -7,18 +7,19 @@
#include <cstring> #include <cstring>
#include <regex> #include <regex>
namespace nix { namespace nix {
PackageInfo::PackageInfo(EvalState & state, std::string attrPath, const Bindings * attrs) PackageInfo::PackageInfo(EvalState & state, std::string attrPath, const Bindings * attrs)
: state(&state), attrs(attrs), attrPath(std::move(attrPath)) : state(&state)
, attrs(attrs)
, attrPath(std::move(attrPath))
{ {
} }
PackageInfo::PackageInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs) PackageInfo::PackageInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs)
: state(&state), attrs(nullptr), attrPath("") : state(&state)
, attrs(nullptr)
, attrPath("")
{ {
auto [drvPath, selectedOutputs] = parsePathWithOutputs(*store, drvPathWithOutputs); auto [drvPath, selectedOutputs] = parsePathWithOutputs(*store, drvPathWithOutputs);
@ -31,10 +32,7 @@ PackageInfo::PackageInfo(EvalState & state, ref<Store> store, const std::string
if (selectedOutputs.size() > 1) if (selectedOutputs.size() > 1)
throw Error("building more than one derivation output is not supported, in '%s'", drvPathWithOutputs); throw Error("building more than one derivation output is not supported, in '%s'", drvPathWithOutputs);
outputName = outputName = selectedOutputs.empty() ? getOr(drv.env, "outputName", "out") : *selectedOutputs.begin();
selectedOutputs.empty()
? getOr(drv.env, "outputName", "out")
: *selectedOutputs.begin();
auto i = drv.outputs.find(outputName); auto i = drv.outputs.find(outputName);
if (i == drv.outputs.end()) if (i == drv.outputs.end())
@ -44,34 +42,36 @@ PackageInfo::PackageInfo(EvalState & state, ref<Store> store, const std::string
outPath = {output.path(*store, drv.name, outputName)}; outPath = {output.path(*store, drv.name, outputName)};
} }
std::string PackageInfo::queryName() const std::string PackageInfo::queryName() const
{ {
if (name == "" && attrs) { if (name == "" && attrs) {
auto i = attrs->find(state->sName); auto i = attrs->find(state->sName);
if (i == attrs->end()) state->error<TypeError>("derivation name missing").debugThrow(); if (i == attrs->end())
state->error<TypeError>("derivation name missing").debugThrow();
name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation"); name = state->forceStringNoCtx(*i->value, noPos, "while evaluating the 'name' attribute of a derivation");
} }
return name; return name;
} }
std::string PackageInfo::querySystem() const std::string PackageInfo::querySystem() const
{ {
if (system == "" && attrs) { if (system == "" && attrs) {
auto i = attrs->find(state->sSystem); auto i = attrs->find(state->sSystem);
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation"); system =
i == attrs->end()
? "unknown"
: state->forceStringNoCtx(*i->value, i->pos, "while evaluating the 'system' attribute of a derivation");
} }
return system; return system;
} }
std::optional<StorePath> PackageInfo::queryDrvPath() const std::optional<StorePath> PackageInfo::queryDrvPath() const
{ {
if (!drvPath && attrs) { if (!drvPath && attrs) {
if (auto i = attrs->get(state->sDrvPath)) { if (auto i = attrs->get(state->sDrvPath)) {
NixStringContext context; NixStringContext context;
auto found = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation"); auto found = state->coerceToStorePath(
i->pos, *i->value, context, "while evaluating the 'drvPath' attribute of a derivation");
try { try {
found.requireDerivation(); found.requireDerivation();
} catch (Error & e) { } catch (Error & e) {
@ -85,7 +85,6 @@ std::optional<StorePath> PackageInfo::queryDrvPath() const
return drvPath.value_or(std::nullopt); return drvPath.value_or(std::nullopt);
} }
StorePath PackageInfo::requireDrvPath() const StorePath PackageInfo::requireDrvPath() const
{ {
if (auto drvPath = queryDrvPath()) if (auto drvPath = queryDrvPath())
@ -93,21 +92,20 @@ StorePath PackageInfo::requireDrvPath() const
throw Error("derivation does not contain a 'drvPath' attribute"); throw Error("derivation does not contain a 'drvPath' attribute");
} }
StorePath PackageInfo::queryOutPath() const StorePath PackageInfo::queryOutPath() const
{ {
if (!outPath && attrs) { if (!outPath && attrs) {
auto i = attrs->find(state->sOutPath); auto i = attrs->find(state->sOutPath);
NixStringContext context; NixStringContext context;
if (i != attrs->end()) if (i != attrs->end())
outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation"); outPath = state->coerceToStorePath(
i->pos, *i->value, context, "while evaluating the output path of a derivation");
} }
if (!outPath) if (!outPath)
throw UnimplementedError("CA derivations are not yet supported"); throw UnimplementedError("CA derivations are not yet supported");
return *outPath; return *outPath;
} }
PackageInfo::Outputs PackageInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall) PackageInfo::Outputs PackageInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall)
{ {
if (outputs.empty()) { if (outputs.empty()) {
@ -118,19 +116,25 @@ PackageInfo::Outputs PackageInfo::queryOutputs(bool withPaths, bool onlyOutputsT
/* For each output... */ /* For each output... */
for (auto elem : i->value->listItems()) { for (auto elem : i->value->listItems()) {
std::string output(state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation")); std::string output(
state->forceStringNoCtx(*elem, i->pos, "while evaluating the name of an output of a derivation"));
if (withPaths) { if (withPaths) {
/* Evaluate the corresponding set. */ /* Evaluate the corresponding set. */
auto out = attrs->get(state->symbols.create(output)); auto out = attrs->get(state->symbols.create(output));
if (!out) continue; // FIXME: throw error? if (!out)
continue; // FIXME: throw error?
state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation"); state->forceAttrs(*out->value, i->pos, "while evaluating an output of a derivation");
/* And evaluate its outPath attribute. */ /* And evaluate its outPath attribute. */
auto outPath = out->value->attrs()->get(state->sOutPath); auto outPath = out->value->attrs()->get(state->sOutPath);
if (!outPath) continue; // FIXME: throw error? if (!outPath)
continue; // FIXME: throw error?
NixStringContext context; NixStringContext context;
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation")); outputs.emplace(
output,
state->coerceToStorePath(
outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
} else } else
outputs.emplace(output, std::nullopt); outputs.emplace(output, std::nullopt);
} }
@ -142,7 +146,8 @@ PackageInfo::Outputs PackageInfo::queryOutputs(bool withPaths, bool onlyOutputsT
return outputs; return outputs;
const Attr * i; const Attr * i;
if (attrs && (i = attrs->get(state->sOutputSpecified)) && state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) { if (attrs && (i = attrs->get(state->sOutputSpecified))
&& state->forceBool(*i->value, i->pos, "while evaluating the 'outputSpecified' attribute of a derivation")) {
Outputs result; Outputs result;
auto out = outputs.find(queryOutputName()); auto out = outputs.find(queryOutputName());
if (out == outputs.end()) if (out == outputs.end())
@ -154,95 +159,103 @@ PackageInfo::Outputs PackageInfo::queryOutputs(bool withPaths, bool onlyOutputsT
else { else {
/* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */ /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */
const Value * outTI = queryMeta("outputsToInstall"); const Value * outTI = queryMeta("outputsToInstall");
if (!outTI) return outputs; if (!outTI)
return outputs;
auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'");
/* ^ this shows during `nix-env -i` right under the bad derivation */ /* ^ this shows during `nix-env -i` right under the bad derivation */
if (!outTI->isList()) throw errMsg; if (!outTI->isList())
throw errMsg;
Outputs result; Outputs result;
for (auto elem : outTI->listItems()) { for (auto elem : outTI->listItems()) {
if (elem->type() != nString) throw errMsg; if (elem->type() != nString)
throw errMsg;
auto out = outputs.find(elem->c_str()); auto out = outputs.find(elem->c_str());
if (out == outputs.end()) throw errMsg; if (out == outputs.end())
throw errMsg;
result.insert(*out); result.insert(*out);
} }
return result; return result;
} }
} }
std::string PackageInfo::queryOutputName() const std::string PackageInfo::queryOutputName() const
{ {
if (outputName == "" && attrs) { if (outputName == "" && attrs) {
auto i = attrs->get(state->sOutputName); auto i = attrs->get(state->sOutputName);
outputName = i ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : ""; outputName =
i ? state->forceStringNoCtx(*i->value, noPos, "while evaluating the output name of a derivation") : "";
} }
return outputName; return outputName;
} }
const Bindings * PackageInfo::getMeta() const Bindings * PackageInfo::getMeta()
{ {
if (meta) return meta; if (meta)
if (!attrs) return 0; return meta;
if (!attrs)
return 0;
auto a = attrs->get(state->sMeta); auto a = attrs->get(state->sMeta);
if (!a) return 0; if (!a)
return 0;
state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation"); state->forceAttrs(*a->value, a->pos, "while evaluating the 'meta' attribute of a derivation");
meta = a->value->attrs(); meta = a->value->attrs();
return meta; return meta;
} }
StringSet PackageInfo::queryMetaNames() StringSet PackageInfo::queryMetaNames()
{ {
StringSet res; StringSet res;
if (!getMeta()) return res; if (!getMeta())
return res;
for (auto & i : *meta) for (auto & i : *meta)
res.emplace(state->symbols[i.name]); res.emplace(state->symbols[i.name]);
return res; return res;
} }
bool PackageInfo::checkMeta(Value & v) bool PackageInfo::checkMeta(Value & v)
{ {
state->forceValue(v, v.determinePos(noPos)); state->forceValue(v, v.determinePos(noPos));
if (v.type() == nList) { if (v.type() == nList) {
for (auto elem : v.listItems()) for (auto elem : v.listItems())
if (!checkMeta(*elem)) return false; if (!checkMeta(*elem))
return false;
return true; return true;
} } else if (v.type() == nAttrs) {
else if (v.type() == nAttrs) { if (v.attrs()->get(state->sOutPath))
if (v.attrs()->get(state->sOutPath)) return false; return false;
for (auto & i : *v.attrs()) for (auto & i : *v.attrs())
if (!checkMeta(*i.value)) return false; if (!checkMeta(*i.value))
return false;
return true; return true;
} else
return v.type() == nInt || v.type() == nBool || v.type() == nString || v.type() == nFloat;
} }
else return v.type() == nInt || v.type() == nBool || v.type() == nString ||
v.type() == nFloat;
}
Value * PackageInfo::queryMeta(const std::string & name) Value * PackageInfo::queryMeta(const std::string & name)
{ {
if (!getMeta()) return 0; if (!getMeta())
return 0;
auto a = meta->get(state->symbols.create(name)); auto a = meta->get(state->symbols.create(name));
if (!a || !checkMeta(*a->value)) return 0; if (!a || !checkMeta(*a->value))
return 0;
return a->value; return a->value;
} }
std::string PackageInfo::queryMetaString(const std::string & name) std::string PackageInfo::queryMetaString(const std::string & name)
{ {
Value * v = queryMeta(name); Value * v = queryMeta(name);
if (!v || v->type() != nString) return ""; if (!v || v->type() != nString)
return "";
return v->c_str(); return v->c_str();
} }
NixInt PackageInfo::queryMetaInt(const std::string & name, NixInt def) NixInt PackageInfo::queryMetaInt(const std::string & name, NixInt def)
{ {
Value * v = queryMeta(name); Value * v = queryMeta(name);
if (!v) return def; if (!v)
if (v->type() == nInt) return v->integer(); return def;
if (v->type() == nInt)
return v->integer();
if (v->type() == nString) { if (v->type() == nString) {
/* Backwards compatibility with before we had support for /* Backwards compatibility with before we had support for
integer meta fields. */ integer meta fields. */
@ -255,8 +268,10 @@ NixInt PackageInfo::queryMetaInt(const std::string & name, NixInt def)
NixFloat PackageInfo::queryMetaFloat(const std::string & name, NixFloat def) NixFloat PackageInfo::queryMetaFloat(const std::string & name, NixFloat def)
{ {
Value * v = queryMeta(name); Value * v = queryMeta(name);
if (!v) return def; if (!v)
if (v->type() == nFloat) return v->fpoint(); return def;
if (v->type() == nFloat)
return v->fpoint();
if (v->type() == nString) { if (v->type() == nString) {
/* Backwards compatibility with before we had support for /* Backwards compatibility with before we had support for
float meta fields. */ float meta fields. */
@ -266,22 +281,24 @@ NixFloat PackageInfo::queryMetaFloat(const std::string & name, NixFloat def)
return def; return def;
} }
bool PackageInfo::queryMetaBool(const std::string & name, bool def) bool PackageInfo::queryMetaBool(const std::string & name, bool def)
{ {
Value * v = queryMeta(name); Value * v = queryMeta(name);
if (!v) return def; if (!v)
if (v->type() == nBool) return v->boolean(); return def;
if (v->type() == nBool)
return v->boolean();
if (v->type() == nString) { if (v->type() == nString) {
/* Backwards compatibility with before we had support for /* Backwards compatibility with before we had support for
Boolean meta fields. */ Boolean meta fields. */
if (v->string_view() == "true") return true; if (v->string_view() == "true")
if (v->string_view() == "false") return false; return true;
if (v->string_view() == "false")
return false;
} }
return def; return def;
} }
void PackageInfo::setMeta(const std::string & name, Value * v) void PackageInfo::setMeta(const std::string & name, Value * v)
{ {
getMeta(); getMeta();
@ -291,30 +308,35 @@ void PackageInfo::setMeta(const std::string & name, Value * v)
for (auto i : *meta) for (auto i : *meta)
if (i.name != sym) if (i.name != sym)
attrs.insert(i); attrs.insert(i);
if (v) attrs.insert(sym, v); if (v)
attrs.insert(sym, v);
meta = attrs.finish(); meta = attrs.finish();
} }
/* Cache for already considered attrsets. */ /* Cache for already considered attrsets. */
typedef std::set<const Bindings *> Done; typedef std::set<const Bindings *> Done;
/* Evaluate value `v'. If it evaluates to a set of type `derivation', /* Evaluate value `v'. If it evaluates to a set of type `derivation',
then put information about it in `drvs' (unless it's already in `done'). then put information about it in `drvs' (unless it's already in `done').
The result boolean indicates whether it makes sense The result boolean indicates whether it makes sense
for the caller to recursively search for derivations in `v'. */ for the caller to recursively search for derivations in `v'. */
static bool getDerivation(EvalState & state, Value & v, static bool getDerivation(
const std::string & attrPath, PackageInfos & drvs, Done & done, EvalState & state,
Value & v,
const std::string & attrPath,
PackageInfos & drvs,
Done & done,
bool ignoreAssertionFailures) bool ignoreAssertionFailures)
{ {
try { try {
state.forceValue(v, v.determinePos(noPos)); state.forceValue(v, v.determinePos(noPos));
if (!state.isDerivation(v)) return true; if (!state.isDerivation(v))
return true;
/* Remove spurious duplicates (e.g., a set like `rec { x = /* Remove spurious duplicates (e.g., a set like `rec { x =
derivation {...}; y = x;}'. */ derivation {...}; y = x;}'. */
if (!done.insert(v.attrs()).second) return false; if (!done.insert(v.attrs()).second)
return false;
PackageInfo drv(state, attrPath, v.attrs()); PackageInfo drv(state, attrPath, v.attrs());
@ -325,42 +347,44 @@ static bool getDerivation(EvalState & state, Value & v,
return false; return false;
} catch (AssertionError & e) { } catch (AssertionError & e) {
if (ignoreAssertionFailures) return false; if (ignoreAssertionFailures)
return false;
throw; throw;
} }
} }
std::optional<PackageInfo> getDerivation(EvalState & state, Value & v, bool ignoreAssertionFailures)
std::optional<PackageInfo> getDerivation(EvalState & state, Value & v,
bool ignoreAssertionFailures)
{ {
Done done; Done done;
PackageInfos drvs; PackageInfos drvs;
getDerivation(state, v, "", drvs, done, ignoreAssertionFailures); getDerivation(state, v, "", drvs, done, ignoreAssertionFailures);
if (drvs.size() != 1) return {}; if (drvs.size() != 1)
return {};
return std::move(drvs.front()); return std::move(drvs.front());
} }
static std::string addToPath(const std::string & s1, std::string_view s2) static std::string addToPath(const std::string & s1, std::string_view s2)
{ {
return s1.empty() ? std::string(s2) : s1 + "." + s2; return s1.empty() ? std::string(s2) : s1 + "." + s2;
} }
static std::regex attrRegex("[A-Za-z_][A-Za-z0-9-_+]*"); static std::regex attrRegex("[A-Za-z_][A-Za-z0-9-_+]*");
static void getDerivations(
static void getDerivations(EvalState & state, Value & vIn, EvalState & state,
const std::string & pathPrefix, Bindings & autoArgs, Value & vIn,
PackageInfos & drvs, Done & done, const std::string & pathPrefix,
Bindings & autoArgs,
PackageInfos & drvs,
Done & done,
bool ignoreAssertionFailures) bool ignoreAssertionFailures)
{ {
Value v; Value v;
state.autoCallFunction(autoArgs, vIn, v); state.autoCallFunction(autoArgs, vIn, v);
/* Process the expression. */ /* Process the expression. */
if (!getDerivation(state, v, pathPrefix, drvs, done, ignoreAssertionFailures)) ; if (!getDerivation(state, v, pathPrefix, drvs, done, ignoreAssertionFailures))
;
else if (v.type() == nAttrs) { else if (v.type() == nAttrs) {
@ -388,8 +412,11 @@ static void getDerivations(EvalState & state, Value & vIn,
`recurseForDerivations = true' attribute. */ `recurseForDerivations = true' attribute. */
if (i->value->type() == nAttrs) { if (i->value->type() == nAttrs) {
auto j = i->value->attrs()->get(state.sRecurseForDerivations); auto j = i->value->attrs()->get(state.sRecurseForDerivations);
if (j && state.forceBool(*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`")) if (j
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures); && state.forceBool(
*j->value, j->pos, "while evaluating the attribute `recurseForDerivations`"))
getDerivations(
state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
} }
} }
} catch (Error & e) { } catch (Error & e) {
@ -411,13 +438,16 @@ static void getDerivations(EvalState & state, Value & vIn,
state.error<TypeError>("expression does not evaluate to a derivation (or a set or list of those)").debugThrow(); state.error<TypeError>("expression does not evaluate to a derivation (or a set or list of those)").debugThrow();
} }
void getDerivations(
void getDerivations(EvalState & state, Value & v, const std::string & pathPrefix, EvalState & state,
Bindings & autoArgs, PackageInfos & drvs, bool ignoreAssertionFailures) Value & v,
const std::string & pathPrefix,
Bindings & autoArgs,
PackageInfos & drvs,
bool ignoreAssertionFailures)
{ {
Done done; Done done;
getDerivations(state, v, pathPrefix, autoArgs, drvs, done, ignoreAssertionFailures); getDerivations(state, v, pathPrefix, autoArgs, drvs, done, ignoreAssertionFailures);
} }
} }

View file

@ -11,11 +11,8 @@ namespace nix {
MakeError(AttrPathNotFound, Error); MakeError(AttrPathNotFound, Error);
MakeError(NoPositionInfo, Error); MakeError(NoPositionInfo, Error);
std::pair<Value *, PosIdx> findAlongAttrPath( std::pair<Value *, PosIdx>
EvalState & state, findAlongAttrPath(EvalState & state, const std::string & attrPath, Bindings & autoArgs, Value & vIn);
const std::string & attrPath,
Bindings & autoArgs,
Value & vIn);
/** /**
* Heuristic to find the filename and lineno or a nix value. * Heuristic to find the filename and lineno or a nix value.

View file

@ -8,7 +8,6 @@
namespace nix { namespace nix {
class EvalState; class EvalState;
struct Value; struct Value;
@ -25,7 +24,9 @@ struct Attr
PosIdx pos; PosIdx pos;
Value * value; Value * value;
Attr(Symbol name, Value * value, PosIdx pos = noPos) Attr(Symbol name, Value * value, PosIdx pos = noPos)
: name(name), pos(pos), value(value) { }; : name(name)
, pos(pos)
, value(value) {};
Attr() {}; Attr() {};
auto operator<=>(const Attr & a) const auto operator<=>(const Attr & a) const
{ {
@ -33,7 +34,8 @@ struct Attr
} }
}; };
static_assert(sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *), static_assert(
sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *),
"performance of the evaluator is highly sensitive to the size of Attr. " "performance of the evaluator is highly sensitive to the size of Attr. "
"avoid introducing any padding into Attr if at all possible, and do not " "avoid introducing any padding into Attr if at all possible, and do not "
"introduce new fields that need not be present for almost every instance."); "introduce new fields that need not be present for almost every instance.");
@ -54,13 +56,23 @@ private:
size_t size_, capacity_; size_t size_, capacity_;
Attr attrs[0]; Attr attrs[0];
Bindings(size_t capacity) : size_(0), capacity_(capacity) { } Bindings(size_t capacity)
: size_(0)
, capacity_(capacity)
{
}
Bindings(const Bindings & bindings) = delete; Bindings(const Bindings & bindings) = delete;
public: public:
size_t size() const { return size_; } size_t size() const
{
return size_;
}
bool empty() const { return !size_; } bool empty() const
{
return !size_;
}
typedef Attr * iterator; typedef Attr * iterator;
@ -76,7 +88,8 @@ public:
{ {
Attr key(name, 0); Attr key(name, 0);
const_iterator i = std::lower_bound(begin(), end(), key); const_iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return i; if (i != end() && i->name == name)
return i;
return end(); return end();
} }
@ -84,15 +97,28 @@ public:
{ {
Attr key(name, 0); Attr key(name, 0);
const_iterator i = std::lower_bound(begin(), end(), key); const_iterator i = std::lower_bound(begin(), end(), key);
if (i != end() && i->name == name) return &*i; if (i != end() && i->name == name)
return &*i;
return nullptr; return nullptr;
} }
iterator begin() { return &attrs[0]; } iterator begin()
iterator end() { return &attrs[size_]; } {
return &attrs[0];
}
iterator end()
{
return &attrs[size_];
}
const_iterator begin() const { return &attrs[0]; } const_iterator begin() const
const_iterator end() const { return &attrs[size_]; } {
return &attrs[0];
}
const_iterator end() const
{
return &attrs[size_];
}
Attr & operator[](size_t pos) Attr & operator[](size_t pos)
{ {
@ -106,7 +132,10 @@ public:
void sort(); void sort();
size_t capacity() const { return capacity_; } size_t capacity() const
{
return capacity_;
}
/** /**
* Returns the attributes in lexicographically sorted order. * Returns the attributes in lexicographically sorted order.
@ -143,8 +172,10 @@ public:
EvalState & state; EvalState & state;
BindingsBuilder(EvalState & state, Bindings * bindings) BindingsBuilder(EvalState & state, Bindings * bindings)
: bindings(bindings), state(state) : bindings(bindings)
{ } , state(state)
{
}
void insert(Symbol name, Value * value, PosIdx pos = noPos) void insert(Symbol name, Value * value, PosIdx pos = noPos)
{ {

View file

@ -43,10 +43,7 @@ class EvalCache : public std::enable_shared_from_this<EvalCache>
public: public:
EvalCache( EvalCache(std::optional<std::reference_wrapper<const Hash>> useCache, EvalState & state, RootLoader rootLoader);
std::optional<std::reference_wrapper<const Hash>> useCache,
EvalState & state,
RootLoader rootLoader);
ref<AttrCursor> getRoot(); ref<AttrCursor> getRoot();
}; };
@ -63,11 +60,18 @@ enum AttrType {
Int = 8, Int = 8,
}; };
struct placeholder_t {}; struct placeholder_t
struct missing_t {}; {};
struct misc_t {}; struct missing_t
struct failed_t {}; {};
struct int_t { NixInt x; }; struct misc_t
{};
struct failed_t
{};
struct int_t
{
NixInt x;
};
typedef uint64_t AttrId; typedef uint64_t AttrId;
typedef std::pair<AttrId, Symbol> AttrKey; typedef std::pair<AttrId, Symbol> AttrKey;
typedef std::pair<std::string, NixStringContext> string_t; typedef std::pair<std::string, NixStringContext> string_t;
@ -81,8 +85,8 @@ typedef std::variant<
failed_t, failed_t,
bool, bool,
int_t, int_t,
std::vector<std::string> std::vector<std::string>>
> AttrValue; AttrValue;
class AttrCursor : public std::enable_shared_from_this<AttrCursor> class AttrCursor : public std::enable_shared_from_this<AttrCursor>
{ {

View file

@ -23,11 +23,11 @@ inline void * allocBytes(size_t n)
#else #else
p = calloc(n, 1); p = calloc(n, 1);
#endif #endif
if (!p) throw std::bad_alloc(); if (!p)
throw std::bad_alloc();
return p; return p;
} }
[[gnu::always_inline]] [[gnu::always_inline]]
Value * EvalState::allocValue() Value * EvalState::allocValue()
{ {
@ -38,7 +38,8 @@ Value * EvalState::allocValue()
have to explicitly clear the first word of every object we take. */ have to explicitly clear the first word of every object we take. */
if (!*valueAllocCache) { if (!*valueAllocCache) {
*valueAllocCache = GC_malloc_many(sizeof(Value)); *valueAllocCache = GC_malloc_many(sizeof(Value));
if (!*valueAllocCache) throw std::bad_alloc(); if (!*valueAllocCache)
throw std::bad_alloc();
} }
/* GC_NEXT is a convenience macro for accessing the first word of an object. /* GC_NEXT is a convenience macro for accessing the first word of an object.
@ -54,7 +55,6 @@ Value * EvalState::allocValue()
return (Value *) p; return (Value *) p;
} }
[[gnu::always_inline]] [[gnu::always_inline]]
Env & EvalState::allocEnv(size_t size) Env & EvalState::allocEnv(size_t size)
{ {
@ -68,7 +68,8 @@ Env & EvalState::allocEnv(size_t size)
/* see allocValue for explanations. */ /* see allocValue for explanations. */
if (!*env1AllocCache) { if (!*env1AllocCache) {
*env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *)); *env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *));
if (!*env1AllocCache) throw std::bad_alloc(); if (!*env1AllocCache)
throw std::bad_alloc();
} }
void * p = *env1AllocCache; void * p = *env1AllocCache;
@ -84,7 +85,6 @@ Env & EvalState::allocEnv(size_t size)
return *env; return *env;
} }
[[gnu::always_inline]] [[gnu::always_inline]]
void EvalState::forceValue(Value & v, const PosIdx pos) void EvalState::forceValue(Value & v, const PosIdx pos)
{ {
@ -104,19 +104,16 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
tryFixupBlackHolePos(v, pos); tryFixupBlackHolePos(v, pos);
throw; throw;
} }
} } else if (v.isApp())
else if (v.isApp())
callFunction(*v.payload.app.left, *v.payload.app.right, v, pos); callFunction(*v.payload.app.left, *v.payload.app.right, v, pos);
} }
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx) inline void EvalState::forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceAttrs(v, [&]() { return pos; }, errorCtx); forceAttrs(v, [&]() { return pos; }, errorCtx);
} }
template<typename Callable> template<typename Callable>
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx) inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view errorCtx)
@ -124,30 +121,26 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos, std::string_view e
PosIdx pos = getPos(); PosIdx pos = getPos();
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nAttrs) { if (v.type() != nAttrs) {
error<TypeError>( error<TypeError>("expected a set but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions))
"expected a set but found %1%: %2%", .withTrace(pos, errorCtx)
showType(v), .debugThrow();
ValuePrinter(*this, v, errorPrintOptions)
).withTrace(pos, errorCtx).debugThrow();
} }
} }
[[gnu::always_inline]] [[gnu::always_inline]]
inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx) inline void EvalState::forceList(Value & v, const PosIdx pos, std::string_view errorCtx)
{ {
forceValue(v, pos); forceValue(v, pos);
if (!v.isList()) { if (!v.isList()) {
error<TypeError>( error<TypeError>("expected a list but found %1%: %2%", showType(v), ValuePrinter(*this, v, errorPrintOptions))
"expected a list but found %1%: %2%", .withTrace(pos, errorCtx)
showType(v), .debugThrow();
ValuePrinter(*this, v, errorPrintOptions)
).withTrace(pos, errorCtx).debugThrow();
} }
} }
[[gnu::always_inline]] [[gnu::always_inline]]
inline CallDepth EvalState::addCallDepth(const PosIdx pos) { inline CallDepth EvalState::addCallDepth(const PosIdx pos)
{
if (callDepth > settings.maxCallDepth) if (callDepth > settings.maxCallDepth)
error<EvalBaseError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow(); error<EvalBaseError>("stack overflow; max-call-depth exceeded").atPos(pos).debugThrow();

View file

@ -73,7 +73,9 @@ struct EvalSettings : Config
)"}; )"};
Setting<Strings> nixPath{ Setting<Strings> nixPath{
this, {}, "nix-path", this,
{},
"nix-path",
R"( R"(
List of search paths to use for [lookup path](@docroot@/language/constructs/lookup-path.md) resolution. List of search paths to use for [lookup path](@docroot@/language/constructs/lookup-path.md) resolution.
This setting determines the value of This setting determines the value of
@ -106,10 +108,14 @@ struct EvalSettings : Config
> If [restricted evaluation](@docroot@/command-ref/conf-file.md#conf-restrict-eval) is enabled, the default value is empty. > If [restricted evaluation](@docroot@/command-ref/conf-file.md#conf-restrict-eval) is enabled, the default value is empty.
> >
> If [pure evaluation](#conf-pure-eval) is enabled, `builtins.nixPath` *always* evaluates to the empty list `[ ]`. > If [pure evaluation](#conf-pure-eval) is enabled, `builtins.nixPath` *always* evaluates to the empty list `[ ]`.
)", {}, false}; )",
{},
false};
Setting<std::string> currentSystem{ Setting<std::string> currentSystem{
this, "", "eval-system", this,
"",
"eval-system",
R"( R"(
This option defines This option defines
[`builtins.currentSystem`](@docroot@/language/builtins.md#builtins-currentSystem) [`builtins.currentSystem`](@docroot@/language/builtins.md#builtins-currentSystem)
@ -129,7 +135,9 @@ struct EvalSettings : Config
const std::string & getCurrentSystem() const; const std::string & getCurrentSystem() const;
Setting<bool> restrictEval{ Setting<bool> restrictEval{
this, false, "restrict-eval", this,
false,
"restrict-eval",
R"( R"(
If set to `true`, the Nix evaluator will not allow access to any If set to `true`, the Nix evaluator will not allow access to any
files outside of files outside of
@ -138,7 +146,10 @@ struct EvalSettings : Config
[`allowed-uris`](@docroot@/command-ref/conf-file.md#conf-allowed-uris). [`allowed-uris`](@docroot@/command-ref/conf-file.md#conf-allowed-uris).
)"}; )"};
Setting<bool> pureEval{this, false, "pure-eval", Setting<bool> pureEval{
this,
false,
"pure-eval",
R"( R"(
Pure evaluation mode ensures that the result of Nix expressions is fully determined by explicitly declared inputs, and not influenced by external state: Pure evaluation mode ensures that the result of Nix expressions is fully determined by explicitly declared inputs, and not influenced by external state:
@ -148,11 +159,12 @@ struct EvalSettings : Config
- [`builtins.currentTime`](@docroot@/language/builtins.md#builtins-currentTime) - [`builtins.currentTime`](@docroot@/language/builtins.md#builtins-currentTime)
- [`builtins.nixPath`](@docroot@/language/builtins.md#builtins-nixPath) - [`builtins.nixPath`](@docroot@/language/builtins.md#builtins-nixPath)
- [`builtins.storePath`](@docroot@/language/builtins.md#builtins-storePath) - [`builtins.storePath`](@docroot@/language/builtins.md#builtins-storePath)
)" )"};
};
Setting<bool> enableImportFromDerivation{ Setting<bool> enableImportFromDerivation{
this, true, "allow-import-from-derivation", this,
true,
"allow-import-from-derivation",
R"( R"(
By default, Nix allows [Import from Derivation](@docroot@/language/import-from-derivation.md). By default, Nix allows [Import from Derivation](@docroot@/language/import-from-derivation.md).
@ -162,7 +174,10 @@ struct EvalSettings : Config
regardless of the state of the store. regardless of the state of the store.
)"}; )"};
Setting<Strings> allowedUris{this, {}, "allowed-uris", Setting<Strings> allowedUris{
this,
{},
"allowed-uris",
R"( R"(
A list of URI prefixes to which access is allowed in restricted A list of URI prefixes to which access is allowed in restricted
evaluation mode. For example, when set to evaluation mode. For example, when set to
@ -175,7 +190,10 @@ struct EvalSettings : Config
- or the prefix is a URI scheme ended by a colon `:` and the URI has the same scheme. - or the prefix is a URI scheme ended by a colon `:` and the URI has the same scheme.
)"}; )"};
Setting<bool> traceFunctionCalls{this, false, "trace-function-calls", Setting<bool> traceFunctionCalls{
this,
false,
"trace-function-calls",
R"( R"(
If set to `true`, the Nix evaluator will trace every function call. If set to `true`, the Nix evaluator will trace every function call.
Nix will print a log message at the "vomit" level for every function Nix will print a log message at the "vomit" level for every function
@ -193,26 +211,38 @@ struct EvalSettings : Config
`flamegraph.pl`. `flamegraph.pl`.
)"}; )"};
Setting<bool> useEvalCache{this, true, "eval-cache", Setting<bool> useEvalCache{
this,
true,
"eval-cache",
R"( R"(
Whether to use the flake evaluation cache. Whether to use the flake evaluation cache.
Certain commands won't have to evaluate when invoked for the second time with a particular version of a flake. Certain commands won't have to evaluate when invoked for the second time with a particular version of a flake.
Intermediate results are not cached. Intermediate results are not cached.
)"}; )"};
Setting<bool> ignoreExceptionsDuringTry{this, false, "ignore-try", Setting<bool> ignoreExceptionsDuringTry{
this,
false,
"ignore-try",
R"( R"(
If set to true, ignore exceptions inside 'tryEval' calls when evaluating nix expressions in If set to true, ignore exceptions inside 'tryEval' calls when evaluating nix expressions in
debug mode (using the --debugger flag). By default the debugger will pause on all exceptions. debug mode (using the --debugger flag). By default the debugger will pause on all exceptions.
)"}; )"};
Setting<bool> traceVerbose{this, false, "trace-verbose", Setting<bool> traceVerbose{
this,
false,
"trace-verbose",
"Whether `builtins.traceVerbose` should trace its first argument when evaluated."}; "Whether `builtins.traceVerbose` should trace its first argument when evaluated."};
Setting<unsigned int> maxCallDepth{this, 10000, "max-call-depth", Setting<unsigned int> maxCallDepth{
"The maximum function call depth to allow before erroring."}; this, 10000, "max-call-depth", "The maximum function call depth to allow before erroring."};
Setting<bool> builtinsTraceDebugger{this, false, "debugger-on-trace", Setting<bool> builtinsTraceDebugger{
this,
false,
"debugger-on-trace",
R"( R"(
If set to true and the `--debugger` flag is given, the following functions If set to true and the `--debugger` flag is given, the following functions
will enter the debugger like [`builtins.break`](@docroot@/language/builtins.md#builtins-break). will enter the debugger like [`builtins.break`](@docroot@/language/builtins.md#builtins-break).
@ -225,7 +255,10 @@ struct EvalSettings : Config
This is useful for debugging warnings in third-party Nix code. This is useful for debugging warnings in third-party Nix code.
)"}; )"};
Setting<bool> builtinsDebuggerOnWarn{this, false, "debugger-on-warn", Setting<bool> builtinsDebuggerOnWarn{
this,
false,
"debugger-on-warn",
R"( R"(
If set to true and the `--debugger` flag is given, [`builtins.warn`](@docroot@/language/builtins.md#builtins-warn) If set to true and the `--debugger` flag is given, [`builtins.warn`](@docroot@/language/builtins.md#builtins-warn)
will enter the debugger like [`builtins.break`](@docroot@/language/builtins.md#builtins-break). will enter the debugger like [`builtins.break`](@docroot@/language/builtins.md#builtins-break).
@ -235,7 +268,10 @@ struct EvalSettings : Config
Use [`debugger-on-trace`](#conf-debugger-on-trace) to also enter the debugger on legacy warnings that are logged with [`builtins.trace`](@docroot@/language/builtins.md#builtins-trace). Use [`debugger-on-trace`](#conf-debugger-on-trace) to also enter the debugger on legacy warnings that are logged with [`builtins.trace`](@docroot@/language/builtins.md#builtins-trace).
)"}; )"};
Setting<bool> builtinsAbortOnWarn{this, false, "abort-on-warn", Setting<bool> builtinsAbortOnWarn{
this,
false,
"abort-on-warn",
R"( R"(
If set to true, [`builtins.warn`](@docroot@/language/builtins.md#builtins-warn) will throw an error when logging a warning. If set to true, [`builtins.warn`](@docroot@/language/builtins.md#builtins-warn) will throw an error when logging a warning.

View file

@ -51,14 +51,18 @@ namespace eval_cache {
/** /**
* Increments a count on construction and decrements on destruction. * Increments a count on construction and decrements on destruction.
*/ */
class CallDepth { class CallDepth
{
size_t & count; size_t & count;
public: public:
CallDepth(size_t & count) : count(count) { CallDepth(size_t & count)
: count(count)
{
++count; ++count;
} }
~CallDepth() { ~CallDepth()
{
--count; --count;
} }
}; };
@ -151,7 +155,9 @@ struct Constant
bool impureOnly = false; bool impureOnly = false;
}; };
typedef std::map<std::string, Value *, std::less<std::string>, traceable_allocator<std::pair<const std::string, Value *> > > ValMap; typedef std::
map<std::string, Value *, std::less<std::string>, traceable_allocator<std::pair<const std::string, Value *>>>
ValMap;
typedef std::unordered_map<PosIdx, DocComment> DocCommentMap; typedef std::unordered_map<PosIdx, DocComment> DocCommentMap;
@ -166,18 +172,20 @@ void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env &
std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env); std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env);
void copyContext(const Value & v, NixStringContext & context, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); void copyContext(
const Value & v,
NixStringContext & context,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
std::string printValue(EvalState & state, Value & v); std::string printValue(EvalState & state, Value & v);
std::ostream & operator<<(std::ostream & os, const ValueType t); std::ostream & operator<<(std::ostream & os, const ValueType t);
struct RegexCache; struct RegexCache;
std::shared_ptr<RegexCache> makeRegexCache(); std::shared_ptr<RegexCache> makeRegexCache();
struct DebugTrace { struct DebugTrace
{
/* WARNING: Converting PosIdx -> Pos should be done with extra care. This is /* WARNING: Converting PosIdx -> Pos should be done with extra care. This is
due to the fact that operator[] of PosTable is incredibly expensive. */ due to the fact that operator[] of PosTable is incredibly expensive. */
std::variant<Pos, PosIdx> pos; std::variant<Pos, PosIdx> pos;
@ -210,19 +218,11 @@ public:
SymbolTable symbols; SymbolTable symbols;
PosTable positions; PosTable positions;
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue, sSystem, sOverrides, sOutputs, sOutputName,
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls, sIgnoreNulls, sFile, sLine, sColumn, sFunctor, sToString, sRight, sWrong, sStructuredAttrs, sAllowedReferences,
sFile, sLine, sColumn, sFunctor, sToString, sAllowedRequisites, sDisallowedReferences, sDisallowedRequisites, sMaxSize, sMaxClosureSize, sBuilder, sArgs,
sRight, sWrong, sStructuredAttrs, sContentAddressed, sImpure, sOutputHash, sOutputHashAlgo, sOutputHashMode, sRecurseForDerivations, sDescription,
sAllowedReferences, sAllowedRequisites, sDisallowedReferences, sDisallowedRequisites, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath, sPrefix, sOutputSpecified;
sMaxSize, sMaxClosureSize,
sBuilder, sArgs,
sContentAddressed, sImpure,
sOutputHash, sOutputHashAlgo, sOutputHashMode,
sRecurseForDerivations,
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
sPrefix,
sOutputSpecified;
const Expr::AstSymbols exprSymbols; const Expr::AstSymbols exprSymbols;
@ -321,7 +321,8 @@ public:
if (i != exprEnvs.end()) if (i != exprEnvs.end())
return i->second; return i->second;
else else
return std::shared_ptr<const StaticEnv>();; return std::shared_ptr<const StaticEnv>();
;
} }
/** Whether a debug repl can be started. If `false`, `runDebugRepl(error)` will return without starting a repl. */ /** Whether a debug repl can be started. If `false`, `runDebugRepl(error)` will return without starting a repl. */
@ -340,7 +341,8 @@ public:
template<class T, typename... Args> template<class T, typename... Args>
[[nodiscard, gnu::noinline]] [[nodiscard, gnu::noinline]]
EvalErrorBuilder<T> & error(const Args & ... args) { EvalErrorBuilder<T> & error(const Args &... args)
{
// `EvalErrorBuilder::debugThrow` performs the corresponding `delete`. // `EvalErrorBuilder::debugThrow` performs the corresponding `delete`.
return *new EvalErrorBuilder<T>(*this, args...); return *new EvalErrorBuilder<T>(*this, args...);
} }
@ -359,13 +361,25 @@ private:
/** /**
* A cache from path names to parse trees. * A cache from path names to parse trees.
*/ */
typedef std::unordered_map<SourcePath, Expr *, std::hash<SourcePath>, std::equal_to<SourcePath>, traceable_allocator<std::pair<const SourcePath, Expr *>>> FileParseCache; typedef std::unordered_map<
SourcePath,
Expr *,
std::hash<SourcePath>,
std::equal_to<SourcePath>,
traceable_allocator<std::pair<const SourcePath, Expr *>>>
FileParseCache;
FileParseCache fileParseCache; FileParseCache fileParseCache;
/** /**
* A cache from path names to values. * A cache from path names to values.
*/ */
typedef std::unordered_map<SourcePath, Value, std::hash<SourcePath>, std::equal_to<SourcePath>, traceable_allocator<std::pair<const SourcePath, Value>>> FileEvalCache; typedef std::unordered_map<
SourcePath,
Value,
std::hash<SourcePath>,
std::equal_to<SourcePath>,
traceable_allocator<std::pair<const SourcePath, Value>>>
FileEvalCache;
FileEvalCache fileEvalCache; FileEvalCache fileEvalCache;
/** /**
@ -405,7 +419,10 @@ public:
std::shared_ptr<Store> buildStore = nullptr); std::shared_ptr<Store> buildStore = nullptr);
~EvalState(); ~EvalState();
LookupPath getLookupPath() { return lookupPath; } LookupPath getLookupPath()
{
return lookupPath;
}
/** /**
* Return a `SourcePath` that refers to `path` in the root * Return a `SourcePath` that refers to `path` in the root
@ -486,9 +503,7 @@ public:
* *
* If it is not found, return `std::nullopt`. * If it is not found, return `std::nullopt`.
*/ */
std::optional<SourcePath> resolveLookupPathPath( std::optional<SourcePath> resolveLookupPathPath(const LookupPath::Path & elem, bool initAccessControl = false);
const LookupPath::Path & elem,
bool initAccessControl = false);
/** /**
* Evaluate an expression to normal form * Evaluate an expression to normal form
@ -539,7 +554,12 @@ public:
*/ */
void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx); void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx);
std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx); std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx);
std::string_view forceString(Value & v, NixStringContext & context, const PosIdx pos, std::string_view errorCtx, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); std::string_view forceString(
Value & v,
NixStringContext & context,
const PosIdx pos,
std::string_view errorCtx,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx); std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
template<typename... Args> template<typename... Args>
@ -556,8 +576,8 @@ public:
*/ */
bool isDerivation(Value & v); bool isDerivation(Value & v);
std::optional<std::string> tryAttrsToString(const PosIdx pos, Value & v, std::optional<std::string> tryAttrsToString(
NixStringContext & context, bool coerceMore = false, bool copyToStore = true); const PosIdx pos, Value & v, NixStringContext & context, bool coerceMore = false, bool copyToStore = true);
/** /**
* String coercion. * String coercion.
@ -567,9 +587,13 @@ public:
* booleans and lists to a string. If `copyToStore` is set, * booleans and lists to a string. If `copyToStore` is set,
* referenced paths are copied to the Nix store as a side effect. * referenced paths are copied to the Nix store as a side effect.
*/ */
BackedStringView coerceToString(const PosIdx pos, Value & v, NixStringContext & context, BackedStringView coerceToString(
const PosIdx pos,
Value & v,
NixStringContext & context,
std::string_view errorCtx, std::string_view errorCtx,
bool coerceMore = false, bool copyToStore = true, bool coerceMore = false,
bool copyToStore = true,
bool canonicalizePath = true); bool canonicalizePath = true);
StorePath copyPathToStore(NixStringContext & context, const SourcePath & path); StorePath copyPathToStore(NixStringContext & context, const SourcePath & path);
@ -591,7 +615,11 @@ public:
/** /**
* Part of `coerceToSingleDerivedPath()` without any store IO which is exposed for unit testing only. * Part of `coerceToSingleDerivedPath()` without any store IO which is exposed for unit testing only.
*/ */
std::pair<SingleDerivedPath, std::string_view> coerceToSingleDerivedPathUnchecked(const PosIdx pos, Value & v, std::string_view errorCtx, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings); std::pair<SingleDerivedPath, std::string_view> coerceToSingleDerivedPathUnchecked(
const PosIdx pos,
Value & v,
std::string_view errorCtx,
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
/** /**
* Coerce to `SingleDerivedPath`. * Coerce to `SingleDerivedPath`.
@ -631,7 +659,13 @@ public:
/** /**
* Internal primops not exposed to the user. * Internal primops not exposed to the user.
*/ */
std::unordered_map<std::string, Value *, std::hash<std::string>, std::equal_to<std::string>, traceable_allocator<std::pair<const std::string, Value *>>> internalPrimOps; std::unordered_map<
std::string,
Value *,
std::hash<std::string>,
std::equal_to<std::string>,
traceable_allocator<std::pair<const std::string, Value *>>>
internalPrimOps;
/** /**
* Name and documentation about every constant. * Name and documentation about every constant.
@ -705,7 +739,8 @@ private:
std::shared_ptr<StaticEnv> & staticEnv); std::shared_ptr<StaticEnv> & staticEnv);
/** /**
* Current Nix call stack depth, used with `max-call-depth` setting to throw stack overflow hopefully before we run out of system stack. * Current Nix call stack depth, used with `max-call-depth` setting to throw stack overflow hopefully before we run
* out of system stack.
*/ */
size_t callDepth = 0; size_t callDepth = 0;
@ -812,9 +847,7 @@ public:
* *
* A combination of `mkStorePathString` and `mkOutputString`. * A combination of `mkStorePathString` and `mkOutputString`.
*/ */
void mkSingleDerivedPathString( void mkSingleDerivedPathString(const SingleDerivedPath & p, Value & v);
const SingleDerivedPath & p,
Value & v);
void concatLists(Value & v, size_t nrLists, Value * const * lists, const PosIdx pos, std::string_view errorCtx); void concatLists(Value & v, size_t nrLists, Value * const * lists, const PosIdx pos, std::string_view errorCtx);
@ -845,22 +878,22 @@ public:
* @param[out] maybePaths if not nullptr, all built or referenced store paths will be added to this set * @param[out] maybePaths if not nullptr, all built or referenced store paths will be added to this set
* @return a mapping from the placeholders used to construct the associated value to their final store path. * @return a mapping from the placeholders used to construct the associated value to their final store path.
*/ */
[[nodiscard]] StringMap realiseContext(const NixStringContext & context, StorePathSet * maybePaths = nullptr, bool isIFD = true); [[nodiscard]] StringMap
realiseContext(const NixStringContext & context, StorePathSet * maybePaths = nullptr, bool isIFD = true);
/** /**
* Realise the given string with context, and return the string with outputs instead of downstream output placeholders. * Realise the given string with context, and return the string with outputs instead of downstream output
* placeholders.
* @param[in] str the string to realise * @param[in] str the string to realise
* @param[out] paths all referenced store paths will be added to this set * @param[out] paths all referenced store paths will be added to this set
* @return the realised string * @return the realised string
* @throw EvalError if the value is not a string, path or derivation (see `coerceToString`) * @throw EvalError if the value is not a string, path or derivation (see `coerceToString`)
*/ */
std::string realiseString(Value & str, StorePathSet * storePathsOutMaybe, bool isIFD = true, const PosIdx pos = noPos); std::string
realiseString(Value & str, StorePathSet * storePathsOutMaybe, bool isIFD = true, const PosIdx pos = noPos);
/* Call the binary path filter predicate used builtins.path etc. */ /* Call the binary path filter predicate used builtins.path etc. */
bool callPathFilter( bool callPathFilter(Value * filterFun, const SourcePath & path, PosIdx pos);
Value * filterFun,
const SourcePath & path,
PosIdx pos);
DocComment getDocCommentForPos(PosIdx pos); DocComment getDocCommentForPos(PosIdx pos);
@ -879,8 +912,7 @@ private:
* Like `mkSingleDerivedPathStringRaw` but just creates a raw string * Like `mkSingleDerivedPathStringRaw` but just creates a raw string
* Value, which would also have a string context. * Value, which would also have a string context.
*/ */
std::string mkSingleDerivedPathStringRaw( std::string mkSingleDerivedPathStringRaw(const SingleDerivedPath & p);
const SingleDerivedPath & p);
unsigned long nrEnvs = 0; unsigned long nrEnvs = 0;
unsigned long nrValuesInEnvs = 0; unsigned long nrValuesInEnvs = 0;
@ -928,7 +960,8 @@ private:
friend class ListBuilder; friend class ListBuilder;
}; };
struct DebugTraceStacker { struct DebugTraceStacker
{
DebugTraceStacker(EvalState & evalState, DebugTrace t); DebugTraceStacker(EvalState & evalState, DebugTrace t);
~DebugTraceStacker() ~DebugTraceStacker()
{ {

View file

@ -7,7 +7,6 @@
#include <string> #include <string>
#include <map> #include <map>
namespace nix { namespace nix {
/** /**
@ -45,7 +44,8 @@ public:
*/ */
std::string attrPath; std::string attrPath;
PackageInfo(EvalState & state) : state(&state) { }; PackageInfo(EvalState & state)
: state(&state) {};
PackageInfo(EvalState & state, std::string attrPath, const Bindings * attrs); PackageInfo(EvalState & state, std::string attrPath, const Bindings * attrs);
PackageInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs); PackageInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs);
@ -74,28 +74,43 @@ public:
MetaValue queryMetaInfo(EvalState & state, const string & name) const; MetaValue queryMetaInfo(EvalState & state, const string & name) const;
*/ */
void setName(const std::string & s) { name = s; } void setName(const std::string & s)
void setDrvPath(StorePath path) { drvPath = {{std::move(path)}}; } {
void setOutPath(StorePath path) { outPath = {{std::move(path)}}; } name = s;
}
void setDrvPath(StorePath path)
{
drvPath = {{std::move(path)}};
}
void setOutPath(StorePath path)
{
outPath = {{std::move(path)}};
}
void setFailed() { failed = true; }; void setFailed()
bool hasFailed() { return failed; }; {
failed = true;
};
bool hasFailed()
{
return failed;
};
}; };
typedef std::list<PackageInfo, traceable_allocator<PackageInfo>> PackageInfos; typedef std::list<PackageInfo, traceable_allocator<PackageInfo>> PackageInfos;
/** /**
* If value `v` denotes a derivation, return a PackageInfo object * If value `v` denotes a derivation, return a PackageInfo object
* describing it. Otherwise return nothing. * describing it. Otherwise return nothing.
*/ */
std::optional<PackageInfo> getDerivation(EvalState & state, std::optional<PackageInfo> getDerivation(EvalState & state, Value & v, bool ignoreAssertionFailures);
Value & v, bool ignoreAssertionFailures);
void getDerivations(EvalState & state, Value & v, const std::string & pathPrefix, void getDerivations(
Bindings & autoArgs, PackageInfos & drvs, EvalState & state,
Value & v,
const std::string & pathPrefix,
Bindings & autoArgs,
PackageInfos & drvs,
bool ignoreAssertionFailures); bool ignoreAssertionFailures);
} }

View file

@ -19,7 +19,8 @@ struct StaticEnv;
struct Value; struct Value;
/** /**
* A documentation comment, in the sense of [RFC 145](https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md) * A documentation comment, in the sense of [RFC
* 145](https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md)
* *
* Note that this does not implement the following: * Note that this does not implement the following:
* - argument attribute names ("formals"): TBD * - argument attribute names ("formals"): TBD
@ -34,7 +35,8 @@ struct Value;
* `f: g: final: prev: <...>`. The parameters `final` and `prev` are part * `f: g: final: prev: <...>`. The parameters `final` and `prev` are part
* of the overlay concept, while distracting from the function's purpose. * of the overlay concept, while distracting from the function's purpose.
*/ */
struct DocComment { struct DocComment
{
/** /**
* Start of the comment, including the opening, ie `/` and `**`. * Start of the comment, including the opening, ie `/` and `**`.
@ -53,10 +55,12 @@ struct DocComment {
* therefore baking optionality into it is also useful, to avoiding the memory * therefore baking optionality into it is also useful, to avoiding the memory
* overhead of `std::optional`. * overhead of `std::optional`.
*/ */
operator bool() const { return static_cast<bool>(begin); } operator bool() const
{
return static_cast<bool>(begin);
}
std::string getInnerText(const PosTable & positions) const; std::string getInnerText(const PosTable & positions) const;
}; };
/** /**
@ -66,26 +70,28 @@ struct AttrName
{ {
Symbol symbol; Symbol symbol;
Expr * expr = nullptr; Expr * expr = nullptr;
AttrName(Symbol s) : symbol(s) {}; AttrName(Symbol s)
AttrName(Expr * e) : expr(e) {}; : symbol(s) {};
AttrName(Expr * e)
: expr(e) {};
}; };
typedef std::vector<AttrName> AttrPath; typedef std::vector<AttrName> AttrPath;
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath); std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath);
/* Abstract syntax of Nix expressions. */ /* Abstract syntax of Nix expressions. */
struct Expr struct Expr
{ {
struct AstSymbols { struct AstSymbols
{
Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body; Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body;
}; };
static unsigned long nrExprs; static unsigned long nrExprs;
Expr() { Expr()
{
nrExprs++; nrExprs++;
} }
virtual ~Expr() {}; virtual ~Expr() {};
@ -95,7 +101,10 @@ struct Expr
virtual Value * maybeThunk(EvalState & state, Env & env); virtual Value * maybeThunk(EvalState & state, Env & env);
virtual void setName(Symbol name); virtual void setName(Symbol name);
virtual void setDocComment(DocComment docComment) {}; virtual void setDocComment(DocComment docComment) {};
virtual PosIdx getPos() const { return noPos; } virtual PosIdx getPos() const
{
return noPos;
}
// These are temporary methods to be used only in parser.y // These are temporary methods to be used only in parser.y
virtual void resetCursedOr() {}; virtual void resetCursedOr() {};
@ -110,8 +119,14 @@ struct Expr
struct ExprInt : Expr struct ExprInt : Expr
{ {
Value v; Value v;
ExprInt(NixInt n) { v.mkInt(n); }; ExprInt(NixInt n)
ExprInt(NixInt::Inner n) { v.mkInt(n); }; {
v.mkInt(n);
};
ExprInt(NixInt::Inner n)
{
v.mkInt(n);
};
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS COMMON_METHODS
}; };
@ -119,7 +134,10 @@ struct ExprInt : Expr
struct ExprFloat : Expr struct ExprFloat : Expr
{ {
Value v; Value v;
ExprFloat(NixFloat nf) { v.mkFloat(nf); }; ExprFloat(NixFloat nf)
{
v.mkFloat(nf);
};
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS COMMON_METHODS
}; };
@ -128,7 +146,11 @@ struct ExprString : Expr
{ {
std::string s; std::string s;
Value v; 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; Value * maybeThunk(EvalState & state, Env & env) override;
COMMON_METHODS COMMON_METHODS
}; };
@ -138,7 +160,9 @@ struct ExprPath : Expr
ref<SourceAccessor> accessor; ref<SourceAccessor> accessor;
std::string s; std::string s;
Value v; Value v;
ExprPath(ref<SourceAccessor> accessor, std::string s) : accessor(accessor), s(std::move(s)) ExprPath(ref<SourceAccessor> accessor, std::string s)
: accessor(accessor)
, s(std::move(s))
{ {
v.mkPath(&*accessor, this->s.c_str()); v.mkPath(&*accessor, this->s.c_str());
} }
@ -170,10 +194,16 @@ struct ExprVar : Expr
Level level = 0; Level level = 0;
Displacement displ = 0; Displacement displ = 0;
ExprVar(Symbol name) : name(name) { }; ExprVar(Symbol name)
ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { }; : name(name) {};
ExprVar(const PosIdx & pos, Symbol name)
: pos(pos)
, name(name) {};
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
PosIdx getPos() const override { return pos; } PosIdx getPos() const override
{
return pos;
}
COMMON_METHODS COMMON_METHODS
}; };
@ -184,7 +214,8 @@ struct ExprVar : Expr
*/ */
struct ExprInheritFrom : ExprVar struct ExprInheritFrom : ExprVar
{ {
ExprInheritFrom(PosIdx pos, Displacement displ): ExprVar(pos, {}) ExprInheritFrom(PosIdx pos, Displacement displ)
: ExprVar(pos, {})
{ {
this->level = 0; this->level = 0;
this->displ = displ; this->displ = displ;
@ -199,9 +230,22 @@ struct ExprSelect : Expr
PosIdx pos; PosIdx pos;
Expr *e, *def; Expr *e, *def;
AttrPath attrPath; AttrPath attrPath;
ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(std::move(attrPath)) { }; ExprSelect(const PosIdx & pos, Expr * e, AttrPath attrPath, Expr * def)
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; : pos(pos)
PosIdx getPos() const override { return 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;
}
/** /**
* Evaluate the `a.b.c` part of `a.b.c.d`. This exists mostly for the purpose of :doc in the repl. * Evaluate the `a.b.c` part of `a.b.c.d`. This exists mostly for the purpose of :doc in the repl.
@ -209,7 +253,8 @@ struct ExprSelect : Expr
* @param[out] attrs The attribute set that should contain the last attribute name (if it exists). * @param[out] attrs The attribute set that should contain the last attribute name (if it exists).
* @return The last attribute name in `attrPath` * @return The last attribute name in `attrPath`
* *
* @note This does *not* evaluate the final attribute, and does not fail if that's the only attribute that does not exist. * @note This does *not* evaluate the final attribute, and does not fail if that's the only attribute that does not
* exist.
*/ */
Symbol evalExceptFinalSelect(EvalState & state, Env & env, Value & attrs); Symbol evalExceptFinalSelect(EvalState & state, Env & env, Value & attrs);
@ -220,8 +265,13 @@ struct ExprOpHasAttr : Expr
{ {
Expr * e; Expr * e;
AttrPath attrPath; AttrPath attrPath;
ExprOpHasAttr(Expr * e, AttrPath attrPath) : e(e), attrPath(std::move(attrPath)) { }; ExprOpHasAttr(Expr * e, AttrPath attrPath)
PosIdx getPos() const override { return e->getPos(); } : e(e)
, attrPath(std::move(attrPath)) {};
PosIdx getPos() const override
{
return e->getPos();
}
COMMON_METHODS COMMON_METHODS
}; };
@ -229,7 +279,8 @@ struct ExprAttrs : Expr
{ {
bool recursive; bool recursive;
PosIdx pos; PosIdx pos;
struct AttrDef { struct AttrDef
{
enum class Kind { enum class Kind {
/** `attr = expr;` */ /** `attr = expr;` */
Plain, Plain,
@ -244,7 +295,9 @@ struct ExprAttrs : Expr
PosIdx pos; PosIdx pos;
Displacement displ = 0; // displacement Displacement displ = 0; // displacement
AttrDef(Expr * e, const PosIdx & pos, Kind kind = Kind::Plain) AttrDef(Expr * e, const PosIdx & pos, Kind kind = Kind::Plain)
: kind(kind), e(e), pos(pos) { }; : kind(kind)
, e(e)
, pos(pos) {};
AttrDef() {}; AttrDef() {};
template<typename T> template<typename T>
@ -264,21 +317,29 @@ struct ExprAttrs : Expr
typedef std::map<Symbol, AttrDef> AttrDefs; typedef std::map<Symbol, AttrDef> AttrDefs;
AttrDefs attrs; AttrDefs attrs;
std::unique_ptr<std::vector<Expr *>> inheritFromExprs; std::unique_ptr<std::vector<Expr *>> inheritFromExprs;
struct DynamicAttrDef { struct DynamicAttrDef
{
Expr *nameExpr, *valueExpr; Expr *nameExpr, *valueExpr;
PosIdx pos; PosIdx pos;
DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const PosIdx & pos) DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const PosIdx & pos)
: nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { }; : nameExpr(nameExpr)
, valueExpr(valueExpr)
, pos(pos) {};
}; };
typedef std::vector<DynamicAttrDef> DynamicAttrDefs; typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
DynamicAttrDefs dynamicAttrs; DynamicAttrDefs dynamicAttrs;
ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { }; ExprAttrs(const PosIdx & pos)
ExprAttrs() : recursive(false) { }; : recursive(false)
PosIdx getPos() const override { return pos; } , pos(pos) {};
ExprAttrs()
: recursive(false) {};
PosIdx getPos() const override
{
return pos;
}
COMMON_METHODS COMMON_METHODS
std::shared_ptr<const StaticEnv> bindInheritSources( std::shared_ptr<const StaticEnv> bindInheritSources(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
EvalState & es, const std::shared_ptr<const StaticEnv> & env);
Env * buildInheritFromEnv(EvalState & state, Env & up); Env * buildInheritFromEnv(EvalState & state, Env & up);
void showBindings(const SymbolTable & symbols, std::ostream & str) const; void showBindings(const SymbolTable & symbols, std::ostream & str) const;
}; };
@ -314,16 +375,15 @@ struct Formals
bool has(Symbol arg) const bool has(Symbol arg) const
{ {
auto it = std::lower_bound(formals.begin(), formals.end(), arg, auto it = std::lower_bound(
[] (const Formal & f, const Symbol & sym) { return f.name < sym; }); formals.begin(), formals.end(), arg, [](const Formal & f, const Symbol & sym) { return f.name < sym; });
return it != formals.end() && it->name == arg; return it != formals.end() && it->name == arg;
} }
std::vector<Formal> lexicographicOrder(const SymbolTable & symbols) const std::vector<Formal> lexicographicOrder(const SymbolTable & symbols) const
{ {
std::vector<Formal> result(formals.begin(), formals.end()); std::vector<Formal> result(formals.begin(), formals.end());
std::sort(result.begin(), result.end(), std::sort(result.begin(), result.end(), [&](const Formal & a, const Formal & b) {
[&] (const Formal & a, const Formal & b) {
std::string_view sa = symbols[a.name], sb = symbols[b.name]; std::string_view sa = symbols[a.name], sb = symbols[b.name];
return sa < sb; return sa < sb;
}); });
@ -341,17 +401,26 @@ struct ExprLambda : Expr
DocComment docComment; DocComment docComment;
ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body) ExprLambda(PosIdx pos, Symbol arg, Formals * formals, Expr * body)
: pos(pos), arg(arg), formals(formals), body(body) : pos(pos)
{ , arg(arg)
}; , formals(formals)
, body(body) {};
ExprLambda(PosIdx pos, Formals * formals, Expr * body) ExprLambda(PosIdx pos, Formals * formals, Expr * body)
: pos(pos), formals(formals), body(body) : pos(pos)
, formals(formals)
, body(body)
{ {
} }
void setName(Symbol name) override; void setName(Symbol name) override;
std::string showNamePos(const EvalState & state) const; std::string showNamePos(const EvalState & state) const;
inline bool hasFormals() const { return formals != nullptr; } inline bool hasFormals() const
PosIdx getPos() const override { return pos; } {
return formals != nullptr;
}
PosIdx getPos() const override
{
return pos;
}
virtual void setDocComment(DocComment docComment) override; virtual void setDocComment(DocComment docComment) override;
COMMON_METHODS COMMON_METHODS
}; };
@ -363,12 +432,23 @@ struct ExprCall : Expr
PosIdx pos; PosIdx pos;
std::optional<PosIdx> cursedOrEndPos; // used during parsing to warn about https://github.com/NixOS/nix/issues/11118 std::optional<PosIdx> cursedOrEndPos; // used during parsing to warn about https://github.com/NixOS/nix/issues/11118
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args) ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
: fun(fun), args(args), pos(pos), cursedOrEndPos({}) : fun(fun)
{ } , args(args)
, pos(pos)
, cursedOrEndPos({})
{
}
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args, PosIdx && cursedOrEndPos) ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args, PosIdx && cursedOrEndPos)
: fun(fun), args(args), pos(pos), cursedOrEndPos(cursedOrEndPos) : fun(fun)
{ } , args(args)
PosIdx getPos() const override { return pos; } , pos(pos)
, cursedOrEndPos(cursedOrEndPos)
{
}
PosIdx getPos() const override
{
return pos;
}
virtual void resetCursedOr() override; virtual void resetCursedOr() override;
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) override; virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) override;
COMMON_METHODS COMMON_METHODS
@ -378,7 +458,9 @@ struct ExprLet : Expr
{ {
ExprAttrs * attrs; ExprAttrs * attrs;
Expr * body; Expr * body;
ExprLet(ExprAttrs * attrs, Expr * body) : attrs(attrs), body(body) { }; ExprLet(ExprAttrs * attrs, Expr * body)
: attrs(attrs)
, body(body) {};
COMMON_METHODS COMMON_METHODS
}; };
@ -388,8 +470,14 @@ struct ExprWith : Expr
Expr *attrs, *body; Expr *attrs, *body;
size_t prevWith; size_t prevWith;
ExprWith * parentWith; ExprWith * parentWith;
ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; ExprWith(const PosIdx & pos, Expr * attrs, Expr * body)
PosIdx getPos() const override { return pos; } : pos(pos)
, attrs(attrs)
, body(body) {};
PosIdx getPos() const override
{
return pos;
}
COMMON_METHODS COMMON_METHODS
}; };
@ -397,8 +485,15 @@ struct ExprIf : Expr
{ {
PosIdx pos; PosIdx pos;
Expr *cond, *then, *else_; Expr *cond, *then, *else_;
ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { }; ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_)
PosIdx getPos() const override { return pos; } : pos(pos)
, cond(cond)
, then(then)
, else_(else_) {};
PosIdx getPos() const override
{
return pos;
}
COMMON_METHODS COMMON_METHODS
}; };
@ -406,16 +501,26 @@ struct ExprAssert : Expr
{ {
PosIdx pos; PosIdx pos;
Expr *cond, *body; Expr *cond, *body;
ExprAssert(const PosIdx & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { }; ExprAssert(const PosIdx & pos, Expr * cond, Expr * body)
PosIdx getPos() const override { return pos; } : pos(pos)
, cond(cond)
, body(body) {};
PosIdx getPos() const override
{
return pos;
}
COMMON_METHODS COMMON_METHODS
}; };
struct ExprOpNot : Expr struct ExprOpNot : Expr
{ {
Expr * e; Expr * e;
ExprOpNot(Expr * e) : e(e) { }; ExprOpNot(Expr * e)
PosIdx getPos() const override { return e->getPos(); } : e(e) {};
PosIdx getPos() const override
{
return e->getPos();
}
COMMON_METHODS COMMON_METHODS
}; };
@ -424,27 +529,35 @@ struct ExprOpNot : Expr
{ \ { \
PosIdx pos; \ PosIdx pos; \
Expr *e1, *e2; \ Expr *e1, *e2; \
name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \ name(Expr * e1, Expr * e2) \
name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \ : e1(e1) \
, e2(e2) {}; \
name(const PosIdx & pos, Expr * e1, Expr * e2) \
: pos(pos) \
, e1(e1) \
, e2(e2) {}; \
void show(const SymbolTable & symbols, std::ostream & str) const override \ void show(const SymbolTable & symbols, std::ostream & str) const override \
{ \ { \
str << "("; e1->show(symbols, str); str << " " s " "; e2->show(symbols, str); str << ")"; \ str << "("; \
e1->show(symbols, str); \
str << " " s " "; \
e2->show(symbols, str); \
str << ")"; \
} \ } \
void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override \ void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) override \
{ \ { \
e1->bindVars(es, env); e2->bindVars(es, env); \ e1->bindVars(es, env); \
e2->bindVars(es, env); \
} \ } \
void eval(EvalState & state, Env & env, Value & v) override; \ void eval(EvalState & state, Env & env, Value & v) override; \
PosIdx getPos() const override { return pos; } \ PosIdx getPos() const override \
{ \
return pos; \
} \
}; };
MakeBinOp(ExprOpEq, "==") MakeBinOp(ExprOpEq, "==") MakeBinOp(ExprOpNEq, "!=") MakeBinOp(ExprOpAnd, "&&") MakeBinOp(ExprOpOr, "||")
MakeBinOp(ExprOpNEq, "!=") MakeBinOp(ExprOpImpl, "->") MakeBinOp(ExprOpUpdate, "//") MakeBinOp(ExprOpConcatLists, "++")
MakeBinOp(ExprOpAnd, "&&")
MakeBinOp(ExprOpOr, "||")
MakeBinOp(ExprOpImpl, "->")
MakeBinOp(ExprOpUpdate, "//")
MakeBinOp(ExprOpConcatLists, "++")
struct ExprConcatStrings : Expr struct ExprConcatStrings : Expr
{ {
@ -452,16 +565,25 @@ struct ExprConcatStrings : Expr
bool forceString; bool forceString;
std::vector<std::pair<PosIdx, Expr *>> * es; std::vector<std::pair<PosIdx, Expr *>> * es;
ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *>> * es) ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *>> * es)
: pos(pos), forceString(forceString), es(es) { }; : pos(pos)
PosIdx getPos() const override { return pos; } , forceString(forceString)
, es(es) {};
PosIdx getPos() const override
{
return pos;
}
COMMON_METHODS COMMON_METHODS
}; };
struct ExprPos : Expr struct ExprPos : Expr
{ {
PosIdx pos; PosIdx pos;
ExprPos(const PosIdx & pos) : pos(pos) { }; ExprPos(const PosIdx & pos)
PosIdx getPos() const override { return pos; } : pos(pos) {};
PosIdx getPos() const override
{
return pos;
}
COMMON_METHODS COMMON_METHODS
}; };
@ -476,7 +598,6 @@ struct ExprBlackHole : Expr
extern ExprBlackHole eBlackHole; extern ExprBlackHole eBlackHole;
/* Static environments are used to map variable names onto (level, /* Static environments are used to map variable names onto (level,
displacement) pairs used to obtain the value of the variable at displacement) pairs used to obtain the value of the variable at
runtime. */ runtime. */
@ -498,8 +619,9 @@ struct StaticEnv
void sort() void sort()
{ {
std::stable_sort(vars.begin(), vars.end(), std::stable_sort(vars.begin(), vars.end(), [](const Vars::value_type & a, const Vars::value_type & b) {
[](const Vars::value_type & a, const Vars::value_type & b) { return a.first < b.first; }); return a.first < b.first;
});
} }
void deduplicate() void deduplicate()
@ -507,7 +629,8 @@ struct StaticEnv
auto it = vars.begin(), jt = it, end = vars.end(); auto it = vars.begin(), jt = it, end = vars.end();
while (jt != end) { while (jt != end) {
*it = *jt++; *it = *jt++;
while (jt != end && it->first == jt->first) *it = *jt++; while (jt != end && it->first == jt->first)
*it = *jt++;
it++; it++;
} }
vars.erase(it, end); vars.erase(it, end);
@ -517,10 +640,10 @@ struct StaticEnv
{ {
Vars::value_type key(name, 0); Vars::value_type key(name, 0);
auto i = std::lower_bound(vars.begin(), vars.end(), key); auto i = std::lower_bound(vars.begin(), vars.end(), key);
if (i != vars.end() && i->first == name) return i; if (i != vars.end() && i->first == name)
return i;
return vars.end(); return vars.end();
} }
}; };
} }

View file

@ -17,7 +17,10 @@ struct StringToken
const char * p; const char * p;
size_t l; size_t l;
bool hasIndentation; bool hasIndentation;
operator std::string_view() const { return {p, l}; } operator std::string_view() const
{
return {p, l};
}
}; };
// This type must be trivially copyable; see YYLTYPE_IS_TRIVIAL in parser.y. // This type must be trivially copyable; see YYLTYPE_IS_TRIVIAL in parser.y.
@ -29,12 +32,14 @@ struct ParserLocation
// backup to recover from yyless(0) // backup to recover from yyless(0)
int stashedBeginOffset, stashedEndOffset; int stashedBeginOffset, stashedEndOffset;
void stash() { void stash()
{
stashedBeginOffset = beginOffset; stashedBeginOffset = beginOffset;
stashedEndOffset = endOffset; stashedEndOffset = endOffset;
} }
void unstash() { void unstash()
{
beginOffset = stashedBeginOffset; beginOffset = stashedBeginOffset;
endOffset = stashedEndOffset; endOffset = stashedEndOffset;
} }
@ -87,32 +92,30 @@ struct ParserState
void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos); void dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos);
void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos); void dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos);
void addAttr(ExprAttrs * attrs, AttrPath && attrPath, const ParserLocation & loc, Expr * e, const ParserLocation & exprLoc); void addAttr(
ExprAttrs * attrs, AttrPath && attrPath, const ParserLocation & loc, Expr * e, const ParserLocation & exprLoc);
void addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symbol, ExprAttrs::AttrDef && def); void addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symbol, ExprAttrs::AttrDef && def);
Formals * validateFormals(Formals * formals, PosIdx pos = noPos, Symbol arg = {}); Formals * validateFormals(Formals * formals, PosIdx pos = noPos, Symbol arg = {});
Expr * stripIndentation(const PosIdx pos, Expr * stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es);
std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es);
PosIdx at(const ParserLocation & loc); PosIdx at(const ParserLocation & loc);
}; };
inline void ParserState::dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos) inline void ParserState::dupAttr(const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos)
{ {
throw ParseError({ throw ParseError(
.msg = HintFmt("attribute '%1%' already defined at %2%", {.msg = HintFmt("attribute '%1%' already defined at %2%", showAttrPath(symbols, attrPath), positions[prevPos]),
showAttrPath(symbols, attrPath), positions[prevPos]), .pos = positions[pos]});
.pos = positions[pos]
});
} }
inline void ParserState::dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos) inline void ParserState::dupAttr(Symbol attr, const PosIdx pos, const PosIdx prevPos)
{ {
throw ParseError({ throw ParseError(
.msg = HintFmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]), {.msg = HintFmt("attribute '%1%' already defined at %2%", symbols[attr], positions[prevPos]),
.pos = positions[pos] .pos = positions[pos]});
});
} }
inline void ParserState::addAttr(ExprAttrs * attrs, AttrPath && attrPath, const ParserLocation & loc, Expr * e, const ParserLocation & exprLoc) inline void ParserState::addAttr(
ExprAttrs * attrs, AttrPath && attrPath, const ParserLocation & loc, Expr * e, const ParserLocation & exprLoc)
{ {
AttrPath::iterator i; AttrPath::iterator i;
// All attrpaths have at least one attr // All attrpaths have at least one attr
@ -159,7 +162,8 @@ inline void ParserState::addAttr(ExprAttrs * attrs, AttrPath && attrPath, const
* Precondition: attrPath is used for error messages and should already contain * Precondition: attrPath is used for error messages and should already contain
* symbol as its last element. * symbol as its last element.
*/ */
inline void ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symbol, ExprAttrs::AttrDef && def) inline void
ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const Symbol & symbol, ExprAttrs::AttrDef && def)
{ {
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(symbol); ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(symbol);
if (j != attrs->attrs.end()) { if (j != attrs->attrs.end()) {
@ -189,12 +193,14 @@ inline void ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const S
attrPath.pop_back(); attrPath.pop_back();
} }
ae->attrs.clear(); ae->attrs.clear();
jAttrs->dynamicAttrs.insert(jAttrs->dynamicAttrs.end(), jAttrs->dynamicAttrs.insert(
jAttrs->dynamicAttrs.end(),
std::make_move_iterator(ae->dynamicAttrs.begin()), std::make_move_iterator(ae->dynamicAttrs.begin()),
std::make_move_iterator(ae->dynamicAttrs.end())); std::make_move_iterator(ae->dynamicAttrs.end()));
ae->dynamicAttrs.clear(); ae->dynamicAttrs.clear();
if (ae->inheritFromExprs) { if (ae->inheritFromExprs) {
jAttrs->inheritFromExprs->insert(jAttrs->inheritFromExprs->end(), jAttrs->inheritFromExprs->insert(
jAttrs->inheritFromExprs->end(),
std::make_move_iterator(ae->inheritFromExprs->begin()), std::make_move_iterator(ae->inheritFromExprs->begin()),
std::make_move_iterator(ae->inheritFromExprs->end())); std::make_move_iterator(ae->inheritFromExprs->end()));
ae->inheritFromExprs = nullptr; ae->inheritFromExprs = nullptr;
@ -211,8 +217,7 @@ inline void ParserState::addAttr(ExprAttrs * attrs, AttrPath & attrPath, const S
inline Formals * ParserState::validateFormals(Formals * formals, PosIdx pos, Symbol arg) inline Formals * ParserState::validateFormals(Formals * formals, PosIdx pos, Symbol arg)
{ {
std::sort(formals->formals.begin(), formals->formals.end(), std::sort(formals->formals.begin(), formals->formals.end(), [](const auto & a, const auto & b) {
[] (const auto & a, const auto & b) {
return std::tie(a.name, a.pos) < std::tie(b.name, b.pos); return std::tie(a.name, a.pos) < std::tie(b.name, b.pos);
}); });
@ -224,24 +229,22 @@ inline Formals * ParserState::validateFormals(Formals * formals, PosIdx pos, Sym
duplicate = std::min(thisDup, duplicate.value_or(thisDup)); duplicate = std::min(thisDup, duplicate.value_or(thisDup));
} }
if (duplicate) if (duplicate)
throw ParseError({ throw ParseError(
.msg = HintFmt("duplicate formal function argument '%1%'", symbols[duplicate->first]), {.msg = HintFmt("duplicate formal function argument '%1%'", symbols[duplicate->first]),
.pos = positions[duplicate->second] .pos = positions[duplicate->second]});
});
if (arg && formals->has(arg)) if (arg && formals->has(arg))
throw ParseError({ throw ParseError(
.msg = HintFmt("duplicate formal function argument '%1%'", symbols[arg]), {.msg = HintFmt("duplicate formal function argument '%1%'", symbols[arg]), .pos = positions[pos]});
.pos = positions[pos]
});
return formals; return formals;
} }
inline Expr * ParserState::stripIndentation(const PosIdx pos, inline Expr *
std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es) ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken>>> && es)
{ {
if (es.empty()) return new ExprString(""); if (es.empty())
return new ExprString("");
/* Figure out the minimum indentation. Note that by design /* Figure out the minimum indentation. Note that by design
whitespace-only final lines are not taken into account. (So whitespace-only final lines are not taken into account. (So
@ -255,7 +258,8 @@ inline Expr * ParserState::stripIndentation(const PosIdx pos,
/* Anti-quotations and escaped characters end the current start-of-line whitespace. */ /* Anti-quotations and escaped characters end the current start-of-line whitespace. */
if (atStartOfLine) { if (atStartOfLine) {
atStartOfLine = false; atStartOfLine = false;
if (curIndent < minIndent) minIndent = curIndent; if (curIndent < minIndent)
minIndent = curIndent;
} }
continue; continue;
} }
@ -269,7 +273,8 @@ inline Expr * ParserState::stripIndentation(const PosIdx pos,
curIndent = 0; curIndent = 0;
} else { } else {
atStartOfLine = false; atStartOfLine = false;
if (curIndent < minIndent) minIndent = curIndent; if (curIndent < minIndent)
minIndent = curIndent;
} }
} else if (str->p[j] == '\n') { } else if (str->p[j] == '\n') {
atStartOfLine = true; atStartOfLine = true;
@ -296,8 +301,7 @@ inline Expr * ParserState::stripIndentation(const PosIdx pos,
if (t.p[j] == ' ') { if (t.p[j] == ' ') {
if (curDropped++ >= minIndent) if (curDropped++ >= minIndent)
s2 += t.p[j]; s2 += t.p[j];
} } else if (t.p[j] == '\n') {
else if (t.p[j] == '\n') {
curDropped = 0; curDropped = 0;
s2 += t.p[j]; s2 += t.p[j];
} else { } else {
@ -307,7 +311,8 @@ inline Expr * ParserState::stripIndentation(const PosIdx pos,
} }
} else { } else {
s2 += t.p[j]; s2 += t.p[j];
if (t.p[j] == '\n') atStartOfLine = true; if (t.p[j] == '\n')
atStartOfLine = true;
} }
} }

View file

@ -15,10 +15,6 @@ namespace nix {
* See: https://github.com/NixOS/nix/issues/9730 * See: https://github.com/NixOS/nix/issues/9730
*/ */
void printAmbiguous( void printAmbiguous(
Value &v, Value & v, const SymbolTable & symbols, std::ostream & str, std::set<const void *> * seen, int depth);
const SymbolTable &symbols,
std::ostream &str,
std::set<const void *> *seen,
int depth);
} }

View file

@ -26,10 +26,12 @@ struct Value;
* @param s The logical string * @param s The logical string
*/ */
std::ostream & printLiteralString(std::ostream & o, std::string_view s); std::ostream & printLiteralString(std::ostream & o, std::string_view s);
inline std::ostream & printLiteralString(std::ostream & o, const char * s) { inline std::ostream & printLiteralString(std::ostream & o, const char * s)
{
return printLiteralString(o, std::string_view(s)); return printLiteralString(o, std::string_view(s));
} }
inline std::ostream & printLiteralString(std::ostream & o, const std::string & s) { inline std::ostream & printLiteralString(std::ostream & o, const std::string & s)
{
return printLiteralString(o, std::string_view(s)); return printLiteralString(o, std::string_view(s));
} }
@ -66,7 +68,8 @@ void printValue(EvalState & state, std::ostream & str, Value & v, PrintOptions o
* A partially-applied form of `printValue` which can be formatted using `<<` * A partially-applied form of `printValue` which can be formatted using `<<`
* without allocating an intermediate string. * without allocating an intermediate string.
*/ */
class ValuePrinter { class ValuePrinter
{
friend std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer); friend std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer);
private: private:
EvalState & state; EvalState & state;
@ -75,12 +78,15 @@ private:
public: public:
ValuePrinter(EvalState & state, Value & value, PrintOptions options = PrintOptions{}) ValuePrinter(EvalState & state, Value & value, PrintOptions options = PrintOptions{})
: state(state), value(value), options(options) { } : state(state)
, value(value)
, options(options)
{
}
}; };
std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer); std::ostream & operator<<(std::ostream & output, const ValuePrinter & printer);
/** /**
* `ValuePrinter` does its own ANSI formatting, so we don't color it * `ValuePrinter` does its own ANSI formatting, so we don't color it
* magenta. * magenta.

View file

@ -23,7 +23,10 @@ class SymbolStr
private: private:
const std::string * s; const std::string * s;
explicit SymbolStr(const std::string & symbol): s(&symbol) {} explicit SymbolStr(const std::string & symbol)
: s(&symbol)
{
}
public: public:
bool operator==(std::string_view s2) const bool operator==(std::string_view s2) const
@ -61,15 +64,30 @@ class Symbol
private: private:
uint32_t id; uint32_t id;
explicit Symbol(uint32_t id): id(id) {} explicit Symbol(uint32_t id)
: id(id)
{
}
public: public:
Symbol() : id(0) {} Symbol()
: id(0)
{
}
explicit operator bool() const { return id > 0; } explicit operator bool() const
{
return id > 0;
}
auto operator<=>(const Symbol other) const { return id <=> other.id; } auto operator<=>(const Symbol other) const
bool operator==(const Symbol other) const { return id == other.id; } {
return id <=> other.id;
}
bool operator==(const Symbol other) const
{
return id == other.id;
}
friend class std::hash<Symbol>; friend class std::hash<Symbol>;
}; };

View file

@ -10,12 +10,17 @@
namespace nix { namespace nix {
nlohmann::json printValueAsJSON(EvalState & state, bool strict, nlohmann::json printValueAsJSON(
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore = true); EvalState & state, bool strict, Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore = true);
void printValueAsJSON(EvalState & state, bool strict,
Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore = true);
void printValueAsJSON(
EvalState & state,
bool strict,
Value & v,
const PosIdx pos,
std::ostream & str,
NixStringContext & context,
bool copyToStore = true);
MakeError(JSONSerializationError, Error); MakeError(JSONSerializationError, Error);

View file

@ -9,7 +9,13 @@
namespace nix { namespace nix {
void printValueAsXML(EvalState & state, bool strict, bool location, void printValueAsXML(
Value & v, std::ostream & out, NixStringContext & context, const PosIdx pos); EvalState & state,
bool strict,
bool location,
Value & v,
std::ostream & out,
NixStringContext & context,
const PosIdx pos);
} }

View file

@ -18,7 +18,6 @@ namespace nix {
struct Value; struct Value;
class BindingsBuilder; class BindingsBuilder;
typedef enum { typedef enum {
tUninitialized = 0, tUninitialized = 0,
tInt = 1, tInt = 1,
@ -44,19 +43,7 @@ typedef enum {
* grouping together implementation details like tList*, different function * grouping together implementation details like tList*, different function
* types, and types in non-normal form (so thunks and co.) * types, and types in non-normal form (so thunks and co.)
*/ */
typedef enum { typedef enum { nThunk, nInt, nFloat, nBool, nString, nPath, nNull, nAttrs, nList, nFunction, nExternal } ValueType;
nThunk,
nInt,
nFloat,
nBool,
nString,
nPath,
nNull,
nAttrs,
nList,
nFunction,
nExternal
} ValueType;
class Bindings; class Bindings;
struct Env; struct Env;
@ -104,7 +91,8 @@ class ExternalValueBase
* Coerce the value to a string. Defaults to uncoercable, i.e. throws an * Coerce the value to a string. Defaults to uncoercable, i.e. throws an
* error. * error.
*/ */
virtual std::string coerceToString(EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const; virtual std::string coerceToString(
EvalState & state, const PosIdx & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
/** /**
* Compare to another value of the same type. Defaults to uncomparable, * Compare to another value of the same type. Defaults to uncomparable,
@ -115,24 +103,26 @@ class ExternalValueBase
/** /**
* Print the value as JSON. Defaults to unconvertable, i.e. throws an error * Print the value as JSON. Defaults to unconvertable, i.e. throws an error
*/ */
virtual nlohmann::json printValueAsJSON(EvalState & state, bool strict, virtual nlohmann::json
NixStringContext & context, bool copyToStore = true) const; printValueAsJSON(EvalState & state, bool strict, NixStringContext & context, bool copyToStore = true) const;
/** /**
* Print the value as XML. Defaults to unevaluated * Print the value as XML. Defaults to unevaluated
*/ */
virtual void printValueAsXML(EvalState & state, bool strict, bool location, virtual void printValueAsXML(
XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen, EvalState & state,
bool strict,
bool location,
XMLWriter & doc,
NixStringContext & context,
PathSet & drvsSeen,
const PosIdx pos) const; const PosIdx pos) const;
virtual ~ExternalValueBase() virtual ~ExternalValueBase() {};
{
};
}; };
std::ostream & operator<<(std::ostream & str, const ExternalValueBase & v); std::ostream & operator<<(std::ostream & str, const ExternalValueBase & v);
class ListBuilder class ListBuilder
{ {
const size_t size; const size_t size;
@ -147,7 +137,8 @@ public:
: size(x.size) : size(x.size)
, inlineElems{x.inlineElems[0], x.inlineElems[1]} , inlineElems{x.inlineElems[0], x.inlineElems[1]}
, elems(size <= 2 ? inlineElems : x.elems) , elems(size <= 2 ? inlineElems : x.elems)
{ } {
}
Value *& operator[](size_t n) Value *& operator[](size_t n)
{ {
@ -156,13 +147,18 @@ public:
typedef Value ** iterator; typedef Value ** iterator;
iterator begin() { return &elems[0]; } iterator begin()
iterator end() { return &elems[size]; } {
return &elems[0];
}
iterator end()
{
return &elems[size];
}
friend struct Value; friend struct Value;
}; };
struct Value struct Value
{ {
private: private:
@ -179,14 +175,29 @@ public:
// needed by callers into methods of this type // needed by callers into methods of this type
// type() == nThunk // type() == nThunk
inline bool isThunk() const { return internalType == tThunk; }; inline bool isThunk() const
inline bool isApp() const { return internalType == tApp; }; {
return internalType == tThunk;
};
inline bool isApp() const
{
return internalType == tApp;
};
inline bool isBlackhole() const; inline bool isBlackhole() const;
// type() == nFunction // type() == nFunction
inline bool isLambda() const { return internalType == tLambda; }; inline bool isLambda() const
inline bool isPrimOp() const { return internalType == tPrimOp; }; {
inline bool isPrimOpApp() const { return internalType == tPrimOpApp; }; return internalType == tLambda;
};
inline bool isPrimOp() const
{
return internalType == tPrimOp;
};
inline bool isPrimOpApp() const
{
return internalType == tPrimOpApp;
};
/** /**
* Strings in the evaluator carry a so-called `context` which * Strings in the evaluator carry a so-called `context` which
@ -210,26 +221,31 @@ public:
* For canonicity, the store paths should be in sorted order. * For canonicity, the store paths should be in sorted order.
*/ */
struct StringWithContext { struct StringWithContext
{
const char * c_str; const char * c_str;
const char ** context; // must be in sorted order const char ** context; // must be in sorted order
}; };
struct Path { struct Path
{
SourceAccessor * accessor; SourceAccessor * accessor;
const char * path; const char * path;
}; };
struct ClosureThunk { struct ClosureThunk
{
Env * env; Env * env;
Expr * expr; Expr * expr;
}; };
struct FunctionApplicationThunk { struct FunctionApplicationThunk
{
Value *left, *right; Value *left, *right;
}; };
struct Lambda { struct Lambda
{
Env * env; Env * env;
ExprLambda * fun; ExprLambda * fun;
}; };
@ -244,7 +260,8 @@ public:
Path path; Path path;
Bindings * attrs; Bindings * attrs;
struct { struct
{
size_t size; size_t size;
Value * const * elems; Value * const * elems;
} bigList; } bigList;
@ -270,18 +287,35 @@ public:
inline ValueType type(bool invalidIsThunk = false) const inline ValueType type(bool invalidIsThunk = false) const
{ {
switch (internalType) { switch (internalType) {
case tUninitialized: break; case tUninitialized:
case tInt: return nInt; break;
case tBool: return nBool; case tInt:
case tString: return nString; return nInt;
case tPath: return nPath; case tBool:
case tNull: return nNull; return nBool;
case tAttrs: return nAttrs; case tString:
case tList1: case tList2: case tListN: return nList; return nString;
case tLambda: case tPrimOp: case tPrimOpApp: return nFunction; case tPath:
case tExternal: return nExternal; return nPath;
case tFloat: return nFloat; case tNull:
case tThunk: case tApp: return nThunk; return nNull;
case tAttrs:
return nAttrs;
case tList1:
case tList2:
case tListN:
return nList;
case tLambda:
case tPrimOp:
case tPrimOpApp:
return nFunction;
case tExternal:
return nExternal;
case tFloat:
return nFloat;
case tThunk:
case tApp:
return nThunk;
} }
if (invalidIsThunk) if (invalidIsThunk)
return nThunk; return nThunk;
@ -444,8 +478,7 @@ public:
{ {
assert(internalType == tPath); assert(internalType == tPath);
return SourcePath( return SourcePath(
ref(payload.path.accessor->shared_from_this()), ref(payload.path.accessor->shared_from_this()), CanonPath(CanonPath::unchecked_t(), payload.path.path));
CanonPath(CanonPath::unchecked_t(), payload.path.path));
} }
std::string_view string_view() const std::string_view string_view() const
@ -466,25 +499,36 @@ public:
} }
ExternalValueBase * external() const ExternalValueBase * external() const
{ return payload.external; } {
return payload.external;
}
const Bindings * attrs() const const Bindings * attrs() const
{ return payload.attrs; } {
return payload.attrs;
}
const PrimOp * primOp() const const PrimOp * primOp() const
{ return payload.primOp; } {
return payload.primOp;
}
bool boolean() const bool boolean() const
{ return payload.boolean; } {
return payload.boolean;
}
NixInt integer() const NixInt integer() const
{ return payload.integer; } {
return payload.integer;
}
NixFloat fpoint() const NixFloat fpoint() const
{ return payload.fpoint; } {
return payload.fpoint;
}
}; };
extern ExprBlackHole eBlackHole; extern ExprBlackHole eBlackHole;
bool Value::isBlackhole() const bool Value::isBlackhole() const
@ -497,11 +541,16 @@ void Value::mkBlackhole()
mkThunk(nullptr, (Expr *) &eBlackHole); mkThunk(nullptr, (Expr *) &eBlackHole);
} }
typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector; typedef std::vector<Value *, traceable_allocator<Value *>> ValueVector;
typedef std::unordered_map<Symbol, Value *, std::hash<Symbol>, std::equal_to<Symbol>, traceable_allocator<std::pair<const Symbol, Value *>>> ValueMap; typedef std::unordered_map<
typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector>>> ValueVectorMap; Symbol,
Value *,
std::hash<Symbol>,
std::equal_to<Symbol>,
traceable_allocator<std::pair<const Symbol, Value *>>>
ValueMap;
typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector>>>
ValueVectorMap;
/** /**
* A value allocated in traceable memory. * A value allocated in traceable memory.

View file

@ -24,7 +24,8 @@ public:
} }
}; };
struct NixStringContextElem { struct NixStringContextElem
{
/** /**
* Plain opaque path to some store object. * Plain opaque path to some store object.
* *
@ -41,7 +42,8 @@ struct NixStringContextElem {
* *
* Encoded in the form `=<drvPath>`. * Encoded in the form `=<drvPath>`.
*/ */
struct DrvDeep { struct DrvDeep
{
StorePath drvPath; StorePath drvPath;
GENERATE_CMP(DrvDeep, me->drvPath); GENERATE_CMP(DrvDeep, me->drvPath);
@ -54,11 +56,7 @@ struct NixStringContextElem {
*/ */
using Built = SingleDerivedPath::Built; using Built = SingleDerivedPath::Built;
using Raw = std::variant< using Raw = std::variant<Opaque, DrvDeep, Built>;
Opaque,
DrvDeep,
Built
>;
Raw raw; Raw raw;
@ -74,9 +72,8 @@ struct NixStringContextElem {
* *
* @param xpSettings Stop-gap to avoid globals during unit tests. * @param xpSettings Stop-gap to avoid globals during unit tests.
*/ */
static NixStringContextElem parse( static NixStringContextElem
std::string_view s, parse(std::string_view s, const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
std::string to_string() const; std::string to_string() const;
}; };

View file

@ -12,8 +12,10 @@ namespace nix {
// for more information, refer to // for more information, refer to
// https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp // https://github.com/nlohmann/json/blob/master/include/nlohmann/detail/input/json_sax.hpp
class JSONSax : nlohmann::json_sax<json> { class JSONSax : nlohmann::json_sax<json>
class JSONState { {
class JSONState
{
protected: protected:
std::unique_ptr<JSONState> parent; std::unique_ptr<JSONState> parent;
RootValue v; RootValue v;
@ -22,8 +24,14 @@ class JSONSax : nlohmann::json_sax<json> {
{ {
throw std::logic_error("tried to close toplevel json parser state"); throw std::logic_error("tried to close toplevel json parser state");
} }
explicit JSONState(std::unique_ptr<JSONState> && p) : parent(std::move(p)) {} explicit JSONState(std::unique_ptr<JSONState> && p)
explicit JSONState(Value * v) : v(allocRootValue(v)) {} : parent(std::move(p))
{
}
explicit JSONState(Value * v)
: v(allocRootValue(v))
{
}
JSONState(JSONState & p) = delete; JSONState(JSONState & p) = delete;
Value & value(EvalState & state) Value & value(EvalState & state)
{ {
@ -35,7 +43,8 @@ class JSONSax : nlohmann::json_sax<json> {
virtual void add() {} virtual void add() {}
}; };
class JSONObjectState : public JSONState { class JSONObjectState : public JSONState
{
using JSONState::JSONState; using JSONState::JSONState;
ValueMap attrs; ValueMap attrs;
std::unique_ptr<JSONState> resolve(EvalState & state) override std::unique_ptr<JSONState> resolve(EvalState & state) override
@ -46,7 +55,10 @@ class JSONSax : nlohmann::json_sax<json> {
parent->value(state).mkAttrs(attrs2); parent->value(state).mkAttrs(attrs2);
return std::move(parent); return std::move(parent);
} }
void add() override { v = nullptr; } void add() override
{
v = nullptr;
}
public: public:
void key(string_t & name, EvalState & state) void key(string_t & name, EvalState & state)
{ {
@ -55,7 +67,8 @@ class JSONSax : nlohmann::json_sax<json> {
} }
}; };
class JSONListState : public JSONState { class JSONListState : public JSONState
{
ValueVector values; ValueVector values;
std::unique_ptr<JSONState> resolve(EvalState & state) override std::unique_ptr<JSONState> resolve(EvalState & state) override
{ {
@ -65,12 +78,14 @@ class JSONSax : nlohmann::json_sax<json> {
parent->value(state).mkList(list); parent->value(state).mkList(list);
return std::move(parent); return std::move(parent);
} }
void add() override { void add() override
{
values.push_back(*v); values.push_back(*v);
v = nullptr; v = nullptr;
} }
public: public:
JSONListState(std::unique_ptr<JSONState> && p, std::size_t reserve) : JSONState(std::move(p)) JSONListState(std::unique_ptr<JSONState> && p, std::size_t reserve)
: JSONState(std::move(p))
{ {
values.reserve(reserve); values.reserve(reserve);
} }
@ -80,7 +95,9 @@ class JSONSax : nlohmann::json_sax<json> {
std::unique_ptr<JSONState> rs; std::unique_ptr<JSONState> rs;
public: public:
JSONSax(EvalState & state, Value & v) : state(state), rs(new JSONState(&v)) {}; JSONSax(EvalState & state, Value & v)
: state(state)
, rs(new JSONState(&v)) {};
bool null() override bool null() override
{ {
@ -150,23 +167,26 @@ public:
return true; return true;
} }
bool end_object() override { bool end_object() override
{
rs = rs->resolve(state); rs = rs->resolve(state);
rs->add(); rs->add();
return true; return true;
} }
bool end_array() override { bool end_array() override
{
return end_object(); return end_object();
} }
bool start_array(size_t len) override { bool start_array(size_t len) override
rs = std::make_unique<JSONListState>(std::move(rs), {
len != std::numeric_limits<size_t>::max() ? len : 128); rs = std::make_unique<JSONListState>(std::move(rs), len != std::numeric_limits<size_t>::max() ? len : 128);
return true; return true;
} }
bool parse_error(std::size_t, const std::string&, const nlohmann::detail::exception& ex) override { bool parse_error(std::size_t, const std::string &, const nlohmann::detail::exception & ex) override
{
throw JSONParseError("%s", ex.what()); throw JSONParseError("%s", ex.what());
} }
}; };

View file

@ -76,7 +76,8 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
{ {
typedef const decltype(attrs)::value_type * Attr; typedef const decltype(attrs)::value_type * Attr;
std::vector<Attr> sorted; std::vector<Attr> sorted;
for (auto & i : attrs) sorted.push_back(&i); for (auto & i : attrs)
sorted.push_back(&i);
std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) { std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) {
std::string_view sa = symbols[a->first], sb = symbols[b->first]; std::string_view sa = symbols[a->first], sb = symbols[b->first];
return sa < sb; return sa < sb;
@ -102,14 +103,16 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
} }
if (!inherits.empty()) { if (!inherits.empty()) {
str << "inherit"; str << "inherit";
for (auto sym : inherits) str << " " << symbols[sym]; for (auto sym : inherits)
str << " " << symbols[sym];
str << "; "; str << "; ";
} }
for (const auto & [from, syms] : inheritsFrom) { for (const auto & [from, syms] : inheritsFrom) {
str << "inherit ("; str << "inherit (";
(*inheritFromExprs)[from]->show(symbols, str); (*inheritFromExprs)[from]->show(symbols, str);
str << ")"; str << ")";
for (auto sym : syms) str << " " << symbols[sym]; for (auto sym : syms)
str << " " << symbols[sym];
str << "; "; str << "; ";
} }
for (auto & i : sorted) { for (auto & i : sorted) {
@ -130,7 +133,8 @@ void ExprAttrs::showBindings(const SymbolTable & symbols, std::ostream & str) co
void ExprAttrs::show(const SymbolTable & symbols, std::ostream & str) const void ExprAttrs::show(const SymbolTable & symbols, std::ostream & str) const
{ {
if (recursive) str << "rec "; if (recursive)
str << "rec ";
str << "{ "; str << "{ ";
showBindings(symbols, str); showBindings(symbols, str);
str << "}"; str << "}";
@ -157,7 +161,10 @@ void ExprLambda::show(const SymbolTable & symbols, std::ostream & str) const
// same expression being printed in two different ways depending on its // same expression being printed in two different ways depending on its
// context. always use lexicographic ordering to avoid this. // context. always use lexicographic ordering to avoid this.
for (auto & i : formals->lexicographicOrder(symbols)) { for (auto & i : formals->lexicographicOrder(symbols)) {
if (first) first = false; else str << ", "; if (first)
first = false;
else
str << ", ";
str << symbols[i.name]; str << symbols[i.name];
if (i.def) { if (i.def) {
str << " ? "; str << " ? ";
@ -165,13 +172,16 @@ void ExprLambda::show(const SymbolTable & symbols, std::ostream & str) const
} }
} }
if (formals->ellipsis) { if (formals->ellipsis) {
if (!first) str << ", "; if (!first)
str << ", ";
str << "..."; str << "...";
} }
str << " }"; str << " }";
if (arg) str << " @ "; if (arg)
str << " @ ";
} }
if (arg) str << symbols[arg]; if (arg)
str << symbols[arg];
str << ": "; str << ": ";
body->show(symbols, str); body->show(symbols, str);
str << ")"; str << ")";
@ -237,7 +247,10 @@ void ExprConcatStrings::show(const SymbolTable & symbols, std::ostream & str) co
bool first = true; bool first = true;
str << "("; str << "(";
for (auto & i : *es) { for (auto & i : *es) {
if (first) first = false; else str << " + "; if (first)
first = false;
else
str << " + ";
i.second->show(symbols, str); i.second->show(symbols, str);
} }
str << ")"; str << ")";
@ -248,13 +261,15 @@ void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const
str << "__curPos"; str << "__curPos";
} }
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath) std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
{ {
std::ostringstream out; std::ostringstream out;
bool first = true; bool first = true;
for (auto & i : attrPath) { for (auto & i : attrPath) {
if (!first) out << '.'; else first = false; if (!first)
out << '.';
else
first = false;
if (i.symbol) if (i.symbol)
out << symbols[i.symbol]; out << symbols[i.symbol];
else { else {
@ -266,7 +281,6 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
return out.str(); return out.str();
} }
/* Computing levels/displacements for variables. */ /* Computing levels/displacements for variables. */
void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
@ -312,7 +326,8 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
int withLevel = -1; int withLevel = -1;
for (curEnv = env.get(), level = 0; curEnv; curEnv = curEnv->up.get(), level++) { for (curEnv = env.get(), level = 0; curEnv; curEnv = curEnv->up.get(), level++) {
if (curEnv->isWith) { if (curEnv->isWith) {
if (withLevel == -1) withLevel = level; if (withLevel == -1)
withLevel = level;
} else { } else {
auto i = curEnv->find(name); auto i = curEnv->find(name);
if (i != curEnv->vars.end()) { if (i != curEnv->vars.end()) {
@ -327,10 +342,7 @@ void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
enclosing `with'. If there is no `with', then we can issue an enclosing `with'. If there is no `with', then we can issue an
"undefined variable" error now. */ "undefined variable" error now. */
if (withLevel == -1) if (withLevel == -1)
es.error<UndefinedVarError>( es.error<UndefinedVarError>("undefined variable '%1%'", es.symbols[name]).atPos(pos).debugThrow();
"undefined variable '%1%'",
es.symbols[name]
).atPos(pos).debugThrow();
for (auto * e = env.get(); e && !fromWith; e = e->up.get()) for (auto * e = env.get(); e && !fromWith; e = e->up.get())
fromWith = e->isWith; fromWith = e->isWith;
this->level = withLevel; this->level = withLevel;
@ -348,7 +360,8 @@ void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
es.exprEnvs.insert(std::make_pair(this, env)); es.exprEnvs.insert(std::make_pair(this, env));
e->bindVars(es, env); e->bindVars(es, env);
if (def) def->bindVars(es, env); if (def)
def->bindVars(es, env);
for (auto & i : attrPath) for (auto & i : attrPath)
if (!i.symbol) if (!i.symbol)
i.expr->bindVars(es, env); i.expr->bindVars(es, env);
@ -365,8 +378,8 @@ void ExprOpHasAttr::bindVars(EvalState & es, const std::shared_ptr<const StaticE
i.expr->bindVars(es, env); i.expr->bindVars(es, env);
} }
std::shared_ptr<const StaticEnv> ExprAttrs::bindInheritSources( std::shared_ptr<const StaticEnv>
EvalState & es, const std::shared_ptr<const StaticEnv> & env) ExprAttrs::bindInheritSources(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (!inheritFromExprs) if (!inheritFromExprs)
return nullptr; return nullptr;
@ -411,8 +424,7 @@ void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
i.nameExpr->bindVars(es, newEnv); i.nameExpr->bindVars(es, newEnv);
i.valueExpr->bindVars(es, newEnv); i.valueExpr->bindVars(es, newEnv);
} }
} } else {
else {
auto inheritFromEnv = bindInheritSources(es, env); auto inheritFromEnv = bindInheritSources(es, env);
for (auto & i : attrs) for (auto & i : attrs)
@ -439,14 +451,13 @@ void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
if (es.debugRepl) if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env)); es.exprEnvs.insert(std::make_pair(this, env));
auto newEnv = std::make_shared<StaticEnv>( auto newEnv =
nullptr, env, std::make_shared<StaticEnv>(nullptr, env, (hasFormals() ? formals->formals.size() : 0) + (!arg ? 0 : 1));
(hasFormals() ? formals->formals.size() : 0) +
(!arg ? 0 : 1));
Displacement displ = 0; Displacement displ = 0;
if (arg) newEnv->vars.emplace_back(arg, displ++); if (arg)
newEnv->vars.emplace_back(arg, displ++);
if (hasFormals()) { if (hasFormals()) {
for (auto & i : formals->formals) for (auto & i : formals->formals)
@ -455,7 +466,8 @@ void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
newEnv->sort(); newEnv->sort();
for (auto & i : formals->formals) for (auto & i : formals->formals)
if (i.def) i.def->bindVars(es, newEnv); if (i.def)
i.def->bindVars(es, newEnv);
} }
body->bindVars(es, newEnv); body->bindVars(es, newEnv);
@ -562,13 +574,9 @@ void ExprPos::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
es.exprEnvs.insert(std::make_pair(this, env)); es.exprEnvs.insert(std::make_pair(this, env));
} }
/* Storing function names. */ /* Storing function names. */
void Expr::setName(Symbol name) void Expr::setName(Symbol name) {}
{
}
void ExprLambda::setName(Symbol name) void ExprLambda::setName(Symbol name)
{ {
@ -576,16 +584,14 @@ void ExprLambda::setName(Symbol name)
body->setName(name); body->setName(name);
} }
std::string ExprLambda::showNamePos(const EvalState & state) const std::string ExprLambda::showNamePos(const EvalState & state) const
{ {
std::string id(name std::string id(name ? concatStrings("'", state.symbols[name], "'") : "anonymous function");
? concatStrings("'", state.symbols[name], "'")
: "anonymous function");
return fmt("%1% at %2%", id, state.positions[pos]); return fmt("%1% at %2%", id, state.positions[pos]);
} }
void ExprLambda::setDocComment(DocComment docComment) { void ExprLambda::setDocComment(DocComment docComment)
{
// RFC 145 specifies that the innermost doc comment wins. // RFC 145 specifies that the innermost doc comment wins.
// See https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md#ambiguous-placement // See https://github.com/NixOS/rfcs/blob/master/rfcs/0145-doc-strings.md#ambiguous-placement
if (!this->docComment) { if (!this->docComment) {
@ -610,7 +616,8 @@ size_t SymbolTable::totalSize() const
return n; return n;
} }
std::string DocComment::getInnerText(const PosTable & positions) const { std::string DocComment::getInnerText(const PosTable & positions) const
{
auto beginPos = positions[begin]; auto beginPos = positions[begin];
auto endPos = positions[end]; auto endPos = positions[end];
auto docCommentStr = beginPos.getSnippetUpTo(endPos).value_or(""); auto docCommentStr = beginPos.getSnippetUpTo(endPos).value_or("");
@ -628,8 +635,6 @@ std::string DocComment::getInnerText(const PosTable & positions) const {
return docStr; return docStr;
} }
/* Cursed or handling. /* Cursed or handling.
* *
* In parser.y, every use of expr_select in a production must call one of the * In parser.y, every use of expr_select in a production must call one of the
@ -647,10 +652,13 @@ void ExprCall::warnIfCursedOr(const SymbolTable & symbols, const PosTable & posi
{ {
if (cursedOrEndPos.has_value()) { if (cursedOrEndPos.has_value()) {
std::ostringstream out; std::ostringstream out;
out << "at " << positions[pos] << ": " out << "at " << positions[pos]
<< ": "
"This expression uses `or` as an identifier in a way that will change in a future Nix release.\n" "This expression uses `or` as an identifier in a way that will change in a future Nix release.\n"
"Wrap this entire expression in parentheses to preserve its current meaning:\n" "Wrap this entire expression in parentheses to preserve its current meaning:\n"
" (" << positions[pos].getSnippetUpTo(positions[*cursedOrEndPos]).value_or("could not read expression") << ")\n" " ("
<< positions[pos].getSnippetUpTo(positions[*cursedOrEndPos]).value_or("could not read expression")
<< ")\n"
"Give feedback at https://github.com/NixOS/nix/pull/11121"; "Give feedback at https://github.com/NixOS/nix/pull/11121";
warn(out.str()); warn(out.str());
} }

File diff suppressed because it is too large Load diff

View file

@ -8,7 +8,8 @@ namespace nix {
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value ** args, Value & v) static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
NixStringContext context; NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext"); auto s = state.coerceToString(
pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
v.mkString(*s); v.mkString(*s);
} }
@ -21,7 +22,6 @@ static RegisterPrimOp primop_unsafeDiscardStringContext({
.fun = prim_unsafeDiscardStringContext, .fun = prim_unsafeDiscardStringContext,
}); });
static void prim_hasContext(EvalState & state, const PosIdx pos, Value ** args, Value & v) static void prim_hasContext(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
NixStringContext context; NixStringContext context;
@ -29,8 +29,8 @@ static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args,
v.mkBool(!context.empty()); v.mkBool(!context.empty());
} }
static RegisterPrimOp primop_hasContext({ static RegisterPrimOp primop_hasContext(
.name = "__hasContext", {.name = "__hasContext",
.args = {"s"}, .args = {"s"},
.doc = R"( .doc = R"(
Return `true` if string *s* has a non-empty context. Return `true` if string *s* has a non-empty context.
@ -50,21 +50,18 @@ static RegisterPrimOp primop_hasContext({
> else { ${name} = meta; } > else { ${name} = meta; }
> ``` > ```
)", )",
.fun = prim_hasContext .fun = prim_hasContext});
});
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value ** args, Value & v) static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
NixStringContext context; NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency"); auto s = state.coerceToString(
pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
NixStringContext context2; NixStringContext context2;
for (auto && c : context) { for (auto && c : context) {
if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c.raw)) { if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c.raw)) {
context2.emplace(NixStringContextElem::Opaque { context2.emplace(NixStringContextElem::Opaque{.path = ptr->drvPath});
.path = ptr->drvPath
});
} else { } else {
/* Can reuse original item */ /* Can reuse original item */
context2.emplace(std::move(c).raw); context2.emplace(std::move(c).raw);
@ -74,8 +71,8 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx p
v.mkString(*s, context2); v.mkString(*s, context2);
} }
static RegisterPrimOp primop_unsafeDiscardOutputDependency({ static RegisterPrimOp primop_unsafeDiscardOutputDependency(
.name = "__unsafeDiscardOutputDependency", {.name = "__unsafeDiscardOutputDependency",
.args = {"s"}, .args = {"s"},
.doc = R"( .doc = R"(
Create a copy of the given string where every Create a copy of the given string where every
@ -94,41 +91,40 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency({
[`builtins.addDrvOutputDependencies`]: #builtins-addDrvOutputDependencies [`builtins.addDrvOutputDependencies`]: #builtins-addDrvOutputDependencies
)", )",
.fun = prim_unsafeDiscardOutputDependency .fun = prim_unsafeDiscardOutputDependency});
});
static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value ** args, Value & v) static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
NixStringContext context; NixStringContext context;
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.addDrvOutputDependencies"); auto s = state.coerceToString(
pos, *args[0], context, "while evaluating the argument passed to builtins.addDrvOutputDependencies");
auto contextSize = context.size(); auto contextSize = context.size();
if (contextSize != 1) { if (contextSize != 1) {
state.error<EvalError>( state.error<EvalError>("context of string '%s' must have exactly one element, but has %d", *s, contextSize)
"context of string '%s' must have exactly one element, but has %d", .atPos(pos)
*s, .debugThrow();
contextSize
).atPos(pos).debugThrow();
} }
NixStringContext context2{ NixStringContext context2{
(NixStringContextElem { std::visit(overloaded { (NixStringContextElem{std::visit(
overloaded{
[&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep { [&](const NixStringContextElem::Opaque & c) -> NixStringContextElem::DrvDeep {
if (!c.path.isDerivation()) { if (!c.path.isDerivation()) {
state.error<EvalError>( state.error<EvalError>("path '%s' is not a derivation", state.store->printStorePath(c.path))
"path '%s' is not a derivation", .atPos(pos)
state.store->printStorePath(c.path) .debugThrow();
).atPos(pos).debugThrow();
} }
return NixStringContextElem::DrvDeep{ return NixStringContextElem::DrvDeep{
.drvPath = c.path, .drvPath = c.path,
}; };
}, },
[&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep { [&](const NixStringContextElem::Built & c) -> NixStringContextElem::DrvDeep {
state.error<EvalError>( state
.error<EvalError>(
"`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'", "`addDrvOutputDependencies` can only act on derivations, not on a derivation output such as '%1%'",
c.output c.output)
).atPos(pos).debugThrow(); .atPos(pos)
.debugThrow();
}, },
[&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep { [&](const NixStringContextElem::DrvDeep & c) -> NixStringContextElem::DrvDeep {
/* Reuse original item because we want this to be idempotent. */ /* Reuse original item because we want this to be idempotent. */
@ -136,14 +132,15 @@ static void prim_addDrvOutputDependencies(EvalState & state, const PosIdx pos, V
above does not make much sense. */ above does not make much sense. */
return std::move(c); return std::move(c);
}, },
}, context.begin()->raw) }), },
context.begin()->raw)}),
}; };
v.mkString(*s, context2); v.mkString(*s, context2);
} }
static RegisterPrimOp primop_addDrvOutputDependencies({ static RegisterPrimOp primop_addDrvOutputDependencies(
.name = "__addDrvOutputDependencies", {.name = "__addDrvOutputDependencies",
.args = {"s"}, .args = {"s"},
.doc = R"( .doc = R"(
Create a copy of the given string where a single Create a copy of the given string where a single
@ -159,9 +156,7 @@ static RegisterPrimOp primop_addDrvOutputDependencies({
This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-unsafeDiscardOutputDependency). This is the opposite of [`builtins.unsafeDiscardOutputDependency`](#builtins-unsafeDiscardOutputDependency).
)", )",
.fun = prim_addDrvOutputDependencies .fun = prim_addDrvOutputDependencies});
});
/* Extract the context of a string as a structured Nix value. /* Extract the context of a string as a structured Nix value.
@ -184,7 +179,8 @@ static RegisterPrimOp primop_addDrvOutputDependencies({
*/ */
static void prim_getContext(EvalState & state, const PosIdx pos, Value ** args, Value & v) static void prim_getContext(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
struct ContextInfo { struct ContextInfo
{
bool path = false; bool path = false;
bool allOutputs = false; bool allOutputs = false;
Strings outputs; Strings outputs;
@ -193,20 +189,18 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext"); state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
auto contextInfos = std::map<StorePath, ContextInfo>(); auto contextInfos = std::map<StorePath, ContextInfo>();
for (auto && i : context) { for (auto && i : context) {
std::visit(overloaded { std::visit(
[&](NixStringContextElem::DrvDeep && d) { overloaded{
contextInfos[std::move(d.drvPath)].allOutputs = true; [&](NixStringContextElem::DrvDeep && d) { contextInfos[std::move(d.drvPath)].allOutputs = true; },
},
[&](NixStringContextElem::Built && b) { [&](NixStringContextElem::Built && b) {
// FIXME should eventually show string context as is, no // FIXME should eventually show string context as is, no
// resolving here. // resolving here.
auto drvPath = resolveDerivedPath(*state.store, *b.drvPath); auto drvPath = resolveDerivedPath(*state.store, *b.drvPath);
contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output)); contextInfos[std::move(drvPath)].outputs.emplace_back(std::move(b.output));
}, },
[&](NixStringContextElem::Opaque && o) { [&](NixStringContextElem::Opaque && o) { contextInfos[std::move(o.path)].path = true; },
contextInfos[std::move(o.path)].path = true;
}, },
}, ((NixStringContextElem &&) i).raw); ((NixStringContextElem &&) i).raw);
} }
auto attrs = state.buildBindings(contextInfos.size()); auto attrs = state.buildBindings(contextInfos.size());
@ -231,8 +225,8 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
v.mkAttrs(attrs); v.mkAttrs(attrs);
} }
static RegisterPrimOp primop_getContext({ static RegisterPrimOp primop_getContext(
.name = "__getContext", {.name = "__getContext",
.args = {"s"}, .args = {"s"},
.doc = R"( .doc = R"(
Return the string context of *s*. Return the string context of *s*.
@ -253,9 +247,7 @@ static RegisterPrimOp primop_getContext({
{ "/nix/store/arhvjaf6zmlyn8vh8fgn55rpwnxq0n7l-a.drv" = { outputs = [ "out" ]; }; } { "/nix/store/arhvjaf6zmlyn8vh8fgn55rpwnxq0n7l-a.drv" = { outputs = [ "out" ]; }; }
``` ```
)", )",
.fun = prim_getContext .fun = prim_getContext});
});
/* Append the given context to a given string. /* Append the given context to a given string.
@ -265,7 +257,8 @@ static RegisterPrimOp primop_getContext({
static void prim_appendContext(EvalState & state, const PosIdx pos, Value ** args, Value & v) static void prim_appendContext(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
NixStringContext context; NixStringContext context;
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext"); auto orig = state.forceString(
*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext"); state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
@ -274,10 +267,7 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
for (auto & i : *args[1]->attrs()) { for (auto & i : *args[1]->attrs()) {
const auto & name = state.symbols[i.name]; const auto & name = state.symbols[i.name];
if (!state.store->isStorePath(name)) if (!state.store->isStorePath(name))
state.error<EvalError>( state.error<EvalError>("context key '%s' is not a store path", name).atPos(i.pos).debugThrow();
"context key '%s' is not a store path",
name
).atPos(i.pos).debugThrow();
auto namePath = state.store->parseStorePath(name); auto namePath = state.store->parseStorePath(name);
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(namePath); state.store->ensurePath(namePath);
@ -285,20 +275,24 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
if (auto attr = i.value->attrs()->get(sPath)) { if (auto attr = i.value->attrs()->get(sPath)) {
if (state.forceBool(*attr->value, attr->pos, "while evaluating the `path` attribute of a string context")) if (state.forceBool(*attr->value, attr->pos, "while evaluating the `path` attribute of a string context"))
context.emplace(NixStringContextElem::Opaque { context.emplace(
NixStringContextElem::Opaque{
.path = namePath, .path = namePath,
}); });
} }
if (auto attr = i.value->attrs()->get(sAllOutputs)) { if (auto attr = i.value->attrs()->get(sAllOutputs)) {
if (state.forceBool(*attr->value, attr->pos, "while evaluating the `allOutputs` attribute of a string context")) { if (state.forceBool(
*attr->value, attr->pos, "while evaluating the `allOutputs` attribute of a string context")) {
if (!isDerivation(name)) { if (!isDerivation(name)) {
state.error<EvalError>( state
"tried to add all-outputs context of %s, which is not a derivation, to a string", .error<EvalError>(
name "tried to add all-outputs context of %s, which is not a derivation, to a string", name)
).atPos(i.pos).debugThrow(); .atPos(i.pos)
.debugThrow();
} }
context.emplace(NixStringContextElem::DrvDeep { context.emplace(
NixStringContextElem::DrvDeep{
.drvPath = namePath, .drvPath = namePath,
}); });
} }
@ -307,14 +301,17 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
if (auto attr = i.value->attrs()->get(state.sOutputs)) { if (auto attr = i.value->attrs()->get(state.sOutputs)) {
state.forceList(*attr->value, attr->pos, "while evaluating the `outputs` attribute of a string context"); state.forceList(*attr->value, attr->pos, "while evaluating the `outputs` attribute of a string context");
if (attr->value->listSize() && !isDerivation(name)) { if (attr->value->listSize() && !isDerivation(name)) {
state.error<EvalError>( state
"tried to add derivation output context of %s, which is not a derivation, to a string", .error<EvalError>(
name "tried to add derivation output context of %s, which is not a derivation, to a string", name)
).atPos(i.pos).debugThrow(); .atPos(i.pos)
.debugThrow();
} }
for (auto elem : attr->value->listItems()) { for (auto elem : attr->value->listItems()) {
auto outputName = state.forceStringNoCtx(*elem, attr->pos, "while evaluating an output name within a string context"); auto outputName =
context.emplace(NixStringContextElem::Built { state.forceStringNoCtx(*elem, attr->pos, "while evaluating an output name within a string context");
context.emplace(
NixStringContextElem::Built{
.drvPath = makeConstantStorePathRef(namePath), .drvPath = makeConstantStorePathRef(namePath),
.output = std::string{outputName}, .output = std::string{outputName},
}); });
@ -325,10 +322,6 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
v.mkString(orig, context); v.mkString(orig, context);
} }
static RegisterPrimOp primop_appendContext({ static RegisterPrimOp primop_appendContext({.name = "__appendContext", .arity = 2, .fun = prim_appendContext});
.name = "__appendContext",
.arity = 2,
.fun = prim_appendContext
});
} }

View file

@ -15,29 +15,35 @@ namespace nix {
* @param toPathMaybe Path to write the rewritten path to. If empty, the error shows the actual path. * @param toPathMaybe Path to write the rewritten path to. If empty, the error shows the actual path.
* @param v Return `Value` * @param v Return `Value`
*/ */
static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, const std::optional<StorePath> & toPathMaybe, Value &v) { static void runFetchClosureWithRewrite(
EvalState & state,
const PosIdx pos,
Store & fromStore,
const StorePath & fromPath,
const std::optional<StorePath> & toPathMaybe,
Value & v)
{
// establish toPath or throw // establish toPath or throw
if (!toPathMaybe || !state.store->isValidPath(*toPathMaybe)) { if (!toPathMaybe || !state.store->isValidPath(*toPathMaybe)) {
auto rewrittenPath = makeContentAddressed(fromStore, *state.store, fromPath); auto rewrittenPath = makeContentAddressed(fromStore, *state.store, fromPath);
if (toPathMaybe && *toPathMaybe != rewrittenPath) if (toPathMaybe && *toPathMaybe != rewrittenPath)
throw Error({ throw Error(
.msg = HintFmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected", {.msg = HintFmt(
"rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
state.store->printStorePath(fromPath), state.store->printStorePath(fromPath),
state.store->printStorePath(rewrittenPath), state.store->printStorePath(rewrittenPath),
state.store->printStorePath(*toPathMaybe)), state.store->printStorePath(*toPathMaybe)),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
if (!toPathMaybe) if (!toPathMaybe)
throw Error({ throw Error(
.msg = HintFmt( {.msg = HintFmt(
"rewriting '%s' to content-addressed form yielded '%s'\n" "rewriting '%s' to content-addressed form yielded '%s'\n"
"Use this value for the 'toPath' attribute passed to 'fetchClosure'", "Use this value for the 'toPath' attribute passed to 'fetchClosure'",
state.store->printStorePath(fromPath), state.store->printStorePath(fromPath),
state.store->printStorePath(rewrittenPath)), state.store->printStorePath(rewrittenPath)),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
} }
const auto & toPath = *toPathMaybe; const auto & toPath = *toPathMaybe;
@ -49,13 +55,12 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
if (!resultInfo->isContentAddressed(*state.store)) { if (!resultInfo->isContentAddressed(*state.store)) {
// We don't perform the rewriting when outPath already exists, as an optimisation. // We don't perform the rewriting when outPath already exists, as an optimisation.
// However, we can quickly detect a mistake if the toPath is input addressed. // However, we can quickly detect a mistake if the toPath is input addressed.
throw Error({ throw Error(
.msg = HintFmt( {.msg = HintFmt(
"The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n" "The 'toPath' value '%s' is input-addressed, so it can't possibly be the result of rewriting to a content-addressed path.\n\n"
"Set 'toPath' to an empty string to make Nix report the correct content-addressed path.", "Set 'toPath' to an empty string to make Nix report the correct content-addressed path.",
state.store->printStorePath(toPath)), state.store->printStorePath(toPath)),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
} }
state.mkStorePathString(toPath, v); state.mkStorePathString(toPath, v);
@ -64,7 +69,9 @@ static void runFetchClosureWithRewrite(EvalState & state, const PosIdx pos, Stor
/** /**
* Fetch the closure and make sure it's content addressed. * Fetch the closure and make sure it's content addressed.
*/ */
static void runFetchClosureWithContentAddressedPath(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v) { static void runFetchClosureWithContentAddressedPath(
EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v)
{
if (!state.store->isValidPath(fromPath)) if (!state.store->isValidPath(fromPath))
copyClosure(fromStore, *state.store, RealisedPath::Set{fromPath}); copyClosure(fromStore, *state.store, RealisedPath::Set{fromPath});
@ -72,16 +79,15 @@ static void runFetchClosureWithContentAddressedPath(EvalState & state, const Pos
auto info = state.store->queryPathInfo(fromPath); auto info = state.store->queryPathInfo(fromPath);
if (!info->isContentAddressed(*state.store)) { if (!info->isContentAddressed(*state.store)) {
throw Error({ throw Error(
.msg = HintFmt( {.msg = HintFmt(
"The 'fromPath' value '%s' is input-addressed, but 'inputAddressed' is set to 'false' (default).\n\n" "The 'fromPath' value '%s' is input-addressed, but 'inputAddressed' is set to 'false' (default).\n\n"
"If you do intend to fetch an input-addressed store path, add\n\n" "If you do intend to fetch an input-addressed store path, add\n\n"
" inputAddressed = true;\n\n" " inputAddressed = true;\n\n"
"to the 'fetchClosure' arguments.\n\n" "to the 'fetchClosure' arguments.\n\n"
"Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.", "Note that to ensure authenticity input-addressed store paths, users must configure a trusted binary cache public key on their systems. This is not needed for content-addressed paths.",
state.store->printStorePath(fromPath)), state.store->printStorePath(fromPath)),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
} }
state.mkStorePathString(fromPath, v); state.mkStorePathString(fromPath, v);
@ -90,7 +96,9 @@ static void runFetchClosureWithContentAddressedPath(EvalState & state, const Pos
/** /**
* Fetch the closure and make sure it's input addressed. * Fetch the closure and make sure it's input addressed.
*/ */
static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v) { static void runFetchClosureWithInputAddressedPath(
EvalState & state, const PosIdx pos, Store & fromStore, const StorePath & fromPath, Value & v)
{
if (!state.store->isValidPath(fromPath)) if (!state.store->isValidPath(fromPath))
copyClosure(fromStore, *state.store, RealisedPath::Set{fromPath}); copyClosure(fromStore, *state.store, RealisedPath::Set{fromPath});
@ -98,13 +106,12 @@ static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosId
auto info = state.store->queryPathInfo(fromPath); auto info = state.store->queryPathInfo(fromPath);
if (info->isContentAddressed(*state.store)) { if (info->isContentAddressed(*state.store)) {
throw Error({ throw Error(
.msg = HintFmt( {.msg = HintFmt(
"The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n" "The store object referred to by 'fromPath' at '%s' is not input-addressed, but 'inputAddressed' is set to 'true'.\n\n"
"Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed", "Remove the 'inputAddressed' attribute (it defaults to 'false') to expect 'fromPath' to be content-addressed",
state.store->printStorePath(fromPath)), state.store->printStorePath(fromPath)),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
} }
state.mkStorePathString(fromPath, v); state.mkStorePathString(fromPath, v);
@ -137,66 +144,57 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
bool isEmptyString = attr.value->type() == nString && attr.value->string_view() == ""; bool isEmptyString = attr.value->type() == nString && attr.value->string_view() == "";
if (isEmptyString) { if (isEmptyString) {
toPath = StorePathOrGap{}; toPath = StorePathOrGap{};
} } else {
else {
NixStringContext context; NixStringContext context;
toPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint()); toPath = state.coerceToStorePath(attr.pos, *attr.value, context, attrHint());
} }
} }
else if (attrName == "fromStore") else if (attrName == "fromStore")
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos, fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos, attrHint());
attrHint());
else if (attrName == "inputAddressed") else if (attrName == "inputAddressed")
inputAddressedMaybe = state.forceBool(*attr.value, attr.pos, attrHint()); inputAddressedMaybe = state.forceBool(*attr.value, attr.pos, attrHint());
else else
throw Error({ throw Error(
.msg = HintFmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName), {.msg = HintFmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
} }
if (!fromPath) if (!fromPath)
throw Error({ throw Error(
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"), {.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
bool inputAddressed = inputAddressedMaybe.value_or(false); bool inputAddressed = inputAddressedMaybe.value_or(false);
if (inputAddressed) { if (inputAddressed) {
if (toPath) if (toPath)
throw Error({ throw Error(
.msg = HintFmt("attribute '%s' is set to true, but '%s' is also set. Please remove one of them", {.msg = HintFmt(
"attribute '%s' is set to true, but '%s' is also set. Please remove one of them",
"inputAddressed", "inputAddressed",
"toPath"), "toPath"),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
} }
if (!fromStoreUrl) if (!fromStoreUrl)
throw Error({ throw Error(
.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"), {.msg = HintFmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
auto parsedURL = parseURL(*fromStoreUrl); auto parsedURL = parseURL(*fromStoreUrl);
if (parsedURL.scheme != "http" && if (parsedURL.scheme != "http" && parsedURL.scheme != "https"
parsedURL.scheme != "https" && && !(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file")) throw Error(
throw Error({ {.msg = HintFmt("'fetchClosure' only supports http:// and https:// stores"), .pos = state.positions[pos]});
.msg = HintFmt("'fetchClosure' only supports http:// and https:// stores"),
.pos = state.positions[pos]
});
if (!parsedURL.query.empty()) if (!parsedURL.query.empty())
throw Error({ throw Error(
.msg = HintFmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl), {.msg = HintFmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
.pos = state.positions[pos] .pos = state.positions[pos]});
});
auto fromStore = openStore(parsedURL.to_string()); auto fromStore = openStore(parsedURL.to_string());

View file

@ -23,31 +23,46 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
for (auto & attr : *args[0]->attrs()) { for (auto & attr : *args[0]->attrs()) {
std::string_view n(state.symbols[attr.name]); std::string_view n(state.symbols[attr.name]);
if (n == "url") if (n == "url")
url = state.coerceToString(attr.pos, *attr.value, context, url = state
.coerceToString(
attr.pos,
*attr.value,
context,
"while evaluating the `url` attribute passed to builtins.fetchMercurial", "while evaluating the `url` attribute passed to builtins.fetchMercurial",
false, false).toOwned(); false,
false)
.toOwned();
else if (n == "rev") { else if (n == "rev") {
// Ugly: unlike fetchGit, here the "rev" attribute can // Ugly: unlike fetchGit, here the "rev" attribute can
// be both a revision or a branch/tag name. // be both a revision or a branch/tag name.
auto value = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial"); auto value = state.forceStringNoCtx(
*attr.value, attr.pos, "while evaluating the `rev` attribute passed to builtins.fetchMercurial");
if (std::regex_match(value.begin(), value.end(), revRegex)) if (std::regex_match(value.begin(), value.end(), revRegex))
rev = Hash::parseAny(value, HashAlgorithm::SHA1); rev = Hash::parseAny(value, HashAlgorithm::SHA1);
else else
ref = value; ref = value;
} } else if (n == "name")
else if (n == "name") name = state.forceStringNoCtx(
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial"); *attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.fetchMercurial");
else else
state.error<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]).atPos(attr.pos).debugThrow(); state.error<EvalError>("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name])
.atPos(attr.pos)
.debugThrow();
} }
if (url.empty()) if (url.empty())
state.error<EvalError>("'url' argument required").atPos(pos).debugThrow(); state.error<EvalError>("'url' argument required").atPos(pos).debugThrow();
} else } else
url = state.coerceToString(pos, *args[0], context, url = state
.coerceToString(
pos,
*args[0],
context,
"while evaluating the first argument passed to builtins.fetchMercurial", "while evaluating the first argument passed to builtins.fetchMercurial",
false, false).toOwned(); false,
false)
.toOwned();
// FIXME: git externals probably can be used to bypass the URI // FIXME: git externals probably can be used to bypass the URI
// whitelist. Ah well. // whitelist. Ah well.
@ -60,8 +75,10 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
attrs.insert_or_assign("type", "hg"); attrs.insert_or_assign("type", "hg");
attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url); attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url);
attrs.insert_or_assign("name", std::string(name)); attrs.insert_or_assign("name", std::string(name));
if (ref) attrs.insert_or_assign("ref", *ref); if (ref)
if (rev) attrs.insert_or_assign("rev", rev->gitRev()); attrs.insert_or_assign("ref", *ref);
if (rev)
attrs.insert_or_assign("rev", rev->gitRev());
auto input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs)); auto input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
auto [storePath, input2] = input.fetchToStore(state.store); auto [storePath, input2] = input.fetchToStore(state.store);
@ -82,10 +99,6 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
state.allowPath(storePath); state.allowPath(storePath);
} }
static RegisterPrimOp r_fetchMercurial({ static RegisterPrimOp r_fetchMercurial({.name = "fetchMercurial", .arity = 1, .fun = prim_fetchMercurial});
.name = "fetchMercurial",
.arity = 1,
.fun = prim_fetchMercurial
});
} }

View file

@ -37,8 +37,7 @@ void emitTreeAttrs(
attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true)); attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true));
if (input.getType() == "git") if (input.getType() == "git")
attrs.alloc("submodules").mkBool( attrs.alloc("submodules").mkBool(fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false));
fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false));
if (!forceDirty) { if (!forceDirty) {
@ -56,7 +55,6 @@ void emitTreeAttrs(
attrs.alloc("revCount").mkInt(*revCount); attrs.alloc("revCount").mkInt(*revCount);
else if (emptyRevFallback) else if (emptyRevFallback)
attrs.alloc("revCount").mkInt(0); attrs.alloc("revCount").mkInt(0);
} }
if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) { if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) {
@ -66,14 +64,14 @@ void emitTreeAttrs(
if (auto lastModified = input.getLastModified()) { if (auto lastModified = input.getLastModified()) {
attrs.alloc("lastModified").mkInt(*lastModified); attrs.alloc("lastModified").mkInt(*lastModified);
attrs.alloc("lastModifiedDate").mkString( attrs.alloc("lastModifiedDate").mkString(fmt("%s", std::put_time(std::gmtime(&*lastModified), "%Y%m%d%H%M%S")));
fmt("%s", std::put_time(std::gmtime(&*lastModified), "%Y%m%d%H%M%S")));
} }
v.mkAttrs(attrs); v.mkAttrs(attrs);
} }
struct FetchTreeParams { struct FetchTreeParams
{
bool emptyRevFallback = false; bool emptyRevFallback = false;
bool allowNameArgument = false; bool allowNameArgument = false;
bool isFetchGit = false; bool isFetchGit = false;
@ -81,17 +79,14 @@ struct FetchTreeParams {
}; };
static void fetchTree( static void fetchTree(
EvalState & state, EvalState & state, const PosIdx pos, Value ** args, Value & v, const FetchTreeParams & params = FetchTreeParams{})
const PosIdx pos, {
Value * * args,
Value & v,
const FetchTreeParams & params = FetchTreeParams{}
) {
fetchers::Input input{state.fetchSettings}; fetchers::Input input{state.fetchSettings};
NixStringContext context; NixStringContext context;
std::optional<std::string> type; std::optional<std::string> type;
auto fetcher = params.isFetchGit ? "fetchGit" : "fetchTree"; auto fetcher = params.isFetchGit ? "fetchGit" : "fetchTree";
if (params.isFetchGit) type = "git"; if (params.isFetchGit)
type = "git";
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
@ -102,47 +97,55 @@ static void fetchTree(
if (auto aType = args[0]->attrs()->get(state.sType)) { if (auto aType = args[0]->attrs()->get(state.sType)) {
if (type) if (type)
state.error<EvalError>( state.error<EvalError>("unexpected argument 'type'").atPos(pos).debugThrow();
"unexpected argument 'type'" type = state.forceStringNoCtx(
).atPos(pos).debugThrow(); *aType->value, aType->pos, fmt("while evaluating the `type` argument passed to '%s'", fetcher));
type = state.forceStringNoCtx(*aType->value, aType->pos,
fmt("while evaluating the `type` argument passed to '%s'", fetcher));
} else if (!type) } else if (!type)
state.error<EvalError>( state.error<EvalError>("argument 'type' is missing in call to '%s'", fetcher).atPos(pos).debugThrow();
"argument 'type' is missing in call to '%s'", fetcher
).atPos(pos).debugThrow();
attrs.emplace("type", type.value()); attrs.emplace("type", type.value());
for (auto & attr : *args[0]->attrs()) { for (auto & attr : *args[0]->attrs()) {
if (attr.name == state.sType) continue; if (attr.name == state.sType)
continue;
state.forceValue(*attr.value, attr.pos); state.forceValue(*attr.value, attr.pos);
if (attr.value->type() == nPath || attr.value->type() == nString) { if (attr.value->type() == nPath || attr.value->type() == nString) {
auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned(); auto s = state.coerceToString(attr.pos, *attr.value, context, "", false, false).toOwned();
attrs.emplace(state.symbols[attr.name], attrs.emplace(
params.isFetchGit && state.symbols[attr.name] == "url" state.symbols[attr.name],
? fixGitURL(s) params.isFetchGit && state.symbols[attr.name] == "url" ? fixGitURL(s) : s);
: s); } else if (attr.value->type() == nBool)
}
else if (attr.value->type() == nBool)
attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean()}); attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean()});
else if (attr.value->type() == nInt) { else if (attr.value->type() == nInt) {
auto intValue = attr.value->integer().value; auto intValue = attr.value->integer().value;
if (intValue < 0) if (intValue < 0)
state.error<EvalError>("negative value given for '%s' argument '%s': %d", fetcher, state.symbols[attr.name], intValue).atPos(pos).debugThrow(); state
.error<EvalError>(
"negative value given for '%s' argument '%s': %d",
fetcher,
state.symbols[attr.name],
intValue)
.atPos(pos)
.debugThrow();
attrs.emplace(state.symbols[attr.name], uint64_t(intValue)); attrs.emplace(state.symbols[attr.name], uint64_t(intValue));
} else if (state.symbols[attr.name] == "publicKeys") { } else if (state.symbols[attr.name] == "publicKeys") {
experimentalFeatureSettings.require(Xp::VerifiedFetches); experimentalFeatureSettings.require(Xp::VerifiedFetches);
attrs.emplace(state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump()); attrs.emplace(
} state.symbols[attr.name], printValueAsJSON(state, true, *attr.value, pos, context).dump());
else } else
state.error<TypeError>("argument '%s' to '%s' is %s while a string, Boolean or integer is expected", state
state.symbols[attr.name], fetcher, showType(*attr.value)).debugThrow(); .error<TypeError>(
"argument '%s' to '%s' is %s while a string, Boolean or integer is expected",
state.symbols[attr.name],
fetcher,
showType(*attr.value))
.debugThrow();
} }
if (params.isFetchGit && !attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) { if (params.isFetchGit && !attrs.contains("exportIgnore")
&& (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) {
attrs.emplace("exportIgnore", Explicit<bool>{true}); attrs.emplace("exportIgnore", Explicit<bool>{true});
} }
@ -153,29 +156,38 @@ static void fetchTree(
if (!params.allowNameArgument) if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
state.error<EvalError>( state.error<EvalError>("argument 'name' isnt supported in call to '%s'", fetcher)
"argument 'name' isnt supported in call to '%s'", fetcher .atPos(pos)
).atPos(pos).debugThrow(); .debugThrow();
input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs)); input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
} else { } else {
auto url = state.coerceToString(pos, *args[0], context, auto url = state
.coerceToString(
pos,
*args[0],
context,
fmt("while evaluating the first argument passed to '%s'", fetcher), fmt("while evaluating the first argument passed to '%s'", fetcher),
false, false).toOwned(); false,
false)
.toOwned();
if (params.isFetchGit) { if (params.isFetchGit) {
fetchers::Attrs attrs; fetchers::Attrs attrs;
attrs.emplace("type", "git"); attrs.emplace("type", "git");
attrs.emplace("url", fixGitURL(url)); attrs.emplace("url", fixGitURL(url));
if (!attrs.contains("exportIgnore") && (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) { if (!attrs.contains("exportIgnore")
&& (!attrs.contains("submodules") || !*fetchers::maybeGetBoolAttr(attrs, "submodules"))) {
attrs.emplace("exportIgnore", Explicit<bool>{true}); attrs.emplace("exportIgnore", Explicit<bool>{true});
} }
input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs)); input = fetchers::Input::fromAttrs(state.fetchSettings, std::move(attrs));
} else { } else {
if (!experimentalFeatureSettings.isEnabled(Xp::Flakes)) if (!experimentalFeatureSettings.isEnabled(Xp::Flakes))
state.error<EvalError>( state
"passing a string argument to '%s' requires the 'flakes' experimental feature", fetcher .error<EvalError>(
).atPos(pos).debugThrow(); "passing a string argument to '%s' requires the 'flakes' experimental feature", fetcher)
.atPos(pos)
.debugThrow();
input = fetchers::Input::fromURL(state.fetchSettings, url); input = fetchers::Input::fromURL(state.fetchSettings, url);
} }
} }
@ -190,9 +202,11 @@ static void fetchTree(
"This is deprecated since such inputs are verifiable but may not be reproducible.", "This is deprecated since such inputs are verifiable but may not be reproducible.",
input.to_string()); input.to_string());
else else
state.error<EvalError>( state
"in pure evaluation mode, '%s' will not fetch unlocked input '%s'", .error<EvalError>(
fetcher, input.to_string()).atPos(pos).debugThrow(); "in pure evaluation mode, '%s' will not fetch unlocked input '%s'", fetcher, input.to_string())
.atPos(pos)
.debugThrow();
} }
state.checkURI(input.toURLString()); state.checkURI(input.toURLString());
@ -458,8 +472,14 @@ static RegisterPrimOp primop_fetchFinalTree({
.internal = true, .internal = true,
}); });
static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v, static void fetch(
const std::string & who, bool unpack, std::string name) EvalState & state,
const PosIdx pos,
Value ** args,
Value & v,
const std::string & who,
bool unpack,
std::string name)
{ {
std::optional<std::string> url; std::optional<std::string> url;
std::optional<Hash> expectedHash; std::optional<Hash> expectedHash;
@ -476,19 +496,20 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
if (n == "url") if (n == "url")
url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch"); url = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the url we should fetch");
else if (n == "sha256") else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"), HashAlgorithm::SHA256); expectedHash = newHashAllowEmpty(
state.forceStringNoCtx(
*attr.value, attr.pos, "while evaluating the sha256 of the content we should fetch"),
HashAlgorithm::SHA256);
else if (n == "name") { else if (n == "name") {
nameAttrPassed = true; nameAttrPassed = true;
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the name of the content we should fetch"); name = state.forceStringNoCtx(
} *attr.value, attr.pos, "while evaluating the name of the content we should fetch");
else } else
state.error<EvalError>("unsupported argument '%s' to '%s'", n, who) state.error<EvalError>("unsupported argument '%s' to '%s'", n, who).atPos(pos).debugThrow();
.atPos(pos).debugThrow();
} }
if (!url) if (!url)
state.error<EvalError>( state.error<EvalError>("'url' argument required").atPos(pos).debugThrow();
"'url' argument required").atPos(pos).debugThrow();
} else } else
url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch"); url = state.forceStringNoCtx(*args[0], pos, "while evaluating the url we should fetch");
@ -504,17 +525,32 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
checkName(name); checkName(name);
} catch (BadStorePathName & e) { } catch (BadStorePathName & e) {
auto resolution = auto resolution =
nameAttrPassed ? HintFmt("Please change the value for the 'name' attribute passed to '%s', so that it can create a valid store path.", who) : nameAttrPassed
isArgAttrs ? HintFmt("Please add a valid 'name' attribute to the argument for '%s', so that it can create a valid store path.", who) : ? HintFmt(
HintFmt("Please pass an attribute set with 'url' and 'name' attributes to '%s', so that it can create a valid store path.", who); "Please change the value for the 'name' attribute passed to '%s', so that it can create a valid store path.",
who)
: isArgAttrs
? HintFmt(
"Please add a valid 'name' attribute to the argument for '%s', so that it can create a valid store path.",
who)
: HintFmt(
"Please pass an attribute set with 'url' and 'name' attributes to '%s', so that it can create a valid store path.",
who);
state.error<EvalError>( state
std::string("invalid store path name when fetching URL '%s': %s. %s"), *url, Uncolored(e.message()), Uncolored(resolution.str())) .error<EvalError>(
.atPos(pos).debugThrow(); std::string("invalid store path name when fetching URL '%s': %s. %s"),
*url,
Uncolored(e.message()),
Uncolored(resolution.str()))
.atPos(pos)
.debugThrow();
} }
if (state.settings.pureEval && !expectedHash) if (state.settings.pureEval && !expectedHash)
state.error<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who).atPos(pos).debugThrow(); state.error<EvalError>("in pure evaluation mode, '%s' requires a 'sha256' argument", who)
.atPos(pos)
.debugThrow();
// early exit if pinned and already in the store // early exit if pinned and already in the store
if (expectedHash && expectedHash->algo == HashAlgorithm::SHA256) { if (expectedHash && expectedHash->algo == HashAlgorithm::SHA256) {
@ -523,8 +559,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
FixedOutputInfo{ FixedOutputInfo{
.method = unpack ? FileIngestionMethod::NixArchive : FileIngestionMethod::Flat, .method = unpack ? FileIngestionMethod::NixArchive : FileIngestionMethod::Flat,
.hash = *expectedHash, .hash = *expectedHash,
.references = {} .references = {}});
});
if (state.store->isValidPath(expectedPath)) { if (state.store->isValidPath(expectedPath)) {
state.allowAndSetStorePathString(expectedPath, v); state.allowAndSetStorePathString(expectedPath, v);
@ -534,9 +569,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
// TODO: fetching may fail, yet the path may be substitutable. // TODO: fetching may fail, yet the path may be substitutable.
// https://github.com/NixOS/nix/issues/4313 // https://github.com/NixOS/nix/issues/4313
auto storePath = auto storePath = unpack ? fetchToStore(
unpack
? fetchToStore(
state.fetchSettings, state.fetchSettings,
*state.store, *state.store,
fetchers::downloadTarball(state.store, state.fetchSettings, *url), fetchers::downloadTarball(state.store, state.fetchSettings, *url),
@ -545,16 +578,16 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
: fetchers::downloadFile(state.store, state.fetchSettings, *url, name).storePath; : fetchers::downloadFile(state.store, state.fetchSettings, *url, name).storePath;
if (expectedHash) { if (expectedHash) {
auto hash = unpack auto hash = unpack ? state.store->queryPathInfo(storePath)->narHash
? state.store->queryPathInfo(storePath)->narHash
: hashFile(HashAlgorithm::SHA256, state.store->toRealPath(storePath)); : hashFile(HashAlgorithm::SHA256, state.store->toRealPath(storePath));
if (hash != *expectedHash) { if (hash != *expectedHash) {
state.error<EvalError>( state
.error<EvalError>(
"hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s", "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
*url, *url,
expectedHash->to_string(HashFormat::Nix32, true), expectedHash->to_string(HashFormat::Nix32, true),
hash.to_string(HashFormat::Nix32, true) hash.to_string(HashFormat::Nix32, true))
).withExitStatus(102) .withExitStatus(102)
.debugThrow(); .debugThrow();
} }
} }
@ -640,12 +673,8 @@ static RegisterPrimOp primop_fetchTarball({
static void prim_fetchGit(EvalState & state, const PosIdx pos, Value ** args, Value & v) static void prim_fetchGit(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
fetchTree(state, pos, args, v, fetchTree(
FetchTreeParams { state, pos, args, v, FetchTreeParams{.emptyRevFallback = true, .allowNameArgument = true, .isFetchGit = true});
.emptyRevFallback = true,
.allowNameArgument = true,
.isFetchGit = true
});
} }
static RegisterPrimOp primop_fetchGit({ static RegisterPrimOp primop_fetchGit({

View file

@ -16,15 +16,15 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
std::function<void(Value &, toml::value)> visit; std::function<void(Value &, toml::value)> visit;
visit = [&](Value & v, toml::value t) { visit = [&](Value & v, toml::value t) {
switch (t.type()) {
switch(t.type()) case toml::value_t::table: {
{
case toml::value_t::table:
{
auto table = toml::get<toml::table>(t); auto table = toml::get<toml::table>(t);
size_t size = 0; size_t size = 0;
for (auto & i : table) { (void) i; size++; } for (auto & i : table) {
(void) i;
size++;
}
auto attrs = state.buildBindings(size); auto attrs = state.buildBindings(size);
@ -34,39 +34,39 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
} }
v.mkAttrs(attrs); v.mkAttrs(attrs);
} } break;
break;; ;
case toml::value_t::array: case toml::value_t::array: {
{
auto array = toml::get<std::vector<toml::value>>(t); auto array = toml::get<std::vector<toml::value>>(t);
auto list = state.buildList(array.size()); auto list = state.buildList(array.size());
for (const auto & [n, v] : enumerate(list)) for (const auto & [n, v] : enumerate(list))
visit(*(v = state.allocValue()), array[n]); visit(*(v = state.allocValue()), array[n]);
v.mkList(list); v.mkList(list);
} } break;
break;; ;
case toml::value_t::boolean: case toml::value_t::boolean:
v.mkBool(toml::get<bool>(t)); v.mkBool(toml::get<bool>(t));
break;; break;
;
case toml::value_t::integer: case toml::value_t::integer:
v.mkInt(toml::get<int64_t>(t)); v.mkInt(toml::get<int64_t>(t));
break;; break;
;
case toml::value_t::floating: case toml::value_t::floating:
v.mkFloat(toml::get<NixFloat>(t)); v.mkFloat(toml::get<NixFloat>(t));
break;; break;
case toml::value_t::string: ;
{ case toml::value_t::string: {
auto s = toml::get<std::string_view>(t); auto s = toml::get<std::string_view>(t);
forceNoNullByte(s); forceNoNullByte(s);
v.mkString(s); v.mkString(s);
} } break;
break;; ;
case toml::value_t::local_datetime: case toml::value_t::local_datetime:
case toml::value_t::offset_datetime: case toml::value_t::offset_datetime:
case toml::value_t::local_date: case toml::value_t::local_date:
case toml::value_t::local_time: case toml::value_t::local_time: {
{
if (experimentalFeatureSettings.isEnabled(Xp::ParseTomlTimestamps)) { if (experimentalFeatureSettings.isEnabled(Xp::ParseTomlTimestamps)) {
auto attrs = state.buildBindings(2); auto attrs = state.buildBindings(2);
attrs.alloc("_type").mkString("timestamp"); attrs.alloc("_type").mkString("timestamp");
@ -79,12 +79,12 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
} else { } else {
throw std::runtime_error("Dates and times are not supported"); throw std::runtime_error("Dates and times are not supported");
} }
} } break;
break;; ;
case toml::value_t::empty: case toml::value_t::empty:
v.mkNull(); v.mkNull();
break;; break;
;
} }
}; };
@ -95,8 +95,8 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, V
} }
} }
static RegisterPrimOp primop_fromTOML({ static RegisterPrimOp primop_fromTOML(
.name = "fromTOML", {.name = "fromTOML",
.args = {"e"}, .args = {"e"},
.doc = R"( .doc = R"(
Convert a TOML string to a Nix value. For example, Convert a TOML string to a Nix value. For example,
@ -112,7 +112,6 @@ static RegisterPrimOp primop_fromTOML({
returns the value `{ s = "a"; table = { y = 2; }; x = 1; }`. returns the value `{ s = "a"; table = { y = 2; }; x = 1; }`.
)", )",
.fun = prim_fromTOML .fun = prim_fromTOML});
});
} }

View file

@ -7,11 +7,7 @@ namespace nix {
// See: https://github.com/NixOS/nix/issues/9730 // See: https://github.com/NixOS/nix/issues/9730
void printAmbiguous( void printAmbiguous(
Value &v, Value & v, const SymbolTable & symbols, std::ostream & str, std::set<const void *> * seen, int depth)
const SymbolTable &symbols,
std::ostream &str,
std::set<const void *> *seen,
int depth)
{ {
checkInterrupt(); checkInterrupt();

View file

@ -28,9 +28,7 @@ void printElided(
output << ANSI_NORMAL; output << ANSI_NORMAL;
} }
std::ostream & printLiteralString(std::ostream & str, const std::string_view string, size_t maxLength, bool ansiColors)
std::ostream &
printLiteralString(std::ostream & str, const std::string_view string, size_t maxLength, bool ansiColors)
{ {
size_t charsPrinted = 0; size_t charsPrinted = 0;
if (ansiColors) if (ansiColors)
@ -43,12 +41,18 @@ printLiteralString(std::ostream & str, const std::string_view string, size_t max
return str; return str;
} }
if (*i == '\"' || *i == '\\') str << "\\" << *i; if (*i == '\"' || *i == '\\')
else if (*i == '\n') str << "\\n"; str << "\\" << *i;
else if (*i == '\r') str << "\\r"; else if (*i == '\n')
else if (*i == '\t') str << "\\t"; str << "\\n";
else if (*i == '$' && *(i+1) == '{') str << "\\" << *i; else if (*i == '\r')
else str << *i; str << "\\r";
else if (*i == '\t')
str << "\\t";
else if (*i == '$' && *(i + 1) == '{')
str << "\\" << *i;
else
str << *i;
charsPrinted++; charsPrinted++;
} }
str << "\""; str << "\"";
@ -57,14 +61,12 @@ printLiteralString(std::ostream & str, const std::string_view string, size_t max
return str; return str;
} }
std::ostream & std::ostream & printLiteralString(std::ostream & str, const std::string_view string)
printLiteralString(std::ostream & str, const std::string_view string)
{ {
return printLiteralString(str, string, std::numeric_limits<size_t>::max(), false); return printLiteralString(str, string, std::numeric_limits<size_t>::max(), false);
} }
std::ostream & std::ostream & printLiteralBool(std::ostream & str, bool boolean)
printLiteralBool(std::ostream & str, bool boolean)
{ {
str << (boolean ? "true" : "false"); str << (boolean ? "true" : "false");
return str; return str;
@ -80,13 +82,12 @@ printLiteralBool(std::ostream & str, bool boolean)
bool isReservedKeyword(const std::string_view str) bool isReservedKeyword(const std::string_view str)
{ {
static const std::unordered_set<std::string_view> reservedKeywords = { static const std::unordered_set<std::string_view> reservedKeywords = {
"if", "then", "else", "assert", "with", "let", "in", "rec", "inherit" "if", "then", "else", "assert", "with", "let", "in", "rec", "inherit"};
};
return reservedKeywords.contains(str); return reservedKeywords.contains(str);
} }
std::ostream & std::ostream & printIdentifier(std::ostream & str, std::string_view s)
printIdentifier(std::ostream & str, std::string_view s) { {
if (s.empty()) if (s.empty())
str << "\"\""; str << "\"\"";
else if (isReservedKeyword(s)) else if (isReservedKeyword(s))
@ -98,10 +99,8 @@ printIdentifier(std::ostream & str, std::string_view s) {
return str; return str;
} }
for (auto c : s) for (auto c : s)
if (!((c >= 'a' && c <= 'z') || if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '\''
(c >= 'A' && c <= 'Z') || || c == '-')) {
(c >= '0' && c <= '9') ||
c == '_' || c == '\'' || c == '-')) {
printLiteralString(str, s); printLiteralString(str, s);
return str; return str;
} }
@ -112,21 +111,22 @@ printIdentifier(std::ostream & str, std::string_view s) {
static bool isVarName(std::string_view s) static bool isVarName(std::string_view s)
{ {
if (s.size() == 0) return false; if (s.size() == 0)
if (isReservedKeyword(s)) return false; return false;
if (isReservedKeyword(s))
return false;
char c = s[0]; char c = s[0];
if ((c >= '0' && c <= '9') || c == '-' || c == '\'') return false; if ((c >= '0' && c <= '9') || c == '-' || c == '\'')
return false;
for (auto & i : s) for (auto & i : s)
if (!((i >= 'a' && i <= 'z') || if (!((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z') || (i >= '0' && i <= '9') || i == '_' || i == '-'
(i >= 'A' && i <= 'Z') || || i == '\''))
(i >= '0' && i <= '9') ||
i == '_' || i == '-' || i == '\''))
return false; return false;
return true; return true;
} }
std::ostream & std::ostream & printAttributeName(std::ostream & str, std::string_view name)
printAttributeName(std::ostream & str, std::string_view name) { {
if (isVarName(name)) if (isVarName(name))
str << name; str << name;
else else
@ -148,8 +148,7 @@ struct ImportantFirstAttrNameCmp
{ {
auto lhsIsImportant = isImportantAttrName(lhs.first); auto lhsIsImportant = isImportantAttrName(lhs.first);
auto rhsIsImportant = isImportantAttrName(rhs.first); auto rhsIsImportant = isImportantAttrName(rhs.first);
return std::forward_as_tuple(!lhsIsImportant, lhs.first) return std::forward_as_tuple(!lhsIsImportant, lhs.first) < std::forward_as_tuple(!rhsIsImportant, rhs.first);
< std::forward_as_tuple(!rhsIsImportant, rhs.first);
} }
}; };
@ -275,7 +274,8 @@ private:
std::optional<StorePath> storePath; std::optional<StorePath> storePath;
if (auto i = v.attrs()->get(state.sDrvPath)) { if (auto i = v.attrs()->get(state.sDrvPath)) {
NixStringContext context; NixStringContext context;
storePath = state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"); storePath =
state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation");
} }
/* This unfortunately breaks printing nested values because of /* This unfortunately breaks printing nested values because of
@ -593,8 +593,7 @@ private:
} }
} catch (Error & e) { } catch (Error & e) {
if (options.errors == ErrorPrintBehavior::Throw if (options.errors == ErrorPrintBehavior::Throw
|| (options.errors == ErrorPrintBehavior::ThrowTopLevel || (options.errors == ErrorPrintBehavior::ThrowTopLevel && depth == 0)) {
&& depth == 0)) {
throw; throw;
} }
printError_(e); printError_(e);
@ -603,7 +602,11 @@ private:
public: public:
Printer(std::ostream & output, EvalState & state, PrintOptions options) Printer(std::ostream & output, EvalState & state, PrintOptions options)
: output(output), state(state), options(options) { } : output(output)
, state(state)
, options(options)
{
}
void print(Value & v) void print(Value & v)
{ {

View file

@ -2,8 +2,7 @@
namespace nix { namespace nix {
std::optional<std::string_view> LookupPath::Prefix::suffixIfPotentialMatch( std::optional<std::string_view> LookupPath::Prefix::suffixIfPotentialMatch(std::string_view path) const
std::string_view path) const
{ {
auto n = s.size(); auto n = s.size();
@ -21,29 +20,25 @@ std::optional<std::string_view> LookupPath::Prefix::suffixIfPotentialMatch(
} }
/* Skip next path separator. */ /* Skip next path separator. */
return { return {path.substr(needSeparator ? n + 1 : n)};
path.substr(needSeparator ? n + 1 : n)
};
} }
LookupPath::Elem LookupPath::Elem::parse(std::string_view rawElem) LookupPath::Elem LookupPath::Elem::parse(std::string_view rawElem)
{ {
size_t pos = rawElem.find('='); size_t pos = rawElem.find('=');
return LookupPath::Elem{ return LookupPath::Elem{
.prefix = Prefix { .prefix =
.s = pos == std::string::npos Prefix{
? std::string { "" } .s = pos == std::string::npos ? std::string{""} : std::string{rawElem.substr(0, pos)},
: std::string { rawElem.substr(0, pos) },
}, },
.path = Path { .path =
Path{
.s = std::string{rawElem.substr(pos + 1)}, .s = std::string{rawElem.substr(pos + 1)},
}, },
}; };
} }
LookupPath LookupPath::parse(const Strings & rawElems) LookupPath LookupPath::parse(const Strings & rawElems)
{ {
LookupPath res; LookupPath res;

View file

@ -7,16 +7,16 @@
#include <iomanip> #include <iomanip>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
namespace nix { namespace nix {
using json = nlohmann::json; using json = nlohmann::json;
// TODO: rename. It doesn't print. // TODO: rename. It doesn't print.
json printValueAsJSON(EvalState & state, bool strict, json printValueAsJSON(
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore) EvalState & state, bool strict, Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore)
{ {
checkInterrupt(); checkInterrupt();
if (strict) state.forceValue(v, pos); if (strict)
state.forceValue(v, pos);
json out; json out;
@ -37,8 +37,7 @@ json printValueAsJSON(EvalState & state, bool strict,
case nPath: case nPath:
if (copyToStore) if (copyToStore)
out = state.store->printStorePath( out = state.store->printStorePath(state.copyPathToStore(context, v.path()));
state.copyPathToStore(context, v.path()));
else else
out = v.path().path.abs(); out = v.path().path.abs();
break; break;
@ -59,10 +58,12 @@ json printValueAsJSON(EvalState & state, bool strict,
out = json::object(); out = json::object();
for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) { for (auto & a : v.attrs()->lexicographicOrder(state.symbols)) {
try { try {
out.emplace(state.symbols[a->name], printValueAsJSON(state, strict, *a->value, a->pos, context, copyToStore)); out.emplace(
state.symbols[a->name],
printValueAsJSON(state, strict, *a->value, a->pos, context, copyToStore));
} catch (Error & e) { } catch (Error & e) {
e.addTrace(state.positions[a->pos], e.addTrace(
HintFmt("while evaluating attribute '%1%'", state.symbols[a->name])); state.positions[a->pos], HintFmt("while evaluating attribute '%1%'", state.symbols[a->name]));
throw; throw;
} }
} }
@ -77,8 +78,7 @@ json printValueAsJSON(EvalState & state, bool strict,
try { try {
out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore)); out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore));
} catch (Error & e) { } catch (Error & e) {
e.addTrace(state.positions[pos], e.addTrace(state.positions[pos], HintFmt("while evaluating list element at index %1%", i));
HintFmt("while evaluating list element at index %1%", i));
throw; throw;
} }
i++; i++;
@ -96,18 +96,19 @@ json printValueAsJSON(EvalState & state, bool strict,
case nThunk: case nThunk:
case nFunction: case nFunction:
state.error<TypeError>( state.error<TypeError>("cannot convert %1% to JSON", showType(v)).atPos(v.determinePos(pos)).debugThrow();
"cannot convert %1% to JSON",
showType(v)
)
.atPos(v.determinePos(pos))
.debugThrow();
} }
return out; return out;
} }
void printValueAsJSON(EvalState & state, bool strict, void printValueAsJSON(
Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore) EvalState & state,
bool strict,
Value & v,
const PosIdx pos,
std::ostream & str,
NixStringContext & context,
bool copyToStore)
{ {
try { try {
str << printValueAsJSON(state, strict, v, pos, context, copyToStore); str << printValueAsJSON(state, strict, v, pos, context, copyToStore);
@ -116,12 +117,10 @@ void printValueAsJSON(EvalState & state, bool strict,
} }
} }
json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, json ExternalValueBase::printValueAsJSON(
NixStringContext & context, bool copyToStore) const EvalState & state, bool strict, NixStringContext & context, bool copyToStore) const
{ {
state.error<TypeError>("cannot convert %1% to JSON", showType()) state.error<TypeError>("cannot convert %1% to JSON", showType()).debugThrow();
.debugThrow();
} }
} }

View file

@ -5,10 +5,8 @@
#include <cstdlib> #include <cstdlib>
namespace nix { namespace nix {
static XMLAttrs singletonAttrs(const std::string & name, std::string_view value) static XMLAttrs singletonAttrs(const std::string & name, std::string_view value)
{ {
XMLAttrs attrs; XMLAttrs attrs;
@ -16,12 +14,16 @@ static XMLAttrs singletonAttrs(const std::string & name, std::string_view value)
return attrs; return attrs;
} }
static void printValueAsXML(
static void printValueAsXML(EvalState & state, bool strict, bool location, EvalState & state,
Value & v, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen, bool strict,
bool location,
Value & v,
XMLWriter & doc,
NixStringContext & context,
PathSet & drvsSeen,
const PosIdx pos); const PosIdx pos);
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos) static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
{ {
if (auto path = std::get_if<SourcePath>(&pos.origin)) if (auto path = std::get_if<SourcePath>(&pos.origin))
@ -30,31 +32,42 @@ static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
xmlAttrs["column"] = fmt("%1%", pos.column); xmlAttrs["column"] = fmt("%1%", pos.column);
} }
static void showAttrs(
static void showAttrs(EvalState & state, bool strict, bool location, EvalState & state,
const Bindings & attrs, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen) bool strict,
bool location,
const Bindings & attrs,
XMLWriter & doc,
NixStringContext & context,
PathSet & drvsSeen)
{ {
StringSet names; StringSet names;
for (auto & a : attrs.lexicographicOrder(state.symbols)) { for (auto & a : attrs.lexicographicOrder(state.symbols)) {
XMLAttrs xmlAttrs; XMLAttrs xmlAttrs;
xmlAttrs["name"] = state.symbols[a->name]; xmlAttrs["name"] = state.symbols[a->name];
if (location && a->pos) posToXML(state, xmlAttrs, state.positions[a->pos]); if (location && a->pos)
posToXML(state, xmlAttrs, state.positions[a->pos]);
XMLOpenElement _(doc, "attr", xmlAttrs); XMLOpenElement _(doc, "attr", xmlAttrs);
printValueAsXML(state, strict, location, printValueAsXML(state, strict, location, *a->value, doc, context, drvsSeen, a->pos);
*a->value, doc, context, drvsSeen, a->pos);
} }
} }
static void printValueAsXML(
static void printValueAsXML(EvalState & state, bool strict, bool location, EvalState & state,
Value & v, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen, bool strict,
bool location,
Value & v,
XMLWriter & doc,
NixStringContext & context,
PathSet & drvsSeen,
const PosIdx pos) const PosIdx pos)
{ {
checkInterrupt(); checkInterrupt();
if (strict) state.forceValue(v, pos); if (strict)
state.forceValue(v, pos);
switch (v.type()) { switch (v.type()) {
@ -86,13 +99,15 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
Path drvPath; Path drvPath;
if (auto a = v.attrs()->get(state.sDrvPath)) { if (auto a = v.attrs()->get(state.sDrvPath)) {
if (strict) state.forceValue(*a->value, a->pos); if (strict)
state.forceValue(*a->value, a->pos);
if (a->value->type() == nString) if (a->value->type() == nString)
xmlAttrs["drvPath"] = drvPath = a->value->c_str(); xmlAttrs["drvPath"] = drvPath = a->value->c_str();
} }
if (auto a = v.attrs()->get(state.sOutPath)) { if (auto a = v.attrs()->get(state.sOutPath)) {
if (strict) state.forceValue(*a->value, a->pos); if (strict)
state.forceValue(*a->value, a->pos);
if (a->value->type() == nString) if (a->value->type() == nString)
xmlAttrs["outPath"] = a->value->c_str(); xmlAttrs["outPath"] = a->value->c_str();
} }
@ -126,13 +141,16 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
break; break;
} }
XMLAttrs xmlAttrs; XMLAttrs xmlAttrs;
if (location) posToXML(state, xmlAttrs, state.positions[v.payload.lambda.fun->pos]); if (location)
posToXML(state, xmlAttrs, state.positions[v.payload.lambda.fun->pos]);
XMLOpenElement _(doc, "function", xmlAttrs); XMLOpenElement _(doc, "function", xmlAttrs);
if (v.payload.lambda.fun->hasFormals()) { if (v.payload.lambda.fun->hasFormals()) {
XMLAttrs attrs; XMLAttrs attrs;
if (v.payload.lambda.fun->arg) attrs["name"] = state.symbols[v.payload.lambda.fun->arg]; if (v.payload.lambda.fun->arg)
if (v.payload.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1"; attrs["name"] = state.symbols[v.payload.lambda.fun->arg];
if (v.payload.lambda.fun->formals->ellipsis)
attrs["ellipsis"] = "1";
XMLOpenElement _(doc, "attrspat", attrs); XMLOpenElement _(doc, "attrspat", attrs);
for (auto & i : v.payload.lambda.fun->formals->lexicographicOrder(state.symbols)) for (auto & i : v.payload.lambda.fun->formals->lexicographicOrder(state.symbols))
doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name])); doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name]));
@ -155,17 +173,26 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
} }
} }
void ExternalValueBase::printValueAsXML(
void ExternalValueBase::printValueAsXML(EvalState & state, bool strict, EvalState & state,
bool location, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen, bool strict,
bool location,
XMLWriter & doc,
NixStringContext & context,
PathSet & drvsSeen,
const PosIdx pos) const const PosIdx pos) const
{ {
doc.writeEmptyElement("unevaluated"); doc.writeEmptyElement("unevaluated");
} }
void printValueAsXML(
void printValueAsXML(EvalState & state, bool strict, bool location, EvalState & state,
Value & v, std::ostream & out, NixStringContext & context, const PosIdx pos) bool strict,
bool location,
Value & v,
std::ostream & out,
NixStringContext & context,
const PosIdx pos)
{ {
XMLWriter doc(true, out); XMLWriter doc(true, out);
XMLOpenElement root(doc, "expr"); XMLOpenElement root(doc, "expr");
@ -173,5 +200,4 @@ void printValueAsXML(EvalState & state, bool strict, bool location,
printValueAsXML(state, strict, location, v, doc, context, drvsSeen, pos); printValueAsXML(state, strict, location, v, doc, context, drvsSeen, pos);
} }
} }

View file

@ -5,9 +5,7 @@
namespace nix { namespace nix {
NixStringContextElem NixStringContextElem::parse( NixStringContextElem NixStringContextElem::parse(std::string_view s0, const ExperimentalFeatureSettings & xpSettings)
std::string_view s0,
const ExperimentalFeatureSettings & xpSettings)
{ {
std::string_view s = s0; std::string_view s = s0;
@ -33,8 +31,7 @@ NixStringContextElem NixStringContextElem::parse(
}; };
if (s.size() == 0) { if (s.size() == 0) {
throw BadNixStringContextElem(s0, throw BadNixStringContextElem(s0, "String context element should never be an empty string");
"String context element should never be an empty string");
} }
switch (s.at(0)) { switch (s.at(0)) {
@ -44,13 +41,10 @@ NixStringContextElem NixStringContextElem::parse(
// Find *second* '!' // Find *second* '!'
if (s.find("!") == std::string_view::npos) { if (s.find("!") == std::string_view::npos) {
throw BadNixStringContextElem(s0, throw BadNixStringContextElem(s0, "String content element beginning with '!' should have a second '!'");
"String content element beginning with '!' should have a second '!'");
} }
return std::visit( return std::visit([&](auto x) -> NixStringContextElem { return std::move(x); }, parseRest());
[&](auto x) -> NixStringContextElem { return std::move(x); },
parseRest());
} }
case '=': { case '=': {
return NixStringContextElem::DrvDeep{ return NixStringContextElem::DrvDeep{
@ -60,12 +54,10 @@ NixStringContextElem NixStringContextElem::parse(
default: { default: {
// Ensure no '!' // Ensure no '!'
if (s.find("!") != std::string_view::npos) { if (s.find("!") != std::string_view::npos) {
throw BadNixStringContextElem(s0, throw BadNixStringContextElem(
"String content element not beginning with '!' should not have a second '!'"); s0, "String content element not beginning with '!' should not have a second '!'");
} }
return std::visit( return std::visit([&](auto x) -> NixStringContextElem { return std::move(x); }, parseRest());
[&](auto x) -> NixStringContextElem { return std::move(x); },
parseRest());
} }
} }
} }
@ -76,31 +68,31 @@ std::string NixStringContextElem::to_string() const
std::function<void(const SingleDerivedPath &)> toStringRest; std::function<void(const SingleDerivedPath &)> toStringRest;
toStringRest = [&](auto & p) { toStringRest = [&](auto & p) {
std::visit(overloaded { std::visit(
[&](const SingleDerivedPath::Opaque & o) { overloaded{
res += o.path.to_string(); [&](const SingleDerivedPath::Opaque & o) { res += o.path.to_string(); },
},
[&](const SingleDerivedPath::Built & o) { [&](const SingleDerivedPath::Built & o) {
res += o.output; res += o.output;
res += '!'; res += '!';
toStringRest(*o.drvPath); toStringRest(*o.drvPath);
}, },
}, p.raw()); },
p.raw());
}; };
std::visit(overloaded { std::visit(
overloaded{
[&](const NixStringContextElem::Built & b) { [&](const NixStringContextElem::Built & b) {
res += '!'; res += '!';
toStringRest(b); toStringRest(b);
}, },
[&](const NixStringContextElem::Opaque & o) { [&](const NixStringContextElem::Opaque & o) { toStringRest(o); },
toStringRest(o);
},
[&](const NixStringContextElem::DrvDeep & d) { [&](const NixStringContextElem::DrvDeep & d) {
res += '='; res += '=';
res += d.drvPath.to_string(); res += d.drvPath.to_string();
}, },
}, raw); },
raw);
return res; return res;
} }

View file

@ -13,13 +13,15 @@ class PublicKeyTest : public CharacterizationTest
std::filesystem::path unitTestData = getUnitTestData() / "public-key"; std::filesystem::path unitTestData = getUnitTestData() / "public-key";
public: public:
std::filesystem::path goldenMaster(std::string_view testStem) const override { std::filesystem::path goldenMaster(std::string_view testStem) const override
{
return unitTestData / testStem; return unitTestData / testStem;
} }
}; };
#define TEST_JSON(FIXTURE, NAME, VAL) \ #define TEST_JSON(FIXTURE, NAME, VAL) \
TEST_F(FIXTURE, PublicKey_ ## NAME ## _from_json) { \ TEST_F(FIXTURE, PublicKey_##NAME##_from_json) \
{ \
readTest(#NAME ".json", [&](const auto & encoded_) { \ readTest(#NAME ".json", [&](const auto & encoded_) { \
fetchers::PublicKey expected{VAL}; \ fetchers::PublicKey expected{VAL}; \
fetchers::PublicKey got = nlohmann::json::parse(encoded_); \ fetchers::PublicKey got = nlohmann::json::parse(encoded_); \
@ -27,14 +29,13 @@ public:
}); \ }); \
} \ } \
\ \
TEST_F(FIXTURE, PublicKey_ ## NAME ## _to_json) { \ TEST_F(FIXTURE, PublicKey_##NAME##_to_json) \
writeTest(#NAME ".json", [&]() -> json { \ { \
return nlohmann::json(fetchers::PublicKey { VAL }); \ writeTest( \
}, [](const auto & file) { \ #NAME ".json", \
return json::parse(readFile(file)); \ [&]() -> json { return nlohmann::json(fetchers::PublicKey{VAL}); }, \
}, [](const auto & file, const auto & got) { \ [](const auto & file) { return json::parse(readFile(file)); }, \
return writeFile(file, got.dump(2) + "\n"); \ [](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
}); \
} }
TEST_JSON(PublicKeyTest, simple, (fetchers::PublicKey{.type = "ssh-rsa", .key = "ABCDE"})) TEST_JSON(PublicKeyTest, simple, (fetchers::PublicKey{.type = "ssh-rsa", .key = "ABCDE"}))
@ -43,7 +44,8 @@ TEST_JSON(PublicKeyTest, defaultType, fetchers::PublicKey { .key = "ABCDE" })
#undef TEST_JSON #undef TEST_JSON
TEST_F(PublicKeyTest, PublicKey_noRoundTrip_from_json) { TEST_F(PublicKeyTest, PublicKey_noRoundTrip_from_json)
{
readTest("noRoundTrip.json", [&](const auto & encoded_) { readTest("noRoundTrip.json", [&](const auto & encoded_) {
fetchers::PublicKey expected = {.type = "ssh-ed25519", .key = "ABCDE"}; fetchers::PublicKey expected = {.type = "ssh-ed25519", .key = "ABCDE"};
fetchers::PublicKey got = nlohmann::json::parse(encoded_); fetchers::PublicKey got = nlohmann::json::parse(encoded_);

View file

@ -33,7 +33,8 @@ nlohmann::json attrsToJSON(const Attrs & attrs)
json[attr.first] = *v; json[attr.first] = *v;
} else if (auto v = std::get_if<Explicit<bool>>(&attr.second)) { } else if (auto v = std::get_if<Explicit<bool>>(&attr.second)) {
json[attr.first] = v->t; json[attr.first] = v->t;
} else unreachable(); } else
unreachable();
} }
return json; return json;
} }
@ -41,7 +42,8 @@ nlohmann::json attrsToJSON(const Attrs & attrs)
std::optional<std::string> maybeGetStrAttr(const Attrs & attrs, const std::string & name) std::optional<std::string> maybeGetStrAttr(const Attrs & attrs, const std::string & name)
{ {
auto i = attrs.find(name); auto i = attrs.find(name);
if (i == attrs.end()) return {}; if (i == attrs.end())
return {};
if (auto v = std::get_if<std::string>(&i->second)) if (auto v = std::get_if<std::string>(&i->second))
return *v; return *v;
throw Error("input attribute '%s' is not a string %s", name, attrsToJSON(attrs).dump()); throw Error("input attribute '%s' is not a string %s", name, attrsToJSON(attrs).dump());
@ -58,7 +60,8 @@ std::string getStrAttr(const Attrs & attrs, const std::string & name)
std::optional<uint64_t> maybeGetIntAttr(const Attrs & attrs, const std::string & name) std::optional<uint64_t> maybeGetIntAttr(const Attrs & attrs, const std::string & name)
{ {
auto i = attrs.find(name); auto i = attrs.find(name);
if (i == attrs.end()) return {}; if (i == attrs.end())
return {};
if (auto v = std::get_if<uint64_t>(&i->second)) if (auto v = std::get_if<uint64_t>(&i->second))
return *v; return *v;
throw Error("input attribute '%s' is not an integer", name); throw Error("input attribute '%s' is not an integer", name);
@ -75,7 +78,8 @@ uint64_t getIntAttr(const Attrs & attrs, const std::string & name)
std::optional<bool> maybeGetBoolAttr(const Attrs & attrs, const std::string & name) std::optional<bool> maybeGetBoolAttr(const Attrs & attrs, const std::string & name)
{ {
auto i = attrs.find(name); auto i = attrs.find(name);
if (i == attrs.end()) return {}; if (i == attrs.end())
return {};
if (auto v = std::get_if<Explicit<bool>>(&i->second)) if (auto v = std::get_if<Explicit<bool>>(&i->second))
return v->t; return v->t;
throw Error("input attribute '%s' is not a Boolean", name); throw Error("input attribute '%s' is not a Boolean", name);
@ -99,7 +103,8 @@ StringMap attrsToQuery(const Attrs & attrs)
query.insert_or_assign(attr.first, *v); query.insert_or_assign(attr.first, *v);
} else if (auto v = std::get_if<Explicit<bool>>(&attr.second)) { } else if (auto v = std::get_if<Explicit<bool>>(&attr.second)) {
query.insert_or_assign(attr.first, v->t ? "1" : "0"); query.insert_or_assign(attr.first, v->t ? "1" : "0");
} else unreachable(); } else
unreachable();
} }
return query; return query;
} }

View file

@ -44,46 +44,37 @@ struct CacheImpl : Cache
state->db.isCache(); state->db.isCache();
state->db.exec(schema); state->db.exec(schema);
state->upsert.create(state->db, state->upsert.create(
"insert or replace into Cache(domain, key, value, timestamp) values (?, ?, ?, ?)"); state->db, "insert or replace into Cache(domain, key, value, timestamp) values (?, ?, ?, ?)");
state->lookup.create(state->db, state->lookup.create(state->db, "select value, timestamp from Cache where domain = ? and key = ?");
"select value, timestamp from Cache where domain = ? and key = ?");
} }
void upsert( void upsert(const Key & key, const Attrs & value) override
const Key & key,
const Attrs & value) override
{ {
_state.lock()->upsert.use() _state.lock()
(key.first) ->upsert.use()(key.first)(attrsToJSON(key.second).dump())(attrsToJSON(value).dump())(time(0))
(attrsToJSON(key.second).dump()) .exec();
(attrsToJSON(value).dump())
(time(0)).exec();
} }
std::optional<Attrs> lookup( std::optional<Attrs> lookup(const Key & key) override
const Key & key) override
{ {
if (auto res = lookupExpired(key)) if (auto res = lookupExpired(key))
return std::move(res->value); return std::move(res->value);
return {}; return {};
} }
std::optional<Attrs> lookupWithTTL( std::optional<Attrs> lookupWithTTL(const Key & key) override
const Key & key) override
{ {
if (auto res = lookupExpired(key)) { if (auto res = lookupExpired(key)) {
if (!res->expired) if (!res->expired)
return std::move(res->value); return std::move(res->value);
debug("ignoring expired cache entry '%s:%s'", debug("ignoring expired cache entry '%s:%s'", key.first, attrsToJSON(key.second).dump());
key.first, attrsToJSON(key.second).dump());
} }
return {}; return {};
} }
std::optional<Result> lookupExpired( std::optional<Result> lookupExpired(const Key & key) override
const Key & key) override
{ {
auto state(_state.lock()); auto state(_state.lock());
@ -106,11 +97,7 @@ struct CacheImpl : Cache
}; };
} }
void upsert( void upsert(Key key, Store & store, Attrs value, const StorePath & storePath) override
Key key,
Store & store,
Attrs value,
const StorePath & storePath) override
{ {
/* Add the store prefix to the cache key to handle multiple /* Add the store prefix to the cache key to handle multiple
store prefixes. */ store prefixes. */
@ -121,14 +108,13 @@ struct CacheImpl : Cache
upsert(key, value); upsert(key, value);
} }
std::optional<ResultWithStorePath> lookupStorePath( std::optional<ResultWithStorePath> lookupStorePath(Key key, Store & store) override
Key key,
Store & store) override
{ {
key.second.insert_or_assign("store", store.storeDir); key.second.insert_or_assign("store", store.storeDir);
auto res = lookupExpired(key); auto res = lookupExpired(key);
if (!res) return std::nullopt; if (!res)
return std::nullopt;
auto storePathS = getStrAttr(res->value, "storePath"); auto storePathS = getStrAttr(res->value, "storePath");
res->value.erase("storePath"); res->value.erase("storePath");
@ -138,14 +124,16 @@ struct CacheImpl : Cache
store.addTempRoot(res2.storePath); store.addTempRoot(res2.storePath);
if (!store.isValidPath(res2.storePath)) { if (!store.isValidPath(res2.storePath)) {
// FIXME: we could try to substitute 'storePath'. // FIXME: we could try to substitute 'storePath'.
debug("ignoring disappeared cache entry '%s:%s' -> '%s'", debug(
"ignoring disappeared cache entry '%s:%s' -> '%s'",
key.first, key.first,
attrsToJSON(key.second).dump(), attrsToJSON(key.second).dump(),
store.printStorePath(res2.storePath)); store.printStorePath(res2.storePath));
return std::nullopt; return std::nullopt;
} }
debug("using cache entry '%s:%s' -> '%s', '%s'", debug(
"using cache entry '%s:%s' -> '%s', '%s'",
key.first, key.first,
attrsToJSON(key.second).dump(), attrsToJSON(key.second).dump(),
attrsToJSON(res2.value).dump(), attrsToJSON(res2.value).dump(),
@ -154,9 +142,7 @@ struct CacheImpl : Cache
return res2; return res2;
} }
std::optional<ResultWithStorePath> lookupStorePathWithTTL( std::optional<ResultWithStorePath> lookupStorePathWithTTL(Key key, Store & store) override
Key key,
Store & store) override
{ {
auto res = lookupStorePath(std::move(key), store); auto res = lookupStorePath(std::move(key), store);
return res && !res->expired ? res : std::nullopt; return res && !res->expired ? res : std::nullopt;

View file

@ -2,8 +2,6 @@
namespace nix::fetchers { namespace nix::fetchers {
Settings::Settings() Settings::Settings() {}
{
}
} }

View file

@ -5,18 +5,11 @@
namespace nix { namespace nix {
fetchers::Cache::Key makeFetchToStoreCacheKey( fetchers::Cache::Key makeFetchToStoreCacheKey(
const std::string & name, const std::string & name, const std::string & fingerprint, ContentAddressMethod method, const std::string & path)
const std::string & fingerprint,
ContentAddressMethod method,
const std::string & path)
{ {
return fetchers::Cache::Key{"fetchToStore", { return fetchers::Cache::Key{
{"name", name}, "fetchToStore",
{"fingerprint", fingerprint}, {{"name", name}, {"fingerprint", fingerprint}, {"method", std::string{method.render()}}, {"path", path}}};
{"method", std::string{method.render()}},
{"path", path}
}};
} }
StorePath fetchToStore( StorePath fetchToStore(
@ -43,17 +36,17 @@ StorePath fetchToStore(
} else } else
debug("source path '%s' is uncacheable", path); debug("source path '%s' is uncacheable", path);
Activity act(*logger, lvlChatty, actUnknown, Activity act(
*logger,
lvlChatty,
actUnknown,
fmt(mode == FetchMode::DryRun ? "hashing '%s'" : "copying '%s' to the store", path)); fmt(mode == FetchMode::DryRun ? "hashing '%s'" : "copying '%s' to the store", path));
auto filter2 = filter ? *filter : defaultPathFilter; auto filter2 = filter ? *filter : defaultPathFilter;
auto storePath = auto storePath = mode == FetchMode::DryRun
mode == FetchMode::DryRun ? store.computeStorePath(name, path, method, HashAlgorithm::SHA256, {}, filter2).first
? store.computeStorePath( : store.addToStore(name, path, method, HashAlgorithm::SHA256, {}, filter2, repair);
name, path, method, HashAlgorithm::SHA256, {}, filter2).first
: store.addToStore(
name, path, method, HashAlgorithm::SHA256, {}, filter2, repair);
debug(mode == FetchMode::DryRun ? "hashed '%s'" : "copied '%s' to '%s'", path, store.printStorePath(storePath)); debug(mode == FetchMode::DryRun ? "hashed '%s'" : "copied '%s' to '%s'", path, store.printStorePath(storePath));

View file

@ -39,9 +39,7 @@ nlohmann::json dumpRegisterInputSchemeInfo()
return res; return res;
} }
Input Input::fromURL( Input Input::fromURL(const Settings & settings, const std::string & url, bool requireTree)
const Settings & settings,
const std::string & url, bool requireTree)
{ {
return fromURL(settings, parseURL(url), requireTree); return fromURL(settings, parseURL(url), requireTree);
} }
@ -55,9 +53,7 @@ static void fixupInput(Input & input)
input.getLastModified(); input.getLastModified();
} }
Input Input::fromURL( Input Input::fromURL(const Settings & settings, const ParsedURL & url, bool requireTree)
const Settings & settings,
const ParsedURL & url, bool requireTree)
{ {
for (auto & [_, inputScheme] : inputSchemes()) { for (auto & [_, inputScheme] : inputSchemes()) {
auto res = inputScheme->inputFromURL(settings, url, requireTree); auto res = inputScheme->inputFromURL(settings, url, requireTree);
@ -97,7 +93,8 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
i ? *i : nullptr; i ? *i : nullptr;
}); });
if (!inputScheme) return raw(); if (!inputScheme)
return raw();
experimentalFeatureSettings.require(inputScheme->experimentalFeature()); experimentalFeatureSettings.require(inputScheme->experimentalFeature());
@ -108,7 +105,8 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
throw Error("input attribute '%s' not supported by scheme '%s'", name, schemeName); throw Error("input attribute '%s' not supported by scheme '%s'", name, schemeName);
auto res = inputScheme->inputFromAttrs(settings, attrs); auto res = inputScheme->inputFromAttrs(settings, attrs);
if (!res) return raw(); if (!res)
return raw();
res->scheme = inputScheme; res->scheme = inputScheme;
fixupInput(*res); fixupInput(*res);
return std::move(*res); return std::move(*res);
@ -116,9 +114,11 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
std::optional<std::string> Input::getFingerprint(ref<Store> store) const std::optional<std::string> Input::getFingerprint(ref<Store> store) const
{ {
if (!scheme) return std::nullopt; if (!scheme)
return std::nullopt;
if (cachedFingerprint) return *cachedFingerprint; if (cachedFingerprint)
return *cachedFingerprint;
auto fingerprint = scheme->getFingerprint(store, *this); auto fingerprint = scheme->getFingerprint(store, *this);
@ -180,11 +180,13 @@ bool Input::operator ==(const Input & other) const noexcept
bool Input::contains(const Input & other) const bool Input::contains(const Input & other) const
{ {
if (*this == other) return true; if (*this == other)
return true;
auto other2(other); auto other2(other);
other2.attrs.erase("ref"); other2.attrs.erase("ref");
other2.attrs.erase("rev"); other2.attrs.erase("rev");
if (*this == other2) return true; if (*this == other2)
return true;
return false; return false;
} }
@ -198,7 +200,8 @@ std::pair<StorePath, Input> Input::fetchToStore(ref<Store> store) const
try { try {
auto [accessor, result] = getAccessorUnchecked(store); auto [accessor, result] = getAccessorUnchecked(store);
auto storePath = nix::fetchToStore(*settings, *store, SourcePath(accessor), FetchMode::Copy, result.getName()); auto storePath =
nix::fetchToStore(*settings, *store, SourcePath(accessor), FetchMode::Copy, result.getName());
auto narHash = store->queryPathInfo(storePath)->narHash; auto narHash = store->queryPathInfo(storePath)->narHash;
result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true)); result.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
@ -237,7 +240,8 @@ void Input::checkLocks(Input specified, Input & result)
for (auto & field : specified.attrs) { for (auto & field : specified.attrs) {
auto field2 = result.attrs.find(field.first); auto field2 = result.attrs.find(field.first);
if (field2 != result.attrs.end() && field.second != field2->second) if (field2 != result.attrs.end() && field.second != field2->second)
throw Error("mismatch in field '%s' of input '%s', got '%s'", throw Error(
"mismatch in field '%s' of input '%s', got '%s'",
field.first, field.first,
attrsToJSON(specified.attrs), attrsToJSON(specified.attrs),
attrsToJSON(result.attrs)); attrsToJSON(result.attrs));
@ -251,30 +255,38 @@ void Input::checkLocks(Input specified, Input & result)
if (auto prevNarHash = specified.getNarHash()) { if (auto prevNarHash = specified.getNarHash()) {
if (result.getNarHash() != prevNarHash) { if (result.getNarHash() != prevNarHash) {
if (result.getNarHash()) if (result.getNarHash())
throw Error((unsigned int) 102, "NAR hash mismatch in input '%s', expected '%s' but got '%s'", throw Error(
specified.to_string(), prevNarHash->to_string(HashFormat::SRI, true), result.getNarHash()->to_string(HashFormat::SRI, true)); (unsigned int) 102,
"NAR hash mismatch in input '%s', expected '%s' but got '%s'",
specified.to_string(),
prevNarHash->to_string(HashFormat::SRI, true),
result.getNarHash()->to_string(HashFormat::SRI, true));
else else
throw Error((unsigned int) 102, "NAR hash mismatch in input '%s', expected '%s' but got none", throw Error(
specified.to_string(), prevNarHash->to_string(HashFormat::SRI, true)); (unsigned int) 102,
"NAR hash mismatch in input '%s', expected '%s' but got none",
specified.to_string(),
prevNarHash->to_string(HashFormat::SRI, true));
} }
} }
if (auto prevLastModified = specified.getLastModified()) { if (auto prevLastModified = specified.getLastModified()) {
if (result.getLastModified() != prevLastModified) if (result.getLastModified() != prevLastModified)
throw Error("'lastModified' attribute mismatch in input '%s', expected %d, got %d", throw Error(
result.to_string(), *prevLastModified, result.getLastModified().value_or(-1)); "'lastModified' attribute mismatch in input '%s', expected %d, got %d",
result.to_string(),
*prevLastModified,
result.getLastModified().value_or(-1));
} }
if (auto prevRev = specified.getRev()) { if (auto prevRev = specified.getRev()) {
if (result.getRev() != prevRev) if (result.getRev() != prevRev)
throw Error("'rev' attribute mismatch in input '%s', expected %s", throw Error("'rev' attribute mismatch in input '%s', expected %s", result.to_string(), prevRev->gitRev());
result.to_string(), prevRev->gitRev());
} }
if (auto prevRevCount = specified.getRevCount()) { if (auto prevRevCount = specified.getRevCount()) {
if (result.getRevCount() != prevRevCount) if (result.getRevCount() != prevRevCount)
throw Error("'revCount' attribute mismatch in input '%s', expected %d", throw Error("'revCount' attribute mismatch in input '%s', expected %d", result.to_string(), *prevRevCount);
result.to_string(), *prevRevCount);
} }
} }
@ -318,8 +330,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
store->ensurePath(storePath); store->ensurePath(storePath);
debug("using substituted/cached input '%s' in '%s'", debug("using substituted/cached input '%s' in '%s'", to_string(), store->printStorePath(storePath));
to_string(), store->printStorePath(storePath));
auto accessor = makeStorePathAccessor(store, storePath); auto accessor = makeStorePathAccessor(store, storePath);
@ -341,11 +352,10 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
return {accessor, std::move(result)}; return {accessor, std::move(result)};
} }
Input Input::applyOverrides( Input Input::applyOverrides(std::optional<std::string> ref, std::optional<Hash> rev) const
std::optional<std::string> ref,
std::optional<Hash> rev) const
{ {
if (!scheme) return *this; if (!scheme)
return *this;
return scheme->applyOverrides(*this, ref, rev); return scheme->applyOverrides(*this, ref, rev);
} }
@ -361,10 +371,7 @@ std::optional<std::filesystem::path> Input::getSourcePath() const
return scheme->getSourcePath(*this); return scheme->getSourcePath(*this);
} }
void Input::putFile( void Input::putFile(const CanonPath & path, std::string_view contents, std::optional<std::string> commitMsg) const
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const
{ {
assert(scheme); assert(scheme);
return scheme->putFile(*this, path, contents, commitMsg); return scheme->putFile(*this, path, contents, commitMsg);
@ -380,7 +387,9 @@ StorePath Input::computeStorePath(Store & store) const
auto narHash = getNarHash(); auto narHash = getNarHash();
if (!narHash) if (!narHash)
throw Error("cannot compute store path for unlocked input '%s'", to_string()); throw Error("cannot compute store path for unlocked input '%s'", to_string());
return store.makeFixedOutputPath(getName(), FixedOutputInfo { return store.makeFixedOutputPath(
getName(),
FixedOutputInfo{
.method = FileIngestionMethod::NixArchive, .method = FileIngestionMethod::NixArchive,
.hash = *narHash, .hash = *narHash,
.references = {}, .references = {},
@ -446,10 +455,7 @@ ParsedURL InputScheme::toURL(const Input & input) const
throw Error("don't know how to convert input '%s' to a URL", attrsToJSON(input.attrs)); throw Error("don't know how to convert input '%s' to a URL", attrsToJSON(input.attrs));
} }
Input InputScheme::applyOverrides( Input InputScheme::applyOverrides(const Input & input, std::optional<std::string> ref, std::optional<Hash> rev) const
const Input & input,
std::optional<std::string> ref,
std::optional<Hash> rev) const
{ {
if (ref) if (ref)
throw Error("don't know how to set branch/tag name of input '%s' to '%s'", input.to_string(), *ref); throw Error("don't know how to set branch/tag name of input '%s' to '%s'", input.to_string(), *ref);
@ -464,10 +470,7 @@ std::optional<std::filesystem::path> InputScheme::getSourcePath(const Input & in
} }
void InputScheme::putFile( void InputScheme::putFile(
const Input & input, const Input & input, const CanonPath & path, std::string_view contents, std::optional<std::string> commitMsg) const
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const
{ {
throw Error("input '%s' does not support modifying file '%s'", input.to_string(), path); throw Error("input '%s' does not support modifying file '%s'", input.to_string(), path);
} }

View file

@ -50,8 +50,7 @@ std::string FilteringSourceAccessor::showPath(const CanonPath & path)
void FilteringSourceAccessor::checkAccess(const CanonPath & path) void FilteringSourceAccessor::checkAccess(const CanonPath & path)
{ {
if (!isAllowed(path)) if (!isAllowed(path))
throw makeNotAllowedError throw makeNotAllowedError ? makeNotAllowedError(path)
? makeNotAllowedError(path)
: RestrictedPathError("access to path '%s' is forbidden", showPath(path)); : RestrictedPathError("access to path '%s' is forbidden", showPath(path));
} }
@ -68,13 +67,12 @@ struct AllowListSourceAccessorImpl : AllowListSourceAccessor
: AllowListSourceAccessor(SourcePath(next), std::move(makeNotAllowedError)) : AllowListSourceAccessor(SourcePath(next), std::move(makeNotAllowedError))
, allowedPrefixes(std::move(allowedPrefixes)) , allowedPrefixes(std::move(allowedPrefixes))
, allowedPaths(std::move(allowedPaths)) , allowedPaths(std::move(allowedPaths))
{ } {
}
bool isAllowed(const CanonPath & path) override bool isAllowed(const CanonPath & path) override
{ {
return return allowedPaths.contains(path) || path.isAllowed(allowedPrefixes);
allowedPaths.contains(path)
|| path.isAllowed(allowedPrefixes);
} }
void allowPrefix(CanonPath prefix) override void allowPrefix(CanonPath prefix) override
@ -90,16 +88,14 @@ ref<AllowListSourceAccessor> AllowListSourceAccessor::create(
MakeNotAllowedError && makeNotAllowedError) MakeNotAllowedError && makeNotAllowedError)
{ {
return make_ref<AllowListSourceAccessorImpl>( return make_ref<AllowListSourceAccessorImpl>(
next, next, std::move(allowedPrefixes), std::move(allowedPaths), std::move(makeNotAllowedError));
std::move(allowedPrefixes),
std::move(allowedPaths),
std::move(makeNotAllowedError));
} }
bool CachingFilteringSourceAccessor::isAllowed(const CanonPath & path) bool CachingFilteringSourceAccessor::isAllowed(const CanonPath & path)
{ {
auto i = cache.find(path); auto i = cache.find(path);
if (i != cache.end()) return i->second; if (i != cache.end())
return i->second;
auto res = isAllowedUncached(path); auto res = isAllowedUncached(path);
cache.emplace(path, res); cache.emplace(path, res);
return res; return res;

View file

@ -37,7 +37,8 @@
namespace std { namespace std {
template<> struct hash<git_oid> template<>
struct hash<git_oid>
{ {
size_t operator()(const git_oid & oid) const size_t operator()(const git_oid & oid) const
{ {
@ -147,7 +148,8 @@ static Object peelToTreeOrBlob(git_object * obj)
return peelObject<Object>(obj, GIT_OBJECT_TREE); return peelObject<Object>(obj, GIT_OBJECT_TREE);
} }
struct PackBuilderContext { struct PackBuilderContext
{
std::exception_ptr exception; std::exception_ptr exception;
void handleException(const char * activity, int errCode) void handleException(const char * activity, int errCode)
@ -188,7 +190,8 @@ static git_packbuilder_progress PACKBUILDER_PROGRESS_CHECK_INTERRUPT = &packBuil
static void initRepoAtomically(std::filesystem::path & path, bool bare) static void initRepoAtomically(std::filesystem::path & path, bool bare)
{ {
if (pathExists(path.string())) return; if (pathExists(path.string()))
return;
Path tmpDir = createTempDir(os_string_to_string(PathViewNG{std::filesystem::path(path).parent_path()})); Path tmpDir = createTempDir(os_string_to_string(PathViewNG{std::filesystem::path(path).parent_path()}));
AutoDelete delTmpDir(tmpDir, true); AutoDelete delTmpDir(tmpDir, true);
@ -204,8 +207,7 @@ static void initRepoAtomically(std::filesystem::path &path, bool bare)
// `path` may be attempted to be deleted by s::f::rename, in which case the code is: // `path` may be attempted to be deleted by s::f::rename, in which case the code is:
|| e.code() == std::errc::directory_not_empty) { || e.code() == std::errc::directory_not_empty) {
return; return;
} } else
else
throw SysError("moving temporary git repository from %s to %s", tmpDir, path); throw SysError("moving temporary git repository from %s to %s", tmpDir, path);
} }
// we successfully moved the repository, so the temporary directory no longer exists. // we successfully moved the repository, so the temporary directory no longer exists.
@ -254,7 +256,8 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
return repo.get(); return repo.get();
} }
void flush() override { void flush() override
{
checkInterrupt(); checkInterrupt();
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
@ -266,14 +269,9 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
git_packbuilder_set_threads(packBuilder.get(), 0 /* autodetect */); git_packbuilder_set_threads(packBuilder.get(), 0 /* autodetect */);
packBuilderContext.handleException( packBuilderContext.handleException(
"preparing packfile", "preparing packfile", git_mempack_write_thin_pack(mempack_backend, packBuilder.get()));
git_mempack_write_thin_pack(mempack_backend, packBuilder.get())
);
checkInterrupt(); checkInterrupt();
packBuilderContext.handleException( packBuilderContext.handleException("writing packfile", git_packbuilder_write_buf(&buf, packBuilder.get()));
"writing packfile",
git_packbuilder_write_buf(&buf, packBuilder.get())
);
checkInterrupt(); checkInterrupt();
std::string repo_path = std::string(git_repository_path(repo.get())); std::string repo_path = std::string(git_repository_path(repo.get()));
@ -318,12 +316,16 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
todo.push(peelObject<Commit>(lookupObject(*this, hashToOID(rev)).get(), GIT_OBJECT_COMMIT)); todo.push(peelObject<Commit>(lookupObject(*this, hashToOID(rev)).get(), GIT_OBJECT_COMMIT));
while (auto commit = pop(todo)) { while (auto commit = pop(todo)) {
if (!done.insert(*git_commit_id(commit->get())).second) continue; if (!done.insert(*git_commit_id(commit->get())).second)
continue;
for (size_t n = 0; n < git_commit_parentcount(commit->get()); ++n) { for (size_t n = 0; n < git_commit_parentcount(commit->get()); ++n) {
git_commit * parent; git_commit * parent;
if (git_commit_parent(&parent, commit->get(), n)) if (git_commit_parent(&parent, commit->get(), n))
throw Error("getting parent of Git commit '%s': %s", *git_commit_id(commit->get()), git_error_last()->message); throw Error(
"getting parent of Git commit '%s': %s",
*git_commit_id(commit->get()),
git_error_last()->message);
todo.push(Commit(parent)); todo.push(Commit(parent));
} }
} }
@ -373,7 +375,8 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
while (true) { while (true) {
git_config_entry * entry = nullptr; git_config_entry * entry = nullptr;
if (auto err = git_config_next(&entry, it.get())) { if (auto err = git_config_next(&entry, it.get())) {
if (err == GIT_ITEROVER) break; if (err == GIT_ITEROVER)
break;
throw Error("iterating over .gitmodules: %s", git_error_last()->message); throw Error("iterating over .gitmodules: %s", git_error_last()->message);
} }
entries.emplace(entry->name + 10, entry->value); entries.emplace(entry->name + 10, entry->value);
@ -382,10 +385,12 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
std::vector<Submodule> result; std::vector<Submodule> result;
for (auto & [key, value] : entries) { for (auto & [key, value] : entries) {
if (!hasSuffix(key, ".path")) continue; if (!hasSuffix(key, ".path"))
continue;
std::string key2(key, 0, key.size() - 5); std::string key2(key, 0, key.size() - 5);
auto path = CanonPath(value); auto path = CanonPath(value);
result.push_back(Submodule { result.push_back(
Submodule{
.path = path, .path = path,
.url = entries[key2 + ".url"], .url = entries[key2 + ".url"],
.branch = entries[key2 + ".branch"], .branch = entries[key2 + ".branch"],
@ -415,11 +420,9 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
/* Get all tracked files and determine whether the working /* Get all tracked files and determine whether the working
directory is dirty. */ directory is dirty. */
std::function<int(const char * path, unsigned int statusFlags)> statusCallback = [&](const char * path, unsigned int statusFlags) std::function<int(const char * path, unsigned int statusFlags)> statusCallback = [&](const char * path,
{ unsigned int statusFlags) {
if (!(statusFlags & GIT_STATUS_INDEX_DELETED) && if (!(statusFlags & GIT_STATUS_INDEX_DELETED) && !(statusFlags & GIT_STATUS_WT_DELETED)) {
!(statusFlags & GIT_STATUS_WT_DELETED))
{
info.files.insert(CanonPath(path)); info.files.insert(CanonPath(path));
if (statusFlags != GIT_STATUS_CURRENT) if (statusFlags != GIT_STATUS_CURRENT)
info.dirtyFiles.insert(CanonPath(path)); info.dirtyFiles.insert(CanonPath(path));
@ -475,7 +478,8 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
Object obj; Object obj;
if (auto errCode = git_object_lookup(Setter(obj), *this, &oid, GIT_OBJECT_ANY)) { if (auto errCode = git_object_lookup(Setter(obj), *this, &oid, GIT_OBJECT_ANY)) {
if (errCode == GIT_ENOTFOUND) return false; if (errCode == GIT_ENOTFOUND)
return false;
auto err = git_error_last(); auto err = git_error_last();
throw Error("getting Git object '%s': %s", oid, err->message); throw Error("getting Git object '%s': %s", oid, err->message);
} }
@ -486,15 +490,10 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
/** /**
* A 'GitSourceAccessor' with no regard for export-ignore or any other transformations. * A 'GitSourceAccessor' with no regard for export-ignore or any other transformations.
*/ */
ref<GitSourceAccessor> getRawAccessor( ref<GitSourceAccessor> getRawAccessor(const Hash & rev, bool smudgeLfs = false);
const Hash & rev,
bool smudgeLfs = false);
ref<SourceAccessor> getAccessor( ref<SourceAccessor>
const Hash & rev, getAccessor(const Hash & rev, bool exportIgnore, std::string displayPrefix, bool smudgeLfs = false) override;
bool exportIgnore,
std::string displayPrefix,
bool smudgeLfs = false) override;
ref<SourceAccessor> getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError e) override; ref<SourceAccessor> getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError e) override;
@ -510,7 +509,8 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
static int transferProgressCallback(const git_indexer_progress * stats, void * payload) static int transferProgressCallback(const git_indexer_progress * stats, void * payload)
{ {
auto act = (Activity *) payload; auto act = (Activity *) payload;
act->result(resFetchStatus, act->result(
resFetchStatus,
fmt("%d/%d objects received, %d/%d deltas indexed, %.1f MiB", fmt("%d/%d objects received, %d/%d deltas indexed, %.1f MiB",
stats->received_objects, stats->received_objects,
stats->total_objects, stats->total_objects,
@ -520,14 +520,12 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
return getInterrupted() ? -1 : 0; return getInterrupted() ? -1 : 0;
} }
void fetch( void fetch(const std::string & url, const std::string & refspec, bool shallow) override
const std::string & url,
const std::string & refspec,
bool shallow) override
{ {
Activity act(*logger, lvlTalkative, actFetchTree, fmt("fetching Git repository '%s'", url)); Activity act(*logger, lvlTalkative, actFetchTree, fmt("fetching Git repository '%s'", url));
// TODO: implement git-credential helper support (preferably via libgit2, which as of 2024-01 does not support that) // TODO: implement git-credential helper support (preferably via libgit2, which as of 2024-01 does not support
// that)
// then use code that was removed in this commit (see blame) // then use code that was removed in this commit (see blame)
auto dir = this->path; auto dir = this->path;
@ -536,53 +534,50 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
append(gitArgs, {"--depth", "1"}); append(gitArgs, {"--depth", "1"});
append(gitArgs, {std::string("--"), url, refspec}); append(gitArgs, {std::string("--"), url, refspec});
runProgram(RunOptions { runProgram(
RunOptions{
.program = "git", .program = "git",
.lookupPath = true, .lookupPath = true,
// FIXME: git stderr messes up our progress indicator, so // FIXME: git stderr messes up our progress indicator, so
// we're using --quiet for now. Should process its stderr. // we're using --quiet for now. Should process its stderr.
.args = gitArgs, .args = gitArgs,
.input = {}, .input = {},
.isInteractive = true .isInteractive = true});
});
} }
void verifyCommit( void verifyCommit(const Hash & rev, const std::vector<fetchers::PublicKey> & publicKeys) override
const Hash & rev,
const std::vector<fetchers::PublicKey> & publicKeys) override
{ {
// Create ad-hoc allowedSignersFile and populate it with publicKeys // Create ad-hoc allowedSignersFile and populate it with publicKeys
auto allowedSignersFile = createTempFile().second; auto allowedSignersFile = createTempFile().second;
std::string allowedSigners; std::string allowedSigners;
for (const fetchers::PublicKey & k : publicKeys) { for (const fetchers::PublicKey & k : publicKeys) {
if (k.type != "ssh-dsa" if (k.type != "ssh-dsa" && k.type != "ssh-ecdsa" && k.type != "ssh-ecdsa-sk" && k.type != "ssh-ed25519"
&& k.type != "ssh-ecdsa" && k.type != "ssh-ed25519-sk" && k.type != "ssh-rsa")
&& k.type != "ssh-ecdsa-sk" throw Error(
&& k.type != "ssh-ed25519" "Unknown key type '%s'.\n"
&& k.type != "ssh-ed25519-sk"
&& k.type != "ssh-rsa")
throw Error("Unknown key type '%s'.\n"
"Please use one of\n" "Please use one of\n"
"- ssh-dsa\n" "- ssh-dsa\n"
" ssh-ecdsa\n" " ssh-ecdsa\n"
" ssh-ecdsa-sk\n" " ssh-ecdsa-sk\n"
" ssh-ed25519\n" " ssh-ed25519\n"
" ssh-ed25519-sk\n" " ssh-ed25519-sk\n"
" ssh-rsa", k.type); " ssh-rsa",
k.type);
allowedSigners += "* " + k.type + " " + k.key + "\n"; allowedSigners += "* " + k.type + " " + k.key + "\n";
} }
writeFile(allowedSignersFile, allowedSigners); writeFile(allowedSignersFile, allowedSigners);
// Run verification command // Run verification command
auto [status, output] = runProgram(RunOptions { auto [status, output] = runProgram(
RunOptions{
.program = "git", .program = "git",
.args = { .args =
"-c", {"-c",
"gpg.ssh.allowedSignersFile=" + allowedSignersFile, "gpg.ssh.allowedSignersFile=" + allowedSignersFile,
"-C", path.string(), "-C",
path.string(),
"verify-commit", "verify-commit",
rev.gitRev() rev.gitRev()},
},
.mergeStderrToStdout = true, .mergeStderrToStdout = true,
}); });
@ -600,7 +595,8 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while decoding public key '%s' used for git signature", k.key); e.addTrace({}, "while decoding public key '%s' used for git signature", k.key);
} }
auto fingerprint = trim(hashString(HashAlgorithm::SHA256, keyDecoded).to_string(nix::HashFormat::Base64, false), "="); auto fingerprint =
trim(hashString(HashAlgorithm::SHA256, keyDecoded).to_string(nix::HashFormat::Base64, false), "=");
auto escaped_fingerprint = std::regex_replace(fingerprint, std::regex("\\+"), "\\+"); auto escaped_fingerprint = std::regex_replace(fingerprint, std::regex("\\+"), "\\+");
re += "(" + escaped_fingerprint + ")"; re += "(" + escaped_fingerprint + ")";
} }
@ -676,7 +672,8 @@ struct GitSourceAccessor : SourceAccessor
if (lfsFetch->shouldFetch(path)) { if (lfsFetch->shouldFetch(path)) {
StringSink s; StringSink s;
try { try {
auto contents = std::string((const char *) git_blob_rawcontent(blob.get()), git_blob_rawsize(blob.get())); auto contents =
std::string((const char *) git_blob_rawcontent(blob.get()), git_blob_rawsize(blob.get()));
lfsFetch->fetch(contents, path, s, [&s](uint64_t size) { s.s.reserve(size); }); lfsFetch->fetch(contents, path, s, [&s](uint64_t size) { s.s.reserve(size); });
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while smudging git-lfs file '%s'", path); e.addTrace({}, "while smudging git-lfs file '%s'", path);
@ -732,7 +729,8 @@ struct GitSourceAccessor : SourceAccessor
DirEntries readDirectory(const CanonPath & path) override DirEntries readDirectory(const CanonPath & path) override
{ {
return std::visit(overloaded { return std::visit(
overloaded{
[&](Tree tree) { [&](Tree tree) {
DirEntries res; DirEntries res;
@ -746,10 +744,8 @@ struct GitSourceAccessor : SourceAccessor
return res; return res;
}, },
[&](Submodule) { [&](Submodule) { return DirEntries(); }},
return DirEntries(); getTree(path));
}
}, getTree(path));
} }
std::string readLink(const CanonPath & path) override std::string readLink(const CanonPath & path) override
@ -777,15 +773,18 @@ struct GitSourceAccessor : SourceAccessor
git_tree_entry * lookup(const CanonPath & path) git_tree_entry * lookup(const CanonPath & path)
{ {
auto i = lookupCache.find(path); auto i = lookupCache.find(path);
if (i != lookupCache.end()) return i->second.get(); if (i != lookupCache.end())
return i->second.get();
auto parent = path.parent(); auto parent = path.parent();
if (!parent) return nullptr; if (!parent)
return nullptr;
auto name = path.baseName().value(); auto name = path.baseName().value();
auto parentTree = lookupTree(*parent); auto parentTree = lookupTree(*parent);
if (!parentTree) return nullptr; if (!parentTree)
return nullptr;
auto count = git_tree_entrycount(parentTree->get()); auto count = git_tree_entrycount(parentTree->get());
@ -841,7 +840,8 @@ struct GitSourceAccessor : SourceAccessor
return entry; return entry;
} }
struct Submodule { }; struct Submodule
{};
std::variant<Tree, Submodule> getTree(const CanonPath & path) std::variant<Tree, Submodule> getTree(const CanonPath & path)
{ {
@ -872,16 +872,12 @@ struct GitSourceAccessor : SourceAccessor
if (!expectSymlink && git_object_type(root.get()) == GIT_OBJECT_BLOB) if (!expectSymlink && git_object_type(root.get()) == GIT_OBJECT_BLOB)
return dupObject<Blob>((git_blob *) &*root); return dupObject<Blob>((git_blob *) &*root);
auto notExpected = [&]() auto notExpected = [&]() {
{ throw Error(expectSymlink ? "'%s' is not a symlink" : "'%s' is not a regular file", showPath(path));
throw Error(
expectSymlink
? "'%s' is not a symlink"
: "'%s' is not a regular file",
showPath(path));
}; };
if (path.isRoot()) notExpected(); if (path.isRoot())
notExpected();
auto entry = need(path); auto entry = need(path);
@ -905,17 +901,22 @@ struct GitSourceAccessor : SourceAccessor
} }
}; };
struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor { struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor
{
ref<GitRepoImpl> repo; ref<GitRepoImpl> repo;
std::optional<Hash> rev; std::optional<Hash> rev;
GitExportIgnoreSourceAccessor(ref<GitRepoImpl> repo, ref<SourceAccessor> next, std::optional<Hash> rev) GitExportIgnoreSourceAccessor(ref<GitRepoImpl> repo, ref<SourceAccessor> next, std::optional<Hash> rev)
: CachingFilteringSourceAccessor(next, [&](const CanonPath & path) { : CachingFilteringSourceAccessor(
return RestrictedPathError(fmt("'%s' does not exist because it was fetched with exportIgnore enabled", path)); next,
[&](const CanonPath & path) {
return RestrictedPathError(
fmt("'%s' does not exist because it was fetched with exportIgnore enabled", path));
}) })
, repo(repo) , repo(repo)
, rev(rev) , rev(rev)
{ } {
}
bool gitAttrGet(const CanonPath & path, const char * attrName, const char *& valueOut) bool gitAttrGet(const CanonPath & path, const char * attrName, const char *& valueOut)
{ {
@ -927,21 +928,10 @@ struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor {
// TODO: test that gitattributes from global and system are not used // TODO: test that gitattributes from global and system are not used
// (ie more or less: home and etc - both of them!) // (ie more or less: home and etc - both of them!)
opts.flags = GIT_ATTR_CHECK_INCLUDE_COMMIT | GIT_ATTR_CHECK_NO_SYSTEM; opts.flags = GIT_ATTR_CHECK_INCLUDE_COMMIT | GIT_ATTR_CHECK_NO_SYSTEM;
return git_attr_get_ext( return git_attr_get_ext(&valueOut, *repo, &opts, pathCStr, attrName);
&valueOut, } else {
*repo,
&opts,
pathCStr,
attrName
);
}
else {
return git_attr_get( return git_attr_get(
&valueOut, &valueOut, *repo, GIT_ATTR_CHECK_INDEX_ONLY | GIT_ATTR_CHECK_NO_SYSTEM, pathCStr, attrName);
*repo,
GIT_ATTR_CHECK_INDEX_ONLY | GIT_ATTR_CHECK_NO_SYSTEM,
pathCStr,
attrName);
} }
} }
@ -958,8 +948,7 @@ struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor {
return false; return false;
else else
throw Error("looking up '%s': %s", showPath(path), git_error_last()->message); throw Error("looking up '%s': %s", showPath(path), git_error_last()->message);
} } else {
else {
// Official git will silently reject export-ignore lines that have // Official git will silently reject export-ignore lines that have
// values. We do the same. // values. We do the same.
return GIT_ATTR_IS_TRUE(exportIgnoreEntry); return GIT_ATTR_IS_TRUE(exportIgnoreEntry);
@ -970,7 +959,6 @@ struct GitExportIgnoreSourceAccessor : CachingFilteringSourceAccessor {
{ {
return !isExportIgnored(path); return !isExportIgnored(path);
} }
}; };
struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
@ -990,9 +978,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
const git_tree_entry * entry; const git_tree_entry * entry;
Tree prevTree = nullptr; Tree prevTree = nullptr;
if (!pendingDirs.empty() && if (!pendingDirs.empty() && (entry = git_treebuilder_get(pendingDirs.back().builder.get(), name.c_str()))) {
(entry = git_treebuilder_get(pendingDirs.back().builder.get(), name.c_str())))
{
/* Clone a tree that we've already finished. This happens /* Clone a tree that we've already finished. This happens
if a tarball has directory entries that are not if a tarball has directory entries that are not
contiguous. */ contiguous. */
@ -1009,7 +995,8 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
pendingDirs.push_back({.name = std::move(name), .builder = TreeBuilder(b)}); pendingDirs.push_back({.name = std::move(name), .builder = TreeBuilder(b)});
}; };
GitFileSystemObjectSinkImpl(ref<GitRepoImpl> repo) : repo(repo) GitFileSystemObjectSinkImpl(ref<GitRepoImpl> repo)
: repo(repo)
{ {
pushBuilder(""); pushBuilder("");
} }
@ -1056,33 +1043,33 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
{ {
std::span<const std::string> pathComponents2{pathComponents}; std::span<const std::string> pathComponents2{pathComponents};
updateBuilders( updateBuilders(isDir ? pathComponents2 : pathComponents2.first(pathComponents2.size() - 1));
isDir
? pathComponents2
: pathComponents2.first(pathComponents2.size() - 1));
return true; return true;
} }
void createRegularFile( void createRegularFile(const CanonPath & path, std::function<void(CreateRegularFileSink &)> func) override
const CanonPath & path,
std::function<void(CreateRegularFileSink &)> func) override
{ {
auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/"); auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/");
if (!prepareDirs(pathComponents, false)) return; if (!prepareDirs(pathComponents, false))
return;
git_writestream * stream = nullptr; git_writestream * stream = nullptr;
if (git_blob_create_from_stream(&stream, *repo, nullptr)) if (git_blob_create_from_stream(&stream, *repo, nullptr))
throw Error("creating a blob stream object: %s", git_error_last()->message); throw Error("creating a blob stream object: %s", git_error_last()->message);
struct CRF : CreateRegularFileSink { struct CRF : CreateRegularFileSink
{
const CanonPath & path; const CanonPath & path;
GitFileSystemObjectSinkImpl & back; GitFileSystemObjectSinkImpl & back;
git_writestream * stream; git_writestream * stream;
bool executable = false; bool executable = false;
CRF(const CanonPath & path, GitFileSystemObjectSinkImpl & back, git_writestream * stream) CRF(const CanonPath & path, GitFileSystemObjectSinkImpl & back, git_writestream * stream)
: path(path), back(back), stream(stream) : path(path)
{} , back(back)
, stream(stream)
{
}
void operator()(std::string_view data) override void operator()(std::string_view data) override
{ {
if (stream->write(stream, data.data(), data.size())) if (stream->write(stream, data.data(), data.size()))
@ -1099,10 +1086,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
if (git_blob_create_from_stream_commit(&oid, stream)) if (git_blob_create_from_stream_commit(&oid, stream))
throw Error("creating a blob object for tarball member '%s': %s", path, git_error_last()->message); throw Error("creating a blob object for tarball member '%s': %s", path, git_error_last()->message);
addToTree(*pathComponents.rbegin(), oid, addToTree(*pathComponents.rbegin(), oid, crf.executable ? GIT_FILEMODE_BLOB_EXECUTABLE : GIT_FILEMODE_BLOB);
crf.executable
? GIT_FILEMODE_BLOB_EXECUTABLE
: GIT_FILEMODE_BLOB);
} }
void createDirectory(const CanonPath & path) override void createDirectory(const CanonPath & path) override
@ -1114,7 +1098,8 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
void createSymlink(const CanonPath & path, const std::string & target) override void createSymlink(const CanonPath & path, const std::string & target) override
{ {
auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/"); auto pathComponents = tokenizeString<std::vector<std::string>>(path.rel(), "/");
if (!prepareDirs(pathComponents, false)) return; if (!prepareDirs(pathComponents, false))
return;
git_oid oid; git_oid oid;
if (git_blob_create_from_buffer(&oid, *repo, target.c_str(), target.size())) if (git_blob_create_from_buffer(&oid, *repo, target.c_str(), target.size()))
@ -1129,7 +1114,8 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
for (auto & c : path) for (auto & c : path)
pathComponents.emplace_back(c); pathComponents.emplace_back(c);
if (!prepareDirs(pathComponents, false)) return; if (!prepareDirs(pathComponents, false))
return;
// We can't just look up the path from the start of the root, since // We can't just look up the path from the start of the root, since
// some parent directories may not have finished yet, so we compute // some parent directories may not have finished yet, so we compute
@ -1173,9 +1159,7 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
assert(entry); assert(entry);
addToTree(*pathComponents.rbegin(), addToTree(*pathComponents.rbegin(), *git_tree_entry_id(entry), git_tree_entry_filemode(entry));
*git_tree_entry_id(entry),
git_tree_entry_filemode(entry));
} }
Hash flush() override Hash flush() override
@ -1190,19 +1174,14 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
} }
}; };
ref<GitSourceAccessor> GitRepoImpl::getRawAccessor( ref<GitSourceAccessor> GitRepoImpl::getRawAccessor(const Hash & rev, bool smudgeLfs)
const Hash & rev,
bool smudgeLfs)
{ {
auto self = ref<GitRepoImpl>(shared_from_this()); auto self = ref<GitRepoImpl>(shared_from_this());
return make_ref<GitSourceAccessor>(self, rev, smudgeLfs); return make_ref<GitSourceAccessor>(self, rev, smudgeLfs);
} }
ref<SourceAccessor> GitRepoImpl::getAccessor( ref<SourceAccessor>
const Hash & rev, GitRepoImpl::getAccessor(const Hash & rev, bool exportIgnore, std::string displayPrefix, bool smudgeLfs)
bool exportIgnore,
std::string displayPrefix,
bool smudgeLfs)
{ {
auto self = ref<GitRepoImpl>(shared_from_this()); auto self = ref<GitRepoImpl>(shared_from_this());
ref<GitSourceAccessor> rawGitAccessor = getRawAccessor(rev, smudgeLfs); ref<GitSourceAccessor> rawGitAccessor = getRawAccessor(rev, smudgeLfs);
@ -1213,16 +1192,17 @@ ref<SourceAccessor> GitRepoImpl::getAccessor(
return rawGitAccessor; return rawGitAccessor;
} }
ref<SourceAccessor> GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) ref<SourceAccessor>
GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError)
{ {
auto self = ref<GitRepoImpl>(shared_from_this()); auto self = ref<GitRepoImpl>(shared_from_this());
ref<SourceAccessor> fileAccessor = ref<SourceAccessor> fileAccessor = AllowListSourceAccessor::create(
AllowListSourceAccessor::create(
makeFSSourceAccessor(path), makeFSSourceAccessor(path),
std::set<CanonPath>{wd.files}, std::set<CanonPath>{wd.files},
// Always allow access to the root, but not its children. // Always allow access to the root, but not its children.
std::unordered_set<CanonPath>{CanonPath::root}, std::unordered_set<CanonPath>{CanonPath::root},
std::move(makeNotAllowedError)).cast<SourceAccessor>(); std::move(makeNotAllowedError))
.cast<SourceAccessor>();
if (exportIgnore) if (exportIgnore)
return make_ref<GitExportIgnoreSourceAccessor>(self, fileAccessor, std::nullopt); return make_ref<GitExportIgnoreSourceAccessor>(self, fileAccessor, std::nullopt);
else else
@ -1240,7 +1220,8 @@ std::vector<std::tuple<GitRepoImpl::Submodule, Hash>> GitRepoImpl::getSubmodules
CanonPath modulesFile(".gitmodules"); CanonPath modulesFile(".gitmodules");
auto accessor = getAccessor(rev, exportIgnore, ""); auto accessor = getAccessor(rev, exportIgnore, "");
if (!accessor->pathExists(modulesFile)) return {}; if (!accessor->pathExists(modulesFile))
return {};
/* Parse it and get the revision of each submodule. */ /* Parse it and get the revision of each submodule. */
auto configS = accessor->readFile(modulesFile); auto configS = accessor->readFile(modulesFile);
@ -1280,7 +1261,8 @@ GitRepo::WorkdirInfo GitRepo::getCachedWorkdirInfo(const std::filesystem::path &
{ {
auto cache(_cache.lock()); auto cache(_cache.lock());
auto i = cache->find(path); auto i = cache->find(path);
if (i != cache->end()) return i->second; if (i != cache->end())
return i->second;
} }
auto workdirInfo = GitRepo::openRepo(path)->getWorkdirInfo(); auto workdirInfo = GitRepo::openRepo(path)->getWorkdirInfo();
_cache.lock()->emplace(path, workdirInfo); _cache.lock()->emplace(path, workdirInfo);

View file

@ -43,9 +43,7 @@ bool isCacheFileWithinTtl(time_t now, const struct stat & st)
Path getCachePath(std::string_view key, bool shallow) Path getCachePath(std::string_view key, bool shallow)
{ {
return getCacheDir() return getCacheDir() + "/gitv3/" + hashString(HashAlgorithm::SHA256, key).to_string(HashFormat::Nix32, false)
+ "/gitv3/"
+ hashString(HashAlgorithm::SHA256, key).to_string(HashFormat::Nix32, false)
+ (shallow ? "-shallow" : ""); + (shallow ? "-shallow" : "");
} }
@ -58,13 +56,15 @@ Path getCachePath(std::string_view key, bool shallow)
// ... // ...
std::optional<std::string> readHead(const Path & path) std::optional<std::string> readHead(const Path & path)
{ {
auto [status, output] = runProgram(RunOptions { auto [status, output] = runProgram(
RunOptions{
.program = "git", .program = "git",
// FIXME: use 'HEAD' to avoid returning all refs // FIXME: use 'HEAD' to avoid returning all refs
.args = {"ls-remote", "--symref", path}, .args = {"ls-remote", "--symref", path},
.isInteractive = true, .isInteractive = true,
}); });
if (status != 0) return std::nullopt; if (status != 0)
return std::nullopt;
std::string_view line = output; std::string_view line = output;
line = line.substr(0, line.find("\n")); line = line.substr(0, line.find("\n"));
@ -116,17 +116,15 @@ std::optional<std::string> readHeadCached(const std::string & actualUrl, bool sh
std::optional<std::string> cachedRef; std::optional<std::string> cachedRef;
if (stat(headRefFile.c_str(), &st) == 0) { if (stat(headRefFile.c_str(), &st) == 0) {
cachedRef = readHead(cacheDir); cachedRef = readHead(cacheDir);
if (cachedRef != std::nullopt && if (cachedRef != std::nullopt && *cachedRef != gitInitialBranch && isCacheFileWithinTtl(now, st)) {
*cachedRef != gitInitialBranch &&
isCacheFileWithinTtl(now, st))
{
debug("using cached HEAD ref '%s' for repo '%s'", *cachedRef, actualUrl); debug("using cached HEAD ref '%s' for repo '%s'", *cachedRef, actualUrl);
return cachedRef; return cachedRef;
} }
} }
auto ref = readHead(actualUrl); auto ref = readHead(actualUrl);
if (ref) return ref; if (ref)
return ref;
if (cachedRef) { if (cachedRef) {
// If the cached git ref is expired in fetch() below, and the 'git fetch' // If the cached git ref is expired in fetch() below, and the 'git fetch'
@ -152,7 +150,8 @@ std::vector<PublicKey> getPublicKeys(const Attrs & attrs)
} }
} }
if (attrs.contains("publicKey")) if (attrs.contains("publicKey"))
publicKeys.push_back(PublicKey{maybeGetStrAttr(attrs, "keytype").value_or("ssh-ed25519"),getStrAttr(attrs, "publicKey")}); publicKeys.push_back(
PublicKey{maybeGetStrAttr(attrs, "keytype").value_or("ssh-ed25519"), getStrAttr(attrs, "publicKey")});
return publicKeys; return publicKeys;
} }
@ -162,18 +161,15 @@ static const Hash nullRev{HashAlgorithm::SHA1};
struct GitInputScheme : InputScheme struct GitInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL( std::optional<Input> inputFromURL(const Settings & settings, const ParsedURL & url, bool requireTree) const override
const Settings & settings,
const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "git" && if (url.scheme != "git" && url.scheme != "git+http" && url.scheme != "git+https" && url.scheme != "git+ssh"
url.scheme != "git+http" && && url.scheme != "git+file")
url.scheme != "git+https" && return {};
url.scheme != "git+ssh" &&
url.scheme != "git+file") return {};
auto url2(url); auto url2(url);
if (hasPrefix(url2.scheme, "git+")) url2.scheme = std::string(url2.scheme, 4); if (hasPrefix(url2.scheme, "git+"))
url2.scheme = std::string(url2.scheme, 4);
url2.query.clear(); url2.query.clear();
Attrs attrs; Attrs attrs;
@ -182,7 +178,9 @@ struct GitInputScheme : InputScheme
for (auto & [name, value] : url.query) { for (auto & [name, value] : url.query) {
if (name == "rev" || name == "ref" || name == "keytype" || name == "publicKey" || name == "publicKeys") if (name == "rev" || name == "ref" || name == "keytype" || name == "publicKey" || name == "publicKeys")
attrs.emplace(name, value); attrs.emplace(name, value);
else if (name == "shallow" || name == "submodules" || name == "lfs" || name == "exportIgnore" || name == "allRefs" || name == "verifyCommit") else if (
name == "shallow" || name == "submodules" || name == "lfs" || name == "exportIgnore"
|| name == "allRefs" || name == "verifyCommit")
attrs.emplace(name, Explicit<bool>{value == "1"}); attrs.emplace(name, Explicit<bool>{value == "1"});
else else
url2.query.emplace(name, value); url2.query.emplace(name, value);
@ -193,7 +191,6 @@ struct GitInputScheme : InputScheme
return inputFromAttrs(settings, attrs); return inputFromAttrs(settings, attrs);
} }
std::string_view schemeName() const override std::string_view schemeName() const override
{ {
return "git"; return "git";
@ -223,15 +220,10 @@ struct GitInputScheme : InputScheme
}; };
} }
std::optional<Input> inputFromAttrs( std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
const Settings & settings,
const Attrs & attrs) const override
{ {
for (auto & [name, _] : attrs) for (auto & [name, _] : attrs)
if (name == "verifyCommit" if (name == "verifyCommit" || name == "keytype" || name == "publicKey" || name == "publicKeys")
|| name == "keytype"
|| name == "publicKey"
|| name == "publicKeys")
experimentalFeatureSettings.require(Xp::VerifiedFetches); experimentalFeatureSettings.require(Xp::VerifiedFetches);
maybeGetBoolAttr(attrs, "verifyCommit"); maybeGetBoolAttr(attrs, "verifyCommit");
@ -255,9 +247,12 @@ struct GitInputScheme : InputScheme
ParsedURL toURL(const Input & input) const override ParsedURL toURL(const Input & input) const override
{ {
auto url = parseURL(getStrAttr(input.attrs, "url")); auto url = parseURL(getStrAttr(input.attrs, "url"));
if (url.scheme != "git") url.scheme = "git+" + url.scheme; if (url.scheme != "git")
if (auto rev = input.getRev()) url.query.insert_or_assign("rev", rev->gitRev()); url.scheme = "git+" + url.scheme;
if (auto ref = input.getRef()) url.query.insert_or_assign("ref", *ref); if (auto rev = input.getRev())
url.query.insert_or_assign("rev", rev->gitRev());
if (auto ref = input.getRef())
url.query.insert_or_assign("ref", *ref);
if (getShallowAttr(input)) if (getShallowAttr(input))
url.query.insert_or_assign("shallow", "1"); url.query.insert_or_assign("shallow", "1");
if (getLfsAttr(input)) if (getLfsAttr(input))
@ -272,20 +267,18 @@ struct GitInputScheme : InputScheme
if (publicKeys.size() == 1) { if (publicKeys.size() == 1) {
url.query.insert_or_assign("keytype", publicKeys.at(0).type); url.query.insert_or_assign("keytype", publicKeys.at(0).type);
url.query.insert_or_assign("publicKey", publicKeys.at(0).key); url.query.insert_or_assign("publicKey", publicKeys.at(0).key);
} } else if (publicKeys.size() > 1)
else if (publicKeys.size() > 1)
url.query.insert_or_assign("publicKeys", publicKeys_to_string(publicKeys)); url.query.insert_or_assign("publicKeys", publicKeys_to_string(publicKeys));
return url; return url;
} }
Input applyOverrides( Input applyOverrides(const Input & input, std::optional<std::string> ref, std::optional<Hash> rev) const override
const Input & input,
std::optional<std::string> ref,
std::optional<Hash> rev) const override
{ {
auto res(input); auto res(input);
if (rev) res.attrs.insert_or_assign("rev", rev->gitRev()); if (rev)
if (ref) res.attrs.insert_or_assign("ref", *ref); res.attrs.insert_or_assign("rev", rev->gitRev());
if (ref)
res.attrs.insert_or_assign("ref", *ref);
if (!res.getRef() && res.getRev()) if (!res.getRef() && res.getRev())
throw Error("Git input '%s' has a commit hash but no branch/tag name", res.to_string()); throw Error("Git input '%s' has a commit hash but no branch/tag name", res.to_string());
return res; return res;
@ -304,7 +297,8 @@ struct GitInputScheme : InputScheme
args.push_back(*ref); args.push_back(*ref);
} }
if (input.getRev()) throw UnimplementedError("cloning a specific revision is not implemented"); if (input.getRev())
throw UnimplementedError("cloning a specific revision is not implemented");
args.push_back(destDir); args.push_back(destDir);
@ -325,13 +319,22 @@ struct GitInputScheme : InputScheme
auto repoInfo = getRepoInfo(input); auto repoInfo = getRepoInfo(input);
auto repoPath = repoInfo.getPath(); auto repoPath = repoInfo.getPath();
if (!repoPath) if (!repoPath)
throw Error("cannot commit '%s' to Git repository '%s' because it's not a working tree", path, input.to_string()); throw Error(
"cannot commit '%s' to Git repository '%s' because it's not a working tree", path, input.to_string());
writeFile(*repoPath / path.rel(), contents); writeFile(*repoPath / path.rel(), contents);
auto result = runProgram(RunOptions { auto result = runProgram(
RunOptions{
.program = "git", .program = "git",
.args = {"-C", repoPath->string(), "--git-dir", repoInfo.gitDir, "check-ignore", "--quiet", std::string(path.rel())}, .args =
{"-C",
repoPath->string(),
"--git-dir",
repoInfo.gitDir,
"check-ignore",
"--quiet",
std::string(path.rel())},
}); });
auto exitCode = auto exitCode =
#ifndef WIN32 // TODO abstract over exit status handling on Windows #ifndef WIN32 // TODO abstract over exit status handling on Windows
@ -343,15 +346,32 @@ struct GitInputScheme : InputScheme
if (exitCode != 0) { if (exitCode != 0) {
// The path is not `.gitignore`d, we can add the file. // The path is not `.gitignore`d, we can add the file.
runProgram("git", true, runProgram(
{ "-C", repoPath->string(), "--git-dir", repoInfo.gitDir, "add", "--intent-to-add", "--", std::string(path.rel()) }); "git",
true,
{"-C",
repoPath->string(),
"--git-dir",
repoInfo.gitDir,
"add",
"--intent-to-add",
"--",
std::string(path.rel())});
if (commitMsg) { if (commitMsg) {
// Pause the logger to allow for user input (such as a gpg passphrase) in `git commit` // Pause the logger to allow for user input (such as a gpg passphrase) in `git commit`
auto suspension = logger->suspend(); auto suspension = logger->suspend();
runProgram("git", true, runProgram(
{ "-C", repoPath->string(), "--git-dir", repoInfo.gitDir, "commit", std::string(path.rel()), "-F", "-" }, "git",
true,
{"-C",
repoPath->string(),
"--git-dir",
repoInfo.gitDir,
"commit",
std::string(path.rel()),
"-F",
"-"},
*commitMsg); *commitMsg);
} }
} }
@ -371,11 +391,9 @@ struct GitInputScheme : InputScheme
{ {
return std::visit( return std::visit(
overloaded{ overloaded{
[&](const std::filesystem::path & path) [&](const std::filesystem::path & path) { return path.string(); },
{ return path.string(); }, [&](const ParsedURL & url) { return url.to_string(); }},
[&](const ParsedURL & url) location);
{ return url.to_string(); }
}, location);
} }
std::optional<std::filesystem::path> getPath() const std::optional<std::filesystem::path> getPath() const
@ -427,10 +445,11 @@ struct GitInputScheme : InputScheme
RepoInfo getRepoInfo(const Input & input) const RepoInfo getRepoInfo(const Input & input) const
{ {
auto checkHashAlgorithm = [&](const std::optional<Hash> & hash) auto checkHashAlgorithm = [&](const std::optional<Hash> & hash) {
{
if (hash.has_value() && !(hash->algo == HashAlgorithm::SHA1 || hash->algo == HashAlgorithm::SHA256)) if (hash.has_value() && !(hash->algo == HashAlgorithm::SHA1 || hash->algo == HashAlgorithm::SHA256))
throw Error("Hash '%s' is not supported by Git. Supported types are sha1 and sha256.", hash->to_string(HashFormat::Base16, true)); throw Error(
"Hash '%s' is not supported by Git. Supported types are sha1 and sha256.",
hash->to_string(HashFormat::Base16, true));
}; };
if (auto rev = input.getRev()) if (auto rev = input.getRev())
@ -480,7 +499,11 @@ struct GitInputScheme : InputScheme
return repoInfo; return repoInfo;
} }
uint64_t getLastModified(const Settings & settings, const RepoInfo & repoInfo, const std::filesystem::path & repoDir, const Hash & rev) const uint64_t getLastModified(
const Settings & settings,
const RepoInfo & repoInfo,
const std::filesystem::path & repoDir,
const Hash & rev) const
{ {
Cache::Key key{"gitLastModified", {{"rev", rev.gitRev()}}}; Cache::Key key{"gitLastModified", {{"rev", rev.gitRev()}}};
@ -496,7 +519,11 @@ struct GitInputScheme : InputScheme
return lastModified; return lastModified;
} }
uint64_t getRevCount(const Settings & settings, const RepoInfo & repoInfo, const std::filesystem::path & repoDir, const Hash & rev) const uint64_t getRevCount(
const Settings & settings,
const RepoInfo & repoInfo,
const std::filesystem::path & repoDir,
const Hash & rev) const
{ {
Cache::Key key{"gitRevCount", {{"rev", rev.gitRev()}}}; Cache::Key key{"gitRevCount", {{"rev", rev.gitRev()}}};
@ -505,7 +532,8 @@ struct GitInputScheme : InputScheme
if (auto revCountAttrs = cache->lookup(key)) if (auto revCountAttrs = cache->lookup(key))
return getIntAttr(*revCountAttrs, "revCount"); return getIntAttr(*revCountAttrs, "revCount");
Activity act(*logger, lvlChatty, actUnknown, fmt("getting Git revision count of '%s'", repoInfo.locationToArg())); Activity act(
*logger, lvlChatty, actUnknown, fmt("getting Git revision count of '%s'", repoInfo.locationToArg()));
auto revCount = GitRepo::openRepo(repoDir)->getRevCount(rev); auto revCount = GitRepo::openRepo(repoDir)->getRevCount(rev);
@ -518,11 +546,9 @@ struct GitInputScheme : InputScheme
{ {
auto head = std::visit( auto head = std::visit(
overloaded{ overloaded{
[&](const std::filesystem::path & path) [&](const std::filesystem::path & path) { return GitRepo::openRepo(path)->getWorkdirRef(); },
{ return GitRepo::openRepo(path)->getWorkdirRef(); }, [&](const ParsedURL & url) { return readHeadCached(url.to_string(), shallow); }},
[&](const ParsedURL & url) repoInfo.location);
{ return readHeadCached(url.to_string(), shallow); }
}, repoInfo.location);
if (!head) { if (!head) {
warn("could not read HEAD ref from repo at '%s', using 'master'", repoInfo.locationToArg()); warn("could not read HEAD ref from repo at '%s', using 'master'", repoInfo.locationToArg());
return "master"; return "master";
@ -556,14 +582,13 @@ struct GitInputScheme : InputScheme
if (input.getRev() && repo) if (input.getRev() && repo)
repo->verifyCommit(*input.getRev(), publicKeys); repo->verifyCommit(*input.getRev(), publicKeys);
else else
throw Error("commit verification is required for Git repository '%s', but it's dirty", input.to_string()); throw Error(
"commit verification is required for Git repository '%s', but it's dirty", input.to_string());
} }
} }
std::pair<ref<SourceAccessor>, Input> getAccessorFromCommit( std::pair<ref<SourceAccessor>, Input>
ref<Store> store, getAccessorFromCommit(ref<Store> store, RepoInfo & repoInfo, Input && input) const
RepoInfo & repoInfo,
Input && input) const
{ {
assert(!repoInfo.workdirInfo.isDirty); assert(!repoInfo.workdirInfo.isDirty);
@ -594,10 +619,7 @@ struct GitInputScheme : InputScheme
// We need to set the origin so resolving submodule URLs works // We need to set the origin so resolving submodule URLs works
repo->setRemote("origin", repoUrl.to_string()); repo->setRemote("origin", repoUrl.to_string());
auto localRefFile = auto localRefFile = ref.compare(0, 5, "refs/") == 0 ? cacheDir / ref : cacheDir / "refs/heads" / ref;
ref.compare(0, 5, "refs/") == 0
? cacheDir / ref
: cacheDir / "refs/heads" / ref;
bool doFetch; bool doFetch;
time_t now = time(0); time_t now = time(0);
@ -613,30 +635,27 @@ struct GitInputScheme : InputScheme
/* If the local ref is older than tarball-ttl seconds, do a /* If the local ref is older than tarball-ttl seconds, do a
git fetch to update the local ref to the remote ref. */ git fetch to update the local ref to the remote ref. */
struct stat st; struct stat st;
doFetch = stat(localRefFile.string().c_str(), &st) != 0 || doFetch = stat(localRefFile.string().c_str(), &st) != 0 || !isCacheFileWithinTtl(now, st);
!isCacheFileWithinTtl(now, st);
} }
} }
if (doFetch) { if (doFetch) {
bool shallow = getShallowAttr(input); bool shallow = getShallowAttr(input);
try { try {
auto fetchRef = auto fetchRef = getAllRefsAttr(input) ? "refs/*:refs/*"
getAllRefsAttr(input) : input.getRev() ? input.getRev()->gitRev()
? "refs/*:refs/*" : ref.compare(0, 5, "refs/") == 0 ? fmt("%1%:%1%", ref)
: input.getRev() : ref == "HEAD" ? ref
? input.getRev()->gitRev()
: ref.compare(0, 5, "refs/") == 0
? fmt("%1%:%1%", ref)
: ref == "HEAD"
? ref
: fmt("%1%:%1%", "refs/heads/" + ref); : fmt("%1%:%1%", "refs/heads/" + ref);
repo->fetch(repoUrl.to_string(), fetchRef, shallow); repo->fetch(repoUrl.to_string(), fetchRef, shallow);
} catch (Error & e) { } catch (Error & e) {
if (!std::filesystem::exists(localRefFile)) throw; if (!std::filesystem::exists(localRefFile))
throw;
logError(e.info()); logError(e.info());
warn("could not update local clone of Git repository '%s'; continuing with the most recent version", repoInfo.locationToArg()); warn(
"could not update local clone of Git repository '%s'; continuing with the most recent version",
repoInfo.locationToArg());
} }
try { try {
@ -653,16 +672,17 @@ struct GitInputScheme : InputScheme
if (!repo->hasObject(*rev)) if (!repo->hasObject(*rev))
throw Error( throw Error(
"Cannot find Git revision '%s' in ref '%s' of repository '%s'! " "Cannot find Git revision '%s' in ref '%s' of repository '%s'! "
"Please make sure that the " ANSI_BOLD "rev" ANSI_NORMAL " exists on the " "Please make sure that the " ANSI_BOLD "rev" ANSI_NORMAL " exists on the " ANSI_BOLD
ANSI_BOLD "ref" ANSI_NORMAL " you've specified or add " ANSI_BOLD "ref" ANSI_NORMAL " you've specified or add " ANSI_BOLD "allRefs = true;" ANSI_NORMAL
"allRefs = true;" ANSI_NORMAL " to " ANSI_BOLD "fetchGit" ANSI_NORMAL ".", " to " ANSI_BOLD "fetchGit" ANSI_NORMAL ".",
rev->gitRev(), rev->gitRev(),
ref, ref,
repoInfo.locationToArg()); repoInfo.locationToArg());
} else } else
input.attrs.insert_or_assign("rev", repo->resolveRef(ref).gitRev()); input.attrs.insert_or_assign("rev", repo->resolveRef(ref).gitRev());
// cache dir lock is removed at scope end; we will only use read-only operations on specific revisions in the remainder // cache dir lock is removed at scope end; we will only use read-only operations on specific revisions in
// the remainder
} }
auto repo = GitRepo::openRepo(repoDir); auto repo = GitRepo::openRepo(repoDir);
@ -670,7 +690,9 @@ struct GitInputScheme : InputScheme
auto isShallow = repo->isShallow(); auto isShallow = repo->isShallow();
if (isShallow && !getShallowAttr(input)) if (isShallow && !getShallowAttr(input))
throw Error("'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified", repoInfo.locationToArg()); throw Error(
"'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified",
repoInfo.locationToArg());
// FIXME: check whether rev is an ancestor of ref? // FIXME: check whether rev is an ancestor of ref?
@ -682,8 +704,7 @@ struct GitInputScheme : InputScheme
}); });
if (!getShallowAttr(input)) if (!getShallowAttr(input))
infoAttrs.insert_or_assign("revCount", infoAttrs.insert_or_assign("revCount", getRevCount(*input.settings, repoInfo, repoDir, rev));
getRevCount(*input.settings, repoInfo, repoDir, rev));
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg()); printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg());
@ -701,8 +722,13 @@ struct GitInputScheme : InputScheme
for (auto & [submodule, submoduleRev] : repo->getSubmodules(rev, exportIgnore)) { for (auto & [submodule, submoduleRev] : repo->getSubmodules(rev, exportIgnore)) {
auto resolved = repo->resolveSubmoduleUrl(submodule.url); auto resolved = repo->resolveSubmoduleUrl(submodule.url);
debug("Git submodule %s: %s %s %s -> %s", debug(
submodule.path, submodule.url, submodule.branch, submoduleRev.gitRev(), resolved); "Git submodule %s: %s %s %s -> %s",
submodule.path,
submodule.url,
submodule.branch,
submoduleRev.gitRev(),
resolved);
fetchers::Attrs attrs; fetchers::Attrs attrs;
attrs.insert_or_assign("type", "git"); attrs.insert_or_assign("type", "git");
attrs.insert_or_assign("url", resolved); attrs.insert_or_assign("url", resolved);
@ -714,8 +740,7 @@ struct GitInputScheme : InputScheme
attrs.insert_or_assign("lfs", Explicit<bool>{smudgeLfs}); attrs.insert_or_assign("lfs", Explicit<bool>{smudgeLfs});
attrs.insert_or_assign("allRefs", Explicit<bool>{true}); attrs.insert_or_assign("allRefs", Explicit<bool>{true});
auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs)); auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
auto [submoduleAccessor, submoduleInput2] = auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(store);
submoduleInput.getAccessor(store);
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»"); submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
mounts.insert_or_assign(submodule.path, submoduleAccessor); mounts.insert_or_assign(submodule.path, submoduleAccessor);
} }
@ -734,10 +759,8 @@ struct GitInputScheme : InputScheme
return {accessor, std::move(input)}; return {accessor, std::move(input)};
} }
std::pair<ref<SourceAccessor>, Input> getAccessorFromWorkdir( std::pair<ref<SourceAccessor>, Input>
ref<Store> store, getAccessorFromWorkdir(ref<Store> store, RepoInfo & repoInfo, Input && input) const
RepoInfo & repoInfo,
Input && input) const
{ {
auto repoPath = repoInfo.getPath().value(); auto repoPath = repoInfo.getPath().value();
@ -751,9 +774,7 @@ struct GitInputScheme : InputScheme
auto exportIgnore = getExportIgnoreAttr(input); auto exportIgnore = getExportIgnoreAttr(input);
ref<SourceAccessor> accessor = ref<SourceAccessor> accessor =
repo->getAccessor(repoInfo.workdirInfo, repo->getAccessor(repoInfo.workdirInfo, exportIgnore, makeNotAllowedError(repoPath));
exportIgnore,
makeNotAllowedError(repoPath));
/* If the repo has submodules, return a mounted input accessor /* If the repo has submodules, return a mounted input accessor
consisting of the accessor for the top-level repo and the consisting of the accessor for the top-level repo and the
@ -772,8 +793,7 @@ struct GitInputScheme : InputScheme
// attrs.insert_or_assign("allRefs", Explicit<bool>{ true }); // attrs.insert_or_assign("allRefs", Explicit<bool>{ true });
auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs)); auto submoduleInput = fetchers::Input::fromAttrs(*input.settings, std::move(attrs));
auto [submoduleAccessor, submoduleInput2] = auto [submoduleAccessor, submoduleInput2] = submoduleInput.getAccessor(store);
submoduleInput.getAccessor(store);
submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»"); submoduleAccessor->setPathDisplay("«" + submoduleInput.to_string() + "»");
/* If the submodule is dirty, mark this repo dirty as /* If the submodule is dirty, mark this repo dirty as
@ -798,18 +818,16 @@ struct GitInputScheme : InputScheme
auto rev = repoInfo.workdirInfo.headRev.value_or(nullRev); auto rev = repoInfo.workdirInfo.headRev.value_or(nullRev);
input.attrs.insert_or_assign("rev", rev.gitRev()); input.attrs.insert_or_assign("rev", rev.gitRev());
input.attrs.insert_or_assign("revCount", input.attrs.insert_or_assign(
rev == nullRev ? 0 : getRevCount(*input.settings, repoInfo, repoPath, rev)); "revCount", rev == nullRev ? 0 : getRevCount(*input.settings, repoInfo, repoPath, rev));
verifyCommit(input, repo); verifyCommit(input, repo);
} else { } else {
repoInfo.warnDirty(*input.settings); repoInfo.warnDirty(*input.settings);
if (repoInfo.workdirInfo.headRev) { if (repoInfo.workdirInfo.headRev) {
input.attrs.insert_or_assign("dirtyRev", input.attrs.insert_or_assign("dirtyRev", repoInfo.workdirInfo.headRev->gitRev() + "-dirty");
repoInfo.workdirInfo.headRev->gitRev() + "-dirty"); input.attrs.insert_or_assign("dirtyShortRev", repoInfo.workdirInfo.headRev->gitShortRev() + "-dirty");
input.attrs.insert_or_assign("dirtyShortRev",
repoInfo.workdirInfo.headRev->gitShortRev() + "-dirty");
} }
verifyCommit(input, nullptr); verifyCommit(input, nullptr);
@ -830,8 +848,7 @@ struct GitInputScheme : InputScheme
auto repoInfo = getRepoInfo(input); auto repoInfo = getRepoInfo(input);
if (getExportIgnoreAttr(input) if (getExportIgnoreAttr(input) && getSubmodulesAttr(input)) {
&& getSubmodulesAttr(input)) {
/* In this situation, we don't have a git CLI behavior that we can copy. /* In this situation, we don't have a git CLI behavior that we can copy.
`git archive` does not support submodules, so it is unclear whether `git archive` does not support submodules, so it is unclear whether
rules from the parent should affect the submodule or not. rules from the parent should affect the submodule or not.
@ -840,8 +857,7 @@ struct GitInputScheme : InputScheme
throw UnimplementedError("exportIgnore and submodules are not supported together yet"); throw UnimplementedError("exportIgnore and submodules are not supported together yet");
} }
auto [accessor, final] = auto [accessor, final] = input.getRef() || input.getRev() || !repoInfo.getPath()
input.getRef() || input.getRev() || !repoInfo.getPath()
? getAccessorFromCommit(store, repoInfo, std::move(input)) ? getAccessorFromCommit(store, repoInfo, std::move(input))
: getAccessorFromWorkdir(store, repoInfo, std::move(input)); : getAccessorFromWorkdir(store, repoInfo, std::move(input));
@ -850,16 +866,17 @@ struct GitInputScheme : InputScheme
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
{ {
auto makeFingerprint = [&](const Hash & rev) auto makeFingerprint = [&](const Hash & rev) {
{ return rev.gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : "")
return rev.gitRev() + (getSubmodulesAttr(input) ? ";s" : "") + (getExportIgnoreAttr(input) ? ";e" : "") + (getLfsAttr(input) ? ";l" : ""); + (getLfsAttr(input) ? ";l" : "");
}; };
if (auto rev = input.getRev()) if (auto rev = input.getRev())
return makeFingerprint(*rev); return makeFingerprint(*rev);
else { else {
auto repoInfo = getRepoInfo(input); auto repoInfo = getRepoInfo(input);
if (auto repoPath = repoInfo.getPath(); repoPath && repoInfo.workdirInfo.headRev && repoInfo.workdirInfo.submodules.empty()) { if (auto repoPath = repoInfo.getPath();
repoPath && repoInfo.workdirInfo.headRev && repoInfo.workdirInfo.submodules.empty()) {
/* Calculate a fingerprint that takes into account the /* Calculate a fingerprint that takes into account the
deleted and modified/added files. */ deleted and modified/added files. */
HashSink hashSink{HashAlgorithm::SHA512}; HashSink hashSink{HashAlgorithm::SHA512};

View file

@ -29,13 +29,14 @@ std::regex hostRegex(hostRegexS, std::regex::ECMAScript);
struct GitArchiveInputScheme : InputScheme struct GitArchiveInputScheme : InputScheme
{ {
virtual std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const = 0; virtual std::optional<std::pair<std::string, std::string>>
accessHeaderFromToken(const std::string & token) const = 0;
std::optional<Input> inputFromURL( std::optional<Input>
const fetchers::Settings & settings, inputFromURL(const fetchers::Settings & settings, const ParsedURL & url, bool requireTree) const override
const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != schemeName()) return {}; if (url.scheme != schemeName())
return {};
auto path = tokenizeString<std::vector<std::string>>(url.path, "/"); auto path = tokenizeString<std::vector<std::string>>(url.path, "/");
@ -73,15 +74,13 @@ struct GitArchiveInputScheme : InputScheme
if (rev) if (rev)
throw BadURL("URL '%s' contains multiple commit hashes", url); throw BadURL("URL '%s' contains multiple commit hashes", url);
rev = Hash::parseAny(value, HashAlgorithm::SHA1); rev = Hash::parseAny(value, HashAlgorithm::SHA1);
} } else if (name == "ref") {
else if (name == "ref") {
if (!std::regex_match(value, refRegex)) if (!std::regex_match(value, refRegex))
throw BadURL("URL '%s' contains an invalid branch/tag name", url); throw BadURL("URL '%s' contains an invalid branch/tag name", url);
if (ref) if (ref)
throw BadURL("URL '%s' contains multiple branch/tag names", url); throw BadURL("URL '%s' contains multiple branch/tag names", url);
ref = value; ref = value;
} } else if (name == "host") {
else if (name == "host") {
if (!std::regex_match(value, hostRegex)) if (!std::regex_match(value, hostRegex))
throw BadURL("URL '%s' contains an invalid instance host", url); throw BadURL("URL '%s' contains an invalid instance host", url);
host_url = value; host_url = value;
@ -96,9 +95,12 @@ struct GitArchiveInputScheme : InputScheme
input.attrs.insert_or_assign("type", std::string{schemeName()}); input.attrs.insert_or_assign("type", std::string{schemeName()});
input.attrs.insert_or_assign("owner", path[0]); input.attrs.insert_or_assign("owner", path[0]);
input.attrs.insert_or_assign("repo", path[1]); input.attrs.insert_or_assign("repo", path[1]);
if (rev) input.attrs.insert_or_assign("rev", rev->gitRev()); if (rev)
if (ref) input.attrs.insert_or_assign("ref", *ref); input.attrs.insert_or_assign("rev", rev->gitRev());
if (host_url) input.attrs.insert_or_assign("host", *host_url); if (ref)
input.attrs.insert_or_assign("ref", *ref);
if (host_url)
input.attrs.insert_or_assign("host", *host_url);
auto narHash = url.query.find("narHash"); auto narHash = url.query.find("narHash");
if (narHash != url.query.end()) if (narHash != url.query.end())
@ -121,9 +123,7 @@ struct GitArchiveInputScheme : InputScheme
}; };
} }
std::optional<Input> inputFromAttrs( std::optional<Input> inputFromAttrs(const fetchers::Settings & settings, const Attrs & attrs) const override
const fetchers::Settings & settings,
const Attrs & attrs) const override
{ {
getStrAttr(attrs, "owner"); getStrAttr(attrs, "owner");
getStrAttr(attrs, "repo"); getStrAttr(attrs, "repo");
@ -141,8 +141,10 @@ struct GitArchiveInputScheme : InputScheme
auto rev = input.getRev(); auto rev = input.getRev();
auto path = owner + "/" + repo; auto path = owner + "/" + repo;
assert(!(ref && rev)); assert(!(ref && rev));
if (ref) path += "/" + *ref; if (ref)
if (rev) path += "/" + rev->to_string(HashFormat::Base16, false); path += "/" + *ref;
if (rev)
path += "/" + rev->to_string(HashFormat::Base16, false);
auto url = ParsedURL{ auto url = ParsedURL{
.scheme = std::string{schemeName()}, .scheme = std::string{schemeName()},
.path = path, .path = path,
@ -155,15 +157,15 @@ struct GitArchiveInputScheme : InputScheme
return url; return url;
} }
Input applyOverrides( Input applyOverrides(const Input & _input, std::optional<std::string> ref, std::optional<Hash> rev) const override
const Input & _input,
std::optional<std::string> ref,
std::optional<Hash> rev) const override
{ {
auto input(_input); auto input(_input);
if (rev && ref) if (rev && ref)
throw BadURL("cannot apply both a commit hash (%s) and a branch/tag name ('%s') to input '%s'", throw BadURL(
rev->gitRev(), *ref, input.to_string()); "cannot apply both a commit hash (%s) and a branch/tag name ('%s') to input '%s'",
rev->gitRev(),
*ref,
input.to_string());
if (rev) { if (rev) {
input.attrs.insert_or_assign("rev", rev->gitRev()); input.attrs.insert_or_assign("rev", rev->gitRev());
input.attrs.erase("ref"); input.attrs.erase("ref");
@ -176,7 +178,8 @@ struct GitArchiveInputScheme : InputScheme
} }
// Search for the longest possible match starting from the begining and ending at either the end or a path segment. // Search for the longest possible match starting from the begining and ending at either the end or a path segment.
std::optional<std::string> getAccessToken(const fetchers::Settings & settings, const std::string & host, const std::string & url) const override std::optional<std::string> getAccessToken(
const fetchers::Settings & settings, const std::string & host, const std::string & url) const override
{ {
auto tokens = settings.accessTokens.get(); auto tokens = settings.accessTokens.get();
std::string answer; std::string answer;
@ -184,14 +187,9 @@ struct GitArchiveInputScheme : InputScheme
if (!url.empty()) { if (!url.empty()) {
for (auto & token : tokens) { for (auto & token : tokens) {
auto first = url.find(token.first); auto first = url.find(token.first);
if ( if (first != std::string::npos && token.first.length() > answer_match_len && first == 0
first != std::string::npos
&& token.first.length() > answer_match_len
&& first == 0
&& url.substr(0, token.first.length()) == token.first && url.substr(0, token.first.length()) == token.first
&& (url.length() == token.first.length() || url[token.first.length()] == '/') && (url.length() == token.first.length() || url[token.first.length()] == '/')) {
)
{
answer = token.second; answer = token.second;
answer_match_len = token.first.length(); answer_match_len = token.first.length();
} }
@ -204,10 +202,8 @@ struct GitArchiveInputScheme : InputScheme
return {}; return {};
} }
Headers makeHeadersWithAuthTokens( Headers
const fetchers::Settings & settings, makeHeadersWithAuthTokens(const fetchers::Settings & settings, const std::string & host, const Input & input) const
const std::string & host,
const Input & input) const
{ {
auto owner = getStrAttr(input.attrs, "owner"); auto owner = getStrAttr(input.attrs, "owner");
auto repo = getStrAttr(input.attrs, "repo"); auto repo = getStrAttr(input.attrs, "repo");
@ -216,9 +212,7 @@ struct GitArchiveInputScheme : InputScheme
} }
Headers makeHeadersWithAuthTokens( Headers makeHeadersWithAuthTokens(
const fetchers::Settings & settings, const fetchers::Settings & settings, const std::string & host, const std::string & hostAndPath) const
const std::string & host,
const std::string & hostAndPath) const
{ {
Headers headers; Headers headers;
auto accessToken = getAccessToken(settings, host, hostAndPath); auto accessToken = getAccessToken(settings, host, hostAndPath);
@ -250,7 +244,8 @@ struct GitArchiveInputScheme : InputScheme
std::pair<Input, TarballInfo> downloadArchive(ref<Store> store, Input input) const std::pair<Input, TarballInfo> downloadArchive(ref<Store> store, Input input) const
{ {
if (!maybeGetStrAttr(input.attrs, "ref")) input.attrs.insert_or_assign("ref", "HEAD"); if (!maybeGetStrAttr(input.attrs, "ref"))
input.attrs.insert_or_assign("ref", "HEAD");
std::optional<Hash> upstreamTreeHash; std::optional<Hash> upstreamTreeHash;
@ -290,8 +285,8 @@ struct GitArchiveInputScheme : InputScheme
getFileTransfer()->download(std::move(req), sink); getFileTransfer()->download(std::move(req), sink);
}); });
auto act = std::make_unique<Activity>(*logger, lvlInfo, actUnknown, auto act = std::make_unique<Activity>(
fmt("unpacking '%s' into the Git cache", input.to_string())); *logger, lvlInfo, actUnknown, fmt("unpacking '%s' into the Git cache", input.to_string()));
TarArchive archive{*source}; TarArchive archive{*source};
auto tarballCache = getTarballCache(); auto tarballCache = getTarballCache();
@ -302,9 +297,7 @@ struct GitArchiveInputScheme : InputScheme
act.reset(); act.reset();
TarballInfo tarballInfo{ TarballInfo tarballInfo{
.treeHash = tarballCache->dereferenceSingletonDirectory(tree), .treeHash = tarballCache->dereferenceSingletonDirectory(tree), .lastModified = lastModified};
.lastModified = lastModified
};
cache->upsert(treeHashKey, Attrs{{"treeHash", tarballInfo.treeHash.gitRev()}}); cache->upsert(treeHashKey, Attrs{{"treeHash", tarballInfo.treeHash.gitRev()}});
cache->upsert(lastModifiedKey, Attrs{{"lastModified", (uint64_t) tarballInfo.lastModified}}); cache->upsert(lastModifiedKey, Attrs{{"lastModified", (uint64_t) tarballInfo.lastModified}});
@ -330,10 +323,7 @@ struct GitArchiveInputScheme : InputScheme
#endif #endif
input.attrs.insert_or_assign("lastModified", uint64_t(tarballInfo.lastModified)); input.attrs.insert_or_assign("lastModified", uint64_t(tarballInfo.lastModified));
auto accessor = getTarballCache()->getAccessor( auto accessor = getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
tarballInfo.treeHash,
false,
"«" + input.to_string() + "»");
return {accessor, input}; return {accessor, input};
} }
@ -345,8 +335,7 @@ struct GitArchiveInputScheme : InputScheme
locking. FIXME: in the future, we may want to require a Git locking. FIXME: in the future, we may want to require a Git
tree hash instead of a NAR hash. */ tree hash instead of a NAR hash. */
return input.getRev().has_value() return input.getRev().has_value()
&& (input.settings->trustTarballsFromGitForges || && (input.settings->trustTarballsFromGitForges || input.getNarHash().has_value());
input.getNarHash().has_value());
} }
std::optional<ExperimentalFeature> experimentalFeature() const override std::optional<ExperimentalFeature> experimentalFeature() const override
@ -365,7 +354,10 @@ struct GitArchiveInputScheme : InputScheme
struct GitHubInputScheme : GitArchiveInputScheme struct GitHubInputScheme : GitArchiveInputScheme
{ {
std::string_view schemeName() const override { return "github"; } std::string_view schemeName() const override
{
return "github";
}
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{ {
@ -397,22 +389,20 @@ struct GitHubInputScheme : GitArchiveInputScheme
{ {
auto host = getHost(input); auto host = getHost(input);
auto url = fmt( auto url = fmt(
host == "github.com" host == "github.com" ? "https://api.%s/repos/%s/%s/commits/%s" : "https://%s/api/v3/repos/%s/%s/commits/%s",
? "https://api.%s/repos/%s/%s/commits/%s" host,
: "https://%s/api/v3/repos/%s/%s/commits/%s", getOwner(input),
host, getOwner(input), getRepo(input), *input.getRef()); getRepo(input),
*input.getRef());
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input); Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
auto json = nlohmann::json::parse( auto json = nlohmann::json::parse(
readFile( readFile(store->toRealPath(downloadFile(store, *input.settings, url, "source", headers).storePath)));
store->toRealPath(
downloadFile(store, *input.settings, url, "source", headers).storePath)));
return RefInfo{ return RefInfo{
.rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1), .rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1),
.treeHash = Hash::parseAny(std::string { json["commit"]["tree"]["sha"] }, HashAlgorithm::SHA1) .treeHash = Hash::parseAny(std::string{json["commit"]["tree"]["sha"]}, HashAlgorithm::SHA1)};
};
} }
DownloadUrl getDownloadUrl(const Input & input) const override DownloadUrl getDownloadUrl(const Input & input) const override
@ -423,15 +413,12 @@ struct GitHubInputScheme : GitArchiveInputScheme
// If we have no auth headers then we default to the public archive // If we have no auth headers then we default to the public archive
// urls so we do not run into rate limits. // urls so we do not run into rate limits.
const auto urlFmt = const auto urlFmt = host != "github.com" ? "https://%s/api/v3/repos/%s/%s/tarball/%s"
host != "github.com" : headers.empty() ? "https://%s/%s/%s/archive/%s.tar.gz"
? "https://%s/api/v3/repos/%s/%s/tarball/%s"
: headers.empty()
? "https://%s/%s/%s/archive/%s.tar.gz"
: "https://api.%s/repos/%s/%s/tarball/%s"; : "https://api.%s/repos/%s/%s/tarball/%s";
const auto url = fmt(urlFmt, host, getOwner(input), getRepo(input), const auto url =
input.getRev()->to_string(HashFormat::Base16, false)); fmt(urlFmt, host, getOwner(input), getRepo(input), input.getRev()->to_string(HashFormat::Base16, false));
return DownloadUrl{url, headers}; return DownloadUrl{url, headers};
} }
@ -439,8 +426,7 @@ struct GitHubInputScheme : GitArchiveInputScheme
void clone(const Input & input, const Path & destDir) const override void clone(const Input & input, const Path & destDir) const override
{ {
auto host = getHost(input); auto host = getHost(input);
Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s.git", Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s.git", host, getOwner(input), getRepo(input)))
host, getOwner(input), getRepo(input)))
.applyOverrides(input.getRef(), input.getRev()) .applyOverrides(input.getRef(), input.getRev())
.clone(destDir); .clone(destDir);
} }
@ -448,7 +434,10 @@ struct GitHubInputScheme : GitArchiveInputScheme
struct GitLabInputScheme : GitArchiveInputScheme struct GitLabInputScheme : GitArchiveInputScheme
{ {
std::string_view schemeName() const override { return "gitlab"; } std::string_view schemeName() const override
{
return "gitlab";
}
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{ {
@ -473,21 +462,22 @@ struct GitLabInputScheme : GitArchiveInputScheme
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
// See rate limiting note below // See rate limiting note below
auto url = fmt("https://%s/api/v4/projects/%s%%2F%s/repository/commits?ref_name=%s", auto url =
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef()); fmt("https://%s/api/v4/projects/%s%%2F%s/repository/commits?ref_name=%s",
host,
getStrAttr(input.attrs, "owner"),
getStrAttr(input.attrs, "repo"),
*input.getRef());
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input); Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
auto json = nlohmann::json::parse( auto json = nlohmann::json::parse(
readFile( readFile(store->toRealPath(downloadFile(store, *input.settings, url, "source", headers).storePath)));
store->toRealPath(
downloadFile(store, *input.settings, url, "source", headers).storePath)));
if (json.is_array() && json.size() >= 1 && json[0]["id"] != nullptr) { if (json.is_array() && json.size() >= 1 && json[0]["id"] != nullptr) {
return RefInfo { return RefInfo{.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)};
.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1) }
}; if (json.is_array() && json.size() == 0) {
} if (json.is_array() && json.size() == 0) {
throw Error("No commits returned by GitLab API -- does the git ref really exist?"); throw Error("No commits returned by GitLab API -- does the git ref really exist?");
} else { } else {
throw Error("Unexpected response received from GitLab: %s", json); throw Error("Unexpected response received from GitLab: %s", json);
@ -502,8 +492,11 @@ struct GitLabInputScheme : GitArchiveInputScheme
// is 10 reqs/sec/ip-addr. See // is 10 reqs/sec/ip-addr. See
// https://docs.gitlab.com/ee/user/gitlab_com/index.html#gitlabcom-specific-rate-limits // https://docs.gitlab.com/ee/user/gitlab_com/index.html#gitlabcom-specific-rate-limits
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
auto url = fmt("https://%s/api/v4/projects/%s%%2F%s/repository/archive.tar.gz?sha=%s", auto url =
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), fmt("https://%s/api/v4/projects/%s%%2F%s/repository/archive.tar.gz?sha=%s",
host,
getStrAttr(input.attrs, "owner"),
getStrAttr(input.attrs, "repo"),
input.getRev()->to_string(HashFormat::Base16, false)); input.getRev()->to_string(HashFormat::Base16, false));
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input); Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
@ -514,8 +507,9 @@ struct GitLabInputScheme : GitArchiveInputScheme
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com");
// FIXME: get username somewhere // FIXME: get username somewhere
Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s.git", Input::fromURL(
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"))) *input.settings,
fmt("git+https://%s/%s/%s.git", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
.applyOverrides(input.getRef(), input.getRev()) .applyOverrides(input.getRef(), input.getRev())
.clone(destDir); .clone(destDir);
} }
@ -523,7 +517,10 @@ struct GitLabInputScheme : GitArchiveInputScheme
struct SourceHutInputScheme : GitArchiveInputScheme struct SourceHutInputScheme : GitArchiveInputScheme
{ {
std::string_view schemeName() const override { return "sourcehut"; } std::string_view schemeName() const override
{
return "sourcehut";
}
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
{ {
@ -543,8 +540,8 @@ struct SourceHutInputScheme : GitArchiveInputScheme
auto ref = *input.getRef(); auto ref = *input.getRef();
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
auto base_url = fmt("https://%s/%s/%s", auto base_url =
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")); fmt("https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"));
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input); Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
@ -581,16 +578,17 @@ struct SourceHutInputScheme : GitArchiveInputScheme
if (!id) if (!id)
throw BadURL("in '%d', couldn't find ref '%d'", input.to_string(), ref); throw BadURL("in '%d', couldn't find ref '%d'", input.to_string(), ref);
return RefInfo { return RefInfo{.rev = Hash::parseAny(*id, HashAlgorithm::SHA1)};
.rev = Hash::parseAny(*id, HashAlgorithm::SHA1)
};
} }
DownloadUrl getDownloadUrl(const Input & input) const override DownloadUrl getDownloadUrl(const Input & input) const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
auto url = fmt("https://%s/%s/%s/archive/%s.tar.gz", auto url =
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), fmt("https://%s/%s/%s/archive/%s.tar.gz",
host,
getStrAttr(input.attrs, "owner"),
getStrAttr(input.attrs, "repo"),
input.getRev()->to_string(HashFormat::Base16, false)); input.getRev()->to_string(HashFormat::Base16, false));
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input); Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
@ -600,8 +598,9 @@ struct SourceHutInputScheme : GitArchiveInputScheme
void clone(const Input & input, const Path & destDir) const override void clone(const Input & input, const Path & destDir) const override
{ {
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht"); auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
Input::fromURL(*input.settings, fmt("git+https://%s/%s/%s", Input::fromURL(
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"))) *input.settings,
fmt("git+https://%s/%s/%s", host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
.applyOverrides(input.getRef(), input.getRev()) .applyOverrides(input.getRef(), input.getRev())
.clone(destDir); .clone(destDir);
} }

View file

@ -28,22 +28,18 @@ struct Cache
/** /**
* Add a key/value pair to the cache. * Add a key/value pair to the cache.
*/ */
virtual void upsert( virtual void upsert(const Key & key, const Attrs & value) = 0;
const Key & key,
const Attrs & value) = 0;
/** /**
* Look up a key with infinite TTL. * Look up a key with infinite TTL.
*/ */
virtual std::optional<Attrs> lookup( virtual std::optional<Attrs> lookup(const Key & key) = 0;
const Key & key) = 0;
/** /**
* Look up a key. Return nothing if its TTL has exceeded * Look up a key. Return nothing if its TTL has exceeded
* `settings.tarballTTL`. * `settings.tarballTTL`.
*/ */
virtual std::optional<Attrs> lookupWithTTL( virtual std::optional<Attrs> lookupWithTTL(const Key & key) = 0;
const Key & key) = 0;
struct Result struct Result
{ {
@ -55,19 +51,14 @@ struct Cache
* Look up a key. Return a bool denoting whether its TTL has * Look up a key. Return a bool denoting whether its TTL has
* exceeded `settings.tarballTTL`. * exceeded `settings.tarballTTL`.
*/ */
virtual std::optional<Result> lookupExpired( virtual std::optional<Result> lookupExpired(const Key & key) = 0;
const Key & key) = 0;
/** /**
* Insert a cache entry that has a store path associated with * Insert a cache entry that has a store path associated with
* it. Such cache entries are always considered stale if the * it. Such cache entries are always considered stale if the
* associated store path is invalid. * associated store path is invalid.
*/ */
virtual void upsert( virtual void upsert(Key key, Store & store, Attrs value, const StorePath & storePath) = 0;
Key key,
Store & store,
Attrs value,
const StorePath & storePath) = 0;
struct ResultWithStorePath : Result struct ResultWithStorePath : Result
{ {
@ -78,17 +69,13 @@ struct Cache
* Look up a store path in the cache. The returned store path will * Look up a store path in the cache. The returned store path will
* be valid, but it may be expired. * be valid, but it may be expired.
*/ */
virtual std::optional<ResultWithStorePath> lookupStorePath( virtual std::optional<ResultWithStorePath> lookupStorePath(Key key, Store & store) = 0;
Key key,
Store & store) = 0;
/** /**
* Look up a store path in the cache. Return nothing if its TTL * Look up a store path in the cache. Return nothing if its TTL
* has exceeded `settings.tarballTTL`. * has exceeded `settings.tarballTTL`.
*/ */
virtual std::optional<ResultWithStorePath> lookupStorePathWithTTL( virtual std::optional<ResultWithStorePath> lookupStorePathWithTTL(Key key, Store & store) = 0;
Key key,
Store & store) = 0;
}; };
} }

View file

@ -19,7 +19,10 @@ struct Settings : public Config
{ {
Settings(); Settings();
Setting<StringMap> accessTokens{this, {}, "access-tokens", Setting<StringMap> accessTokens{
this,
{},
"access-tokens",
R"( R"(
Access tokens used to access protected GitHub, GitLab, or Access tokens used to access protected GitHub, GitLab, or
other locations requiring token-based authentication. other locations requiring token-based authentication.
@ -70,11 +73,9 @@ struct Settings : public Config
value. value.
)"}; )"};
Setting<bool> allowDirty{this, true, "allow-dirty", Setting<bool> allowDirty{this, true, "allow-dirty", "Whether to allow dirty Git/Mercurial trees."};
"Whether to allow dirty Git/Mercurial trees."};
Setting<bool> warnDirty{this, true, "warn-dirty", Setting<bool> warnDirty{this, true, "warn-dirty", "Whether to warn about dirty Git/Mercurial trees."};
"Whether to warn about dirty Git/Mercurial trees."};
Setting<bool> allowDirtyLocks{ Setting<bool> allowDirtyLocks{
this, this,
@ -93,7 +94,9 @@ struct Settings : public Config
Xp::Flakes}; Xp::Flakes};
Setting<bool> trustTarballsFromGitForges{ Setting<bool> trustTarballsFromGitForges{
this, true, "trust-tarballs-from-git-forges", this,
true,
"trust-tarballs-from-git-forges",
R"( R"(
If enabled (the default), Nix will consider tarballs from If enabled (the default), Nix will consider tarballs from
GitHub and similar Git forges to be locked if a Git revision GitHub and similar Git forges to be locked if a Git revision
@ -107,13 +110,18 @@ struct Settings : public Config
e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f?narHash=sha256-PPXqKY2hJng4DBVE0I4xshv/vGLUskL7jl53roB8UdU%3D`. e.g. `github:NixOS/patchelf/7c2f768bf9601268a4e71c2ebe91e2011918a70f?narHash=sha256-PPXqKY2hJng4DBVE0I4xshv/vGLUskL7jl53roB8UdU%3D`.
)"}; )"};
Setting<std::string> flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry", Setting<std::string> flakeRegistry{
this,
"https://channels.nixos.org/flake-registry.json",
"flake-registry",
R"( R"(
Path or URI of the global flake registry. Path or URI of the global flake registry.
When empty, disables the global flake registry. When empty, disables the global flake registry.
)", )",
{}, true, Xp::Flakes}; {},
true,
Xp::Flakes};
ref<Cache> getCache() const; ref<Cache> getCache() const;

View file

@ -13,7 +13,11 @@
#include "nix/util/ref.hh" #include "nix/util/ref.hh"
namespace nix { class Store; class StorePath; struct SourceAccessor; } namespace nix {
class Store;
class StorePath;
struct SourceAccessor;
}
namespace nix::fetchers { namespace nix::fetchers {
@ -36,7 +40,8 @@ struct Input
Input(const Settings & settings) Input(const Settings & settings)
: settings{&settings} : settings{&settings}
{ } {
}
std::shared_ptr<InputScheme> scheme; // note: can be null std::shared_ptr<InputScheme> scheme; // note: can be null
Attrs attrs; Attrs attrs;
@ -52,22 +57,16 @@ public:
* *
* The URL indicate which sort of fetcher, and provides information to that fetcher. * The URL indicate which sort of fetcher, and provides information to that fetcher.
*/ */
static Input fromURL( static Input fromURL(const Settings & settings, const std::string & url, bool requireTree = true);
const Settings & settings,
const std::string & url, bool requireTree = true);
static Input fromURL( static Input fromURL(const Settings & settings, const ParsedURL & url, bool requireTree = true);
const Settings & settings,
const ParsedURL & url, bool requireTree = true);
/** /**
* Create an `Input` from a an `Attrs`. * Create an `Input` from a an `Attrs`.
* *
* The URL indicate which sort of fetcher, and provides information to that fetcher. * The URL indicate which sort of fetcher, and provides information to that fetcher.
*/ */
static Input fromAttrs( static Input fromAttrs(const Settings & settings, Attrs && attrs);
const Settings & settings,
Attrs && attrs);
ParsedURL toURL() const; ParsedURL toURL() const;
@ -149,9 +148,7 @@ private:
public: public:
Input applyOverrides( Input applyOverrides(std::optional<std::string> ref, std::optional<Hash> rev) const;
std::optional<std::string> ref,
std::optional<Hash> rev) const;
void clone(const Path & destDir) const; void clone(const Path & destDir) const;
@ -161,10 +158,7 @@ public:
* Write a file to this input, for input types that support * Write a file to this input, for input types that support
* writing. Optionally commit the change (for e.g. Git inputs). * writing. Optionally commit the change (for e.g. Git inputs).
*/ */
void putFile( void putFile(const CanonPath & path, std::string_view contents, std::optional<std::string> commitMsg) const;
const CanonPath & path,
std::string_view contents,
std::optional<std::string> commitMsg) const;
std::string getName() const; std::string getName() const;
@ -200,16 +194,12 @@ public:
*/ */
struct InputScheme struct InputScheme
{ {
virtual ~InputScheme() virtual ~InputScheme() {}
{ }
virtual std::optional<Input> inputFromURL( virtual std::optional<Input>
const Settings & settings, inputFromURL(const Settings & settings, const ParsedURL & url, bool requireTree) const = 0;
const ParsedURL & url, bool requireTree) const = 0;
virtual std::optional<Input> inputFromAttrs( virtual std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const = 0;
const Settings & settings,
const Attrs & attrs) const = 0;
/** /**
* What is the name of the scheme? * What is the name of the scheme?
@ -231,10 +221,7 @@ struct InputScheme
virtual ParsedURL toURL(const Input & input) const; virtual ParsedURL toURL(const Input & input) const;
virtual Input applyOverrides( virtual Input applyOverrides(const Input & input, std::optional<std::string> ref, std::optional<Hash> rev) const;
const Input & input,
std::optional<std::string> ref,
std::optional<Hash> rev) const;
virtual void clone(const Input & input, const Path & destDir) const; virtual void clone(const Input & input, const Path & destDir) const;
@ -254,19 +241,30 @@ struct InputScheme
virtual std::optional<ExperimentalFeature> experimentalFeature() const; virtual std::optional<ExperimentalFeature> experimentalFeature() const;
virtual bool isDirect(const Input & input) const virtual bool isDirect(const Input & input) const
{ return true; } {
return true;
}
virtual std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const virtual std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const
{ return std::nullopt; } {
return std::nullopt;
}
virtual bool isLocked(const Input & input) const virtual bool isLocked(const Input & input) const
{ return false; } {
return false;
}
virtual std::optional<std::string> isRelative(const Input & input) const virtual std::optional<std::string> isRelative(const Input & input) const
{ return std::nullopt; } {
return std::nullopt;
}
virtual std::optional<std::string> getAccessToken(const fetchers::Settings & settings, const std::string & host, const std::string & url) const virtual std::optional<std::string>
{ return {};} getAccessToken(const fetchers::Settings & settings, const std::string & host, const std::string & url) const
{
return {};
}
}; };
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher); void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);

View file

@ -5,7 +5,10 @@
namespace nix { namespace nix {
namespace fetchers { struct PublicKey; struct Settings; } namespace fetchers {
struct PublicKey;
struct Settings;
}
/** /**
* A sink that writes into a Git repository. Note that nothing may be written * A sink that writes into a Git repository. Note that nothing may be written
@ -21,8 +24,7 @@ struct GitFileSystemObjectSink : ExtendedFileSystemObjectSink
struct GitRepo struct GitRepo
{ {
virtual ~GitRepo() virtual ~GitRepo() {}
{ }
static ref<GitRepo> openRepo(const std::filesystem::path & path, bool create = false, bool bare = false); static ref<GitRepo> openRepo(const std::filesystem::path & path, bool create = false, bool bare = false);
@ -86,30 +88,23 @@ struct GitRepo
virtual bool hasObject(const Hash & oid) = 0; virtual bool hasObject(const Hash & oid) = 0;
virtual ref<SourceAccessor> getAccessor( virtual ref<SourceAccessor>
const Hash & rev, getAccessor(const Hash & rev, bool exportIgnore, std::string displayPrefix, bool smudgeLfs = false) = 0;
bool exportIgnore,
std::string displayPrefix,
bool smudgeLfs = false) = 0;
virtual ref<SourceAccessor> getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) = 0; virtual ref<SourceAccessor>
getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) = 0;
virtual ref<GitFileSystemObjectSink> getFileSystemObjectSink() = 0; virtual ref<GitFileSystemObjectSink> getFileSystemObjectSink() = 0;
virtual void flush() = 0; virtual void flush() = 0;
virtual void fetch( virtual void fetch(const std::string & url, const std::string & refspec, bool shallow) = 0;
const std::string & url,
const std::string & refspec,
bool shallow) = 0;
/** /**
* Verify that commit `rev` is signed by one of the keys in * Verify that commit `rev` is signed by one of the keys in
* `publicKeys`. Throw an error if it isn't. * `publicKeys`. Throw an error if it isn't.
*/ */
virtual void verifyCommit( virtual void verifyCommit(const Hash & rev, const std::vector<fetchers::PublicKey> & publicKeys) = 0;
const Hash & rev,
const std::vector<fetchers::PublicKey> & publicKeys) = 0;
/** /**
* Given a Git tree hash, compute the hash of its NAR * Given a Git tree hash, compute the hash of its NAR
@ -132,7 +127,10 @@ template<auto del>
struct Deleter struct Deleter
{ {
template<typename T> template<typename T>
void operator()(T * p) const { del(p); }; void operator()(T * p) const
{
del(p);
};
}; };
// A helper to ensure that we don't leak objects returned by libgit2. // A helper to ensure that we don't leak objects returned by libgit2.
@ -142,11 +140,21 @@ struct Setter
T & t; T & t;
typename T::pointer p = nullptr; typename T::pointer p = nullptr;
Setter(T & t) : t(t) { } Setter(T & t)
: t(t)
{
}
~Setter() { if (p) t = T(p); } ~Setter()
{
if (p)
t = T(p);
}
operator typename T::pointer * () { return &p; } operator typename T::pointer *()
{
return &p;
}
}; };
} }

View file

@ -4,7 +4,9 @@
#include "nix/util/types.hh" #include "nix/util/types.hh"
#include "nix/fetchers/fetchers.hh" #include "nix/fetchers/fetchers.hh"
namespace nix { class Store; } namespace nix {
class Store;
}
namespace nix::fetchers { namespace nix::fetchers {
@ -34,18 +36,14 @@ struct Registry
Registry(const Settings & settings, RegistryType type) Registry(const Settings & settings, RegistryType type)
: settings{settings} : settings{settings}
, type{type} , type{type}
{ } {
}
static std::shared_ptr<Registry> read( static std::shared_ptr<Registry> read(const Settings & settings, const Path & path, RegistryType type);
const Settings & settings,
const Path & path, RegistryType type);
void write(const Path & path); void write(const Path & path);
void add( void add(const Input & from, const Input & to, const Attrs & extraAttrs);
const Input & from,
const Input & to,
const Attrs & extraAttrs);
void remove(const Input & input); void remove(const Input & input);
}; };
@ -60,10 +58,7 @@ Path getUserRegistryPath();
Registries getRegistries(const Settings & settings, ref<Store> store); Registries getRegistries(const Settings & settings, ref<Store> store);
void overrideRegistry( void overrideRegistry(const Input & from, const Input & to, const Attrs & extraAttrs);
const Input & from,
const Input & to,
const Attrs & extraAttrs);
enum class UseRegistries : int { enum class UseRegistries : int {
No, No,
@ -75,9 +70,6 @@ enum class UseRegistries : int {
* Rewrite a flakeref using the registries. If `filter` is set, only * Rewrite a flakeref using the registries. If `filter` is set, only
* use the registries for which the filter function returns true. * use the registries for which the filter function returns true.
*/ */
std::pair<Input, Attrs> lookupInRegistries( std::pair<Input, Attrs> lookupInRegistries(ref<Store> store, const Input & input, UseRegistries useRegistries);
ref<Store> store,
const Input & input,
UseRegistries useRegistries);
} }

View file

@ -43,9 +43,6 @@ struct DownloadTarballResult
* Download and import a tarball into the Git cache. The result is the * Download and import a tarball into the Git cache. The result is the
* Git tree hash of the root directory. * Git tree hash of the root directory.
*/ */
ref<SourceAccessor> downloadTarball( ref<SourceAccessor> downloadTarball(ref<Store> store, const Settings & settings, const std::string & url);
ref<Store> store,
const Settings & settings,
const std::string & url);
} }

View file

@ -8,11 +8,10 @@ std::regex flakeRegex("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript);
struct IndirectInputScheme : InputScheme struct IndirectInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL( std::optional<Input> inputFromURL(const Settings & settings, const ParsedURL & url, bool requireTree) const override
const Settings & settings,
const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "flake") return {}; if (url.scheme != "flake")
return {};
auto path = tokenizeString<std::vector<std::string>>(url.path, "/"); auto path = tokenizeString<std::vector<std::string>>(url.path, "/");
@ -46,8 +45,10 @@ struct IndirectInputScheme : InputScheme
Input input{settings}; Input input{settings};
input.attrs.insert_or_assign("type", "indirect"); input.attrs.insert_or_assign("type", "indirect");
input.attrs.insert_or_assign("id", id); input.attrs.insert_or_assign("id", id);
if (rev) input.attrs.insert_or_assign("rev", rev->gitRev()); if (rev)
if (ref) input.attrs.insert_or_assign("ref", *ref); input.attrs.insert_or_assign("rev", rev->gitRev());
if (ref)
input.attrs.insert_or_assign("ref", *ref);
return input; return input;
} }
@ -67,9 +68,7 @@ struct IndirectInputScheme : InputScheme
}; };
} }
std::optional<Input> inputFromAttrs( std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
const Settings & settings,
const Attrs & attrs) const override
{ {
auto id = getStrAttr(attrs, "id"); auto id = getStrAttr(attrs, "id");
if (!std::regex_match(id, flakeRegex)) if (!std::regex_match(id, flakeRegex))
@ -85,19 +84,24 @@ struct IndirectInputScheme : InputScheme
ParsedURL url; ParsedURL url;
url.scheme = "flake"; url.scheme = "flake";
url.path = getStrAttr(input.attrs, "id"); url.path = getStrAttr(input.attrs, "id");
if (auto ref = input.getRef()) { url.path += '/'; url.path += *ref; }; if (auto ref = input.getRef()) {
if (auto rev = input.getRev()) { url.path += '/'; url.path += rev->gitRev(); }; url.path += '/';
url.path += *ref;
};
if (auto rev = input.getRev()) {
url.path += '/';
url.path += rev->gitRev();
};
return url; return url;
} }
Input applyOverrides( Input applyOverrides(const Input & _input, std::optional<std::string> ref, std::optional<Hash> rev) const override
const Input & _input,
std::optional<std::string> ref,
std::optional<Hash> rev) const override
{ {
auto input(_input); auto input(_input);
if (rev) input.attrs.insert_or_assign("rev", rev->gitRev()); if (rev)
if (ref) input.attrs.insert_or_assign("ref", *ref); input.attrs.insert_or_assign("rev", rev->gitRev());
if (ref)
input.attrs.insert_or_assign("ref", *ref);
return input; return input;
} }
@ -112,7 +116,9 @@ struct IndirectInputScheme : InputScheme
} }
bool isDirect(const Input & input) const override bool isDirect(const Input & input) const override
{ return false; } {
return false;
}
}; };
static auto rIndirectInputScheme = OnStartup([] { registerInputScheme(std::make_unique<IndirectInputScheme>()); }); static auto rIndirectInputScheme = OnStartup([] { registerInputScheme(std::make_unique<IndirectInputScheme>()); });

View file

@ -21,12 +21,7 @@ static RunOptions hgOptions(const Strings & args)
// Set HGPLAIN: this means we get consistent output from hg and avoids leakage from a user or system .hgrc. // Set HGPLAIN: this means we get consistent output from hg and avoids leakage from a user or system .hgrc.
env["HGPLAIN"] = ""; env["HGPLAIN"] = "";
return { return {.program = "hg", .lookupPath = true, .args = args, .environment = env};
.program = "hg",
.lookupPath = true,
.args = args,
.environment = env
};
} }
// runProgram wrapper that uses hgOptions instead of stock RunOptions. // runProgram wrapper that uses hgOptions instead of stock RunOptions.
@ -45,14 +40,10 @@ static std::string runHg(const Strings & args, const std::optional<std::string>
struct MercurialInputScheme : InputScheme struct MercurialInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL( std::optional<Input> inputFromURL(const Settings & settings, const ParsedURL & url, bool requireTree) const override
const Settings & settings,
const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "hg+http" && if (url.scheme != "hg+http" && url.scheme != "hg+https" && url.scheme != "hg+ssh" && url.scheme != "hg+file")
url.scheme != "hg+https" && return {};
url.scheme != "hg+ssh" &&
url.scheme != "hg+file") return {};
auto url2(url); auto url2(url);
url2.scheme = std::string(url2.scheme, 3); url2.scheme = std::string(url2.scheme, 3);
@ -90,9 +81,7 @@ struct MercurialInputScheme : InputScheme
}; };
} }
std::optional<Input> inputFromAttrs( std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
const Settings & settings,
const Attrs & attrs) const override
{ {
parseURL(getStrAttr(attrs, "url")); parseURL(getStrAttr(attrs, "url"));
@ -110,19 +99,20 @@ struct MercurialInputScheme : InputScheme
{ {
auto url = parseURL(getStrAttr(input.attrs, "url")); auto url = parseURL(getStrAttr(input.attrs, "url"));
url.scheme = "hg+" + url.scheme; url.scheme = "hg+" + url.scheme;
if (auto rev = input.getRev()) url.query.insert_or_assign("rev", rev->gitRev()); if (auto rev = input.getRev())
if (auto ref = input.getRef()) url.query.insert_or_assign("ref", *ref); url.query.insert_or_assign("rev", rev->gitRev());
if (auto ref = input.getRef())
url.query.insert_or_assign("ref", *ref);
return url; return url;
} }
Input applyOverrides( Input applyOverrides(const Input & input, std::optional<std::string> ref, std::optional<Hash> rev) const override
const Input & input,
std::optional<std::string> ref,
std::optional<Hash> rev) const override
{ {
auto res(input); auto res(input);
if (rev) res.attrs.insert_or_assign("rev", rev->gitRev()); if (rev)
if (ref) res.attrs.insert_or_assign("ref", *ref); res.attrs.insert_or_assign("rev", rev->gitRev());
if (ref)
res.attrs.insert_or_assign("ref", *ref);
return res; return res;
} }
@ -142,19 +132,20 @@ struct MercurialInputScheme : InputScheme
{ {
auto [isLocal, repoPath] = getActualUrl(input); auto [isLocal, repoPath] = getActualUrl(input);
if (!isLocal) if (!isLocal)
throw Error("cannot commit '%s' to Mercurial repository '%s' because it's not a working tree", path, input.to_string()); throw Error(
"cannot commit '%s' to Mercurial repository '%s' because it's not a working tree",
path,
input.to_string());
auto absPath = CanonPath(repoPath) / path; auto absPath = CanonPath(repoPath) / path;
writeFile(absPath.abs(), contents); writeFile(absPath.abs(), contents);
// FIXME: shut up if file is already tracked. // FIXME: shut up if file is already tracked.
runHg( runHg({"add", absPath.abs()});
{ "add", absPath.abs() });
if (commitMsg) if (commitMsg)
runHg( runHg({"commit", absPath.abs(), "-m", *commitMsg});
{ "commit", absPath.abs(), "-m", *commitMsg });
} }
std::pair<bool, std::string> getActualUrl(const Input & input) const std::pair<bool, std::string> getActualUrl(const Input & input) const
@ -195,7 +186,8 @@ struct MercurialInputScheme : InputScheme
input.attrs.insert_or_assign("ref", chomp(runHg({"branch", "-R", actualUrl}))); input.attrs.insert_or_assign("ref", chomp(runHg({"branch", "-R", actualUrl})));
auto files = tokenizeString<StringSet>( auto files = tokenizeString<StringSet>(
runHg({ "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s); runHg({"status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0"}),
"\0"s);
Path actualPath(absPath(actualUrl)); Path actualPath(absPath(actualUrl));
@ -217,29 +209,28 @@ struct MercurialInputScheme : InputScheme
auto storePath = store->addToStore( auto storePath = store->addToStore(
input.getName(), input.getName(),
{getFSSourceAccessor(), CanonPath(actualPath)}, {getFSSourceAccessor(), CanonPath(actualPath)},
ContentAddressMethod::Raw::NixArchive, HashAlgorithm::SHA256, {}, ContentAddressMethod::Raw::NixArchive,
HashAlgorithm::SHA256,
{},
filter); filter);
return storePath; return storePath;
} }
} }
if (!input.getRef()) input.attrs.insert_or_assign("ref", "default"); if (!input.getRef())
input.attrs.insert_or_assign("ref", "default");
auto revInfoKey = [&](const Hash & rev) auto revInfoKey = [&](const Hash & rev) {
{
if (rev.algo != HashAlgorithm::SHA1) if (rev.algo != HashAlgorithm::SHA1)
throw Error("Hash '%s' is not supported by Mercurial. Only sha1 is supported.", rev.to_string(HashFormat::Base16, true)); throw Error(
"Hash '%s' is not supported by Mercurial. Only sha1 is supported.",
rev.to_string(HashFormat::Base16, true));
return Cache::Key{"hgRev", { return Cache::Key{"hgRev", {{"store", store->storeDir}, {"name", name}, {"rev", input.getRev()->gitRev()}}};
{"store", store->storeDir},
{"name", name},
{"rev", input.getRev()->gitRev()}
}};
}; };
auto makeResult = [&](const Attrs & infoAttrs, const StorePath & storePath) -> StorePath auto makeResult = [&](const Attrs & infoAttrs, const StorePath & storePath) -> StorePath {
{
assert(input.getRev()); assert(input.getRev());
assert(!origRev || origRev == input.getRev()); assert(!origRev || origRev == input.getRev());
input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount")); input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount"));
@ -247,10 +238,7 @@ struct MercurialInputScheme : InputScheme
}; };
/* Check the cache for the most recent rev for this URL/ref. */ /* Check the cache for the most recent rev for this URL/ref. */
Cache::Key refToRevKey{"hgRefToRev", { Cache::Key refToRevKey{"hgRefToRev", {{"url", actualUrl}, {"ref", *input.getRef()}}};
{"url", actualUrl},
{"ref", *input.getRef()}
}};
if (!input.getRev()) { if (!input.getRev()) {
if (auto res = input.settings->getCache()->lookupWithTTL(refToRevKey)) if (auto res = input.settings->getCache()->lookupWithTTL(refToRevKey))
@ -263,21 +251,23 @@ struct MercurialInputScheme : InputScheme
return makeResult(res->value, res->storePath); return makeResult(res->value, res->storePath);
} }
Path cacheDir = fmt("%s/hg/%s", getCacheDir(), hashString(HashAlgorithm::SHA256, actualUrl).to_string(HashFormat::Nix32, false)); Path cacheDir =
fmt("%s/hg/%s",
getCacheDir(),
hashString(HashAlgorithm::SHA256, actualUrl).to_string(HashFormat::Nix32, false));
/* If this is a commit hash that we already have, we don't /* If this is a commit hash that we already have, we don't
have to pull again. */ have to pull again. */
if (!(input.getRev() if (!(input.getRev() && pathExists(cacheDir)
&& pathExists(cacheDir) && runProgram(hgOptions({"log", "-R", cacheDir, "-r", input.getRev()->gitRev(), "--template", "1"}))
&& runProgram(hgOptions({ "log", "-R", cacheDir, "-r", input.getRev()->gitRev(), "--template", "1" })).second == "1")) .second
{ == "1")) {
Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", actualUrl)); Activity act(*logger, lvlTalkative, actUnknown, fmt("fetching Mercurial repository '%s'", actualUrl));
if (pathExists(cacheDir)) { if (pathExists(cacheDir)) {
try { try {
runHg({"pull", "-R", cacheDir, "--", actualUrl}); runHg({"pull", "-R", cacheDir, "--", actualUrl});
} } catch (ExecError & e) {
catch (ExecError & e) {
auto transJournal = cacheDir + "/.hg/store/journal"; auto transJournal = cacheDir + "/.hg/store/journal";
/* hg throws "abandoned transaction" error only if this file exists */ /* hg throws "abandoned transaction" error only if this file exists */
if (pathExists(transJournal)) { if (pathExists(transJournal)) {
@ -294,12 +284,14 @@ struct MercurialInputScheme : InputScheme
} }
/* Fetch the remote rev or ref. */ /* Fetch the remote rev or ref. */
auto tokens = tokenizeString<std::vector<std::string>>( auto tokens = tokenizeString<std::vector<std::string>>(runHg(
runHg({ {"log",
"log", "-R", cacheDir, "-R",
"-r", input.getRev() ? input.getRev()->gitRev() : *input.getRef(), cacheDir,
"--template", "{node} {rev} {branch}" "-r",
})); input.getRev() ? input.getRev()->gitRev() : *input.getRef(),
"--template",
"{node} {rev} {branch}"}));
assert(tokens.size() == 3); assert(tokens.size() == 3);
auto rev = Hash::parseAny(tokens[0], HashAlgorithm::SHA1); auto rev = Hash::parseAny(tokens[0], HashAlgorithm::SHA1);

View file

@ -10,11 +10,10 @@ namespace nix::fetchers {
struct PathInputScheme : InputScheme struct PathInputScheme : InputScheme
{ {
std::optional<Input> inputFromURL( std::optional<Input> inputFromURL(const Settings & settings, const ParsedURL & url, bool requireTree) const override
const Settings & settings,
const ParsedURL & url, bool requireTree) const override
{ {
if (url.scheme != "path") return {}; if (url.scheme != "path")
return {};
if (url.authority && *url.authority != "") if (url.authority && *url.authority != "")
throw Error("path URL '%s' should not have an authority ('%s')", url, *url.authority); throw Error("path URL '%s' should not have an authority ('%s')", url, *url.authority);
@ -31,8 +30,7 @@ struct PathInputScheme : InputScheme
input.attrs.insert_or_assign(name, *n); input.attrs.insert_or_assign(name, *n);
else else
throw Error("path URL '%s' has invalid parameter '%s'", url, name); throw Error("path URL '%s' has invalid parameter '%s'", url, name);
} } else
else
throw Error("path URL '%s' has unsupported parameter '%s'", url, name); throw Error("path URL '%s' has unsupported parameter '%s'", url, name);
return input; return input;
@ -59,9 +57,7 @@ struct PathInputScheme : InputScheme
}; };
} }
std::optional<Input> inputFromAttrs( std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
const Settings & settings,
const Attrs & attrs) const override
{ {
getStrAttr(attrs, "path"); getStrAttr(attrs, "path");
@ -139,9 +135,8 @@ struct PathInputScheme : InputScheme
time_t mtime = 0; time_t mtime = 0;
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) { if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
// FIXME: try to substitute storePath. // FIXME: try to substitute storePath.
auto src = sinkToSource([&](Sink & sink) { auto src = sinkToSource(
mtime = dumpPathAndGetMtime(absPath.string(), sink, defaultPathFilter); [&](Sink & sink) { mtime = dumpPathAndGetMtime(absPath.string(), sink, defaultPathFilter); });
});
storePath = store->addToStoreFromDump(*src, "source"); storePath = store->addToStoreFromDump(*src, "source");
} }

View file

@ -10,9 +10,7 @@
namespace nix::fetchers { namespace nix::fetchers {
std::shared_ptr<Registry> Registry::read( std::shared_ptr<Registry> Registry::read(const Settings & settings, const Path & path, RegistryType type)
const Settings & settings,
const Path & path, RegistryType type)
{ {
debug("reading registry '%s'", path); debug("reading registry '%s'", path);
@ -42,8 +40,7 @@ std::shared_ptr<Registry> Registry::read(
.from = Input::fromAttrs(settings, jsonToAttrs(i["from"])), .from = Input::fromAttrs(settings, jsonToAttrs(i["from"])),
.to = Input::fromAttrs(settings, std::move(toAttrs)), .to = Input::fromAttrs(settings, std::move(toAttrs)),
.extraAttrs = extraAttrs, .extraAttrs = extraAttrs,
.exact = exact != i.end() && exact.value() .exact = exact != i.end() && exact.value()});
});
} }
} }
@ -81,17 +78,9 @@ void Registry::write(const Path & path)
writeFile(path, json.dump(2)); writeFile(path, json.dump(2));
} }
void Registry::add( void Registry::add(const Input & from, const Input & to, const Attrs & extraAttrs)
const Input & from,
const Input & to,
const Attrs & extraAttrs)
{ {
entries.emplace_back( entries.emplace_back(Entry{.from = from, .to = to, .extraAttrs = extraAttrs});
Entry {
.from = from,
.to = to,
.extraAttrs = extraAttrs
});
} }
void Registry::remove(const Input & input) void Registry::remove(const Input & input)
@ -108,8 +97,7 @@ static Path getSystemRegistryPath()
static std::shared_ptr<Registry> getSystemRegistry(const Settings & settings) static std::shared_ptr<Registry> getSystemRegistry(const Settings & settings)
{ {
static auto systemRegistry = static auto systemRegistry = Registry::read(settings, getSystemRegistryPath(), Registry::System);
Registry::read(settings, getSystemRegistryPath(), Registry::System);
return systemRegistry; return systemRegistry;
} }
@ -120,29 +108,23 @@ Path getUserRegistryPath()
std::shared_ptr<Registry> getUserRegistry(const Settings & settings) std::shared_ptr<Registry> getUserRegistry(const Settings & settings)
{ {
static auto userRegistry = static auto userRegistry = Registry::read(settings, getUserRegistryPath(), Registry::User);
Registry::read(settings, getUserRegistryPath(), Registry::User);
return userRegistry; return userRegistry;
} }
std::shared_ptr<Registry> getCustomRegistry(const Settings & settings, const Path & p) std::shared_ptr<Registry> getCustomRegistry(const Settings & settings, const Path & p)
{ {
static auto customRegistry = static auto customRegistry = Registry::read(settings, p, Registry::Custom);
Registry::read(settings, p, Registry::Custom);
return customRegistry; return customRegistry;
} }
std::shared_ptr<Registry> getFlagRegistry(const Settings & settings) std::shared_ptr<Registry> getFlagRegistry(const Settings & settings)
{ {
static auto flagRegistry = static auto flagRegistry = std::make_shared<Registry>(settings, Registry::Flag);
std::make_shared<Registry>(settings, Registry::Flag);
return flagRegistry; return flagRegistry;
} }
void overrideRegistry( void overrideRegistry(const Input & from, const Input & to, const Attrs & extraAttrs)
const Input & from,
const Input & to,
const Attrs & extraAttrs)
{ {
getFlagRegistry(*from.settings)->add(from, to, extraAttrs); getFlagRegistry(*from.settings)->add(from, to, extraAttrs);
} }
@ -178,10 +160,7 @@ Registries getRegistries(const Settings & settings, ref<Store> store)
return registries; return registries;
} }
std::pair<Input, Attrs> lookupInRegistries( std::pair<Input, Attrs> lookupInRegistries(ref<Store> store, const Input & _input, UseRegistries useRegistries)
ref<Store> store,
const Input & _input,
UseRegistries useRegistries)
{ {
Attrs extraAttrs; Attrs extraAttrs;
int n = 0; int n = 0;
@ -193,7 +172,8 @@ std::pair<Input, Attrs> lookupInRegistries(
restart: restart:
n++; n++;
if (n > 100) throw Error("cycle detected in flake registry for '%s'", input.to_string()); if (n > 100)
throw Error("cycle detected in flake registry for '%s'", input.to_string());
for (auto & registry : getRegistries(*input.settings, store)) { for (auto & registry : getRegistries(*input.settings, store)) {
if (useRegistries == UseRegistries::Limited if (useRegistries == UseRegistries::Limited

View file

@ -22,15 +22,16 @@ DownloadFileResult downloadFile(
{ {
// FIXME: check store // FIXME: check store
Cache::Key key{"file", {{ Cache::Key key{
"file",
{{
{"url", url}, {"url", url},
{"name", name}, {"name", name},
}}}; }}};
auto cached = settings.getCache()->lookupStorePath(key, *store); auto cached = settings.getCache()->lookupStorePath(key, *store);
auto useCached = [&]() -> DownloadFileResult auto useCached = [&]() -> DownloadFileResult {
{
return { return {
.storePath = std::move(cached->storePath), .storePath = std::move(cached->storePath),
.etag = getStrAttr(cached->value, "etag"), .etag = getStrAttr(cached->value, "etag"),
@ -106,17 +107,13 @@ DownloadFileResult downloadFile(
} }
static DownloadTarballResult downloadTarball_( static DownloadTarballResult downloadTarball_(
const Settings & settings, const Settings & settings, const std::string & url, const Headers & headers, const std::string & displayPrefix)
const std::string & url,
const Headers & headers,
const std::string & displayPrefix)
{ {
Cache::Key cacheKey{"tarball", {{"url", url}}}; Cache::Key cacheKey{"tarball", {{"url", url}}};
auto cached = settings.getCache()->lookupExpired(cacheKey); auto cached = settings.getCache()->lookupExpired(cacheKey);
auto attrsToResult = [&](const Attrs & infoAttrs) auto attrsToResult = [&](const Attrs & infoAttrs) {
{
auto treeHash = getRevAttr(infoAttrs, "treeHash"); auto treeHash = getRevAttr(infoAttrs, "treeHash");
return DownloadTarballResult{ return DownloadTarballResult{
.treeHash = treeHash, .treeHash = treeHash,
@ -139,25 +136,18 @@ static DownloadTarballResult downloadTarball_(
auto source = sinkToSource([&](Sink & sink) { auto source = sinkToSource([&](Sink & sink) {
FileTransferRequest req(url); FileTransferRequest req(url);
req.expectedETag = cached ? getStrAttr(cached->value, "etag") : ""; req.expectedETag = cached ? getStrAttr(cached->value, "etag") : "";
getFileTransfer()->download(std::move(req), sink, getFileTransfer()->download(std::move(req), sink, [_res](FileTransferResult r) { *_res->lock() = r; });
[_res](FileTransferResult r)
{
*_res->lock() = r;
});
}); });
// TODO: fall back to cached value if download fails. // TODO: fall back to cached value if download fails.
auto act = std::make_unique<Activity>(*logger, lvlInfo, actUnknown, auto act = std::make_unique<Activity>(*logger, lvlInfo, actUnknown, fmt("unpacking '%s' into the Git cache", url));
fmt("unpacking '%s' into the Git cache", url));
AutoDelete cleanupTemp; AutoDelete cleanupTemp;
/* Note: if the download is cached, `importTarball()` will receive /* Note: if the download is cached, `importTarball()` will receive
no data, which causes it to import an empty tarball. */ no data, which causes it to import an empty tarball. */
auto archive = auto archive = hasSuffix(toLower(parseURL(url).path), ".zip") ? ({
hasSuffix(toLower(parseURL(url).path), ".zip")
? ({
/* In streaming mode, libarchive doesn't handle /* In streaming mode, libarchive doesn't handle
symlinks in zip files correctly (#10649). So write symlinks in zip files correctly (#10649). So write
the entire file to disk so libarchive can access it the entire file to disk so libarchive can access it
@ -189,8 +179,7 @@ static DownloadTarballResult downloadTarball_(
infoAttrs = cached->value; infoAttrs = cached->value;
} else { } else {
infoAttrs.insert_or_assign("etag", res->etag); infoAttrs.insert_or_assign("etag", res->etag);
infoAttrs.insert_or_assign("treeHash", infoAttrs.insert_or_assign("treeHash", tarballCache->dereferenceSingletonDirectory(tree).gitRev());
tarballCache->dereferenceSingletonDirectory(tree).gitRev());
infoAttrs.insert_or_assign("lastModified", uint64_t(lastModified)); infoAttrs.insert_or_assign("lastModified", uint64_t(lastModified));
if (res->immutableUrl) if (res->immutableUrl)
infoAttrs.insert_or_assign("immutableUrl", *res->immutableUrl); infoAttrs.insert_or_assign("immutableUrl", *res->immutableUrl);
@ -208,10 +197,7 @@ static DownloadTarballResult downloadTarball_(
return attrsToResult(infoAttrs); return attrsToResult(infoAttrs);
} }
ref<SourceAccessor> downloadTarball( ref<SourceAccessor> downloadTarball(ref<Store> store, const Settings & settings, const std::string & url)
ref<Store> store,
const Settings & settings,
const std::string & url)
{ {
/* Go through Input::getAccessor() to ensure that the resulting /* Go through Input::getAccessor() to ensure that the resulting
accessor has a fingerprint. */ accessor has a fingerprint. */
@ -231,9 +217,8 @@ struct CurlInputScheme : InputScheme
bool hasTarballExtension(std::string_view path) const bool hasTarballExtension(std::string_view path) const
{ {
return hasSuffix(path, ".zip") || hasSuffix(path, ".tar") return hasSuffix(path, ".zip") || hasSuffix(path, ".tar") || hasSuffix(path, ".tgz")
|| hasSuffix(path, ".tgz") || hasSuffix(path, ".tar.gz") || hasSuffix(path, ".tar.gz") || hasSuffix(path, ".tar.xz") || hasSuffix(path, ".tar.bz2")
|| hasSuffix(path, ".tar.xz") || hasSuffix(path, ".tar.bz2")
|| hasSuffix(path, ".tar.zst"); || hasSuffix(path, ".tar.zst");
} }
@ -241,9 +226,8 @@ struct CurlInputScheme : InputScheme
static const StringSet specialParams; static const StringSet specialParams;
std::optional<Input> inputFromURL( std::optional<Input>
const Settings & settings, inputFromURL(const Settings & settings, const ParsedURL & _url, bool requireTree) const override
const ParsedURL & _url, bool requireTree) const override
{ {
if (!isValidURL(_url, requireTree)) if (!isValidURL(_url, requireTree))
return std::nullopt; return std::nullopt;
@ -296,9 +280,7 @@ struct CurlInputScheme : InputScheme
}; };
} }
std::optional<Input> inputFromAttrs( std::optional<Input> inputFromAttrs(const Settings & settings, const Attrs & attrs) const override
const Settings & settings,
const Attrs & attrs) const override
{ {
Input input{settings}; Input input{settings};
input.attrs = attrs; input.attrs = attrs;
@ -325,14 +307,16 @@ struct CurlInputScheme : InputScheme
struct FileInputScheme : CurlInputScheme struct FileInputScheme : CurlInputScheme
{ {
std::string_view schemeName() const override { return "file"; } std::string_view schemeName() const override
{
return "file";
}
bool isValidURL(const ParsedURL & url, bool requireTree) const override bool isValidURL(const ParsedURL & url, bool requireTree) const override
{ {
auto parsedUrlScheme = parseUrlScheme(url.scheme); auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport)) return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application && (parsedUrlScheme.application ? parsedUrlScheme.application.value() == schemeName()
? parsedUrlScheme.application.value() == schemeName()
: (!requireTree && !hasTarballExtension(url.path))); : (!requireTree && !hasTarballExtension(url.path)));
} }
@ -359,15 +343,17 @@ struct FileInputScheme : CurlInputScheme
struct TarballInputScheme : CurlInputScheme struct TarballInputScheme : CurlInputScheme
{ {
std::string_view schemeName() const override { return "tarball"; } std::string_view schemeName() const override
{
return "tarball";
}
bool isValidURL(const ParsedURL & url, bool requireTree) const override bool isValidURL(const ParsedURL & url, bool requireTree) const override
{ {
auto parsedUrlScheme = parseUrlScheme(url.scheme); auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport)) return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application && (parsedUrlScheme.application ? parsedUrlScheme.application.value() == schemeName()
? parsedUrlScheme.application.value() == schemeName()
: (requireTree || hasTarballExtension(url.path))); : (requireTree || hasTarballExtension(url.path)));
} }
@ -375,11 +361,8 @@ struct TarballInputScheme : CurlInputScheme
{ {
auto input(_input); auto input(_input);
auto result = downloadTarball_( auto result =
*input.settings, downloadTarball_(*input.settings, getStrAttr(input.attrs, "url"), {}, "«" + input.to_string() + "»");
getStrAttr(input.attrs, "url"),
{},
"«" + input.to_string() + "»");
if (result.immutableUrl) { if (result.immutableUrl) {
auto immutableInput = Input::fromURL(*input.settings, *result.immutableUrl); auto immutableInput = Input::fromURL(*input.settings, *result.immutableUrl);
@ -393,7 +376,8 @@ struct TarballInputScheme : CurlInputScheme
if (result.lastModified && !input.attrs.contains("lastModified")) if (result.lastModified && !input.attrs.contains("lastModified"))
input.attrs.insert_or_assign("lastModified", uint64_t(result.lastModified)); input.attrs.insert_or_assign("lastModified", uint64_t(result.lastModified));
input.attrs.insert_or_assign("narHash", input.attrs.insert_or_assign(
"narHash",
getTarballCache()->treeHashToNarHash(*input.settings, result.treeHash).to_string(HashFormat::SRI, true)); getTarballCache()->treeHashToNarHash(*input.settings, result.treeHash).to_string(HashFormat::SRI, true));
return {result.accessor, input}; return {result.accessor, input};

View file

@ -7,7 +7,8 @@ namespace nix {
/* ----------- tests for flake/flakeref.hh --------------------------------------------------*/ /* ----------- tests for flake/flakeref.hh --------------------------------------------------*/
TEST(parseFlakeRef, path) { TEST(parseFlakeRef, path)
{
experimentalFeatureSettings.experimentalFeatures.get().insert(Xp::Flakes); experimentalFeatureSettings.experimentalFeatures.get().insert(Xp::Flakes);
fetchers::Settings fetchSettings; fetchers::Settings fetchSettings;
@ -26,16 +27,12 @@ namespace nix {
{ {
auto s = "/foo/bar?xyzzy=123"; auto s = "/foo/bar?xyzzy=123";
EXPECT_THROW( EXPECT_THROW(parseFlakeRef(fetchSettings, s), Error);
parseFlakeRef(fetchSettings, s),
Error);
} }
{ {
auto s = "/foo/bar#bla"; auto s = "/foo/bar#bla";
EXPECT_THROW( EXPECT_THROW(parseFlakeRef(fetchSettings, s), Error);
parseFlakeRef(fetchSettings, s),
Error);
} }
{ {
@ -53,7 +50,8 @@ namespace nix {
} }
} }
TEST(to_string, doesntReencodeUrl) { TEST(to_string, doesntReencodeUrl)
{
fetchers::Settings fetchSettings; fetchers::Settings fetchSettings;
auto s = "http://localhost:8181/test/+3d.tar.gz"; auto s = "http://localhost:8181/test/+3d.tar.gz";
auto flakeref = parseFlakeRef(fetchSettings, s); auto flakeref = parseFlakeRef(fetchSettings, s);

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