Merge branch 'master' into lto

This commit is contained in:
Théophane Hufschmitt 2022-06-01 17:55:03 +02:00 committed by GitHub
commit a5f7b934bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
54 changed files with 1721 additions and 519 deletions

View file

@ -1 +1 @@
2.9.0 2.10.0

31
doc/manual/anchors.jq Executable file
View file

@ -0,0 +1,31 @@
"\\[\\]\\{#(?<anchor>[^\\}]+?)\\}" as $empty_anchor_regex |
"\\[(?<text>[^\\]]+?)\\]\\{#(?<anchor>[^\\}]+?)\\}" as $anchor_regex |
def transform_anchors_html:
. | gsub($empty_anchor_regex; "<a name=\"" + .anchor + "\"></a>")
| gsub($anchor_regex; "<a href=\"#" + .anchor + "\" id=\"" + .anchor + "\">" + .text + "</a>");
def transform_anchors_strip:
. | gsub($empty_anchor_regex; "")
| gsub($anchor_regex; .text);
def map_contents_recursively(transformer):
. + {
Chapter: (.Chapter + {
content: .Chapter.content | transformer,
sub_items: .Chapter.sub_items | map(map_contents_recursively(transformer)),
}),
};
def process_command:
.[0] as $context |
.[1] as $body |
$body + {
sections: $body.sections | map(map_contents_recursively(if $context.renderer == "html" then transform_anchors_html else transform_anchors_strip end)),
};
process_command

View file

@ -1,2 +1,7 @@
[output.html] [output.html]
additional-css = ["custom.css"] additional-css = ["custom.css"]
additional-js = ["redirects.js"]
[preprocessor.anchors]
renderers = ["html"]
command = "jq --from-file doc/manual/anchors.jq"

View file

