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

@ -6,7 +6,7 @@
#include <tuple> #include <tuple>
#include <iomanip> #include <iomanip>
#ifdef __APPLE__ #ifdef __APPLE__
#include <sys/time.h> # include <sys/time.h>
#endif #endif
#include "nix/store/machines.hh" #include "nix/store/machines.hh"
@ -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,13 +41,15 @@ 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;
} }
static int main_build_remote(int argc, char * * argv) static int main_build_remote(int argc, char ** argv)
{ {
{ {
logger = makeJSONLogger(getStandardError()); logger = makeJSONLogger(getStandardError());
@ -85,7 +86,7 @@ static int main_build_remote(int argc, char * * argv)
that gets cleared on reboot, but it wouldn't work on macOS. */ that gets cleared on reboot, but it wouldn't work on macOS. */
auto currentLoadName = "/current-load"; auto currentLoadName = "/current-load";
if (auto localStore = store.dynamic_pointer_cast<LocalFSStore>()) if (auto localStore = store.dynamic_pointer_cast<LocalFSStore>())
currentLoad = std::string { localStore->config.stateDir } + currentLoadName; currentLoad = std::string{localStore->config.stateDir} + currentLoadName;
else else
currentLoad = settings.nixStateDir + currentLoadName; currentLoad = settings.nixStateDir + currentLoadName;
@ -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;
} }
@ -253,7 +246,7 @@ static int main_build_remote(int argc, char * * argv)
} }
} }
connected: connected:
close(5); close(5);
assert(sshStore); assert(sshStore);
@ -265,13 +258,14 @@ connected:
AutoCloseFD uploadLock; AutoCloseFD uploadLock;
{ {
auto setUpdateLock = [&](auto && fileName){ auto setUpdateLock = [&](auto && fileName) {
uploadLock = openLockFile(currentLoad + "/" + escapeUri(fileName) + ".upload-lock", true); uploadLock = openLockFile(currentLoad + "/" + escapeUri(fileName) + ".upload-lock", true);
}; };
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,28 +324,26 @@ 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;
if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) { if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
for (auto & outputName : wantedOutputs) { for (auto & outputName : wantedOutputs) {
auto thisOutputHash = outputHashes.at(outputName); auto thisOutputHash = outputHashes.at(outputName);
auto thisOutputId = DrvOutput{ thisOutputHash, outputName }; auto thisOutputId = DrvOutput{thisOutputHash, outputName};
if (!store->queryRealisation(thisOutputId)) { if (!store->queryRealisation(thisOutputId)) {
debug("missing output %s", outputName); debug("missing output %s", outputName);
assert(optResult); assert(optResult);

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,13 +39,13 @@ 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
{ {
return SingleDerivedPath::Built { return SingleDerivedPath::Built{
.drvPath = make_ref<SingleDerivedPath>(drvPath->discardOutputPath()), .drvPath = make_ref<SingleDerivedPath>(drvPath->discardOutputPath()),
.output = output.first, .output = output.first,
}; };
@ -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(Xp::CaDerivations)) {
if (experimentalFeatureSettings.isEnabled(
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,12 +18,11 @@
namespace nix { namespace nix {
fetchers::Settings fetchSettings; fetchers::Settings fetchSettings;
static GlobalConfig::Register rFetchSettings(&fetchSettings); static GlobalConfig::Register rFetchSettings(&fetchSettings);
EvalSettings evalSettings { EvalSettings evalSettings{
settings.readOnlyMode, settings.readOnlyMode,
{ {
{ {
@ -31,7 +30,7 @@ EvalSettings evalSettings {
[](EvalState & state, std::string_view rest) { [](EvalState & state, std::string_view rest) {
experimentalFeatureSettings.require(Xp::Flakes); experimentalFeatureSettings.require(Xp::Flakes);
// FIXME `parseFlakeRef` should take a `std::string_view`. // FIXME `parseFlakeRef` should take a `std::string_view`.
auto flakeRef = parseFlakeRef(fetchSettings, std::string { rest }, {}, true, false); auto flakeRef = parseFlakeRef(fetchSettings, std::string{rest}, {}, true, false);
debug("fetching flake search path element '%s''", rest); debug("fetching flake search path element '%s''", rest);
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(
@ -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;
@ -18,26 +19,25 @@ struct SingleBuiltPathBuilt {
static SingleBuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view); static SingleBuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view);
nlohmann::json toJSON(const StoreDirConfig & store) const; nlohmann::json toJSON(const StoreDirConfig & store) const;
bool operator ==(const SingleBuiltPathBuilt &) const noexcept; bool operator==(const SingleBuiltPathBuilt &) const noexcept;
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;
using Opaque = DerivedPathOpaque; using Opaque = DerivedPathOpaque;
using Built = SingleBuiltPathBuilt; using Built = SingleBuiltPathBuilt;
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);
} }
@ -51,7 +51,7 @@ struct SingleBuiltPath : _SingleBuiltPathRaw {
static inline ref<SingleBuiltPath> staticDrv(StorePath drvPath) static inline ref<SingleBuiltPath> staticDrv(StorePath drvPath)
{ {
return make_ref<SingleBuiltPath>(SingleBuiltPath::Opaque { drvPath }); return make_ref<SingleBuiltPath>(SingleBuiltPath::Opaque{drvPath});
} }
/** /**
@ -59,40 +59,40 @@ 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;
bool operator == (const BuiltPathBuilt &) const noexcept; bool operator==(const BuiltPathBuilt &) const noexcept;
// 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.
//std::strong_ordering operator <=> (const BuiltPathBuilt &) const noexcept; // std::strong_ordering operator <=> (const BuiltPathBuilt &) const noexcept;
std::string to_string(const StoreDirConfig & store) const; std::string to_string(const StoreDirConfig & store) const;
static BuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view); static BuiltPathBuilt parse(const StoreDirConfig & store, std::string_view, std::string_view);
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;
using Opaque = DerivedPathOpaque; using Opaque = DerivedPathOpaque;
using Built = BuiltPathBuilt; using Built = BuiltPathBuilt;
bool operator == (const BuiltPath &) const = default; bool operator==(const BuiltPath &) const = default;
// 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;
@ -79,11 +84,9 @@ struct InstallableFlake : InstallableValue
*/ */
static inline FlakeRef defaultNixpkgsFlakeRef() 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,9 +79,12 @@ 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() {}
virtual std::pair<Value *, PosIdx> toValue(EvalState & state) = 0; virtual std::pair<Value *, PosIdx> toValue(EvalState & state) = 0;
@ -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

@ -112,7 +112,7 @@ typedef std::vector<ref<Installable>> Installables;
*/ */
struct Installable struct Installable
{ {
virtual ~Installable() { } virtual ~Installable() {}
/** /**
* What Installable is this? * What Installable is this?
@ -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

@ -7,13 +7,14 @@
namespace nix { namespace nix {
typedef std::function<void(int, char * *)> MainFunction; typedef std::function<void(int, char **)> MainFunction;
struct RegisterLegacyCommand 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;
}; };
}; };
@ -29,7 +30,7 @@ public:
virtual Guard init(detail::ReplCompleterMixin * repl) = 0; virtual Guard init(detail::ReplCompleterMixin * repl) = 0;
/** Returns a boolean of whether the interacter got EOF */ /** Returns a boolean of whether the interacter got EOF */
virtual bool getLine(std::string & input, ReplPromptType promptType) = 0; virtual bool getLine(std::string & input, ReplPromptType promptType) = 0;
virtual ~ReplInteracter(){}; virtual ~ReplInteracter() {};
}; };
class ReadlineLikeInteracter : public virtual ReplInteracter class ReadlineLikeInteracter : public virtual ReplInteracter

View file

@ -12,12 +12,12 @@ 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;
using RunNix = void(Path program, const Strings & args, const std::optional<std::string> & input); using RunNix = void(Path program, const Strings & args, const std::optional<std::string> & input);
@ -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,12 +49,9 @@ 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, return {*derivedPathWithInfo};
fmt("while evaluating the attribute '%s'", attrPath)))
{
return { *derivedPathWithInfo };
} }
Bindings & autoArgs = *cmd.getAutoArgs(*state); Bindings & autoArgs = *cmd.getAutoArgs(*state);
@ -70,19 +68,19 @@ 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))
outputsToInstall.insert(output.first); outputsToInstall.insert(output.first);
if (outputsToInstall.empty()) if (outputsToInstall.empty())
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,11 +91,12 @@ 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,
}, },
.info = make_ref<ExtraPathInfoValue>(ExtraPathInfoValue::Value { .info = make_ref<ExtraPathInfoValue>(ExtraPathInfoValue::Value{
.extendedOutputsSpec = outputs, .extendedOutputsSpec = outputs,
/* FIXME: reconsider backwards compatibility above /* FIXME: reconsider backwards compatibility above
so we can fill in this info. */ so we can fill in this info. */
@ -115,8 +114,10 @@ InstallableAttrPath InstallableAttrPath::parse(
ExtendedOutputsSpec extendedOutputsSpec) ExtendedOutputsSpec extendedOutputsSpec)
{ {
return { return {
state, cmd, v, state,
prefix == "." ? "" : std::string { prefix }, cmd,
v,
prefix == "." ? "" : std::string{prefix},
std::move(extendedOutputsSpec), std::move(extendedOutputsSpec),
}; };
} }

View file

@ -21,18 +21,17 @@ 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.
[&](const ExtendedOutputsSpec::Default &) -> DerivedPath { [&](const ExtendedOutputsSpec::Default &) -> DerivedPath {
auto storePath = store->followLinksToStorePath(prefix); auto storePath = store->followLinksToStorePath(prefix);
return DerivedPath::Opaque { return DerivedPath::Opaque{
.path = std::move(storePath), .path = std::move(storePath),
}; };
}, },
@ -40,13 +39,14 @@ InstallableDerivedPath InstallableDerivedPath::parse(
[&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath { [&](const ExtendedOutputsSpec::Explicit & outputSpec) -> DerivedPath {
auto drv = make_ref<SingleDerivedPath>(SingleDerivedPath::parse(*store, prefix)); auto drv = make_ref<SingleDerivedPath>(SingleDerivedPath::parse(*store, prefix));
drvRequireExperiment(*drv); drvRequireExperiment(*drv);
return DerivedPath::Built { return DerivedPath::Built{
.drvPath = std::move(drv), .drvPath = std::move(drv),
.outputs = outputSpec, .outputs = outputSpec,
}; };
}, },
}, extendedOutputsSpec.raw); },
return InstallableDerivedPath { extendedOutputsSpec.raw);
return InstallableDerivedPath{
store, store,
std::move(derivedPath), std::move(derivedPath),
}; };

View file

@ -28,8 +28,8 @@ namespace nix {
std::vector<std::string> InstallableFlake::getActualAttrPaths() std::vector<std::string> InstallableFlake::getActualAttrPaths()
{ {
std::vector<std::string> res; std::vector<std::string> res;
if (attrPaths.size() == 1 && attrPaths.front().starts_with(".")){ if (attrPaths.size() == 1 && attrPaths.front().starts_with(".")) {
attrPaths.front().erase(0,1); attrPaths.front().erase(0, 1);
res.push_back(attrPaths.front()); res.push_back(attrPaths.front());
return res; return res;
} }
@ -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, return {*derivedPathWithInfo};
fmt("while evaluating the flake output attribute '%s'", attrPath)))
{
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,15 +112,17 @@ 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)) {
if (aOutputSpecified->getBool()) { if (aOutputSpecified->getBool()) {
if (auto aOutputName = attr->maybeGetAttr("outputName")) if (auto aOutputName = attr->maybeGetAttr("outputName"))
outputsToInstall = { aOutputName->getString() }; outputsToInstall = {aOutputName->getString()};
} }
} else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) { } else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall")) if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
@ -132,20 +133,19 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
if (outputsToInstall.empty()) if (outputsToInstall.empty())
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),
}, },
.info = make_ref<ExtraPathInfoFlake>( .info = make_ref<ExtraPathInfoFlake>(
ExtraPathInfoValue::Value { ExtraPathInfoValue::Value{
.priority = priority, .priority = priority,
.attrPath = attrPath, .attrPath = attrPath,
.extendedOutputsSpec = extendedOutputsSpec, .extendedOutputsSpec = extendedOutputsSpec,
}, },
ExtraPathInfoFlake::Flake { ExtraPathInfoFlake::Flake{
.originalRef = flakeRef, .originalRef = flakeRef,
.lockedRef = getLockedFlake()->flake.lockedRef, .lockedRef = getLockedFlake()->flake.lockedRef,
}), }),
@ -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] */
@ -39,15 +36,17 @@ ref<InstallableValue> InstallableValue::require(ref<Installable> installable)
auto castedInstallable = installable.dynamic_pointer_cast<InstallableValue>(); auto castedInstallable = installable.dynamic_pointer_cast<InstallableValue>();
if (!castedInstallable) if (!castedInstallable)
throw nonValueInstallable(*installable); throw nonValueInstallable(*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,
}); });
@ -175,12 +174,12 @@ MixFlakeOptions::MixFlakeOptions()
flakeSettings, flakeSettings,
*evalState, *evalState,
parseFlakeRef(fetchSettings, flakeRef, absPath(getCommandBaseDir())), parseFlakeRef(fetchSettings, flakeRef, absPath(getCommandBaseDir())),
{ .writeLockFile = false }); {.writeLockFile = false});
for (auto & [inputName, input] : flake.lockFile.root->inputs) { for (auto & [inputName, input] : flake.lockFile.root->inputs) {
auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes
if (auto input3 = std::dynamic_pointer_cast<const flake::LockedNode>(input2)) { if (auto input3 = std::dynamic_pointer_cast<const flake::LockedNode>(input2)) {
overrideRegistry( overrideRegistry(
fetchers::Input::fromAttrs(fetchSettings, {{"type","indirect"}, {"id", inputName}}), fetchers::Input::fromAttrs(fetchSettings, {{"type", "indirect"}, {"id", inputName}}),
input3->lockedRef.input, input3->lockedRef.input,
{}); {});
} }
@ -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);
@ -285,7 +276,7 @@ void SourceExprCommand::completeInstallable(AddCompletions & completions, std::s
} }
auto [v, pos] = findAlongAttrPath(*state, prefix_, *autoArgs, root); auto [v, pos] = findAlongAttrPath(*state, prefix_, *autoArgs, root);
Value &v1(*v); Value & v1(*v);
state->forceValue(v1, pos); state->forceValue(v1, pos);
Value v2; Value v2;
state->autoCallFunction(*autoArgs, v1, v2); state->autoCallFunction(*autoArgs, v1, v2);
@ -310,7 +301,7 @@ void SourceExprCommand::completeInstallable(AddCompletions & completions, std::s
getDefaultFlakeAttrPaths(), getDefaultFlakeAttrPaths(),
prefix); prefix);
} }
} catch (EvalError&) { } catch (EvalError &) {
// Don't want eval errors to mess-up with the completion engine, so let's just swallow them // Don't want eval errors to mess-up with the completion engine, so let's just swallow them
} }
} }
@ -334,22 +325,23 @@ void completeFlakeRefWithFragment(
auto fragment = prefix.substr(hash + 1); auto fragment = prefix.substr(hash + 1);
std::string prefixRoot = ""; std::string prefixRoot = "";
if (fragment.starts_with(".")){ if (fragment.starts_with(".")) {
fragment = fragment.substr(1); fragment = fragment.substr(1);
prefixRoot = "."; prefixRoot = ".";
} }
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();
if (prefixRoot == "."){ if (prefixRoot == ".") {
attrPathPrefixes.clear(); attrPathPrefixes.clear();
} }
/* Complete 'fragment' relative to all the /* Complete 'fragment' relative to all the
@ -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,20 +569,18 @@ 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.
SingleDerivedPath::Built truncatedBfd { SingleDerivedPath::Built truncatedBfd{
.drvPath = makeConstantStorePathRef(drvPath.outPath()), .drvPath = makeConstantStorePathRef(drvPath.outPath()),
.output = bfd.output, .output = bfd.output,
}; };
auto outputPath = resolveDerivedPath(*store, truncatedBfd, &*evalStore); auto outputPath = resolveDerivedPath(*store, truncatedBfd, &*evalStore);
return SingleBuiltPath::Built { return SingleBuiltPath::Built{
.drvPath = make_ref<SingleBuiltPath>(std::move(drvPath)), .drvPath = make_ref<SingleBuiltPath>(std::move(drvPath)),
.output = { bfd.output, outputPath }, .output = {bfd.output, outputPath},
}; };
}, },
}, },
@ -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

@ -18,25 +18,24 @@ static std::string doRenderMarkdownToTerminal(std::string_view markdown)
{ {
int windowWidth = getWindowSize().second; int windowWidth = getWindowSize().second;
#if HAVE_LOWDOWN_1_4 # if HAVE_LOWDOWN_1_4
struct lowdown_opts_term opts_term { struct lowdown_opts_term opts_term{
.cols = (size_t) std::max(windowWidth - 5, 60), .cols = (size_t) std::max(windowWidth - 5, 60),
.hmargin = 0, .hmargin = 0,
.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,
#endif # endif
.maxdepth = 20, .maxdepth = 20,
#if !HAVE_LOWDOWN_1_4 # if !HAVE_LOWDOWN_1_4
.cols = (size_t) std::max(windowWidth - 5, 60), .cols = (size_t) std::max(windowWidth - 5, 60),
.hmargin = 0, .hmargin = 0,
.vmargin = 0, .vmargin = 0,
#endif # endif
.feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES, .feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
.oflags = LOWDOWN_TERM_NOLINK, .oflags = LOWDOWN_TERM_NOLINK,
}; };

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)
{ {
@ -15,26 +14,22 @@ static void hashFormatCompleter(AddCompletions & completions, size_t index, std:
Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf) Args::Flag hashFormatWithDefault(std::string && longName, HashFormat * hf)
{ {
assert(*hf == nix::HashFormat::SRI); assert(*hf == nix::HashFormat::SRI);
return Args::Flag { return Args::Flag{
.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,
}; };
} }
Args::Flag hashFormatOpt(std::string && longName, std::optional<HashFormat> * ohf) Args::Flag hashFormatOpt(std::string && longName, std::optional<HashFormat> * ohf)
{ {
return Args::Flag { return Args::Flag{
.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,
}; };
} }
@ -48,33 +43,30 @@ static void hashAlgoCompleter(AddCompletions & completions, size_t index, std::s
Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha) Args::Flag hashAlgo(std::string && longName, HashAlgorithm * ha)
{ {
return Args::Flag { return Args::Flag{
.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,
}; };
} }
Args::Flag hashAlgoOpt(std::string && longName, std::optional<HashAlgorithm> * oha) Args::Flag hashAlgoOpt(std::string && longName, std::optional<HashAlgorithm> * oha)
{ {
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,
}; };
} }
Args::Flag fileIngestionMethod(FileIngestionMethod * method) Args::Flag fileIngestionMethod(FileIngestionMethod * method)
{ {
return Args::Flag { return Args::Flag{
.longName = "mode", .longName = "mode",
// FIXME indentation carefully made for context, this is messed up. // FIXME indentation carefully made for context, this is messed up.
.description = R"( .description = R"(
@ -92,15 +84,13 @@ 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);
}},
}; };
} }
Args::Flag contentAddressMethod(ContentAddressMethod * method) Args::Flag contentAddressMethod(ContentAddressMethod * method)
{ {
return Args::Flag { return Args::Flag{
.longName = "mode", .longName = "mode",
// FIXME indentation carefully made for context, this is messed up. // FIXME indentation carefully made for context, this is messed up.
.description = R"( .description = R"(
@ -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

@ -3,8 +3,8 @@
#include <cstdio> #include <cstdio>
#if USE_READLINE #if USE_READLINE
#include <readline/history.h> # include <readline/history.h>
#include <readline/readline.h> # include <readline/readline.h>
#else #else
// editline < 1.15.2 don't wrap their API for C++ usage // editline < 1.15.2 don't wrap their API for C++ usage
// (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461). // (added in https://github.com/troglobit/editline/commit/91398ceb3427b730995357e9d120539fb9bb7461).
@ -12,7 +12,7 @@
// For compatibility with these versions, we wrap the API here // For compatibility with these versions, we wrap the API here
// (wrapping multiple times on newer versions is no problem). // (wrapping multiple times on newer versions is no problem).
extern "C" { extern "C" {
#include <editline.h> # include <editline.h>
} }
#endif #endif
@ -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) {
@ -477,7 +487,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
Value v; Value v;
evalString(arg, v); evalString(arg, v);
const auto [path, line] = [&] () -> std::pair<SourcePath, uint32_t> { const auto [path, line] = [&]() -> std::pair<SourcePath, uint32_t> {
if (v.type() == nPath || v.type() == nString) { if (v.type() == nPath || v.type() == nString) {
NixStringContext context; NixStringContext context;
auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit"); auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
@ -501,7 +511,7 @@ ProcessLineResult NixRepl::processLine(std::string line)
// runProgram redirects stdout to a StringSink, // runProgram redirects stdout to a StringSink,
// using runProgram2 to allow editors to display their UI // using runProgram2 to allow editors to display their UI
runProgram2(RunOptions { .program = editor, .lookupPath = true, .args = args , .isInteractive = true }); runProgram2(RunOptions{.program = editor, .lookupPath = true, .args = args, .isInteractive = true});
// Reload right after exiting the editor // Reload right after exiting the editor
state->resetFileCache(); state->resetFileCache();
@ -532,9 +542,9 @@ ProcessLineResult NixRepl::processLine(std::string line)
if (command == ":b" || command == ":bl") { if (command == ":b" || command == ":bl") {
state->store->buildPaths({ state->store->buildPaths({
DerivedPath::Built { DerivedPath::Built{
.drvPath = makeConstantStorePathRef(drvPath), .drvPath = makeConstantStorePathRef(drvPath),
.outputs = OutputsSpec::All { }, .outputs = OutputsSpec::All{},
}, },
}); });
auto drv = state->store->readDerivation(drvPath); auto drv = state->store->readDerivation(drvPath);
@ -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,9 +741,13 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
Value v; Value v;
flake::callFlake(*state, flake::callFlake(
flake::lockFlake(flakeSettings, *state, flakeRef, *state,
flake::LockFlags { flake::lockFlake(
flakeSettings,
*state,
flakeRef,
flake::LockFlags{
.updateLockFile = false, .updateLockFile = false,
.useRegistries = !evalSettings.pureEval, .useRegistries = !evalSettings.pureEval,
.allowUnlocked = !evalSettings.pureEval, .allowUnlocked = !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,35 +846,29 @@ 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;
return values; return values;
}; };
@ -880,8 +878,7 @@ ReplExitStatus AbstractNixRepl::runSimple(
openStore(), openStore(),
evalState, evalState,
getValues, getValues,
/*runNix=*/nullptr /*runNix=*/nullptr);
);
repl->initEnv(); repl->initEnv();