@ -97,7 +97,7 @@ doc/manual/generated/man1/nix3-manpages: $(d)/src/command-ref/new-cli
done done
@touch $@ @touch $@
$(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/command-ref/conf-file.md $(d)/src/expressions/builtins.md $(call rwildcard, $(d)/src, *.md) $(docdir)/manual/index.html: $(MANUAL_SRCS) $(d)/book.toml $(d)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/command-ref/conf-file.md $(d)/src/expressions/builtins.md $(call rwildcard, $(d)/src, *.md)
$(trace-gen) RUST_LOG=warn mdbook build doc/manual -d $(DESTDIR)$(docdir)/manual $(trace-gen) RUST_LOG=warn mdbook build doc/manual -d $(DESTDIR)$(docdir)/manual
endif endif

337
doc/manual/redirects.js Normal file
View file

@ -0,0 +1,337 @@
// Redirects from old DocBook manual.
var redirects = {
"#part-advanced-topics": "advanced-topics/advanced-topics.html",
"#chap-tuning-cores-and-jobs": "advanced-topics/cores-vs-jobs.html",
"#chap-diff-hook": "advanced-topics/diff-hook.html",
"#check-dirs-are-unregistered": "advanced-topics/diff-hook.html#check-dirs-are-unregistered",
"#chap-distributed-builds": "advanced-topics/distributed-builds.html",
"#chap-post-build-hook": "advanced-topics/post-build-hook.html",
"#chap-post-build-hook-caveats": "advanced-topics/post-build-hook.html#implementation-caveats",
"#part-command-ref": "command-ref/command-ref.html",
"#conf-allow-import-from-derivation": "command-ref/conf-file.html#conf-allow-import-from-derivation",
"#conf-allow-new-privileges": "command-ref/conf-file.html#conf-allow-new-privileges",
"#conf-allowed-uris": "command-ref/conf-file.html#conf-allowed-uris",
"#conf-allowed-users": "command-ref/conf-file.html#conf-allowed-users",
"#conf-auto-optimise-store": "command-ref/conf-file.html#conf-auto-optimise-store",
"#conf-binary-cache-public-keys": "command-ref/conf-file.html#conf-binary-cache-public-keys",
"#conf-binary-caches": "command-ref/conf-file.html#conf-binary-caches",
"#conf-build-compress-log": "command-ref/conf-file.html#conf-build-compress-log",
"#conf-build-cores": "command-ref/conf-file.html#conf-build-cores",
"#conf-build-extra-chroot-dirs": "command-ref/conf-file.html#conf-build-extra-chroot-dirs",
"#conf-build-extra-sandbox-paths": "command-ref/conf-file.html#conf-build-extra-sandbox-paths",
"#conf-build-fallback": "command-ref/conf-file.html#conf-build-fallback",
"#conf-build-max-jobs": "command-ref/conf-file.html#conf-build-max-jobs",
"#conf-build-max-log-size": "command-ref/conf-file.html#conf-build-max-log-size",
"#conf-build-max-silent-time": "command-ref/conf-file.html#conf-build-max-silent-time",
"#conf-build-repeat": "command-ref/conf-file.html#conf-build-repeat",
"#conf-build-timeout": "command-ref/conf-file.html#conf-build-timeout",
"#conf-build-use-chroot": "command-ref/conf-file.html#conf-build-use-chroot",
"#conf-build-use-sandbox": "command-ref/conf-file.html#conf-build-use-sandbox",
"#conf-build-use-substitutes": "command-ref/conf-file.html#conf-build-use-substitutes",
"#conf-build-users-group": "command-ref/conf-file.html#conf-build-users-group",
"#conf-builders": "command-ref/conf-file.html#conf-builders",
"#conf-builders-use-substitutes": "command-ref/conf-file.html#conf-builders-use-substitutes",
"#conf-compress-build-log": "command-ref/conf-file.html#conf-compress-build-log",
"#conf-connect-timeout": "command-ref/conf-file.html#conf-connect-timeout",
"#conf-cores": "command-ref/conf-file.html#conf-cores",
"#conf-diff-hook": "command-ref/conf-file.html#conf-diff-hook",
"#conf-enforce-determinism": "command-ref/conf-file.html#conf-enforce-determinism",
"#conf-env-keep-derivations": "command-ref/conf-file.html#conf-env-keep-derivations",
"#conf-extra-binary-caches": "command-ref/conf-file.html#conf-extra-binary-caches",
"#conf-extra-platforms": "command-ref/conf-file.html#conf-extra-platforms",
"#conf-extra-sandbox-paths": "command-ref/conf-file.html#conf-extra-sandbox-paths",
"#conf-extra-substituters": "command-ref/conf-file.html#conf-extra-substituters",
"#conf-fallback": "command-ref/conf-file.html#conf-fallback",
"#conf-fsync-metadata": "command-ref/conf-file.html#conf-fsync-metadata",
"#conf-gc-keep-derivations": "command-ref/conf-file.html#conf-gc-keep-derivations",
"#conf-gc-keep-outputs": "command-ref/conf-file.html#conf-gc-keep-outputs",
"#conf-hashed-mirrors": "command-ref/conf-file.html#conf-hashed-mirrors",
"#conf-http-connections": "command-ref/conf-file.html#conf-http-connections",
"#conf-keep-build-log": "command-ref/conf-file.html#conf-keep-build-log",
"#conf-keep-derivations": "command-ref/conf-file.html#conf-keep-derivations",
"#conf-keep-env-derivations": "command-ref/conf-file.html#conf-keep-env-derivations",
"#conf-keep-outputs": "command-ref/conf-file.html#conf-keep-outputs",
"#conf-max-build-log-size": "command-ref/conf-file.html#conf-max-build-log-size",
"#conf-max-free": "command-ref/conf-file.html#conf-max-free",
"#conf-max-jobs": "command-ref/conf-file.html#conf-max-jobs",
"#conf-max-silent-time": "command-ref/conf-file.html#conf-max-silent-time",
"#conf-min-free": "command-ref/conf-file.html#conf-min-free",
"#conf-narinfo-cache-negative-ttl": "command-ref/conf-file.html#conf-narinfo-cache-negative-ttl",
"#conf-narinfo-cache-positive-ttl": "command-ref/conf-file.html#conf-narinfo-cache-positive-ttl",
"#conf-netrc-file": "command-ref/conf-file.html#conf-netrc-file",
"#conf-plugin-files": "command-ref/conf-file.html#conf-plugin-files",
"#conf-post-build-hook": "command-ref/conf-file.html#conf-post-build-hook",
"#conf-pre-build-hook": "command-ref/conf-file.html#conf-pre-build-hook",
"#conf-repeat": "command-ref/conf-file.html#conf-repeat",
"#conf-require-sigs": "command-ref/conf-file.html#conf-require-sigs",
"#conf-restrict-eval": "command-ref/conf-file.html#conf-restrict-eval",
"#conf-run-diff-hook": "command-ref/conf-file.html#conf-run-diff-hook",
"#conf-sandbox": "command-ref/conf-file.html#conf-sandbox",
"#conf-sandbox-dev-shm-size": "command-ref/conf-file.html#conf-sandbox-dev-shm-size",
"#conf-sandbox-paths": "command-ref/conf-file.html#conf-sandbox-paths",
"#conf-secret-key-files": "command-ref/conf-file.html#conf-secret-key-files",
"#conf-show-trace": "command-ref/conf-file.html#conf-show-trace",
"#conf-stalled-download-timeout": "command-ref/conf-file.html#conf-stalled-download-timeout",
"#conf-substitute": "command-ref/conf-file.html#conf-substitute",
"#conf-substituters": "command-ref/conf-file.html#conf-substituters",
"#conf-system": "command-ref/conf-file.html#conf-system",
"#conf-system-features": "command-ref/conf-file.html#conf-system-features",
"#conf-tarball-ttl": "command-ref/conf-file.html#conf-tarball-ttl",
"#conf-timeout": "command-ref/conf-file.html#conf-timeout",
"#conf-trace-function-calls": "command-ref/conf-file.html#conf-trace-function-calls",
"#conf-trusted-binary-caches": "command-ref/conf-file.html#conf-trusted-binary-caches",
"#conf-trusted-public-keys": "command-ref/conf-file.html#conf-trusted-public-keys",
"#conf-trusted-substituters": "command-ref/conf-file.html#conf-trusted-substituters",
"#conf-trusted-users": "command-ref/conf-file.html#conf-trusted-users",
"#extra-sandbox-paths": "command-ref/conf-file.html#extra-sandbox-paths",
"#sec-conf-file": "command-ref/conf-file.html",
"#env-NIX_PATH": "command-ref/env-common.html#env-NIX_PATH",
"#env-common": "command-ref/env-common.html",
"#envar-remote": "command-ref/env-common.html#env-NIX_REMOTE",
"#sec-common-env": "command-ref/env-common.html",
"#ch-files": "command-ref/files.html",
"#ch-main-commands": "command-ref/main-commands.html",
"#opt-out-link": "command-ref/nix-build.html#opt-out-link",
"#sec-nix-build": "command-ref/nix-build.html",
"#sec-nix-channel": "command-ref/nix-channel.html",
"#sec-nix-collect-garbage": "command-ref/nix-collect-garbage.html",
"#sec-nix-copy-closure": "command-ref/nix-copy-closure.html",
"#sec-nix-daemon": "command-ref/nix-daemon.html",
"#refsec-nix-env-install-examples": "command-ref/nix-env.html#examples",
"#rsec-nix-env-install": "command-ref/nix-env.html#operation---install",
"#rsec-nix-env-set": "command-ref/nix-env.html#operation---set",
"#rsec-nix-env-set-flag": "command-ref/nix-env.html#operation---set-flag",
"#rsec-nix-env-upgrade": "command-ref/nix-env.html#operation---upgrade",
"#sec-nix-env": "command-ref/nix-env.html",
"#ssec-version-comparisons": "command-ref/nix-env.html#versions",
"#sec-nix-hash": "command-ref/nix-hash.html",
"#sec-nix-instantiate": "command-ref/nix-instantiate.html",
"#sec-nix-prefetch-url": "command-ref/nix-prefetch-url.html",
"#sec-nix-shell": "command-ref/nix-shell.html",
"#ssec-nix-shell-shebang": "command-ref/nix-shell.html#use-as-a--interpreter",
"#nixref-queries": "command-ref/nix-store.html#queries",
"#opt-add-root": "command-ref/nix-store.html#opt-add-root",
"#refsec-nix-store-dump": "command-ref/nix-store.html#operation---dump",
"#refsec-nix-store-export": "command-ref/nix-store.html#operation---export",
"#refsec-nix-store-import": "command-ref/nix-store.html#operation---import",
"#refsec-nix-store-query": "command-ref/nix-store.html#operation---query",
"#refsec-nix-store-verify": "command-ref/nix-store.html#operation---verify",
"#rsec-nix-store-gc": "command-ref/nix-store.html#operation---gc",
"#rsec-nix-store-generate-binary-cache-key": "command-ref/nix-store.html#operation---generate-binary-cache-key",
"#rsec-nix-store-realise": "command-ref/nix-store.html#operation---realise",
"#rsec-nix-store-serve": "command-ref/nix-store.html#operation---serve",
"#sec-nix-store": "command-ref/nix-store.html",
"#opt-I": "command-ref/opt-common.html#opt-I",
"#opt-attr": "command-ref/opt-common.html#opt-attr",
"#opt-common": "command-ref/opt-common.html",
"#opt-cores": "command-ref/opt-common.html#opt-cores",
"#opt-log-format": "command-ref/opt-common.html#opt-log-format",
"#opt-max-jobs": "command-ref/opt-common.html#opt-max-jobs",
"#opt-max-silent-time": "command-ref/opt-common.html#opt-max-silent-time",
"#opt-timeout": "command-ref/opt-common.html#opt-timeout",
"#sec-common-options": "command-ref/opt-common.html",
"#ch-utilities": "command-ref/utilities.html",
"#chap-hacking": "contributing/hacking.html",
"#adv-attr-allowSubstitutes": "expressions/advanced-attributes.html#adv-attr-allowSubstitutes",
"#adv-attr-allowedReferences": "expressions/advanced-attributes.html#adv-attr-allowedReferences",
"#adv-attr-allowedRequisites": "expressions/advanced-attributes.html#adv-attr-allowedRequisites",
"#adv-attr-disallowedReferences": "expressions/advanced-attributes.html#adv-attr-disallowedReferences",
"#adv-attr-disallowedRequisites": "expressions/advanced-attributes.html#adv-attr-disallowedRequisites",
"#adv-attr-exportReferencesGraph": "expressions/advanced-attributes.html#adv-attr-exportReferencesGraph",
"#adv-attr-impureEnvVars": "expressions/advanced-attributes.html#adv-attr-impureEnvVars",
"#adv-attr-outputHash": "expressions/advanced-attributes.html#adv-attr-outputHash",
"#adv-attr-outputHashAlgo": "expressions/advanced-attributes.html#adv-attr-outputHashAlgo",
"#adv-attr-outputHashMode": "expressions/advanced-attributes.html#adv-attr-outputHashMode",
"#adv-attr-passAsFile": "expressions/advanced-attributes.html#adv-attr-passAsFile",
"#adv-attr-preferLocalBuild": "expressions/advanced-attributes.html#adv-attr-preferLocalBuild",
"#fixed-output-drvs": "expressions/advanced-attributes.html#adv-attr-outputHash",
"#sec-advanced-attributes": "expressions/advanced-attributes.html",
"#sec-arguments": "expressions/arguments-variables.html",
"#sec-build-script": "expressions/build-script.html",
"#builtin-abort": "expressions/builtins.html#builtins-abort",
"#builtin-add": "expressions/builtins.html#builtins-add",
"#builtin-all": "expressions/builtins.html#builtins-all",
"#builtin-any": "expressions/builtins.html#builtins-any",
"#builtin-attrNames": "expressions/builtins.html#builtins-attrNames",
"#builtin-attrValues": "expressions/builtins.html#builtins-attrValues",
"#builtin-baseNameOf": "expressions/builtins.html#builtins-baseNameOf",
"#builtin-bitAnd": "expressions/builtins.html#builtins-bitAnd",
"#builtin-bitOr": "expressions/builtins.html#builtins-bitOr",
"#builtin-bitXor": "expressions/builtins.html#builtins-bitXor",
"#builtin-builtins": "expressions/builtins.html#builtins-builtins",
"#builtin-compareVersions": "expressions/builtins.html#builtins-compareVersions",
"#builtin-concatLists": "expressions/builtins.html#builtins-concatLists",
"#builtin-concatStringsSep": "expressions/builtins.html#builtins-concatStringsSep",
"#builtin-currentSystem": "expressions/builtins.html#builtins-currentSystem",
"#builtin-deepSeq": "expressions/builtins.html#builtins-deepSeq",
"#builtin-derivation": "expressions/builtins.html#builtins-derivation",
"#builtin-dirOf": "expressions/builtins.html#builtins-dirOf",
"#builtin-div": "expressions/builtins.html#builtins-div",
"#builtin-elem": "expressions/builtins.html#builtins-elem",
"#builtin-elemAt": "expressions/builtins.html#builtins-elemAt",
"#builtin-fetchGit": "expressions/builtins.html#builtins-fetchGit",
"#builtin-fetchTarball": "expressions/builtins.html#builtins-fetchTarball",
"#builtin-fetchurl": "expressions/builtins.html#builtins-fetchurl",
"#builtin-filterSource": "expressions/builtins.html#builtins-filterSource",
"#builtin-foldl-prime": "expressions/builtins.html#builtins-foldl-prime",
"#builtin-fromJSON": "expressions/builtins.html#builtins-fromJSON",
"#builtin-functionArgs": "expressions/builtins.html#builtins-functionArgs",
"#builtin-genList": "expressions/builtins.html#builtins-genList",
"#builtin-getAttr": "expressions/builtins.html#builtins-getAttr",
"#builtin-getEnv": "expressions/builtins.html#builtins-getEnv",
"#builtin-hasAttr": "expressions/builtins.html#builtins-hasAttr",
"#builtin-hashFile": "expressions/builtins.html#builtins-hashFile",
"#builtin-hashString": "expressions/builtins.html#builtins-hashString",
"#builtin-head": "expressions/builtins.html#builtins-head",
"#builtin-import": "expressions/builtins.html#builtins-import",
"#builtin-intersectAttrs": "expressions/builtins.html#builtins-intersectAttrs",
"#builtin-isAttrs": "expressions/builtins.html#builtins-isAttrs",
"#builtin-isBool": "expressions/builtins.html#builtins-isBool",
"#builtin-isFloat": "expressions/builtins.html#builtins-isFloat",
"#builtin-isFunction": "expressions/builtins.html#builtins-isFunction",
"#builtin-isInt": "expressions/builtins.html#builtins-isInt",
"#builtin-isList": "expressions/builtins.html#builtins-isList",
"#builtin-isNull": "expressions/builtins.html#builtins-isNull",
"#builtin-isString": "expressions/builtins.html#builtins-isString",
"#builtin-length": "expressions/builtins.html#builtins-length",
"#builtin-lessThan": "expressions/builtins.html#builtins-lessThan",
"#builtin-listToAttrs": "expressions/builtins.html#builtins-listToAttrs",
"#builtin-map": "expressions/builtins.html#builtins-map",
"#builtin-match": "expressions/builtins.html#builtins-match",
"#builtin-mul": "expressions/builtins.html#builtins-mul",
"#builtin-parseDrvName": "expressions/builtins.html#builtins-parseDrvName",
"#builtin-path": "expressions/builtins.html#builtins-path",
"#builtin-pathExists": "expressions/builtins.html#builtins-pathExists",
"#builtin-placeholder": "expressions/builtins.html#builtins-placeholder",
"#builtin-readDir": "expressions/builtins.html#builtins-readDir",
"#builtin-readFile": "expressions/builtins.html#builtins-readFile",
"#builtin-removeAttrs": "expressions/builtins.html#builtins-removeAttrs",
"#builtin-replaceStrings": "expressions/builtins.html#builtins-replaceStrings",
"#builtin-seq": "expressions/builtins.html#builtins-seq",
"#builtin-sort": "expressions/builtins.html#builtins-sort",
"#builtin-split": "expressions/builtins.html#builtins-split",
"#builtin-splitVersion": "expressions/builtins.html#builtins-splitVersion",
"#builtin-stringLength": "expressions/builtins.html#builtins-stringLength",
"#builtin-sub": "expressions/builtins.html#builtins-sub",
"#builtin-substring": "expressions/builtins.html#builtins-substring",
"#builtin-tail": "expressions/builtins.html#builtins-tail",
"#builtin-throw": "expressions/builtins.html#builtins-throw",
"#builtin-toFile": "expressions/builtins.html#builtins-toFile",
"#builtin-toJSON": "expressions/builtins.html#builtins-toJSON",
"#builtin-toPath": "expressions/builtins.html#builtins-toPath",
"#builtin-toString": "expressions/builtins.html#builtins-toString",
"#builtin-toXML": "expressions/builtins.html#builtins-toXML",
"#builtin-trace": "expressions/builtins.html#builtins-trace",
"#builtin-tryEval": "expressions/builtins.html#builtins-tryEval",
"#builtin-typeOf": "expressions/builtins.html#builtins-typeOf",
"#ssec-builtins": "expressions/builtins.html",
"#attr-system": "expressions/derivations.html#attr-system",
"#ssec-derivation": "expressions/derivations.html",
"#ch-expression-language": "expressions/expression-language.html",
"#sec-expression-syntax": "expressions/expression-syntax.html",
"#sec-generic-builder": "expressions/generic-builder.html",
"#sec-constructs": "expressions/language-constructs.html",
"#sect-let-expressions": "expressions/language-constructs.html#let-expressions",
"#ss-functions": "expressions/language-constructs.html#functions",
"#sec-language-operators": "expressions/language-operators.html",
"#table-operators": "expressions/language-operators.html",
"#ssec-values": "expressions/language-values.html",
"#sec-building-simple": "expressions/simple-building-testing.html",
"#ch-simple-expression": "expressions/simple-expression.html",
"#chap-writing-nix-expressions": "expressions/writing-nix-expressions.html",
"#gloss-closure": "glossary.html#gloss-closure",
"#gloss-derivation": "glossary.html#gloss-derivation",
"#gloss-deriver": "glossary.html#gloss-deriver",
"#gloss-nar": "glossary.html#gloss-nar",
"#gloss-output-path": "glossary.html#gloss-output-path",
"#gloss-profile": "glossary.html#gloss-profile",
"#gloss-reachable": "glossary.html#gloss-reachable",
"#gloss-reference": "glossary.html#gloss-reference",
"#gloss-substitute": "glossary.html#gloss-substitute",
"#gloss-user-env": "glossary.html#gloss-user-env",
"#gloss-validity": "glossary.html#gloss-validity",
"#part-glossary": "glossary.html",
"#sec-building-source": "installation/building-source.html",
"#ch-env-variables": "installation/env-variables.html",
"#sec-installer-proxy-settings": "installation/env-variables.html#proxy-environment-variables",
"#sec-nix-ssl-cert-file": "installation/env-variables.html#nix_ssl_cert_file",
"#sec-nix-ssl-cert-file-with-nix-daemon-and-macos": "installation/env-variables.html#nix_ssl_cert_file-with-macos-and-the-nix-daemon",
"#chap-installation": "installation/installation.html",
"#ch-installing-binary": "installation/installing-binary.html",
"#sect-macos-installation": "installation/installing-binary.html#macos-installation",
"#sect-macos-installation-change-store-prefix": "installation/installing-binary.html#macos-installation",
"#sect-macos-installation-encrypted-volume": "installation/installing-binary.html#macos-installation",
"#sect-macos-installation-recommended-notes": "installation/installing-binary.html#macos-installation",
"#sect-macos-installation-symlink": "installation/installing-binary.html#macos-installation",
"#sect-multi-user-installation": "installation/installing-binary.html#multi-user-installation",
"#sect-nix-install-binary-tarball": "installation/installing-binary.html#installing-from-a-binary-tarball",
"#sect-nix-install-pinned-version-url": "installation/installing-binary.html#installing-a-pinned-nix-version-from-a-url",
"#sect-single-user-installation": "installation/installing-binary.html#single-user-installation",
"#ch-installing-source": "installation/installing-source.html",
"#ssec-multi-user": "installation/multi-user.html",
"#ch-nix-security": "installation/nix-security.html",
"#sec-obtaining-source": "installation/obtaining-source.html",
"#sec-prerequisites-source": "installation/prerequisites-source.html",
"#sec-single-user": "installation/single-user.html",
"#ch-supported-platforms": "installation/supported-platforms.html",
"#ch-upgrading-nix": "installation/upgrading.html",
"#ch-about-nix": "introduction.html",
"#chap-introduction": "introduction.html",
"#ch-basic-package-mgmt": "package-management/basic-package-mgmt.html",
"#ssec-binary-cache-substituter": "package-management/binary-cache-substituter.html",
"#sec-channels": "package-management/channels.html",
"#ssec-copy-closure": "package-management/copy-closure.html",
"#sec-garbage-collection": "package-management/garbage-collection.html",
"#ssec-gc-roots": "package-management/garbage-collector-roots.html",
"#chap-package-management": "package-management/package-management.html",
"#sec-profiles": "package-management/profiles.html",
"#ssec-s3-substituter": "package-management/s3-substituter.html",
"#ssec-s3-substituter-anonymous-reads": "package-management/s3-substituter.html#anonymous-reads-to-your-s3-compatible-binary-cache",
"#ssec-s3-substituter-authenticated-reads": "package-management/s3-substituter.html#authenticated-reads-to-your-s3-binary-cache",
"#ssec-s3-substituter-authenticated-writes": "package-management/s3-substituter.html#authenticated-writes-to-your-s3-compatible-binary-cache",
"#sec-sharing-packages": "package-management/sharing-packages.html",
"#ssec-ssh-substituter": "package-management/ssh-substituter.html",
"#chap-quick-start": "quick-start.html",
"#sec-relnotes": "release-notes/release-notes.html",
"#ch-relnotes-0.10.1": "release-notes/rl-0.10.1.html",
"#ch-relnotes-0.10": "release-notes/rl-0.10.html",
"#ssec-relnotes-0.11": "release-notes/rl-0.11.html",
"#ssec-relnotes-0.12": "release-notes/rl-0.12.html",
"#ssec-relnotes-0.13": "release-notes/rl-0.13.html",
"#ssec-relnotes-0.14": "release-notes/rl-0.14.html",
"#ssec-relnotes-0.15": "release-notes/rl-0.15.html",
"#ssec-relnotes-0.16": "release-notes/rl-0.16.html",
"#ch-relnotes-0.5": "release-notes/rl-0.5.html",
"#ch-relnotes-0.6": "release-notes/rl-0.6.html",
"#ch-relnotes-0.7": "release-notes/rl-0.7.html",
"#ch-relnotes-0.8.1": "release-notes/rl-0.8.1.html",
"#ch-relnotes-0.8": "release-notes/rl-0.8.html",
"#ch-relnotes-0.9.1": "release-notes/rl-0.9.1.html",
"#ch-relnotes-0.9.2": "release-notes/rl-0.9.2.html",
"#ch-relnotes-0.9": "release-notes/rl-0.9.html",
"#ssec-relnotes-1.0": "release-notes/rl-1.0.html",
"#ssec-relnotes-1.1": "release-notes/rl-1.1.html",
"#ssec-relnotes-1.10": "release-notes/rl-1.10.html",
"#ssec-relnotes-1.11.10": "release-notes/rl-1.11.10.html",
"#ssec-relnotes-1.11": "release-notes/rl-1.11.html",
"#ssec-relnotes-1.2": "release-notes/rl-1.2.html",
"#ssec-relnotes-1.3": "release-notes/rl-1.3.html",
"#ssec-relnotes-1.4": "release-notes/rl-1.4.html",
"#ssec-relnotes-1.5.1": "release-notes/rl-1.5.1.html",
"#ssec-relnotes-1.5.2": "release-notes/rl-1.5.2.html",
"#ssec-relnotes-1.5": "release-notes/rl-1.5.html",
"#ssec-relnotes-1.6.1": "release-notes/rl-1.6.1.html",
"#ssec-relnotes-1.6.0": "release-notes/rl-1.6.html",
"#ssec-relnotes-1.7": "release-notes/rl-1.7.html",
"#ssec-relnotes-1.8": "release-notes/rl-1.8.html",
"#ssec-relnotes-1.9": "release-notes/rl-1.9.html",
"#ssec-relnotes-2.0": "release-notes/rl-2.0.html",
"#ssec-relnotes-2.1": "release-notes/rl-2.1.html",
"#ssec-relnotes-2.2": "release-notes/rl-2.2.html",
"#ssec-relnotes-2.3": "release-notes/rl-2.3.html"
};
var isRoot = (document.location.pathname.endsWith('/') || document.location.pathname.endsWith('/index.html')) && path_to_root === '';
if (isRoot && redirects[document.location.hash]) {
document.location.href = path_to_root + redirects[document.location.hash];
}

View file

@ -72,6 +72,7 @@
- [CLI guideline](contributing/cli-guideline.md) - [CLI guideline](contributing/cli-guideline.md)
- [Release Notes](release-notes/release-notes.md) - [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.md) - [Release X.Y (202?-??-??)](release-notes/rl-next.md)
- [Release 2.9 (2022-05-30)](release-notes/rl-2.9.md)
- [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md) - [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md)
- [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md) - [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md)
- [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md) - [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md)

View file

@ -101,7 +101,7 @@ In particular, notice the
`/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable.check` output. Nix `/nix/store/krpqk0l9ib0ibi1d2w52z293zw455cap-unstable.check` output. Nix
has copied the build results to that directory where you can examine it. has copied the build results to that directory where you can examine it.
> **Note** > []{#check-dirs-are-unregistered} **Note**
> >
> Check paths are not protected against garbage collection, and this > Check paths are not protected against garbage collection, and this
> path will be deleted on the next garbage collection. > path will be deleted on the next garbage collection.

View file

@ -2,11 +2,11 @@
Most Nix commands interpret the following environment variables: Most Nix commands interpret the following environment variables:
- `IN_NIX_SHELL`\ - [`IN_NIX_SHELL`]{#env-IN_NIX_SHELL}\
Indicator that tells if the current environment was set up by Indicator that tells if the current environment was set up by
`nix-shell`. Since Nix 2.0 the values are `"pure"` and `"impure"` `nix-shell`. It can have the values `pure` or `impure`.
- `NIX_PATH`\ - [`NIX_PATH`]{#env-NIX_PATH}\
A colon-separated list of directories used to look up Nix A colon-separated list of directories used to look up Nix
expressions enclosed in angle brackets (i.e., `<path>`). For expressions enclosed in angle brackets (i.e., `<path>`). For
instance, the value instance, the value
@ -44,7 +44,7 @@ Most Nix commands interpret the following environment variables:
The Nix search path can also be extended using the `-I` option to The Nix search path can also be extended using the `-I` option to
many Nix commands, which takes precedence over `NIX_PATH`. many Nix commands, which takes precedence over `NIX_PATH`.
- `NIX_IGNORE_SYMLINK_STORE`\ - [`NIX_IGNORE_SYMLINK_STORE`]{#env-NIX_IGNORE_SYMLINK_STORE}\
Normally, the Nix store directory (typically `/nix/store`) is not Normally, the Nix store directory (typically `/nix/store`) is not
allowed to contain any symlink components. This is to prevent allowed to contain any symlink components. This is to prevent
“impure” builds. Builders sometimes “canonicalise” paths by “impure” builds. Builders sometimes “canonicalise” paths by
@ -66,41 +66,41 @@ Most Nix commands interpret the following environment variables:
Consult the mount 8 manual page for details. Consult the mount 8 manual page for details.
- `NIX_STORE_DIR`\ - [`NIX_STORE_DIR`]{#env-NIX_STORE_DIR}\
Overrides the location of the Nix store (default `prefix/store`). Overrides the location of the Nix store (default `prefix/store`).
- `NIX_DATA_DIR`\ - [`NIX_DATA_DIR`]{#env-NIX_DATA_DIR}\
Overrides the location of the Nix static data directory (default Overrides the location of the Nix static data directory (default
`prefix/share`). `prefix/share`).
- `NIX_LOG_DIR`\ - [`NIX_LOG_DIR`]{#env-NIX_LOG_DIR}\
Overrides the location of the Nix log directory (default Overrides the location of the Nix log directory (default
`prefix/var/log/nix`). `prefix/var/log/nix`).
- `NIX_STATE_DIR`\ - [`NIX_STATE_DIR`]{#env-NIX_STATE_DIR}\
Overrides the location of the Nix state directory (default Overrides the location of the Nix state directory (default
`prefix/var/nix`). `prefix/var/nix`).
- `NIX_CONF_DIR`\ - [`NIX_CONF_DIR`]{#env-NIX_CONF_DIR}\
Overrides the location of the system Nix configuration directory Overrides the location of the system Nix configuration directory
(default `prefix/etc/nix`). (default `prefix/etc/nix`).
- `NIX_CONFIG`\ - [`NIX_CONFIG`]{#env-NIX_CONFIG}\
Applies settings from Nix configuration from the environment. Applies settings from Nix configuration from the environment.
The content is treated as if it was read from a Nix configuration file. The content is treated as if it was read from a Nix configuration file.
Settings are separated by the newline character. Settings are separated by the newline character.
- `NIX_USER_CONF_FILES`\ - [`NIX_USER_CONF_FILES`]{#env-NIX_USER_CONF_FILES}\
Overrides the location of the user Nix configuration files to load Overrides the location of the user Nix configuration files to load
from (defaults to the XDG spec locations). The variable is treated from (defaults to the XDG spec locations). The variable is treated
as a list separated by the `:` token. as a list separated by the `:` token.
- `TMPDIR`\ - [`TMPDIR`]{#env-TMPDIR}\
Use the specified directory to store temporary files. In particular, Use the specified directory to store temporary files. In particular,
this includes temporary build directories; these can take up this includes temporary build directories; these can take up
substantial amounts of disk space. The default is `/tmp`. substantial amounts of disk space. The default is `/tmp`.
- `NIX_REMOTE`\ - [`NIX_REMOTE`]{#env-NIX_REMOTE}\
This variable should be set to `daemon` if you want to use the Nix This variable should be set to `daemon` if you want to use the Nix
daemon to execute Nix operations. This is necessary in [multi-user daemon to execute Nix operations. This is necessary in [multi-user
Nix installations](../installation/multi-user.md). If the Nix Nix installations](../installation/multi-user.md). If the Nix
@ -108,16 +108,16 @@ Most Nix commands interpret the following environment variables:
should be set to `unix://path/to/socket`. Otherwise, it should be should be set to `unix://path/to/socket`. Otherwise, it should be
left unset. left unset.
- `NIX_SHOW_STATS`\ - [`NIX_SHOW_STATS`]{#env-NIX_SHOW_STATS}\
If set to `1`, Nix will print some evaluation statistics, such as If set to `1`, Nix will print some evaluation statistics, such as
the number of values allocated. the number of values allocated.
- `NIX_COUNT_CALLS`\ - [`NIX_COUNT_CALLS`]{#env-NIX_COUNT_CALLS}\
If set to `1`, Nix will print how often functions were called during If set to `1`, Nix will print how often functions were called during
Nix expression evaluation. This is useful for profiling your Nix Nix expression evaluation. This is useful for profiling your Nix
expressions. expressions.
- `GC_INITIAL_HEAP_SIZE`\ - [`GC_INITIAL_HEAP_SIZE`]{#env-GC_INITIAL_HEAP_SIZE}\
If Nix has been configured to use the Boehm garbage collector, this If Nix has been configured to use the Boehm garbage collector, this
variable sets the initial size of the heap in bytes. It defaults to variable sets the initial size of the heap in bytes. It defaults to
384 MiB. Setting it to a low value reduces memory consumption, but 384 MiB. Setting it to a low value reduces memory consumption, but

View file

@ -47,16 +47,16 @@ All options not listed here are passed to `nix-store
--realise`, except for `--arg` and `--attr` / `-A` which are passed to --realise`, except for `--arg` and `--attr` / `-A` which are passed to
`nix-instantiate`. `nix-instantiate`.
- `--no-out-link`\ - [`--no-out-link`]{#opt-no-out-link}\
Do not create a symlink to the output path. Note that as a result Do not create a symlink to the output path. Note that as a result
the output does not become a root of the garbage collector, and so the output does not become a root of the garbage collector, and so
might be deleted by `nix-store might be deleted by `nix-store
--gc`. --gc`.
- `--dry-run`\ - [`--dry-run`]{#opt-dry-run}\
Show what store paths would be built or downloaded. Show what store paths would be built or downloaded.
- `--out-link` / `-o` *outlink*\ - [`--out-link`]{#opt-out-link} / `-o` *outlink*\
Change the name of the symlink to the output path created from Change the name of the symlink to the output path created from
`result` to *outlink*. `result` to *outlink*.

View file

@ -22,7 +22,7 @@ This section lists the options that are common to all operations. These
options are allowed for every subcommand, though they may not always options are allowed for every subcommand, though they may not always
have an effect. have an effect.
- `--add-root` *path*\ - [`--add-root`]{#opt-add-root} *path*\
Causes the result of a realisation (`--realise` and Causes the result of a realisation (`--realise` and
`--force-realise`) to be registered as a root of the garbage `--force-realise`) to be registered as a root of the garbage
collector. *path* will be created as a symlink to the resulting collector. *path* will be created as a symlink to the resulting

View file

@ -2,13 +2,13 @@
Most Nix commands accept the following command-line options: Most Nix commands accept the following command-line options:
- `--help`\ - [`--help`]{#opt-help}\
Prints out a summary of the command syntax and exits. Prints out a summary of the command syntax and exits.
- `--version`\ - [`--version`]{#opt-version}\
Prints out the Nix version number on standard output and exits. Prints out the Nix version number on standard output and exits.
- `--verbose` / `-v`\ - [`--verbose`]{#opt-verbose} / `-v`\
Increases the level of verbosity of diagnostic messages printed on Increases the level of verbosity of diagnostic messages printed on
standard error. For each Nix operation, the information printed on standard error. For each Nix operation, the information printed on
standard output is well-defined; any diagnostic information is standard output is well-defined; any diagnostic information is
@ -37,14 +37,14 @@ Most Nix commands accept the following command-line options:
- 5\ - 5\
“Vomit”: print vast amounts of debug information. “Vomit”: print vast amounts of debug information.
- `--quiet`\ - [`--quiet`]{#opt-quiet}\
Decreases the level of verbosity of diagnostic messages printed on Decreases the level of verbosity of diagnostic messages printed on
standard error. This is the inverse option to `-v` / `--verbose`. standard error. This is the inverse option to `-v` / `--verbose`.
This option may be specified repeatedly. See the previous verbosity This option may be specified repeatedly. See the previous verbosity
levels list. levels list.
- `--log-format` *format*\ - [`--log-format`]{#opt-log-format} *format*\
This option can be used to change the output of the log format, with This option can be used to change the output of the log format, with
*format* being one of: *format* being one of:
@ -66,14 +66,14 @@ Most Nix commands accept the following command-line options:
- bar-with-logs\ - bar-with-logs\
Display the raw logs, with the progress bar at the bottom. Display the raw logs, with the progress bar at the bottom.
- `--no-build-output` / `-Q`\ - [`--no-build-output`]{#opt-no-build-output} / `-Q`\
By default, output written by builders to standard output and By default, output written by builders to standard output and
standard error is echoed to the Nix command's standard error. This standard error is echoed to the Nix command's standard error. This
option suppresses this behaviour. Note that the builder's standard option suppresses this behaviour. Note that the builder's standard
output and error are always written to a log file in output and error are always written to a log file in
`prefix/nix/var/log/nix`. `prefix/nix/var/log/nix`.
- `--max-jobs` / `-j` *number*\ - [`--max-jobs`]{#opt-max-jobs} / `-j` *number*\
Sets the maximum number of build jobs that Nix will perform in Sets the maximum number of build jobs that Nix will perform in
parallel to the specified number. Specify `auto` to use the number parallel to the specified number. Specify `auto` to use the number
of CPUs in the system. The default is specified by the `max-jobs` of CPUs in the system. The default is specified by the `max-jobs`
@ -83,7 +83,7 @@ Most Nix commands accept the following command-line options:
Setting it to `0` disallows building on the local machine, which is Setting it to `0` disallows building on the local machine, which is
useful when you want builds to happen only on remote builders. useful when you want builds to happen only on remote builders.
- `--cores`\ - [`--cores`]{#opt-cores}\
Sets the value of the `NIX_BUILD_CORES` environment variable in Sets the value of the `NIX_BUILD_CORES` environment variable in
the invocation of builders. Builders can use this variable at the invocation of builders. Builders can use this variable at
their discretion to control the maximum amount of parallelism. For their discretion to control the maximum amount of parallelism. For
@ -94,18 +94,18 @@ Most Nix commands accept the following command-line options:
means that the builder should use all available CPU cores in the means that the builder should use all available CPU cores in the
system. system.
- `--max-silent-time`\ - [`--max-silent-time`]{#opt-max-silent-time}\
Sets the maximum number of seconds that a builder can go without Sets the maximum number of seconds that a builder can go without
producing any data on standard output or standard error. The producing any data on standard output or standard error. The
default is specified by the `max-silent-time` configuration default is specified by the `max-silent-time` configuration
setting. `0` means no time-out. setting. `0` means no time-out.
- `--timeout`\ - [`--timeout`]{#opt-timeout}\
Sets the maximum number of seconds that a builder can run. The Sets the maximum number of seconds that a builder can run. The
default is specified by the `timeout` configuration setting. `0` default is specified by the `timeout` configuration setting. `0`
means no timeout. means no timeout.
- `--keep-going` / `-k`\ - [`--keep-going`]{#opt-keep-going} / `-k`\
Keep going in case of failed builds, to the greatest extent Keep going in case of failed builds, to the greatest extent
possible. That is, if building an input of some derivation fails, possible. That is, if building an input of some derivation fails,
Nix will still build the other inputs, but not the derivation Nix will still build the other inputs, but not the derivation
@ -113,13 +113,13 @@ Most Nix commands accept the following command-line options:
for builds of substitutes), possibly killing builds in progress (in for builds of substitutes), possibly killing builds in progress (in
case of parallel or distributed builds). case of parallel or distributed builds).
- `--keep-failed` / `-K`\ - [`--keep-failed`]{#opt-keep-failed} / `-K`\
Specifies that in case of a build failure, the temporary directory Specifies that in case of a build failure, the temporary directory
(usually in `/tmp`) in which the build takes place should not be (usually in `/tmp`) in which the build takes place should not be
deleted. The path of the build directory is printed as an deleted. The path of the build directory is printed as an
informational message. informational message.
- `--fallback`\ - [`--fallback`]{#opt-fallback}\
Whenever Nix attempts to build a derivation for which substitutes Whenever Nix attempts to build a derivation for which substitutes
are known for each output path, but realising the output paths are known for each output path, but realising the output paths
through the substitutes fails, fall back on building the derivation. through the substitutes fails, fall back on building the derivation.
@ -134,12 +134,12 @@ Most Nix commands accept the following command-line options:
failure in obtaining the substitutes to lead to a full build from failure in obtaining the substitutes to lead to a full build from
source (with the related consumption of resources). source (with the related consumption of resources).
- `--readonly-mode`\ - [`--readonly-mode`]{#opt-readonly-mode}\
When this option is used, no attempt is made to open the Nix When this option is used, no attempt is made to open the Nix
database. Most Nix operations do need database access, so those database. Most Nix operations do need database access, so those
operations will fail. operations will fail.
- `--arg` *name* *value*\ - [`--arg`]{#opt-arg} *name* *value*\
This option is accepted by `nix-env`, `nix-instantiate`, This option is accepted by `nix-env`, `nix-instantiate`,
`nix-shell` and `nix-build`. When evaluating Nix expressions, the `nix-shell` and `nix-build`. When evaluating Nix expressions, the
expression evaluator will automatically try to call functions that expression evaluator will automatically try to call functions that
@ -170,13 +170,13 @@ Most Nix commands accept the following command-line options:
since the argument is a Nix string literal, you have to escape the since the argument is a Nix string literal, you have to escape the
quotes.) quotes.)
- `--argstr` *name* *value*\ - [`--argstr`]{#opt-argstr} *name* *value*\
This option is like `--arg`, only the value is not a Nix This option is like `--arg`, only the value is not a Nix
expression but a string. So instead of `--arg system expression but a string. So instead of `--arg system
\"i686-linux\"` (the outer quotes are to keep the shell happy) you \"i686-linux\"` (the outer quotes are to keep the shell happy) you
can say `--argstr system i686-linux`. can say `--argstr system i686-linux`.
- `--attr` / `-A` *attrPath*\ - [`--attr`]{#opt-attr} / `-A` *attrPath*\
Select an attribute from the top-level Nix expression being Select an attribute from the top-level Nix expression being
evaluated. (`nix-env`, `nix-instantiate`, `nix-build` and evaluated. (`nix-env`, `nix-instantiate`, `nix-build` and
`nix-shell` only.) The *attribute path* *attrPath* is a sequence `nix-shell` only.) The *attribute path* *attrPath* is a sequence
@ -191,7 +191,7 @@ Most Nix commands accept the following command-line options:
attribute of the fourth element of the array in the `foo` attribute attribute of the fourth element of the array in the `foo` attribute
of the top-level expression. of the top-level expression.
- `--expr` / `-E`\ - [`--expr`]{#opt-expr} / `-E`\
Interpret the command line arguments as a list of Nix expressions to Interpret the command line arguments as a list of Nix expressions to
be parsed and evaluated, rather than as a list of file names of Nix be parsed and evaluated, rather than as a list of file names of Nix
expressions. (`nix-instantiate`, `nix-build` and `nix-shell` only.) expressions. (`nix-instantiate`, `nix-build` and `nix-shell` only.)
@ -202,17 +202,17 @@ Most Nix commands accept the following command-line options:
use, give your expression to the `nix-shell -p` convenience flag use, give your expression to the `nix-shell -p` convenience flag
instead. instead.
- `-I` *path*\ - [`-I`]{#opt-I} *path*\
Add a path to the Nix expression search path. This option may be Add a path to the Nix expression search path. This option may be
given multiple times. See the `NIX_PATH` environment variable for given multiple times. See the `NIX_PATH` environment variable for
information on the semantics of the Nix search path. Paths added information on the semantics of the Nix search path. Paths added
through `-I` take precedence over `NIX_PATH`. through `-I` take precedence over `NIX_PATH`.
- `--option` *name* *value*\ - [`--option`]{#opt-option} *name* *value*\
Set the Nix configuration option *name* to *value*. This overrides Set the Nix configuration option *name* to *value*. This overrides
settings in the Nix configuration file (see nix.conf5). settings in the Nix configuration file (see nix.conf5).
- `--repair`\ - [`--repair`]{#opt-repair}\
Fix corrupted or missing store paths by redownloading or rebuilding Fix corrupted or missing store paths by redownloading or rebuilding
them. Note that this is slow because it requires computing a them. Note that this is slow because it requires computing a
cryptographic hash of the contents of every path in the closure of cryptographic hash of the contents of every path in the closure of

View file

@ -2,7 +2,7 @@
Derivations can declare some infrequently used optional attributes. Derivations can declare some infrequently used optional attributes.
- `allowedReferences`\ - [`allowedReferences`]{#adv-attr-allowedReferences}\
The optional attribute `allowedReferences` specifies a list of legal The optional attribute `allowedReferences` specifies a list of legal
references (dependencies) of the output of the builder. For example, references (dependencies) of the output of the builder. For example,
@ -17,7 +17,7 @@ Derivations can declare some infrequently used optional attributes.
booting Linux dont have accidental dependencies on other paths in booting Linux dont have accidental dependencies on other paths in
the Nix store. the Nix store.
- `allowedRequisites`\ - [`allowedRequisites`]{#adv-attr-allowedRequisites}\
This attribute is similar to `allowedReferences`, but it specifies This attribute is similar to `allowedReferences`, but it specifies
the legal requisites of the whole closure, so all the dependencies the legal requisites of the whole closure, so all the dependencies
recursively. For example, recursively. For example,
@ -30,7 +30,7 @@ Derivations can declare some infrequently used optional attributes.
runtime dependency than `foobar`, and in addition it enforces that runtime dependency than `foobar`, and in addition it enforces that
`foobar` itself doesn't introduce any other dependency itself. `foobar` itself doesn't introduce any other dependency itself.
- `disallowedReferences`\ - [`disallowedReferences`]{#adv-attr-disallowedReferences}\
The optional attribute `disallowedReferences` specifies a list of The optional attribute `disallowedReferences` specifies a list of
illegal references (dependencies) of the output of the builder. For illegal references (dependencies) of the output of the builder. For
example, example,
@ -42,7 +42,7 @@ Derivations can declare some infrequently used optional attributes.
enforces that the output of a derivation cannot have a direct enforces that the output of a derivation cannot have a direct
runtime dependencies on the derivation `foo`. runtime dependencies on the derivation `foo`.
- `disallowedRequisites`\ - [`disallowedRequisites`]{#adv-attr-disallowedRequisites}\
This attribute is similar to `disallowedReferences`, but it This attribute is similar to `disallowedReferences`, but it
specifies illegal requisites for the whole closure, so all the specifies illegal requisites for the whole closure, so all the
dependencies recursively. For example, dependencies recursively. For example,
@ -55,7 +55,7 @@ Derivations can declare some infrequently used optional attributes.
dependency on `foobar` or any other derivation depending recursively dependency on `foobar` or any other derivation depending recursively
on `foobar`. on `foobar`.
- `exportReferencesGraph`\ - [`exportReferencesGraph`]{#adv-attr-exportReferencesGraph}\
This attribute allows builders access to the references graph of This attribute allows builders access to the references graph of
their inputs. The attribute is a list of inputs in the Nix store their inputs. The attribute is a list of inputs in the Nix store
whose references graph the builder needs to know. The value of whose references graph the builder needs to know. The value of
@ -84,7 +84,7 @@ Derivations can declare some infrequently used optional attributes.
with a Nix store containing the closure of a bootable NixOS with a Nix store containing the closure of a bootable NixOS
configuration). configuration).
- `impureEnvVars`\ - [`impureEnvVars`]{#adv-attr-impureEnvVars}\
This attribute allows you to specify a list of environment variables This attribute allows you to specify a list of environment variables
that should be passed from the environment of the calling user to that should be passed from the environment of the calling user to
the builder. Usually, the environment is cleared completely when the the builder. Usually, the environment is cleared completely when the
@ -112,7 +112,7 @@ Derivations can declare some infrequently used optional attributes.
> environmental variables come from the environment of the > environmental variables come from the environment of the
> `nix-build`. > `nix-build`.
- `outputHash`; `outputHashAlgo`; `outputHashMode`\ - [`outputHash`]{#adv-attr-outputHash}; [`outputHashAlgo`]{#adv-attr-outputHashAlgo}; [`outputHashMode`]{#adv-attr-outputHashMode}\
These attributes declare that the derivation is a so-called These attributes declare that the derivation is a so-called
*fixed-output derivation*, which means that a cryptographic hash of *fixed-output derivation*, which means that a cryptographic hash of
the output is already known in advance. When the build of a the output is already known in advance. When the build of a
@ -208,7 +208,7 @@ Derivations can declare some infrequently used optional attributes.
[`nix-hash` command](../command-ref/nix-hash.md) for information [`nix-hash` command](../command-ref/nix-hash.md) for information
about converting to and from base-32 notation.) about converting to and from base-32 notation.)
- `__contentAddressed` - [`__contentAddressed`]{#adv-attr-__contentAddressed}
If this **experimental** attribute is set to true, then the derivation If this **experimental** attribute is set to true, then the derivation
outputs will be stored in a content-addressed location rather than the outputs will be stored in a content-addressed location rather than the
traditional input-addressed one. traditional input-addressed one.
@ -216,7 +216,7 @@ Derivations can declare some infrequently used optional attributes.
Setting this attribute also requires setting `outputHashMode` and `outputHashAlgo` like for *fixed-output derivations* (see above). Setting this attribute also requires setting `outputHashMode` and `outputHashAlgo` like for *fixed-output derivations* (see above).
- `passAsFile`\ - [`passAsFile`]{#adv-attr-passAsFile}\
A list of names of attributes that should be passed via files rather A list of names of attributes that should be passed via files rather
than environment variables. For example, if you have than environment variables. For example, if you have
@ -234,7 +234,7 @@ Derivations can declare some infrequently used optional attributes.
builder, since most operating systems impose a limit on the size builder, since most operating systems impose a limit on the size
of the environment (typically, a few hundred kilobyte). of the environment (typically, a few hundred kilobyte).
- `preferLocalBuild`\ - [`preferLocalBuild`]{#adv-attr-preferLocalBuild}\
If this attribute is set to `true` and [distributed building is If this attribute is set to `true` and [distributed building is
enabled](../advanced-topics/distributed-builds.md), then, if enabled](../advanced-topics/distributed-builds.md), then, if
possible, the derivation will be built locally instead of forwarded possible, the derivation will be built locally instead of forwarded
@ -242,7 +242,7 @@ Derivations can declare some infrequently used optional attributes.
where the cost of doing a download or remote build would exceed where the cost of doing a download or remote build would exceed
the cost of building locally. the cost of building locally.
- `allowSubstitutes`\ - [`allowSubstitutes`]{#adv-attr-allowSubstitutes}\
If this attribute is set to `false`, then Nix will always build this If this attribute is set to `false`, then Nix will always build this
derivation; it will not try to substitute its outputs. This is derivation; it will not try to substitute its outputs. This is
useful for very trivial derivations (such as `writeText` in Nixpkgs) useful for very trivial derivations (such as `writeText` in Nixpkgs)

View file

@ -14,7 +14,7 @@ Here are the constants built into the Nix expression evaluator:
This allows a Nix expression to fall back gracefully on older Nix This allows a Nix expression to fall back gracefully on older Nix
installations that dont have the desired built-in function. installations that dont have the desired built-in function.
- `builtins.currentSystem`\ - [`builtins.currentSystem`]{#builtins-currentSystem}\
The built-in value `currentSystem` evaluates to the Nix platform The built-in value `currentSystem` evaluates to the Nix platform
identifier for the Nix installation on which the expression is being identifier for the Nix installation on which the expression is being
evaluated, such as `"i686-linux"` or `"x86_64-darwin"`. evaluated, such as `"i686-linux"` or `"x86_64-darwin"`.

View file

@ -4,7 +4,7 @@ The most important built-in function is `derivation`, which is used to
describe a single derivation (a build action). It takes as input a set, describe a single derivation (a build action). It takes as input a set,
the attributes of which specify the inputs of the build. the attributes of which specify the inputs of the build.
- There must be an attribute named `system` whose value must be a - There must be an attribute named [`system`]{#attr-system} whose value must be a
string specifying a Nix system type, such as `"i686-linux"` or string specifying a Nix system type, such as `"i686-linux"` or
`"x86_64-darwin"`. (To figure out your system type, run `nix -vv `"x86_64-darwin"`. (To figure out your system type, run `nix -vv
--version`.) The build can only be performed on a machine and --version`.) The build can only be performed on a machine and

View file

@ -1,48 +1,48 @@
# Glossary # Glossary
- derivation\ - [derivation]{#gloss-derivation}\
A description of a build action. The result of a derivation is a A description of a build action. The result of a derivation is a
store object. Derivations are typically specified in Nix expressions store object. Derivations are typically specified in Nix expressions
using the [`derivation` primitive](expressions/derivations.md). These are using the [`derivation` primitive](expressions/derivations.md). These are
translated into low-level *store derivations* (implicitly by translated into low-level *store derivations* (implicitly by
`nix-env` and `nix-build`, or explicitly by `nix-instantiate`). `nix-env` and `nix-build`, or explicitly by `nix-instantiate`).
- store\ - [store]{#gloss-store}\
The location in the file system where store objects live. Typically The location in the file system where store objects live. Typically
`/nix/store`. `/nix/store`.
- store path\ - [store path]{#gloss-store-path}\
The location in the file system of a store object, i.e., an The location in the file system of a store object, i.e., an
immediate child of the Nix store directory. immediate child of the Nix store directory.
- store object\ - [store object]{#gloss-store-object}\
A file that is an immediate child of the Nix store directory. These A file that is an immediate child of the Nix store directory. These
can be regular files, but also entire directory trees. Store objects can be regular files, but also entire directory trees. Store objects
can be sources (objects copied from outside of the store), can be sources (objects copied from outside of the store),
derivation outputs (objects produced by running a build action), or derivation outputs (objects produced by running a build action), or
derivations (files describing a build action). derivations (files describing a build action).
- substitute\ - [substitute]{#gloss-substitute}\
A substitute is a command invocation stored in the Nix database that A substitute is a command invocation stored in the Nix database that
describes how to build a store object, bypassing the normal build describes how to build a store object, bypassing the normal build
mechanism (i.e., derivations). Typically, the substitute builds the mechanism (i.e., derivations). Typically, the substitute builds the
store object by downloading a pre-built version of the store object store object by downloading a pre-built version of the store object
from some server. from some server.
- purity\ - [purity]{#gloss-purity}\
The assumption that equal Nix derivations when run always produce The assumption that equal Nix derivations when run always produce
the same output. This cannot be guaranteed in general (e.g., a the same output. This cannot be guaranteed in general (e.g., a
builder can rely on external inputs such as the network or the builder can rely on external inputs such as the network or the
system time) but the Nix model assumes it. system time) but the Nix model assumes it.
- Nix expression\ - [Nix expression]{#gloss-nix-expression}\
A high-level description of software packages and compositions A high-level description of software packages and compositions
thereof. Deploying software using Nix entails writing Nix thereof. Deploying software using Nix entails writing Nix
expressions for your packages. Nix expressions are translated to expressions for your packages. Nix expressions are translated to
derivations that are stored in the Nix store. These derivations can derivations that are stored in the Nix store. These derivations can
then be built. then be built.
- reference\ - [reference]{#gloss-reference}\
A store path `P` is said to have a reference to a store path `Q` if A store path `P` is said to have a reference to a store path `Q` if
the store object at `P` contains the path `Q` somewhere. The the store object at `P` contains the path `Q` somewhere. The
*references* of a store path are the set of store paths to which it *references* of a store path are the set of store paths to which it
@ -52,11 +52,11 @@
output paths), whereas an output path only references other output output paths), whereas an output path only references other output
paths. paths.
- reachable\ - [reachable]{#gloss-reachable}\
A store path `Q` is reachable from another store path `P` if `Q` A store path `Q` is reachable from another store path `P` if `Q`
is in the *closure* of the *references* relation. is in the *closure* of the *references* relation.
- closure\ - [closure]{#gloss-closure}\
The closure of a store path is the set of store paths that are The closure of a store path is the set of store paths that are
directly or indirectly “reachable” from that store path; that is, directly or indirectly “reachable” from that store path; that is,
its the closure of the path under the *references* relation. For its the closure of the path under the *references* relation. For
@ -71,34 +71,34 @@
to path `Q`, then `Q` is in the closure of `P`. Further, if `Q` to path `Q`, then `Q` is in the closure of `P`. Further, if `Q`
references `R` then `R` is also in the closure of `P`. references `R` then `R` is also in the closure of `P`.
- output path\ - [output path]{#gloss-output-path}\
A store path produced by a derivation. A store path produced by a derivation.
- deriver\ - [deriver]{#gloss-deriver}\
The deriver of an *output path* is the store The deriver of an *output path* is the store
derivation that built it. derivation that built it.
- validity\ - [validity]{#gloss-validity}\
A store path is considered *valid* if it exists in the file system, A store path is considered *valid* if it exists in the file system,
is listed in the Nix database as being valid, and if all paths in is listed in the Nix database as being valid, and if all paths in
its closure are also valid. its closure are also valid.
- user environment\ - [user environment]{#gloss-user-env}\
An automatically generated store object that consists of a set of An automatically generated store object that consists of a set of
symlinks to “active” applications, i.e., other store paths. These symlinks to “active” applications, i.e., other store paths. These
are generated automatically by are generated automatically by
[`nix-env`](command-ref/nix-env.md). See *profiles*. [`nix-env`](command-ref/nix-env.md). See *profiles*.
- profile\ - [profile]{#gloss-profile}\
A symlink to the current *user environment* of a user, e.g., A symlink to the current *user environment* of a user, e.g.,
`/nix/var/nix/profiles/default`. `/nix/var/nix/profiles/default`.
- NAR\ - [NAR]{#gloss-nar}\
A *N*ix *AR*chive. This is a serialisation of a path in the Nix A *N*ix *AR*chive. This is a serialisation of a path in the Nix
store. It can contain regular files, directories and symbolic store. It can contain regular files, directories and symbolic
links. NARs are generated and unpacked using `nix-store --dump` links. NARs are generated and unpacked using `nix-store --dump`
and `nix-store --restore`. and `nix-store --restore`.
- `∅` \ - [`∅`]{#gloss-emtpy-set}\
The empty set symbol. In the context of profile history, this denotes a package is not present in a particular version of the profile. The empty set symbol. In the context of profile history, this denotes a package is not present in a particular version of the profile.
- `ε` \ - [`ε`]{#gloss-epsilon}\
The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute. The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute.

View file

@ -186,7 +186,8 @@ and `/etc/zshrc` which you may remove.
> read-only root will prevent you from manually deleting the empty `/nix` > read-only root will prevent you from manually deleting the empty `/nix`
> mountpoint. > mountpoint.
# macOS Installation <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a> # macOS Installation
[]{#sect-macos-installation-change-store-prefix}[]{#sect-macos-installation-encrypted-volume}[]{#sect-macos-installation-symlink}[]{#sect-macos-installation-recommended-notes}
<!-- Note: anchors above to catch permalinks to old explanations --> <!-- Note: anchors above to catch permalinks to old explanations -->
We believe we have ironed out how to cleanly support the read-only root We believe we have ironed out how to cleanly support the read-only root

View file

@ -0,0 +1,47 @@
# Release 2.9 (2022-05-30)
* Running Nix with the new `--debugger` flag will cause it to start a
repl session if an exception is thrown during evaluation, or if
`builtins.break` is called. From there you can inspect the values
of variables and evaluate Nix expressions. In debug mode, the
following new repl commands are available:
```
:env Show env stack
:bt Show trace stack
:st Show current trace
:st <idx> Change to another trace in the stack
:c Go until end of program, exception, or builtins.break().
:s Go one step
```
Read more about the debugger
[here](https://www.zknotes.com/note/5970).
* Nix now provides better integration with zsh's `run-help`
feature. It is now included in the Nix installation in the form of
an autoloadable shell function, `run-help-nix`. It picks up Nix
subcommands from the currently typed in command and directs the user
to the associated man pages.
* `nix repl` has a new build-and-link (`:bl`) command that builds a
derivation while creating GC root symlinks.
* The path produced by `builtins.toFile` is now allowed to be imported
or read even with restricted evaluation. Note that this will not
work with a read-only store.
* `nix build` has a new `--print-out-paths` flag to print the
resulting output paths. This matches the default behaviour of
`nix-build`.
* You can now specify which outputs of a derivation `nix` should
operate on using the syntax `installable^outputs`,
e.g. `nixpkgs#glibc^dev,static` or `nixpkgs#glibc^*`. By default,
`nix` will use the outputs specified by the derivation's
`meta.outputsToInstall` attribute if it exists, or all outputs
otherwise.
* `builtins.fetchTree` (and flake inputs) can now be used to fetch
plain files over the `http(s)` and `file` protocols in addition to
directory tarballs.

View file

@ -1,29 +1,4 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
* Nix now provides better integration with zsh's run-help feature. It is now
included in the Nix installation in the form of an autoloadable shell
function, run-help-nix. It picks up Nix subcommands from the currently typed
in command and directs the user to the associated man pages.
* `nix repl` has a new build-'n-link (`:bl`) command that builds a derivation
while creating GC root symlinks.
* The path produced by `builtins.toFile` is now allowed to be imported or read
even with restricted evaluation. Note that this will not work with a
read-only store.
* `nix build` has a new `--print-out-paths` flag to print the resulting output paths.
This matches the default behaviour of `nix-build`.
* Nix can now be built with LTO by passing `--enable-lto` to `configure`. * Nix can now be built with LTO by passing `--enable-lto` to `configure`.
LTO is currently only supported when building with GCC. LTO is currently only supported when building with GCC.
* You can now specify which outputs of a derivation `nix` should
operate on using the syntax `installable^outputs`,
e.g. `nixpkgs#glibc^dev,static` or `nixpkgs#glibc^*`. By default,
`nix` will use the outputs specified by the derivation's
`meta.outputsToInstall` attribute if it exists, or all outputs
otherwise.
Selecting derivation outputs using the attribute selection syntax
(e.g. `nixpkgs#glibc.dev`) no longer works.

View file

@ -26,9 +26,10 @@
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "owner": "NixOS",
"ref": "nixos-21.05-small", "ref": "nixos-21.05-small",
"type": "indirect" "repo": "nixpkgs",
"type": "github"
} }
}, },
"nixpkgs-regression": { "nixpkgs-regression": {
@ -41,9 +42,10 @@
"type": "github" "type": "github"
}, },
"original": { "original": {
"id": "nixpkgs", "owner": "NixOS",
"repo": "nixpkgs",
"rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2", "rev": "215d4d0fd80ca5163643b03a33fde804a29cc1e2",
"type": "indirect" "type": "github"
} }
}, },
"root": { "root": {

View file

@ -1,8 +1,8 @@
{ {
description = "The purely functional package manager"; description = "The purely functional package manager";
inputs.nixpkgs.url = "nixpkgs/nixos-21.05-small"; inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-21.05-small";
inputs.nixpkgs-regression.url = "nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2"; inputs.nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2";
inputs.lowdown-src = { url = "github:kristapsdz/lowdown"; flake = false; }; inputs.lowdown-src = { url = "github:kristapsdz/lowdown"; flake = false; };
outputs = { self, nixpkgs, nixpkgs-regression, lowdown-src }: outputs = { self, nixpkgs, nixpkgs-regression, lowdown-src }:
@ -102,7 +102,7 @@
# Tests # Tests
buildPackages.git buildPackages.git
buildPackages.mercurial # FIXME: remove? only needed for tests buildPackages.mercurial # FIXME: remove? only needed for tests
buildPackages.jq buildPackages.jq # Also for custom mdBook preprocessor.
] ]
++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)]; ++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)];
@ -135,11 +135,6 @@
})) }))
nlohmann_json nlohmann_json
]; ];
perlDeps =
[ perl
perlPackages.DBDSQLite
];
}; };
installScriptFor = systems: installScriptFor = systems:
@ -353,7 +348,7 @@
strictDeps = true; strictDeps = true;
passthru.perl-bindings = with final; currentStdenv.mkDerivation { passthru.perl-bindings = with final; perl.pkgs.toPerlModule (currentStdenv.mkDerivation {
name = "nix-perl-${version}"; name = "nix-perl-${version}";
src = self; src = self;
@ -383,8 +378,9 @@
enableParallelBuilding = true; enableParallelBuilding = true;
postUnpack = "sourceRoot=$sourceRoot/perl"; postUnpack = "sourceRoot=$sourceRoot/perl";
}; });
meta.platforms = systems;
}; };
lowdown-nix = with final; currentStdenv.mkDerivation rec { lowdown-nix = with final; currentStdenv.mkDerivation rec {
@ -673,7 +669,7 @@
outputs = [ "out" "dev" "doc" ]; outputs = [ "out" "dev" "doc" ];
nativeBuildInputs = nativeBuildDeps; nativeBuildInputs = nativeBuildDeps;
buildInputs = buildDeps ++ propagatedDeps ++ awsDeps ++ perlDeps; buildInputs = buildDeps ++ propagatedDeps ++ awsDeps;
inherit configureFlags; inherit configureFlags;

View file

@ -86,6 +86,11 @@ ref<Store> CopyCommand::getDstStore()
EvalCommand::EvalCommand() EvalCommand::EvalCommand()
{ {
addFlag({
.longName = "debugger",
.description = "start an interactive environment if evaluation fails",
.handler = {&startReplOnEvalErrors, true},
});
} }
EvalCommand::~EvalCommand() EvalCommand::~EvalCommand()
@ -103,7 +108,7 @@ ref<Store> EvalCommand::getEvalStore()
ref<EvalState> EvalCommand::getEvalState() ref<EvalState> EvalCommand::getEvalState()
{ {
if (!evalState) if (!evalState) {
evalState = evalState =
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
std::allocate_shared<EvalState>(traceable_allocator<EvalState>(), std::allocate_shared<EvalState>(traceable_allocator<EvalState>(),
@ -113,6 +118,11 @@ ref<EvalState> EvalCommand::getEvalState()
searchPath, getEvalStore(), getStore()) searchPath, getEvalStore(), getStore())
#endif #endif
; ;
if (startReplOnEvalErrors) {
evalState->debugRepl = &runRepl;
};
}
return ref<EvalState>(evalState); return ref<EvalState>(evalState);
} }

View file

@ -57,6 +57,8 @@ struct CopyCommand : virtual StoreCommand
struct EvalCommand : virtual StoreCommand, MixEvalArgs struct EvalCommand : virtual StoreCommand, MixEvalArgs
{ {
bool startReplOnEvalErrors = false;
EvalCommand(); EvalCommand();
~EvalCommand(); ~EvalCommand();
@ -270,4 +272,8 @@ void printClosureDiff(
const StorePath & afterPath, const StorePath & afterPath,
std::string_view indent); std::string_view indent);
void runRepl(
ref<EvalState> evalState,
const ValMap & extraEnv);
} }

View file

@ -623,7 +623,14 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
std::set<std::string> outputsToInstall; std::set<std::string> outputsToInstall;
std::optional<NixInt> priority; std::optional<NixInt> priority;
if (auto aMeta = attr->maybeGetAttr(state->sMeta)) { if (auto aOutputSpecified = attr->maybeGetAttr(state->sOutputSpecified)) {
if (aOutputSpecified->getBool()) {
if (auto aOutputName = attr->maybeGetAttr("outputName"))
outputsToInstall = { aOutputName->getString() };
}
}
else if (auto aMeta = attr->maybeGetAttr(state->sMeta)) {
if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall")) if (auto aOutputsToInstall = aMeta->maybeGetAttr("outputsToInstall"))
for (auto & s : aOutputsToInstall->getListOfStrings()) for (auto & s : aOutputsToInstall->getListOfStrings())
outputsToInstall.insert(s); outputsToInstall.insert(s);

View file

@ -6,9 +6,9 @@ libcmd_DIR := $(d)
libcmd_SOURCES := $(wildcard $(d)/*.cc) libcmd_SOURCES := $(wildcard $(d)/*.cc)
libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -I src/nix
libcmd_LDFLAGS += $(LOWDOWN_LIBS) -pthread libcmd_LDFLAGS = $(EDITLINE_LIBS) -llowdown -pthread
libcmd_LIBS = libstore libutil libexpr libmain libfetchers libcmd_LIBS = libstore libutil libexpr libmain libfetchers

View file

@ -48,20 +48,22 @@ struct NixRepl
#endif #endif
{ {
std::string curDir; std::string curDir;
std::unique_ptr<EvalState> state; ref<EvalState> state;
Bindings * autoArgs; Bindings * autoArgs;
size_t debugTraceIndex;
Strings loadedFiles; Strings loadedFiles;
const static int envSize = 32768; const static int envSize = 32768;
StaticEnv staticEnv; std::shared_ptr<StaticEnv> staticEnv;
Env * env; Env * env;
int displ; int displ;
StringSet varNames; StringSet varNames;
const Path historyFile; const Path historyFile;
NixRepl(const Strings & searchPath, nix::ref<Store> store); NixRepl(ref<EvalState> state);
~NixRepl(); ~NixRepl();
void mainLoop(const std::vector<std::string> & files); void mainLoop(const std::vector<std::string> & files);
StringSet completePrefix(const std::string & prefix); StringSet completePrefix(const std::string & prefix);
@ -71,11 +73,13 @@ struct NixRepl
void loadFile(const Path & path); void loadFile(const Path & path);
void loadFlake(const std::string & flakeRef); void loadFlake(const std::string & flakeRef);
void initEnv(); void initEnv();
void loadFiles();
void reloadFiles(); void reloadFiles();
void addAttrsToScope(Value & attrs); void addAttrsToScope(Value & attrs);
void addVarToScope(const Symbol name, Value & v); void addVarToScope(const Symbol name, Value & v);
Expr * parseString(std::string s); Expr * parseString(std::string s);
void evalString(std::string s, Value & v); void evalString(std::string s, Value & v);
void loadDebugTraceEnv(DebugTrace & dt);
typedef std::set<Value *> ValuesSeen; typedef std::set<Value *> ValuesSeen;
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth); std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
@ -92,9 +96,10 @@ std::string removeWhitespace(std::string s)
} }
NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store) NixRepl::NixRepl(ref<EvalState> state)
: state(std::make_unique<EvalState>(searchPath, store)) : state(state)
, staticEnv(false, &state->staticBaseEnv) , debugTraceIndex(0)
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
, historyFile(getDataDir() + "/nix/repl-history") , historyFile(getDataDir() + "/nix/repl-history")
{ {
curDir = absPath("."); curDir = absPath(".");
@ -198,15 +203,42 @@ namespace {
} }
} }
static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positions, const DebugTrace & dt)
{
if (dt.isError)
out << ANSI_RED "error: " << ANSI_NORMAL;
out << dt.hint.str() << "\n";
// prefer direct pos, but if noPos then try the expr.
auto pos = *dt.pos
? *dt.pos
: positions[dt.expr.getPos() ? dt.expr.getPos() : noPos];
if (pos) {
printAtPos(pos, out);
auto loc = getCodeLines(pos);
if (loc.has_value()) {
out << "\n";
printCodeLines(out, "", pos, *loc);
out << "\n";
}
}
return out;
}
void NixRepl::mainLoop(const std::vector<std::string> & files) void NixRepl::mainLoop(const std::vector<std::string> & files)
{ {
std::string error = ANSI_RED "error:" ANSI_NORMAL " "; std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n"); notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
if (!files.empty()) {
for (auto & i : files) for (auto & i : files)
loadedFiles.push_back(i); loadedFiles.push_back(i);
}
reloadFiles(); loadFiles();
if (!loadedFiles.empty()) notice(""); if (!loadedFiles.empty()) notice("");
// Allow nix-repl specific settings in .inputrc // Allow nix-repl specific settings in .inputrc
@ -227,9 +259,12 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
while (true) { while (true) {
// 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 (!getLine(input, input.empty() ? "nix-repl> " : " ")) if (!getLine(input, input.empty() ? "nix-repl> " : " ")) {
// ctrl-D should exit the debugger.
state->debugStop = false;
state->debugQuit = true;
break; break;
}
try { try {
if (!removeWhitespace(input).empty() && !processLine(input)) return; if (!removeWhitespace(input).empty() && !processLine(input)) return;
} catch (ParseError & e) { } catch (ParseError & e) {
@ -240,6 +275,14 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
} else { } else {
printMsg(lvlError, e.msg()); printMsg(lvlError, e.msg());
} }
} catch (EvalError & e) {
// in debugger mode, an EvalError should trigger another repl session.
// when that session returns the exception will land here. No need to show it again;
// show the error for this repl session instead.
if (state->debugRepl && !state->debugTraces.empty())
showDebugTrace(std::cout, state->positions, state->debugTraces.front());
else
printMsg(lvlError, e.msg());
} catch (Error & e) { } catch (Error & e) {
printMsg(lvlError, e.msg()); printMsg(lvlError, e.msg());
} catch (Interrupted & e) { } catch (Interrupted & e) {
@ -394,6 +437,19 @@ StorePath NixRepl::getDerivationPath(Value & v) {
return *drvPath; return *drvPath;
} }
void NixRepl::loadDebugTraceEnv(DebugTrace & dt)
{
initEnv();
auto se = state->getStaticEnv(dt.expr);
if (se) {
auto vm = mapStaticEnvBindings(state->symbols, *se.get(), dt.env);
// add staticenv vars.
for (auto & [name, value] : *(vm.get()))
addVarToScope(state->symbols.create(name), *value);
}
}
bool NixRepl::processLine(std::string line) bool NixRepl::processLine(std::string line)
{ {
@ -429,12 +485,72 @@ bool NixRepl::processLine(std::string line)
<< " :p <expr> Evaluate and print expression recursively\n" << " :p <expr> Evaluate and print expression recursively\n"
<< " :q Exit nix-repl\n" << " :q Exit nix-repl\n"
<< " :r Reload all files\n" << " :r Reload all files\n"
<< " :s <expr> Build dependencies of derivation, then start nix-shell\n" << " :sh <expr> Build dependencies of derivation, then start nix-shell\n"
<< " :t <expr> Describe result of evaluation\n" << " :t <expr> Describe result of evaluation\n"
<< " :u <expr> Build derivation, then start nix-shell\n" << " :u <expr> Build derivation, then start nix-shell\n"
<< " :doc <expr> Show documentation of a builtin function\n" << " :doc <expr> Show documentation of a builtin function\n"
<< " :log <expr> Show logs for a derivation\n" << " :log <expr> Show logs for a derivation\n"
<< " :st [bool] Enable, disable or toggle showing traces for errors\n"; << " :te [bool] Enable, disable or toggle showing traces for errors\n"
;
if (state->debugRepl) {
std::cout
<< "\n"
<< " Debug mode commands\n"
<< " :env Show env stack\n"
<< " :bt Show trace stack\n"
<< " :st Show current trace\n"
<< " :st <idx> Change to another trace in the stack\n"
<< " :c Go until end of program, exception, or builtins.break\n"
<< " :s Go one step\n"
;
}
}
else if (state->debugRepl && (command == ":bt" || command == ":backtrace")) {
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, state->positions, i);
}
}
else if (state->debugRepl && (command == ":env")) {
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
if (idx == debugTraceIndex) {
printEnvBindings(*state, i.expr, i.env);
break;
}
}
}
else if (state->debugRepl && (command == ":st")) {
try {
// change the DebugTrace index.
debugTraceIndex = stoi(arg);
} catch (...) { }
for (const auto & [idx, i] : enumerate(state->debugTraces)) {
if (idx == debugTraceIndex) {
std::cout << "\n" << ANSI_BLUE << idx << ANSI_NORMAL << ": ";
showDebugTrace(std::cout, state->positions, i);
std::cout << std::endl;
printEnvBindings(*state, i.expr, i.env);
loadDebugTraceEnv(i);
break;
}
}
}
else if (state->debugRepl && (command == ":s" || command == ":step")) {
// set flag to stop at next DebugTrace; exit repl.
state->debugStop = true;
return false;
}
else if (state->debugRepl && (command == ":c" || command == ":continue")) {
// set flag to run to next breakpoint or end of program; exit repl.
state->debugStop = false;
return false;
} }
else if (command == ":a" || command == ":add") { else if (command == ":a" || command == ":add") {
@ -506,7 +622,7 @@ bool NixRepl::processLine(std::string line)
runNix("nix-shell", {state->store->printStorePath(drvPath)}); runNix("nix-shell", {state->store->printStorePath(drvPath)});
} }
else if (command == ":b" || command == ":bl" || command == ":i" || command == ":s" || command == ":log") { else if (command == ":b" || command == ":bl" || command == ":i" || command == ":sh" || command == ":log") {
Value v; Value v;
evalString(arg, v); evalString(arg, v);
StorePath drvPath = getDerivationPath(v); StorePath drvPath = getDerivationPath(v);
@ -567,8 +683,11 @@ bool NixRepl::processLine(std::string line)
printValue(std::cout, v, 1000000000) << std::endl; printValue(std::cout, v, 1000000000) << std::endl;
} }
else if (command == ":q" || command == ":quit") else if (command == ":q" || command == ":quit") {
state->debugStop = false;
state->debugQuit = true;
return false; return false;
}
else if (command == ":doc") { else if (command == ":doc") {
Value v; Value v;
@ -593,7 +712,7 @@ bool NixRepl::processLine(std::string line)
throw Error("value does not have documentation"); throw Error("value does not have documentation");
} }
else if (command == ":st" || command == ":show-trace") { else if (command == ":te" || command == ":trace-enable") {
if (arg == "false" || (arg == "" && loggerSettings.showTrace)) { if (arg == "false" || (arg == "" && loggerSettings.showTrace)) {
std::cout << "not showing error traces\n"; std::cout << "not showing error traces\n";
loggerSettings.showTrace = false; loggerSettings.showTrace = false;
@ -669,10 +788,10 @@ void NixRepl::initEnv()
env = &state->allocEnv(envSize); env = &state->allocEnv(envSize);
env->up = &state->baseEnv; env->up = &state->baseEnv;
displ = 0; displ = 0;
staticEnv.vars.clear(); staticEnv->vars.clear();
varNames.clear(); varNames.clear();
for (auto & i : state->staticBaseEnv.vars) for (auto & i : state->staticBaseEnv->vars)
varNames.emplace(state->symbols[i.first]); varNames.emplace(state->symbols[i.first]);
} }
@ -681,6 +800,12 @@ void NixRepl::reloadFiles()
{ {
initEnv(); initEnv();
loadFiles();
}
void NixRepl::loadFiles()
{
Strings old = loadedFiles; Strings old = loadedFiles;
loadedFiles.clear(); loadedFiles.clear();
@ -701,12 +826,12 @@ void NixRepl::addAttrsToScope(Value & attrs)
throw Error("environment full; cannot add more variables"); throw Error("environment full; cannot add more variables");
for (auto & i : *attrs.attrs) { for (auto & i : *attrs.attrs) {
staticEnv.vars.emplace_back(i.name, displ); staticEnv->vars.emplace_back(i.name, displ);
env->values[displ++] = i.value; env->values[displ++] = i.value;
varNames.emplace(state->symbols[i.name]); varNames.emplace(state->symbols[i.name]);
} }
staticEnv.sort(); staticEnv->sort();
staticEnv.deduplicate(); staticEnv->deduplicate();
notice("Added %1% variables.", attrs.attrs->size()); notice("Added %1% variables.", attrs.attrs->size());
} }
@ -715,10 +840,10 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
{ {
if (displ >= envSize) if (displ >= envSize)
throw Error("environment full; cannot add more variables"); throw Error("environment full; cannot add more variables");
if (auto oldVar = staticEnv.find(name); oldVar != staticEnv.vars.end()) if (auto oldVar = staticEnv->find(name); oldVar != staticEnv->vars.end())
staticEnv.vars.erase(oldVar); staticEnv->vars.erase(oldVar);
staticEnv.vars.emplace_back(name, displ); staticEnv->vars.emplace_back(name, displ);
staticEnv.sort(); staticEnv->sort();
env->values[displ++] = &v; env->values[displ++] = &v;
varNames.emplace(state->symbols[name]); varNames.emplace(state->symbols[name]);
} }
@ -886,6 +1011,21 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
return str; return str;
} }
void runRepl(
ref<EvalState>evalState,
const ValMap & extraEnv)
{
auto repl = std::make_unique<NixRepl>(evalState);
repl->initEnv();
// add 'extra' vars.
for (auto & [name, value] : extraEnv)
repl->addVarToScope(repl->state->symbols.create(name), *value);
repl->mainLoop({});
}
struct CmdRepl : StoreCommand, MixEvalArgs struct CmdRepl : StoreCommand, MixEvalArgs
{ {
std::vector<std::string> files; std::vector<std::string> files;
@ -914,8 +1054,12 @@ struct CmdRepl : StoreCommand, MixEvalArgs
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
evalSettings.pureEval = false; evalSettings.pureEval = false;
auto repl = std::make_unique<NixRepl>(searchPath, openStore());
auto evalState = make_ref<EvalState>(searchPath, store);
auto repl = std::make_unique<NixRepl>(evalState);
repl->autoArgs = getAutoArgs(*repl->state); repl->autoArgs = getAutoArgs(*repl->state);
repl->initEnv();
repl->mainLoop(files); repl->mainLoop(files);
} }
}; };

View file

@ -576,14 +576,14 @@ std::string AttrCursor::getString()
debug("using cached string attribute '%s'", getAttrPathStr()); debug("using cached string attribute '%s'", getAttrPathStr());
return s->first; return s->first;
} else } else
throw TypeError("'%s' is not a string", getAttrPathStr()); root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr()));
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nString && v.type() != nPath) if (v.type() != nString && v.type() != nPath)
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())); root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())));
return v.type() == nString ? v.string.s : v.path; return v.type() == nString ? v.string.s : v.path;
} }
@ -607,7 +607,7 @@ string_t AttrCursor::getStringWithContext()
return *s; return *s;
} }
} else } else
throw TypeError("'%s' is not a string", getAttrPathStr()); root->state.debugThrowLastTrace(TypeError("'%s' is not a string", getAttrPathStr()));
} }
} }
@ -618,7 +618,7 @@ string_t AttrCursor::getStringWithContext()
else if (v.type() == nPath) else if (v.type() == nPath)
return {v.path, {}}; return {v.path, {}};
else else
throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())); root->state.debugThrowLastTrace(TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type())));
} }
bool AttrCursor::getBool() bool AttrCursor::getBool()
@ -631,14 +631,14 @@ bool AttrCursor::getBool()
debug("using cached Boolean attribute '%s'", getAttrPathStr()); debug("using cached Boolean attribute '%s'", getAttrPathStr());
return *b; return *b;
} else } else
throw TypeError("'%s' is not a Boolean", getAttrPathStr()); root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr()));
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nBool) if (v.type() != nBool)
throw TypeError("'%s' is not a Boolean", getAttrPathStr()); root->state.debugThrowLastTrace(TypeError("'%s' is not a Boolean", getAttrPathStr()));
return v.boolean; return v.boolean;
} }
@ -708,14 +708,14 @@ std::vector<Symbol> AttrCursor::getAttrs()
debug("using cached attrset attribute '%s'", getAttrPathStr()); debug("using cached attrset attribute '%s'", getAttrPathStr());
return *attrs; return *attrs;
} else } else
throw TypeError("'%s' is not an attribute set", getAttrPathStr()); root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr()));
} }
} }
auto & v = forceValue(); auto & v = forceValue();
if (v.type() != nAttrs) if (v.type() != nAttrs)
throw TypeError("'%s' is not an attribute set", getAttrPathStr()); root->state.debugThrowLastTrace(TypeError("'%s' is not an attribute set", getAttrPathStr()));
std::vector<Symbol> attrs; std::vector<Symbol> attrs;
for (auto & attr : *getValue().attrs) for (auto & attr : *getValue().attrs)

View file

@ -4,7 +4,6 @@
namespace nix { namespace nix {
/* Note: Various places expect the allocated memory to be zeroed. */ /* Note: Various places expect the allocated memory to be zeroed. */
[[gnu::always_inline]] [[gnu::always_inline]]
inline void * allocBytes(size_t n) inline void * allocBytes(size_t n)

View file

@ -18,6 +18,7 @@
#include <sys/resource.h> #include <sys/resource.h>
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <functional>
#include <sys/resource.h> #include <sys/resource.h>
@ -36,7 +37,6 @@
namespace nix { namespace nix {
static char * allocString(size_t size) static char * allocString(size_t size)
{ {
char * t; char * t;
@ -459,10 +459,14 @@ EvalState::EvalState(
, sKey(symbols.create("key")) , sKey(symbols.create("key"))
, sPath(symbols.create("path")) , sPath(symbols.create("path"))
, sPrefix(symbols.create("prefix")) , sPrefix(symbols.create("prefix"))
, sOutputSpecified(symbols.create("outputSpecified"))
, repair(NoRepair) , repair(NoRepair)
, emptyBindings(0) , emptyBindings(0)
, store(store) , store(store)
, buildStore(buildStore ? buildStore : store) , buildStore(buildStore ? buildStore : store)
, debugRepl(0)
, debugStop(false)
, debugQuit(false)
, regexCache(makeRegexCache()) , regexCache(makeRegexCache())
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)) , valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
@ -472,7 +476,7 @@ EvalState::EvalState(
, env1AllocCache(std::make_shared<void *>(nullptr)) , env1AllocCache(std::make_shared<void *>(nullptr))
#endif #endif
, baseEnv(allocEnv(128)) , baseEnv(allocEnv(128))
, staticBaseEnv(false, 0) , staticBaseEnv{std::make_shared<StaticEnv>(false, nullptr)}
{ {
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0"; countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
@ -638,7 +642,7 @@ Value * EvalState::addConstant(const std::string & name, Value & v)
void EvalState::addConstant(const std::string & name, Value * v) void EvalState::addConstant(const std::string & name, Value * v)
{ {
staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl); staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v; baseEnv.values[baseEnvDispl++] = v;
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name; auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v)); baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
@ -663,7 +667,7 @@ Value * EvalState::addPrimOp(const std::string & name,
Value * v = allocValue(); Value * v = allocValue();
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 }); v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 });
staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl); staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v; baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(sym, v)); baseEnv.values[0]->attrs->push_back(Attr(sym, v));
return v; return v;
@ -689,7 +693,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
Value * v = allocValue(); Value * v = allocValue();
v->mkPrimOp(new PrimOp(primOp)); v->mkPrimOp(new PrimOp(primOp));
staticBaseEnv.vars.emplace_back(envName, baseEnvDispl); staticBaseEnv->vars.emplace_back(envName, baseEnvDispl);
baseEnv.values[baseEnvDispl++] = v; baseEnv.values[baseEnvDispl++] = v;
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v)); baseEnv.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v));
return v; return v;
@ -719,128 +723,284 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
} }
// just for the current level of StaticEnv, not the whole chain.
void printStaticEnvBindings(const SymbolTable & st, const StaticEnv & se)
{
std::cout << ANSI_MAGENTA;
for (auto & i : se.vars)
std::cout << st[i.first] << " ";
std::cout << ANSI_NORMAL;
std::cout << std::endl;
}
// just for the current level of Env, not the whole chain.
void printWithBindings(const SymbolTable & st, const Env & env)
{
if (env.type == Env::HasWithAttrs) {
std::cout << "with: ";
std::cout << ANSI_MAGENTA;
Bindings::iterator j = env.values[0]->attrs->begin();
while (j != env.values[0]->attrs->end()) {
std::cout << st[j->name] << " ";
++j;
}
std::cout << ANSI_NORMAL;
std::cout << std::endl;
}
}
void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, int lvl)
{
std::cout << "Env level " << lvl << std::endl;
if (se.up && env.up) {
std::cout << "static: ";
printStaticEnvBindings(st, se);
printWithBindings(st, env);
std::cout << std::endl;
printEnvBindings(st, *se.up, *env.up, ++lvl);
} else {
std::cout << ANSI_MAGENTA;
// for the top level, don't print the double underscore ones;
// they are in builtins.
for (auto & i : se.vars)
if (!hasPrefix(st[i.first], "__"))
std::cout << st[i.first] << " ";
std::cout << ANSI_NORMAL;
std::cout << std::endl;
printWithBindings(st, env); // probably nothing there for the top level.
std::cout << std::endl;
}
}
void printEnvBindings(const EvalState &es, const Expr & expr, const Env & env)
{
// just print the names for now
auto se = es.getStaticEnv(expr);
if (se)
printEnvBindings(es.symbols, *se, env, 0);
}
void mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env, ValMap & vm)
{
// add bindings for the next level up first, so that the bindings for this level
// override the higher levels.
// The top level bindings (builtins) are skipped since they are added for us by initEnv()
if (env.up && se.up) {
mapStaticEnvBindings(st, *se.up, *env.up, vm);
if (env.type == Env::HasWithAttrs) {
// add 'with' bindings.
Bindings::iterator j = env.values[0]->attrs->begin();
while (j != env.values[0]->attrs->end()) {
vm[st[j->name]] = j->value;
++j;
}
} else {
// iterate through staticenv bindings and add them.
for (auto & i : se.vars)
vm[st[i.first]] = env.values[i.second];
}
}
}
std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env)
{
auto vm = std::make_unique<ValMap>();
mapStaticEnvBindings(st, se, env, *vm);
return vm;
}
void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & expr)
{
// double check we've got the debugRepl function pointer.
if (!debugRepl)
return;
auto dts =
error && expr.getPos()
? std::make_unique<DebugTraceStacker>(
*this,
DebugTrace {
.pos = error->info().errPos ? *error->info().errPos : positions[expr.getPos()],
.expr = expr,
.env = env,
.hint = error->info().msg,
.isError = true
})
: nullptr;
if (error)
printError("%s\n\n" ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL, error->what());
auto se = getStaticEnv(expr);
if (se) {
auto vm = mapStaticEnvBindings(symbols, *se.get(), env);
(debugRepl)(ref<EvalState>(shared_from_this()), *vm);
}
}
/* Every "format" object (even temporary) takes up a few hundred bytes /* Every "format" object (even temporary) takes up a few hundred bytes
of stack space, which is a real killer in the recursive of stack space, which is a real killer in the recursive
evaluator. So here are some helper functions for throwing evaluator. So here are some helper functions for throwing
exceptions. */ exceptions. */
void EvalState::throwEvalError(const PosIdx pos, const char * s, Env & env, Expr & expr)
void EvalState::throwEvalError(const PosIdx pos, const char * s) const
{ {
throw EvalError({ debugThrow(EvalError({
.msg = hintfmt(s), .msg = hintfmt(s),
.errPos = positions[pos] .errPos = positions[pos]
}); }), env, expr);
} }
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v) const void EvalState::throwEvalError(const PosIdx pos, const char * s)
{ {
throw TypeError({ debugThrowLastTrace(EvalError({
.msg = hintfmt(s, showType(v)), .msg = hintfmt(s),
.errPos = positions[pos] .errPos = positions[pos]
}); }));
} }
void EvalState::throwEvalError(const char * s, const std::string & s2) const void EvalState::throwEvalError(const char * s, const std::string & s2)
{ {
throw EvalError(s, s2); debugThrowLastTrace(EvalError(s, s2));
} }
void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const std::string & s2) const const std::string & s2, Env & env, Expr & expr)
{ {
throw EvalError(ErrorInfo { debugThrow(EvalError(ErrorInfo{
.msg = hintfmt(s, s2), .msg = hintfmt(s, s2),
.errPos = positions[pos], .errPos = positions[pos],
.suggestions = suggestions, .suggestions = suggestions,
}); }), env, expr);
} }
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2)
{ {
throw EvalError(ErrorInfo { debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2), .msg = hintfmt(s, s2),
.errPos = positions[pos] .errPos = positions[pos]
}); }));
} }
void EvalState::throwEvalError(const char * s, const std::string & s2, const std::string & s3) const void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, Env & env, Expr & expr)
{ {
throw EvalError(s, s2, s3); debugThrow(EvalError({
.msg = hintfmt(s, s2),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const char * s, const std::string & s2,
const std::string & s3)
{
debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2),
.errPos = positions[noPos]
}));
} }
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2, void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
const std::string & s3) const const std::string & s3)
{ {
throw EvalError({ debugThrowLastTrace(EvalError({
.msg = hintfmt(s, s2, s3), .msg = hintfmt(s, s2),
.errPos = positions[pos] .errPos = positions[pos]
}); }));
} }
void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2) const void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
const std::string & s3, Env & env, Expr & expr)
{
debugThrow(EvalError({
.msg = hintfmt(s, s2),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2, Env & env, Expr & expr)
{ {
// p1 is where the error occurred; p2 is a position mentioned in the message. // p1 is where the error occurred; p2 is a position mentioned in the message.
throw EvalError({ debugThrow(EvalError({
.msg = hintfmt(s, symbols[sym], positions[p2]), .msg = hintfmt(s, symbols[sym], positions[p2]),
.errPos = positions[p1] .errPos = positions[p1]
}); }), env, expr);
} }
void EvalState::throwTypeError(const PosIdx pos, const char * s) const void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v)
{ {
throw TypeError({ debugThrowLastTrace(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
}));
}
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v, Env & env, Expr & expr)
{
debugThrow(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[pos]
}), env, expr);
}
void EvalState::throwTypeError(const PosIdx pos, const char * s)
{
debugThrowLastTrace(TypeError({
.msg = hintfmt(s), .msg = hintfmt(s),
.errPos = positions[pos] .errPos = positions[pos]
}); }));
} }
void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun,
const Symbol s2) const const Symbol s2, Env & env, Expr &expr)
{ {
throw TypeError({ debugThrow(TypeError({
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = positions[pos] .errPos = positions[pos]
}); }), env, expr);
} }
void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s,
const ExprLambda & fun, const Symbol s2) const const ExprLambda & fun, const Symbol s2, Env & env, Expr &expr)
{ {
throw TypeError(ErrorInfo { debugThrow(TypeError(ErrorInfo {
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]), .msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
.errPos = positions[pos], .errPos = positions[pos],
.suggestions = suggestions, .suggestions = suggestions,
}); }), env, expr);
} }
void EvalState::throwTypeError(const char * s, const Value & v, Env & env, Expr &expr)
void EvalState::throwTypeError(const char * s, const Value & v) const
{ {
throw TypeError(s, showType(v)); debugThrow(TypeError({
.msg = hintfmt(s, showType(v)),
.errPos = positions[expr.getPos()],
}), env, expr);
} }
void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{ {
throw AssertionError({ debugThrow(AssertionError({
.msg = hintfmt(s, s1), .msg = hintfmt(s, s1),
.errPos = positions[pos] .errPos = positions[pos]
}); }), env, expr);
} }
void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{ {
throw UndefinedVarError({ debugThrow(UndefinedVarError({
.msg = hintfmt(s, s1), .msg = hintfmt(s, s1),
.errPos = positions[pos] .errPos = positions[pos]
}); }), env, expr);
} }
void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1, Env & env, Expr &expr)
{ {
throw MissingArgumentError({ debugThrow(MissingArgumentError({
.msg = hintfmt(s, s1), .msg = hintfmt(s, s1),
.errPos = positions[pos] .errPos = positions[pos]
}); }), env, expr);
} }
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
@ -853,6 +1013,32 @@ void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const
e.addTrace(positions[pos], s, s2); e.addTrace(positions[pos], s, s2);
} }
static std::unique_ptr<DebugTraceStacker> makeDebugTraceStacker(
EvalState & state,
Expr & expr,
Env & env,
std::optional<ErrPos> pos,
const char * s,
const std::string & s2)
{
return std::make_unique<DebugTraceStacker>(state,
DebugTrace {
.pos = pos,
.expr = expr,
.env = env,
.hint = hintfmt(s, s2),
.isError = false
});
}
DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
: evalState(evalState)
, trace(std::move(t))
{
evalState.debugTraces.push_front(trace);
if (evalState.debugStop && evalState.debugRepl)
evalState.runDebugRepl(nullptr, trace.env, trace.expr);
}
void Value::mkString(std::string_view s) void Value::mkString(std::string_view s)
{ {
@ -911,12 +1097,11 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
return j->value; return j->value;
} }
if (!env->prevWith) if (!env->prevWith)
throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name]); throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name], *env, const_cast<ExprVar&>(var));
for (size_t l = env->prevWith; l; --l, env = env->up) ; for (size_t l = env->prevWith; l; --l, env = env->up) ;
} }
} }
void EvalState::mkList(Value & v, size_t size) void EvalState::mkList(Value & v, size_t size)
{ {
v.mkList(size); v.mkList(size);
@ -1049,6 +1234,15 @@ void EvalState::cacheFile(
fileParseCache[resolvedPath] = e; fileParseCache[resolvedPath] = e;
try { try {
auto dts = debugRepl
? makeDebugTraceStacker(
*this,
*e,
this->baseEnv,
e->getPos() ? std::optional(ErrPos(positions[e->getPos()])) : std::nullopt,
"while evaluating the file '%1%':", resolvedPath)
: nullptr;
// Enforce that 'flake.nix' is a direct attrset, not a // Enforce that 'flake.nix' is a direct attrset, not a
// computation. // computation.
if (mustBeTrivial && if (mustBeTrivial &&
@ -1076,7 +1270,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e)
Value v; Value v;
e->eval(*this, env, v); e->eval(*this, env, v);
if (v.type() != nBool) if (v.type() != nBool)
throwTypeError("value is %1% while a Boolean was expected", v); throwTypeError(noPos, "value is %1% while a Boolean was expected", v, env, *e);
return v.boolean; return v.boolean;
} }
@ -1086,7 +1280,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos)
Value v; Value v;
e->eval(*this, env, v); e->eval(*this, env, v);
if (v.type() != nBool) if (v.type() != nBool)
throwTypeError(pos, "value is %1% while a Boolean was expected", v); throwTypeError(pos, "value is %1% while a Boolean was expected", v, env, *e);
return v.boolean; return v.boolean;
} }
@ -1095,7 +1289,7 @@ inline void EvalState::evalAttrs(Env & env, Expr * e, Value & v)
{ {
e->eval(*this, env, v); e->eval(*this, env, v);
if (v.type() != nAttrs) if (v.type() != nAttrs)
throwTypeError("value is %1% while a set was expected", v); throwTypeError(noPos, "value is %1% while a set was expected", v, env, *e);
} }
@ -1200,7 +1394,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
auto nameSym = state.symbols.create(nameVal.string.s); auto nameSym = state.symbols.create(nameVal.string.s);
Bindings::iterator j = v.attrs->find(nameSym); Bindings::iterator j = v.attrs->find(nameSym);
if (j != v.attrs->end()) if (j != v.attrs->end())
state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos); state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos, env, *this);
i.valueExpr->setName(nameSym); i.valueExpr->setName(nameSym);
/* Keep sorted order so find can catch duplicates */ /* Keep sorted order so find can catch duplicates */
@ -1274,6 +1468,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
e->eval(state, env, vTmp); e->eval(state, env, vTmp);
try { try {
auto dts = state.debugRepl
? makeDebugTraceStacker(
state,
*this,
env,
state.positions[pos2],
"while evaluating the attribute '%1%'",
showAttrPath(state, env, attrPath))
: nullptr;
for (auto & i : attrPath) { for (auto & i : attrPath) {
state.nrLookups++; state.nrLookups++;
@ -1296,7 +1499,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
state.throwEvalError( state.throwEvalError(
pos, pos,
Suggestions::bestMatches(allAttrNames, state.symbols[name]), Suggestions::bestMatches(allAttrNames, state.symbols[name]),
"attribute '%1%' missing", state.symbols[name]); "attribute '%1%' missing", state.symbols[name], env, *this);
} }
} }
vAttrs = j->value; vAttrs = j->value;
@ -1387,7 +1590,6 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
if (!lambda.hasFormals()) if (!lambda.hasFormals())
env2.values[displ++] = args[0]; env2.values[displ++] = args[0];
else { else {
forceAttrs(*args[0], pos); forceAttrs(*args[0], pos);
@ -1402,7 +1604,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
auto j = args[0]->attrs->get(i.name); auto j = args[0]->attrs->get(i.name);
if (!j) { if (!j) {
if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'", if (!i.def) throwTypeError(pos, "%1% called without required argument '%2%'",
lambda, i.name); lambda, i.name, *fun.lambda.env, lambda);
env2.values[displ++] = i.def->maybeThunk(*this, env2); env2.values[displ++] = i.def->maybeThunk(*this, env2);
} else { } else {
attrsUsed++; attrsUsed++;
@ -1424,8 +1626,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
pos, pos,
Suggestions::bestMatches(formalNames, symbols[i.name]), Suggestions::bestMatches(formalNames, symbols[i.name]),
"%1% called with unexpected argument '%2%'", "%1% called with unexpected argument '%2%'",
lambda, lambda, i.name, *fun.lambda.env, lambda);
i.name);
} }
abort(); // can't happen abort(); // can't happen
} }
@ -1436,6 +1637,15 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
/* Evaluate the body. */ /* Evaluate the body. */
try { try {
auto dts = debugRepl
? makeDebugTraceStacker(
*this, *lambda.body, env2, positions[lambda.pos],
"while evaluating %s",
lambda.name
? concatStrings("'", symbols[lambda.name], "'")
: "anonymous lambda")
: nullptr;
lambda.body->eval(*this, env2, vCur); lambda.body->eval(*this, env2, vCur);
} catch (Error & e) { } catch (Error & e) {
if (loggerSettings.showTrace.get()) { if (loggerSettings.showTrace.get()) {
@ -1590,8 +1800,8 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
Nix attempted to evaluate a function as a top level expression; in Nix attempted to evaluate a function as a top level expression; in
this case it must have its arguments supplied either by default this case it must have its arguments supplied either by default
values, or passed explicitly with '--arg' or '--argstr'. See values, or passed explicitly with '--arg' or '--argstr'. See
https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name]); https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name],
*fun.lambda.env, *fun.lambda.fun);
} }
} }
} }
@ -1623,7 +1833,7 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
if (!state.evalBool(env, cond, pos)) { if (!state.evalBool(env, cond, pos)) {
std::ostringstream out; std::ostringstream out;
cond->show(state.symbols, out); cond->show(state.symbols, out);
state.throwAssertionError(pos, "assertion '%1%' failed", out.str()); state.throwAssertionError(pos, "assertion '%1%' failed", out.str(), env, *this);
} }
body->eval(state, env, v); body->eval(state, env, v);
} }
@ -1800,14 +2010,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
nf = n; nf = n;
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp)); state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp), env, *this);
} else if (firstType == nFloat) { } else if (firstType == nFloat) {
if (vTmp.type() == nInt) { if (vTmp.type() == nInt) {
nf += vTmp.integer; nf += vTmp.integer;
} else if (vTmp.type() == nFloat) { } else if (vTmp.type() == nFloat) {
nf += vTmp.fpoint; nf += vTmp.fpoint;
} else } else
state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp)); state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp), env, *this);
} else { } else {
if (s.empty()) s.reserve(es->size()); if (s.empty()) s.reserve(es->size());
/* skip canonization of first path, which would only be not /* skip canonization of first path, which would only be not
@ -1827,7 +2037,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
v.mkFloat(nf); v.mkFloat(nf);
else if (firstType == nPath) { else if (firstType == nPath) {
if (!context.empty()) if (!context.empty())
state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path"); state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path", env, *this);
v.mkPath(canonPath(str())); v.mkPath(canonPath(str()));
} else } else
v.mkStringMove(c_str(), context); v.mkStringMove(c_str(), context);
@ -1854,6 +2064,12 @@ void EvalState::forceValueDeep(Value & v)
if (v.type() == nAttrs) { if (v.type() == nAttrs) {
for (auto & i : *v.attrs) for (auto & i : *v.attrs)
try { try {
// If the value is a thunk, we're evaling. Otherwise no trace necessary.
auto dts = debugRepl && i.value->isThunk()
? makeDebugTraceStacker(*this, *i.value->thunk.expr, *i.value->thunk.env, positions[i.pos],
"while evaluating the attribute '%1%'", symbols[i.name])
: nullptr;
recurse(*i.value); recurse(*i.value);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, i.pos, "while evaluating the attribute '%1%'", symbols[i.name]); addErrorTrace(e, i.pos, "while evaluating the attribute '%1%'", symbols[i.name]);
@ -1876,6 +2092,7 @@ NixInt EvalState::forceInt(Value & v, const PosIdx pos)
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nInt) if (v.type() != nInt)
throwTypeError(pos, "value is %1% while an integer was expected", v); throwTypeError(pos, "value is %1% while an integer was expected", v);
return v.integer; return v.integer;
} }
@ -1918,10 +2135,7 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos)
{ {
forceValue(v, pos); forceValue(v, pos);
if (v.type() != nString) { if (v.type() != nString) {
if (pos)
throwTypeError(pos, "value is %1% while a string was expected", v); throwTypeError(pos, "value is %1% while a string was expected", v);
else
throwTypeError("value is %1% while a string was expected", v);
} }
return v.string.s; return v.string.s;
} }
@ -2038,7 +2252,8 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
if (maybeString) if (maybeString)
return std::move(*maybeString); return std::move(*maybeString);
auto i = v.attrs->find(sOutPath); auto i = v.attrs->find(sOutPath);
if (i == v.attrs->end()) throwTypeError(pos, "cannot coerce a set to a string"); if (i == v.attrs->end())
throwTypeError(pos, "cannot coerce a set to a string");
return coerceToString(pos, *i->value, context, coerceMore, copyToStore); return coerceToString(pos, *i->value, context, coerceMore, copyToStore);
} }
@ -2046,7 +2261,6 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore); return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
if (coerceMore) { if (coerceMore) {
/* Note that `false' is represented as an empty string for /* Note that `false' is represented as an empty string for
shell scripting convenience, just like `null'. */ shell scripting convenience, just like `null'. */
if (v.type() == nBool && v.boolean) return "1"; if (v.type() == nBool && v.boolean) return "1";
@ -2191,7 +2405,9 @@ bool EvalState::eqValues(Value & v1, Value & v2)
return v1.fpoint == v2.fpoint; return v1.fpoint == v2.fpoint;
default: default:
throwEvalError("cannot compare %1% with %2%", showType(v1), showType(v2)); throwEvalError("cannot compare %1% with %2%",
showType(v1),
showType(v2));
} }
} }

View file

@ -13,7 +13,6 @@
#include <unordered_map> #include <unordered_map>
#include <mutex> #include <mutex>
namespace nix { namespace nix {
@ -25,7 +24,6 @@ enum RepairFlag : bool;
typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v); typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v);
struct PrimOp struct PrimOp
{ {
PrimOpFun fun; PrimOpFun fun;
@ -35,6 +33,11 @@ struct PrimOp
const char * doc = nullptr; const char * doc = nullptr;
}; };
#if HAVE_BOEHMGC
typedef std::map<std::string, Value *, std::less<std::string>, traceable_allocator<std::pair<const std::string, Value *> > > ValMap;
#else
typedef std::map<std::string, Value *> ValMap;
#endif
struct Env struct Env
{ {
@ -44,6 +47,10 @@ struct Env
Value * values[0]; Value * values[0];
}; };
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);
std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env);
void copyContext(const Value & v, PathSet & context); void copyContext(const Value & v, PathSet & context);
@ -70,8 +77,17 @@ struct RegexCache;
std::shared_ptr<RegexCache> makeRegexCache(); std::shared_ptr<RegexCache> makeRegexCache();
struct DebugTrace {
std::optional<ErrPos> pos;
const Expr & expr;
const Env & env;
hintformat hint;
bool isError;
};
class EvalState void debugError(Error * e, Env & env, Expr & expr);
class EvalState : public std::enable_shared_from_this<EvalState>
{ {
public: public:
SymbolTable symbols; SymbolTable symbols;
@ -87,7 +103,8 @@ public:
sOutputHash, sOutputHashAlgo, sOutputHashMode, sOutputHash, sOutputHashAlgo, sOutputHashMode,
sRecurseForDerivations, sRecurseForDerivations,
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath, sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
sPrefix; sPrefix,
sOutputSpecified;
Symbol sDerivationNix; Symbol sDerivationNix;
/* If set, force copying files to the Nix store even if they /* If set, force copying files to the Nix store even if they
@ -109,6 +126,49 @@ public:
RootValue vCallFlake = nullptr; RootValue vCallFlake = nullptr;
RootValue vImportedDrvToDerivation = nullptr; RootValue vImportedDrvToDerivation = nullptr;
/* Debugger */
void (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv);
bool debugStop;
bool debugQuit;
std::list<DebugTrace> debugTraces;
std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs;
const std::shared_ptr<const StaticEnv> getStaticEnv(const Expr & expr) const
{
auto i = exprEnvs.find(&expr);
if (i != exprEnvs.end())
return i->second;
else
return std::shared_ptr<const StaticEnv>();;
}
void runDebugRepl(const Error * error, const Env & env, const Expr & expr);
template<class E>
[[gnu::noinline, gnu::noreturn]]
void debugThrow(E && error, const Env & env, const Expr & expr)
{
if (debugRepl)
runDebugRepl(&error, env, expr);
throw error;
}
template<class E>
[[gnu::noinline, gnu::noreturn]]
void debugThrowLastTrace(E && e)
{
// Call this in the situation where Expr and Env are inaccessible.
// The debugger will start in the last context that's in the
// DebugTrace stack.
if (debugRepl && !debugTraces.empty()) {
const DebugTrace & last = debugTraces.front();
runDebugRepl(&e, last.env, last.expr);
}
throw e;
}
private: private:
SrcToStore srcToStore; SrcToStore srcToStore;
@ -185,10 +245,10 @@ public:
/* Parse a Nix expression from the specified file. */ /* Parse a Nix expression from the specified file. */
Expr * parseExprFromFile(const Path & path); Expr * parseExprFromFile(const Path & path);
Expr * parseExprFromFile(const Path & path, StaticEnv & staticEnv); Expr * parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv);
/* Parse a Nix expression from the specified string. */ /* Parse a Nix expression from the specified string. */
Expr * parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv); Expr * parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv);
Expr * parseExprFromString(std::string s, const Path & basePath); Expr * parseExprFromString(std::string s, const Path & basePath);
Expr * parseStdin(); Expr * parseStdin();
@ -198,7 +258,7 @@ public:
trivial (i.e. doesn't require arbitrary computation). */ trivial (i.e. doesn't require arbitrary computation). */
void evalFile(const Path & path, Value & v, bool mustBeTrivial = false); void evalFile(const Path & path, Value & v, bool mustBeTrivial = false);
/* Like `cacheFile`, but with an already parsed expression. */ /* Like `evalFile`, but with an already parsed expression. */
void cacheFile( void cacheFile(
const Path & path, const Path & path,
const Path & resolvedPath, const Path & resolvedPath,
@ -255,37 +315,68 @@ public:
std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos); std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s) const; void throwEvalError(const PosIdx pos, const char * s);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const Value & v) const; void throwEvalError(const PosIdx pos, const char * s,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2) const; void throwEvalError(const char * s, const std::string & s2);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, void throwEvalError(const PosIdx pos, const char * s, const std::string & s2);
const std::string & s2) const;
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const; void throwEvalError(const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwEvalError(const char * s, const std::string & s2, const std::string & s3) const; void throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const; void throwEvalError(const char * s, const std::string & s2, const std::string & s3,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2) const; void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s) const; void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2) const; void throwEvalError(const char * s, const std::string & s2, const std::string & s3);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s, const std::string & s2,
const ExprLambda & fun, const Symbol s2) const; Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwTypeError(const char * s, const Value & v) const; void throwEvalError(const PosIdx p1, const char * s, const Symbol sym, const PosIdx p2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const; void throwTypeError(const PosIdx pos, const char * s, const Value & v);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const; void throwTypeError(const PosIdx pos, const char * s, const Value & v,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]] [[gnu::noinline, gnu::noreturn]]
void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const; void throwTypeError(const PosIdx pos, const char * s);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const Symbol s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol s2,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwTypeError(const char * s, const Value & v,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline, gnu::noreturn]]
void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1,
Env & env, Expr & expr);
[[gnu::noinline]] [[gnu::noinline]]
void addErrorTrace(Error & e, const char * s, const std::string & s2) const; void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
@ -325,7 +416,7 @@ public:
Env & baseEnv; Env & baseEnv;
/* The same, but used during parsing to resolve variables. */ /* The same, but used during parsing to resolve variables. */
StaticEnv staticBaseEnv; // !!! should be private std::shared_ptr<StaticEnv> staticBaseEnv; // !!! should be private
private: private:
@ -366,7 +457,7 @@ private:
friend struct ExprLet; friend struct ExprLet;
Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path, Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path,
const PathView basePath, StaticEnv & staticEnv); const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv);
public: public:
@ -461,6 +552,16 @@ private:
friend struct Value; friend struct Value;
}; };
struct DebugTraceStacker {
DebugTraceStacker(EvalState & evalState, DebugTrace t);
~DebugTraceStacker()
{
// assert(evalState.debugTraces.front() == trace);
evalState.debugTraces.pop_front();
}
EvalState & evalState;
DebugTrace trace;
};
/* Return a string representing the type of the value `v'. */ /* Return a string representing the type of the value `v'. */
std::string_view showType(ValueType type); std::string_view showType(ValueType type);

View file

@ -132,9 +132,21 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
} else } else
outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt); outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
} }
if (!onlyOutputsToInstall || !attrs) if (!onlyOutputsToInstall || !attrs)
return outputs; return outputs;
Bindings::iterator i;
if (attrs && (i = attrs->find(state->sOutputSpecified)) != attrs->end() && state->forceBool(*i->value, i->pos)) {
Outputs result;
auto out = outputs.find(queryOutputName());
if (out == outputs.end())
throw Error("derivation does not have output '%s'", queryOutputName());
result.insert(*out);
return result;
}
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;
@ -150,6 +162,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
} }
return result; return result;
} }
}
std::string DrvInfo::queryOutputName() const std::string DrvInfo::queryOutputName() const