View file

@ -31,13 +31,11 @@
* @param init Function that takes a T* and returns the initializer for T * @param init Function that takes a T* and returns the initializer for T
* @return Pointer to allocated and initialized object * @return Pointer to allocated and initialized object
*/ */
template <typename T, typename F> 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,12 +84,13 @@ 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;
try { try {
state->state.callFunction(fn->value, {(nix::Value * *) args, nargs}, value->value, nix::noPos); state->state.callFunction(fn->value, {(nix::Value **) args, nargs}, value->value, nix::noPos);
state->state.forceValue(value->value, nix::noPos); state->state.forceValue(value->value, nix::noPos);
} }
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
@ -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,11 +191,10 @@ 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);
nix_eval_state_builder_free(builder); nix_eval_state_builder_free(builder);
return state; return state;
} }
@ -274,11 +269,11 @@ nix_err nix_gc_decref(nix_c_context * context, const void *)
void nix_gc_now() {} void nix_gc_now() {}
#endif #endif
nix_err nix_value_incref(nix_c_context * context, nix_value *x) nix_err nix_value_incref(nix_c_context * context, nix_value * x)
{ {
return nix_gc_incref(context, (const void *) x); return nix_gc_incref(context, (const void *) x);
} }
nix_err nix_value_decref(nix_c_context * context, nix_value *x) nix_err nix_value_decref(nix_c_context * context, nix_value * x)
{ {
return nix_gc_decref(context, (const void *) x); return nix_gc_decref(context, (const void *) x);
} }

View file

@ -48,7 +48,7 @@ class NixCExternalValue : public nix::ExternalValueBase
public: public:
NixCExternalValue(NixCExternalValueDesc & desc, void * v) NixCExternalValue(NixCExternalValueDesc & desc, void * v)
: desc(desc) : desc(desc)
, v(v){}; , v(v) {};
void * get_ptr() void * get_ptr()
{ {
return v; return v;
@ -155,11 +155,17 @@ 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));
} }
virtual ~NixCExternalValue() override{}; virtual ~NixCExternalValue() override {};
}; };
ExternalValue * nix_create_external_value(nix_c_context * context, NixCExternalValueDesc * desc, void * v) ExternalValue * nix_create_external_value(nix_c_context * context, NixCExternalValueDesc * desc, void * v)

View file

@ -16,21 +16,24 @@
#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: {
static void SetUpTestSuite() { public:
static void SetUpTestSuite()
{
LibStoreTest::SetUpTestSuite(); LibStoreTest::SetUpTestSuite();
initGC(); initGC();
} }
protected: protected:
LibExprTest() LibExprTest()
: LibStoreTest() : LibStoreTest()
, state({}, store, fetchSettings, evalSettings, nullptr) , state({}, store, fetchSettings, evalSettings, nullptr)
{ {
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);
} }
@ -54,68 +59,80 @@ namespace nix {
fetchers::Settings fetchSettings{}; fetchers::Settings fetchSettings{};
EvalSettings evalSettings{readOnlyMode}; EvalSettings evalSettings{readOnlyMode};
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;
@ -127,21 +144,22 @@ 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;
} else if (arg.listSize() != (size_t)n) { } else if (arg.listSize() != (size_t) n) {
*result_listener << "Expected as list of size " << n << " got " << arg.listSize(); *result_listener << "Expected as list of size " << n << " got " << arg.listSize();
return false; return false;
} }
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;
@ -150,7 +168,6 @@ namespace nix {
return false; return false;
} }
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,36 +8,30 @@
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);
auto d = state.coerceToSingleDerivedPath(noPos, *v, ""); auto d = state.coerceToSingleDerivedPath(noPos, *v, "");
RC_ASSERT(SingleDerivedPath { o } == d); RC_ASSERT(SingleDerivedPath{o} == d);
} }
// TODO use DerivedPath::Built for parameter once it supports a single output // TODO use DerivedPath::Built for parameter once it supports a single output
// 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
@ -49,7 +43,7 @@ RC_GTEST_FIXTURE_PROP(
auto * v = state.allocValue(); auto * v = state.allocValue();
state.mkOutputString(*v, b, std::nullopt, mockXpSettings); state.mkOutputString(*v, b, std::nullopt, mockXpSettings);
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "", mockXpSettings); auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "", mockXpSettings);
RC_ASSERT(SingleDerivedPath { b } == d); RC_ASSERT(SingleDerivedPath{b} == d);
} }
RC_GTEST_FIXTURE_PROP( RC_GTEST_FIXTURE_PROP(
@ -63,7 +57,7 @@ RC_GTEST_FIXTURE_PROP(
auto * v = state.allocValue(); auto * v = state.allocValue();
state.mkOutputString(*v, b, outPath, mockXpSettings); state.mkOutputString(*v, b, outPath, mockXpSettings);
auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "", mockXpSettings); auto [d, _] = state.coerceToSingleDerivedPathUnchecked(noPos, *v, "", mockXpSettings);
RC_ASSERT(SingleDerivedPath { b } == d); RC_ASSERT(SingleDerivedPath{b} == d);
} }
#endif #endif

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,65 +4,75 @@
namespace nix { namespace nix {
// Testing the conversion to JSON // Testing the conversion to JSON
class JSONValueTest : public LibExprTest { class JSONValueTest : public LibExprTest
protected: {
std::string getJSONValue(Value& value) { protected:
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);
return ss.str(); return ss.str();
} }
}; };
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\"");
ASSERT_EQ(getJSONValue(v), "\"test\\\"\""); ASSERT_EQ(getJSONValue(v), "\"test\\\"\"");
} }
// 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\"");
} }
} /* namespace nix */ } /* namespace nix */

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;
@ -14,25 +15,26 @@ int main (int argc, char **argv) {
// Disable build hook. We won't be testing remote builds in these unit tests. If we do, fix the above build hook. // Disable build hook. We won't be testing remote builds in these unit tests. If we do, fix the above build hook.
settings.buildHook = {}; settings.buildHook = {};
#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
#ifdef __APPLE__ #ifdef __APPLE__
// Avoid this error, when already running in a sandbox: // Avoid this error, when already running in a sandbox:
// sandbox-exec: sandbox_apply: Operation not permitted // sandbox-exec: sandbox_apply: Operation not permitted
settings.sandboxMode = smDisabled; settings.sandboxMode = smDisabled;
setEnv("_NIX_TEST_NO_SANDBOX", "1"); setEnv("_NIX_TEST_NO_SANDBOX", "1");
#endif #endif
// For pipe operator tests in trivial.cc // For pipe operator tests in trivial.cc
experimentalFeatureSettings.set("experimental-features", "pipe-operators"); experimentalFeatureSettings.set("experimental-features", "pipe-operators");

File diff suppressed because it is too large Load diff

View file

@ -5,86 +5,98 @@
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{
.prefix = LookupPath::Prefix { .s = "" }, .prefix = LookupPath::Prefix{.s = ""},
.path = LookupPath::Path { .s = "foo" }, .path = LookupPath::Path{.s = "foo"},
})); }));
} }
TEST(LookupPathElem, parse_emptyPrefix) { TEST(LookupPathElem, parse_emptyPrefix)
{
ASSERT_EQ( ASSERT_EQ(
LookupPath::Elem::parse("=foo"), LookupPath::Elem::parse("=foo"),
(LookupPath::Elem { (LookupPath::Elem{
.prefix = LookupPath::Prefix { .s = "" }, .prefix = LookupPath::Prefix{.s = ""},
.path = LookupPath::Path { .s = "foo" }, .path = LookupPath::Path{.s = "foo"},
})); }));
} }
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{
.prefix = LookupPath::Prefix { .s = "foo" }, .prefix = LookupPath::Prefix{.s = "foo"},
.path = LookupPath::Path { .s = "bar" }, .path = LookupPath::Path{.s = "bar"},
})); }));
} }
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{
.prefix = LookupPath::Prefix { .s = "foo" }, .prefix = LookupPath::Prefix{.s = "foo"},
.path = LookupPath::Path { .s = "bar=baz" }, .path = LookupPath::Path{.s = "bar=baz"},
})); }));
} }
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" }; {
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::optional { "" }); LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo"), std::optional{""});
} }
TEST(LookupPathElem, suffixIfPotentialMatch_multiKey) { TEST(LookupPathElem, suffixIfPotentialMatch_multiKey)
LookupPath::Prefix prefix { .s = "foo/bar" }; {
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional { "baz" }); LookupPath::Prefix prefix{.s = "foo/bar"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional{"baz"});
} }
TEST(LookupPathElem, suffixIfPotentialMatch_trailingSlash) { TEST(LookupPathElem, suffixIfPotentialMatch_trailingSlash)
LookupPath::Prefix prefix { .s = "foo" }; {
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/"), std::optional { "" }); LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/"), std::optional{""});
} }
TEST(LookupPathElem, suffixIfPotentialMatch_trailingDoubleSlash) { TEST(LookupPathElem, suffixIfPotentialMatch_trailingDoubleSlash)
LookupPath::Prefix prefix { .s = "foo" }; {
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo//"), std::optional { "/" }); LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo//"), std::optional{"/"});
} }
TEST(LookupPathElem, suffixIfPotentialMatch_trailingPath) { TEST(LookupPathElem, suffixIfPotentialMatch_trailingPath)
LookupPath::Prefix prefix { .s = "foo" }; {
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional { "bar/baz" }); LookupPath::Prefix prefix{.s = "foo"};
ASSERT_EQ(prefix.suffixIfPotentialMatch("foo/bar/baz"), std::optional{"bar/baz"});
} }
} }

View file