View file

@ -198,7 +198,7 @@ or { return OR_KW; }
(...|\$[^\{\"\\]|\\.|\$\\.)+ would have triggered. (...|\$[^\{\"\\]|\\.|\$\\.)+ would have triggered.
This is technically invalid, but we leave the problem to the This is technically invalid, but we leave the problem to the
parser who fails with exact location. */ parser who fails with exact location. */
return STR; return EOF;
} }
\'\'(\ *\n)? { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; } \'\'(\ *\n)? { PUSH_STATE(IND_STRING); return IND_STRING_OPEN; }

View file

@ -6,10 +6,8 @@
#include <cstdlib> #include <cstdlib>
namespace nix { namespace nix {
/* Displaying abstract syntax trees. */ /* Displaying abstract syntax trees. */
static void showString(std::ostream & str, std::string_view s) static void showString(std::ostream & str, std::string_view s)
@ -294,35 +292,46 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
/* Computing levels/displacements for variables. */ /* Computing levels/displacements for variables. */
void Expr::bindVars(const EvalState & es, const StaticEnv & env) void Expr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
abort(); abort();
} }
void ExprInt::bindVars(const EvalState & es, const StaticEnv & env) void ExprInt::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
} }
void ExprFloat::bindVars(const EvalState & es, const StaticEnv & env) void ExprFloat::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
} }
void ExprString::bindVars(const EvalState & es, const StaticEnv & env) void ExprString::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
} }
void ExprPath::bindVars(const EvalState & es, const StaticEnv & env) void ExprPath::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
} }
void ExprVar::bindVars(const EvalState & es, const StaticEnv & env) void ExprVar::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
/* Check whether the variable appears in the environment. If so, /* Check whether the variable appears in the environment. If so,
set its level and displacement. */ set its level and displacement. */
const StaticEnv * curEnv; const StaticEnv * curEnv;
Level level; Level level;
int withLevel = -1; int withLevel = -1;
for (curEnv = &env, level = 0; curEnv; curEnv = curEnv->up, level++) { for (curEnv = env.get(), level = 0; curEnv; curEnv = curEnv->up, level++) {
if (curEnv->isWith) { if (curEnv->isWith) {
if (withLevel == -1) withLevel = level; if (withLevel == -1) withLevel = level;
} else { } else {
@ -348,8 +357,11 @@ void ExprVar::bindVars(const EvalState & es, const StaticEnv & env)
this->level = withLevel; this->level = withLevel;
} }
void ExprSelect::bindVars(const EvalState & es, const StaticEnv & env) void ExprSelect::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
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)
@ -357,64 +369,78 @@ void ExprSelect::bindVars(const EvalState & es, const StaticEnv & env)
i.expr->bindVars(es, env); i.expr->bindVars(es, env);
} }
void ExprOpHasAttr::bindVars(const EvalState & es, const StaticEnv & env) void ExprOpHasAttr::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
e->bindVars(es, env); e->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);
} }
void ExprAttrs::bindVars(const EvalState & es, const StaticEnv & env) void ExprAttrs::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
const StaticEnv * dynamicEnv = &env; if (es.debugRepl)
StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0); es.exprEnvs.insert(std::make_pair(this, env));
if (recursive) { if (recursive) {
dynamicEnv = &newEnv; auto newEnv = std::make_shared<StaticEnv>(false, env.get(), recursive ? attrs.size() : 0);
Displacement displ = 0; Displacement displ = 0;
for (auto & i : attrs) for (auto & i : attrs)
newEnv.vars.emplace_back(i.first, i.second.displ = displ++); newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
// No need to sort newEnv since attrs is in sorted order. // No need to sort newEnv since attrs is in sorted order.
for (auto & i : attrs) for (auto & i : attrs)
i.second.e->bindVars(es, i.second.inherited ? env : newEnv); i.second.e->bindVars(es, i.second.inherited ? env : newEnv);
}
else for (auto & i : dynamicAttrs) {
i.nameExpr->bindVars(es, newEnv);
i.valueExpr->bindVars(es, newEnv);
}
}
else {
for (auto & i : attrs) for (auto & i : attrs)
i.second.e->bindVars(es, env); i.second.e->bindVars(es, env);
for (auto & i : dynamicAttrs) { for (auto & i : dynamicAttrs) {
i.nameExpr->bindVars(es, *dynamicEnv); i.nameExpr->bindVars(es, env);
i.valueExpr->bindVars(es, *dynamicEnv); i.valueExpr->bindVars(es, env);
}
} }
} }
void ExprList::bindVars(const EvalState & es, const StaticEnv & env) void ExprList::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
for (auto & i : elems) for (auto & i : elems)
i->bindVars(es, env); i->bindVars(es, env);
} }
void ExprLambda::bindVars(const EvalState & es, const StaticEnv & env) void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
StaticEnv newEnv( if (es.debugRepl)
false, &env, es.exprEnvs.insert(std::make_pair(this, env));
auto newEnv = std::make_shared<StaticEnv>(
false, env.get(),
(hasFormals() ? formals->formals.size() : 0) + (hasFormals() ? formals->formals.size() : 0) +
(!arg ? 0 : 1)); (!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)
newEnv.vars.emplace_back(i.name, displ++); newEnv->vars.emplace_back(i.name, displ++);
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);
@ -423,20 +449,26 @@ void ExprLambda::bindVars(const EvalState & es, const StaticEnv & env)
body->bindVars(es, newEnv); body->bindVars(es, newEnv);
} }
void ExprCall::bindVars(const EvalState & es, const StaticEnv & env) void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
fun->bindVars(es, env); fun->bindVars(es, env);
for (auto e : args) for (auto e : args)
e->bindVars(es, env); e->bindVars(es, env);
} }
void ExprLet::bindVars(const EvalState & es, const StaticEnv & env) void ExprLet::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
StaticEnv newEnv(false, &env, attrs->attrs.size()); if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
auto newEnv = std::make_shared<StaticEnv>(false, env.get(), attrs->attrs.size());
Displacement displ = 0; Displacement displ = 0;
for (auto & i : attrs->attrs) for (auto & i : attrs->attrs)
newEnv.vars.emplace_back(i.first, i.second.displ = displ++); newEnv->vars.emplace_back(i.first, i.second.displ = displ++);
// No need to sort newEnv since attrs->attrs is in sorted order. // No need to sort newEnv since attrs->attrs is in sorted order.
@ -446,51 +478,71 @@ void ExprLet::bindVars(const EvalState & es, const StaticEnv & env)
body->bindVars(es, newEnv); body->bindVars(es, newEnv);
} }
void ExprWith::bindVars(const EvalState & es, const StaticEnv & env) void ExprWith::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
/* Does this `with' have an enclosing `with'? If so, record its /* Does this `with' have an enclosing `with'? If so, record its
level so that `lookupVar' can look up variables in the previous level so that `lookupVar' can look up variables in the previous
`with' if this one doesn't contain the desired attribute. */ `with' if this one doesn't contain the desired attribute. */
const StaticEnv * curEnv; const StaticEnv * curEnv;
Level level; Level level;
prevWith = 0; prevWith = 0;
for (curEnv = &env, level = 1; curEnv; curEnv = curEnv->up, level++) for (curEnv = env.get(), level = 1; curEnv; curEnv = curEnv->up, level++)
if (curEnv->isWith) { if (curEnv->isWith) {
prevWith = level; prevWith = level;
break; break;
} }
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
attrs->bindVars(es, env); attrs->bindVars(es, env);
StaticEnv newEnv(true, &env); auto newEnv = std::make_shared<StaticEnv>(true, env.get());
body->bindVars(es, newEnv); body->bindVars(es, newEnv);
} }
void ExprIf::bindVars(const EvalState & es, const StaticEnv & env) void ExprIf::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
cond->bindVars(es, env); cond->bindVars(es, env);
then->bindVars(es, env); then->bindVars(es, env);
else_->bindVars(es, env); else_->bindVars(es, env);
} }
void ExprAssert::bindVars(const EvalState & es, const StaticEnv & env) void ExprAssert::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
cond->bindVars(es, env); cond->bindVars(es, env);
body->bindVars(es, env); body->bindVars(es, env);
} }
void ExprOpNot::bindVars(const EvalState & es, const StaticEnv & env) void ExprOpNot::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
e->bindVars(es, env); e->bindVars(es, env);
} }
void ExprConcatStrings::bindVars(const EvalState & es, const StaticEnv & env) void ExprConcatStrings::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
for (auto & i : *this->es) for (auto & i : *this->es)
i.second->bindVars(es, env); i.second->bindVars(es, env);
} }
void ExprPos::bindVars(const EvalState & es, const StaticEnv & env) void ExprPos::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env));
} }

View file

@ -22,7 +22,6 @@ MakeError(UndefinedVarError, Error);
MakeError(MissingArgumentError, EvalError); MakeError(MissingArgumentError, EvalError);
MakeError(RestrictedPathError, Error); MakeError(RestrictedPathError, Error);
/* Position objects. */ /* Position objects. */
struct Pos struct Pos
@ -143,24 +142,25 @@ struct Expr
{ {
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(const EvalState & es, 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 PosIdx getPos() const { return noPos; }
}; };
#define COMMON_METHODS \ #define COMMON_METHODS \
void show(const SymbolTable & symbols, std::ostream & str) const; \ void show(const SymbolTable & symbols, std::ostream & str) const; \
void eval(EvalState & state, Env & env, Value & v); \ void eval(EvalState & state, Env & env, Value & v); \
void bindVars(const EvalState & es, const StaticEnv & env); void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
struct ExprInt : Expr struct ExprInt : Expr
{ {
NixInt n; NixInt n;
Value v; Value v;
ExprInt(NixInt n) : n(n) { v.mkInt(n); }; ExprInt(NixInt n) : n(n) { v.mkInt(n); };
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env); Value * maybeThunk(EvalState & state, Env & env);
COMMON_METHODS
}; };
struct ExprFloat : Expr struct ExprFloat : Expr
@ -168,8 +168,8 @@ struct ExprFloat : Expr
NixFloat nf; NixFloat nf;
Value v; Value v;
ExprFloat(NixFloat nf) : nf(nf) { v.mkFloat(nf); }; ExprFloat(NixFloat nf) : nf(nf) { v.mkFloat(nf); };
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env); Value * maybeThunk(EvalState & state, Env & env);
COMMON_METHODS
}; };
struct ExprString : Expr struct ExprString : Expr
@ -177,8 +177,8 @@ 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()); };
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env); Value * maybeThunk(EvalState & state, Env & env);
COMMON_METHODS
}; };
struct ExprPath : Expr struct ExprPath : Expr
@ -186,8 +186,8 @@ struct ExprPath : Expr
std::string s; std::string s;
Value v; Value v;
ExprPath(std::string s) : s(std::move(s)) { v.mkPath(this->s.c_str()); }; ExprPath(std::string s) : s(std::move(s)) { v.mkPath(this->s.c_str()); };
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env); Value * maybeThunk(EvalState & state, Env & env);
COMMON_METHODS
}; };
typedef uint32_t Level; typedef uint32_t Level;
@ -213,8 +213,9 @@ struct ExprVar : Expr
ExprVar(Symbol name) : name(name) { }; ExprVar(Symbol name) : name(name) { };
ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { }; ExprVar(const PosIdx & pos, Symbol name) : pos(pos), name(name) { };
COMMON_METHODS
Value * maybeThunk(EvalState & state, Env & env); Value * maybeThunk(EvalState & state, Env & env);
PosIdx getPos() const override { return pos; }
COMMON_METHODS
}; };
struct ExprSelect : Expr struct ExprSelect : Expr
@ -224,6 +225,7 @@ struct ExprSelect : Expr
AttrPath attrPath; AttrPath attrPath;
ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { }; ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { };
ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); }; ExprSelect(const PosIdx & pos, Expr * e, Symbol name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
}; };
@ -232,6 +234,7 @@ struct ExprOpHasAttr : Expr
Expr * e; Expr * e;
AttrPath attrPath; AttrPath attrPath;
ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { }; ExprOpHasAttr(Expr * e, const AttrPath & attrPath) : e(e), attrPath(attrPath) { };
PosIdx getPos() const override { return e->getPos(); }
COMMON_METHODS COMMON_METHODS
}; };
@ -260,6 +263,7 @@ struct ExprAttrs : Expr
DynamicAttrDefs dynamicAttrs; DynamicAttrDefs dynamicAttrs;
ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { }; ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { };
ExprAttrs() : recursive(false) { }; ExprAttrs() : recursive(false) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
}; };
@ -268,6 +272,11 @@ struct ExprList : Expr
std::vector<Expr *> elems; std::vector<Expr *> elems;
ExprList() { }; ExprList() { };
COMMON_METHODS COMMON_METHODS
PosIdx getPos() const override
{
return elems.empty() ? noPos : elems.front()->getPos();
}
}; };
struct Formal struct Formal
@ -320,6 +329,7 @@ struct ExprLambda : Expr
void setName(Symbol name); void setName(Symbol name);
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 { return formals != nullptr; }
PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
}; };
@ -331,6 +341,7 @@ struct ExprCall : Expr
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) : fun(fun), args(args), pos(pos)
{ } { }
PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
}; };
@ -348,6 +359,7 @@ struct ExprWith : Expr
Expr * attrs, * body; Expr * attrs, * body;
size_t prevWith; size_t prevWith;
ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { }; ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
}; };
@ -356,6 +368,7 @@ 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_) : pos(pos), cond(cond), then(then), else_(else_) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
}; };
@ -364,6 +377,7 @@ 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) : pos(pos), cond(cond), body(body) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
}; };
@ -385,11 +399,12 @@ struct ExprOpNot : Expr
{ \ { \
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(const EvalState & es, const StaticEnv & env) \ void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) \
{ \ { \
e1->bindVars(es, env); e2->bindVars(es, env); \ e1->bindVars(es, env); e2->bindVars(es, env); \
} \ } \
void eval(EvalState & state, Env & env, Value & v); \ void eval(EvalState & state, Env & env, Value & v); \
PosIdx getPos() const override { return pos; } \
}; };
MakeBinOp(ExprOpEq, "==") MakeBinOp(ExprOpEq, "==")
@ -407,6 +422,7 @@ struct ExprConcatStrings : Expr
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), forceString(forceString), es(es) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
}; };
@ -414,6 +430,7 @@ struct ExprPos : Expr
{ {
PosIdx pos; PosIdx pos;
ExprPos(const PosIdx & pos) : pos(pos) { }; ExprPos(const PosIdx & pos) : pos(pos) { };
PosIdx getPos() const override { return pos; }
COMMON_METHODS COMMON_METHODS
}; };

View file

@ -643,7 +643,7 @@ namespace nix {
Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
const PathView path, const PathView basePath, StaticEnv & staticEnv) const PathView path, const PathView basePath, std::shared_ptr<StaticEnv> & staticEnv)
{ {
yyscan_t scanner; yyscan_t scanner;
std::string file; std::string file;
@ -706,7 +706,7 @@ Expr * EvalState::parseExprFromFile(const Path & path)
} }
Expr * EvalState::parseExprFromFile(const Path & path, StaticEnv & staticEnv) Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv)
{ {
auto buffer = readFile(path); auto buffer = readFile(path);
// readFile should have left some extra space for terminators // readFile should have left some extra space for terminators
@ -715,7 +715,7 @@ Expr * EvalState::parseExprFromFile(const Path & path, StaticEnv & staticEnv)
} }
Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, StaticEnv & staticEnv) Expr * EvalState::parseExprFromString(std::string s, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
{ {
s.append("\0\0", 2); s.append("\0\0", 2);
return parse(s.data(), s.size(), foString, "", basePath, staticEnv); return parse(s.data(), s.size(), foString, "", basePath, staticEnv);
@ -782,13 +782,13 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
if (hasPrefix(path, "nix/")) if (hasPrefix(path, "nix/"))
return concatStrings(corepkgsPrefix, path.substr(4)); return concatStrings(corepkgsPrefix, path.substr(4));
throw ThrownError({ debugThrowLastTrace(ThrownError({
.msg = hintfmt(evalSettings.pureEval .msg = hintfmt(evalSettings.pureEval
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)" ? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)", : "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
path), path),
.errPos = positions[pos] .errPos = positions[pos]
}); }));
} }

View file

@ -46,7 +46,7 @@ StringMap EvalState::realiseContext(const PathSet & context)
auto [ctx, outputName] = decodeContext(*store, i); auto [ctx, outputName] = decodeContext(*store, i);
auto ctxS = store->printStorePath(ctx); auto ctxS = store->printStorePath(ctx);
if (!store->isValidPath(ctx)) if (!store->isValidPath(ctx))
throw InvalidPathError(store->printStorePath(ctx)); debugThrowLastTrace(InvalidPathError(store->printStorePath(ctx)));
if (!outputName.empty() && ctx.isDerivation()) { if (!outputName.empty() && ctx.isDerivation()) {
drvs.push_back({ctx, {outputName}}); drvs.push_back({ctx, {outputName}});
} else { } else {
@ -57,9 +57,9 @@ StringMap EvalState::realiseContext(const PathSet & context)
if (drvs.empty()) return {}; if (drvs.empty()) return {};
if (!evalSettings.enableImportFromDerivation) if (!evalSettings.enableImportFromDerivation)
throw Error( debugThrowLastTrace(Error(
"cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled", "cannot build '%1%' during evaluation because the option 'allow-import-from-derivation' is disabled",
store->printStorePath(drvs.begin()->drvPath)); store->printStorePath(drvs.begin()->drvPath)));
/* Build/substitute the context. */ /* Build/substitute the context. */
std::vector<DerivedPath> buildReqs; std::vector<DerivedPath> buildReqs;
@ -72,8 +72,8 @@ StringMap EvalState::realiseContext(const PathSet & context)
for (auto & outputName : outputs) { for (auto & outputName : outputs) {
auto outputPath = get(outputPaths, outputName); auto outputPath = get(outputPaths, outputName);
if (!outputPath) if (!outputPath)
throw Error("derivation '%s' does not have an output named '%s'", debugThrowLastTrace(Error("derivation '%s' does not have an output named '%s'",
store->printStorePath(drvPath), outputName); store->printStorePath(drvPath), outputName));
res.insert_or_assign( res.insert_or_assign(
downstreamPlaceholder(*store, drvPath, outputName), downstreamPlaceholder(*store, drvPath, outputName),
store->printStorePath(*outputPath) store->printStorePath(*outputPath)
@ -216,11 +216,11 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
Env * env = &state.allocEnv(vScope->attrs->size()); Env * env = &state.allocEnv(vScope->attrs->size());
env->up = &state.baseEnv; env->up = &state.baseEnv;
StaticEnv staticEnv(false, &state.staticBaseEnv, vScope->attrs->size()); auto staticEnv = std::make_shared<StaticEnv>(false, state.staticBaseEnv.get(), vScope->attrs->size());
unsigned int displ = 0; unsigned int displ = 0;
for (auto & attr : *vScope->attrs) { for (auto & attr : *vScope->attrs) {
staticEnv.vars.emplace_back(attr.name, displ); staticEnv->vars.emplace_back(attr.name, displ);
env->values[displ++] = attr.value; env->values[displ++] = attr.value;
} }
@ -319,17 +319,16 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL); void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle) if (!handle)
throw EvalError("could not open '%1%': %2%", path, dlerror()); state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
dlerror(); dlerror();
ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str()); ValueInitializer func = (ValueInitializer) dlsym(handle, sym.c_str());
if(!func) { if(!func) {
char *message = dlerror(); char *message = dlerror();
if (message) if (message)
throw EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message); state.debugThrowLastTrace(EvalError("could not load symbol '%1%' from '%2%': %3%", sym, path, message));
else else
throw EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", state.debugThrowLastTrace(EvalError("symbol '%1%' from '%2%' resolved to NULL when a function pointer was expected", sym, path));
sym, path);
} }
(func)(state, v); (func)(state, v);
@ -344,12 +343,11 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
state.forceList(*args[0], pos); state.forceList(*args[0], pos);
auto elems = args[0]->listElems(); auto elems = args[0]->listElems();
auto count = args[0]->listSize(); auto count = args[0]->listSize();
if (count == 0) { if (count == 0)
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("at least one argument to 'exec' required"), .msg = hintfmt("at least one argument to 'exec' required"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
}
PathSet context; PathSet context;
auto program = state.coerceToString(pos, *elems[0], context, false, false).toOwned(); auto program = state.coerceToString(pos, *elems[0], context, false, false).toOwned();
Strings commandArgs; Strings commandArgs;
@ -359,11 +357,11 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
try { try {
auto _ = state.realiseContext(context); // FIXME: Handle CA derivations auto _ = state.realiseContext(context); // FIXME: Handle CA derivations
} catch (InvalidPathError & e) { } catch (InvalidPathError & e) {
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid", .msg = hintfmt("cannot execute '%1%', since path '%2%' is not valid",
program, e.path), program, e.path),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
} }
auto output = runProgram(program, true, commandArgs); auto output = runProgram(program, true, commandArgs);
@ -547,7 +545,7 @@ struct CompareValues
if (v1->type() == nInt && v2->type() == nFloat) if (v1->type() == nInt && v2->type() == nFloat)
return v1->integer < v2->fpoint; return v1->integer < v2->fpoint;
if (v1->type() != v2->type()) if (v1->type() != v2->type())
throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)); state.debugThrowLastTrace(EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)));
switch (v1->type()) { switch (v1->type()) {
case nInt: case nInt:
return v1->integer < v2->integer; return v1->integer < v2->integer;
@ -569,7 +567,7 @@ struct CompareValues
} }
} }
default: default:
throw EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)); state.debugThrowLastTrace(EvalError("cannot compare %1% with %2%", showType(*v1), showType(*v2)));
} }
} }
}; };
@ -599,10 +597,10 @@ static Bindings::iterator getAttr(
auto aPos = attrSet->pos; auto aPos = attrSet->pos;
if (!aPos) { if (!aPos) {
throw TypeError({ state.debugThrowLastTrace(TypeError({
.msg = errorMsg, .msg = errorMsg,
.errPos = state.positions[pos], .errPos = state.positions[pos],
}); }));
} else { } else {
auto e = TypeError({ auto e = TypeError({
.msg = errorMsg, .msg = errorMsg,
@ -612,7 +610,7 @@ static Bindings::iterator getAttr(
// Adding another trace for the function name to make it clear // Adding another trace for the function name to make it clear
// which call received wrong arguments. // which call received wrong arguments.
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName)); e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", funcName));
throw e; state.debugThrowLastTrace(e);
} }
} }
@ -666,10 +664,10 @@ static void prim_genericClosure(EvalState & state, const PosIdx pos, Value * * a
Bindings::iterator key = Bindings::iterator key =
e->attrs->find(state.sKey); e->attrs->find(state.sKey);
if (key == e->attrs->end()) if (key == e->attrs->end())
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'key' required"), .msg = hintfmt("attribute 'key' required"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
state.forceValue(*key->value, pos); state.forceValue(*key->value, pos);
if (!doneKeys.insert(key->value).second) continue; if (!doneKeys.insert(key->value).second) continue;
@ -725,6 +723,41 @@ static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
.fun = prim_genericClosure, .fun = prim_genericClosure,
}); });
static RegisterPrimOp primop_break({
.name = "break",
.args = {"v"},
.doc = R"(
In debug mode (enabled using `--debugger`), pause Nix expression evaluation and enter the REPL.
Otherwise, return the argument `v`.
)",
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
if (state.debugRepl && !state.debugTraces.empty()) {
auto error = Error(ErrorInfo {
.level = lvlInfo,
.msg = hintfmt("breakpoint reached"),
.errPos = state.positions[pos],
});
auto & dt = state.debugTraces.front();
state.runDebugRepl(&error, dt.env, dt.expr);
if (state.debugQuit) {
// If the user elects to quit the repl, throw an exception.
throw Error(ErrorInfo{
.level = lvlInfo,
.msg = hintfmt("quit the debugger"),
.errPos = state.positions[noPos],
});
}
}
// Return the value we were passed.
v = *args[0];
}
});
static RegisterPrimOp primop_abort({ static RegisterPrimOp primop_abort({
.name = "abort", .name = "abort",
.args = {"s"}, .args = {"s"},
@ -735,7 +768,7 @@ static RegisterPrimOp primop_abort({
{ {
PathSet context; PathSet context;
auto s = state.coerceToString(pos, *args[0], context).toOwned(); auto s = state.coerceToString(pos, *args[0], context).toOwned();
throw Abort("evaluation aborted with the following error message: '%1%'", s); state.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s));
} }
}); });
@ -753,7 +786,7 @@ static RegisterPrimOp primop_throw({
{ {
PathSet context; PathSet context;
auto s = state.coerceToString(pos, *args[0], context).toOwned(); auto s = state.coerceToString(pos, *args[0], context).toOwned();
throw ThrownError(s); state.debugThrowLastTrace(ThrownError(s));
} }
}); });
@ -1008,37 +1041,37 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive; if (s == "recursive") ingestionMethod = FileIngestionMethod::Recursive;
else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat; else if (s == "flat") ingestionMethod = FileIngestionMethod::Flat;
else else
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s), .msg = hintfmt("invalid value '%s' for 'outputHashMode' attribute", s),
.errPos = state.positions[posDrvName] .errPos = state.positions[posDrvName]
}); }));
}; };
auto handleOutputs = [&](const Strings & ss) { auto handleOutputs = [&](const Strings & ss) {
outputs.clear(); outputs.clear();
for (auto & j : ss) { for (auto & j : ss) {
if (outputs.find(j) != outputs.end()) if (outputs.find(j) != outputs.end())
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("duplicate derivation output '%1%'", j), .msg = hintfmt("duplicate derivation output '%1%'", j),
.errPos = state.positions[posDrvName] .errPos = state.positions[posDrvName]
}); }));
/* !!! Check whether j is a valid attribute /* !!! Check whether j is a valid attribute
name. */ name. */
/* Derivations cannot be named drv, because /* Derivations cannot be named drv, because
then we'd have an attribute drvPath in then we'd have an attribute drvPath in
the resulting set. */ the resulting set. */
if (j == "drv") if (j == "drv")
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid derivation output name 'drv'" ), .msg = hintfmt("invalid derivation output name 'drv'" ),
.errPos = state.positions[posDrvName] .errPos = state.positions[posDrvName]
}); }));
outputs.insert(j); outputs.insert(j);
} }
if (outputs.empty()) if (outputs.empty())
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation cannot have an empty set of outputs"), .msg = hintfmt("derivation cannot have an empty set of outputs"),
.errPos = state.positions[posDrvName] .errPos = state.positions[posDrvName]
}); }));
}; };
try { try {
@ -1163,23 +1196,23 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
/* Do we have all required attributes? */ /* Do we have all required attributes? */
if (drv.builder == "") if (drv.builder == "")
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'builder' missing"), .msg = hintfmt("required attribute 'builder' missing"),
.errPos = state.positions[posDrvName] .errPos = state.positions[posDrvName]
}); }));
if (drv.platform == "") if (drv.platform == "")
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("required attribute 'system' missing"), .msg = hintfmt("required attribute 'system' missing"),
.errPos = state.positions[posDrvName] .errPos = state.positions[posDrvName]
}); }));
/* Check whether the derivation name is valid. */ /* Check whether the derivation name is valid. */
if (isDerivation(drvName)) if (isDerivation(drvName))
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension), .msg = hintfmt("derivation names are not allowed to end in '%s'", drvExtension),
.errPos = state.positions[posDrvName] .errPos = state.positions[posDrvName]
}); }));
if (outputHash) { if (outputHash) {
/* Handle fixed-output derivations. /* Handle fixed-output derivations.
@ -1187,10 +1220,10 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * *
Ignore `__contentAddressed` because fixed output derivations are Ignore `__contentAddressed` because fixed output derivations are
already content addressed. */ already content addressed. */
if (outputs.size() != 1 || *(outputs.begin()) != "out") if (outputs.size() != 1 || *(outputs.begin()) != "out")
throw Error({ state.debugThrowLastTrace(Error({
.msg = hintfmt("multiple outputs are not supported in fixed-output derivations"), .msg = hintfmt("multiple outputs are not supported in fixed-output derivations"),
.errPos = state.positions[posDrvName] .errPos = state.positions[posDrvName]
}); }));
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo)); auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
@ -1358,10 +1391,10 @@ static RegisterPrimOp primop_toPath({
static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
if (evalSettings.pureEval) if (evalSettings.pureEval)
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"), .msg = hintfmt("'%s' is not allowed in pure evaluation mode", "builtins.storePath"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
PathSet context; PathSet context;
Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context)); Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context));
@ -1370,10 +1403,10 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
e.g. nix-push does the right thing. */ e.g. nix-push does the right thing. */
if (!state.store->isStorePath(path)) path = canonPath(path, true); if (!state.store->isStorePath(path)) path = canonPath(path, true);
if (!state.store->isInStore(path)) if (!state.store->isInStore(path))
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("path '%1%' is not in the Nix store", path), .msg = hintfmt("path '%1%' is not in the Nix store", path),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
auto path2 = state.store->toStorePath(path).first; auto path2 = state.store->toStorePath(path).first;
if (!settings.readOnlyMode) if (!settings.readOnlyMode)
state.store->ensurePath(path2); state.store->ensurePath(path2);
@ -1476,7 +1509,7 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V
auto path = realisePath(state, pos, *args[0]); auto path = realisePath(state, pos, *args[0]);
auto s = readFile(path); auto s = readFile(path);
if (s.find((char) 0) != std::string::npos) if (s.find((char) 0) != std::string::npos)
throw Error("the contents of the file '%1%' cannot be represented as a Nix string", path); state.debugThrowLastTrace(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
StorePathSet refs; StorePathSet refs;
if (state.store->isInStore(path)) { if (state.store->isInStore(path)) {
try { try {
@ -1528,13 +1561,12 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
auto rewrites = state.realiseContext(context); auto rewrites = state.realiseContext(context);
path = rewriteStrings(path, rewrites); path = rewriteStrings(path, rewrites);
} catch (InvalidPathError & e) { } catch (InvalidPathError & e) {
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path), .msg = hintfmt("cannot find '%1%', since path '%2%' is not valid", path, e.path),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
} }
searchPath.emplace_back(prefix, path); searchPath.emplace_back(prefix, path);
} }
@ -1555,10 +1587,10 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
auto type = state.forceStringNoCtx(*args[0], pos); auto type = state.forceStringNoCtx(*args[0], pos);
std::optional<HashType> ht = parseHashType(type); std::optional<HashType> ht = parseHashType(type);
if (!ht) if (!ht)
throw Error({ state.debugThrowLastTrace(Error({
.msg = hintfmt("unknown hash type '%1%'", type), .msg = hintfmt("unknown hash type '%1%'", type),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
auto path = realisePath(state, pos, *args[1]); auto path = realisePath(state, pos, *args[1]);
@ -1795,13 +1827,13 @@ static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Val
for (auto path : context) { for (auto path : context) {
if (path.at(0) != '/') if (path.at(0) != '/')
throw EvalError( { state.debugThrowLastTrace(EvalError({
.msg = hintfmt( .msg = hintfmt(
"in 'toFile': the file named '%1%' must not contain a reference " "in 'toFile': the file named '%1%' must not contain a reference "
"to a derivation but contains (%2%)", "to a derivation but contains (%2%)",
name, path), name, path),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
refs.insert(state.store->parseStorePath(path)); refs.insert(state.store->parseStorePath(path));
} }
@ -1959,7 +1991,7 @@ static void addPath(
? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first ? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first
: state.store->addToStore(name, path, method, htSHA256, filter, state.repair, refs); : state.store->addToStore(name, path, method, htSHA256, filter, state.repair, refs);
if (expectedHash && expectedStorePath != dstPath) if (expectedHash && expectedStorePath != dstPath)
throw Error("store path mismatch in (possibly filtered) path added from '%s'", path); state.debugThrowLastTrace(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
state.allowAndSetStorePathString(dstPath, v); state.allowAndSetStorePathString(dstPath, v);
} else } else
state.allowAndSetStorePathString(*expectedStorePath, v); state.allowAndSetStorePathString(*expectedStorePath, v);
@ -1977,12 +2009,12 @@ static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * arg
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
if (args[0]->type() != nFunction) if (args[0]->type() != nFunction)
throw TypeError({ state.debugThrowLastTrace(TypeError({
.msg = hintfmt( .msg = hintfmt(
"first argument in call to 'filterSource' is not a function but %1%", "first argument in call to 'filterSource' is not a function but %1%",
showType(*args[0])), showType(*args[0])),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context); addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
} }
@ -2066,16 +2098,16 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
else if (n == "sha256") else if (n == "sha256")
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256); expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256);
else else
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]), .msg = hintfmt("unsupported argument '%1%' to 'addPath'", state.symbols[attr.name]),
.errPos = state.positions[attr.pos] .errPos = state.positions[attr.pos]
}); }));
} }
if (path.empty()) if (path.empty())
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'path' required"), .msg = hintfmt("'path' required"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
if (name.empty()) if (name.empty())
name = baseNameOf(path); name = baseNameOf(path);
@ -2447,10 +2479,10 @@ static void prim_functionArgs(EvalState & state, const PosIdx pos, Value * * arg
return; return;
} }
if (!args[0]->isLambda()) if (!args[0]->isLambda())
throw TypeError({ state.debugThrowLastTrace(TypeError({
.msg = hintfmt("'functionArgs' requires a function"), .msg = hintfmt("'functionArgs' requires a function"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
if (!args[0]->lambda.fun->hasFormals()) { if (!args[0]->lambda.fun->hasFormals()) {
v.mkAttrs(&state.emptyBindings); v.mkAttrs(&state.emptyBindings);
@ -2538,7 +2570,7 @@ static void prim_zipAttrsWith(EvalState & state, const PosIdx pos, Value * * arg
attrsSeen[attr.name].first++; attrsSeen[attr.name].first++;
} catch (TypeError & e) { } catch (TypeError & e) {
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith")); e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "zipAttrsWith"));
throw; state.debugThrowLastTrace(e);
} }
} }
@ -2625,10 +2657,10 @@ static void elemAt(EvalState & state, const PosIdx pos, Value & list, int n, Val
{ {
state.forceList(list, pos); state.forceList(list, pos);
if (n < 0 || (unsigned int) n >= list.listSize()) if (n < 0 || (unsigned int) n >= list.listSize())
throw Error({ state.debugThrowLastTrace(Error({
.msg = hintfmt("list index %1% is out of bounds", n), .msg = hintfmt("list index %1% is out of bounds", n),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
state.forceValue(*list.listElems()[n], pos); state.forceValue(*list.listElems()[n], pos);
v = *list.listElems()[n]; v = *list.listElems()[n];
} }
@ -2673,10 +2705,10 @@ static void prim_tail(EvalState & state, const PosIdx pos, Value * * args, Value
{ {
state.forceList(*args[0], pos); state.forceList(*args[0], pos);
if (args[0]->listSize() == 0) if (args[0]->listSize() == 0)
throw Error({ state.debugThrowLastTrace(Error({
.msg = hintfmt("'tail' called on an empty list"), .msg = hintfmt("'tail' called on an empty list"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
state.mkList(v, args[0]->listSize() - 1); state.mkList(v, args[0]->listSize() - 1);
for (unsigned int n = 0; n < v.listSize(); ++n) for (unsigned int n = 0; n < v.listSize(); ++n)
@ -2911,10 +2943,10 @@ static void prim_genList(EvalState & state, const PosIdx pos, Value * * args, Va
auto len = state.forceInt(*args[1], pos); auto len = state.forceInt(*args[1], pos);
if (len < 0) if (len < 0)
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("cannot create list of size %1%", len), .msg = hintfmt("cannot create list of size %1%", len),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
state.mkList(v, len); state.mkList(v, len);
@ -3123,7 +3155,7 @@ static void prim_concatMap(EvalState & state, const PosIdx pos, Value * * args,
state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos))); state.forceList(lists[n], lists[n].determinePos(args[0]->determinePos(pos)));
} catch (TypeError &e) { } catch (TypeError &e) {
e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap")); e.addTrace(state.positions[pos], hintfmt("while invoking '%s'", "concatMap"));
throw; state.debugThrowLastTrace(e);
} }
len += lists[n].listSize(); len += lists[n].listSize();
} }
@ -3218,10 +3250,10 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
NixFloat f2 = state.forceFloat(*args[1], pos); NixFloat f2 = state.forceFloat(*args[1], pos);
if (f2 == 0) if (f2 == 0)
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("division by zero"), .msg = hintfmt("division by zero"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
if (args[0]->type() == nFloat || args[1]->type() == nFloat) { if (args[0]->type() == nFloat || args[1]->type() == nFloat) {
v.mkFloat(state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos)); v.mkFloat(state.forceFloat(*args[0], pos) / state.forceFloat(*args[1], pos));
@ -3230,10 +3262,10 @@ static void prim_div(EvalState & state, const PosIdx pos, Value * * args, Value
NixInt i2 = state.forceInt(*args[1], pos); NixInt i2 = state.forceInt(*args[1], pos);
/* Avoid division overflow as it might raise SIGFPE. */ /* Avoid division overflow as it might raise SIGFPE. */
if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1) if (i1 == std::numeric_limits<NixInt>::min() && i2 == -1)
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("overflow in integer division"), .msg = hintfmt("overflow in integer division"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
v.mkInt(i1 / i2); v.mkInt(i1 / i2);
} }
@ -3361,10 +3393,10 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args,
auto s = state.coerceToString(pos, *args[2], context); auto s = state.coerceToString(pos, *args[2], context);
if (start < 0) if (start < 0)
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("negative start position in 'substring'"), .msg = hintfmt("negative start position in 'substring'"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
v.mkString((unsigned int) start >= s->size() ? "" : s->substr(start, len), context); v.mkString((unsigned int) start >= s->size() ? "" : s->substr(start, len), context);
} }
@ -3412,10 +3444,10 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args,
auto type = state.forceStringNoCtx(*args[0], pos); auto type = state.forceStringNoCtx(*args[0], pos);
std::optional<HashType> ht = parseHashType(type); std::optional<HashType> ht = parseHashType(type);
if (!ht) if (!ht)
throw Error({ state.debugThrowLastTrace(Error({
.msg = hintfmt("unknown hash type '%1%'", type), .msg = hintfmt("unknown hash type '%1%'", type),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
PathSet context; // discarded PathSet context; // discarded
auto s = state.forceString(*args[1], context, pos); auto s = state.forceString(*args[1], context, pos);
@ -3485,16 +3517,15 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
} catch (std::regex_error & e) { } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) { if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re), .msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
} else { } else
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid regular expression '%s'", re), .msg = hintfmt("invalid regular expression '%s'", re),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
}
} }
} }
@ -3590,16 +3621,15 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
} catch (std::regex_error & e) { } catch (std::regex_error & e) {
if (e.code() == std::regex_constants::error_space) { if (e.code() == std::regex_constants::error_space) {
// limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++ // limit is _GLIBCXX_REGEX_STATE_LIMIT for libstdc++
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("memory limit exceeded by regular expression '%s'", re), .msg = hintfmt("memory limit exceeded by regular expression '%s'", re),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
} else { } else
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("invalid regular expression '%s'", re), .msg = hintfmt("invalid regular expression '%s'", re),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
}
} }
} }
@ -3675,10 +3705,10 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
state.forceList(*args[0], pos); state.forceList(*args[0], pos);
state.forceList(*args[1], pos); state.forceList(*args[1], pos);
if (args[0]->listSize() != args[1]->listSize()) if (args[0]->listSize() != args[1]->listSize())
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"), .msg = hintfmt("'from' and 'to' arguments to 'replaceStrings' have different lengths"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
std::vector<std::string> from; std::vector<std::string> from;
from.reserve(args[0]->listSize()); from.reserve(args[0]->listSize());
@ -3931,7 +3961,7 @@ void EvalState::createBaseEnv()
because attribute lookups expect it to be sorted. */ because attribute lookups expect it to be sorted. */
baseEnv.values[0]->attrs->sort(); baseEnv.values[0]->attrs->sort();
staticBaseEnv.sort(); staticBaseEnv->sort();
/* Note: we have to initialize the 'derivation' constant *after* /* Note: we have to initialize the 'derivation' constant *after*
building baseEnv/staticBaseEnv because it uses 'builtins'. */ building baseEnv/staticBaseEnv because it uses 'builtins'. */

View file

@ -108,16 +108,16 @@ 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)
throw Error({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unexpected attribute 'type'"), .msg = hintfmt("unexpected attribute 'type'"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
type = state.forceStringNoCtx(*aType->value, aType->pos); type = state.forceStringNoCtx(*aType->value, aType->pos);
} else if (!type) } else if (!type)
throw Error({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"), .msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
attrs.emplace("type", type.value()); attrs.emplace("type", type.value());
@ -138,16 +138,16 @@ static void fetchTree(
else if (attr.value->type() == nInt) else if (attr.value->type() == nInt)
attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer)); attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
else else
throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected", state.debugThrowLastTrace(TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
state.symbols[attr.name], showType(*attr.value)); state.symbols[attr.name], showType(*attr.value)));
} }
if (!params.allowNameArgument) if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
throw Error({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"), .msg = hintfmt("attribute 'name' isnt supported in call to 'fetchTree'"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
input = fetchers::Input::fromAttrs(std::move(attrs)); input = fetchers::Input::fromAttrs(std::move(attrs));
} else { } else {
@ -167,7 +167,7 @@ static void fetchTree(
input = lookupInRegistries(state.store, input).first; input = lookupInRegistries(state.store, input).first;
if (evalSettings.pureEval && !input.isLocked()) if (evalSettings.pureEval && !input.isLocked())
throw Error("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]); state.debugThrowLastTrace(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]));
auto [tree, input2] = input.fetch(state.store); auto [tree, input2] = input.fetch(state.store);
@ -206,17 +206,17 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
else if (n == "name") else if (n == "name")
name = state.forceStringNoCtx(*attr.value, attr.pos); name = state.forceStringNoCtx(*attr.value, attr.pos);
else else
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who), .msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
.errPos = state.positions[attr.pos] .errPos = state.positions[attr.pos]
}); }));
} }
if (!url) if (!url)
throw EvalError({ state.debugThrowLastTrace(EvalError({
.msg = hintfmt("'url' argument required"), .msg = hintfmt("'url' argument required"),
.errPos = state.positions[pos] .errPos = state.positions[pos]
}); }));
} else } else
url = state.forceStringNoCtx(*args[0], pos); url = state.forceStringNoCtx(*args[0], pos);
@ -228,7 +228,7 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
name = baseNameOf(*url); name = baseNameOf(*url);
if (evalSettings.pureEval && !expectedHash) if (evalSettings.pureEval && !expectedHash)
throw Error("in pure evaluation mode, '%s' requires a 'sha256' argument", who); state.debugThrowLastTrace(EvalError("in pure evaluation mode, '%s' requires a 'sha256' argument", who));
// early exit if pinned and already in the store // early exit if pinned and already in the store
if (expectedHash && expectedHash->type == htSHA256) { if (expectedHash && expectedHash->type == htSHA256) {
@ -255,8 +255,8 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
? state.store->queryPathInfo(storePath)->narHash ? state.store->queryPathInfo(storePath)->narHash
: hashFile(htSHA256, state.store->toRealPath(storePath)); : hashFile(htSHA256, state.store->toRealPath(storePath));
if (hash != *expectedHash) if (hash != *expectedHash)
throw Error((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s", state.debugThrowLastTrace(EvalError((unsigned int) 102, "hash mismatch in file downloaded from '%s':\n specified: %s\n got: %s",
*url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true)); *url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true)));
} }
state.allowAndSetStorePathString(storePath, v); state.allowAndSetStorePathString(storePath, v);

View file

@ -85,6 +85,7 @@ void printValueAsJSON(EvalState & state, bool strict,
.errPos = state.positions[v.determinePos(pos)] .errPos = state.positions[v.determinePos(pos)]
}); });
e.addTrace(state.positions[pos], hintfmt("message for the trace")); e.addTrace(state.positions[pos], hintfmt("message for the trace"));
state.debugThrowLastTrace(e);
throw e; throw e;
} }
} }
@ -99,7 +100,7 @@ void printValueAsJSON(EvalState & state, bool strict,
void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
JSONPlaceholder & out, PathSet & context) const JSONPlaceholder & out, PathSet & context) const
{ {
throw TypeError("cannot convert %1% to JSON", showType()); state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
} }

View file

@ -449,11 +449,10 @@ struct GitInputScheme : InputScheme
} }
} }
const Attrs unlockedAttrs({ Attrs unlockedAttrs({
{"type", cacheType}, {"type", cacheType},
{"name", name}, {"name", name},
{"url", actualUrl}, {"url", actualUrl},
{"ref", *input.getRef()},
}); });
Path repoDir; Path repoDir;
@ -466,6 +465,7 @@ struct GitInputScheme : InputScheme
head = "master"; head = "master";
} }
input.attrs.insert_or_assign("ref", *head); input.attrs.insert_or_assign("ref", *head);
unlockedAttrs.insert_or_assign("ref", *head);
} }
if (!input.getRev()) if (!input.getRev())
@ -482,6 +482,7 @@ struct GitInputScheme : InputScheme
head = "master"; head = "master";
} }
input.attrs.insert_or_assign("ref", *head); input.attrs.insert_or_assign("ref", *head);
unlockedAttrs.insert_or_assign("ref", *head);
} }
if (auto res = getCache()->lookup(store, unlockedAttrs)) { if (auto res = getCache()->lookup(store, unlockedAttrs)) {

View file

@ -6,6 +6,7 @@
#include "archive.hh" #include "archive.hh"
#include "tarfile.hh" #include "tarfile.hh"
#include "types.hh" #include "types.hh"
#include "split.hh"
namespace nix::fetchers { namespace nix::fetchers {
@ -168,24 +169,34 @@ std::pair<Tree, time_t> downloadTarball(
}; };
} }
struct TarballInputScheme : InputScheme // An input scheme corresponding to a curl-downloadable resource.
struct CurlInputScheme : InputScheme
{ {
virtual const std::string inputType() const = 0;
const std::set<std::string> transportUrlSchemes = {"file", "http", "https"};
const bool hasTarballExtension(std::string_view path) const
{
return hasSuffix(path, ".zip") || hasSuffix(path, ".tar")
|| hasSuffix(path, ".tgz") || hasSuffix(path, ".tar.gz")
|| hasSuffix(path, ".tar.xz") || hasSuffix(path, ".tar.bz2")
|| hasSuffix(path, ".tar.zst");
}
virtual bool isValidURL(const ParsedURL & url) const = 0;
std::optional<Input> inputFromURL(const ParsedURL & url) override std::optional<Input> inputFromURL(const ParsedURL & url) override
{ {
if (url.scheme != "file" && url.scheme != "http" && url.scheme != "https") return {}; if (!isValidURL(url))
return std::nullopt;
if (!hasSuffix(url.path, ".zip")
&& !hasSuffix(url.path, ".tar")
&& !hasSuffix(url.path, ".tgz")
&& !hasSuffix(url.path, ".tar.gz")
&& !hasSuffix(url.path, ".tar.xz")
&& !hasSuffix(url.path, ".tar.bz2")
&& !hasSuffix(url.path, ".tar.zst"))
return {};
Input input; Input input;
input.attrs.insert_or_assign("type", "tarball");
input.attrs.insert_or_assign("url", url.to_string()); auto urlWithoutApplicationScheme = url;
urlWithoutApplicationScheme.scheme = parseUrlScheme(url.scheme).transport;
input.attrs.insert_or_assign("type", inputType());
input.attrs.insert_or_assign("url", urlWithoutApplicationScheme.to_string());
auto narHash = url.query.find("narHash"); auto narHash = url.query.find("narHash");
if (narHash != url.query.end()) if (narHash != url.query.end())
input.attrs.insert_or_assign("narHash", narHash->second); input.attrs.insert_or_assign("narHash", narHash->second);
@ -194,14 +205,17 @@ struct TarballInputScheme : InputScheme
std::optional<Input> inputFromAttrs(const Attrs & attrs) override std::optional<Input> inputFromAttrs(const Attrs & attrs) override
{ {
if (maybeGetStrAttr(attrs, "type") != "tarball") return {}; auto type = maybeGetStrAttr(attrs, "type");
if (type != inputType()) return {};
std::set<std::string> allowedNames = {"type", "url", "narHash", "name", "unpack"};
for (auto & [name, value] : attrs) for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && /* name != "hash" && */ name != "narHash" && name != "name") if (!allowedNames.count(name))
throw Error("unsupported tarball input attribute '%s'", name); throw Error("unsupported %s input attribute '%s'", *type, name);
Input input; Input input;
input.attrs = attrs; input.attrs = attrs;
//input.locked = (bool) maybeGetStrAttr(input.attrs, "hash"); //input.locked = (bool) maybeGetStrAttr(input.attrs, "hash");
return input; return input;
} }
@ -209,14 +223,9 @@ struct TarballInputScheme : InputScheme
ParsedURL toURL(const Input & input) override ParsedURL toURL(const Input & input) override
{ {
auto url = parseURL(getStrAttr(input.attrs, "url")); auto url = parseURL(getStrAttr(input.attrs, "url"));
// NAR hashes are preferred over file hashes since tar/zip files // NAR hashes are preferred over file hashes since tar/zip files // don't have a canonical representation.
// don't have a canonical representation.
if (auto narHash = input.getNarHash()) if (auto narHash = input.getNarHash())
url.query.insert_or_assign("narHash", narHash->to_string(SRI, true)); url.query.insert_or_assign("narHash", narHash->to_string(SRI, true));
/*
else if (auto hash = maybeGetStrAttr(input.attrs, "hash"))
url.query.insert_or_assign("hash", Hash(*hash).to_string(SRI, true));
*/
return url; return url;
} }
@ -225,6 +234,42 @@ struct TarballInputScheme : InputScheme
return true; return true;
} }
};
struct FileInputScheme : CurlInputScheme
{
const std::string inputType() const override { return "file"; }
bool isValidURL(const ParsedURL & url) const override
{
auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType()
: !hasTarballExtension(url.path));
}
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
{
auto file = downloadFile(store, getStrAttr(input.attrs, "url"), input.getName(), false);
return {std::move(file.storePath), input};
}
};
struct TarballInputScheme : CurlInputScheme
{
const std::string inputType() const override { return "tarball"; }
bool isValidURL(const ParsedURL & url) const override
{
auto parsedUrlScheme = parseUrlScheme(url.scheme);
return transportUrlSchemes.count(std::string(parsedUrlScheme.transport))
&& (parsedUrlScheme.application
? parsedUrlScheme.application.value() == inputType()
: hasTarballExtension(url.path));
}
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
{ {
auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first; auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first;
@ -233,5 +278,6 @@ struct TarballInputScheme : InputScheme
}; };
static auto rTarballInputScheme = OnStartup([] { registerInputScheme(std::make_unique<TarballInputScheme>()); }); static auto rTarballInputScheme = OnStartup([] { registerInputScheme(std::make_unique<TarballInputScheme>()); });
static auto rFileInputScheme = OnStartup([] { registerInputScheme(std::make_unique<FileInputScheme>()); });
} }

View file

@ -161,7 +161,12 @@ protected:
void getFile(const std::string & path, void getFile(const std::string & path,
Callback<std::optional<std::string>> callback) noexcept override Callback<std::optional<std::string>> callback) noexcept override
{ {
try {
checkEnabled(); checkEnabled();
} catch (...) {
callback.rethrow();
return;
}
auto request(makeRequest(path)); auto request(makeRequest(path));

View file

@ -718,13 +718,12 @@ void RemoteStore::registerDrvOutput(const Realisation & info)
void RemoteStore::queryRealisationUncached(const DrvOutput & id, void RemoteStore::queryRealisationUncached(const DrvOutput & id,
Callback<std::shared_ptr<const Realisation>> callback) noexcept Callback<std::shared_ptr<const Realisation>> callback) noexcept
{ {
try {
auto conn(getConnection()); auto conn(getConnection());
if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 27) { if (GET_PROTOCOL_MINOR(conn->daemonVersion) < 27) {
warn("the daemon is too old to support content-addressed derivations, please upgrade it to 2.4"); warn("the daemon is too old to support content-addressed derivations, please upgrade it to 2.4");
try { return callback(nullptr);
callback(nullptr);
} catch (...) { return callback.rethrow(); }
} }
conn->to << wopQueryRealisation; conn->to << wopQueryRealisation;
@ -747,7 +746,6 @@ void RemoteStore::queryRealisationUncached(const DrvOutput & id,
} }
}(); }();
try {
callback(std::shared_ptr<const Realisation>(real)); callback(std::shared_ptr<const Realisation>(real));
} catch (...) { return callback.rethrow(); } } catch (...) { return callback.rethrow(); }
} }

View file

@ -98,6 +98,15 @@ struct ErrPos {
} }
}; };
std::optional<LinesOfCode> getCodeLines(const ErrPos & errPos);
void printCodeLines(std::ostream & out,
const std::string & prefix,
const ErrPos & errPos,
const LinesOfCode & loc);
void printAtPos(const ErrPos & pos, std::ostream & out);
struct Trace { struct Trace {
std::optional<ErrPos> pos; std::optional<ErrPos> pos;
hintformat hint; hintformat hint;

View file

@ -1,6 +1,7 @@
#include "json.hh" #include "json.hh"
#include <iomanip> #include <iomanip>
#include <cstdint>
#include <cstring> #include <cstring>
namespace nix { namespace nix {

View file

@ -7,7 +7,7 @@
namespace nix { namespace nix {
/* A simple non-nullable reference-counted pointer. Actually a wrapper /* A simple non-nullable reference-counted pointer. Actually a wrapper
around std::shared_ptr that prevents non-null constructions. */ around std::shared_ptr that prevents null constructions. */
template<typename T> template<typename T>
class ref class ref
{ {

View file

@ -1,6 +1,7 @@
#include "url.hh" #include "url.hh"
#include "url-parts.hh" #include "url-parts.hh"
#include "util.hh" #include "util.hh"
#include "split.hh"
namespace nix { namespace nix {
@ -136,4 +137,21 @@ bool ParsedURL::operator ==(const ParsedURL & other) const
&& fragment == other.fragment; && fragment == other.fragment;
} }
/**
* Parse a URL scheme of the form '(applicationScheme\+)?transportScheme'
* into a tuple '(applicationScheme, transportScheme)'
*
* > parseUrlScheme("http") == ParsedUrlScheme{ {}, "http"}
* > parseUrlScheme("tarball+http") == ParsedUrlScheme{ {"tarball"}, "http"}
*/
ParsedUrlScheme parseUrlScheme(std::string_view scheme)
{
auto application = splitPrefixTo(scheme, '+');
auto transport = scheme;
return ParsedUrlScheme {
.application = application,
.transport = transport,
};
}
} }

View file

@ -27,4 +27,19 @@ std::map<std::string, std::string> decodeQuery(const std::string & query);
ParsedURL parseURL(const std::string & url); ParsedURL parseURL(const std::string & url);
/*
* Although thats not really standardized anywhere, an number of tools
* use a scheme of the form 'x+y' in urls, where y is the transport layer
* scheme, and x is the application layer scheme.
*
* For example git uses `git+https` to designate remotes using a Git
* protocol over http.
*/
struct ParsedUrlScheme {
std::optional<std::string_view> application;
std::string_view transport;
};
ParsedUrlScheme parseUrlScheme(std::string_view scheme);
} }

View file

@ -1818,7 +1818,7 @@ AutoCloseFD createUnixDomainSocket(const Path & path, mode_t mode)
if (chmod(path.c_str(), mode) == -1) if (chmod(path.c_str(), mode) == -1)
throw SysError("changing permissions on '%1%'", path); throw SysError("changing permissions on '%1%'", path);
if (listen(fdSocket.get(), 5) == -1) if (listen(fdSocket.get(), 100) == -1)
throw SysError("cannot listen on socket '%1%'", path); throw SysError("cannot listen on socket '%1%'", path);
return fdSocket; return fdSocket;

View file

@ -181,9 +181,17 @@ Currently the `type` attribute can be one of the following:
* `tarball`: Tarballs. The location of the tarball is specified by the * `tarball`: Tarballs. The location of the tarball is specified by the
attribute `url`. attribute `url`.
In URL form, the schema must be `http://`, `https://` or `file://` In URL form, the schema must be `tarball+http://`, `tarball+https://` or `tarball+file://`.
URLs and the extension must be `.zip`, `.tar`, `.tgz`, `.tar.gz`, If the extension corresponds to a known archive format (`.zip`, `.tar`,
`.tar.xz`, `.tar.bz2` or `.tar.zst`. `.tgz`, `.tar.gz`, `.tar.xz`, `.tar.bz2` or `.tar.zst`), then the `tarball+`
can be dropped.
* `file`: Plain files or directory tarballs, either over http(s) or from the local
disk.
In URL form, the schema must be `file+http://`, `file+https://` or `file+file://`.
If the extension doesnt correspond to a known archive format (as defined by the
`tarball` fetcher), then the `file+` prefix can be dropped.
* `github`: A more efficient way to fetch repositories from * `github`: A more efficient way to fetch repositories from
GitHub. The following attributes are required: GitHub. The following attributes are required:

View file

@ -263,7 +263,8 @@ builtPathsPerInstallable(
struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
{ {
std::optional<int> priority; std::optional<int64_t> priority;
CmdProfileInstall() { CmdProfileInstall() {
addFlag({ addFlag({
.longName = "priority", .longName = "priority",

105
tests/fetchTree-file.sh Normal file
View file

@ -0,0 +1,105 @@
source common.sh
clearStore
cd "$TEST_ROOT"
test_fetch_file () {
echo foo > test_input
input_hash="$(nix hash path test_input)"
nix eval --impure --file - <<EOF
let
tree = builtins.fetchTree { type = "file"; url = "file://$PWD/test_input"; };
in
assert (tree.narHash == "$input_hash");
tree
EOF
}
# Make sure that `http(s)` and `file` flake inputs are properly extracted when
# they should be, and treated as opaque files when they should be
test_file_flake_input () {
rm -fr "$TEST_ROOT/testFlake";
mkdir "$TEST_ROOT/testFlake";
pushd testFlake
mkdir inputs
echo foo > inputs/test_input_file
tar cfa test_input.tar.gz inputs
cp test_input.tar.gz test_input_no_ext
input_tarball_hash="$(nix hash path test_input.tar.gz)"
input_directory_hash="$(nix hash path inputs)"
cat <<EOF > flake.nix
{
inputs.no_ext_default_no_unpack = {
url = "file://$PWD/test_input_no_ext";
flake = false;
};
inputs.no_ext_explicit_unpack = {
url = "tarball+file://$PWD/test_input_no_ext";
flake = false;
};
inputs.tarball_default_unpack = {
url = "file://$PWD/test_input.tar.gz";
flake = false;
};
inputs.tarball_explicit_no_unpack = {
url = "file+file://$PWD/test_input.tar.gz";
flake = false;
};
outputs = { ... }: {};
}
EOF
nix flake update
nix eval --file - <<EOF
with (builtins.fromJSON (builtins.readFile ./flake.lock));
# Url inputs whose extension doesnt match a know archive format should
# not be unpacked by default
assert (nodes.no_ext_default_no_unpack.locked.type == "file");
assert (nodes.no_ext_default_no_unpack.locked.unpack or false == false);
assert (nodes.no_ext_default_no_unpack.locked.narHash == "$input_tarball_hash");
# For backwards compatibility, flake inputs that correspond to the
# old 'tarball' fetcher should still have their type set to 'tarball'
assert (nodes.tarball_default_unpack.locked.type == "tarball");
# Unless explicitely specified, the 'unpack' parameter shouldnt appear here
# because that would break older Nix versions
assert (!nodes.tarball_default_unpack.locked ? unpack);
assert (nodes.tarball_default_unpack.locked.narHash == "$input_directory_hash");
# Explicitely passing the unpack parameter should enforce the desired behavior
assert (nodes.no_ext_explicit_unpack.locked.narHash == nodes.tarball_default_unpack.locked.narHash);
assert (nodes.tarball_explicit_no_unpack.locked.narHash == nodes.no_ext_default_no_unpack.locked.narHash);
true
EOF
popd
[[ -z "${NIX_DAEMON_PACKAGE}" ]] && return 0
# Ensure that a lockfile generated by the current Nix for tarball inputs
# can still be read by an older Nix
cat <<EOF > flake.nix
{
inputs.tarball = {
url = "file://$PWD/test_input.tar.gz";
flake = false;
};
outputs = { self, tarball }: {
foo = builtins.readFile "${tarball}/test_input_file";
};
}
nix flake update
clearStore
"$NIX_DAEMON_PACKAGE/bin/nix" eval .#foo
EOF
}
test_fetch_file
test_file_flake_input

View file

@ -0,0 +1,3 @@
# https://github.com/NixOS/nix/issues/6562
# Note that this file must not end with a newline.
a 1"$

View file

@ -23,6 +23,7 @@ nix_tests = \
fetchGit.sh \ fetchGit.sh \
fetchurl.sh \ fetchurl.sh \
fetchPath.sh \ fetchPath.sh \
fetchTree-file.sh \
simple.sh \ simple.sh \
referrers.sh \ referrers.sh \
optimise-store.sh \ optimise-store.sh \