@ -1,65 +1,78 @@
#include "nix/expr/tests/libexpr.hh" #include "nix/expr/tests/libexpr.hh"
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"));
@ -69,77 +82,89 @@ namespace nix {
auto b = v.attrs()->find(createSymbol("b")); auto b = v.attrs()->find(createSymbol("b"));
ASSERT_NE(b, nullptr); ASSERT_NE(b, nullptr);
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; }`.
@ -166,16 +191,12 @@ namespace nix {
auto c = a->value->attrs()->find(createSymbol("c")); auto c = a->value->attrs()->find(createSymbol("c"));
ASSERT_NE(c, nullptr); ASSERT_NE(c, nullptr);
ASSERT_THAT(*c->value, IsIntEq(2)); ASSERT_THAT(*c->value, IsIntEq(2));
} }
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,74 +255,88 @@ 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"));
ASSERT_NE(b, nullptr); ASSERT_NE(b, nullptr);
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,46 +10,42 @@ 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);
ASSERT_TRUE(p); ASSERT_TRUE(p);
ASSERT_EQ(p->path, StorePath { opaque }); ASSERT_EQ(p->path, StorePath{opaque});
ASSERT_EQ(elem.to_string(), opaque); ASSERT_EQ(elem.to_string(), opaque);
} }
@ -57,12 +53,13 @@ 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);
ASSERT_TRUE(p); ASSERT_TRUE(p);
ASSERT_EQ(p->drvPath, StorePath { drvDeep.substr(1) }); ASSERT_EQ(p->drvPath, StorePath{drvDeep.substr(1)});
ASSERT_EQ(elem.to_string(), drvDeep); ASSERT_EQ(elem.to_string(), drvDeep);
} }
@ -70,14 +67,17 @@ 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(
.path = StorePath { built.substr(5) }, *p->drvPath,
((SingleDerivedPath) SingleDerivedPath::Opaque{
.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,8 +103,10 @@ 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(
.path = StorePath { built.substr(9) }, *drvPath->drvPath,
((SingleDerivedPath) SingleDerivedPath::Opaque{
.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,14 +106,11 @@ 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);
auto formals = Formals {}; auto formals = Formals{};
ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body); ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
@ -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);
@ -220,10 +213,13 @@ TEST_F(ValuePrintingTests, depthAttrs)
Value vNested; Value vNested;
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(
test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 3 }); vNested,
test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 4 }); "{ 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 = 4});
} }
TEST_F(ValuePrintingTests, depthList) TEST_F(ValuePrintingTests, depthList)
@ -256,11 +252,11 @@ TEST_F(ValuePrintingTests, depthList)
Value vList; Value vList;
vList.mkList(list); vList.mkList(list);
test(vList, "[ 1 2 { ... } ]", PrintOptions { .maxDepth = 1 }); test(vList, "[ 1 2 { ... } ]", PrintOptions{.maxDepth = 1});
test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 2 }); test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions{.maxDepth = 2});
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 3 }); test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions{.maxDepth = 3});
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 4 }); test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions{.maxDepth = 4});
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 5 }); test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions{.maxDepth = 5});
} }
struct StringPrintingTests : LibExprTest struct StringPrintingTests : LibExprTest
@ -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,11 +414,10 @@ 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,26 +441,16 @@ 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,
.derivationPaths = true, .derivationPaths = 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,77 +485,51 @@ 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);
auto formals = Formals {}; auto formals = Formals{};
ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body); ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
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)
@ -696,12 +594,8 @@ TEST_F(ValuePrintingTests, listRepeated)
Value vList; Value vList;
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,14 +451,14 @@ 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
} else } else
return nullptr; return nullptr;
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); // error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
} }
} }
@ -533,7 +466,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name)
if (v.type() != nAttrs) if (v.type() != nAttrs)
return nullptr; return nullptr;
//error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow(); // error<TypeError>("'%s' is not an attribute set", getAttrPathStr()).debugThrow();
auto attr = v.attrs()->get(name); auto attr = v.attrs()->get(name);
@ -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
else return v.type() == nInt || v.type() == nBool || v.type() == nString || return v.type() == nInt || v.type() == nBool || v.type() == nString || v.type() == nFloat;
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,15 +24,18 @@ 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)
Attr() { }; , pos(pos)
auto operator <=> (const Attr & a) const , value(value) {};
Attr() {};
auto operator<=>(const Attr & a) const
{ {
return name <=> a.name; return name <=> a.name;
} }
}; };
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)
{ {
@ -94,7 +94,7 @@ void EvalState::forceValue(Value & v, const PosIdx pos)
Expr * expr = v.payload.thunk.expr; Expr * expr = v.payload.thunk.expr;
try { try {
v.mkBlackhole(); v.mkBlackhole();
//checkInterrupt(); // checkInterrupt();
if (env) [[likely]] if (env) [[likely]]
expr->eval(*this, *env, v); expr->eval(*this, *env, v);
else else
@ -104,50 +104,43 @@ 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)
{ {
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

@ -45,20 +45,24 @@ struct SingleDerivedPath;
enum RepairFlag : bool; enum RepairFlag : bool;
struct MemorySourceAccessor; struct MemorySourceAccessor;
namespace eval_cache { namespace eval_cache {
class EvalCache; class EvalCache;
} }
/** /**
* 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;
} }
}; };
@ -66,7 +70,7 @@ public:
/** /**
* Function that implements a primop. * Function that implements a primop.
*/ */
using PrimOpFun = void(EvalState & state, const PosIdx pos, Value * * args, Value & v); using PrimOpFun = void(EvalState & state, const PosIdx pos, Value ** args, Value & v);
/** /**
* Info about a primitive operation, and its implementation * Info about a primitive operation, and its implementation
@ -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;
@ -161,23 +167,25 @@ struct Env
Value * values[0]; Value * values[0];
}; };
void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env); void printEnvBindings(const EvalState & es, const Expr & expr, const Env & env);
void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, int lvl = 0); void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, int lvl = 0);
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;
@ -309,19 +309,20 @@ public:
/** /**
* Debugger * Debugger
*/ */
ReplExitStatus (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv); ReplExitStatus (*debugRepl)(ref<EvalState> es, const ValMap & extraEnv);
bool debugStop; bool debugStop;
bool inDebugger = false; bool inDebugger = false;
int trylevel; int trylevel;
std::list<DebugTrace> debugTraces; std::list<DebugTrace> debugTraces;
std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs; std::map<const Expr *, const std::shared_ptr<const StaticEnv>> exprEnvs;
const std::shared_ptr<const StaticEnv> getStaticEnv(const Expr & expr) const const std::shared_ptr<const StaticEnv> getStaticEnv(const Expr & expr) const
{ {
auto i = exprEnvs.find(&expr); auto i = exprEnvs.find(&expr);
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
@ -530,7 +545,7 @@ public:
void forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx); void forceAttrs(Value & v, const PosIdx pos, std::string_view errorCtx);
template <typename Callable> template<typename Callable>
inline void forceAttrs(Value & v, Callable getPos, std::string_view errorCtx); inline void forceAttrs(Value & v, Callable getPos, std::string_view errorCtx);
inline void forceList(Value & v, const PosIdx pos, std::string_view errorCtx); inline void forceList(Value & v, const PosIdx pos, std::string_view errorCtx);
@ -539,15 +554,20 @@ 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>
[[gnu::noinline]] [[gnu::noinline]]
void addErrorTrace(Error & e, const Args & ... formatArgs) const; void addErrorTrace(Error & e, const Args &... formatArgs) const;
template<typename... Args> template<typename... Args>
[[gnu::noinline]] [[gnu::noinline]]
void addErrorTrace(Error & e, const PosIdx pos, const Args & ... formatArgs) const; void addErrorTrace(Error & e, const PosIdx pos, const Args &... formatArgs) const;
public: public:
/** /**
@ -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;
@ -768,7 +803,7 @@ public:
/** /**
* Return a boolean `Value *` without allocating. * Return a boolean `Value *` without allocating.
*/ */
Value *getBool(bool b); Value * getBool(bool b);
void mkThunk_(Value & v, Expr * expr); void mkThunk_(Value & v, Expr * expr);
void mkPos(Value & v, PosIdx pos); void mkPos(Value & v, PosIdx pos);
@ -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;
@ -920,15 +952,16 @@ private:
friend struct ExprFloat; friend struct ExprFloat;
friend struct ExprPath; friend struct ExprPath;
friend struct ExprSelect; friend struct ExprSelect;
friend void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v); friend void prim_getAttr(EvalState & state, const PosIdx pos, Value ** args, Value & v);
friend void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v); friend void prim_match(EvalState & state, const PosIdx pos, Value ** args, Value & v);
friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v); friend void prim_split(EvalState & state, const PosIdx pos, Value ** args, Value & v);
friend struct Value; friend struct Value;
friend class ListBuilder; friend class ListBuilder;
}; };
struct DebugTraceStacker { struct DebugTraceStacker
{
DebugTraceStacker(EvalState & evalState, DebugTrace t); DebugTraceStacker(EvalState & evalState, DebugTrace t);
~DebugTraceStacker() ~DebugTraceStacker()
{ {

View file

@ -9,13 +9,13 @@ namespace nix {
/** /**
* A GC compatible vector that may used a reserved portion of `nItems` on the stack instead of allocating on the heap. * A GC compatible vector that may used a reserved portion of `nItems` on the stack instead of allocating on the heap.
*/ */
template <typename T, size_t nItems> template<typename T, size_t nItems>
using SmallVector = boost::container::small_vector<T, nItems, traceable_allocator<T>>; using SmallVector = boost::container::small_vector<T, nItems, traceable_allocator<T>>;
/** /**
* A vector of value pointers. See `SmallVector`. * A vector of value pointers. See `SmallVector`.
*/ */
template <size_t nItems> template<size_t nItems>
using SmallValueVector = SmallVector<Value *, nItems>; using SmallValueVector = SmallVector<Value *, nItems>;
/** /**
@ -23,7 +23,7 @@ using SmallValueVector = SmallVector<Value *, nItems>;
* *
* See also `SmallValueVector`. * See also `SmallValueVector`.
*/ */
template <size_t nItems> template<size_t nItems>
using SmallTemporaryValueVector = SmallVector<Value, nItems>; using SmallTemporaryValueVector = SmallVector<Value, nItems>;
} }

View file

@ -7,7 +7,6 @@
#include <string> #include <string>
#include <map> #include <map>
namespace nix { namespace nix {
/** /**
@ -33,7 +32,7 @@ private:
*/ */
bool failed = false; bool failed = false;
const Bindings * attrs = nullptr, * meta = nullptr; const Bindings *attrs = nullptr, *meta = nullptr;
const Bindings * getMeta(); const Bindings * getMeta();
@ -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,40 +70,45 @@ 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() {};
virtual void show(const SymbolTable & symbols, std::ostream & str) const; virtual void show(const SymbolTable & symbols, std::ostream & str) const;
virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env); virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
virtual void eval(EvalState & state, Env & env, Value & v); virtual void eval(EvalState & state, Env & env, Value & v);
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() {};
virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) { }; virtual void warnIfCursedOr(const SymbolTable & symbols, const PosTable & positions) {};
}; };
#define COMMON_METHODS \ #define COMMON_METHODS \
@ -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;
@ -197,11 +228,24 @@ struct ExprInheritFrom : ExprVar
struct ExprSelect : Expr 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,8 +295,10 @@ 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)
AttrDef() { }; , e(e)
, pos(pos) {};
AttrDef() {};
template<typename T> template<typename T>
const T & chooseByKind(const T & plain, const T & inherited, const T & inheritedFrom) const const T & chooseByKind(const T & plain, const T & inherited, const T & inheritedFrom) const
@ -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;
}; };
@ -286,7 +347,7 @@ struct ExprAttrs : Expr
struct ExprList : Expr struct ExprList : Expr
{ {
std::vector<Expr *> elems; std::vector<Expr *> elems;
ExprList() { }; ExprList() {};
COMMON_METHODS COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env) override; Value * maybeThunk(EvalState & state, Env & env) override;
@ -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,44 +458,69 @@ 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
}; };
struct ExprWith : Expr struct ExprWith : Expr
{ {
PosIdx pos; PosIdx pos;
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
}; };
struct ExprIf : Expr 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
}; };
struct ExprAssert : Expr 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
}; };
@ -423,45 +528,62 @@ struct ExprOpNot : Expr
struct name : Expr \ struct name : 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
{ {
PosIdx pos; PosIdx pos;
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;
@ -284,20 +289,19 @@ inline Expr * ParserState::stripIndentation(const PosIdx pos,
size_t curDropped = 0; size_t curDropped = 0;
size_t n = es.size(); size_t n = es.size();
auto i = es.begin(); auto i = es.begin();
const auto trimExpr = [&] (Expr * e) { const auto trimExpr = [&](Expr * e) {
atStartOfLine = false; atStartOfLine = false;
curDropped = 0; curDropped = 0;
es2->emplace_back(i->first, e); es2->emplace_back(i->first, e);
}; };
const auto trimString = [&] (const StringToken & t) { const auto trimString = [&](const StringToken & t) {
std::string s2; std::string s2;
for (size_t j = 0; j < t.l; ++j) { for (size_t j = 0; j < t.l; ++j) {
if (atStartOfLine) { if (atStartOfLine) {
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;
} }
} }
@ -325,20 +330,20 @@ inline Expr * ParserState::stripIndentation(const PosIdx pos,
} }
}; };
for (; i != es.end(); ++i, --n) { for (; i != es.end(); ++i, --n) {
std::visit(overloaded { trimExpr, trimString }, i->second); std::visit(overloaded{trimExpr, trimString}, i->second);
} }
// If there is nothing at all, return the empty string directly. // If there is nothing at all, return the empty string directly.
// This also ensures that equivalent empty strings result in the same ast, which is helpful when testing formatters. // This also ensures that equivalent empty strings result in the same ast, which is helpful when testing formatters.
if (es2->size() == 0) { if (es2->size() == 0) {
auto *const result = new ExprString(""); auto * const result = new ExprString("");
delete es2; delete es2;
return result; return result;
} }
/* If this is a single string, then don't do a concatenation. */ /* If this is a single string, then don't do a concatenation. */
if (es2->size() == 1 && dynamic_cast<ExprString *>((*es2)[0].second)) { if (es2->size() == 1 && dynamic_cast<ExprString *>((*es2)[0].second)) {
auto *const result = (*es2)[0].second; auto * const result = (*es2)[0].second;
delete es2; delete es2;
return result; return result;
} }

View file

@ -49,12 +49,12 @@ struct RegisterPrimOp
/** /**
* Load a ValueInitializer from a DSO and return whatever it initializes * Load a ValueInitializer from a DSO and return whatever it initializes
*/ */
void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Value & v); void prim_importNative(EvalState & state, const PosIdx pos, Value ** args, Value & v);
/** /**
* Execute a program and parse its output * Execute a program and parse its output
*/ */
void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v); void prim_exec(EvalState & state, const PosIdx pos, Value ** args, Value & v);
void makePositionThunks(EvalState & state, const PosIdx pos, Value & line, Value & column); void makePositionThunks(EvalState & state, const PosIdx pos, Value & line, Value & column);

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

@ -110,7 +110,7 @@ struct PrintOptions
* `PrintOptions` for unknown and therefore potentially large values in error messages, * `PrintOptions` for unknown and therefore potentially large values in error messages,
* to avoid printing "too much" output. * to avoid printing "too much" output.
*/ */
static PrintOptions errorPrintOptions = PrintOptions { static PrintOptions errorPrintOptions = PrintOptions{
.ansiColors = true, .ansiColors = true,
.maxDepth = 10, .maxDepth = 10,
.maxAttrs = 10, .maxAttrs = 10,

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));
} }
@ -60,27 +62,31 @@ bool isReservedKeyword(const std::string_view str);
*/ */
std::ostream & printIdentifier(std::ostream & o, std::string_view s); std::ostream & printIdentifier(std::ostream & o, std::string_view s);
void printValue(EvalState & state, std::ostream & str, Value & v, PrintOptions options = PrintOptions {}); void printValue(EvalState & state, std::ostream & str, Value & v, PrintOptions options = PrintOptions{});
/** /**
* 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;
Value & value; Value & value;
PrintOptions options; PrintOptions options;
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,10 +23,13 @@ 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
{ {
return *s == s2; return *s == s2;
} }
@ -36,12 +39,12 @@ public:
return s->c_str(); return s->c_str();
} }
operator const std::string_view () const operator const std::string_view() const
{ {
return *s; return *s;
} }
friend std::ostream & operator <<(std::ostream & os, const SymbolStr & symbol); friend std::ostream & operator<<(std::ostream & os, const SymbolStr & symbol);
bool empty() const bool empty() 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;
@ -81,15 +68,15 @@ using NixFloat = double;
*/ */
class ExternalValueBase class ExternalValueBase
{ {
friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v); friend std::ostream & operator<<(std::ostream & str, const ExternalValueBase & v);
friend class Printer; friend class Printer;
protected: protected:
/** /**
* Print out the value * Print out the value
*/ */
virtual std::ostream & print(std::ostream & str) const = 0; virtual std::ostream & print(std::ostream & str) const = 0;
public: public:
/** /**
* Return a simple string describing the type * Return a simple string describing the type
*/ */
@ -104,41 +91,44 @@ 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,
* i.e. always false. * i.e. always false.
*/ */
virtual bool operator ==(const ExternalValueBase & b) const noexcept; virtual bool operator==(const ExternalValueBase & b) const noexcept;
/** /**
* 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;
Value * inlineElems[2] = {nullptr, nullptr}; Value * inlineElems[2] = {nullptr, nullptr};
public: public:
Value * * elems; Value ** elems;
ListBuilder(EvalState & state, size_t size); ListBuilder(EvalState & state, size_t size);
// NOTE: Can be noexcept because we are just copying integral values and // NOTE: Can be noexcept because we are just copying integral values and
@ -147,22 +137,28 @@ 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)
{ {
return elems[n]; return elems[n];
} }
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:
@ -172,21 +168,36 @@ private:
public: public:
void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {}); void print(EvalState & state, std::ostream & str, PrintOptions options = PrintOptions{});
// Functions needed to distinguish the type // Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's // These should be removed eventually, by putting the functionality that's
// 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;
@ -312,17 +346,17 @@ public:
inline void mkInt(NixInt n) inline void mkInt(NixInt n)
{ {
finishValue(tInt, { .integer = n }); finishValue(tInt, {.integer = n});
} }
inline void mkBool(bool b) inline void mkBool(bool b)
{ {
finishValue(tBool, { .boolean = b }); finishValue(tBool, {.boolean = b});
} }
inline void mkString(const char * s, const char * * context = 0) inline void mkString(const char * s, const char ** context = 0)
{ {
finishValue(tString, { .string = { .c_str = s, .context = context } }); finishValue(tString, {.string = {.c_str = s, .context = context}});
} }
void mkString(std::string_view s); void mkString(std::string_view s);
@ -341,7 +375,7 @@ public:
inline void mkPath(SourceAccessor * accessor, const char * path) inline void mkPath(SourceAccessor * accessor, const char * path)
{ {
finishValue(tPath, { .path = { .accessor = accessor, .path = path } }); finishValue(tPath, {.path = {.accessor = accessor, .path = path}});
} }
inline void mkNull() inline void mkNull()
@ -351,7 +385,7 @@ public:
inline void mkAttrs(Bindings * a) inline void mkAttrs(Bindings * a)
{ {
finishValue(tAttrs, { .attrs = a }); finishValue(tAttrs, {.attrs = a});
} }
Value & mkAttrs(BindingsBuilder & bindings); Value & mkAttrs(BindingsBuilder & bindings);
@ -359,26 +393,26 @@ public:
void mkList(const ListBuilder & builder) void mkList(const ListBuilder & builder)
{ {
if (builder.size == 1) if (builder.size == 1)
finishValue(tList1, { .smallList = { builder.inlineElems[0] } }); finishValue(tList1, {.smallList = {builder.inlineElems[0]}});
else if (builder.size == 2) else if (builder.size == 2)
finishValue(tList2, { .smallList = { builder.inlineElems[0], builder.inlineElems[1] } }); finishValue(tList2, {.smallList = {builder.inlineElems[0], builder.inlineElems[1]}});
else else
finishValue(tListN, { .bigList = { .size = builder.size, .elems = builder.elems } }); finishValue(tListN, {.bigList = {.size = builder.size, .elems = builder.elems}});
} }
inline void mkThunk(Env * e, Expr * ex) inline void mkThunk(Env * e, Expr * ex)
{ {
finishValue(tThunk, { .thunk = { .env = e, .expr = ex } }); finishValue(tThunk, {.thunk = {.env = e, .expr = ex}});
} }
inline void mkApp(Value * l, Value * r) inline void mkApp(Value * l, Value * r)
{ {
finishValue(tApp, { .app = { .left = l, .right = r } }); finishValue(tApp, {.app = {.left = l, .right = r}});
} }
inline void mkLambda(Env * e, ExprLambda * f) inline void mkLambda(Env * e, ExprLambda * f)
{ {
finishValue(tLambda, { .lambda = { .env = e, .fun = f } }); finishValue(tLambda, {.lambda = {.env = e, .fun = f}});
} }
inline void mkBlackhole(); inline void mkBlackhole();
@ -387,7 +421,7 @@ public:
inline void mkPrimOpApp(Value * l, Value * r) inline void mkPrimOpApp(Value * l, Value * r)
{ {
finishValue(tPrimOpApp, { .primOpApp = { .left = l, .right = r } }); finishValue(tPrimOpApp, {.primOpApp = {.left = l, .right = r}});
} }
/** /**
@ -397,12 +431,12 @@ public:
inline void mkExternal(ExternalValueBase * e) inline void mkExternal(ExternalValueBase * e)
{ {
finishValue(tExternal, { .external = e }); finishValue(tExternal, {.external = e});
} }
inline void mkFloat(NixFloat n) inline void mkFloat(NixFloat n)
{ {
finishValue(tFloat, { .fpoint = n }); finishValue(tFloat, {.fpoint = n});
} }
bool isList() const bool isList() const
@ -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
@ -460,36 +493,47 @@ public:
return payload.string.c_str; return payload.string.c_str;
} }
const char * * context() const const char ** context() const
{ {
return payload.string.context; return payload.string.context;
} }
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
{ {
return internalType == tThunk && payload.thunk.expr == (Expr*) &eBlackHole; return internalType == tThunk && payload.thunk.expr == (Expr *) &eBlackHole;
} }
void Value::mkBlackhole() void Value::mkBlackhole()
@ -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

@ -15,7 +15,7 @@ public:
std::string_view raw; std::string_view raw;
template<typename... Args> template<typename... Args>
BadNixStringContextElem(std::string_view raw_, const Args & ... args) BadNixStringContextElem(std::string_view raw_, const Args &... args)
: Error("") : Error("")
{ {
raw = raw_; raw = raw_;
@ -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
{ {
@ -130,7 +147,7 @@ public:
} }
#if NLOHMANN_JSON_VERSION_MAJOR >= 3 && NLOHMANN_JSON_VERSION_MINOR >= 8 #if NLOHMANN_JSON_VERSION_MAJOR >= 3 && NLOHMANN_JSON_VERSION_MINOR >= 8
bool binary(binary_t&) override bool binary(binary_t &) override
{ {
// This function ought to be unreachable // This function ought to be unreachable
assert(false); assert(false);
@ -146,27 +163,30 @@ public:
bool key(string_t & name) override bool key(string_t & name) override
{ {
dynamic_cast<JSONObjectState*>(rs.get())->key(name, state); dynamic_cast<JSONObjectState *>(rs.get())->key(name, state);
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

@ -17,7 +17,7 @@ ExprBlackHole eBlackHole;
// FIXME: remove, because *symbols* are abstract and do not have a single // FIXME: remove, because *symbols* are abstract and do not have a single
// textual representation; see printIdentifier() // textual representation; see printIdentifier()
std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol) std::ostream & operator<<(std::ostream & str, const SymbolStr & symbol)
{ {
std::string_view s = symbol; std::string_view s = symbol;
return printIdentifier(str, s); return printIdentifier(str, s);
@ -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;
@ -392,7 +405,7 @@ void ExprAttrs::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));
if (recursive) { if (recursive) {
auto newEnv = [&] () -> std::shared_ptr<const StaticEnv> { auto newEnv = [&]() -> std::shared_ptr<const StaticEnv> {
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs.size()); auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs.size());
Displacement displ = 0; Displacement displ = 0;
@ -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);
@ -473,7 +485,7 @@ void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> &
void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
auto newEnv = [&] () -> std::shared_ptr<const StaticEnv> { auto newEnv = [&]() -> std::shared_ptr<const StaticEnv> {
auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs->attrs.size()); auto newEnv = std::make_shared<StaticEnv>(nullptr, env, attrs->attrs.size());
Displacement displ = 0; Displacement displ = 0;
@ -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) {
@ -606,11 +612,12 @@ void ExprLambda::setDocComment(DocComment docComment) {
size_t SymbolTable::totalSize() const size_t SymbolTable::totalSize() const
{ {
size_t n = 0; size_t n = 0;
dump([&] (const std::string & s) { n += s.size(); }); dump([&](const std::string & s) { n += s.size(); });
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

@ -5,10 +5,11 @@
namespace nix { 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,16 +22,15 @@ 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;
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext"); state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
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.
@ -182,9 +177,10 @@ static RegisterPrimOp primop_addDrvOutputDependencies({
Note that for a given path any combination of the above attributes Note that for a given path any combination of the above attributes
may be present. may be present.
*/ */
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,19 +247,18 @@ 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.
See the commentary above getContext for details of the See the commentary above getContext for details of the
context representation. context representation.
*/ */
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,16 +301,19 @@ 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,24 +69,25 @@ 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});
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,21 +96,22 @@ 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});
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);
@ -112,7 +119,7 @@ static void runFetchClosureWithInputAddressedPath(EvalState & state, const PosId
typedef std::optional<StorePath> StorePathOrGap; typedef std::optional<StorePath> StorePathOrGap;
static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure"); state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.fetchClosure");
@ -136,67 +143,58 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
state.forceValue(*attr.value, attr.pos); state.forceValue(*attr.value, attr.pos);
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

@ -8,7 +8,7 @@
namespace nix { namespace nix {
static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
std::string url; std::string url;
std::optional<Hash> rev; std::optional<Hash> rev;
@ -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, fetchers::Input input{state.fetchSettings};
Value & v,
const FetchTreeParams & params = FetchTreeParams{}
) {
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());
@ -211,9 +225,9 @@ static void fetchTree(
emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false); emitTreeAttrs(state, storePath, input2, v, params.emptyRevFallback, false);
} }
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_fetchTree(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
fetchTree(state, pos, args, v, { }); fetchTree(state, pos, args, v, {});
} }
static RegisterPrimOp primop_fetchTree({ static RegisterPrimOp primop_fetchTree({
@ -446,7 +460,7 @@ static RegisterPrimOp primop_fetchTree({
.experimentalFeature = Xp::FetchTree, .experimentalFeature = Xp::FetchTree,
}); });
void prim_fetchFinalTree(EvalState & state, const PosIdx pos, Value * * args, Value & v) void prim_fetchFinalTree(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
fetchTree(state, pos, args, v, {.isFinal = true}); fetchTree(state, pos, args, v, {.isFinal = true});
} }
@ -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,27 +525,41 @@ 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) {
auto expectedPath = state.store->makeFixedOutputPath( auto expectedPath = state.store->makeFixedOutputPath(
name, name,
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();
} }
} }
@ -562,7 +595,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
state.allowAndSetStorePathString(storePath, v); state.allowAndSetStorePathString(storePath, v);
} }
static void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_fetchurl(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
fetch(state, pos, args, v, "fetchurl", false, ""); fetch(state, pos, args, v, "fetchurl", false, "");
} }
@ -588,7 +621,7 @@ static RegisterPrimOp primop_fetchurl({
.fun = prim_fetchurl, .fun = prim_fetchurl,
}); });
static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
fetch(state, pos, args, v, "fetchTarball", true, "source"); fetch(state, pos, args, v, "fetchTarball", true, "source");
} }
@ -638,14 +671,10 @@ static RegisterPrimOp primop_fetchTarball({
.fun = prim_fetchTarball, .fun = prim_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

@ -7,7 +7,7 @@
namespace nix { namespace nix {
static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val) static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Value & val)
{ {
auto toml = state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.fromTOML"); auto toml = state.forceStringNoCtx(*args[0], pos, "while evaluating the argument passed to builtins.fromTOML");
@ -16,57 +16,57 @@ 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);
for(auto & elem : table) { for (auto & elem : table) {
forceNoNullByte(elem.first); forceNoNullByte(elem.first);
visit(attrs.alloc(elem.first), elem.second); visit(attrs.alloc(elem.first), elem.second);
} }
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
@ -134,7 +134,7 @@ printAttributeName(std::ostream & str, std::string_view name) {
return str; return str;
} }
bool isImportantAttrName(const std::string& attrName) bool isImportantAttrName(const std::string & attrName)
{ {
return attrName == "type" || attrName == "_type"; return attrName == "type" || attrName == "_type";
} }
@ -144,12 +144,11 @@ typedef std::pair<std::string, Value *> AttrPair;
struct ImportantFirstAttrNameCmp struct ImportantFirstAttrNameCmp
{ {
bool operator()(const AttrPair& lhs, const AttrPair& rhs) const bool operator()(const AttrPair & lhs, const AttrPair & rhs) const
{ {
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 =
.s = std::string { rawElem.substr(pos + 1) }, Path{
.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;
@ -16,16 +14,16 @@ NixStringContextElem NixStringContextElem::parse(
// Case on whether there is a '!' // Case on whether there is a '!'
size_t index = s.find("!"); size_t index = s.find("!");
if (index == std::string_view::npos) { if (index == std::string_view::npos) {
return SingleDerivedPath::Opaque { return SingleDerivedPath::Opaque{
.path = StorePath { s }, .path = StorePath{s},
}; };
} else { } else {
std::string output { s.substr(0, index) }; std::string output{s.substr(0, index)};
// Advance string to parse after the '!' // Advance string to parse after the '!'
s = s.substr(index + 1); s = s.substr(index + 1);
auto drv = make_ref<SingleDerivedPath>(parseRest()); auto drv = make_ref<SingleDerivedPath>(parseRest());
drvRequireExperiment(*drv, xpSettings); drvRequireExperiment(*drv, xpSettings);
return SingleDerivedPath::Built { return SingleDerivedPath::Built{
.drvPath = std::move(drv), .drvPath = std::move(drv),
.output = std::move(output), .output = std::move(output),
}; };
@ -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,28 +41,23 @@ 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{
.drvPath = StorePath { s.substr(1) }, .drvPath = StorePath{s.substr(1)},
}; };
} }
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,39 +13,41 @@ 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_); \
ASSERT_EQ(got, expected); \ ASSERT_EQ(got, expected); \
}); \ }); \
} \ } \
\ \
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"}))
TEST_JSON(PublicKeyTest, defaultType, fetchers::PublicKey { .key = "ABCDE" }) 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_);
ASSERT_EQ(got, expected); ASSERT_EQ(got, expected);
}); });

View file

@ -15,7 +15,7 @@ Attrs jsonToAttrs(const nlohmann::json & json)
else if (i.value().is_string()) else if (i.value().is_string())
attrs.emplace(i.key(), i.value().get<std::string>()); attrs.emplace(i.key(), i.value().get<std::string>());
else if (i.value().is_boolean()) else if (i.value().is_boolean())
attrs.emplace(i.key(), Explicit<bool> { i.value().get<bool>() }); attrs.emplace(i.key(), Explicit<bool>{i.value().get<bool>()});
else else
throw Error("unsupported input attribute type in lock file"); throw Error("unsupported input attribute type in lock 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());
@ -100,17 +91,13 @@ struct CacheImpl : Cache
debug("using cache entry '%s:%s' -> '%s'", key.first, keyJSON, valueJSON); debug("using cache entry '%s:%s' -> '%s'", key.first, keyJSON, valueJSON);
return Result { return Result{
.expired = settings.tarballTtl.get() == 0 || timestamp + settings.tarballTtl < time(0), .expired = settings.tarballTtl.get() == 0 || timestamp + settings.tarballTtl < time(0),
.value = jsonToAttrs(nlohmann::json::parse(valueJSON)), .value = jsonToAttrs(nlohmann::json::parse(valueJSON)),
}; };
} }
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);
@ -86,7 +82,7 @@ Input Input::fromAttrs(const Settings & settings, Attrs && attrs)
// but not all of them. Doing this is to support those other // but not all of them. Doing this is to support those other
// operations which are supposed to be robust on // operations which are supposed to be robust on
// unknown/uninterpretable inputs. // unknown/uninterpretable inputs.
Input input { settings }; Input input{settings};
input.attrs = attrs; input.attrs = attrs;
fixupInput(input); fixupInput(input);
return input; return input;
@ -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);
@ -173,18 +173,20 @@ Attrs Input::toAttrs() const
return attrs; return attrs;
} }
bool Input::operator ==(const Input & other) const noexcept bool Input::operator==(const Input & other) const noexcept
{ {
return attrs == other.attrs; return attrs == other.attrs;
} }
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 = {},
@ -417,7 +426,7 @@ std::optional<Hash> Input::getRev() const
if (auto s = maybeGetStrAttr(attrs, "rev")) { if (auto s = maybeGetStrAttr(attrs, "rev")) {
try { try {
hash = Hash::parseAnyPrefixed(*s); hash = Hash::parseAnyPrefixed(*s);
} catch (BadHash &e) { } catch (BadHash & e) {
// Default to sha1 for backwards compatibility with existing // Default to sha1 for backwards compatibility with existing
// usages (e.g. `builtins.fetchTree` calls or flake inputs). // usages (e.g. `builtins.fetchTree` calls or flake inputs).
hash = Hash::parseAny(*s, HashAlgorithm::SHA1); hash = Hash::parseAny(*s, HashAlgorithm::SHA1);
@ -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);
} }
@ -482,7 +485,7 @@ std::optional<ExperimentalFeature> InputScheme::experimentalFeature() const
return {}; return {};
} }
std::string publicKeys_to_string(const std::vector<PublicKey>& publicKeys) std::string publicKeys_to_string(const std::vector<PublicKey> & publicKeys)
{ {
return ((nlohmann::json) publicKeys).dump(); return ((nlohmann::json) publicKeys).dump();
} }
@ -497,7 +500,7 @@ using namespace nix;
fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json & json) fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json & json)
{ {
fetchers::PublicKey res = { }; fetchers::PublicKey res = {};
if (auto type = optionalValueAt(json, "type")) if (auto type = optionalValueAt(json, "type"))
res.type = getString(*type); res.type = getString(*type);

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,23 +37,24 @@
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
{ {
return * (size_t *) oid.id; return *(size_t *) oid.id;
} }
}; };
} }
std::ostream & operator << (std::ostream & str, const git_oid & oid) std::ostream & operator<<(std::ostream & str, const git_oid & oid)
{ {
str << git_oid_tostr_s(&oid); str << git_oid_tostr_s(&oid);
return str; return str;
} }
bool operator == (const git_oid & oid1, const git_oid & oid2) bool operator==(const git_oid & oid1, const git_oid & oid2)
{ {
return git_oid_equal(&oid1, &oid2); return git_oid_equal(&oid1, &oid2);
} }
@ -81,9 +82,9 @@ typedef std::unique_ptr<git_indexer, Deleter<git_indexer_free>> Indexer;
Hash toHash(const git_oid & oid) Hash toHash(const git_oid & oid)
{ {
#ifdef GIT_EXPERIMENTAL_SHA256 #ifdef GIT_EXPERIMENTAL_SHA256
assert(oid.type == GIT_OID_SHA1); assert(oid.type == GIT_OID_SHA1);
#endif #endif
Hash hash(HashAlgorithm::SHA1); Hash hash(HashAlgorithm::SHA1);
memcpy(hash.hash, oid.id, hash.hashSize); memcpy(hash.hash, oid.id, hash.hashSize);
return hash; return hash;
@ -117,7 +118,7 @@ template<typename T>
T peelObject(git_object * obj, git_object_t type) T peelObject(git_object * obj, git_object_t type)
{ {
T obj2; T obj2;
if (git_object_peel((git_object * *) (typename T::pointer *) Setter(obj2), obj, type)) { if (git_object_peel((git_object **) (typename T::pointer *) Setter(obj2), obj, type)) {
auto err = git_error_last(); auto err = git_error_last();
throw Error("peeling Git object '%s': %s", *git_object_id(obj), err->message); throw Error("peeling Git object '%s': %s", *git_object_id(obj), err->message);
} }
@ -128,7 +129,7 @@ template<typename T>
T dupObject(typename T::pointer obj) T dupObject(typename T::pointer obj)
{ {
T obj2; T obj2;
if (git_object_dup((git_object * *) (typename T::pointer *) Setter(obj2), (git_object *) obj)) if (git_object_dup((git_object **) (typename T::pointer *) Setter(obj2), (git_object *) obj))
throw Error("duplicating object '%s': %s", *git_object_id((git_object *) obj), git_error_last()->message); throw Error("duplicating object '%s': %s", *git_object_id((git_object *) obj), git_error_last()->message);
return obj2; return obj2;
} }
@ -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)
@ -171,9 +173,9 @@ extern "C" {
/** /**
* A `git_packbuilder_progress` implementation that aborts the pack building if needed. * A `git_packbuilder_progress` implementation that aborts the pack building if needed.
*/ */
static int packBuilderProgressCheckInterrupt(int stage, uint32_t current, uint32_t total, void *payload) static int packBuilderProgressCheckInterrupt(int stage, uint32_t current, uint32_t total, void * payload)
{ {
PackBuilderContext & args = * (PackBuilderContext *) payload; PackBuilderContext & args = *(PackBuilderContext *) payload;
try { try {
checkInterrupt(); checkInterrupt();
return GIT_OK; return GIT_OK;
@ -186,11 +188,12 @@ static git_packbuilder_progress PACKBUILDER_PROGRESS_CHECK_INTERRUPT = &packBuil
} // extern "C" } // extern "C"
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);
Repository tmpRepo; Repository tmpRepo;
@ -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.
@ -249,16 +251,17 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
throw Error("adding mempack backend to Git object database: %s", git_error_last()->message); throw Error("adding mempack backend to Git object database: %s", git_error_last()->message);
} }
operator git_repository * () operator git_repository *()
{ {
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;
Finally _disposeBuf { [&] { git_buf_dispose(&buf); } }; Finally _disposeBuf{[&] { git_buf_dispose(&buf); }};
PackBuilder packBuilder; PackBuilder packBuilder;
PackBuilderContext packBuilderContext; PackBuilderContext packBuilderContext;
git_packbuilder_new(Setter(packBuilder), *this); git_packbuilder_new(Setter(packBuilder), *this);
@ -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,
}); });
@ -592,7 +587,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
commit being signed by gpg keys that are present in the commit being signed by gpg keys that are present in the
users key agent. */ users key agent. */
std::string re = R"(Good "git" signature for \* with .* key SHA256:[)"; std::string re = R"(Good "git" signature for \* with .* key SHA256:[)";
for (const fetchers::PublicKey & k : publicKeys){ for (const fetchers::PublicKey & k : publicKeys) {
// Calculate sha256 fingerprint from public key and escape the regex symbol '+' to match the key literally // Calculate sha256 fingerprint from public key and escape the regex symbol '+' to match the key literally
std::string keyDecoded; std::string keyDecoded;
try { try {
@ -600,8 +595,9 @@ 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 =
auto escaped_fingerprint = std::regex_replace(fingerprint, std::regex("\\+"), "\\+" ); trim(hashString(HashAlgorithm::SHA256, keyDecoded).to_string(nix::HashFormat::Base64, false), "=");
auto escaped_fingerprint = std::regex_replace(fingerprint, std::regex("\\+"), "\\+");
re += "(" + escaped_fingerprint + ")"; re += "(" + escaped_fingerprint + ")";
} }
re += "]"; re += "]";
@ -676,8 +672,9 @@ 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 =
lfsFetch->fetch(contents, path, s, [&s](uint64_t size){ s.s.reserve(size); }); 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); });
} catch (Error & e) { } catch (Error & e) {
e.addTrace({}, "while smudging git-lfs file '%s'", path); e.addTrace({}, "while smudging git-lfs file '%s'", path);
throw; throw;
@ -702,7 +699,7 @@ struct GitSourceAccessor : SourceAccessor
std::optional<Stat> maybeLstat(const CanonPath & path) override std::optional<Stat> maybeLstat(const CanonPath & path) override
{ {
if (path.isRoot()) if (path.isRoot())
return Stat { .type = git_object_type(root.get()) == GIT_OBJECT_TREE ? tDirectory : tRegular }; return Stat{.type = git_object_type(root.get()) == GIT_OBJECT_TREE ? tDirectory : tRegular};
auto entry = lookup(path); auto entry = lookup(path);
if (!entry) if (!entry)
@ -711,20 +708,20 @@ struct GitSourceAccessor : SourceAccessor
auto mode = git_tree_entry_filemode(entry); auto mode = git_tree_entry_filemode(entry);
if (mode == GIT_FILEMODE_TREE) if (mode == GIT_FILEMODE_TREE)
return Stat { .type = tDirectory }; return Stat{.type = tDirectory};
else if (mode == GIT_FILEMODE_BLOB) else if (mode == GIT_FILEMODE_BLOB)
return Stat { .type = tRegular }; return Stat{.type = tRegular};
else if (mode == GIT_FILEMODE_BLOB_EXECUTABLE) else if (mode == GIT_FILEMODE_BLOB_EXECUTABLE)
return Stat { .type = tRegular, .isExecutable = true }; return Stat{.type = tRegular, .isExecutable = true};
else if (mode == GIT_FILEMODE_LINK) else if (mode == GIT_FILEMODE_LINK)
return Stat { .type = tSymlink }; return Stat{.type = tSymlink};
else if (mode == GIT_FILEMODE_COMMIT) else if (mode == GIT_FILEMODE_COMMIT)
// Treat submodules as an empty directory. // Treat submodules as an empty directory.
return Stat { .type = tDirectory }; return Stat{.type = tDirectory};
else else
throw Error("file '%s' has an unsupported Git file type"); throw Error("file '%s' has an unsupported Git file type");
@ -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());
@ -827,7 +826,7 @@ struct GitSourceAccessor : SourceAccessor
return std::nullopt; return std::nullopt;
Tree tree; Tree tree;
if (git_tree_entry_to_object((git_object * *) (git_tree * *) Setter(tree), *repo, entry)) if (git_tree_entry_to_object((git_object **) (git_tree **) Setter(tree), *repo, entry))
throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message); throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message);
return tree; return tree;
@ -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)
{ {
@ -861,7 +861,7 @@ struct GitSourceAccessor : SourceAccessor
throw Error("'%s' is not a directory", showPath(path)); throw Error("'%s' is not a directory", showPath(path));
Tree tree; Tree tree;
if (git_tree_entry_to_object((git_object * *) (git_tree * *) Setter(tree), *repo, entry)) if (git_tree_entry_to_object((git_object **) (git_tree **) Setter(tree), *repo, entry))
throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message); throw Error("looking up directory '%s': %s", showPath(path), git_error_last()->message);
return tree; return tree;
@ -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);
@ -898,26 +894,31 @@ struct GitSourceAccessor : SourceAccessor
} }
Blob blob; Blob blob;
if (git_tree_entry_to_object((git_object * *) (git_blob * *) Setter(blob), *repo, entry)) if (git_tree_entry_to_object((git_object **) (git_blob **) Setter(blob), *repo, entry))
throw Error("looking up file '%s': %s", showPath(path), git_error_last()->message); throw Error("looking up file '%s': %s", showPath(path), git_error_last()->message);
return blob; return blob;
} }
}; };
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)
{ {
const char * pathCStr = path.rel_c_str(); const char * pathCStr = path.rel_c_str();
@ -927,27 +928,16 @@ 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);
} }
} }
bool isExportIgnored(const CanonPath & path) bool isExportIgnored(const CanonPath & path)
{ {
const char *exportIgnoreEntry = nullptr; const char * exportIgnoreEntry = nullptr;
// GIT_ATTR_CHECK_INDEX_ONLY: // GIT_ATTR_CHECK_INDEX_ONLY:
// > It will use index only for creating archives or for a bare repo // > It will use index only for creating archives or for a bare repo
@ -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,26 +978,25 @@ 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. */
if (git_tree_entry_type(entry) != GIT_OBJECT_TREE) if (git_tree_entry_type(entry) != GIT_OBJECT_TREE)
throw Error("parent of '%s' is not a directory", name); throw Error("parent of '%s' is not a directory", name);
if (git_tree_entry_to_object((git_object * *) (git_tree * *) Setter(prevTree), *repo, entry)) if (git_tree_entry_to_object((git_object **) (git_tree **) Setter(prevTree), *repo, entry))
throw Error("looking up parent of '%s': %s", name, git_error_last()->message); throw Error("looking up parent of '%s': %s", name, git_error_last()->message);
} }
git_treebuilder * b; git_treebuilder * b;
if (git_treebuilder_new(&b, *repo, prevTree.get())) if (git_treebuilder_new(&b, *repo, prevTree.get()))
throw Error("creating a tree builder: %s", git_error_last()->message); throw Error("creating a tree builder: %s", git_error_last()->message);
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,34 +1043,34 @@ 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)
void operator () (std::string_view data) override , stream(stream)
{
}
void operator()(std::string_view data) override
{ {
if (stream->write(stream, data.data(), data.size())) if (stream->write(stream, data.data(), data.size()))
throw Error("writing a blob for tarball member '%s': %s", path, git_error_last()->message); throw Error("writing a blob for tarball member '%s': %s", path, git_error_last()->message);
@ -1092,17 +1079,14 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
{ {
executable = true; executable = true;
} }
} crf { path, *this, stream }; } crf{path, *this, stream};
func(crf); func(crf);
git_oid oid; git_oid oid;
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"));
@ -87,8 +87,8 @@ bool storeCachedHead(const std::string & actualUrl, bool shallow, const std::str
{ {
Path cacheDir = getCachePath(actualUrl, shallow); Path cacheDir = getCachePath(actualUrl, shallow);
try { try {
runProgram("git", true, { "-C", cacheDir, "--git-dir", ".", "symbolic-ref", "--", "HEAD", headRef }); runProgram("git", true, {"-C", cacheDir, "--git-dir", ".", "symbolic-ref", "--", "HEAD", headRef});
} catch (ExecError &e) { } catch (ExecError & e) {
if ( if (
#ifndef WIN32 // TODO abstract over exit status handling on Windows #ifndef WIN32 // TODO abstract over exit status handling on Windows
!WIFEXITED(e.status) !WIFEXITED(e.status)
@ -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,8 +178,10 @@ 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 (
attrs.emplace(name, Explicit<bool> { value == "1" }); name == "shallow" || name == "submodules" || name == "lfs" || name == "exportIgnore"
|| name == "allRefs" || name == "verifyCommit")
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);
} }
} }
@ -370,12 +390,10 @@ struct GitInputScheme : InputScheme
std::string locationToArg() const std::string locationToArg() const
{ {
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);
@ -517,12 +545,10 @@ struct GitInputScheme : InputScheme
std::string getDefaultRef(const RepoInfo & repoInfo, bool shallow) const std::string getDefaultRef(const RepoInfo & repoInfo, bool shallow) const
{ {
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,21 +722,25 @@ 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);
if (submodule.branch != "") if (submodule.branch != "")
attrs.insert_or_assign("ref", submodule.branch); attrs.insert_or_assign("ref", submodule.branch);
attrs.insert_or_assign("rev", submoduleRev.gitRev()); attrs.insert_or_assign("rev", submoduleRev.gitRev());
attrs.insert_or_assign("exportIgnore", Explicit<bool>{ exportIgnore }); attrs.insert_or_assign("exportIgnore", Explicit<bool>{exportIgnore});
attrs.insert_or_assign("submodules", Explicit<bool>{ true }); attrs.insert_or_assign("submodules", Explicit<bool>{true});
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
@ -766,14 +787,13 @@ struct GitInputScheme : InputScheme
fetchers::Attrs attrs; fetchers::Attrs attrs;
attrs.insert_or_assign("type", "git"); attrs.insert_or_assign("type", "git");
attrs.insert_or_assign("url", submodulePath.string()); attrs.insert_or_assign("url", submodulePath.string());
attrs.insert_or_assign("exportIgnore", Explicit<bool>{ exportIgnore }); attrs.insert_or_assign("exportIgnore", Explicit<bool>{exportIgnore});
attrs.insert_or_assign("submodules", Explicit<bool>{ true }); attrs.insert_or_assign("submodules", Explicit<bool>{true});
// TODO: fall back to getAccessorFromCommit-like fetch when submodules aren't checked out // TODO: fall back to getAccessorFromCommit-like fetch when submodules aren't checked out
// 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, "/");
@ -68,20 +69,18 @@ struct GitArchiveInputScheme : InputScheme
} else if (size < 2) } else if (size < 2)
throw BadURL("URL '%s' is invalid", url); throw BadURL("URL '%s' is invalid", url);
for (auto &[name, value] : url.query) { for (auto & [name, value] : url.query) {
if (name == "rev") { if (name == "rev") {
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;
@ -93,12 +92,15 @@ struct GitArchiveInputScheme : InputScheme
throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url, *ref, rev->gitRev()); throw BadURL("URL '%s' contains both a commit hash and a branch/tag name %s %s", url, *ref, rev->gitRev());
Input input{settings}; Input input{settings};
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,10 +141,12 @@ 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;
auto url = ParsedURL { if (rev)
.scheme = std::string { schemeName() }, path += "/" + rev->to_string(HashFormat::Base16, false);
auto url = ParsedURL{
.scheme = std::string{schemeName()},
.path = path, .path = path,
}; };
if (auto narHash = input.getNarHash()) if (auto narHash = input.getNarHash())
@ -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,22 +178,18 @@ 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;
size_t answer_match_len = 0; size_t answer_match_len = 0;
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 && url.substr(0, token.first.length()) == token.first
&& token.first.length() > answer_match_len && (url.length() == token.first.length() || url[token.first.length()] == '/')) {
&& first == 0
&& url.substr(0,token.first.length()) == token.first
&& (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,21 +202,17 @@ 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");
auto hostAndPath = fmt( "%s/%s/%s", host, owner, repo); auto hostAndPath = fmt("%s/%s/%s", host, owner, repo);
return makeHeadersWithAuthTokens(settings, host, hostAndPath); return makeHeadersWithAuthTokens(settings, host, hostAndPath);
} }
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;
@ -275,7 +270,7 @@ struct GitArchiveInputScheme : InputScheme
auto treeHash = getRevAttr(*treeHashAttrs, "treeHash"); auto treeHash = getRevAttr(*treeHashAttrs, "treeHash");
auto lastModified = getIntAttr(*lastModifiedAttrs, "lastModified"); auto lastModified = getIntAttr(*lastModifiedAttrs, "lastModified");
if (getTarballCache()->hasObject(treeHash)) if (getTarballCache()->hasObject(treeHash))
return {std::move(input), TarballInfo { .treeHash = treeHash, .lastModified = (time_t) lastModified }}; return {std::move(input), TarballInfo{.treeHash = treeHash, .lastModified = (time_t) lastModified}};
else else
debug("Git tree with hash '%s' has disappeared from the cache, refetching...", treeHash.gitRev()); debug("Git tree with hash '%s' has disappeared from the cache, refetching...", treeHash.gitRev());
} }
@ -290,10 +285,10 @@ 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();
auto parseSink = tarballCache->getFileSystemObjectSink(); auto parseSink = tarballCache->getFileSystemObjectSink();
auto lastModified = unpackTarfileToSink(archive, *parseSink); auto lastModified = unpackTarfileToSink(archive, *parseSink);
@ -301,22 +296,20 @@ 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}});
#if 0 #if 0
if (upstreamTreeHash != tarballInfo.treeHash) if (upstreamTreeHash != tarballInfo.treeHash)
warn( warn(
"Git tree hash mismatch for revision '%s' of '%s': " "Git tree hash mismatch for revision '%s' of '%s': "
"expected '%s', got '%s'. " "expected '%s', got '%s'. "
"This can happen if the Git repository uses submodules.", "This can happen if the Git repository uses submodules.",
rev->gitRev(), input.to_string(), upstreamTreeHash->gitRev(), tarballInfo.treeHash.gitRev()); rev->gitRev(), input.to_string(), upstreamTreeHash->gitRev(), tarballInfo.treeHash.gitRev());
#endif #endif
return {std::move(input), tarballInfo}; return {std::move(input), tarballInfo};
} }
@ -325,15 +318,12 @@ struct GitArchiveInputScheme : InputScheme
{ {
auto [input, tarballInfo] = downloadArchive(store, _input); auto [input, tarballInfo] = downloadArchive(store, _input);
#if 0 #if 0
input.attrs.insert_or_assign("treeHash", tarballInfo.treeHash.gitRev()); input.attrs.insert_or_assign("treeHash", tarballInfo.treeHash.gitRev());
#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,24 +413,20 @@ 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};
} }
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
{ {
@ -462,32 +451,33 @@ struct GitLabInputScheme : GitArchiveInputScheme
auto fldsplit = token.find_first_of(':'); auto fldsplit = token.find_first_of(':');
// n.b. C++20 would allow: if (token.starts_with("OAuth2:")) ... // n.b. C++20 would allow: if (token.starts_with("OAuth2:")) ...
if ("OAuth2" == token.substr(0, fldsplit)) if ("OAuth2" == token.substr(0, fldsplit))
return std::make_pair("Authorization", fmt("Bearer %s", token.substr(fldsplit+1))); return std::make_pair("Authorization", fmt("Bearer %s", token.substr(fldsplit + 1)));
if ("PAT" == token.substr(0, fldsplit)) if ("PAT" == token.substr(0, fldsplit))
return std::make_pair("Private-token", token.substr(fldsplit+1)); return std::make_pair("Private-token", token.substr(fldsplit + 1));
warn("Unrecognized GitLab token type %s", token.substr(0, fldsplit)); warn("Unrecognized GitLab token type %s", token.substr(0, fldsplit));
return std::make_pair(token.substr(0,fldsplit), token.substr(fldsplit+1)); return std::make_pair(token.substr(0, fldsplit), token.substr(fldsplit + 1));
} }
RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const override RefInfo getRevFromRef(nix::ref<Store> store, const Input & input) const override
{ {
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,20 +492,24 @@ 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);
return DownloadUrl { url, headers }; return DownloadUrl{url, headers};
} }
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("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);
@ -572,7 +569,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
std::string line; std::string line;
std::optional<std::string> id; std::optional<std::string> id;
while(!id && getline(is, line)) { while (!id && getline(is, line)) {
auto parsedLine = git::parseLsRemoteLine(line); auto parsedLine = git::parseLsRemoteLine(line);
if (parsedLine && parsedLine->reference && std::regex_match(*parsedLine->reference, refRegex)) if (parsedLine && parsedLine->reference && std::regex_match(*parsedLine->reference, refRegex))
id = parsedLine->target; id = parsedLine->target;
@ -581,27 +578,29 @@ 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);
return DownloadUrl { url, headers }; return DownloadUrl{url, headers};
} }
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

@ -12,7 +12,7 @@ namespace nix::fetchers {
*/ */
struct Cache struct Cache
{ {
virtual ~Cache() { } virtual ~Cache() {}
/** /**
* A domain is a partition of the key/value cache for a particular * A domain is a partition of the key/value cache for a particular
@ -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;
@ -108,9 +107,9 @@ public:
*/ */
bool isFinal() const; bool isFinal() const;
bool operator ==(const Input & other) const noexcept; bool operator==(const Input & other) const noexcept;
bool operator <(const Input & other) const bool operator<(const Input & other) const
{ {
return attrs < other.attrs; return attrs < other.attrs;
} }
@ -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);
@ -278,10 +276,10 @@ struct PublicKey
std::string type = "ssh-ed25519"; std::string type = "ssh-ed25519";
std::string key; std::string key;
auto operator <=>(const PublicKey &) const = default; auto operator<=>(const PublicKey &) const = default;
}; };
std::string publicKeys_to_string(const std::vector<PublicKey>&); std::string publicKeys_to_string(const std::vector<PublicKey> &);
} }

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
@ -131,8 +126,11 @@ ref<GitRepo> getTarballCache();
template<auto del> 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);
@ -61,7 +52,7 @@ struct MercurialInputScheme : InputScheme
Attrs attrs; Attrs attrs;
attrs.emplace("type", "hg"); attrs.emplace("type", "hg");
for (auto &[name, value] : url.query) { for (auto & [name, value] : url.query) {
if (name == "rev" || name == "ref") if (name == "rev" || name == "ref")
attrs.emplace(name, value); attrs.emplace(name, value);
else else
@ -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
@ -179,7 +170,7 @@ struct MercurialInputScheme : InputScheme
if (!input.getRef() && !input.getRev() && isLocal && pathExists(actualUrl + "/.hg")) { if (!input.getRef() && !input.getRev() && isLocal && pathExists(actualUrl + "/.hg")) {
bool clean = runHg({ "status", "-R", actualUrl, "--modified", "--added", "--removed" }) == ""; bool clean = runHg({"status", "-R", actualUrl, "--modified", "--added", "--removed"}) == "";
if (!clean) { if (!clean) {
@ -192,10 +183,11 @@ struct MercurialInputScheme : InputScheme
if (input.settings->warnDirty) if (input.settings->warnDirty)
warn("Mercurial tree '%s' is unclean", actualUrl); warn("Mercurial tree '%s' is unclean", actualUrl);
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,43 +251,47 @@ 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)) {
runHg({ "recover", "-R", cacheDir }); runHg({"recover", "-R", cacheDir});
runHg({ "pull", "-R", cacheDir, "--", actualUrl }); runHg({"pull", "-R", cacheDir, "--", actualUrl});
} else { } else {
throw ExecError(e.status, "'hg pull' %s", statusToString(e.status)); throw ExecError(e.status, "'hg pull' %s", statusToString(e.status));
} }
} }
} else { } else {
createDirs(dirOf(cacheDir)); createDirs(dirOf(cacheDir));
runHg({ "clone", "--noupdate", "--", actualUrl, cacheDir }); runHg({"clone", "--noupdate", "--", actualUrl, cacheDir});
} }
} }
/* 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);
@ -315,7 +307,7 @@ struct MercurialInputScheme : InputScheme
Path tmpDir = createTempDir(); Path tmpDir = createTempDir();
AutoDelete delTmpDir(tmpDir, true); AutoDelete delTmpDir(tmpDir, true);
runHg({ "archive", "-R", cacheDir, "-r", rev.gitRev(), tmpDir }); runHg({"archive", "-R", cacheDir, "-r", rev.gitRev(), tmpDir});
deletePath(tmpDir + "/.hg_archival.txt"); deletePath(tmpDir + "/.hg_archival.txt");

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");
@ -76,7 +72,7 @@ struct PathInputScheme : InputScheme
query.erase("path"); query.erase("path");
query.erase("type"); query.erase("type");
query.erase("__final"); query.erase("__final");
return ParsedURL { return ParsedURL{
.scheme = "path", .scheme = "path",
.path = getStrAttr(input.attrs, "path"), .path = getStrAttr(input.attrs, "path"),
.query = query, .query = query,
@ -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");
} }

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