mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-29 17:16:15 +02:00
Merge remote-tracking branch 'upstream/master' into overlayfs-store
This commit is contained in:
commit
d5fe828d76
164 changed files with 3477 additions and 1623 deletions
17
.github/labeler.yml
vendored
17
.github/labeler.yml
vendored
|
@ -2,5 +2,22 @@
|
||||||
- doc/manual/*
|
- doc/manual/*
|
||||||
- src/nix/**/*.md
|
- src/nix/**/*.md
|
||||||
|
|
||||||
|
"store":
|
||||||
|
- src/libstore/store-api.*
|
||||||
|
- src/libstore/*-store.*
|
||||||
|
|
||||||
|
"fetching":
|
||||||
|
- src/libfetchers/**/*
|
||||||
|
|
||||||
|
"repl":
|
||||||
|
- src/libcmd/repl.*
|
||||||
|
- src/nix/repl.*
|
||||||
|
|
||||||
|
"new-cli":
|
||||||
|
- src/nix/**/*
|
||||||
|
|
||||||
"tests":
|
"tests":
|
||||||
|
# Unit tests
|
||||||
|
- src/*/tests/**/*
|
||||||
|
# Functional and integration tests
|
||||||
- tests/**/*
|
- tests/**/*
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -19,9 +19,12 @@ perl/Makefile.config
|
||||||
/doc/manual/nix.json
|
/doc/manual/nix.json
|
||||||
/doc/manual/conf-file.json
|
/doc/manual/conf-file.json
|
||||||
/doc/manual/builtins.json
|
/doc/manual/builtins.json
|
||||||
|
/doc/manual/xp-features.json
|
||||||
/doc/manual/src/SUMMARY.md
|
/doc/manual/src/SUMMARY.md
|
||||||
/doc/manual/src/command-ref/new-cli
|
/doc/manual/src/command-ref/new-cli
|
||||||
/doc/manual/src/command-ref/conf-file.md
|
/doc/manual/src/command-ref/conf-file.md
|
||||||
|
/doc/manual/src/command-ref/experimental-features-shortlist.md
|
||||||
|
/doc/manual/src/contributing/experimental-feature-descriptions.md
|
||||||
/doc/manual/src/language/builtins.md
|
/doc/manual/src/language/builtins.md
|
||||||
|
|
||||||
# /scripts/
|
# /scripts/
|
||||||
|
|
2
.version
2
.version
|
@ -1 +1 @@
|
||||||
2.15.0
|
2.16.0
|
||||||
|
|
|
@ -184,7 +184,7 @@ fi
|
||||||
|
|
||||||
# Look for OpenSSL, a required dependency. FIXME: this is only (maybe)
|
# Look for OpenSSL, a required dependency. FIXME: this is only (maybe)
|
||||||
# used by S3BinaryCacheStore.
|
# used by S3BinaryCacheStore.
|
||||||
PKG_CHECK_MODULES([OPENSSL], [libcrypto], [CXXFLAGS="$OPENSSL_CFLAGS $CXXFLAGS"])
|
PKG_CHECK_MODULES([OPENSSL], [libcrypto >= 1.1.1], [CXXFLAGS="$OPENSSL_CFLAGS $CXXFLAGS"])
|
||||||
|
|
||||||
|
|
||||||
# Look for libarchive.
|
# Look for libarchive.
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
builtinsDump:
|
let
|
||||||
|
inherit (builtins) concatStringsSep attrNames;
|
||||||
|
in
|
||||||
|
|
||||||
|
builtinsInfo:
|
||||||
let
|
let
|
||||||
showBuiltin = name:
|
showBuiltin = name:
|
||||||
let
|
let
|
||||||
inherit (builtinsDump.${name}) doc args;
|
inherit (builtinsInfo.${name}) doc args;
|
||||||
in
|
in
|
||||||
''
|
''
|
||||||
<dt id="builtins-${name}">
|
<dt id="builtins-${name}">
|
||||||
|
@ -14,7 +18,7 @@ let
|
||||||
|
|
||||||
</dd>
|
</dd>
|
||||||
'';
|
'';
|
||||||
listArgs = args: builtins.concatStringsSep " " (map (s: "<var>${s}</var>") args);
|
listArgs = args: concatStringsSep " " (map (s: "<var>${s}</var>") args);
|
||||||
in
|
in
|
||||||
with builtins; concatStringsSep "\n" (map showBuiltin (attrNames builtinsDump))
|
concatStringsSep "\n" (map showBuiltin (attrNames builtinsInfo))
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,24 @@
|
||||||
cliDumpStr:
|
let
|
||||||
|
inherit (builtins)
|
||||||
|
attrNames attrValues fromJSON listToAttrs mapAttrs
|
||||||
|
concatStringsSep concatMap length lessThan replaceStrings sort;
|
||||||
|
inherit (import ./utils.nix) concatStrings optionalString filterAttrs trim squash unique showSettings;
|
||||||
|
in
|
||||||
|
|
||||||
with builtins;
|
commandDump:
|
||||||
with import ./utils.nix;
|
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
|
commandInfo = fromJSON commandDump;
|
||||||
|
|
||||||
showCommand = { command, details, filename, toplevel }:
|
showCommand = { command, details, filename, toplevel }:
|
||||||
let
|
let
|
||||||
|
|
||||||
result = ''
|
result = ''
|
||||||
> **Warning** \
|
> **Warning** \
|
||||||
> This program is **experimental** and its interface is subject to change.
|
> This program is
|
||||||
|
> [**experimental**](@docroot@/contributing/experimental-features.md#xp-feature-nix-command)
|
||||||
|
> and its interface is subject to change.
|
||||||
|
|
||||||
# Name
|
# Name
|
||||||
|
|
||||||
|
@ -29,19 +37,18 @@ let
|
||||||
|
|
||||||
showSynopsis = command: args:
|
showSynopsis = command: args:
|
||||||
let
|
let
|
||||||
showArgument = arg: "*${arg.label}*" + (if arg ? arity then "" else "...");
|
showArgument = arg: "*${arg.label}*" + optionalString (! arg ? arity) "...";
|
||||||
arguments = concatStringsSep " " (map showArgument args);
|
arguments = concatStringsSep " " (map showArgument args);
|
||||||
in ''
|
in ''
|
||||||
`${command}` [*option*...] ${arguments}
|
`${command}` [*option*...] ${arguments}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
maybeSubcommands = if details ? commands && details.commands != {}
|
maybeSubcommands = optionalString (details ? commands && details.commands != {})
|
||||||
then ''
|
''
|
||||||
where *subcommand* is one of the following:
|
where *subcommand* is one of the following:
|
||||||
|
|
||||||
${subcommands}
|
${subcommands}
|
||||||
''
|
'';
|
||||||
else "";
|
|
||||||
|
|
||||||
subcommands = if length categories > 1
|
subcommands = if length categories > 1
|
||||||
then listCategories
|
then listCategories
|
||||||
|
@ -63,12 +70,11 @@ let
|
||||||
* [`${command} ${name}`](./${appendName filename name}.md) - ${subcmd.description}
|
* [`${command} ${name}`](./${appendName filename name}.md) - ${subcmd.description}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
maybeDocumentation =
|
maybeDocumentation = optionalString
|
||||||
if details ? doc
|
(details ? doc)
|
||||||
then replaceStrings ["@stores@"] [storeDocs] details.doc
|
(replaceStrings ["@stores@"] [storeDocs] details.doc);
|
||||||
else "";
|
|
||||||
|
|
||||||
maybeOptions = if details.flags == {} then "" else ''
|
maybeOptions = optionalString (details.flags != {}) ''
|
||||||
# Options
|
# Options
|
||||||
|
|
||||||
${showOptions details.flags toplevel.flags}
|
${showOptions details.flags toplevel.flags}
|
||||||
|
@ -78,21 +84,25 @@ let
|
||||||
let
|
let
|
||||||
allOptions = options // commonOptions;
|
allOptions = options // commonOptions;
|
||||||
showCategory = cat: ''
|
showCategory = cat: ''
|
||||||
${if cat != "" then "**${cat}:**" else ""}
|
${optionalString (cat != "") "**${cat}:**"}
|
||||||
|
|
||||||
${listOptions (filterAttrs (n: v: v.category == cat) allOptions)}
|
${listOptions (filterAttrs (n: v: v.category == cat) allOptions)}
|
||||||
'';
|
'';
|
||||||
listOptions = opts: concatStringsSep "\n" (attrValues (mapAttrs showOption opts));
|
listOptions = opts: concatStringsSep "\n" (attrValues (mapAttrs showOption opts));
|
||||||
showOption = name: option:
|
showOption = name: option:
|
||||||
let
|
let
|
||||||
shortName = if option ? shortName then "/ `-${option.shortName}`" else "";
|
shortName = optionalString
|
||||||
labels = if option ? labels then (concatStringsSep " " (map (s: "*${s}*") option.labels)) else "";
|
(option ? shortName)
|
||||||
|
("/ `-${option.shortName}`");
|
||||||
|
labels = optionalString
|
||||||
|
(option ? labels)
|
||||||
|
(concatStringsSep " " (map (s: "*${s}*") option.labels));
|
||||||
in trim ''
|
in trim ''
|
||||||
- `--${name}` ${shortName} ${labels}
|
- `--${name}` ${shortName} ${labels}
|
||||||
|
|
||||||
${option.description}
|
${option.description}
|
||||||
'';
|
'';
|
||||||
categories = sort builtins.lessThan (unique (map (cmd: cmd.category) (attrValues allOptions)));
|
categories = sort lessThan (unique (map (cmd: cmd.category) (attrValues allOptions)));
|
||||||
in concatStrings (map showCategory categories);
|
in concatStrings (map showCategory categories);
|
||||||
in squash result;
|
in squash result;
|
||||||
|
|
||||||
|
@ -113,13 +123,11 @@ let
|
||||||
};
|
};
|
||||||
in [ cmd ] ++ concatMap subcommand (attrNames details.commands or {});
|
in [ cmd ] ++ concatMap subcommand (attrNames details.commands or {});
|
||||||
|
|
||||||
cliDump = builtins.fromJSON cliDumpStr;
|
|
||||||
|
|
||||||
manpages = processCommand {
|
manpages = processCommand {
|
||||||
command = "nix";
|
command = "nix";
|
||||||
details = cliDump.args;
|
details = commandInfo.args;
|
||||||
filename = "nix";
|
filename = "nix";
|
||||||
toplevel = cliDump.args;
|
toplevel = commandInfo.args;
|
||||||
};
|
};
|
||||||
|
|
||||||
tableOfContents = let
|
tableOfContents = let
|
||||||
|
@ -139,6 +147,6 @@ let
|
||||||
|
|
||||||
${showSettings { useAnchors = false; } settings}
|
${showSettings { useAnchors = false; } settings}
|
||||||
'';
|
'';
|
||||||
in concatStrings (attrValues (mapAttrs showStore cliDump.stores));
|
in concatStrings (attrValues (mapAttrs showStore commandInfo.stores));
|
||||||
|
|
||||||
in (listToAttrs manpages) // { "SUMMARY.md" = tableOfContents; }
|
in (listToAttrs manpages) // { "SUMMARY.md" = tableOfContents; }
|
||||||
|
|
9
doc/manual/generate-xp-features-shortlist.nix
Normal file
9
doc/manual/generate-xp-features-shortlist.nix
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
with builtins;
|
||||||
|
with import ./utils.nix;
|
||||||
|
|
||||||
|
let
|
||||||
|
showExperimentalFeature = name: doc:
|
||||||
|
''
|
||||||
|
- [`${name}`](@docroot@/contributing/experimental-features.md#xp-feature-${name})
|
||||||
|
'';
|
||||||
|
in xps: indent " " (concatStrings (attrValues (mapAttrs showExperimentalFeature xps)))
|
11
doc/manual/generate-xp-features.nix
Normal file
11
doc/manual/generate-xp-features.nix
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
with builtins;
|
||||||
|
with import ./utils.nix;
|
||||||
|
|
||||||
|
let
|
||||||
|
showExperimentalFeature = name: doc:
|
||||||
|
squash ''
|
||||||
|
## [`${name}`]{#xp-feature-${name}}
|
||||||
|
|
||||||
|
${doc}
|
||||||
|
'';
|
||||||
|
in xps: (concatStringsSep "\n" (attrValues (mapAttrs showExperimentalFeature xps)))
|
|
@ -81,19 +81,20 @@ $(d)/%.8: $(d)/src/command-ref/%.md
|
||||||
$(d)/nix.conf.5: $(d)/src/command-ref/conf-file.md
|
$(d)/nix.conf.5: $(d)/src/command-ref/conf-file.md
|
||||||
@printf "Title: %s\n\n" "$$(basename $@ .5)" > $^.tmp
|
@printf "Title: %s\n\n" "$$(basename $@ .5)" > $^.tmp
|
||||||
@cat $^ >> $^.tmp
|
@cat $^ >> $^.tmp
|
||||||
|
@$(call process-includes,$^,$^.tmp)
|
||||||
$(trace-gen) lowdown -sT man --nroff-nolinks -M section=5 $^.tmp -o $@
|
$(trace-gen) lowdown -sT man --nroff-nolinks -M section=5 $^.tmp -o $@
|
||||||
@rm $^.tmp
|
@rm $^.tmp
|
||||||
|
|
||||||
$(d)/src/SUMMARY.md: $(d)/src/SUMMARY.md.in $(d)/src/command-ref/new-cli
|
$(d)/src/SUMMARY.md: $(d)/src/SUMMARY.md.in $(d)/src/command-ref/new-cli $(d)/src/contributing/experimental-feature-descriptions.md
|
||||||
@cp $< $@
|
@cp $< $@
|
||||||
@$(call process-includes,$@,$@)
|
@$(call process-includes,$@,$@)
|
||||||
|
|
||||||
$(d)/src/command-ref/new-cli: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix
|
$(d)/src/command-ref/new-cli: $(d)/nix.json $(d)/utils.nix $(d)/generate-manpage.nix $(bindir)/nix
|
||||||
@rm -rf $@ $@.tmp
|
@rm -rf $@ $@.tmp
|
||||||
$(trace-gen) $(nix-eval) --write-to $@.tmp --expr 'import doc/manual/generate-manpage.nix (builtins.readFile $<)'
|
$(trace-gen) $(nix-eval) --write-to $@.tmp --expr 'import doc/manual/generate-manpage.nix (builtins.readFile $<)'
|
||||||
@mv $@.tmp $@
|
@mv $@.tmp $@
|
||||||
|
|
||||||
$(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/utils.nix $(d)/src/command-ref/conf-file-prefix.md $(bindir)/nix
|
$(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/utils.nix $(d)/src/command-ref/conf-file-prefix.md $(d)/src/command-ref/experimental-features-shortlist.md $(bindir)/nix
|
||||||
@cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp
|
@cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp
|
||||||
$(trace-gen) $(nix-eval) --expr '(import doc/manual/utils.nix).showSettings { useAnchors = true; } (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp;
|
$(trace-gen) $(nix-eval) --expr '(import doc/manual/utils.nix).showSettings { useAnchors = true; } (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp;
|
||||||
@mv $@.tmp $@
|
@mv $@.tmp $@
|
||||||
|
@ -106,6 +107,20 @@ $(d)/conf-file.json: $(bindir)/nix
|
||||||
$(trace-gen) $(dummy-env) $(bindir)/nix show-config --json --experimental-features nix-command > $@.tmp
|
$(trace-gen) $(dummy-env) $(bindir)/nix show-config --json --experimental-features nix-command > $@.tmp
|
||||||
@mv $@.tmp $@
|
@mv $@.tmp $@
|
||||||
|
|
||||||
|
$(d)/src/contributing/experimental-feature-descriptions.md: $(d)/xp-features.json $(d)/utils.nix $(d)/generate-xp-features.nix $(bindir)/nix
|
||||||
|
@rm -rf $@ $@.tmp
|
||||||
|
$(trace-gen) $(nix-eval) --write-to $@.tmp --expr 'import doc/manual/generate-xp-features.nix (builtins.fromJSON (builtins.readFile $<))'
|
||||||
|
@mv $@.tmp $@
|
||||||
|
|
||||||
|
$(d)/src/command-ref/experimental-features-shortlist.md: $(d)/xp-features.json $(d)/utils.nix $(d)/generate-xp-features-shortlist.nix $(bindir)/nix
|
||||||
|
@rm -rf $@ $@.tmp
|
||||||
|
$(trace-gen) $(nix-eval) --write-to $@.tmp --expr 'import doc/manual/generate-xp-features-shortlist.nix (builtins.fromJSON (builtins.readFile $<))'
|
||||||
|
@mv $@.tmp $@
|
||||||
|
|
||||||
|
$(d)/xp-features.json: $(bindir)/nix
|
||||||
|
$(trace-gen) $(dummy-env) NIX_PATH=nix/corepkgs=corepkgs $(bindir)/nix __dump-xp-features > $@.tmp
|
||||||
|
@mv $@.tmp $@
|
||||||
|
|
||||||
$(d)/src/language/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/language/builtins-prefix.md $(bindir)/nix
|
$(d)/src/language/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/language/builtins-prefix.md $(bindir)/nix
|
||||||
@cat doc/manual/src/language/builtins-prefix.md > $@.tmp
|
@cat doc/manual/src/language/builtins-prefix.md > $@.tmp
|
||||||
$(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp;
|
$(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp;
|
||||||
|
@ -145,7 +160,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)/anchors.jq $(d)/custom.css $(d)/src/SUMMARY.md $(d)/src/command-ref/new-cli $(d)/src/command-ref/conf-file.md $(d)/src/language/builtins.md
|
$(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/contributing/experimental-feature-descriptions.md $(d)/src/command-ref/conf-file.md $(d)/src/language/builtins.md
|
||||||
$(trace-gen) \
|
$(trace-gen) \
|
||||||
tmp="$$(mktemp -d)"; \
|
tmp="$$(mktemp -d)"; \
|
||||||
cp -r doc/manual "$$tmp"; \
|
cp -r doc/manual "$$tmp"; \
|
||||||
|
|
|
@ -338,6 +338,9 @@ const redirects = {
|
||||||
"strings": "#string",
|
"strings": "#string",
|
||||||
"lists": "#list",
|
"lists": "#list",
|
||||||
"attribute-sets": "#attribute-set"
|
"attribute-sets": "#attribute-set"
|
||||||
|
},
|
||||||
|
"installation/installing-binary.html": {
|
||||||
|
"uninstalling": "uninstall.html"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
- [Multi-User Mode](installation/multi-user.md)
|
- [Multi-User Mode](installation/multi-user.md)
|
||||||
- [Environment Variables](installation/env-variables.md)
|
- [Environment Variables](installation/env-variables.md)
|
||||||
- [Upgrading Nix](installation/upgrading.md)
|
- [Upgrading Nix](installation/upgrading.md)
|
||||||
|
- [Uninstalling Nix](installation/uninstall.md)
|
||||||
- [Package Management](package-management/package-management.md)
|
- [Package Management](package-management/package-management.md)
|
||||||
- [Basic Package Management](package-management/basic-package-mgmt.md)
|
- [Basic Package Management](package-management/basic-package-mgmt.md)
|
||||||
- [Profiles](package-management/profiles.md)
|
- [Profiles](package-management/profiles.md)
|
||||||
|
@ -99,6 +100,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.15 (2023-04-11)](release-notes/rl-2.15.md)
|
||||||
- [Release 2.14 (2023-02-28)](release-notes/rl-2.14.md)
|
- [Release 2.14 (2023-02-28)](release-notes/rl-2.14.md)
|
||||||
- [Release 2.13 (2023-01-17)](release-notes/rl-2.13.md)
|
- [Release 2.13 (2023-01-17)](release-notes/rl-2.13.md)
|
||||||
- [Release 2.12 (2022-12-06)](release-notes/rl-2.12.md)
|
- [Release 2.12 (2022-12-06)](release-notes/rl-2.12.md)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Experimental Commands
|
# Experimental Commands
|
||||||
|
|
||||||
This section lists experimental commands.
|
This section lists [experimental commands](@docroot@/contributing/experimental-features.md#xp-feature-nix-command).
|
||||||
|
|
||||||
> **Warning**
|
> **Warning**
|
||||||
>
|
>
|
||||||
|
|
|
@ -203,10 +203,9 @@ Most Nix commands accept the following command-line options:
|
||||||
instead.
|
instead.
|
||||||
|
|
||||||
- <span id="opt-I">[`-I`](#opt-I)</span> *path*\
|
- <span id="opt-I">[`-I`](#opt-I)</span> *path*\
|
||||||
Add a path to the Nix expression search path. This option may be
|
Add an entry to the [Nix expression search path](@docroot@/command-ref/conf-file.md#conf-nix-path).
|
||||||
given multiple times. See the `NIX_PATH` environment variable for
|
This option may be given multiple times.
|
||||||
information on the semantics of the Nix search path. Paths added
|
Paths added through `-I` take precedence over [`NIX_PATH`](@docroot@/command-ref/env-common.md#env-NIX_PATH).
|
||||||
through `-I` take precedence over `NIX_PATH`.
|
|
||||||
|
|
||||||
- <span id="opt-option">[`--option`](#opt-option)</span> *name* *value*\
|
- <span id="opt-option">[`--option`](#opt-option)</span> *name* *value*\
|
||||||
Set the Nix configuration option *name* to *value*. This overrides
|
Set the Nix configuration option *name* to *value*. This overrides
|
||||||
|
|
|
@ -89,3 +89,7 @@ However they serve different purposes:
|
||||||
It is primarily an issue of *design* and *communication*, targeting the broader community.
|
It is primarily an issue of *design* and *communication*, targeting the broader community.
|
||||||
|
|
||||||
This means that experimental features and RFCs are orthogonal mechanisms, and can be used independently or together as needed.
|
This means that experimental features and RFCs are orthogonal mechanisms, and can be used independently or together as needed.
|
||||||
|
|
||||||
|
# Currently available experimental features
|
||||||
|
|
||||||
|
{{#include ./experimental-feature-descriptions.md}}
|
||||||
|
|
|
@ -127,7 +127,7 @@
|
||||||
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 database{#gloss-nix-database}\
|
- [Nix database]{#gloss-nix-database}\
|
||||||
An SQlite database to track [reference]s between [store object]s.
|
An SQlite database to track [reference]s between [store object]s.
|
||||||
This is an implementation detail of the [local store].
|
This is an implementation detail of the [local store].
|
||||||
|
|
||||||
|
@ -225,3 +225,9 @@
|
||||||
[string]: ./language/values.md#type-string
|
[string]: ./language/values.md#type-string
|
||||||
[path]: ./language/values.md#type-path
|
[path]: ./language/values.md#type-path
|
||||||
[attribute name]: ./language/values.md#attribute-set
|
[attribute name]: ./language/values.md#attribute-set
|
||||||
|
|
||||||
|
- [experimental feature]{#gloss-experimental-feature}\
|
||||||
|
Not yet stabilized functionality guarded by named experimental feature flags.
|
||||||
|
These flags are enabled or disabled with the [`experimental-features`](./command-ref/conf-file.html#conf-experimental-features) setting.
|
||||||
|
|
||||||
|
See the contribution guide on the [purpose and lifecycle of experimental feaures](@docroot@/contributing/experimental-features.md).
|
||||||
|
|
|
@ -47,12 +47,6 @@ The install script will modify the first writable file from amongst
|
||||||
`NIX_INSTALLER_NO_MODIFY_PROFILE` environment variable before executing
|
`NIX_INSTALLER_NO_MODIFY_PROFILE` environment variable before executing
|
||||||
the install script to disable this behaviour.
|
the install script to disable this behaviour.
|
||||||
|
|
||||||
You can uninstall Nix simply by running:
|
|
||||||
|
|
||||||
```console
|
|
||||||
$ rm -rf /nix
|
|
||||||
```
|
|
||||||
|
|
||||||
# Multi User Installation
|
# Multi User Installation
|
||||||
|
|
||||||
The multi-user Nix installation creates system users, and a system
|
The multi-user Nix installation creates system users, and a system
|
||||||
|
@ -84,154 +78,8 @@ The installer will modify `/etc/bashrc`, and `/etc/zshrc` if they exist.
|
||||||
The installer will first back up these files with a `.backup-before-nix`
|
The installer will first back up these files with a `.backup-before-nix`
|
||||||
extension. The installer will also create `/etc/profile.d/nix.sh`.
|
extension. The installer will also create `/etc/profile.d/nix.sh`.
|
||||||
|
|
||||||
## Uninstalling
|
|
||||||
|
|
||||||
### Linux
|
|
||||||
|
|
||||||
If you are on Linux with systemd:
|
|
||||||
|
|
||||||
1. Remove the Nix daemon service:
|
|
||||||
|
|
||||||
```console
|
|
||||||
sudo systemctl stop nix-daemon.service
|
|
||||||
sudo systemctl disable nix-daemon.socket nix-daemon.service
|
|
||||||
sudo systemctl daemon-reload
|
|
||||||
```
|
|
||||||
|
|
||||||
1. Remove systemd service files:
|
|
||||||
|
|
||||||
```console
|
|
||||||
sudo rm /etc/systemd/system/nix-daemon.service /etc/systemd/system/nix-daemon.socket
|
|
||||||
```
|
|
||||||
|
|
||||||
1. The installer script uses systemd-tmpfiles to create the socket directory.
|
|
||||||
You may also want to remove the configuration for that:
|
|
||||||
|
|
||||||
```console
|
|
||||||
sudo rm /etc/tmpfiles.d/nix-daemon.conf
|
|
||||||
```
|
|
||||||
|
|
||||||
Remove files created by Nix:
|
|
||||||
|
|
||||||
```console
|
|
||||||
sudo rm -rf /nix /etc/nix /etc/profile/nix.sh ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
|
|
||||||
```
|
|
||||||
|
|
||||||
Remove build users and their group:
|
|
||||||
|
|
||||||
```console
|
|
||||||
for i in $(seq 1 32); do
|
|
||||||
sudo userdel nixbld$i
|
|
||||||
done
|
|
||||||
sudo groupdel nixbld
|
|
||||||
```
|
|
||||||
|
|
||||||
There may also be references to Nix in
|
|
||||||
|
|
||||||
- `/etc/profile`
|
|
||||||
- `/etc/bashrc`
|
|
||||||
- `/etc/zshrc`
|
|
||||||
|
|
||||||
which you may remove.
|
|
||||||
|
|
||||||
### macOS
|
|
||||||
|
|
||||||
1. Edit `/etc/zshrc` and `/etc/bashrc` to remove the lines sourcing
|
|
||||||
`nix-daemon.sh`, which should look like this:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Nix
|
|
||||||
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
|
|
||||||
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
|
|
||||||
fi
|
|
||||||
# End Nix
|
|
||||||
```
|
|
||||||
|
|
||||||
If these files haven't been altered since installing Nix you can simply put
|
|
||||||
the backups back in place:
|
|
||||||
|
|
||||||
```console
|
|
||||||
sudo mv /etc/zshrc.backup-before-nix /etc/zshrc
|
|
||||||
sudo mv /etc/bashrc.backup-before-nix /etc/bashrc
|
|
||||||
```
|
|
||||||
|
|
||||||
This will stop shells from sourcing the file and bringing everything you
|
|
||||||
installed using Nix in scope.
|
|
||||||
|
|
||||||
2. Stop and remove the Nix daemon services:
|
|
||||||
|
|
||||||
```console
|
|
||||||
sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
|
||||||
sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
|
||||||
sudo launchctl unload /Library/LaunchDaemons/org.nixos.darwin-store.plist
|
|
||||||
sudo rm /Library/LaunchDaemons/org.nixos.darwin-store.plist
|
|
||||||
```
|
|
||||||
|
|
||||||
This stops the Nix daemon and prevents it from being started next time you
|
|
||||||
boot the system.
|
|
||||||
|
|
||||||
3. Remove the `nixbld` group and the `_nixbuildN` users:
|
|
||||||
|
|
||||||
```console
|
|
||||||
sudo dscl . -delete /Groups/nixbld
|
|
||||||
for u in $(sudo dscl . -list /Users | grep _nixbld); do sudo dscl . -delete /Users/$u; done
|
|
||||||
```
|
|
||||||
|
|
||||||
This will remove all the build users that no longer serve a purpose.
|
|
||||||
|
|
||||||
4. Edit fstab using `sudo vifs` to remove the line mounting the Nix Store
|
|
||||||
volume on `/nix`, which looks like
|
|
||||||
`UUID=<uuid> /nix apfs rw,noauto,nobrowse,suid,owners` or
|
|
||||||
`LABEL=Nix\040Store /nix apfs rw,nobrowse`. This will prevent automatic
|
|
||||||
mounting of the Nix Store volume.
|
|
||||||
|
|
||||||
5. Edit `/etc/synthetic.conf` to remove the `nix` line. If this is the only
|
|
||||||
line in the file you can remove it entirely, `sudo rm /etc/synthetic.conf`.
|
|
||||||
This will prevent the creation of the empty `/nix` directory to provide a
|
|
||||||
mountpoint for the Nix Store volume.
|
|
||||||
|
|
||||||
6. Remove the files Nix added to your system:
|
|
||||||
|
|
||||||
```console
|
|
||||||
sudo rm -rf /etc/nix /var/root/.nix-profile /var/root/.nix-defexpr /var/root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
|
|
||||||
```
|
|
||||||
|
|
||||||
This gets rid of any data Nix may have created except for the store which is
|
|
||||||
removed next.
|
|
||||||
|
|
||||||
7. Remove the Nix Store volume:
|
|
||||||
|
|
||||||
```console
|
|
||||||
sudo diskutil apfs deleteVolume /nix
|
|
||||||
```
|
|
||||||
|
|
||||||
This will remove the Nix Store volume and everything that was added to the
|
|
||||||
store.
|
|
||||||
|
|
||||||
If the output indicates that the command couldn't remove the volume, you should
|
|
||||||
make sure you don't have an _unmounted_ Nix Store volume. Look for a
|
|
||||||
"Nix Store" volume in the output of the following command:
|
|
||||||
|
|
||||||
```console
|
|
||||||
diskutil list
|
|
||||||
```
|
|
||||||
|
|
||||||
If you _do_ see a "Nix Store" volume, delete it by re-running the diskutil
|
|
||||||
deleteVolume command, but replace `/nix` with the store volume's `diskXsY`
|
|
||||||
identifier.
|
|
||||||
|
|
||||||
> **Note**
|
|
||||||
>
|
|
||||||
> After you complete the steps here, you will still have an empty `/nix`
|
|
||||||
> directory. This is an expected sign of a successful uninstall. The empty
|
|
||||||
> `/nix` directory will disappear the next time you reboot.
|
|
||||||
>
|
|
||||||
> You do not have to reboot to finish uninstalling Nix. The uninstall is
|
|
||||||
> complete. macOS (Catalina+) directly controls root directories and its
|
|
||||||
> read-only root will prevent you from manually deleting the empty `/nix`
|
|
||||||
> mountpoint.
|
|
||||||
|
|
||||||
# macOS Installation
|
# macOS Installation
|
||||||
|
|
||||||
[]{#sect-macos-installation-change-store-prefix}[]{#sect-macos-installation-encrypted-volume}[]{#sect-macos-installation-symlink}[]{#sect-macos-installation-recommended-notes}
|
[]{#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 -->
|
||||||
|
|
||||||
|
@ -280,19 +128,16 @@ this to run the installer, but it may help if you run into trouble:
|
||||||
|
|
||||||
# Installing a pinned Nix version from a URL
|
# Installing a pinned Nix version from a URL
|
||||||
|
|
||||||
NixOS.org hosts version-specific installation URLs for all Nix versions
|
Version-specific installation URLs for all Nix versions
|
||||||
since 1.11.16, at `https://releases.nixos.org/nix/nix-version/install`.
|
since 1.11.16 can be found at [releases.nixos.org](https://releases.nixos.org/?prefix=nix/).
|
||||||
|
The corresponding SHA-256 hash can be found in the directory for the given version.
|
||||||
|
|
||||||
These install scripts can be used the same as the main NixOS.org
|
These install scripts can be used the same as usual:
|
||||||
installation script:
|
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ curl -L https://nixos.org/nix/install | sh
|
$ curl -L https://releases.nixos.org/nix/nix-<version>/install | sh
|
||||||
```
|
```
|
||||||
|
|
||||||
In the same directory of the install script are sha256 sums, and gpg
|
|
||||||
signature files.
|
|
||||||
|
|
||||||
# Installing from a binary tarball
|
# Installing from a binary tarball
|
||||||
|
|
||||||
You can also download a binary tarball that contains Nix and all its
|
You can also download a binary tarball that contains Nix and all its
|
||||||
|
|
159
doc/manual/src/installation/uninstall.md
Normal file
159
doc/manual/src/installation/uninstall.md
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
# Uninstalling Nix
|
||||||
|
|
||||||
|
## Single User
|
||||||
|
|
||||||
|
If you have a [single-user installation](./installing-binary.md#single-user-installation) of Nix, uninstall it by running:
|
||||||
|
|
||||||
|
```console
|
||||||
|
$ rm -rf /nix
|
||||||
|
```
|
||||||
|
|
||||||
|
## Multi User
|
||||||
|
|
||||||
|
Removing a [multi-user installation](./installing-binary.md#multi-user-installation) of Nix is more involved, and depends on the operating system.
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
|
If you are on Linux with systemd:
|
||||||
|
|
||||||
|
1. Remove the Nix daemon service:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo systemctl stop nix-daemon.service
|
||||||
|
sudo systemctl disable nix-daemon.socket nix-daemon.service
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
1. Remove systemd service files:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo rm /etc/systemd/system/nix-daemon.service /etc/systemd/system/nix-daemon.socket
|
||||||
|
```
|
||||||
|
|
||||||
|
1. The installer script uses systemd-tmpfiles to create the socket directory.
|
||||||
|
You may also want to remove the configuration for that:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo rm /etc/tmpfiles.d/nix-daemon.conf
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove files created by Nix:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo rm -rf /nix /etc/nix /etc/profile/nix.sh ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
|
||||||
|
```
|
||||||
|
|
||||||
|
Remove build users and their group:
|
||||||
|
|
||||||
|
```console
|
||||||
|
for i in $(seq 1 32); do
|
||||||
|
sudo userdel nixbld$i
|
||||||
|
done
|
||||||
|
sudo groupdel nixbld
|
||||||
|
```
|
||||||
|
|
||||||
|
There may also be references to Nix in
|
||||||
|
|
||||||
|
- `/etc/profile`
|
||||||
|
- `/etc/bashrc`
|
||||||
|
- `/etc/zshrc`
|
||||||
|
|
||||||
|
which you may remove.
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
1. Edit `/etc/zshrc`, `/etc/bashrc`, and `/etc/bash.bashrc` to remove the lines sourcing `nix-daemon.sh`, which should look like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Nix
|
||||||
|
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
|
||||||
|
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
|
||||||
|
fi
|
||||||
|
# End Nix
|
||||||
|
```
|
||||||
|
|
||||||
|
If these files haven't been altered since installing Nix you can simply put
|
||||||
|
the backups back in place:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo mv /etc/zshrc.backup-before-nix /etc/zshrc
|
||||||
|
sudo mv /etc/bashrc.backup-before-nix /etc/bashrc
|
||||||
|
sudo mv /etc/bash.bashrc.backup-before-nix /etc/bash.bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
This will stop shells from sourcing the file and bringing everything you
|
||||||
|
installed using Nix in scope.
|
||||||
|
|
||||||
|
2. Stop and remove the Nix daemon services:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
||||||
|
sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
||||||
|
sudo launchctl unload /Library/LaunchDaemons/org.nixos.darwin-store.plist
|
||||||
|
sudo rm /Library/LaunchDaemons/org.nixos.darwin-store.plist
|
||||||
|
```
|
||||||
|
|
||||||
|
This stops the Nix daemon and prevents it from being started next time you
|
||||||
|
boot the system.
|
||||||
|
|
||||||
|
3. Remove the `nixbld` group and the `_nixbuildN` users:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo dscl . -delete /Groups/nixbld
|
||||||
|
for u in $(sudo dscl . -list /Users | grep _nixbld); do sudo dscl . -delete /Users/$u; done
|
||||||
|
```
|
||||||
|
|
||||||
|
This will remove all the build users that no longer serve a purpose.
|
||||||
|
|
||||||
|
4. Edit fstab using `sudo vifs` to remove the line mounting the Nix Store
|
||||||
|
volume on `/nix`, which looks like
|
||||||
|
`UUID=<uuid> /nix apfs rw,noauto,nobrowse,suid,owners` or
|
||||||
|
`LABEL=Nix\040Store /nix apfs rw,nobrowse`. This will prevent automatic
|
||||||
|
mounting of the Nix Store volume.
|
||||||
|
|
||||||
|
5. Edit `/etc/synthetic.conf` to remove the `nix` line. If this is the only
|
||||||
|
line in the file you can remove it entirely, `sudo rm /etc/synthetic.conf`.
|
||||||
|
This will prevent the creation of the empty `/nix` directory to provide a
|
||||||
|
mountpoint for the Nix Store volume.
|
||||||
|
|
||||||
|
6. Remove the files Nix added to your system:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo rm -rf /etc/nix /var/root/.nix-profile /var/root/.nix-defexpr /var/root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
|
||||||
|
```
|
||||||
|
|
||||||
|
This gets rid of any data Nix may have created except for the store which is
|
||||||
|
removed next.
|
||||||
|
|
||||||
|
7. Remove the Nix Store volume:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo diskutil apfs deleteVolume /nix
|
||||||
|
```
|
||||||
|
|
||||||
|
This will remove the Nix Store volume and everything that was added to the
|
||||||
|
store.
|
||||||
|
|
||||||
|
If the output indicates that the command couldn't remove the volume, you should
|
||||||
|
make sure you don't have an _unmounted_ Nix Store volume. Look for a
|
||||||
|
"Nix Store" volume in the output of the following command:
|
||||||
|
|
||||||
|
```console
|
||||||
|
diskutil list
|
||||||
|
```
|
||||||
|
|
||||||
|
If you _do_ see a "Nix Store" volume, delete it by re-running the diskutil
|
||||||
|
deleteVolume command, but replace `/nix` with the store volume's `diskXsY`
|
||||||
|
identifier.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> After you complete the steps here, you will still have an empty `/nix`
|
||||||
|
> directory. This is an expected sign of a successful uninstall. The empty
|
||||||
|
> `/nix` directory will disappear the next time you reboot.
|
||||||
|
>
|
||||||
|
> You do not have to reboot to finish uninstalling Nix. The uninstall is
|
||||||
|
> complete. macOS (Catalina+) directly controls root directories and its
|
||||||
|
> read-only root will prevent you from manually deleting the empty `/nix`
|
||||||
|
> mountpoint.
|
||||||
|
|
|
@ -208,12 +208,26 @@ Derivations can declare some infrequently used optional attributes.
|
||||||
about converting to and from base-32 notation.)
|
about converting to and from base-32 notation.)
|
||||||
|
|
||||||
- [`__contentAddressed`]{#adv-attr-__contentAddressed}
|
- [`__contentAddressed`]{#adv-attr-__contentAddressed}
|
||||||
If this **experimental** attribute is set to true, then the derivation
|
> **Warning**
|
||||||
|
> This attribute is part of an [experimental feature](@docroot@/contributing/experimental-features.md).
|
||||||
|
>
|
||||||
|
> To use this attribute, you must enable the
|
||||||
|
> [`ca-derivations`](@docroot@/contributing/experimental-features.md#xp-feature-ca-derivations) experimental feature.
|
||||||
|
> For example, in [nix.conf](../command-ref/conf-file.md) you could add:
|
||||||
|
>
|
||||||
|
> ```
|
||||||
|
> extra-experimental-features = ca-derivations
|
||||||
|
> ```
|
||||||
|
|
||||||
|
If this 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.
|
||||||
This only has an effect if the `ca-derivations` experimental feature is enabled.
|
|
||||||
|
|
||||||
Setting this attribute also requires setting `outputHashMode` and `outputHashAlgo` like for *fixed-output derivations* (see above).
|
Setting this attribute also requires setting
|
||||||
|
[`outputHashMode`](#adv-attr-outputHashMode)
|
||||||
|
and
|
||||||
|
[`outputHashAlgo`](#adv-attr-outputHashAlgo)
|
||||||
|
like for *fixed-output derivations* (see above).
|
||||||
|
|
||||||
- [`passAsFile`]{#adv-attr-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
|
||||||
|
@ -307,9 +321,11 @@ Derivations can declare some infrequently used optional attributes.
|
||||||
|
|
||||||
- [`unsafeDiscardReferences`]{#adv-attr-unsafeDiscardReferences}\
|
- [`unsafeDiscardReferences`]{#adv-attr-unsafeDiscardReferences}\
|
||||||
> **Warning**
|
> **Warning**
|
||||||
> This is an experimental feature.
|
> This attribute is part of an [experimental feature](@docroot@/contributing/experimental-features.md).
|
||||||
>
|
>
|
||||||
> To enable it, add the following to [nix.conf](../command-ref/conf-file.md):
|
> To use this attribute, you must enable the
|
||||||
|
> [`discard-references`](@docroot@/contributing/experimental-features.md#xp-feature-discard-references) experimental feature.
|
||||||
|
> For example, in [nix.conf](../command-ref/conf-file.md) you could add:
|
||||||
>
|
>
|
||||||
> ```
|
> ```
|
||||||
> extra-experimental-features = discard-references
|
> extra-experimental-features = discard-references
|
||||||
|
|
|
@ -19,7 +19,7 @@ to subsequent chapters.
|
||||||
channel:
|
channel:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-env -qaP
|
$ nix-env --query --available --attr-path
|
||||||
nixpkgs.docbook_xml_dtd_43 docbook-xml-4.3
|
nixpkgs.docbook_xml_dtd_43 docbook-xml-4.3
|
||||||
nixpkgs.docbook_xml_dtd_45 docbook-xml-4.5
|
nixpkgs.docbook_xml_dtd_45 docbook-xml-4.5
|
||||||
nixpkgs.firefox firefox-33.0.2
|
nixpkgs.firefox firefox-33.0.2
|
||||||
|
@ -31,7 +31,7 @@ to subsequent chapters.
|
||||||
1. Install some packages from the channel:
|
1. Install some packages from the channel:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-env -iA nixpkgs.hello
|
$ nix-env --install --attr nixpkgs.hello
|
||||||
```
|
```
|
||||||
|
|
||||||
This should download pre-built packages; it should not build them
|
This should download pre-built packages; it should not build them
|
||||||
|
@ -49,13 +49,13 @@ to subsequent chapters.
|
||||||
1. Uninstall a package:
|
1. Uninstall a package:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-env -e hello
|
$ nix-env --uninstall hello
|
||||||
```
|
```
|
||||||
|
|
||||||
1. You can also test a package without installing it:
|
1. You can also test a package without installing it:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-shell -p hello
|
$ nix-shell --packages hello
|
||||||
```
|
```
|
||||||
|
|
||||||
This builds or downloads GNU Hello and its dependencies, then drops
|
This builds or downloads GNU Hello and its dependencies, then drops
|
||||||
|
@ -76,7 +76,7 @@ to subsequent chapters.
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-channel --update nixpkgs
|
$ nix-channel --update nixpkgs
|
||||||
$ nix-env -u '*'
|
$ nix-env --upgrade '*'
|
||||||
```
|
```
|
||||||
|
|
||||||
The latter command will upgrade each installed package for which
|
The latter command will upgrade each installed package for which
|
||||||
|
@ -95,5 +95,5 @@ to subsequent chapters.
|
||||||
them:
|
them:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-collect-garbage -d
|
$ nix-collect-garbage --delete-old
|
||||||
```
|
```
|
||||||
|
|
58
doc/manual/src/release-notes/rl-2.15.md
Normal file
58
doc/manual/src/release-notes/rl-2.15.md
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
# Release 2.15 (2023-04-11)
|
||||||
|
|
||||||
|
* Commands which take installables on the command line can now read them from the standard input if
|
||||||
|
passed the `--stdin` flag. This is primarily useful when you have a large amount of paths which
|
||||||
|
exceed the OS argument limit.
|
||||||
|
|
||||||
|
* The `nix-hash` command now supports Base64 and SRI. Use the flags `--base64`
|
||||||
|
or `--sri` to specify the format of output hash as Base64 or SRI, and `--to-base64`
|
||||||
|
or `--to-sri` to convert a hash to Base64 or SRI format, respectively.
|
||||||
|
|
||||||
|
As the choice of hash formats is no longer binary, the `--base16` flag is also added
|
||||||
|
to explicitly specify the Base16 format, which is still the default.
|
||||||
|
|
||||||
|
* The special handling of an [installable](../command-ref/new-cli/nix.md#installables) with `.drv` suffix being interpreted as all of the given [store derivation](../glossary.md#gloss-store-derivation)'s output paths is removed, and instead taken as the literal store path that it represents.
|
||||||
|
|
||||||
|
The new `^` syntax for store paths introduced in Nix 2.13 allows explicitly referencing output paths of a derivation.
|
||||||
|
Using this is better and more clear than relying on the now-removed `.drv` special handling.
|
||||||
|
|
||||||
|
For example,
|
||||||
|
```shell-session
|
||||||
|
$ nix path-info /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv
|
||||||
|
```
|
||||||
|
|
||||||
|
now gives info about the derivation itself, while
|
||||||
|
|
||||||
|
```shell-session
|
||||||
|
$ nix path-info /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*
|
||||||
|
```
|
||||||
|
provides information about each of its outputs.
|
||||||
|
|
||||||
|
* The experimental command `nix describe-stores` has been removed.
|
||||||
|
|
||||||
|
* Nix stores and their settings are now documented in [`nix help-stores`](@docroot@/command-ref/new-cli/nix3-help-stores.md).
|
||||||
|
|
||||||
|
* Documentation for operations of `nix-store` and `nix-env` are now available on separate pages of the manual.
|
||||||
|
They include all common options that can be specified and common environment variables that affect these commands.
|
||||||
|
|
||||||
|
These pages can be viewed offline with `man` using
|
||||||
|
|
||||||
|
* `man nix-store-<operation>` and `man nix-env-<operation>`
|
||||||
|
* `nix-store --help --<operation>` and `nix-env --help --<operation>`.
|
||||||
|
|
||||||
|
* Nix when used as a client now checks whether the store (the server) trusts the client.
|
||||||
|
(The store always had to check whether it trusts the client, but now the client is informed of the store's decision.)
|
||||||
|
This is useful for scripting interactions with (non-legacy-ssh) remote Nix stores.
|
||||||
|
|
||||||
|
`nix store ping` and `nix doctor` now display this information.
|
||||||
|
|
||||||
|
* The new command `nix derivation add` allows adding derivations to the store without involving the Nix language.
|
||||||
|
It exists to round out our collection of basic utility/plumbing commands, and allow for a low barrier-to-entry way of experimenting with alternative front-ends to the Nix Store.
|
||||||
|
It uses the same JSON layout as `nix derivation show`, and is its inverse.
|
||||||
|
|
||||||
|
* `nix show-derivation` has been renamed to `nix derivation show`.
|
||||||
|
This matches `nix derivation add`, and avoids bloating the top-level namespace.
|
||||||
|
The old name is still kept as an alias for compatibility, however.
|
||||||
|
|
||||||
|
* The `nix derivation {add,show}` JSON format now includes the derivation name as a top-level field.
|
||||||
|
This is useful in general, but especially necessary for the `add` direction, as otherwise we would need to pass in the name out of band for certain cases.
|
|
@ -1,58 +1,2 @@
|
||||||
# Release X.Y (202?-??-??)
|
# Release X.Y (202?-??-??)
|
||||||
|
|
||||||
* Commands which take installables on the command line can now read them from the standard input if
|
|
||||||
passed the `--stdin` flag. This is primarily useful when you have a large amount of paths which
|
|
||||||
exceed the OS arg limit.
|
|
||||||
|
|
||||||
* The `nix-hash` command now supports Base64 and SRI. Use the flags `--base64`
|
|
||||||
or `--sri` to specify the format of output hash as Base64 or SRI, and `--to-base64`
|
|
||||||
or `--to-sri` to convert a hash to Base64 or SRI format, respectively.
|
|
||||||
|
|
||||||
As the choice of hash formats is no longer binary, the `--base16` flag is also added
|
|
||||||
to explicitly specify the Base16 format, which is still the default.
|
|
||||||
|
|
||||||
* The special handling of an [installable](../command-ref/new-cli/nix.md#installables) with `.drv` suffix being interpreted as all of the given [store derivation](../glossary.md#gloss-store-derivation)'s output paths is removed, and instead taken as the literal store path that it represents.
|
|
||||||
|
|
||||||
The new `^` syntax for store paths introduced in Nix 2.13 allows explicitly referencing output paths of a derivation.
|
|
||||||
Using this is better and more clear than relying on the now-removed `.drv` special handling.
|
|
||||||
|
|
||||||
For example,
|
|
||||||
```shell-session
|
|
||||||
$ nix path-info /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv
|
|
||||||
```
|
|
||||||
|
|
||||||
now gives info about the derivation itself, while
|
|
||||||
|
|
||||||
```shell-session
|
|
||||||
$ nix path-info /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*
|
|
||||||
```
|
|
||||||
provides information about each of its outputs.
|
|
||||||
|
|
||||||
* The experimental command `nix describe-stores` has been removed.
|
|
||||||
|
|
||||||
* Nix stores and their settings are now documented in [`nix help-stores`](@docroot@/command-ref/new-cli/nix3-help-stores.md).
|
|
||||||
|
|
||||||
* Documentation for operations of `nix-store` and `nix-env` are now available on separate pages of the manual.
|
|
||||||
They include all common options that can be specified and common environment variables that affect these commands.
|
|
||||||
|
|
||||||
These pages can be viewed offline with `man` using
|
|
||||||
|
|
||||||
* `man nix-store-<operation>` and `man nix-env-<operation>`
|
|
||||||
* `nix-store --help --<operation>` and `nix-env --help --<operation>`.
|
|
||||||
|
|
||||||
* Nix when used as a client now checks whether the store (the server) trusts the client.
|
|
||||||
(The store always had to check whether it trusts the client, but now the client is informed of the store's decision.)
|
|
||||||
This is useful for scripting interactions with (non-legacy-ssh) remote Nix stores.
|
|
||||||
|
|
||||||
`nix store ping` and `nix doctor` now display this information.
|
|
||||||
|
|
||||||
* A new command `nix derivation add` is created, to allow adding derivations to the store without involving the Nix language.
|
|
||||||
It exists to round out our collection of basic utility/plumbing commands, and allow for a low barrier-to-entry way of experimenting with alternative front-ends to the Nix Store.
|
|
||||||
It uses the same JSON layout as `nix show-derivation`, and is its inverse.
|
|
||||||
|
|
||||||
* `nix show-derivation` has been renamed to `nix derivation show`.
|
|
||||||
This matches `nix derivation add`, and avoids bloating the top-level namespace.
|
|
||||||
The old name is still kept as an alias for compatibility, however.
|
|
||||||
|
|
||||||
* The `nix derivation {add,show}` JSON format now includes the derivation name as a top-level field.
|
|
||||||
This is useful in general, but especially necessary for the `add` direction, as otherwise we would need to pass in the name out of band for certain cases.
|
|
||||||
|
|
|
@ -5,6 +5,9 @@ rec {
|
||||||
|
|
||||||
concatStrings = concatStringsSep "";
|
concatStrings = concatStringsSep "";
|
||||||
|
|
||||||
|
attrsToList = a:
|
||||||
|
map (name: { inherit name; value = a.${name}; }) (builtins.attrNames a);
|
||||||
|
|
||||||
replaceStringsRec = from: to: string:
|
replaceStringsRec = from: to: string:
|
||||||
# recursively replace occurrences of `from` with `to` within `string`
|
# recursively replace occurrences of `from` with `to` within `string`
|
||||||
# example:
|
# example:
|
||||||
|
@ -39,7 +42,9 @@ rec {
|
||||||
filterAttrs = pred: set:
|
filterAttrs = pred: set:
|
||||||
listToAttrs (concatMap (name: let v = set.${name}; in if pred name v then [(nameValuePair name v)] else []) (attrNames set));
|
listToAttrs (concatMap (name: let v = set.${name}; in if pred name v then [(nameValuePair name v)] else []) (attrNames set));
|
||||||
|
|
||||||
showSetting = { useAnchors }: name: { description, documentDefault, defaultValue, aliases, value }:
|
optionalString = cond: string: if cond then string else "";
|
||||||
|
|
||||||
|
showSetting = { useAnchors }: name: { description, documentDefault, defaultValue, aliases, value, experimentalFeature }:
|
||||||
let
|
let
|
||||||
result = squash ''
|
result = squash ''
|
||||||
- ${if useAnchors
|
- ${if useAnchors
|
||||||
|
@ -49,10 +54,28 @@ rec {
|
||||||
${indent " " body}
|
${indent " " body}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
experimentalFeatureNote = optionalString (experimentalFeature != null) ''
|
||||||
|
> **Warning**
|
||||||
|
> This setting is part of an
|
||||||
|
> [experimental feature](@docroot@/contributing/experimental-features.md).
|
||||||
|
|
||||||
|
To change this setting, you need to make sure the corresponding experimental feature,
|
||||||
|
[`${experimentalFeature}`](@docroot@/contributing/experimental-features.md#xp-feature-${experimentalFeature}),
|
||||||
|
is enabled.
|
||||||
|
For example, include the following in [`nix.conf`](#):
|
||||||
|
|
||||||
|
```
|
||||||
|
extra-experimental-features = ${experimentalFeature}
|
||||||
|
${name} = ...
|
||||||
|
```
|
||||||
|
'';
|
||||||
|
|
||||||
# separate body to cleanly handle indentation
|
# separate body to cleanly handle indentation
|
||||||
body = ''
|
body = ''
|
||||||
${description}
|
${description}
|
||||||
|
|
||||||
|
${experimentalFeatureNote}
|
||||||
|
|
||||||
**Default:** ${showDefault documentDefault defaultValue}
|
**Default:** ${showDefault documentDefault defaultValue}
|
||||||
|
|
||||||
${showAliases aliases}
|
${showAliases aliases}
|
||||||
|
@ -71,13 +94,13 @@ rec {
|
||||||
else "*machine-specific*";
|
else "*machine-specific*";
|
||||||
|
|
||||||
showAliases = aliases:
|
showAliases = aliases:
|
||||||
if aliases == [] then "" else
|
optionalString (aliases != [])
|
||||||
"**Deprecated alias:** ${(concatStringsSep ", " (map (s: "`${s}`") aliases))}";
|
"**Deprecated alias:** ${(concatStringsSep ", " (map (s: "`${s}`") aliases))}";
|
||||||
|
|
||||||
indent = prefix: s:
|
|
||||||
concatStringsSep "\n" (map (x: if x == "" then x else "${prefix}${x}") (splitLines s));
|
|
||||||
|
|
||||||
in result;
|
in result;
|
||||||
|
|
||||||
|
indent = prefix: s:
|
||||||
|
concatStringsSep "\n" (map (x: if x == "" then x else "${prefix}${x}") (splitLines s));
|
||||||
|
|
||||||
showSettings = args: settingsInfo: concatStrings (attrValues (mapAttrs (showSetting args) settingsInfo));
|
showSettings = args: settingsInfo: concatStrings (attrValues (mapAttrs (showSetting args) settingsInfo));
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,12 +42,12 @@ The team meets twice a week:
|
||||||
|
|
||||||
- Discussion meeting: [Fridays 13:00-14:00 CET](https://calendar.google.com/calendar/event?eid=MHNtOGVuNWtrZXNpZHR2bW1sM3QyN2ZjaGNfMjAyMjExMjVUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn)
|
- Discussion meeting: [Fridays 13:00-14:00 CET](https://calendar.google.com/calendar/event?eid=MHNtOGVuNWtrZXNpZHR2bW1sM3QyN2ZjaGNfMjAyMjExMjVUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn)
|
||||||
|
|
||||||
1. Triage issues and pull requests from the _No Status_ column (30 min)
|
1. Triage issues and pull requests from the [No Status](#no-status) column (30 min)
|
||||||
2. Discuss issues and pull requests from the _To discuss_ column (30 min)
|
2. Discuss issues and pull requests from the [To discuss](#to-discuss) column (30 min)
|
||||||
|
|
||||||
- Work meeting: [Mondays 13:00-15:00 CET](https://calendar.google.com/calendar/event?eid=NTM1MG1wNGJnOGpmOTZhYms3bTB1bnY5cWxfMjAyMjExMjFUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn)
|
- Work meeting: [Mondays 13:00-15:00 CET](https://calendar.google.com/calendar/event?eid=NTM1MG1wNGJnOGpmOTZhYms3bTB1bnY5cWxfMjAyMjExMjFUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn)
|
||||||
|
|
||||||
1. Code review on pull requests from _In review_.
|
1. Code review on pull requests from [In review](#in-review).
|
||||||
2. Other chores and tasks.
|
2. Other chores and tasks.
|
||||||
|
|
||||||
Meeting notes are collected on a [collaborative scratchpad](https://pad.lassul.us/Cv7FpYx-Ri-4VjUykQOLAw), and published on Discourse under the [Nix category](https://discourse.nixos.org/c/dev/nix/50).
|
Meeting notes are collected on a [collaborative scratchpad](https://pad.lassul.us/Cv7FpYx-Ri-4VjUykQOLAw), and published on Discourse under the [Nix category](https://discourse.nixos.org/c/dev/nix/50).
|
||||||
|
@ -58,64 +58,74 @@ The team uses a [GitHub project board](https://github.com/orgs/NixOS/projects/19
|
||||||
|
|
||||||
Items on the board progress through the following states:
|
Items on the board progress through the following states:
|
||||||
|
|
||||||
- No Status
|
### No Status
|
||||||
|
|
||||||
During the discussion meeting, the team triages new items.
|
During the discussion meeting, the team triages new items.
|
||||||
To be considered, issues and pull requests must have a high-level description to provide the whole team with the necessary context at a glance.
|
To be considered, issues and pull requests must have a high-level description to provide the whole team with the necessary context at a glance.
|
||||||
|
|
||||||
On every meeting, at least one item from each of the following categories is inspected:
|
On every meeting, at least one item from each of the following categories is inspected:
|
||||||
|
|
||||||
1. [critical](https://github.com/NixOS/nix/labels/critical)
|
1. [critical](https://github.com/NixOS/nix/labels/critical)
|
||||||
2. [security](https://github.com/NixOS/nix/labels/security)
|
2. [security](https://github.com/NixOS/nix/labels/security)
|
||||||
3. [regression](https://github.com/NixOS/nix/labels/regression)
|
3. [regression](https://github.com/NixOS/nix/labels/regression)
|
||||||
4. [bug](https://github.com/NixOS/nix/issues?q=is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc)
|
4. [bug](https://github.com/NixOS/nix/issues?q=is%3Aopen+label%3Abug+sort%3Areactions-%2B1-desc)
|
||||||
5. [tests of existing functionality](https://github.com/NixOS/nix/issues?q=is%3Aopen+label%3Atests+-label%3Afeature+sort%3Areactions-%2B1-desc)
|
5. [tests of existing functionality](https://github.com/NixOS/nix/issues?q=is%3Aopen+label%3Atests+-label%3Afeature+sort%3Areactions-%2B1-desc)
|
||||||
|
|
||||||
- [oldest pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Acreated-asc)
|
- [oldest pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Acreated-asc)
|
||||||
- [most popular pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Areactions-%2B1-desc)
|
- [most popular pull requests](https://github.com/NixOS/nix/pulls?q=is%3Apr+is%3Aopen+sort%3Areactions-%2B1-desc)
|
||||||
- [oldest issues](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc)
|
- [oldest issues](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc)
|
||||||
- [most popular issues](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc)
|
- [most popular issues](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc)
|
||||||
|
|
||||||
Team members can also add pull requests or issues they would like the whole team to consider.
|
Team members can also add pull requests or issues they would like the whole team to consider.
|
||||||
|
To ensure process quality and reliability, all non-trivial pull requests must be triaged before merging.
|
||||||
|
|
||||||
If there is disagreement on the general idea behind an issue or pull request, it is moved to _To discuss_, otherwise to _In review_.
|
If there is disagreement on the general idea behind an issue or pull request, it is moved to [To discuss](#to-discuss).
|
||||||
|
Otherwise, the issue or pull request in questions get the label [`idea approved`](https://github.com/NixOS/nix/labels/idea%20approved).
|
||||||
|
For issues this means that an implementation is welcome and will be prioritised for review.
|
||||||
|
For pull requests this means that:
|
||||||
|
- Unfinished work is encouraged to be continued.
|
||||||
|
- A reviewer is assigned to take responsibility for getting the pull request merged.
|
||||||
|
The item is moved to the [Assigned](#assigned) column.
|
||||||
|
- If needed, the team can decide to do a collarorative review.
|
||||||
|
Then the item is moved to the [In review](#in-review) column, and review session is scheduled.
|
||||||
|
|
||||||
To ensure process quality and reliability, all non-trivial pull requests must be triaged before merging.
|
What constitutes a trivial pull request is up to maintainers' judgement.
|
||||||
What constitutes a trivial pull request is up to maintainers' judgement.
|
|
||||||
|
|
||||||
- To discuss
|
### To discuss
|
||||||
|
|
||||||
Pull requests and issues that are deemed important and controversial are discussed by the team during discussion meetings.
|
Pull requests and issues that are deemed important and controversial are discussed by the team during discussion meetings.
|
||||||
|
|
||||||
This may be where the merit of the change itself or the implementation strategy is contested by a team member.
|
This may be where the merit of the change itself or the implementation strategy is contested by a team member.
|
||||||
|
|
||||||
As a general guideline, the order of items is determined as follows:
|
As a general guideline, the order of items is determined as follows:
|
||||||
|
|
||||||
- Prioritise pull requests over issues
|
- Prioritise pull requests over issues
|
||||||
|
|
||||||
Contributors who took the time to implement concrete change proposals should not wait indefinitely.
|
Contributors who took the time to implement concrete change proposals should not wait indefinitely.
|
||||||
|
|
||||||
- Prioritise fixing bugs and testing over documentation, improvements or new features
|
- Prioritise fixing bugs and testing over documentation, improvements or new features
|
||||||
|
|
||||||
The team values stability and accessibility higher than raw functionality.
|
The team values stability and accessibility higher than raw functionality.
|
||||||
|
|
||||||
- Interleave issues and PRs
|
- Interleave issues and PRs
|
||||||
|
|
||||||
This way issues without attempts at a solution get a chance to get addressed.
|
This way issues without attempts at a solution get a chance to get addressed.
|
||||||
|
|
||||||
- In review
|
### In review
|
||||||
|
|
||||||
Pull requests in this column are reviewed together during work meetings.
|
Pull requests in this column are reviewed together during work meetings.
|
||||||
This is both for spreading implementation knowledge and for establishing common values in code reviews.
|
This is both for spreading implementation knowledge and for establishing common values in code reviews.
|
||||||
|
|
||||||
When the overall direction is agreed upon, even when further changes are required, the pull request is assigned to one team member.
|
When the overall direction is agreed upon, even when further changes are required, the pull request is assigned to one team member.
|
||||||
|
|
||||||
- Assigned for merging
|
### Assigned
|
||||||
|
|
||||||
One team member is assigned to each of these pull requests.
|
One team member is assigned to each of these pull requests.
|
||||||
They will communicate with the authors, and make the final approval once all remaining issues are addressed.
|
They will communicate with the authors, and make the final approval once all remaining issues are addressed.
|
||||||
|
|
||||||
If more substantive issues arise, the assignee can move the pull request back to _To discuss_ to involve the team again.
|
If more substantive issues arise, the assignee can move the pull request back to [To discuss](#to-discuss) or [In review](#in-review) to involve the team again.
|
||||||
|
|
||||||
|
### Flowchart
|
||||||
|
|
||||||
The process is illustrated in the following diagram:
|
The process is illustrated in the following diagram:
|
||||||
|
|
||||||
|
|
|
@ -27,8 +27,6 @@ static ref<Store> store()
|
||||||
if (!_store) {
|
if (!_store) {
|
||||||
try {
|
try {
|
||||||
initLibStore();
|
initLibStore();
|
||||||
loadConfFile();
|
|
||||||
settings.lockCPU = false;
|
|
||||||
_store = openStore();
|
_store = openStore();
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
croak("%s", e.what());
|
croak("%s", e.what());
|
||||||
|
@ -295,7 +293,13 @@ SV * makeFixedOutputPath(int recursive, char * algo, char * hash, char * name)
|
||||||
try {
|
try {
|
||||||
auto h = Hash::parseAny(hash, parseHashType(algo));
|
auto h = Hash::parseAny(hash, parseHashType(algo));
|
||||||
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
|
auto method = recursive ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat;
|
||||||
auto path = store()->makeFixedOutputPath(method, h, name);
|
auto path = store()->makeFixedOutputPath(name, FixedOutputInfo {
|
||||||
|
.hash = {
|
||||||
|
.method = method,
|
||||||
|
.hash = h,
|
||||||
|
},
|
||||||
|
.references = {},
|
||||||
|
});
|
||||||
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0)));
|
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(path).c_str(), 0)));
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
croak("%s", e.what());
|
croak("%s", e.what());
|
||||||
|
|
|
@ -92,7 +92,7 @@ poly_configure_nix_daemon_service() {
|
||||||
task "Setting up the nix-daemon systemd service"
|
task "Setting up the nix-daemon systemd service"
|
||||||
|
|
||||||
_sudo "to create the nix-daemon tmpfiles config" \
|
_sudo "to create the nix-daemon tmpfiles config" \
|
||||||
ln -sfn /nix/var/nix/profiles/default/$TMPFILES_SRC $TMPFILES_DEST
|
ln -sfn "/nix/var/nix/profiles/default$TMPFILES_SRC" "$TMPFILES_DEST"
|
||||||
|
|
||||||
_sudo "to run systemd-tmpfiles once to pick that path up" \
|
_sudo "to run systemd-tmpfiles once to pick that path up" \
|
||||||
systemd-tmpfiles --create --prefix=/nix/var/nix
|
systemd-tmpfiles --create --prefix=/nix/var/nix
|
||||||
|
|
|
@ -311,8 +311,9 @@ connected:
|
||||||
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
|
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
|
||||||
if (!store->queryRealisation(thisOutputId)) {
|
if (!store->queryRealisation(thisOutputId)) {
|
||||||
debug("missing output %s", outputName);
|
debug("missing output %s", outputName);
|
||||||
assert(result.builtOutputs.count(thisOutputId));
|
auto i = result.builtOutputs.find(outputName);
|
||||||
auto newRealisation = result.builtOutputs.at(thisOutputId);
|
assert(i != result.builtOutputs.end());
|
||||||
|
auto & newRealisation = i->second;
|
||||||
missingRealisations.insert(newRealisation);
|
missingRealisations.insert(newRealisation);
|
||||||
missingPaths.insert(newRealisation.outPath);
|
missingPaths.insert(newRealisation.outPath);
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,6 +121,8 @@ ref<EvalState> EvalCommand::getEvalState()
|
||||||
#endif
|
#endif
|
||||||
;
|
;
|
||||||
|
|
||||||
|
evalState->repair = repair;
|
||||||
|
|
||||||
if (startReplOnEvalErrors) {
|
if (startReplOnEvalErrors) {
|
||||||
evalState->debugRepl = &AbstractNixRepl::runSimple;
|
evalState->debugRepl = &AbstractNixRepl::runSimple;
|
||||||
};
|
};
|
||||||
|
|
|
@ -153,7 +153,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
|
||||||
for (auto & i : autoArgs) {
|
for (auto & i : autoArgs) {
|
||||||
auto v = state.allocValue();
|
auto v = state.allocValue();
|
||||||
if (i.second[0] == 'E')
|
if (i.second[0] == 'E')
|
||||||
state.mkThunk_(*v, state.parseExprFromString(i.second.substr(1), absPath(".")));
|
state.mkThunk_(*v, state.parseExprFromString(i.second.substr(1), state.rootPath(CanonPath::fromCwd())));
|
||||||
else
|
else
|
||||||
v->mkString(((std::string_view) i.second).substr(1));
|
v->mkString(((std::string_view) i.second).substr(1));
|
||||||
res.insert(state.symbols.create(i.first), v);
|
res.insert(state.symbols.create(i.first), v);
|
||||||
|
@ -161,19 +161,19 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
|
||||||
return res.finish();
|
return res.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
Path lookupFileArg(EvalState & state, std::string_view s)
|
SourcePath lookupFileArg(EvalState & state, std::string_view s)
|
||||||
{
|
{
|
||||||
if (EvalSettings::isPseudoUrl(s)) {
|
if (EvalSettings::isPseudoUrl(s)) {
|
||||||
auto storePath = fetchers::downloadTarball(
|
auto storePath = fetchers::downloadTarball(
|
||||||
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).first.storePath;
|
state.store, EvalSettings::resolvePseudoUrl(s), "source", false).first.storePath;
|
||||||
return state.store->toRealPath(storePath);
|
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (hasPrefix(s, "flake:")) {
|
else if (hasPrefix(s, "flake:")) {
|
||||||
experimentalFeatureSettings.require(Xp::Flakes);
|
experimentalFeatureSettings.require(Xp::Flakes);
|
||||||
auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false);
|
auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false);
|
||||||
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath;
|
auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath;
|
||||||
return state.store->toRealPath(storePath);
|
return state.rootPath(CanonPath(state.store->toRealPath(storePath)));
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
|
else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
|
||||||
|
@ -182,7 +182,7 @@ Path lookupFileArg(EvalState & state, std::string_view s)
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
return absPath(std::string(s));
|
return state.rootPath(CanonPath::fromCwd(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,16 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "args.hh"
|
#include "args.hh"
|
||||||
|
#include "common-args.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
class Store;
|
class Store;
|
||||||
class EvalState;
|
class EvalState;
|
||||||
class Bindings;
|
class Bindings;
|
||||||
|
struct SourcePath;
|
||||||
|
|
||||||
struct MixEvalArgs : virtual Args
|
struct MixEvalArgs : virtual Args, virtual MixRepair
|
||||||
{
|
{
|
||||||
static constexpr auto category = "Common evaluation options";
|
static constexpr auto category = "Common evaluation options";
|
||||||
|
|
||||||
|
@ -25,6 +27,6 @@ private:
|
||||||
std::map<std::string, std::string> autoArgs;
|
std::map<std::string, std::string> autoArgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
Path lookupFileArg(EvalState & state, std::string_view s);
|
SourcePath lookupFileArg(EvalState & state, std::string_view s);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,11 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
Strings editorFor(const Path & file, uint32_t line)
|
Strings editorFor(const SourcePath & file, uint32_t line)
|
||||||
{
|
{
|
||||||
|
auto path = file.getPhysicalPath();
|
||||||
|
if (!path)
|
||||||
|
throw Error("cannot open '%s' in an editor because it has no physical path", file);
|
||||||
auto editor = getEnv("EDITOR").value_or("cat");
|
auto editor = getEnv("EDITOR").value_or("cat");
|
||||||
auto args = tokenizeString<Strings>(editor);
|
auto args = tokenizeString<Strings>(editor);
|
||||||
if (line > 0 && (
|
if (line > 0 && (
|
||||||
|
@ -13,7 +16,7 @@ Strings editorFor(const Path & file, uint32_t line)
|
||||||
editor.find("vim") != std::string::npos ||
|
editor.find("vim") != std::string::npos ||
|
||||||
editor.find("kak") != std::string::npos))
|
editor.find("kak") != std::string::npos))
|
||||||
args.push_back(fmt("+%d", line));
|
args.push_back(fmt("+%d", line));
|
||||||
args.push_back(file);
|
args.push_back(path->abs());
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
|
#include "input-accessor.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -9,6 +10,6 @@ namespace nix {
|
||||||
* Helper function to generate args that invoke $EDITOR on
|
* Helper function to generate args that invoke $EDITOR on
|
||||||
* filename:lineno.
|
* filename:lineno.
|
||||||
*/
|
*/
|
||||||
Strings editorFor(const Path & file, uint32_t line);
|
Strings editorFor(const SourcePath & file, uint32_t line);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,8 +96,7 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
|
||||||
auto v = attr->forceValue();
|
auto v = attr->forceValue();
|
||||||
|
|
||||||
if (v.type() == nPath) {
|
if (v.type() == nPath) {
|
||||||
PathSet context;
|
auto storePath = v.path().fetchToStore(state->store);
|
||||||
auto storePath = state->copyPathToStore(context, Path(v.path));
|
|
||||||
return {{
|
return {{
|
||||||
.path = DerivedPath::Opaque {
|
.path = DerivedPath::Opaque {
|
||||||
.path = std::move(storePath),
|
.path = std::move(storePath),
|
||||||
|
@ -107,10 +106,10 @@ DerivedPathsWithInfo InstallableFlake::toDerivedPaths()
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (v.type() == nString) {
|
else if (v.type() == nString) {
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto s = state->forceString(v, context, noPos, fmt("while evaluating the flake output attribute '%s'", attrPath));
|
auto s = state->forceString(v, context, noPos, fmt("while evaluating the flake output attribute '%s'", attrPath));
|
||||||
auto storePath = state->store->maybeParseStorePath(s);
|
auto storePath = state->store->maybeParseStorePath(s);
|
||||||
if (storePath && context.count(std::string(s))) {
|
if (storePath && context.count(NixStringContextElem::Opaque { .path = *storePath })) {
|
||||||
return {{
|
return {{
|
||||||
.path = DerivedPath::Opaque {
|
.path = DerivedPath::Opaque {
|
||||||
.path = std::move(*storePath),
|
.path = std::move(*storePath),
|
||||||
|
|
|
@ -449,7 +449,7 @@ Installables SourceExprCommand::parseInstallables(
|
||||||
else if (file)
|
else if (file)
|
||||||
state->evalFile(lookupFileArg(*state, *file), *vFile);
|
state->evalFile(lookupFileArg(*state, *file), *vFile);
|
||||||
else {
|
else {
|
||||||
auto e = state->parseExprFromString(*expr, absPath("."));
|
auto e = state->parseExprFromString(*expr, state->rootPath(CanonPath::fromCwd()));
|
||||||
state->eval(e, *vFile);
|
state->eval(e, *vFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -593,8 +593,8 @@ std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> Installable::build
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const DerivedPath::Built & bfd) {
|
[&](const DerivedPath::Built & bfd) {
|
||||||
std::map<std::string, StorePath> outputs;
|
std::map<std::string, StorePath> outputs;
|
||||||
for (auto & path : buildResult.builtOutputs)
|
for (auto & [outputName, realisation] : buildResult.builtOutputs)
|
||||||
outputs.emplace(path.first.outputName, path.second.outPath);
|
outputs.emplace(outputName, realisation.outPath);
|
||||||
res.push_back({aux.installable, {
|
res.push_back({aux.installable, {
|
||||||
.path = BuiltPath::Built { bfd.drvPath, outputs },
|
.path = BuiltPath::Built { bfd.drvPath, outputs },
|
||||||
.info = aux.info,
|
.info = aux.info,
|
||||||
|
|
|
@ -40,6 +40,7 @@ extern "C" {
|
||||||
#include "markdown.hh"
|
#include "markdown.hh"
|
||||||
#include "local-fs-store.hh"
|
#include "local-fs-store.hh"
|
||||||
#include "progress-bar.hh"
|
#include "progress-bar.hh"
|
||||||
|
#include "print.hh"
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
#define GC_INCLUDE_NEW
|
#define GC_INCLUDE_NEW
|
||||||
|
@ -54,8 +55,6 @@ struct NixRepl
|
||||||
, gc
|
, gc
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
std::string curDir;
|
|
||||||
|
|
||||||
size_t debugTraceIndex;
|
size_t debugTraceIndex;
|
||||||
|
|
||||||
Strings loadedFiles;
|
Strings loadedFiles;
|
||||||
|
@ -113,7 +112,6 @@ NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalStat
|
||||||
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
|
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
|
||||||
, historyFile(getDataDir() + "/nix/repl-history")
|
, historyFile(getDataDir() + "/nix/repl-history")
|
||||||
{
|
{
|
||||||
curDir = absPath(".");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -425,6 +423,7 @@ StringSet NixRepl::completePrefix(const std::string & prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME: DRY and match or use the parser
|
||||||
static bool isVarName(std::string_view s)
|
static bool isVarName(std::string_view s)
|
||||||
{
|
{
|
||||||
if (s.size() == 0) return false;
|
if (s.size() == 0) return false;
|
||||||
|
@ -592,14 +591,14 @@ bool NixRepl::processLine(std::string line)
|
||||||
Value v;
|
Value v;
|
||||||
evalString(arg, v);
|
evalString(arg, v);
|
||||||
|
|
||||||
const auto [path, line] = [&] () -> std::pair<Path, uint32_t> {
|
const auto [path, line] = [&] () -> std::pair<SourcePath, uint32_t> {
|
||||||
if (v.type() == nPath || v.type() == nString) {
|
if (v.type() == nPath || v.type() == nString) {
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
|
auto path = state->coerceToPath(noPos, v, context, "while evaluating the filename to edit");
|
||||||
return {path, 0};
|
return {path, 0};
|
||||||
} else if (v.isLambda()) {
|
} else if (v.isLambda()) {
|
||||||
auto pos = state->positions[v.lambda.fun->pos];
|
auto pos = state->positions[v.lambda.fun->pos];
|
||||||
if (auto path = std::get_if<Path>(&pos.origin))
|
if (auto path = std::get_if<SourcePath>(&pos.origin))
|
||||||
return {*path, pos.line};
|
return {*path, pos.line};
|
||||||
else
|
else
|
||||||
throw Error("'%s' cannot be shown in an editor", pos);
|
throw Error("'%s' cannot be shown in an editor", pos);
|
||||||
|
@ -874,8 +873,7 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
|
||||||
|
|
||||||
Expr * NixRepl::parseString(std::string s)
|
Expr * NixRepl::parseString(std::string s)
|
||||||
{
|
{
|
||||||
Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv);
|
return state->parseExprFromString(std::move(s), state->rootPath(CanonPath::fromCwd()), staticEnv);
|
||||||
return e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -894,17 +892,6 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::ostream & printStringValue(std::ostream & str, const char * string) {
|
|
||||||
str << "\"";
|
|
||||||
for (const char * i = string; *i; i++)
|
|
||||||
if (*i == '\"' || *i == '\\') str << "\\" << *i;
|
|
||||||
else if (*i == '\n') str << "\\n";
|
|
||||||
else if (*i == '\r') str << "\\r";
|
|
||||||
else if (*i == '\t') str << "\\t";
|
|
||||||
else str << *i;
|
|
||||||
str << "\"";
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// FIXME: lot of cut&paste from Nix's eval.cc.
|
// FIXME: lot of cut&paste from Nix's eval.cc.
|
||||||
|
@ -922,17 +909,19 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nBool:
|
case nBool:
|
||||||
str << ANSI_CYAN << (v.boolean ? "true" : "false") << ANSI_NORMAL;
|
str << ANSI_CYAN;
|
||||||
|
printLiteralBool(str, v.boolean);
|
||||||
|
str << ANSI_NORMAL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nString:
|
case nString:
|
||||||
str << ANSI_WARNING;
|
str << ANSI_WARNING;
|
||||||
printStringValue(str, v.string.s);
|
printLiteralString(str, v.string.s);
|
||||||
str << ANSI_NORMAL;
|
str << ANSI_NORMAL;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nPath:
|
case nPath:
|
||||||
str << ANSI_GREEN << v.path << ANSI_NORMAL; // !!! escaping?
|
str << ANSI_GREEN << v.path().to_string() << ANSI_NORMAL; // !!! escaping?
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nNull:
|
case nNull:
|
||||||
|
@ -947,7 +936,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
||||||
if (isDrv) {
|
if (isDrv) {
|
||||||
str << "«derivation ";
|
str << "«derivation ";
|
||||||
Bindings::iterator i = v.attrs->find(state->sDrvPath);
|
Bindings::iterator i = v.attrs->find(state->sDrvPath);
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
if (i != v.attrs->end())
|
if (i != v.attrs->end())
|
||||||
str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
|
str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
|
||||||
else
|
else
|
||||||
|
@ -964,10 +953,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
||||||
sorted.emplace(state->symbols[i.name], i.value);
|
sorted.emplace(state->symbols[i.name], i.value);
|
||||||
|
|
||||||
for (auto & i : sorted) {
|
for (auto & i : sorted) {
|
||||||
if (isVarName(i.first))
|
printAttributeName(str, i.first);
|
||||||
str << i.first;
|
|
||||||
else
|
|
||||||
printStringValue(str, i.first.c_str());
|
|
||||||
str << " = ";
|
str << " = ";
|
||||||
if (seen.count(i.second))
|
if (seen.count(i.second))
|
||||||
str << "«repeated»";
|
str << "«repeated»";
|
||||||
|
|
|
@ -106,7 +106,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::strin
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
|
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
|
||||||
{
|
{
|
||||||
Value * v2;
|
Value * v2;
|
||||||
try {
|
try {
|
||||||
|
@ -118,21 +118,25 @@ std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value &
|
||||||
|
|
||||||
// FIXME: is it possible to extract the Pos object instead of doing this
|
// FIXME: is it possible to extract the Pos object instead of doing this
|
||||||
// toString + parsing?
|
// toString + parsing?
|
||||||
auto pos = state.forceString(*v2, noPos, "while evaluating the 'meta.position' attribute of a derivation");
|
NixStringContext context;
|
||||||
|
auto path = state.coerceToPath(noPos, *v2, context, "while evaluating the 'meta.position' attribute of a derivation");
|
||||||
|
|
||||||
auto colon = pos.rfind(':');
|
auto fn = path.path.abs();
|
||||||
if (colon == std::string::npos)
|
|
||||||
throw ParseError("cannot parse meta.position attribute '%s'", pos);
|
auto fail = [fn]() {
|
||||||
|
throw ParseError("cannot parse 'meta.position' attribute '%s'", fn);
|
||||||
|
};
|
||||||
|
|
||||||
std::string filename(pos, 0, colon);
|
|
||||||
unsigned int lineno;
|
|
||||||
try {
|
try {
|
||||||
lineno = std::stoi(std::string(pos, colon + 1, std::string::npos));
|
auto colon = fn.rfind(':');
|
||||||
|
if (colon == std::string::npos) fail();
|
||||||
|
std::string filename(fn, 0, colon);
|
||||||
|
auto lineno = std::stoi(std::string(fn, colon + 1, std::string::npos));
|
||||||
|
return {CanonPath(fn.substr(0, colon)), lineno};
|
||||||
} catch (std::invalid_argument & e) {
|
} catch (std::invalid_argument & e) {
|
||||||
throw ParseError("cannot parse line number '%s'", pos);
|
fail();
|
||||||
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
return { std::move(filename), lineno };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,7 @@ std::pair<Value *, PosIdx> findAlongAttrPath(
|
||||||
/**
|
/**
|
||||||
* Heuristic to find the filename and lineno or a nix value.
|
* Heuristic to find the filename and lineno or a nix value.
|
||||||
*/
|
*/
|
||||||
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
|
std::pair<SourcePath, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
|
||||||
|
|
||||||
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);
|
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ struct AttrDb
|
||||||
{
|
{
|
||||||
auto state(_state->lock());
|
auto state(_state->lock());
|
||||||
|
|
||||||
Path cacheDir = getCacheDir() + "/nix/eval-cache-v4";
|
Path cacheDir = getCacheDir() + "/nix/eval-cache-v5";
|
||||||
createDirs(cacheDir);
|
createDirs(cacheDir);
|
||||||
|
|
||||||
Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite";
|
Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite";
|
||||||
|
@ -300,7 +300,7 @@ struct AttrDb
|
||||||
NixStringContext context;
|
NixStringContext context;
|
||||||
if (!queryAttribute.isNull(3))
|
if (!queryAttribute.isNull(3))
|
||||||
for (auto & s : tokenizeString<std::vector<std::string>>(queryAttribute.getStr(3), ";"))
|
for (auto & s : tokenizeString<std::vector<std::string>>(queryAttribute.getStr(3), ";"))
|
||||||
context.push_back(NixStringContextElem::parse(cfg, s));
|
context.insert(NixStringContextElem::parse(s));
|
||||||
return {{rowId, string_t{queryAttribute.getStr(2), context}}};
|
return {{rowId, string_t{queryAttribute.getStr(2), context}}};
|
||||||
}
|
}
|
||||||
case AttrType::Bool:
|
case AttrType::Bool:
|
||||||
|
@ -442,8 +442,10 @@ Value & AttrCursor::forceValue()
|
||||||
if (v.type() == nString)
|
if (v.type() == nString)
|
||||||
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
|
cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context),
|
||||||
string_t{v.string.s, {}}};
|
string_t{v.string.s, {}}};
|
||||||
else if (v.type() == nPath)
|
else if (v.type() == nPath) {
|
||||||
cachedValue = {root->db->setString(getKey(), v.path), string_t{v.path, {}}};
|
auto path = v.path().path;
|
||||||
|
cachedValue = {root->db->setString(getKey(), path.abs()), string_t{path.abs(), {}}};
|
||||||
|
}
|
||||||
else if (v.type() == nBool)
|
else if (v.type() == nBool)
|
||||||
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
|
cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
|
||||||
else if (v.type() == nInt)
|
else if (v.type() == nInt)
|
||||||
|
@ -580,7 +582,7 @@ std::string AttrCursor::getString()
|
||||||
if (v.type() != nString && v.type() != nPath)
|
if (v.type() != nString && v.type() != nPath)
|
||||||
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
|
|
||||||
return v.type() == nString ? v.string.s : v.path;
|
return v.type() == nString ? v.string.s : v.path().to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
string_t AttrCursor::getStringWithContext()
|
string_t AttrCursor::getStringWithContext()
|
||||||
|
@ -619,10 +621,13 @@ string_t AttrCursor::getStringWithContext()
|
||||||
|
|
||||||
auto & v = forceValue();
|
auto & v = forceValue();
|
||||||
|
|
||||||
if (v.type() == nString)
|
if (v.type() == nString) {
|
||||||
return {v.string.s, v.getContext(*root->state.store)};
|
NixStringContext context;
|
||||||
|
copyContext(v, context);
|
||||||
|
return {v.string.s, std::move(context)};
|
||||||
|
}
|
||||||
else if (v.type() == nPath)
|
else if (v.type() == nPath)
|
||||||
return {v.path, {}};
|
return {v.path().to_string(), {}};
|
||||||
else
|
else
|
||||||
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
|
root->state.error("'%s' is not a string but %s", getAttrPathStr()).debugThrow<TypeError>();
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "filetransfer.hh"
|
#include "filetransfer.hh"
|
||||||
#include "function-trace.hh"
|
#include "function-trace.hh"
|
||||||
#include "profiles.hh"
|
#include "profiles.hh"
|
||||||
|
#include "print.hh"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
@ -104,21 +105,13 @@ void Value::print(const SymbolTable & symbols, std::ostream & str,
|
||||||
str << integer;
|
str << integer;
|
||||||
break;
|
break;
|
||||||
case tBool:
|
case tBool:
|
||||||
str << (boolean ? "true" : "false");
|
printLiteralBool(str, boolean);
|
||||||
break;
|
break;
|
||||||
case tString:
|
case tString:
|
||||||
str << "\"";
|
printLiteralString(str, string.s);
|
||||||
for (const char * i = string.s; *i; i++)
|
|
||||||
if (*i == '\"' || *i == '\\') str << "\\" << *i;
|
|
||||||
else if (*i == '\n') str << "\\n";
|
|
||||||
else if (*i == '\r') str << "\\r";
|
|
||||||
else if (*i == '\t') str << "\\t";
|
|
||||||
else if (*i == '$' && *(i+1) == '{') str << "\\" << *i;
|
|
||||||
else str << *i;
|
|
||||||
str << "\"";
|
|
||||||
break;
|
break;
|
||||||
case tPath:
|
case tPath:
|
||||||
str << path; // !!! escaping?
|
str << path().to_string(); // !!! escaping?
|
||||||
break;
|
break;
|
||||||
case tNull:
|
case tNull:
|
||||||
str << "null";
|
str << "null";
|
||||||
|
@ -542,6 +535,7 @@ EvalState::EvalState(
|
||||||
, sOutputSpecified(symbols.create("outputSpecified"))
|
, sOutputSpecified(symbols.create("outputSpecified"))
|
||||||
, repair(NoRepair)
|
, repair(NoRepair)
|
||||||
, emptyBindings(0)
|
, emptyBindings(0)
|
||||||
|
, derivationInternal(rootPath(CanonPath("/builtin/derivation.nix")))
|
||||||
, store(store)
|
, store(store)
|
||||||
, buildStore(buildStore ? buildStore : store)
|
, buildStore(buildStore ? buildStore : store)
|
||||||
, debugRepl(nullptr)
|
, debugRepl(nullptr)
|
||||||
|
@ -616,15 +610,14 @@ void EvalState::allowAndSetStorePathString(const StorePath & storePath, Value &
|
||||||
{
|
{
|
||||||
allowPath(storePath);
|
allowPath(storePath);
|
||||||
|
|
||||||
auto path = store->printStorePath(storePath);
|
mkStorePathString(storePath, v);
|
||||||
v.mkString(path, PathSet({path}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Path EvalState::checkSourcePath(const Path & path_)
|
SourcePath EvalState::checkSourcePath(const SourcePath & path_)
|
||||||
{
|
{
|
||||||
if (!allowedPaths) return path_;
|
if (!allowedPaths) return path_;
|
||||||
|
|
||||||
auto i = resolvedPaths.find(path_);
|
auto i = resolvedPaths.find(path_.path.abs());
|
||||||
if (i != resolvedPaths.end())
|
if (i != resolvedPaths.end())
|
||||||
return i->second;
|
return i->second;
|
||||||
|
|
||||||
|
@ -634,9 +627,9 @@ Path EvalState::checkSourcePath(const Path & path_)
|
||||||
* attacker can't append ../../... to a path that would be in allowedPaths
|
* attacker can't append ../../... to a path that would be in allowedPaths
|
||||||
* and thus leak symlink targets.
|
* and thus leak symlink targets.
|
||||||
*/
|
*/
|
||||||
Path abspath = canonPath(path_);
|
Path abspath = canonPath(path_.path.abs());
|
||||||
|
|
||||||
if (hasPrefix(abspath, corepkgsPrefix)) return abspath;
|
if (hasPrefix(abspath, corepkgsPrefix)) return CanonPath(abspath);
|
||||||
|
|
||||||
for (auto & i : *allowedPaths) {
|
for (auto & i : *allowedPaths) {
|
||||||
if (isDirOrInDir(abspath, i)) {
|
if (isDirOrInDir(abspath, i)) {
|
||||||
|
@ -654,11 +647,11 @@ Path EvalState::checkSourcePath(const Path & path_)
|
||||||
|
|
||||||
/* Resolve symlinks. */
|
/* Resolve symlinks. */
|
||||||
debug("checking access to '%s'", abspath);
|
debug("checking access to '%s'", abspath);
|
||||||
Path path = canonPath(abspath, true);
|
SourcePath path = CanonPath(canonPath(abspath, true));
|
||||||
|
|
||||||
for (auto & i : *allowedPaths) {
|
for (auto & i : *allowedPaths) {
|
||||||
if (isDirOrInDir(path, i)) {
|
if (isDirOrInDir(path.path.abs(), i)) {
|
||||||
resolvedPaths[path_] = path;
|
resolvedPaths.insert_or_assign(path_.path.abs(), path);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -686,12 +679,12 @@ void EvalState::checkURI(const std::string & uri)
|
||||||
/* If the URI is a path, then check it against allowedPaths as
|
/* If the URI is a path, then check it against allowedPaths as
|
||||||
well. */
|
well. */
|
||||||
if (hasPrefix(uri, "/")) {
|
if (hasPrefix(uri, "/")) {
|
||||||
checkSourcePath(uri);
|
checkSourcePath(CanonPath(uri));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPrefix(uri, "file://")) {
|
if (hasPrefix(uri, "file://")) {
|
||||||
checkSourcePath(std::string(uri, 7));
|
checkSourcePath(CanonPath(std::string(uri, 7)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,7 +692,7 @@ void EvalState::checkURI(const std::string & uri)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Path EvalState::toRealPath(const Path & path, const PathSet & context)
|
Path EvalState::toRealPath(const Path & path, const NixStringContext & context)
|
||||||
{
|
{
|
||||||
// FIXME: check whether 'path' is in 'context'.
|
// FIXME: check whether 'path' is in 'context'.
|
||||||
return
|
return
|
||||||
|
@ -951,34 +944,34 @@ void Value::mkString(std::string_view s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void copyContextToValue(Value & v, const PathSet & context)
|
static void copyContextToValue(Value & v, const NixStringContext & context)
|
||||||
{
|
{
|
||||||
if (!context.empty()) {
|
if (!context.empty()) {
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
v.string.context = (const char * *)
|
v.string.context = (const char * *)
|
||||||
allocBytes((context.size() + 1) * sizeof(char *));
|
allocBytes((context.size() + 1) * sizeof(char *));
|
||||||
for (auto & i : context)
|
for (auto & i : context)
|
||||||
v.string.context[n++] = dupString(i.c_str());
|
v.string.context[n++] = dupString(i.to_string().c_str());
|
||||||
v.string.context[n] = 0;
|
v.string.context[n] = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Value::mkString(std::string_view s, const PathSet & context)
|
void Value::mkString(std::string_view s, const NixStringContext & context)
|
||||||
{
|
{
|
||||||
mkString(s);
|
mkString(s);
|
||||||
copyContextToValue(*this, context);
|
copyContextToValue(*this, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Value::mkStringMove(const char * s, const PathSet & context)
|
void Value::mkStringMove(const char * s, const NixStringContext & context)
|
||||||
{
|
{
|
||||||
mkString(s);
|
mkString(s);
|
||||||
copyContextToValue(*this, context);
|
copyContextToValue(*this, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Value::mkPath(std::string_view s)
|
void Value::mkPath(const SourcePath & path)
|
||||||
{
|
{
|
||||||
mkPath(makeImmutableString(s));
|
mkPath(makeImmutableString(path.path.abs()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1034,9 +1027,9 @@ void EvalState::mkThunk_(Value & v, Expr * expr)
|
||||||
void EvalState::mkPos(Value & v, PosIdx p)
|
void EvalState::mkPos(Value & v, PosIdx p)
|
||||||
{
|
{
|
||||||
auto pos = positions[p];
|
auto pos = positions[p];
|
||||||
if (auto path = std::get_if<Path>(&pos.origin)) {
|
if (auto path = std::get_if<SourcePath>(&pos.origin)) {
|
||||||
auto attrs = buildBindings(3);
|
auto attrs = buildBindings(3);
|
||||||
attrs.alloc(sFile).mkString(*path);
|
attrs.alloc(sFile).mkString(path->path.abs());
|
||||||
attrs.alloc(sLine).mkInt(pos.line);
|
attrs.alloc(sLine).mkInt(pos.line);
|
||||||
attrs.alloc(sColumn).mkInt(pos.column);
|
attrs.alloc(sColumn).mkInt(pos.column);
|
||||||
v.mkAttrs(attrs);
|
v.mkAttrs(attrs);
|
||||||
|
@ -1045,6 +1038,16 @@ void EvalState::mkPos(Value & v, PosIdx p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void EvalState::mkStorePathString(const StorePath & p, Value & v)
|
||||||
|
{
|
||||||
|
v.mkString(
|
||||||
|
store->printStorePath(p),
|
||||||
|
NixStringContext {
|
||||||
|
NixStringContextElem::Opaque { .path = p },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Create a thunk for the delayed computation of the given expression
|
/* Create a thunk for the delayed computation of the given expression
|
||||||
in the given environment. But if the expression is a variable,
|
in the given environment. But if the expression is a variable,
|
||||||
then look it up right away. This significantly reduces the number
|
then look it up right away. This significantly reduces the number
|
||||||
|
@ -1092,7 +1095,7 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
|
void EvalState::evalFile(const SourcePath & path_, Value & v, bool mustBeTrivial)
|
||||||
{
|
{
|
||||||
auto path = checkSourcePath(path_);
|
auto path = checkSourcePath(path_);
|
||||||
|
|
||||||
|
@ -1102,7 +1105,7 @@ void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Path resolvedPath = resolveExprPath(path);
|
auto resolvedPath = resolveExprPath(path);
|
||||||
if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) {
|
if ((i = fileEvalCache.find(resolvedPath)) != fileEvalCache.end()) {
|
||||||
v = i->second;
|
v = i->second;
|
||||||
return;
|
return;
|
||||||
|
@ -1130,8 +1133,8 @@ void EvalState::resetFileCache()
|
||||||
|
|
||||||
|
|
||||||
void EvalState::cacheFile(
|
void EvalState::cacheFile(
|
||||||
const Path & path,
|
const SourcePath & path,
|
||||||
const Path & resolvedPath,
|
const SourcePath & resolvedPath,
|
||||||
Expr * e,
|
Expr * e,
|
||||||
Value & v,
|
Value & v,
|
||||||
bool mustBeTrivial)
|
bool mustBeTrivial)
|
||||||
|
@ -1145,7 +1148,7 @@ void EvalState::cacheFile(
|
||||||
*e,
|
*e,
|
||||||
this->baseEnv,
|
this->baseEnv,
|
||||||
e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr,
|
e->getPos() ? static_cast<std::shared_ptr<AbstractPos>>(positions[e->getPos()]) : nullptr,
|
||||||
"while evaluating the file '%1%':", resolvedPath)
|
"while evaluating the file '%1%':", resolvedPath.to_string())
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
|
||||||
// Enforce that 'flake.nix' is a direct attrset, not a
|
// Enforce that 'flake.nix' is a direct attrset, not a
|
||||||
|
@ -1155,7 +1158,7 @@ void EvalState::cacheFile(
|
||||||
error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
|
error("file '%s' must be an attribute set", path).debugThrow<EvalError>();
|
||||||
eval(e, v);
|
eval(e, v);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath);
|
addErrorTrace(e, "while evaluating the file '%1%':", resolvedPath.to_string());
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1416,8 +1419,8 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
if (pos2) {
|
if (pos2) {
|
||||||
auto pos2r = state.positions[pos2];
|
auto pos2r = state.positions[pos2];
|
||||||
auto origin = std::get_if<Path>(&pos2r.origin);
|
auto origin = std::get_if<SourcePath>(&pos2r.origin);
|
||||||
if (!(origin && *origin == state.derivationNixPath))
|
if (!(origin && *origin == state.derivationInternal))
|
||||||
state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
|
state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
|
||||||
showAttrPath(state, env, attrPath));
|
showAttrPath(state, env, attrPath));
|
||||||
}
|
}
|
||||||
|
@ -1907,7 +1910,7 @@ void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Po
|
||||||
|
|
||||||
void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
std::vector<BackedStringView> s;
|
std::vector<BackedStringView> s;
|
||||||
size_t sSize = 0;
|
size_t sSize = 0;
|
||||||
NixInt n = 0;
|
NixInt n = 0;
|
||||||
|
@ -1990,7 +1993,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
else if (firstType == nPath) {
|
else if (firstType == nPath) {
|
||||||
if (!context.empty())
|
if (!context.empty())
|
||||||
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
|
state.error("a string that refers to a store path cannot be appended to a path").atPos(pos).withFrame(env, *this).debugThrow<EvalError>();
|
||||||
v.mkPath(canonPath(str()));
|
v.mkPath(CanonPath(canonPath(str())));
|
||||||
} else
|
} else
|
||||||
v.mkStringMove(c_str(), context);
|
v.mkStringMove(c_str(), context);
|
||||||
}
|
}
|
||||||
|
@ -2116,26 +2119,15 @@ std::string_view EvalState::forceString(Value & v, const PosIdx pos, std::string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void copyContext(const Value & v, PathSet & context)
|
void copyContext(const Value & v, NixStringContext & context)
|
||||||
{
|
{
|
||||||
if (v.string.context)
|
if (v.string.context)
|
||||||
for (const char * * p = v.string.context; *p; ++p)
|
for (const char * * p = v.string.context; *p; ++p)
|
||||||
context.insert(*p);
|
context.insert(NixStringContextElem::parse(*p));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NixStringContext Value::getContext(const Store & store)
|
std::string_view EvalState::forceString(Value & v, NixStringContext & context, const PosIdx pos, std::string_view errorCtx)
|
||||||
{
|
|
||||||
NixStringContext res;
|
|
||||||
assert(internalType == tString);
|
|
||||||
if (string.context)
|
|
||||||
for (const char * * p = string.context; *p; ++p)
|
|
||||||
res.push_back(NixStringContextElem::parse(store, *p));
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx)
|
|
||||||
{
|
{
|
||||||
auto s = forceString(v, pos, errorCtx);
|
auto s = forceString(v, pos, errorCtx);
|
||||||
copyContext(v, context);
|
copyContext(v, context);
|
||||||
|
@ -2165,7 +2157,7 @@ bool EvalState::isDerivation(Value & v)
|
||||||
|
|
||||||
|
|
||||||
std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value & v,
|
std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value & v,
|
||||||
PathSet & context, bool coerceMore, bool copyToStore)
|
NixStringContext & context, bool coerceMore, bool copyToStore)
|
||||||
{
|
{
|
||||||
auto i = v.attrs->find(sToString);
|
auto i = v.attrs->find(sToString);
|
||||||
if (i != v.attrs->end()) {
|
if (i != v.attrs->end()) {
|
||||||
|
@ -2179,8 +2171,14 @@ std::optional<std::string> EvalState::tryAttrsToString(const PosIdx pos, Value &
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &context,
|
BackedStringView EvalState::coerceToString(
|
||||||
std::string_view errorCtx, bool coerceMore, bool copyToStore, bool canonicalizePath)
|
const PosIdx pos,
|
||||||
|
Value & v,
|
||||||
|
NixStringContext & context,
|
||||||
|
std::string_view errorCtx,
|
||||||
|
bool coerceMore,
|
||||||
|
bool copyToStore,
|
||||||
|
bool canonicalizePath)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
|
|
||||||
|
@ -2190,12 +2188,14 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.type() == nPath) {
|
if (v.type() == nPath) {
|
||||||
BackedStringView path(PathView(v.path));
|
return
|
||||||
if (canonicalizePath)
|
!canonicalizePath && !copyToStore
|
||||||
path = canonPath(*path);
|
? // FIXME: hack to preserve path literals that end in a
|
||||||
if (copyToStore)
|
// slash, as in /foo/${x}.
|
||||||
path = store->printStorePath(copyPathToStore(context, std::move(path).toOwned()));
|
v._path
|
||||||
return path;
|
: copyToStore
|
||||||
|
? store->printStorePath(copyPathToStore(context, v.path()))
|
||||||
|
: std::string(v.path().path.abs());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.type() == nAttrs) {
|
if (v.type() == nAttrs) {
|
||||||
|
@ -2256,40 +2256,40 @@ BackedStringView EvalState::coerceToString(const PosIdx pos, Value &v, PathSet &
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StorePath EvalState::copyPathToStore(PathSet & context, const Path & path)
|
StorePath EvalState::copyPathToStore(NixStringContext & context, const SourcePath & path)
|
||||||
{
|
{
|
||||||
if (nix::isDerivation(path))
|
if (nix::isDerivation(path.path.abs()))
|
||||||
error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
|
error("file names are not allowed to end in '%1%'", drvExtension).debugThrow<EvalError>();
|
||||||
|
|
||||||
auto dstPath = [&]() -> StorePath
|
auto i = srcToStore.find(path);
|
||||||
{
|
|
||||||
auto i = srcToStore.find(path);
|
|
||||||
if (i != srcToStore.end()) return i->second;
|
|
||||||
|
|
||||||
auto dstPath = settings.readOnlyMode
|
auto dstPath = i != srcToStore.end()
|
||||||
? store->computeStorePathForPath(std::string(baseNameOf(path)), checkSourcePath(path)).first
|
? i->second
|
||||||
: store->addToStore(std::string(baseNameOf(path)), checkSourcePath(path), FileIngestionMethod::Recursive, htSHA256, defaultPathFilter, repair);
|
: [&]() {
|
||||||
allowPath(dstPath);
|
auto dstPath = path.fetchToStore(store, path.baseName(), nullptr, repair);
|
||||||
srcToStore.insert_or_assign(path, dstPath);
|
allowPath(dstPath);
|
||||||
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath));
|
srcToStore.insert_or_assign(path, dstPath);
|
||||||
return dstPath;
|
printMsg(lvlChatty, "copied source '%1%' -> '%2%'", path, store->printStorePath(dstPath));
|
||||||
}();
|
return dstPath;
|
||||||
|
}();
|
||||||
|
|
||||||
context.insert(store->printStorePath(dstPath));
|
context.insert(NixStringContextElem::Opaque {
|
||||||
|
.path = dstPath
|
||||||
|
});
|
||||||
return dstPath;
|
return dstPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
|
SourcePath EvalState::coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
|
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
|
||||||
if (path == "" || path[0] != '/')
|
if (path == "" || path[0] != '/')
|
||||||
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
error("string '%1%' doesn't represent an absolute path", path).withTrace(pos, errorCtx).debugThrow<EvalError>();
|
||||||
return path;
|
return CanonPath(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx)
|
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx)
|
||||||
{
|
{
|
||||||
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
|
auto path = coerceToString(pos, v, context, errorCtx, false, false, true).toOwned();
|
||||||
if (auto storePath = store->maybeParseStorePath(path))
|
if (auto storePath = store->maybeParseStorePath(path))
|
||||||
|
@ -2328,7 +2328,7 @@ bool EvalState::eqValues(Value & v1, Value & v2, const PosIdx pos, std::string_v
|
||||||
return strcmp(v1.string.s, v2.string.s) == 0;
|
return strcmp(v1.string.s, v2.string.s) == 0;
|
||||||
|
|
||||||
case nPath:
|
case nPath:
|
||||||
return strcmp(v1.path, v2.path) == 0;
|
return strcmp(v1._path, v2._path) == 0;
|
||||||
|
|
||||||
case nNull:
|
case nNull:
|
||||||
return true;
|
return true;
|
||||||
|
@ -2455,8 +2455,8 @@ void EvalState::printStats()
|
||||||
else
|
else
|
||||||
obj["name"] = nullptr;
|
obj["name"] = nullptr;
|
||||||
if (auto pos = positions[fun->pos]) {
|
if (auto pos = positions[fun->pos]) {
|
||||||
if (auto path = std::get_if<Path>(&pos.origin))
|
if (auto path = std::get_if<SourcePath>(&pos.origin))
|
||||||
obj["file"] = *path;
|
obj["file"] = path->to_string();
|
||||||
obj["line"] = pos.line;
|
obj["line"] = pos.line;
|
||||||
obj["column"] = pos.column;
|
obj["column"] = pos.column;
|
||||||
}
|
}
|
||||||
|
@ -2470,8 +2470,8 @@ void EvalState::printStats()
|
||||||
for (auto & i : attrSelects) {
|
for (auto & i : attrSelects) {
|
||||||
json obj = json::object();
|
json obj = json::object();
|
||||||
if (auto pos = positions[i.first]) {
|
if (auto pos = positions[i.first]) {
|
||||||
if (auto path = std::get_if<Path>(&pos.origin))
|
if (auto path = std::get_if<SourcePath>(&pos.origin))
|
||||||
obj["file"] = *path;
|
obj["file"] = path->to_string();
|
||||||
obj["line"] = pos.line;
|
obj["line"] = pos.line;
|
||||||
obj["column"] = pos.column;
|
obj["column"] = pos.column;
|
||||||
}
|
}
|
||||||
|
@ -2496,7 +2496,7 @@ void EvalState::printStats()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
|
std::string ExternalValueBase::coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const
|
||||||
{
|
{
|
||||||
throw TypeError({
|
throw TypeError({
|
||||||
.msg = hintfmt("cannot coerce %1% to a string", showType())
|
.msg = hintfmt("cannot coerce %1% to a string", showType())
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
#include "config.hh"
|
#include "config.hh"
|
||||||
#include "experimental-features.hh"
|
#include "experimental-features.hh"
|
||||||
|
#include "input-accessor.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -56,20 +57,14 @@ void printEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env &
|
||||||
|
|
||||||
std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env);
|
std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const StaticEnv & se, const Env & env);
|
||||||
|
|
||||||
void copyContext(const Value & v, PathSet & context);
|
void copyContext(const Value & v, NixStringContext & context);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cache for calls to addToStore(); maps source paths to the store
|
|
||||||
* paths.
|
|
||||||
*/
|
|
||||||
typedef std::map<Path, StorePath> SrcToStore;
|
|
||||||
|
|
||||||
|
|
||||||
std::string printValue(const EvalState & state, const Value & v);
|
std::string printValue(const EvalState & state, const Value & v);
|
||||||
std::ostream & operator << (std::ostream & os, const ValueType t);
|
std::ostream & operator << (std::ostream & os, const ValueType t);
|
||||||
|
|
||||||
|
|
||||||
|
// FIXME: maybe change this to an std::variant<SourcePath, URL>.
|
||||||
typedef std::pair<std::string, std::string> SearchPathElem;
|
typedef std::pair<std::string, std::string> SearchPathElem;
|
||||||
typedef std::list<SearchPathElem> SearchPath;
|
typedef std::list<SearchPathElem> SearchPath;
|
||||||
|
|
||||||
|
@ -137,8 +132,6 @@ public:
|
||||||
SymbolTable symbols;
|
SymbolTable symbols;
|
||||||
PosTable positions;
|
PosTable positions;
|
||||||
|
|
||||||
static inline std::string derivationNixPath = "//builtin/derivation.nix";
|
|
||||||
|
|
||||||
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
|
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
|
||||||
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
|
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
|
||||||
sFile, sLine, sColumn, sFunctor, sToString,
|
sFile, sLine, sColumn, sFunctor, sToString,
|
||||||
|
@ -149,7 +142,6 @@ public:
|
||||||
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
|
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
|
||||||
sPrefix,
|
sPrefix,
|
||||||
sOutputSpecified;
|
sOutputSpecified;
|
||||||
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
|
||||||
|
@ -165,6 +157,8 @@ public:
|
||||||
|
|
||||||
Bindings emptyBindings;
|
Bindings emptyBindings;
|
||||||
|
|
||||||
|
const SourcePath derivationInternal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store used to materialise .drv files.
|
* Store used to materialise .drv files.
|
||||||
*/
|
*/
|
||||||
|
@ -234,15 +228,18 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SrcToStore srcToStore;
|
|
||||||
|
/* Cache for calls to addToStore(); maps source paths to the store
|
||||||
|
paths. */
|
||||||
|
std::map<SourcePath, StorePath> srcToStore;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A cache from path names to parse trees.
|
* A cache from path names to parse trees.
|
||||||
*/
|
*/
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
typedef std::map<Path, Expr *, std::less<Path>, traceable_allocator<std::pair<const Path, Expr *>>> FileParseCache;
|
typedef std::map<SourcePath, Expr *, std::less<SourcePath>, traceable_allocator<std::pair<const SourcePath, Expr *>>> FileParseCache;
|
||||||
#else
|
#else
|
||||||
typedef std::map<Path, Expr *> FileParseCache;
|
typedef std::map<SourcePath, Expr *> FileParseCache;
|
||||||
#endif
|
#endif
|
||||||
FileParseCache fileParseCache;
|
FileParseCache fileParseCache;
|
||||||
|
|
||||||
|
@ -250,9 +247,9 @@ private:
|
||||||
* A cache from path names to values.
|
* A cache from path names to values.
|
||||||
*/
|
*/
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
typedef std::map<Path, Value, std::less<Path>, traceable_allocator<std::pair<const Path, Value>>> FileEvalCache;
|
typedef std::map<SourcePath, Value, std::less<SourcePath>, traceable_allocator<std::pair<const SourcePath, Value>>> FileEvalCache;
|
||||||
#else
|
#else
|
||||||
typedef std::map<Path, Value> FileEvalCache;
|
typedef std::map<SourcePath, Value> FileEvalCache;
|
||||||
#endif
|
#endif
|
||||||
FileEvalCache fileEvalCache;
|
FileEvalCache fileEvalCache;
|
||||||
|
|
||||||
|
@ -263,7 +260,7 @@ private:
|
||||||
/**
|
/**
|
||||||
* Cache used by checkSourcePath().
|
* Cache used by checkSourcePath().
|
||||||
*/
|
*/
|
||||||
std::unordered_map<Path, Path> resolvedPaths;
|
std::unordered_map<Path, SourcePath> resolvedPaths;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache used by prim_match().
|
* Cache used by prim_match().
|
||||||
|
@ -294,6 +291,12 @@ public:
|
||||||
|
|
||||||
SearchPath getSearchPath() { return searchPath; }
|
SearchPath getSearchPath() { return searchPath; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a `SourcePath` that refers to `path` in the root
|
||||||
|
* filesystem.
|
||||||
|
*/
|
||||||
|
SourcePath rootPath(CanonPath path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow access to a path.
|
* Allow access to a path.
|
||||||
*/
|
*/
|
||||||
|
@ -314,7 +317,7 @@ public:
|
||||||
* Check whether access to a path is allowed and throw an error if
|
* Check whether access to a path is allowed and throw an error if
|
||||||
* not. Otherwise return the canonicalised path.
|
* not. Otherwise return the canonicalised path.
|
||||||
*/
|
*/
|
||||||
Path checkSourcePath(const Path & path);
|
SourcePath checkSourcePath(const SourcePath & path);
|
||||||
|
|
||||||
void checkURI(const std::string & uri);
|
void checkURI(const std::string & uri);
|
||||||
|
|
||||||
|
@ -327,19 +330,19 @@ public:
|
||||||
* intended to distinguish between import-from-derivation and
|
* intended to distinguish between import-from-derivation and
|
||||||
* sources stored in the actual /nix/store.
|
* sources stored in the actual /nix/store.
|
||||||
*/
|
*/
|
||||||
Path toRealPath(const Path & path, const PathSet & context);
|
Path toRealPath(const Path & path, const NixStringContext & context);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parse a Nix expression from the specified file.
|
* Parse a Nix expression from the specified file.
|
||||||
*/
|
*/
|
||||||
Expr * parseExprFromFile(const Path & path);
|
Expr * parseExprFromFile(const SourcePath & path);
|
||||||
Expr * parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv);
|
Expr * parseExprFromFile(const SourcePath & 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, std::shared_ptr<StaticEnv> & staticEnv);
|
Expr * parseExprFromString(std::string s, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv);
|
||||||
Expr * parseExprFromString(std::string s, const Path & basePath);
|
Expr * parseExprFromString(std::string s, const SourcePath & basePath);
|
||||||
|
|
||||||
Expr * parseStdin();
|
Expr * parseStdin();
|
||||||
|
|
||||||
|
@ -348,14 +351,14 @@ public:
|
||||||
* form. Optionally enforce that the top-level expression is
|
* form. Optionally enforce that the top-level expression is
|
||||||
* 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 SourcePath & path, Value & v, bool mustBeTrivial = false);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like `evalFile`, but with an already parsed expression.
|
* Like `evalFile`, but with an already parsed expression.
|
||||||
*/
|
*/
|
||||||
void cacheFile(
|
void cacheFile(
|
||||||
const Path & path,
|
const SourcePath & path,
|
||||||
const Path & resolvedPath,
|
const SourcePath & resolvedPath,
|
||||||
Expr * e,
|
Expr * e,
|
||||||
Value & v,
|
Value & v,
|
||||||
bool mustBeTrivial = false);
|
bool mustBeTrivial = false);
|
||||||
|
@ -365,8 +368,8 @@ public:
|
||||||
/**
|
/**
|
||||||
* Look up a file in the search path.
|
* Look up a file in the search path.
|
||||||
*/
|
*/
|
||||||
Path findFile(const std::string_view path);
|
SourcePath findFile(const std::string_view path);
|
||||||
Path findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
|
SourcePath findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the specified search path element is a URI, download it.
|
* If the specified search path element is a URI, download it.
|
||||||
|
@ -423,7 +426,7 @@ public:
|
||||||
*/
|
*/
|
||||||
void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx);
|
void forceFunction(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx);
|
std::string_view forceString(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos, std::string_view errorCtx);
|
std::string_view forceString(Value & v, NixStringContext & context, const PosIdx pos, std::string_view errorCtx);
|
||||||
std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
|
std::string_view forceStringNoCtx(Value & v, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
[[gnu::noinline]]
|
[[gnu::noinline]]
|
||||||
|
@ -439,7 +442,7 @@ public:
|
||||||
bool isDerivation(Value & v);
|
bool isDerivation(Value & v);
|
||||||
|
|
||||||
std::optional<std::string> tryAttrsToString(const PosIdx pos, Value & v,
|
std::optional<std::string> tryAttrsToString(const PosIdx pos, Value & v,
|
||||||
PathSet & context, bool coerceMore = false, bool copyToStore = true);
|
NixStringContext & context, bool coerceMore = false, bool copyToStore = true);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* String coercion.
|
* String coercion.
|
||||||
|
@ -449,12 +452,12 @@ public:
|
||||||
* booleans and lists to a string. If `copyToStore` is set,
|
* booleans and lists to a string. If `copyToStore` is set,
|
||||||
* referenced paths are copied to the Nix store as a side effect.
|
* referenced paths are copied to the Nix store as a side effect.
|
||||||
*/
|
*/
|
||||||
BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
BackedStringView coerceToString(const PosIdx pos, Value & v, NixStringContext & context,
|
||||||
std::string_view errorCtx,
|
std::string_view errorCtx,
|
||||||
bool coerceMore = false, bool copyToStore = true,
|
bool coerceMore = false, bool copyToStore = true,
|
||||||
bool canonicalizePath = true);
|
bool canonicalizePath = true);
|
||||||
|
|
||||||
StorePath copyPathToStore(PathSet & context, const Path & path);
|
StorePath copyPathToStore(NixStringContext & context, const SourcePath & path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Path coercion.
|
* Path coercion.
|
||||||
|
@ -463,12 +466,12 @@ public:
|
||||||
* path. The result is guaranteed to be a canonicalised, absolute
|
* path. The result is guaranteed to be a canonicalised, absolute
|
||||||
* path. Nothing is copied to the store.
|
* path. Nothing is copied to the store.
|
||||||
*/
|
*/
|
||||||
Path coerceToPath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
|
SourcePath coerceToPath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Like coerceToPath, but the result must be a store path.
|
* Like coerceToPath, but the result must be a store path.
|
||||||
*/
|
*/
|
||||||
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context, std::string_view errorCtx);
|
StorePath coerceToStorePath(const PosIdx pos, Value & v, NixStringContext & context, std::string_view errorCtx);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -525,7 +528,7 @@ private:
|
||||||
char * text,
|
char * text,
|
||||||
size_t length,
|
size_t length,
|
||||||
Pos::Origin origin,
|
Pos::Origin origin,
|
||||||
Path basePath,
|
const SourcePath & basePath,
|
||||||
std::shared_ptr<StaticEnv> & staticEnv);
|
std::shared_ptr<StaticEnv> & staticEnv);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -573,6 +576,12 @@ public:
|
||||||
void mkThunk_(Value & v, Expr * expr);
|
void mkThunk_(Value & v, Expr * expr);
|
||||||
void mkPos(Value & v, PosIdx pos);
|
void mkPos(Value & v, PosIdx pos);
|
||||||
|
|
||||||
|
/* Create a string representing a store path.
|
||||||
|
|
||||||
|
The string is the printed store path with a context containing a single
|
||||||
|
`Opaque` element of that store path. */
|
||||||
|
void mkStorePathString(const StorePath & storePath, Value & v);
|
||||||
|
|
||||||
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
|
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos, std::string_view errorCtx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -584,7 +593,7 @@ public:
|
||||||
* Realise the given context, and return a mapping from the placeholders
|
* Realise the given context, and return a mapping from the placeholders
|
||||||
* used to construct the associated value to their final store path
|
* used to construct the associated value to their final store path
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] StringMap realiseContext(const PathSet & context);
|
[[nodiscard]] StringMap realiseContext(const NixStringContext & context);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
@ -650,7 +659,7 @@ std::string showType(const Value & v);
|
||||||
/**
|
/**
|
||||||
* If `path` refers to a directory, then append "/default.nix".
|
* If `path` refers to a directory, then append "/default.nix".
|
||||||
*/
|
*/
|
||||||
Path resolveExprPath(Path path);
|
SourcePath resolveExprPath(const SourcePath & path);
|
||||||
|
|
||||||
struct InvalidPathError : EvalError
|
struct InvalidPathError : EvalError
|
||||||
{
|
{
|
||||||
|
|
|
@ -31,7 +31,7 @@ static void writeTrustedList(const TrustedList & trustedList)
|
||||||
|
|
||||||
void ConfigFile::apply()
|
void ConfigFile::apply()
|
||||||
{
|
{
|
||||||
std::set<std::string> whitelist{"bash-prompt", "bash-prompt-prefix", "bash-prompt-suffix", "flake-registry"};
|
std::set<std::string> whitelist{"bash-prompt", "bash-prompt-prefix", "bash-prompt-suffix", "flake-registry", "commit-lockfile-summary"};
|
||||||
|
|
||||||
for (auto & [name, value] : settings) {
|
for (auto & [name, value] : settings) {
|
||||||
|
|
||||||
|
|
|
@ -222,9 +222,9 @@ static Flake getFlake(
|
||||||
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
|
throw Error("source tree referenced by '%s' does not contain a '%s/flake.nix' file", lockedRef, lockedRef.subdir);
|
||||||
|
|
||||||
Value vInfo;
|
Value vInfo;
|
||||||
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
|
state.evalFile(CanonPath(flakeFile), vInfo, true); // FIXME: symlink attack
|
||||||
|
|
||||||
expectType(state, nAttrs, vInfo, state.positions.add({flakeFile}, 1, 1));
|
expectType(state, nAttrs, vInfo, state.positions.add({CanonPath(flakeFile)}, 1, 1));
|
||||||
|
|
||||||
if (auto description = vInfo.attrs->get(state.sDescription)) {
|
if (auto description = vInfo.attrs->get(state.sDescription)) {
|
||||||
expectType(state, nString, *description->value, description->pos);
|
expectType(state, nString, *description->value, description->pos);
|
||||||
|
@ -265,7 +265,7 @@ static Flake getFlake(
|
||||||
state.symbols[setting.name],
|
state.symbols[setting.name],
|
||||||
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
|
std::string(state.forceStringNoCtx(*setting.value, setting.pos, "")));
|
||||||
else if (setting.value->type() == nPath) {
|
else if (setting.value->type() == nPath) {
|
||||||
PathSet emptyContext = {};
|
NixStringContext emptyContext = {};
|
||||||
flake.config.settings.emplace(
|
flake.config.settings.emplace(
|
||||||
state.symbols[setting.name],
|
state.symbols[setting.name],
|
||||||
state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true, true) .toOwned());
|
state.coerceToString(setting.pos, *setting.value, emptyContext, "", false, true, true) .toOwned());
|
||||||
|
@ -745,7 +745,7 @@ void callFlake(EvalState & state,
|
||||||
state.vCallFlake = allocRootValue(state.allocValue());
|
state.vCallFlake = allocRootValue(state.allocValue());
|
||||||
state.eval(state.parseExprFromString(
|
state.eval(state.parseExprFromString(
|
||||||
#include "call-flake.nix.gen.hh"
|
#include "call-flake.nix.gen.hh"
|
||||||
, "/"), **state.vCallFlake);
|
, CanonPath::root), **state.vCallFlake);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos);
|
state.callFunction(**state.vCallFlake, *vLocks, *vTmp1, noPos);
|
||||||
|
|
|
@ -234,6 +234,11 @@ bool LockFile::operator ==(const LockFile & other) const
|
||||||
return toJSON() == other.toJSON();
|
return toJSON() == other.toJSON();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool LockFile::operator !=(const LockFile & other) const
|
||||||
|
{
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
InputPath parseInputPath(std::string_view s)
|
InputPath parseInputPath(std::string_view s)
|
||||||
{
|
{
|
||||||
InputPath path;
|
InputPath path;
|
||||||
|
|
|
@ -73,6 +73,9 @@ struct LockFile
|
||||||
std::optional<FlakeRef> isUnlocked() const;
|
std::optional<FlakeRef> isUnlocked() const;
|
||||||
|
|
||||||
bool operator ==(const LockFile & other) const;
|
bool operator ==(const LockFile & other) const;
|
||||||
|
// Needed for old gcc versions that don't synthesize it (like gcc 8.2.2
|
||||||
|
// that is still the default on aarch64-linux)
|
||||||
|
bool operator !=(const LockFile & other) const;
|
||||||
|
|
||||||
std::shared_ptr<Node> findInput(const InputPath & path);
|
std::shared_ptr<Node> findInput(const InputPath & path);
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,7 @@ std::optional<StorePath> DrvInfo::queryDrvPath() const
|
||||||
{
|
{
|
||||||
if (!drvPath && attrs) {
|
if (!drvPath && attrs) {
|
||||||
Bindings::iterator i = attrs->find(state->sDrvPath);
|
Bindings::iterator i = attrs->find(state->sDrvPath);
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
if (i == attrs->end())
|
if (i == attrs->end())
|
||||||
drvPath = {std::nullopt};
|
drvPath = {std::nullopt};
|
||||||
else
|
else
|
||||||
|
@ -93,7 +93,7 @@ StorePath DrvInfo::queryOutPath() const
|
||||||
{
|
{
|
||||||
if (!outPath && attrs) {
|
if (!outPath && attrs) {
|
||||||
Bindings::iterator i = attrs->find(state->sOutPath);
|
Bindings::iterator i = attrs->find(state->sOutPath);
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
if (i != attrs->end())
|
if (i != attrs->end())
|
||||||
outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
|
outPath = state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the output path of a derivation");
|
||||||
}
|
}
|
||||||
|
@ -124,7 +124,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall
|
||||||
/* And evaluate its ‘outPath’ attribute. */
|
/* And evaluate its ‘outPath’ attribute. */
|
||||||
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
|
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
|
||||||
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
|
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
|
outputs.emplace(output, state->coerceToStorePath(outPath->pos, *outPath->value, context, "while evaluating an output path of a derivation"));
|
||||||
} else
|
} else
|
||||||
outputs.emplace(output, std::nullopt);
|
outputs.emplace(output, std::nullopt);
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
|
#include "print.hh"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
|
||||||
|
@ -31,9 +32,9 @@ struct PosAdapter : AbstractPos
|
||||||
// Get rid of the null terminators added by the parser.
|
// Get rid of the null terminators added by the parser.
|
||||||
return std::string(s.source->c_str());
|
return std::string(s.source->c_str());
|
||||||
},
|
},
|
||||||
[](const Path & path) -> std::optional<std::string> {
|
[](const SourcePath & path) -> std::optional<std::string> {
|
||||||
try {
|
try {
|
||||||
return readFile(path);
|
return path.readFile();
|
||||||
} catch (Error &) {
|
} catch (Error &) {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
@ -47,7 +48,7 @@ struct PosAdapter : AbstractPos
|
||||||
[&](const Pos::none_tag &) { out << "«none»"; },
|
[&](const Pos::none_tag &) { out << "«none»"; },
|
||||||
[&](const Pos::Stdin &) { out << "«stdin»"; },
|
[&](const Pos::Stdin &) { out << "«stdin»"; },
|
||||||
[&](const Pos::String & s) { out << "«string»"; },
|
[&](const Pos::String & s) { out << "«string»"; },
|
||||||
[&](const Path & path) { out << path; }
|
[&](const SourcePath & path) { out << path; }
|
||||||
}, origin);
|
}, origin);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -60,45 +61,12 @@ Pos::operator std::shared_ptr<AbstractPos>() const
|
||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Displaying abstract syntax trees. */
|
// FIXME: remove, because *symbols* are abstract and do not have a single
|
||||||
|
// textual representation; see printIdentifier()
|
||||||
static void showString(std::ostream & str, std::string_view s)
|
|
||||||
{
|
|
||||||
str << '"';
|
|
||||||
for (auto c : s)
|
|
||||||
if (c == '"' || c == '\\' || c == '$') str << "\\" << c;
|
|
||||||
else if (c == '\n') str << "\\n";
|
|
||||||
else if (c == '\r') str << "\\r";
|
|
||||||
else if (c == '\t') str << "\\t";
|
|
||||||
else str << c;
|
|
||||||
str << '"';
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol)
|
std::ostream & operator <<(std::ostream & str, const SymbolStr & symbol)
|
||||||
{
|
{
|
||||||
std::string_view s = symbol;
|
std::string_view s = symbol;
|
||||||
|
return printIdentifier(str, s);
|
||||||
if (s.empty())
|
|
||||||
str << "\"\"";
|
|
||||||
else if (s == "if") // FIXME: handle other keywords
|
|
||||||
str << '"' << s << '"';
|
|
||||||
else {
|
|
||||||
char c = s[0];
|
|
||||||
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) {
|
|
||||||
showString(str, s);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
for (auto c : s)
|
|
||||||
if (!((c >= 'a' && c <= 'z') ||
|
|
||||||
(c >= 'A' && c <= 'Z') ||
|
|
||||||
(c >= '0' && c <= '9') ||
|
|
||||||
c == '_' || c == '\'' || c == '-')) {
|
|
||||||
showString(str, s);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
str << s;
|
|
||||||
}
|
|
||||||
return str;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expr::show(const SymbolTable & symbols, std::ostream & str) const
|
void Expr::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
|
@ -118,7 +86,7 @@ void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
|
|
||||||
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
|
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
showString(str, s);
|
printLiteralString(str, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
|
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
|
|
|
@ -34,7 +34,7 @@ struct Pos
|
||||||
struct Stdin { ref<std::string> source; };
|
struct Stdin { ref<std::string> source; };
|
||||||
struct String { ref<std::string> source; };
|
struct String { ref<std::string> source; };
|
||||||
|
|
||||||
typedef std::variant<none_tag, Stdin, String, Path> Origin;
|
typedef std::variant<none_tag, Stdin, String, SourcePath> Origin;
|
||||||
|
|
||||||
Origin origin;
|
Origin origin;
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ namespace nix {
|
||||||
EvalState & state;
|
EvalState & state;
|
||||||
SymbolTable & symbols;
|
SymbolTable & symbols;
|
||||||
Expr * result;
|
Expr * result;
|
||||||
Path basePath;
|
SourcePath basePath;
|
||||||
PosTable::Origin origin;
|
PosTable::Origin origin;
|
||||||
std::optional<ErrorInfo> error;
|
std::optional<ErrorInfo> error;
|
||||||
};
|
};
|
||||||
|
@ -509,7 +509,7 @@ string_parts_interpolated
|
||||||
|
|
||||||
path_start
|
path_start
|
||||||
: PATH {
|
: PATH {
|
||||||
Path path(absPath({$1.p, $1.l}, data->basePath));
|
Path path(absPath({$1.p, $1.l}, data->basePath.path.abs()));
|
||||||
/* add back in the trailing '/' to the first segment */
|
/* add back in the trailing '/' to the first segment */
|
||||||
if ($1.p[$1.l-1] == '/' && $1.l > 1)
|
if ($1.p[$1.l-1] == '/' && $1.l > 1)
|
||||||
path += "/";
|
path += "/";
|
||||||
|
@ -651,7 +651,7 @@ Expr * EvalState::parse(
|
||||||
char * text,
|
char * text,
|
||||||
size_t length,
|
size_t length,
|
||||||
Pos::Origin origin,
|
Pos::Origin origin,
|
||||||
Path basePath,
|
const SourcePath & basePath,
|
||||||
std::shared_ptr<StaticEnv> & staticEnv)
|
std::shared_ptr<StaticEnv> & staticEnv)
|
||||||
{
|
{
|
||||||
yyscan_t scanner;
|
yyscan_t scanner;
|
||||||
|
@ -675,48 +675,36 @@ Expr * EvalState::parse(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Path resolveExprPath(Path path)
|
SourcePath resolveExprPath(const SourcePath & path)
|
||||||
{
|
{
|
||||||
assert(path[0] == '/');
|
|
||||||
|
|
||||||
unsigned int followCount = 0, maxFollow = 1024;
|
|
||||||
|
|
||||||
/* If `path' is a symlink, follow it. This is so that relative
|
/* If `path' is a symlink, follow it. This is so that relative
|
||||||
path references work. */
|
path references work. */
|
||||||
struct stat st;
|
auto path2 = path.resolveSymlinks();
|
||||||
while (true) {
|
|
||||||
// Basic cycle/depth limit to avoid infinite loops.
|
|
||||||
if (++followCount >= maxFollow)
|
|
||||||
throw Error("too many symbolic links encountered while traversing the path '%s'", path);
|
|
||||||
st = lstat(path);
|
|
||||||
if (!S_ISLNK(st.st_mode)) break;
|
|
||||||
path = absPath(readLink(path), dirOf(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If `path' refers to a directory, append `/default.nix'. */
|
/* If `path' refers to a directory, append `/default.nix'. */
|
||||||
if (S_ISDIR(st.st_mode))
|
if (path2.lstat().type == InputAccessor::tDirectory)
|
||||||
path = canonPath(path + "/default.nix");
|
return path2 + "default.nix";
|
||||||
|
|
||||||
return path;
|
return path2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Expr * EvalState::parseExprFromFile(const Path & path)
|
Expr * EvalState::parseExprFromFile(const SourcePath & path)
|
||||||
{
|
{
|
||||||
return parseExprFromFile(path, staticBaseEnv);
|
return parseExprFromFile(path, staticBaseEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr<StaticEnv> & staticEnv)
|
Expr * EvalState::parseExprFromFile(const SourcePath & path, std::shared_ptr<StaticEnv> & staticEnv)
|
||||||
{
|
{
|
||||||
auto buffer = readFile(path);
|
auto buffer = path.readFile();
|
||||||
// readFile should have left some extra space for terminators
|
// readFile hopefully have left some extra space for terminators
|
||||||
buffer.append("\0\0", 2);
|
buffer.append("\0\0", 2);
|
||||||
return parse(buffer.data(), buffer.size(), path, dirOf(path), staticEnv);
|
return parse(buffer.data(), buffer.size(), Pos::Origin(path), path.parent(), staticEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std::shared_ptr<StaticEnv> & staticEnv)
|
Expr * EvalState::parseExprFromString(std::string s_, const SourcePath & basePath, std::shared_ptr<StaticEnv> & staticEnv)
|
||||||
{
|
{
|
||||||
auto s = make_ref<std::string>(std::move(s_));
|
auto s = make_ref<std::string>(std::move(s_));
|
||||||
s->append("\0\0", 2);
|
s->append("\0\0", 2);
|
||||||
|
@ -724,7 +712,7 @@ Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Expr * EvalState::parseExprFromString(std::string s, const Path & basePath)
|
Expr * EvalState::parseExprFromString(std::string s, const SourcePath & basePath)
|
||||||
{
|
{
|
||||||
return parseExprFromString(std::move(s), basePath, staticBaseEnv);
|
return parseExprFromString(std::move(s), basePath, staticBaseEnv);
|
||||||
}
|
}
|
||||||
|
@ -737,7 +725,7 @@ Expr * EvalState::parseStdin()
|
||||||
// drainFD should have left some extra space for terminators
|
// drainFD should have left some extra space for terminators
|
||||||
buffer.append("\0\0", 2);
|
buffer.append("\0\0", 2);
|
||||||
auto s = make_ref<std::string>(std::move(buffer));
|
auto s = make_ref<std::string>(std::move(buffer));
|
||||||
return parse(s->data(), s->size(), Pos::Stdin{.source = s}, absPath("."), staticBaseEnv);
|
return parse(s->data(), s->size(), Pos::Stdin{.source = s}, rootPath(CanonPath::fromCwd()), staticBaseEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -757,13 +745,13 @@ void EvalState::addToSearchPath(const std::string & s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Path EvalState::findFile(const std::string_view path)
|
SourcePath EvalState::findFile(const std::string_view path)
|
||||||
{
|
{
|
||||||
return findFile(searchPath, path);
|
return findFile(searchPath, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos)
|
SourcePath EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos)
|
||||||
{
|
{
|
||||||
for (auto & i : searchPath) {
|
for (auto & i : searchPath) {
|
||||||
std::string suffix;
|
std::string suffix;
|
||||||
|
@ -779,11 +767,11 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
|
||||||
auto r = resolveSearchPathElem(i);
|
auto r = resolveSearchPathElem(i);
|
||||||
if (!r.first) continue;
|
if (!r.first) continue;
|
||||||
Path res = r.second + suffix;
|
Path res = r.second + suffix;
|
||||||
if (pathExists(res)) return canonPath(res);
|
if (pathExists(res)) return CanonPath(canonPath(res));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPrefix(path, "nix/"))
|
if (hasPrefix(path, "nix/"))
|
||||||
return concatStrings(corepkgsPrefix, path.substr(4));
|
return CanonPath(concatStrings(corepkgsPrefix, path.substr(4)));
|
||||||
|
|
||||||
debugThrow(ThrownError({
|
debugThrow(ThrownError({
|
||||||
.msg = hintfmt(evalSettings.pureEval
|
.msg = hintfmt(evalSettings.pureEval
|
||||||
|
|
10
src/libexpr/paths.cc
Normal file
10
src/libexpr/paths.cc
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
#include "eval.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
SourcePath EvalState::rootPath(CanonPath path)
|
||||||
|
{
|
||||||
|
return std::move(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -38,17 +38,16 @@ namespace nix {
|
||||||
InvalidPathError::InvalidPathError(const Path & path) :
|
InvalidPathError::InvalidPathError(const Path & path) :
|
||||||
EvalError("path '%s' is not valid", path), path(path) {}
|
EvalError("path '%s' is not valid", path), path(path) {}
|
||||||
|
|
||||||
StringMap EvalState::realiseContext(const PathSet & context)
|
StringMap EvalState::realiseContext(const NixStringContext & context)
|
||||||
{
|
{
|
||||||
std::vector<DerivedPath::Built> drvs;
|
std::vector<DerivedPath::Built> drvs;
|
||||||
StringMap res;
|
StringMap res;
|
||||||
|
|
||||||
for (auto & c_ : context) {
|
for (auto & c : context) {
|
||||||
auto ensureValid = [&](const StorePath & p) {
|
auto ensureValid = [&](const StorePath & p) {
|
||||||
if (!store->isValidPath(p))
|
if (!store->isValidPath(p))
|
||||||
debugThrowLastTrace(InvalidPathError(store->printStorePath(p)));
|
debugThrowLastTrace(InvalidPathError(store->printStorePath(p)));
|
||||||
};
|
};
|
||||||
auto c = NixStringContextElem::parse(*store, c_);
|
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const NixStringContextElem::Built & b) {
|
[&](const NixStringContextElem::Built & b) {
|
||||||
drvs.push_back(DerivedPath::Built {
|
drvs.push_back(DerivedPath::Built {
|
||||||
|
@ -110,16 +109,16 @@ struct RealisePathFlags {
|
||||||
bool checkForPureEval = true;
|
bool checkForPureEval = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
static Path realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {})
|
static SourcePath realisePath(EvalState & state, const PosIdx pos, Value & v, const RealisePathFlags flags = {})
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
|
|
||||||
auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path");
|
auto path = state.coerceToPath(noPos, v, context, "while realising the context of a path");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
StringMap rewrites = state.realiseContext(context);
|
StringMap rewrites = state.realiseContext(context);
|
||||||
|
|
||||||
auto realPath = state.toRealPath(rewriteStrings(path, rewrites), context);
|
auto realPath = state.rootPath(CanonPath(state.toRealPath(rewriteStrings(path.path.abs(), rewrites), context)));
|
||||||
|
|
||||||
return flags.checkForPureEval
|
return flags.checkForPureEval
|
||||||
? state.checkSourcePath(realPath)
|
? state.checkSourcePath(realPath)
|
||||||
|
@ -158,7 +157,12 @@ static void mkOutputString(
|
||||||
/* FIXME: we need to depend on the basic derivation, not
|
/* FIXME: we need to depend on the basic derivation, not
|
||||||
derivation */
|
derivation */
|
||||||
: downstreamPlaceholder(*state.store, drvPath, o.first),
|
: downstreamPlaceholder(*state.store, drvPath, o.first),
|
||||||
{"!" + o.first + "!" + state.store->printStorePath(drvPath)});
|
NixStringContext {
|
||||||
|
NixStringContextElem::Built {
|
||||||
|
.drvPath = drvPath,
|
||||||
|
.output = o.first,
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Load and evaluate an expression from path specified by the
|
/* Load and evaluate an expression from path specified by the
|
||||||
|
@ -166,28 +170,30 @@ static void mkOutputString(
|
||||||
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
|
static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * vScope, Value & v)
|
||||||
{
|
{
|
||||||
auto path = realisePath(state, pos, vPath);
|
auto path = realisePath(state, pos, vPath);
|
||||||
|
auto path2 = path.path.abs();
|
||||||
|
|
||||||
// FIXME
|
// FIXME
|
||||||
auto isValidDerivationInStore = [&]() -> std::optional<StorePath> {
|
auto isValidDerivationInStore = [&]() -> std::optional<StorePath> {
|
||||||
if (!state.store->isStorePath(path))
|
if (!state.store->isStorePath(path2))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
auto storePath = state.store->parseStorePath(path);
|
auto storePath = state.store->parseStorePath(path2);
|
||||||
if (!(state.store->isValidPath(storePath) && isDerivation(path)))
|
if (!(state.store->isValidPath(storePath) && isDerivation(path2)))
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
return storePath;
|
return storePath;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (auto optStorePath = isValidDerivationInStore()) {
|
if (auto storePath = isValidDerivationInStore()) {
|
||||||
auto storePath = *optStorePath;
|
Derivation drv = state.store->readDerivation(*storePath);
|
||||||
Derivation drv = state.store->readDerivation(storePath);
|
|
||||||
auto attrs = state.buildBindings(3 + drv.outputs.size());
|
auto attrs = state.buildBindings(3 + drv.outputs.size());
|
||||||
attrs.alloc(state.sDrvPath).mkString(path, {"=" + path});
|
attrs.alloc(state.sDrvPath).mkString(path2, {
|
||||||
|
NixStringContextElem::DrvDeep { .drvPath = *storePath },
|
||||||
|
});
|
||||||
attrs.alloc(state.sName).mkString(drv.env["name"]);
|
attrs.alloc(state.sName).mkString(drv.env["name"]);
|
||||||
auto & outputsVal = attrs.alloc(state.sOutputs);
|
auto & outputsVal = attrs.alloc(state.sOutputs);
|
||||||
state.mkList(outputsVal, drv.outputs.size());
|
state.mkList(outputsVal, drv.outputs.size());
|
||||||
|
|
||||||
for (const auto & [i, o] : enumerate(drv.outputs)) {
|
for (const auto & [i, o] : enumerate(drv.outputs)) {
|
||||||
mkOutputString(state, attrs, storePath, drv, o);
|
mkOutputString(state, attrs, *storePath, drv, o);
|
||||||
(outputsVal.listElems()[i] = state.allocValue())->mkString(o.first);
|
(outputsVal.listElems()[i] = state.allocValue())->mkString(o.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +204,7 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
|
||||||
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
|
state.vImportedDrvToDerivation = allocRootValue(state.allocValue());
|
||||||
state.eval(state.parseExprFromString(
|
state.eval(state.parseExprFromString(
|
||||||
#include "imported-drv-to-derivation.nix.gen.hh"
|
#include "imported-drv-to-derivation.nix.gen.hh"
|
||||||
, "/"), **state.vImportedDrvToDerivation);
|
, CanonPath::root), **state.vImportedDrvToDerivation);
|
||||||
}
|
}
|
||||||
|
|
||||||
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
|
state.forceFunction(**state.vImportedDrvToDerivation, pos, "while evaluating imported-drv-to-derivation.nix.gen.hh");
|
||||||
|
@ -206,10 +212,10 @@ static void import(EvalState & state, const PosIdx pos, Value & vPath, Value * v
|
||||||
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
|
state.forceAttrs(v, pos, "while calling imported-drv-to-derivation.nix.gen.hh");
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (path == corepkgsPrefix + "fetchurl.nix") {
|
else if (path2 == corepkgsPrefix + "fetchurl.nix") {
|
||||||
state.eval(state.parseExprFromString(
|
state.eval(state.parseExprFromString(
|
||||||
#include "fetchurl.nix.gen.hh"
|
#include "fetchurl.nix.gen.hh"
|
||||||
, "/"), v);
|
, CanonPath::root), v);
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
@ -330,7 +336,7 @@ void prim_importNative(EvalState & state, const PosIdx pos, Value * * args, Valu
|
||||||
|
|
||||||
std::string sym(state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.importNative"));
|
std::string sym(state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.importNative"));
|
||||||
|
|
||||||
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
void *handle = dlopen(path.path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||||
if (!handle)
|
if (!handle)
|
||||||
state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
|
state.debugThrowLastTrace(EvalError("could not open '%1%': %2%", path, dlerror()));
|
||||||
|
|
||||||
|
@ -358,7 +364,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
auto count = args[0]->listSize();
|
auto count = args[0]->listSize();
|
||||||
if (count == 0)
|
if (count == 0)
|
||||||
state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
|
state.error("at least one argument to 'exec' required").atPos(pos).debugThrow<EvalError>();
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto program = state.coerceToString(pos, *elems[0], context,
|
auto program = state.coerceToString(pos, *elems[0], context,
|
||||||
"while evaluating the first element of the argument passed to builtins.exec",
|
"while evaluating the first element of the argument passed to builtins.exec",
|
||||||
false, false).toOwned();
|
false, false).toOwned();
|
||||||
|
@ -378,7 +384,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
auto output = runProgram(program, true, commandArgs);
|
auto output = runProgram(program, true, commandArgs);
|
||||||
Expr * parsed;
|
Expr * parsed;
|
||||||
try {
|
try {
|
||||||
parsed = state.parseExprFromString(std::move(output), "/");
|
parsed = state.parseExprFromString(std::move(output), state.rootPath(CanonPath::root));
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program);
|
e.addTrace(state.positions[pos], "while parsing the output from '%1%'", program);
|
||||||
throw;
|
throw;
|
||||||
|
@ -588,7 +594,7 @@ struct CompareValues
|
||||||
case nString:
|
case nString:
|
||||||
return strcmp(v1->string.s, v2->string.s) < 0;
|
return strcmp(v1->string.s, v2->string.s) < 0;
|
||||||
case nPath:
|
case nPath:
|
||||||
return strcmp(v1->path, v2->path) < 0;
|
return strcmp(v1->_path, v2->_path) < 0;
|
||||||
case nList:
|
case nList:
|
||||||
// Lexicographic comparison
|
// Lexicographic comparison
|
||||||
for (size_t i = 0;; i++) {
|
for (size_t i = 0;; i++) {
|
||||||
|
@ -700,12 +706,14 @@ static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
|
||||||
.arity = 1,
|
.arity = 1,
|
||||||
.doc = R"(
|
.doc = R"(
|
||||||
Take an *attrset* with values named `startSet` and `operator` in order to
|
Take an *attrset* with values named `startSet` and `operator` in order to
|
||||||
return a *list of attrsets* by starting with the `startSet`, recursively
|
return a *list of attrsets* by starting with the `startSet` and recursively
|
||||||
applying the `operator` function to each element. The *attrsets* in the
|
applying the `operator` function to each `item`. The *attrsets* in the
|
||||||
`startSet` and produced by the `operator` must each contain value named
|
`startSet` and the *attrsets* produced by `operator` must contain a value
|
||||||
`key` which are comparable to each other. The result is produced by
|
named `key` which is comparable. The result is produced by calling `operator`
|
||||||
repeatedly calling the operator for each element encountered with a
|
for each `item` with a value for `key` that has not been called yet including
|
||||||
unique key, terminating when no new elements are produced. For example,
|
newly produced `item`s. The function terminates when no new `item`s are
|
||||||
|
produced. The resulting *list of attrsets* contains only *attrsets* with a
|
||||||
|
unique key. For example,
|
||||||
|
|
||||||
```
|
```
|
||||||
builtins.genericClosure {
|
builtins.genericClosure {
|
||||||
|
@ -768,7 +776,7 @@ static RegisterPrimOp primop_abort({
|
||||||
)",
|
)",
|
||||||
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context,
|
auto s = state.coerceToString(pos, *args[0], context,
|
||||||
"while evaluating the error message passed to builtins.abort").toOwned();
|
"while evaluating the error message passed to builtins.abort").toOwned();
|
||||||
state.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s));
|
state.debugThrowLastTrace(Abort("evaluation aborted with the following error message: '%1%'", s));
|
||||||
|
@ -787,7 +795,7 @@ static RegisterPrimOp primop_throw({
|
||||||
)",
|
)",
|
||||||
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
.fun = [](EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context,
|
auto s = state.coerceToString(pos, *args[0], context,
|
||||||
"while evaluating the error message passed to builtin.throw").toOwned();
|
"while evaluating the error message passed to builtin.throw").toOwned();
|
||||||
state.debugThrowLastTrace(ThrownError(s));
|
state.debugThrowLastTrace(ThrownError(s));
|
||||||
|
@ -800,7 +808,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * *
|
||||||
state.forceValue(*args[1], pos);
|
state.forceValue(*args[1], pos);
|
||||||
v = *args[1];
|
v = *args[1];
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto message = state.coerceToString(pos, *args[0], context,
|
auto message = state.coerceToString(pos, *args[0], context,
|
||||||
"while evaluating the error message passed to builtins.addErrorContext",
|
"while evaluating the error message passed to builtins.addErrorContext",
|
||||||
false, false).toOwned();
|
false, false).toOwned();
|
||||||
|
@ -1086,7 +1094,7 @@ drvName, Bindings * attrs, Value & v)
|
||||||
Derivation drv;
|
Derivation drv;
|
||||||
drv.name = drvName;
|
drv.name = drvName;
|
||||||
|
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
|
|
||||||
bool contentAddressed = false;
|
bool contentAddressed = false;
|
||||||
bool isImpure = false;
|
bool isImpure = false;
|
||||||
|
@ -1232,8 +1240,7 @@ drvName, Bindings * attrs, Value & v)
|
||||||
/* Everything in the context of the strings in the derivation
|
/* Everything in the context of the strings in the derivation
|
||||||
attributes should be added as dependencies of the resulting
|
attributes should be added as dependencies of the resulting
|
||||||
derivation. */
|
derivation. */
|
||||||
for (auto & c_ : context) {
|
for (auto & c : context) {
|
||||||
auto c = NixStringContextElem::parse(*state.store, c_);
|
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
/* Since this allows the builder to gain access to every
|
/* Since this allows the builder to gain access to every
|
||||||
path in the dependency graph of the derivation (including
|
path in the dependency graph of the derivation (including
|
||||||
|
@ -1293,7 +1300,13 @@ drvName, Bindings * attrs, Value & v)
|
||||||
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
|
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
|
||||||
|
|
||||||
auto method = ingestionMethod.value_or(FileIngestionMethod::Flat);
|
auto method = ingestionMethod.value_or(FileIngestionMethod::Flat);
|
||||||
auto outPath = state.store->makeFixedOutputPath(method, h, drvName);
|
auto outPath = state.store->makeFixedOutputPath(drvName, FixedOutputInfo {
|
||||||
|
.hash = {
|
||||||
|
.method = method,
|
||||||
|
.hash = h,
|
||||||
|
},
|
||||||
|
.references = {},
|
||||||
|
});
|
||||||
drv.env["out"] = state.store->printStorePath(outPath);
|
drv.env["out"] = state.store->printStorePath(outPath);
|
||||||
drv.outputs.insert_or_assign("out",
|
drv.outputs.insert_or_assign("out",
|
||||||
DerivationOutput::CAFixed {
|
DerivationOutput::CAFixed {
|
||||||
|
@ -1386,7 +1399,9 @@ drvName, Bindings * attrs, Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = state.buildBindings(1 + drv.outputs.size());
|
auto result = state.buildBindings(1 + drv.outputs.size());
|
||||||
result.alloc(state.sDrvPath).mkString(drvPathS, {"=" + drvPathS});
|
result.alloc(state.sDrvPath).mkString(drvPathS, {
|
||||||
|
NixStringContextElem::DrvDeep { .drvPath = drvPath },
|
||||||
|
});
|
||||||
for (auto & i : drv.outputs)
|
for (auto & i : drv.outputs)
|
||||||
mkOutputString(state, result, drvPath, drv, i);
|
mkOutputString(state, result, drvPath, drv, i);
|
||||||
|
|
||||||
|
@ -1431,9 +1446,9 @@ static RegisterPrimOp primop_placeholder({
|
||||||
/* Convert the argument to a path. !!! obsolete? */
|
/* Convert the argument to a path. !!! obsolete? */
|
||||||
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_toPath(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
Path path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath");
|
auto path = state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.toPath");
|
||||||
v.mkString(canonPath(path), context);
|
v.mkString(path.path.abs(), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_toPath({
|
static RegisterPrimOp primop_toPath({
|
||||||
|
@ -1462,22 +1477,23 @@ static void prim_storePath(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
.errPos = state.positions[pos]
|
.errPos = state.positions[pos]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
Path path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath"));
|
auto path = state.checkSourcePath(state.coerceToPath(pos, *args[0], context, "while evaluating the first argument passed to builtins.storePath")).path;
|
||||||
/* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
|
/* Resolve symlinks in ‘path’, unless ‘path’ itself is a symlink
|
||||||
directly in the store. The latter condition is necessary so
|
directly in the store. The latter condition is necessary so
|
||||||
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.abs()))
|
||||||
if (!state.store->isInStore(path))
|
path = CanonPath(canonPath(path.abs(), true));
|
||||||
|
if (!state.store->isInStore(path.abs()))
|
||||||
state.debugThrowLastTrace(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.abs()).first;
|
||||||
if (!settings.readOnlyMode)
|
if (!settings.readOnlyMode)
|
||||||
state.store->ensurePath(path2);
|
state.store->ensurePath(path2);
|
||||||
context.insert(state.store->printStorePath(path2));
|
context.insert(NixStringContextElem::Opaque { .path = path2 });
|
||||||
v.mkString(path, context);
|
v.mkString(path.abs(), context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_storePath({
|
static RegisterPrimOp primop_storePath({
|
||||||
|
@ -1508,7 +1524,7 @@ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false });
|
auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
v.mkBool(pathExists(state.checkSourcePath(path)));
|
v.mkBool(state.checkSourcePath(path).pathExists());
|
||||||
} catch (SysError & e) {
|
} catch (SysError & e) {
|
||||||
/* Don't give away info from errors while canonicalising
|
/* Don't give away info from errors while canonicalising
|
||||||
‘path’ in restricted mode. */
|
‘path’ in restricted mode. */
|
||||||
|
@ -1532,7 +1548,7 @@ static RegisterPrimOp primop_pathExists({
|
||||||
following the last slash. */
|
following the last slash. */
|
||||||
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_baseNameOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context,
|
v.mkString(baseNameOf(*state.coerceToString(pos, *args[0], context,
|
||||||
"while evaluating the first argument passed to builtins.baseNameOf",
|
"while evaluating the first argument passed to builtins.baseNameOf",
|
||||||
false, false)), context);
|
false, false)), context);
|
||||||
|
@ -1554,12 +1570,18 @@ static RegisterPrimOp primop_baseNameOf({
|
||||||
of the argument. */
|
of the argument. */
|
||||||
static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_dirOf(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
state.forceValue(*args[0], pos);
|
||||||
auto path = state.coerceToString(pos, *args[0], context,
|
if (args[0]->type() == nPath) {
|
||||||
"while evaluating the first argument passed to builtins.dirOf",
|
auto path = args[0]->path();
|
||||||
|
v.mkPath(path.path.isRoot() ? path : path.parent());
|
||||||
|
} else {
|
||||||
|
NixStringContext context;
|
||||||
|
auto path = state.coerceToString(pos, *args[0], context,
|
||||||
|
"while evaluating the first argument passed to 'builtins.dirOf'",
|
||||||
false, false);
|
false, false);
|
||||||
auto dir = dirOf(*path);
|
auto dir = dirOf(*path);
|
||||||
if (args[0]->type() == nPath) v.mkPath(dir); else v.mkString(dir, context);
|
v.mkString(dir, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_dirOf({
|
static RegisterPrimOp primop_dirOf({
|
||||||
|
@ -1577,13 +1599,13 @@ static RegisterPrimOp primop_dirOf({
|
||||||
static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
auto path = realisePath(state, pos, *args[0]);
|
auto path = realisePath(state, pos, *args[0]);
|
||||||
auto s = readFile(path);
|
auto s = path.readFile();
|
||||||
if (s.find((char) 0) != std::string::npos)
|
if (s.find((char) 0) != std::string::npos)
|
||||||
state.debugThrowLastTrace(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.path.abs())) {
|
||||||
try {
|
try {
|
||||||
refs = state.store->queryPathInfo(state.store->toStorePath(path).first)->references;
|
refs = state.store->queryPathInfo(state.store->toStorePath(path.path.abs()).first)->references;
|
||||||
} catch (Error &) { // FIXME: should be InvalidPathError
|
} catch (Error &) { // FIXME: should be InvalidPathError
|
||||||
}
|
}
|
||||||
// Re-scan references to filter down to just the ones that actually occur in the file.
|
// Re-scan references to filter down to just the ones that actually occur in the file.
|
||||||
|
@ -1591,7 +1613,12 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
refsSink << s;
|
refsSink << s;
|
||||||
refs = refsSink.getResultPaths();
|
refs = refsSink.getResultPaths();
|
||||||
}
|
}
|
||||||
auto context = state.store->printStorePathSet(refs);
|
NixStringContext context;
|
||||||
|
for (auto && p : std::move(refs)) {
|
||||||
|
context.insert(NixStringContextElem::Opaque {
|
||||||
|
.path = std::move((StorePath &&)p),
|
||||||
|
});
|
||||||
|
}
|
||||||
v.mkString(s, context);
|
v.mkString(s, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1622,7 +1649,7 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
|
|
||||||
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
|
i = getAttr(state, state.sPath, v2->attrs, "in an element of the __nixPath");
|
||||||
|
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto path = state.coerceToString(pos, *i->value, context,
|
auto path = state.coerceToString(pos, *i->value, context,
|
||||||
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile",
|
"while evaluating the `path` attribute of an element of the list passed to builtins.findFile",
|
||||||
false, false).toOwned();
|
false, false).toOwned();
|
||||||
|
@ -1664,7 +1691,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value * * args, V
|
||||||
|
|
||||||
auto path = realisePath(state, pos, *args[1]);
|
auto path = realisePath(state, pos, *args[1]);
|
||||||
|
|
||||||
v.mkString(hashFile(*ht, path).to_string(Base16, false));
|
v.mkString(hashString(*ht, path.readFile()).to_string(Base16, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_hashFile({
|
static RegisterPrimOp primop_hashFile({
|
||||||
|
@ -1678,26 +1705,20 @@ static RegisterPrimOp primop_hashFile({
|
||||||
.fun = prim_hashFile,
|
.fun = prim_hashFile,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
static std::string_view fileTypeToString(InputAccessor::Type type)
|
||||||
/* Stringize a directory entry enum. Used by `readFileType' and `readDir'. */
|
|
||||||
static const char * dirEntTypeToString(unsigned char dtType)
|
|
||||||
{
|
{
|
||||||
/* Enum DT_(DIR|LNK|REG|UNKNOWN) */
|
return
|
||||||
switch(dtType) {
|
type == InputAccessor::Type::tRegular ? "regular" :
|
||||||
case DT_REG: return "regular"; break;
|
type == InputAccessor::Type::tDirectory ? "directory" :
|
||||||
case DT_DIR: return "directory"; break;
|
type == InputAccessor::Type::tSymlink ? "symlink" :
|
||||||
case DT_LNK: return "symlink"; break;
|
"unknown";
|
||||||
default: return "unknown"; break;
|
|
||||||
}
|
|
||||||
return "unknown"; /* Unreachable */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_readFileType(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
auto path = realisePath(state, pos, *args[0]);
|
auto path = realisePath(state, pos, *args[0]);
|
||||||
/* Retrieve the directory entry type and stringize it. */
|
/* Retrieve the directory entry type and stringize it. */
|
||||||
v.mkString(dirEntTypeToString(getFileType(path)));
|
v.mkString(fileTypeToString(path.lstat().type));
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_readFileType({
|
static RegisterPrimOp primop_readFileType({
|
||||||
|
@ -1718,8 +1739,7 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
|
||||||
// Retrieve directory entries for all nodes in a directory.
|
// Retrieve directory entries for all nodes in a directory.
|
||||||
// This is similar to `getFileType` but is optimized to reduce system calls
|
// This is similar to `getFileType` but is optimized to reduce system calls
|
||||||
// on many systems.
|
// on many systems.
|
||||||
DirEntries entries = readDirectory(path);
|
auto entries = path.readDirectory();
|
||||||
|
|
||||||
auto attrs = state.buildBindings(entries.size());
|
auto attrs = state.buildBindings(entries.size());
|
||||||
|
|
||||||
// If we hit unknown directory entry types we may need to fallback to
|
// If we hit unknown directory entry types we may need to fallback to
|
||||||
|
@ -1728,22 +1748,21 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value * * args, Va
|
||||||
// `builtins.readFileType` application.
|
// `builtins.readFileType` application.
|
||||||
Value * readFileType = nullptr;
|
Value * readFileType = nullptr;
|
||||||
|
|
||||||
for (auto & ent : entries) {
|
for (auto & [name, type] : entries) {
|
||||||
auto & attr = attrs.alloc(ent.name);
|
auto & attr = attrs.alloc(name);
|
||||||
if (ent.type == DT_UNKNOWN) {
|
if (!type) {
|
||||||
// Some filesystems or operating systems may not be able to return
|
// Some filesystems or operating systems may not be able to return
|
||||||
// detailed node info quickly in this case we produce a thunk to
|
// detailed node info quickly in this case we produce a thunk to
|
||||||
// query the file type lazily.
|
// query the file type lazily.
|
||||||
auto epath = state.allocValue();
|
auto epath = state.allocValue();
|
||||||
Path path2 = path + "/" + ent.name;
|
epath->mkPath(path + name);
|
||||||
epath->mkString(path2);
|
|
||||||
if (!readFileType)
|
if (!readFileType)
|
||||||
readFileType = &state.getBuiltin("readFileType");
|
readFileType = &state.getBuiltin("readFileType");
|
||||||
attr.mkApp(readFileType, epath);
|
attr.mkApp(readFileType, epath);
|
||||||
} else {
|
} else {
|
||||||
// This branch of the conditional is much more likely.
|
// This branch of the conditional is much more likely.
|
||||||
// Here we just stringize the directory entry type.
|
// Here we just stringize the directory entry type.
|
||||||
attr.mkString(dirEntTypeToString(ent.type));
|
attr.mkString(fileTypeToString(*type));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1781,7 +1800,7 @@ static RegisterPrimOp primop_readDir({
|
||||||
static void prim_toXML(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_toXML(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
printValueAsXML(state, true, false, *args[0], out, context, pos);
|
printValueAsXML(state, true, false, *args[0], out, context, pos);
|
||||||
v.mkString(out.str(), context);
|
v.mkString(out.str(), context);
|
||||||
}
|
}
|
||||||
|
@ -1889,7 +1908,7 @@ static RegisterPrimOp primop_toXML({
|
||||||
static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_toJSON(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
printValueAsJSON(state, true, *args[0], pos, out, context);
|
printValueAsJSON(state, true, *args[0], pos, out, context);
|
||||||
v.mkString(out.str(), context);
|
v.mkString(out.str(), context);
|
||||||
}
|
}
|
||||||
|
@ -1939,22 +1958,23 @@ static RegisterPrimOp primop_fromJSON({
|
||||||
as an input by derivations. */
|
as an input by derivations. */
|
||||||
static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_toFile(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
std::string name(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.toFile"));
|
std::string name(state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.toFile"));
|
||||||
std::string contents(state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.toFile"));
|
std::string contents(state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.toFile"));
|
||||||
|
|
||||||
StorePathSet refs;
|
StorePathSet refs;
|
||||||
|
|
||||||
for (auto path : context) {
|
for (auto c : context) {
|
||||||
if (path.at(0) != '/')
|
if (auto p = std::get_if<NixStringContextElem::Opaque>(&c))
|
||||||
|
refs.insert(p->path);
|
||||||
|
else
|
||||||
state.debugThrowLastTrace(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, c.to_string()),
|
||||||
.errPos = state.positions[pos]
|
.errPos = state.positions[pos]
|
||||||
}));
|
}));
|
||||||
refs.insert(state.store->parseStorePath(path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto storePath = settings.readOnlyMode
|
auto storePath = settings.readOnlyMode
|
||||||
|
@ -2049,13 +2069,13 @@ static RegisterPrimOp primop_toFile({
|
||||||
static void addPath(
|
static void addPath(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const PosIdx pos,
|
const PosIdx pos,
|
||||||
const std::string & name,
|
std::string_view name,
|
||||||
Path path,
|
Path path,
|
||||||
Value * filterFun,
|
Value * filterFun,
|
||||||
FileIngestionMethod method,
|
FileIngestionMethod method,
|
||||||
const std::optional<Hash> expectedHash,
|
const std::optional<Hash> expectedHash,
|
||||||
Value & v,
|
Value & v,
|
||||||
const PathSet & context)
|
const NixStringContext & context)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
// FIXME: handle CA derivation outputs (where path needs to
|
// FIXME: handle CA derivation outputs (where path needs to
|
||||||
|
@ -2077,7 +2097,7 @@ static void addPath(
|
||||||
|
|
||||||
path = evalSettings.pureEval && expectedHash
|
path = evalSettings.pureEval && expectedHash
|
||||||
? path
|
? path
|
||||||
: state.checkSourcePath(path);
|
: state.checkSourcePath(CanonPath(path)).path.abs();
|
||||||
|
|
||||||
PathFilter filter = filterFun ? ([&](const Path & path) {
|
PathFilter filter = filterFun ? ([&](const Path & path) {
|
||||||
auto st = lstat(path);
|
auto st = lstat(path);
|
||||||
|
@ -2103,7 +2123,13 @@ static void addPath(
|
||||||
|
|
||||||
std::optional<StorePath> expectedStorePath;
|
std::optional<StorePath> expectedStorePath;
|
||||||
if (expectedHash)
|
if (expectedHash)
|
||||||
expectedStorePath = state.store->makeFixedOutputPath(method, *expectedHash, name);
|
expectedStorePath = state.store->makeFixedOutputPath(name, FixedOutputInfo {
|
||||||
|
.hash = {
|
||||||
|
.method = method,
|
||||||
|
.hash = *expectedHash,
|
||||||
|
},
|
||||||
|
.references = {},
|
||||||
|
});
|
||||||
|
|
||||||
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
|
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
|
||||||
StorePath dstPath = settings.readOnlyMode
|
StorePath dstPath = settings.readOnlyMode
|
||||||
|
@ -2123,10 +2149,11 @@ static void addPath(
|
||||||
|
|
||||||
static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_filterSource(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
Path path = state.coerceToPath(pos, *args[1], context, "while evaluating the second argument (the path to filter) passed to builtins.filterSource");
|
auto path = state.coerceToPath(pos, *args[1], context,
|
||||||
|
"while evaluating the second argument (the path to filter) passed to builtins.filterSource");
|
||||||
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
|
state.forceFunction(*args[0], pos, "while evaluating the first argument passed to builtins.filterSource");
|
||||||
addPath(state, pos, std::string(baseNameOf(path)), path, args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
|
addPath(state, pos, path.baseName(), path.path.abs(), args[0], FileIngestionMethod::Recursive, std::nullopt, v, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_filterSource({
|
static RegisterPrimOp primop_filterSource({
|
||||||
|
@ -2186,18 +2213,19 @@ static RegisterPrimOp primop_filterSource({
|
||||||
|
|
||||||
static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to builtins.path");
|
std::optional<SourcePath> path;
|
||||||
Path path;
|
|
||||||
std::string name;
|
std::string name;
|
||||||
Value * filterFun = nullptr;
|
Value * filterFun = nullptr;
|
||||||
auto method = FileIngestionMethod::Recursive;
|
auto method = FileIngestionMethod::Recursive;
|
||||||
std::optional<Hash> expectedHash;
|
std::optional<Hash> expectedHash;
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
|
|
||||||
|
state.forceAttrs(*args[0], pos, "while evaluating the argument passed to 'builtins.path'");
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
for (auto & attr : *args[0]->attrs) {
|
||||||
auto n = state.symbols[attr.name];
|
auto n = state.symbols[attr.name];
|
||||||
if (n == "path")
|
if (n == "path")
|
||||||
path = state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the `path` attribute passed to builtins.path");
|
path.emplace(state.coerceToPath(attr.pos, *attr.value, context, "while evaluating the 'path' attribute passed to 'builtins.path'"));
|
||||||
else if (attr.name == state.sName)
|
else if (attr.name == state.sName)
|
||||||
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.path");
|
name = state.forceStringNoCtx(*attr.value, attr.pos, "while evaluating the `name` attribute passed to builtins.path");
|
||||||
else if (n == "filter")
|
else if (n == "filter")
|
||||||
|
@ -2212,15 +2240,15 @@ static void prim_path(EvalState & state, const PosIdx pos, Value * * args, Value
|
||||||
.errPos = state.positions[attr.pos]
|
.errPos = state.positions[attr.pos]
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
if (path.empty())
|
if (!path)
|
||||||
state.debugThrowLastTrace(EvalError({
|
state.debugThrowLastTrace(EvalError({
|
||||||
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
|
.msg = hintfmt("missing required 'path' attribute in the first argument to builtins.path"),
|
||||||
.errPos = state.positions[pos]
|
.errPos = state.positions[pos]
|
||||||
}));
|
}));
|
||||||
if (name.empty())
|
if (name.empty())
|
||||||
name = baseNameOf(path);
|
name = path->baseName();
|
||||||
|
|
||||||
addPath(state, pos, name, path, filterFun, method, expectedHash, v, context);
|
addPath(state, pos, name, path->path.abs(), filterFun, method, expectedHash, v, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_path({
|
static RegisterPrimOp primop_path({
|
||||||
|
@ -3526,7 +3554,7 @@ static RegisterPrimOp primop_lessThan({
|
||||||
`"/nix/store/whatever..."'. */
|
`"/nix/store/whatever..."'. */
|
||||||
static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_toString(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context,
|
auto s = state.coerceToString(pos, *args[0], context,
|
||||||
"while evaluating the first argument passed to builtins.toString",
|
"while evaluating the first argument passed to builtins.toString",
|
||||||
true, false);
|
true, false);
|
||||||
|
@ -3565,7 +3593,7 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
{
|
{
|
||||||
int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
|
int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
|
||||||
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
|
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[2], context, "while evaluating the third argument (the string) passed to builtins.substring");
|
auto s = state.coerceToString(pos, *args[2], context, "while evaluating the third argument (the string) passed to builtins.substring");
|
||||||
|
|
||||||
if (start < 0)
|
if (start < 0)
|
||||||
|
@ -3599,7 +3627,7 @@ static RegisterPrimOp primop_substring({
|
||||||
|
|
||||||
static void prim_stringLength(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_stringLength(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.stringLength");
|
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.stringLength");
|
||||||
v.mkInt(s->size());
|
v.mkInt(s->size());
|
||||||
}
|
}
|
||||||
|
@ -3625,7 +3653,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
.errPos = state.positions[pos]
|
.errPos = state.positions[pos]
|
||||||
}));
|
}));
|
||||||
|
|
||||||
PathSet context; // discarded
|
NixStringContext context; // discarded
|
||||||
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
|
auto s = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
|
||||||
|
|
||||||
v.mkString(hashString(*ht, s).to_string(Base16, false));
|
v.mkString(hashString(*ht, s).to_string(Base16, false));
|
||||||
|
@ -3671,7 +3699,7 @@ void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
|
|
||||||
auto regex = state.regexCache->get(re);
|
auto regex = state.regexCache->get(re);
|
||||||
|
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
const auto str = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.match");
|
const auto str = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.match");
|
||||||
|
|
||||||
std::cmatch match;
|
std::cmatch match;
|
||||||
|
@ -3751,7 +3779,7 @@ void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
|
|
||||||
auto regex = state.regexCache->get(re);
|
auto regex = state.regexCache->get(re);
|
||||||
|
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
const auto str = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.split");
|
const auto str = state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.split");
|
||||||
|
|
||||||
auto begin = std::cregex_iterator(str.begin(), str.end(), regex);
|
auto begin = std::cregex_iterator(str.begin(), str.end(), regex);
|
||||||
|
@ -3848,7 +3876,7 @@ static RegisterPrimOp primop_split({
|
||||||
|
|
||||||
static void prim_concatStringsSep(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_concatStringsSep(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
|
|
||||||
auto sep = state.forceString(*args[0], context, pos, "while evaluating the first argument (the separator string) passed to builtins.concatStringsSep");
|
auto sep = state.forceString(*args[0], context, pos, "while evaluating the first argument (the separator string) passed to builtins.concatStringsSep");
|
||||||
state.forceList(*args[1], pos, "while evaluating the second argument (the list of strings to concat) passed to builtins.concatStringsSep");
|
state.forceList(*args[1], pos, "while evaluating the second argument (the list of strings to concat) passed to builtins.concatStringsSep");
|
||||||
|
@ -3888,15 +3916,15 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
|
||||||
for (auto elem : args[0]->listItems())
|
for (auto elem : args[0]->listItems())
|
||||||
from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace passed to builtins.replaceStrings"));
|
from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace passed to builtins.replaceStrings"));
|
||||||
|
|
||||||
std::vector<std::pair<std::string, PathSet>> to;
|
std::vector<std::pair<std::string, NixStringContext>> to;
|
||||||
to.reserve(args[1]->listSize());
|
to.reserve(args[1]->listSize());
|
||||||
for (auto elem : args[1]->listItems()) {
|
for (auto elem : args[1]->listItems()) {
|
||||||
PathSet ctx;
|
NixStringContext ctx;
|
||||||
auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
|
auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
|
||||||
to.emplace_back(s, std::move(ctx));
|
to.emplace_back(s, std::move(ctx));
|
||||||
}
|
}
|
||||||
|
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto s = state.forceString(*args[2], context, pos, "while evaluating the third argument passed to builtins.replaceStrings");
|
auto s = state.forceString(*args[2], context, pos, "while evaluating the third argument passed to builtins.replaceStrings");
|
||||||
|
|
||||||
std::string res;
|
std::string res;
|
||||||
|
@ -4138,7 +4166,6 @@ void EvalState::createBaseEnv()
|
||||||
|
|
||||||
/* Add a wrapper around the derivation primop that computes the
|
/* Add a wrapper around the derivation primop that computes the
|
||||||
`drvPath' and `outPath' attributes lazily. */
|
`drvPath' and `outPath' attributes lazily. */
|
||||||
sDerivationNix = symbols.create(derivationNixPath);
|
|
||||||
auto vDerivation = allocValue();
|
auto vDerivation = allocValue();
|
||||||
addConstant("derivation", vDerivation);
|
addConstant("derivation", vDerivation);
|
||||||
|
|
||||||
|
@ -4155,7 +4182,7 @@ void EvalState::createBaseEnv()
|
||||||
// the parser needs two NUL bytes as terminators; one of them
|
// the parser needs two NUL bytes as terminators; one of them
|
||||||
// is implied by being a C string.
|
// is implied by being a C string.
|
||||||
"\0";
|
"\0";
|
||||||
eval(parse(code, sizeof(code), derivationNixPath, "/", staticBaseEnv), *vDerivation);
|
eval(parse(code, sizeof(code), derivationInternal, {CanonPath::root}, staticBaseEnv), *vDerivation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ namespace nix {
|
||||||
|
|
||||||
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
|
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
|
||||||
v.mkString(*s);
|
v.mkString(*s);
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringCo
|
||||||
|
|
||||||
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
|
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.hasContext");
|
||||||
v.mkBool(!context.empty());
|
v.mkBool(!context.empty());
|
||||||
}
|
}
|
||||||
|
@ -33,17 +33,18 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
|
||||||
drv.inputDrvs. */
|
drv.inputDrvs. */
|
||||||
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
|
auto s = state.coerceToString(pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardOutputDependency");
|
||||||
|
|
||||||
PathSet context2;
|
NixStringContext context2;
|
||||||
for (auto && p : context) {
|
for (auto && c : context) {
|
||||||
auto c = NixStringContextElem::parse(*state.store, p);
|
|
||||||
if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c)) {
|
if (auto * ptr = std::get_if<NixStringContextElem::DrvDeep>(&c)) {
|
||||||
context2.emplace(state.store->printStorePath(ptr->drvPath));
|
context2.emplace(NixStringContextElem::Opaque {
|
||||||
|
.path = ptr->drvPath
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
/* Can reuse original item */
|
/* Can reuse original item */
|
||||||
context2.emplace(std::move(p));
|
context2.emplace(std::move(c));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,22 +80,21 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args,
|
||||||
bool allOutputs = false;
|
bool allOutputs = false;
|
||||||
Strings outputs;
|
Strings outputs;
|
||||||
};
|
};
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
|
state.forceString(*args[0], context, pos, "while evaluating the argument passed to builtins.getContext");
|
||||||
auto contextInfos = std::map<StorePath, ContextInfo>();
|
auto contextInfos = std::map<StorePath, ContextInfo>();
|
||||||
for (const auto & p : context) {
|
for (auto && i : context) {
|
||||||
NixStringContextElem ctx = NixStringContextElem::parse(*state.store, p);
|
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](NixStringContextElem::DrvDeep & d) {
|
[&](NixStringContextElem::DrvDeep && d) {
|
||||||
contextInfos[d.drvPath].allOutputs = true;
|
contextInfos[std::move(d.drvPath)].allOutputs = true;
|
||||||
},
|
},
|
||||||
[&](NixStringContextElem::Built & b) {
|
[&](NixStringContextElem::Built && b) {
|
||||||
contextInfos[b.drvPath].outputs.emplace_back(std::move(b.output));
|
contextInfos[std::move(b.drvPath)].outputs.emplace_back(std::move(b.output));
|
||||||
},
|
},
|
||||||
[&](NixStringContextElem::Opaque & o) {
|
[&](NixStringContextElem::Opaque && o) {
|
||||||
contextInfos[o.path].path = true;
|
contextInfos[std::move(o.path)].path = true;
|
||||||
},
|
},
|
||||||
}, ctx.raw());
|
}, ((NixStringContextElem &&) i).raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto attrs = state.buildBindings(contextInfos.size());
|
auto attrs = state.buildBindings(contextInfos.size());
|
||||||
|
@ -129,7 +129,7 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
|
||||||
*/
|
*/
|
||||||
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
|
auto orig = state.forceString(*args[0], context, noPos, "while evaluating the first argument passed to builtins.appendContext");
|
||||||
|
|
||||||
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
|
state.forceAttrs(*args[1], pos, "while evaluating the second argument passed to builtins.appendContext");
|
||||||
|
@ -143,13 +143,16 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
|
||||||
.msg = hintfmt("context key '%s' is not a store path", name),
|
.msg = hintfmt("context key '%s' is not a store path", name),
|
||||||
.errPos = state.positions[i.pos]
|
.errPos = state.positions[i.pos]
|
||||||
});
|
});
|
||||||
|
auto namePath = state.store->parseStorePath(name);
|
||||||
if (!settings.readOnlyMode)
|
if (!settings.readOnlyMode)
|
||||||
state.store->ensurePath(state.store->parseStorePath(name));
|
state.store->ensurePath(namePath);
|
||||||
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
|
state.forceAttrs(*i.value, i.pos, "while evaluating the value of a string context");
|
||||||
auto iter = i.value->attrs->find(sPath);
|
auto iter = i.value->attrs->find(sPath);
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
|
if (state.forceBool(*iter->value, iter->pos, "while evaluating the `path` attribute of a string context"))
|
||||||
context.emplace(name);
|
context.emplace(NixStringContextElem::Opaque {
|
||||||
|
.path = namePath,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
iter = i.value->attrs->find(sAllOutputs);
|
iter = i.value->attrs->find(sAllOutputs);
|
||||||
|
@ -161,7 +164,9 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
|
||||||
.errPos = state.positions[i.pos]
|
.errPos = state.positions[i.pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
context.insert(concatStrings("=", name));
|
context.emplace(NixStringContextElem::DrvDeep {
|
||||||
|
.drvPath = namePath,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +181,10 @@ static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * ar
|
||||||
}
|
}
|
||||||
for (auto elem : iter->value->listItems()) {
|
for (auto elem : iter->value->listItems()) {
|
||||||
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
|
auto outputName = state.forceStringNoCtx(*elem, iter->pos, "while evaluating an output name within a string context");
|
||||||
context.insert(concatStrings("!", outputName, "!", name));
|
context.emplace(NixStringContextElem::Built {
|
||||||
|
.drvPath = namePath,
|
||||||
|
.output = std::string { outputName },
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
const auto & attrName = state.symbols[attr.name];
|
const auto & attrName = state.symbols[attr.name];
|
||||||
|
|
||||||
if (attrName == "fromPath") {
|
if (attrName == "fromPath") {
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context,
|
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context,
|
||||||
"while evaluating the 'fromPath' attribute passed to builtins.fetchClosure");
|
"while evaluating the 'fromPath' attribute passed to builtins.fetchClosure");
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
state.forceValue(*attr.value, attr.pos);
|
state.forceValue(*attr.value, attr.pos);
|
||||||
toCA = true;
|
toCA = true;
|
||||||
if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
|
if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
toPath = state.coerceToStorePath(attr.pos, *attr.value, context,
|
toPath = state.coerceToStorePath(attr.pos, *attr.value, context,
|
||||||
"while evaluating the 'toPath' attribute passed to builtins.fetchClosure");
|
"while evaluating the 'toPath' attribute passed to builtins.fetchClosure");
|
||||||
}
|
}
|
||||||
|
@ -114,8 +114,7 @@ static void prim_fetchClosure(EvalState & state, const PosIdx pos, Value * * arg
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto toPathS = state.store->printStorePath(*toPath);
|
state.mkStorePathString(*toPath, v);
|
||||||
v.mkString(toPathS, {toPathS});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp primop_fetchClosure({
|
static RegisterPrimOp primop_fetchClosure({
|
||||||
|
|
|
@ -13,7 +13,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
||||||
std::optional<Hash> rev;
|
std::optional<Hash> rev;
|
||||||
std::optional<std::string> ref;
|
std::optional<std::string> ref;
|
||||||
std::string_view name = "source";
|
std::string_view name = "source";
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
|
|
||||||
state.forceValue(*args[0], pos);
|
state.forceValue(*args[0], pos);
|
||||||
|
|
||||||
|
@ -73,8 +73,7 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * a
|
||||||
auto [tree, input2] = input.fetch(state.store);
|
auto [tree, input2] = input.fetch(state.store);
|
||||||
|
|
||||||
auto attrs2 = state.buildBindings(8);
|
auto attrs2 = state.buildBindings(8);
|
||||||
auto storePath = state.store->printStorePath(tree.storePath);
|
state.mkStorePathString(tree.storePath, attrs2.alloc(state.sOutPath));
|
||||||
attrs2.alloc(state.sOutPath).mkString(storePath, {storePath});
|
|
||||||
if (input2.getRef())
|
if (input2.getRef())
|
||||||
attrs2.alloc("branch").mkString(*input2.getRef());
|
attrs2.alloc("branch").mkString(*input2.getRef());
|
||||||
// Backward compatibility: set 'rev' to
|
// Backward compatibility: set 'rev' to
|
||||||
|
|
|
@ -24,9 +24,8 @@ void emitTreeAttrs(
|
||||||
|
|
||||||
auto attrs = state.buildBindings(8);
|
auto attrs = state.buildBindings(8);
|
||||||
|
|
||||||
auto storePath = state.store->printStorePath(tree.storePath);
|
|
||||||
|
|
||||||
attrs.alloc(state.sOutPath).mkString(storePath, {storePath});
|
state.mkStorePathString(tree.storePath, attrs.alloc(state.sOutPath));
|
||||||
|
|
||||||
// FIXME: support arbitrary input attributes.
|
// FIXME: support arbitrary input attributes.
|
||||||
|
|
||||||
|
@ -107,7 +106,7 @@ static void fetchTree(
|
||||||
const FetchTreeParams & params = FetchTreeParams{}
|
const FetchTreeParams & params = FetchTreeParams{}
|
||||||
) {
|
) {
|
||||||
fetchers::Input input;
|
fetchers::Input input;
|
||||||
PathSet context;
|
NixStringContext context;
|
||||||
|
|
||||||
state.forceValue(*args[0], pos);
|
state.forceValue(*args[0], pos);
|
||||||
|
|
||||||
|
@ -243,10 +242,15 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v
|
||||||
|
|
||||||
// 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) {
|
||||||
auto expectedPath =
|
auto expectedPath = state.store->makeFixedOutputPath(
|
||||||
unpack
|
name,
|
||||||
? state.store->makeFixedOutputPath(FileIngestionMethod::Recursive, *expectedHash, name, {})
|
FixedOutputInfo {
|
||||||
: state.store->makeFixedOutputPath(FileIngestionMethod::Flat, *expectedHash, name, {});
|
.hash = {
|
||||||
|
.method = unpack ? FileIngestionMethod::Recursive : FileIngestionMethod::Flat,
|
||||||
|
.hash = *expectedHash,
|
||||||
|
},
|
||||||
|
.references = {}
|
||||||
|
});
|
||||||
|
|
||||||
if (state.store->isValidPath(expectedPath)) {
|
if (state.store->isValidPath(expectedPath)) {
|
||||||
state.allowAndSetStorePathString(expectedPath, v);
|
state.allowAndSetStorePathString(expectedPath, v);
|
||||||
|
|
78
src/libexpr/print.cc
Normal file
78
src/libexpr/print.cc
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#include "print.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
std::ostream &
|
||||||
|
printLiteralString(std::ostream & str, const std::string_view string)
|
||||||
|
{
|
||||||
|
str << "\"";
|
||||||
|
for (auto i = string.begin(); i != string.end(); ++i) {
|
||||||
|
if (*i == '\"' || *i == '\\') str << "\\" << *i;
|
||||||
|
else if (*i == '\n') str << "\\n";
|
||||||
|
else if (*i == '\r') str << "\\r";
|
||||||
|
else if (*i == '\t') str << "\\t";
|
||||||
|
else if (*i == '$' && *(i+1) == '{') str << "\\" << *i;
|
||||||
|
else str << *i;
|
||||||
|
}
|
||||||
|
str << "\"";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream &
|
||||||
|
printLiteralBool(std::ostream & str, bool boolean)
|
||||||
|
{
|
||||||
|
str << (boolean ? "true" : "false");
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream &
|
||||||
|
printIdentifier(std::ostream & str, std::string_view s) {
|
||||||
|
if (s.empty())
|
||||||
|
str << "\"\"";
|
||||||
|
else if (s == "if") // FIXME: handle other keywords
|
||||||
|
str << '"' << s << '"';
|
||||||
|
else {
|
||||||
|
char c = s[0];
|
||||||
|
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || c == '_')) {
|
||||||
|
printLiteralString(str, s);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
for (auto c : s)
|
||||||
|
if (!((c >= 'a' && c <= 'z') ||
|
||||||
|
(c >= 'A' && c <= 'Z') ||
|
||||||
|
(c >= '0' && c <= '9') ||
|
||||||
|
c == '_' || c == '\'' || c == '-')) {
|
||||||
|
printLiteralString(str, s);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
str << s;
|
||||||
|
}
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: keywords
|
||||||
|
static bool isVarName(std::string_view s)
|
||||||
|
{
|
||||||
|
if (s.size() == 0) return false;
|
||||||
|
char c = s[0];
|
||||||
|
if ((c >= '0' && c <= '9') || c == '-' || c == '\'') return false;
|
||||||
|
for (auto & i : s)
|
||||||
|
if (!((i >= 'a' && i <= 'z') ||
|
||||||
|
(i >= 'A' && i <= 'Z') ||
|
||||||
|
(i >= '0' && i <= '9') ||
|
||||||
|
i == '_' || i == '-' || i == '\''))
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream &
|
||||||
|
printAttributeName(std::ostream & str, std::string_view name) {
|
||||||
|
if (isVarName(name))
|
||||||
|
str << name;
|
||||||
|
else
|
||||||
|
printLiteralString(str, name);
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
48
src/libexpr/print.hh
Normal file
48
src/libexpr/print.hh
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
#pragma once
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* @brief Common printing functions for the Nix language
|
||||||
|
*
|
||||||
|
* While most types come with their own methods for printing, they share some
|
||||||
|
* functions that are placed here.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
/**
|
||||||
|
* Print a string as a Nix string literal.
|
||||||
|
*
|
||||||
|
* Quotes and fairly minimal escaping are added.
|
||||||
|
*
|
||||||
|
* @param s The logical string
|
||||||
|
*/
|
||||||
|
std::ostream & printLiteralString(std::ostream & o, std::string_view s);
|
||||||
|
inline std::ostream & printLiteralString(std::ostream & o, const char * s) {
|
||||||
|
return printLiteralString(o, std::string_view(s));
|
||||||
|
}
|
||||||
|
inline std::ostream & printLiteralString(std::ostream & o, const std::string & s) {
|
||||||
|
return printLiteralString(o, std::string_view(s));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Print `true` or `false`. */
|
||||||
|
std::ostream & printLiteralBool(std::ostream & o, bool b);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a string as an attribute name in the Nix expression language syntax.
|
||||||
|
*
|
||||||
|
* Prints a quoted string if necessary.
|
||||||
|
*/
|
||||||
|
std::ostream & printAttributeName(std::ostream & o, std::string_view s);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a string as an identifier in the Nix expression language syntax.
|
||||||
|
*
|
||||||
|
* FIXME: "identifier" is ambiguous. Identifiers do not have a single
|
||||||
|
* textual representation. They can be used in variable references,
|
||||||
|
* let bindings, left-hand sides or attribute names in a select
|
||||||
|
* expression, or something else entirely, like JSON. Use one of the
|
||||||
|
* `print*` functions instead.
|
||||||
|
*/
|
||||||
|
std::ostream & printIdentifier(std::ostream & o, std::string_view s);
|
||||||
|
}
|
|
@ -8,7 +8,7 @@ namespace nix {
|
||||||
protected:
|
protected:
|
||||||
std::string getJSONValue(Value& value) {
|
std::string getJSONValue(Value& value) {
|
||||||
std::stringstream ss;
|
std::stringstream ss;
|
||||||
PathSet ps;
|
NixStringContext ps;
|
||||||
printValueAsJSON(state, true, value, noPos, ss, ps);
|
printValueAsJSON(state, true, value, noPos, ss, ps);
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace nix {
|
||||||
}
|
}
|
||||||
Value eval(std::string input, bool forceValue = true) {
|
Value eval(std::string input, bool forceValue = true) {
|
||||||
Value v;
|
Value v;
|
||||||
Expr * e = state.parseExprFromString(input, "");
|
Expr * e = state.parseExprFromString(input, state.rootPath(CanonPath::root));
|
||||||
assert(e);
|
assert(e);
|
||||||
state.eval(e, v);
|
state.eval(e, v);
|
||||||
if (forceValue)
|
if (forceValue)
|
||||||
|
|
|
@ -12,7 +12,7 @@ libexpr-tests_SOURCES := \
|
||||||
$(wildcard $(d)/*.cc) \
|
$(wildcard $(d)/*.cc) \
|
||||||
$(wildcard $(d)/value/*.cc)
|
$(wildcard $(d)/value/*.cc)
|
||||||
|
|
||||||
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests
|
libexpr-tests_CXXFLAGS += -I src/libexpr -I src/libutil -I src/libstore -I src/libexpr/tests -I src/libfetchers
|
||||||
|
|
||||||
libexpr-tests_LIBS = libstore-tests libutils-tests libexpr libutil libstore libfetchers
|
libexpr-tests_LIBS = libstore-tests libutils-tests libexpr libutil libstore libfetchers
|
||||||
|
|
||||||
|
|
|
@ -8,69 +8,62 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
// Testing of trivial expressions
|
TEST(NixStringContextElemTest, empty_invalid) {
|
||||||
struct NixStringContextElemTest : public LibExprTest {
|
|
||||||
const Store & store() const {
|
|
||||||
return *LibExprTest::store;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST_F(NixStringContextElemTest, empty_invalid) {
|
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(
|
||||||
NixStringContextElem::parse(store(), ""),
|
NixStringContextElem::parse(""),
|
||||||
BadNixStringContextElem);
|
BadNixStringContextElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(NixStringContextElemTest, single_bang_invalid) {
|
TEST(NixStringContextElemTest, single_bang_invalid) {
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(
|
||||||
NixStringContextElem::parse(store(), "!"),
|
NixStringContextElem::parse("!"),
|
||||||
BadNixStringContextElem);
|
BadNixStringContextElem);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(NixStringContextElemTest, double_bang_invalid) {
|
TEST(NixStringContextElemTest, double_bang_invalid) {
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(
|
||||||
NixStringContextElem::parse(store(), "!!/"),
|
NixStringContextElem::parse("!!/"),
|
||||||
BadStorePath);
|
BadStorePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(NixStringContextElemTest, eq_slash_invalid) {
|
TEST(NixStringContextElemTest, eq_slash_invalid) {
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(
|
||||||
NixStringContextElem::parse(store(), "=/"),
|
NixStringContextElem::parse("=/"),
|
||||||
BadStorePath);
|
BadStorePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(NixStringContextElemTest, slash_invalid) {
|
TEST(NixStringContextElemTest, slash_invalid) {
|
||||||
EXPECT_THROW(
|
EXPECT_THROW(
|
||||||
NixStringContextElem::parse(store(), "/"),
|
NixStringContextElem::parse("/"),
|
||||||
BadStorePath);
|
BadStorePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(NixStringContextElemTest, opaque) {
|
TEST(NixStringContextElemTest, opaque) {
|
||||||
std::string_view opaque = "/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
|
std::string_view opaque = "g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x";
|
||||||
auto elem = NixStringContextElem::parse(store(), opaque);
|
auto elem = NixStringContextElem::parse(opaque);
|
||||||
auto * p = std::get_if<NixStringContextElem::Opaque>(&elem);
|
auto * p = std::get_if<NixStringContextElem::Opaque>(&elem);
|
||||||
ASSERT_TRUE(p);
|
ASSERT_TRUE(p);
|
||||||
ASSERT_EQ(p->path, store().parseStorePath(opaque));
|
ASSERT_EQ(p->path, StorePath { opaque });
|
||||||
ASSERT_EQ(elem.to_string(store()), opaque);
|
ASSERT_EQ(elem.to_string(), opaque);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(NixStringContextElemTest, drvDeep) {
|
TEST(NixStringContextElemTest, drvDeep) {
|
||||||
std::string_view drvDeep = "=/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
|
std::string_view drvDeep = "=g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
|
||||||
auto elem = NixStringContextElem::parse(store(), drvDeep);
|
auto elem = NixStringContextElem::parse(drvDeep);
|
||||||
auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem);
|
auto * p = std::get_if<NixStringContextElem::DrvDeep>(&elem);
|
||||||
ASSERT_TRUE(p);
|
ASSERT_TRUE(p);
|
||||||
ASSERT_EQ(p->drvPath, store().parseStorePath(drvDeep.substr(1)));
|
ASSERT_EQ(p->drvPath, StorePath { drvDeep.substr(1) });
|
||||||
ASSERT_EQ(elem.to_string(store()), drvDeep);
|
ASSERT_EQ(elem.to_string(), drvDeep);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(NixStringContextElemTest, built) {
|
TEST(NixStringContextElemTest, built) {
|
||||||
std::string_view built = "!foo!/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
|
std::string_view built = "!foo!g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x.drv";
|
||||||
auto elem = NixStringContextElem::parse(store(), built);
|
auto elem = NixStringContextElem::parse(built);
|
||||||
auto * p = std::get_if<NixStringContextElem::Built>(&elem);
|
auto * p = std::get_if<NixStringContextElem::Built>(&elem);
|
||||||
ASSERT_TRUE(p);
|
ASSERT_TRUE(p);
|
||||||
ASSERT_EQ(p->output, "foo");
|
ASSERT_EQ(p->output, "foo");
|
||||||
ASSERT_EQ(p->drvPath, store().parseStorePath(built.substr(5)));
|
ASSERT_EQ(p->drvPath, StorePath { built.substr(5) });
|
||||||
ASSERT_EQ(elem.to_string(store()), built);
|
ASSERT_EQ(elem.to_string(), built);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -116,12 +109,12 @@ Gen<NixStringContextElem> Arbitrary<NixStringContextElem>::arbitrary()
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
RC_GTEST_FIXTURE_PROP(
|
RC_GTEST_PROP(
|
||||||
NixStringContextElemTest,
|
NixStringContextElemTest,
|
||||||
prop_round_rip,
|
prop_round_rip,
|
||||||
(const NixStringContextElem & o))
|
(const NixStringContextElem & o))
|
||||||
{
|
{
|
||||||
RC_ASSERT(o == NixStringContextElem::parse(store(), o.to_string(store())));
|
RC_ASSERT(o == NixStringContextElem::parse(o.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
using json = nlohmann::json;
|
using json = nlohmann::json;
|
||||||
json printValueAsJSON(EvalState & state, bool strict,
|
json printValueAsJSON(EvalState & state, bool strict,
|
||||||
Value & v, const PosIdx pos, PathSet & context, bool copyToStore)
|
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore)
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
|
@ -36,9 +36,10 @@ json printValueAsJSON(EvalState & state, bool strict,
|
||||||
|
|
||||||
case nPath:
|
case nPath:
|
||||||
if (copyToStore)
|
if (copyToStore)
|
||||||
out = state.store->printStorePath(state.copyPathToStore(context, v.path));
|
out = state.store->printStorePath(
|
||||||
|
state.copyPathToStore(context, v.path()));
|
||||||
else
|
else
|
||||||
out = v.path;
|
out = v.path().path.abs();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nNull:
|
case nNull:
|
||||||
|
@ -94,13 +95,13 @@ json printValueAsJSON(EvalState & state, bool strict,
|
||||||
}
|
}
|
||||||
|
|
||||||
void printValueAsJSON(EvalState & state, bool strict,
|
void printValueAsJSON(EvalState & state, bool strict,
|
||||||
Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore)
|
Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore)
|
||||||
{
|
{
|
||||||
str << printValueAsJSON(state, strict, v, pos, context, copyToStore);
|
str << printValueAsJSON(state, strict, v, pos, context, copyToStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
|
json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict,
|
||||||
PathSet & context, bool copyToStore) const
|
NixStringContext & context, bool copyToStore) const
|
||||||
{
|
{
|
||||||
state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
|
state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,9 +11,9 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
nlohmann::json printValueAsJSON(EvalState & state, bool strict,
|
nlohmann::json printValueAsJSON(EvalState & state, bool strict,
|
||||||
Value & v, const PosIdx pos, PathSet & context, bool copyToStore = true);
|
Value & v, const PosIdx pos, NixStringContext & context, bool copyToStore = true);
|
||||||
|
|
||||||
void printValueAsJSON(EvalState & state, bool strict,
|
void printValueAsJSON(EvalState & state, bool strict,
|
||||||
Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore = true);
|
Value & v, const PosIdx pos, std::ostream & str, NixStringContext & context, bool copyToStore = true);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,21 +18,21 @@ static XMLAttrs singletonAttrs(const std::string & name, const std::string & val
|
||||||
|
|
||||||
|
|
||||||
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
Value & v, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen,
|
||||||
const PosIdx pos);
|
const PosIdx pos);
|
||||||
|
|
||||||
|
|
||||||
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
|
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
|
||||||
{
|
{
|
||||||
if (auto path = std::get_if<Path>(&pos.origin))
|
if (auto path = std::get_if<SourcePath>(&pos.origin))
|
||||||
xmlAttrs["path"] = *path;
|
xmlAttrs["path"] = path->path.abs();
|
||||||
xmlAttrs["line"] = fmt("%1%", pos.line);
|
xmlAttrs["line"] = fmt("%1%", pos.line);
|
||||||
xmlAttrs["column"] = fmt("%1%", pos.column);
|
xmlAttrs["column"] = fmt("%1%", pos.column);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void showAttrs(EvalState & state, bool strict, bool location,
|
static void showAttrs(EvalState & state, bool strict, bool location,
|
||||||
Bindings & attrs, XMLWriter & doc, PathSet & context, PathSet & drvsSeen)
|
Bindings & attrs, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen)
|
||||||
{
|
{
|
||||||
StringSet names;
|
StringSet names;
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ static void showAttrs(EvalState & state, bool strict, bool location,
|
||||||
|
|
||||||
|
|
||||||
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
Value & v, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen,
|
||||||
const PosIdx pos)
|
const PosIdx pos)
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
@ -78,7 +78,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nPath:
|
case nPath:
|
||||||
doc.writeEmptyElement("path", singletonAttrs("value", v.path));
|
doc.writeEmptyElement("path", singletonAttrs("value", v.path().to_string()));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case nNull:
|
case nNull:
|
||||||
|
@ -166,7 +166,7 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
|
|
||||||
|
|
||||||
void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
|
void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
|
||||||
bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
bool location, XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen,
|
||||||
const PosIdx pos) const
|
const PosIdx pos) const
|
||||||
{
|
{
|
||||||
doc.writeEmptyElement("unevaluated");
|
doc.writeEmptyElement("unevaluated");
|
||||||
|
@ -174,7 +174,7 @@ void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
|
||||||
|
|
||||||
|
|
||||||
void printValueAsXML(EvalState & state, bool strict, bool location,
|
void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
Value & v, std::ostream & out, PathSet & context, const PosIdx pos)
|
Value & v, std::ostream & out, NixStringContext & context, const PosIdx pos)
|
||||||
{
|
{
|
||||||
XMLWriter doc(true, out);
|
XMLWriter doc(true, out);
|
||||||
XMLOpenElement root(doc, "expr");
|
XMLOpenElement root(doc, "expr");
|
||||||
|
|
|
@ -10,6 +10,6 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
void printValueAsXML(EvalState & state, bool strict, bool location,
|
void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
Value & v, std::ostream & out, PathSet & context, const PosIdx pos);
|
Value & v, std::ostream & out, NixStringContext & context, const PosIdx pos);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
#include "value/context.hh"
|
#include "value/context.hh"
|
||||||
|
#include "input-accessor.hh"
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
#include <gc/gc_allocator.h>
|
#include <gc/gc_allocator.h>
|
||||||
|
@ -100,7 +101,7 @@ class ExternalValueBase
|
||||||
* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
||||||
* error.
|
* error.
|
||||||
*/
|
*/
|
||||||
virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const;
|
virtual std::string coerceToString(const Pos & pos, NixStringContext & context, bool copyMore, bool copyToStore) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compare to another value of the same type. Defaults to uncomparable,
|
* Compare to another value of the same type. Defaults to uncomparable,
|
||||||
|
@ -112,13 +113,13 @@ class ExternalValueBase
|
||||||
* Print the value as JSON. Defaults to unconvertable, i.e. throws an error
|
* Print the value as JSON. Defaults to unconvertable, i.e. throws an error
|
||||||
*/
|
*/
|
||||||
virtual nlohmann::json printValueAsJSON(EvalState & state, bool strict,
|
virtual nlohmann::json printValueAsJSON(EvalState & state, bool strict,
|
||||||
PathSet & context, bool copyToStore = true) const;
|
NixStringContext & context, bool copyToStore = true) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Print the value as XML. Defaults to unevaluated
|
* Print the value as XML. Defaults to unevaluated
|
||||||
*/
|
*/
|
||||||
virtual void printValueAsXML(EvalState & state, bool strict, bool location,
|
virtual void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
XMLWriter & doc, NixStringContext & context, PathSet & drvsSeen,
|
||||||
const PosIdx pos) const;
|
const PosIdx pos) const;
|
||||||
|
|
||||||
virtual ~ExternalValueBase()
|
virtual ~ExternalValueBase()
|
||||||
|
@ -188,7 +189,7 @@ public:
|
||||||
const char * * context; // must be in sorted order
|
const char * * context; // must be in sorted order
|
||||||
} string;
|
} string;
|
||||||
|
|
||||||
const char * path;
|
const char * _path;
|
||||||
Bindings * attrs;
|
Bindings * attrs;
|
||||||
struct {
|
struct {
|
||||||
size_t size;
|
size_t size;
|
||||||
|
@ -268,19 +269,24 @@ public:
|
||||||
|
|
||||||
void mkString(std::string_view s);
|
void mkString(std::string_view s);
|
||||||
|
|
||||||
void mkString(std::string_view s, const PathSet & context);
|
void mkString(std::string_view s, const NixStringContext & context);
|
||||||
|
|
||||||
void mkStringMove(const char * s, const PathSet & context);
|
void mkStringMove(const char * s, const NixStringContext & context);
|
||||||
|
|
||||||
inline void mkPath(const char * s)
|
inline void mkString(const Symbol & s)
|
||||||
|
{
|
||||||
|
mkString(((const std::string &) s).c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void mkPath(const SourcePath & path);
|
||||||
|
|
||||||
|
inline void mkPath(const char * path)
|
||||||
{
|
{
|
||||||
clearValue();
|
clearValue();
|
||||||
internalType = tPath;
|
internalType = tPath;
|
||||||
path = s;
|
_path = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void mkPath(std::string_view s);
|
|
||||||
|
|
||||||
inline void mkNull()
|
inline void mkNull()
|
||||||
{
|
{
|
||||||
clearValue();
|
clearValue();
|
||||||
|
@ -394,8 +400,6 @@ public:
|
||||||
*/
|
*/
|
||||||
bool isTrivial() const;
|
bool isTrivial() const;
|
||||||
|
|
||||||
NixStringContext getContext(const Store &);
|
|
||||||
|
|
||||||
auto listItems()
|
auto listItems()
|
||||||
{
|
{
|
||||||
struct ListIterable
|
struct ListIterable
|
||||||
|
@ -423,6 +427,18 @@ public:
|
||||||
auto begin = listElems();
|
auto begin = listElems();
|
||||||
return ConstListIterable { begin, begin + listSize() };
|
return ConstListIterable { begin, begin + listSize() };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SourcePath path() const
|
||||||
|
{
|
||||||
|
assert(internalType == tPath);
|
||||||
|
return SourcePath{CanonPath(_path)};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view str() const
|
||||||
|
{
|
||||||
|
assert(internalType == tString);
|
||||||
|
return std::string_view(string.s);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
#include "value/context.hh"
|
#include "value/context.hh"
|
||||||
#include "store-api.hh"
|
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
NixStringContextElem NixStringContextElem::parse(const Store & store, std::string_view s0)
|
NixStringContextElem NixStringContextElem::parse(std::string_view s0)
|
||||||
{
|
{
|
||||||
std::string_view s = s0;
|
std::string_view s = s0;
|
||||||
|
|
||||||
|
@ -25,41 +24,41 @@ NixStringContextElem NixStringContextElem::parse(const Store & store, std::strin
|
||||||
"String content element beginning with '!' should have a second '!'");
|
"String content element beginning with '!' should have a second '!'");
|
||||||
}
|
}
|
||||||
return NixStringContextElem::Built {
|
return NixStringContextElem::Built {
|
||||||
.drvPath = store.parseStorePath(s.substr(index + 1)),
|
.drvPath = StorePath { s.substr(index + 1) },
|
||||||
.output = std::string(s.substr(0, index)),
|
.output = std::string(s.substr(0, index)),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
case '=': {
|
case '=': {
|
||||||
return NixStringContextElem::DrvDeep {
|
return NixStringContextElem::DrvDeep {
|
||||||
.drvPath = store.parseStorePath(s.substr(1)),
|
.drvPath = StorePath { s.substr(1) },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return NixStringContextElem::Opaque {
|
return NixStringContextElem::Opaque {
|
||||||
.path = store.parseStorePath(s),
|
.path = StorePath { s },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string NixStringContextElem::to_string(const Store & store) const {
|
std::string NixStringContextElem::to_string() const {
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[&](const NixStringContextElem::Built & b) {
|
[&](const NixStringContextElem::Built & b) {
|
||||||
std::string res;
|
std::string res;
|
||||||
res += '!';
|
res += '!';
|
||||||
res += b.output;
|
res += b.output;
|
||||||
res += '!';
|
res += '!';
|
||||||
res += store.printStorePath(b.drvPath);
|
res += b.drvPath.to_string();
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
[&](const NixStringContextElem::DrvDeep & d) {
|
[&](const NixStringContextElem::DrvDeep & d) {
|
||||||
std::string res;
|
std::string res;
|
||||||
res += '=';
|
res += '=';
|
||||||
res += store.printStorePath(d.drvPath);
|
res += d.drvPath.to_string();
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
[&](const NixStringContextElem::Opaque & o) {
|
[&](const NixStringContextElem::Opaque & o) {
|
||||||
return store.printStorePath(o.path);
|
return std::string { o.path.to_string() };
|
||||||
},
|
},
|
||||||
}, raw());
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,8 +26,6 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class Store;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plain opaque path to some store object.
|
* Plain opaque path to some store object.
|
||||||
*
|
*
|
||||||
|
@ -80,12 +78,15 @@ struct NixStringContextElem : _NixStringContextElem_Raw {
|
||||||
using DrvDeep = NixStringContextElem_DrvDeep;
|
using DrvDeep = NixStringContextElem_DrvDeep;
|
||||||
using Built = NixStringContextElem_Built;
|
using Built = NixStringContextElem_Built;
|
||||||
|
|
||||||
inline const Raw & raw() const {
|
inline const Raw & raw() const & {
|
||||||
return static_cast<const Raw &>(*this);
|
return static_cast<const Raw &>(*this);
|
||||||
}
|
}
|
||||||
inline Raw & raw() {
|
inline Raw & raw() & {
|
||||||
return static_cast<Raw &>(*this);
|
return static_cast<Raw &>(*this);
|
||||||
}
|
}
|
||||||
|
inline Raw && raw() && {
|
||||||
|
return static_cast<Raw &&>(*this);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode a context string, one of:
|
* Decode a context string, one of:
|
||||||
|
@ -93,10 +94,10 @@ struct NixStringContextElem : _NixStringContextElem_Raw {
|
||||||
* - ‘=<path>’
|
* - ‘=<path>’
|
||||||
* - ‘!<name>!<path>’
|
* - ‘!<name>!<path>’
|
||||||
*/
|
*/
|
||||||
static NixStringContextElem parse(const Store & store, std::string_view s);
|
static NixStringContextElem parse(std::string_view s);
|
||||||
std::string to_string(const Store & store) const;
|
std::string to_string() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<NixStringContextElem> NixStringContext;
|
typedef std::set<NixStringContextElem> NixStringContext;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,7 +210,13 @@ StorePath Input::computeStorePath(Store & store) const
|
||||||
auto narHash = getNarHash();
|
auto narHash = getNarHash();
|
||||||
if (!narHash)
|
if (!narHash)
|
||||||
throw Error("cannot compute store path for unlocked input '%s'", to_string());
|
throw Error("cannot compute store path for unlocked input '%s'", to_string());
|
||||||
return store.makeFixedOutputPath(FileIngestionMethod::Recursive, *narHash, getName());
|
return store.makeFixedOutputPath(getName(), FixedOutputInfo {
|
||||||
|
.hash = {
|
||||||
|
.method = FileIngestionMethod::Recursive,
|
||||||
|
.hash = *narHash,
|
||||||
|
},
|
||||||
|
.references = {},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string Input::getType() const
|
std::string Input::getType() const
|
||||||
|
|
|
@ -21,7 +21,7 @@ struct DownloadUrl
|
||||||
};
|
};
|
||||||
|
|
||||||
// A github, gitlab, or sourcehut host
|
// A github, gitlab, or sourcehut host
|
||||||
const static std::string hostRegexS = "[a-zA-Z0-9.]*"; // FIXME: check
|
const static std::string hostRegexS = "[a-zA-Z0-9.-]*"; // FIXME: check
|
||||||
std::regex hostRegex(hostRegexS, std::regex::ECMAScript);
|
std::regex hostRegex(hostRegexS, std::regex::ECMAScript);
|
||||||
|
|
||||||
struct GitArchiveInputScheme : InputScheme
|
struct GitArchiveInputScheme : InputScheme
|
||||||
|
|
100
src/libfetchers/input-accessor.cc
Normal file
100
src/libfetchers/input-accessor.cc
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
#include "input-accessor.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
std::ostream & operator << (std::ostream & str, const SourcePath & path)
|
||||||
|
{
|
||||||
|
str << path.to_string();
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view SourcePath::baseName() const
|
||||||
|
{
|
||||||
|
return path.baseName().value_or("source");
|
||||||
|
}
|
||||||
|
|
||||||
|
SourcePath SourcePath::parent() const
|
||||||
|
{
|
||||||
|
auto p = path.parent();
|
||||||
|
assert(p);
|
||||||
|
return std::move(*p);
|
||||||
|
}
|
||||||
|
|
||||||
|
InputAccessor::Stat SourcePath::lstat() const
|
||||||
|
{
|
||||||
|
auto st = nix::lstat(path.abs());
|
||||||
|
return InputAccessor::Stat {
|
||||||
|
.type =
|
||||||
|
S_ISREG(st.st_mode) ? InputAccessor::tRegular :
|
||||||
|
S_ISDIR(st.st_mode) ? InputAccessor::tDirectory :
|
||||||
|
S_ISLNK(st.st_mode) ? InputAccessor::tSymlink :
|
||||||
|
InputAccessor::tMisc,
|
||||||
|
.isExecutable = S_ISREG(st.st_mode) && st.st_mode & S_IXUSR
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<InputAccessor::Stat> SourcePath::maybeLstat() const
|
||||||
|
{
|
||||||
|
// FIXME: merge these into one operation.
|
||||||
|
if (!pathExists())
|
||||||
|
return {};
|
||||||
|
return lstat();
|
||||||
|
}
|
||||||
|
|
||||||
|
InputAccessor::DirEntries SourcePath::readDirectory() const
|
||||||
|
{
|
||||||
|
InputAccessor::DirEntries res;
|
||||||
|
for (auto & entry : nix::readDirectory(path.abs())) {
|
||||||
|
std::optional<InputAccessor::Type> type;
|
||||||
|
switch (entry.type) {
|
||||||
|
case DT_REG: type = InputAccessor::Type::tRegular; break;
|
||||||
|
case DT_LNK: type = InputAccessor::Type::tSymlink; break;
|
||||||
|
case DT_DIR: type = InputAccessor::Type::tDirectory; break;
|
||||||
|
}
|
||||||
|
res.emplace(entry.name, type);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
StorePath SourcePath::fetchToStore(
|
||||||
|
ref<Store> store,
|
||||||
|
std::string_view name,
|
||||||
|
PathFilter * filter,
|
||||||
|
RepairFlag repair) const
|
||||||
|
{
|
||||||
|
return
|
||||||
|
settings.readOnlyMode
|
||||||
|
? store->computeStorePathForPath(name, path.abs(), FileIngestionMethod::Recursive, htSHA256, filter ? *filter : defaultPathFilter).first
|
||||||
|
: store->addToStore(name, path.abs(), FileIngestionMethod::Recursive, htSHA256, filter ? *filter : defaultPathFilter, repair);
|
||||||
|
}
|
||||||
|
|
||||||
|
SourcePath SourcePath::resolveSymlinks() const
|
||||||
|
{
|
||||||
|
SourcePath res(CanonPath::root);
|
||||||
|
|
||||||
|
int linksAllowed = 1024;
|
||||||
|
|
||||||
|
for (auto & component : path) {
|
||||||
|
res.path.push(component);
|
||||||
|
while (true) {
|
||||||
|
if (auto st = res.maybeLstat()) {
|
||||||
|
if (!linksAllowed--)
|
||||||
|
throw Error("infinite symlink recursion in path '%s'", path);
|
||||||
|
if (st->type != InputAccessor::tSymlink) break;
|
||||||
|
auto target = res.readLink();
|
||||||
|
if (hasPrefix(target, "/"))
|
||||||
|
res = CanonPath(target);
|
||||||
|
else {
|
||||||
|
res.path.pop();
|
||||||
|
res.path.extend(CanonPath(target));
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
167
src/libfetchers/input-accessor.hh
Normal file
167
src/libfetchers/input-accessor.hh
Normal file
|
@ -0,0 +1,167 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ref.hh"
|
||||||
|
#include "types.hh"
|
||||||
|
#include "archive.hh"
|
||||||
|
#include "canon-path.hh"
|
||||||
|
#include "repair-flag.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
class StorePath;
|
||||||
|
class Store;
|
||||||
|
|
||||||
|
struct InputAccessor
|
||||||
|
{
|
||||||
|
enum Type {
|
||||||
|
tRegular, tSymlink, tDirectory,
|
||||||
|
/**
|
||||||
|
Any other node types that may be encountered on the file system, such as device nodes, sockets, named pipe, and possibly even more exotic things.
|
||||||
|
|
||||||
|
Responsible for `"unknown"` from `builtins.readFileType "/dev/null"`.
|
||||||
|
|
||||||
|
Unlike `DT_UNKNOWN`, this must not be used for deferring the lookup of types.
|
||||||
|
*/
|
||||||
|
tMisc
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Stat
|
||||||
|
{
|
||||||
|
Type type = tMisc;
|
||||||
|
//uint64_t fileSize = 0; // regular files only
|
||||||
|
bool isExecutable = false; // regular files only
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::optional<Type> DirEntry;
|
||||||
|
|
||||||
|
typedef std::map<std::string, DirEntry> DirEntries;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An abstraction for accessing source files during
|
||||||
|
* evaluation. Currently, it's just a wrapper around `CanonPath` that
|
||||||
|
* accesses files in the regular filesystem, but in the future it will
|
||||||
|
* support fetching files in other ways.
|
||||||
|
*/
|
||||||
|
struct SourcePath
|
||||||
|
{
|
||||||
|
CanonPath path;
|
||||||
|
|
||||||
|
SourcePath(CanonPath path)
|
||||||
|
: path(std::move(path))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
std::string_view baseName() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct the parent of this `SourcePath`. Aborts if `this`
|
||||||
|
* denotes the root.
|
||||||
|
*/
|
||||||
|
SourcePath parent() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this `SourcePath` denotes a regular file (not a symlink),
|
||||||
|
* return its contents; otherwise throw an error.
|
||||||
|
*/
|
||||||
|
std::string readFile() const
|
||||||
|
{ return nix::readFile(path.abs()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return whether this `SourcePath` denotes a file (of any type)
|
||||||
|
* that exists
|
||||||
|
*/
|
||||||
|
bool pathExists() const
|
||||||
|
{ return nix::pathExists(path.abs()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return stats about this `SourcePath`, or throw an exception if
|
||||||
|
* it doesn't exist.
|
||||||
|
*/
|
||||||
|
InputAccessor::Stat lstat() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return stats about this `SourcePath`, or std::nullopt if it
|
||||||
|
* doesn't exist.
|
||||||
|
*/
|
||||||
|
std::optional<InputAccessor::Stat> maybeLstat() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this `SourcePath` denotes a directory (not a symlink),
|
||||||
|
* return its directory entries; otherwise throw an error.
|
||||||
|
*/
|
||||||
|
InputAccessor::DirEntries readDirectory() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this `SourcePath` denotes a symlink, return its target;
|
||||||
|
* otherwise throw an error.
|
||||||
|
*/
|
||||||
|
std::string readLink() const
|
||||||
|
{ return nix::readLink(path.abs()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dump this `SourcePath` to `sink` as a NAR archive.
|
||||||
|
*/
|
||||||
|
void dumpPath(
|
||||||
|
Sink & sink,
|
||||||
|
PathFilter & filter = defaultPathFilter) const
|
||||||
|
{ return nix::dumpPath(path.abs(), sink, filter); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy this `SourcePath` to the Nix store.
|
||||||
|
*/
|
||||||
|
StorePath fetchToStore(
|
||||||
|
ref<Store> store,
|
||||||
|
std::string_view name = "source",
|
||||||
|
PathFilter * filter = nullptr,
|
||||||
|
RepairFlag repair = NoRepair) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the location of this path in the "real" filesystem, if
|
||||||
|
* it has a physical location.
|
||||||
|
*/
|
||||||
|
std::optional<CanonPath> getPhysicalPath() const
|
||||||
|
{ return path; }
|
||||||
|
|
||||||
|
std::string to_string() const
|
||||||
|
{ return path.abs(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a `CanonPath` to this path.
|
||||||
|
*/
|
||||||
|
SourcePath operator + (const CanonPath & x) const
|
||||||
|
{ return {path + x}; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Append a single component `c` to this path. `c` must not
|
||||||
|
* contain a slash. A slash is implicitly added between this path
|
||||||
|
* and `c`.
|
||||||
|
*/
|
||||||
|
SourcePath operator + (std::string_view c) const
|
||||||
|
{ return {path + c}; }
|
||||||
|
|
||||||
|
bool operator == (const SourcePath & x) const
|
||||||
|
{
|
||||||
|
return path == x.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator != (const SourcePath & x) const
|
||||||
|
{
|
||||||
|
return path != x.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator < (const SourcePath & x) const
|
||||||
|
{
|
||||||
|
return path < x.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve any symlinks in this `SourcePath` (including its
|
||||||
|
* parents). The result is a `SourcePath` in which no element is a
|
||||||
|
* symlink.
|
||||||
|
*/
|
||||||
|
SourcePath resolveSymlinks() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::ostream & operator << (std::ostream & str, const SourcePath & path);
|
||||||
|
|
||||||
|
}
|
|
@ -71,15 +71,19 @@ DownloadFileResult downloadFile(
|
||||||
dumpString(res.data, sink);
|
dumpString(res.data, sink);
|
||||||
auto hash = hashString(htSHA256, res.data);
|
auto hash = hashString(htSHA256, res.data);
|
||||||
ValidPathInfo info {
|
ValidPathInfo info {
|
||||||
store->makeFixedOutputPath(FileIngestionMethod::Flat, hash, name),
|
*store,
|
||||||
|
name,
|
||||||
|
FixedOutputInfo {
|
||||||
|
.hash = {
|
||||||
|
.method = FileIngestionMethod::Flat,
|
||||||
|
.hash = hash,
|
||||||
|
},
|
||||||
|
.references = {},
|
||||||
|
},
|
||||||
hashString(htSHA256, sink.s),
|
hashString(htSHA256, sink.s),
|
||||||
};
|
};
|
||||||
info.narSize = sink.s.size();
|
info.narSize = sink.s.size();
|
||||||
info.ca = FixedOutputHash {
|
auto source = StringSource { sink.s };
|
||||||
.method = FileIngestionMethod::Flat,
|
|
||||||
.hash = hash,
|
|
||||||
};
|
|
||||||
auto source = StringSource(sink.s);
|
|
||||||
store->addToStore(info, source, NoRepair, NoCheckSigs);
|
store->addToStore(info, source, NoRepair, NoCheckSigs);
|
||||||
storePath = std::move(info.path);
|
storePath = std::move(info.path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
///@file
|
///@file
|
||||||
|
|
||||||
#include "args.hh"
|
#include "args.hh"
|
||||||
|
#include "repair-flag.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -49,4 +50,21 @@ struct MixJSON : virtual Args
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct MixRepair : virtual Args
|
||||||
|
{
|
||||||
|
RepairFlag repair = NoRepair;
|
||||||
|
|
||||||
|
MixRepair()
|
||||||
|
{
|
||||||
|
addFlag({
|
||||||
|
.longName = "repair",
|
||||||
|
.description =
|
||||||
|
"During evaluation, rewrite missing or corrupted files in the Nix store. "
|
||||||
|
"During building, rebuild missing or corrupted store paths.",
|
||||||
|
.category = miscCategory,
|
||||||
|
.handler = {&repair, Repair},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <mutex>
|
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
@ -20,16 +19,9 @@
|
||||||
#ifdef __linux__
|
#ifdef __linux__
|
||||||
#include <features.h>
|
#include <features.h>
|
||||||
#endif
|
#endif
|
||||||
#ifdef __GLIBC__
|
|
||||||
#include <gnu/lib-names.h>
|
|
||||||
#include <nss.h>
|
|
||||||
#include <dlfcn.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <openssl/crypto.h>
|
#include <openssl/crypto.h>
|
||||||
|
|
||||||
#include <sodium.h>
|
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -115,57 +107,6 @@ std::string getArg(const std::string & opt,
|
||||||
return *i;
|
return *i;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x10101000L
|
|
||||||
/* OpenSSL is not thread-safe by default - it will randomly crash
|
|
||||||
unless the user supplies a mutex locking function. So let's do
|
|
||||||
that. */
|
|
||||||
static std::vector<std::mutex> opensslLocks;
|
|
||||||
|
|
||||||
static void opensslLockCallback(int mode, int type, const char * file, int line)
|
|
||||||
{
|
|
||||||
if (mode & CRYPTO_LOCK)
|
|
||||||
opensslLocks[type].lock();
|
|
||||||
else
|
|
||||||
opensslLocks[type].unlock();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static std::once_flag dns_resolve_flag;
|
|
||||||
|
|
||||||
static void preloadNSS() {
|
|
||||||
/* builtin:fetchurl can trigger a DNS lookup, which with glibc can trigger a dynamic library load of
|
|
||||||
one of the glibc NSS libraries in a sandboxed child, which will fail unless the library's already
|
|
||||||
been loaded in the parent. So we force a lookup of an invalid domain to force the NSS machinery to
|
|
||||||
load its lookup libraries in the parent before any child gets a chance to. */
|
|
||||||
std::call_once(dns_resolve_flag, []() {
|
|
||||||
#ifdef __GLIBC__
|
|
||||||
/* On linux, glibc will run every lookup through the nss layer.
|
|
||||||
* That means every lookup goes, by default, through nscd, which acts as a local
|
|
||||||
* cache.
|
|
||||||
* Because we run builds in a sandbox, we also remove access to nscd otherwise
|
|
||||||
* lookups would leak into the sandbox.
|
|
||||||
*
|
|
||||||
* But now we have a new problem, we need to make sure the nss_dns backend that
|
|
||||||
* does the dns lookups when nscd is not available is loaded or available.
|
|
||||||
*
|
|
||||||
* We can't make it available without leaking nix's environment, so instead we'll
|
|
||||||
* load the backend, and configure nss so it does not try to run dns lookups
|
|
||||||
* through nscd.
|
|
||||||
*
|
|
||||||
* This is technically only used for builtins:fetch* functions so we only care
|
|
||||||
* about dns.
|
|
||||||
*
|
|
||||||
* All other platforms are unaffected.
|
|
||||||
*/
|
|
||||||
if (!dlopen(LIBNSS_DNS_SO, RTLD_NOW))
|
|
||||||
warn("unable to load nss_dns backend");
|
|
||||||
// FIXME: get hosts entry from nsswitch.conf.
|
|
||||||
__nss_configure_lookup("hosts", "files dns");
|
|
||||||
#endif
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static void sigHandler(int signo) { }
|
static void sigHandler(int signo) { }
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,16 +118,7 @@ void initNix()
|
||||||
std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
|
std::cerr.rdbuf()->pubsetbuf(buf, sizeof(buf));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if OPENSSL_VERSION_NUMBER < 0x10101000L
|
initLibStore();
|
||||||
/* Initialise OpenSSL locking. */
|
|
||||||
opensslLocks = std::vector<std::mutex>(CRYPTO_num_locks());
|
|
||||||
CRYPTO_set_locking_callback(opensslLockCallback);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (sodium_init() == -1)
|
|
||||||
throw Error("could not initialise libsodium");
|
|
||||||
|
|
||||||
loadConfFile();
|
|
||||||
|
|
||||||
startSignalHandlerThread();
|
startSignalHandlerThread();
|
||||||
|
|
||||||
|
@ -223,7 +155,10 @@ void initNix()
|
||||||
if (sigaction(SIGTRAP, &act, 0)) throw SysError("handling SIGTRAP");
|
if (sigaction(SIGTRAP, &act, 0)) throw SysError("handling SIGTRAP");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/* Register a SIGSEGV handler to detect stack overflows. */
|
/* Register a SIGSEGV handler to detect stack overflows.
|
||||||
|
Why not initLibExpr()? initGC() is essentially that, but
|
||||||
|
detectStackOverflow is not an instance of the init function concept, as
|
||||||
|
it may have to be invoked more than once per process. */
|
||||||
detectStackOverflow();
|
detectStackOverflow();
|
||||||
|
|
||||||
/* There is no privacy in the Nix system ;-) At least not for
|
/* There is no privacy in the Nix system ;-) At least not for
|
||||||
|
@ -236,16 +171,6 @@ void initNix()
|
||||||
gettimeofday(&tv, 0);
|
gettimeofday(&tv, 0);
|
||||||
srandom(tv.tv_usec);
|
srandom(tv.tv_usec);
|
||||||
|
|
||||||
/* On macOS, don't use the per-session TMPDIR (as set e.g. by
|
|
||||||
sshd). This breaks build users because they don't have access
|
|
||||||
to the TMPDIR, in particular in ‘nix-store --serve’. */
|
|
||||||
#if __APPLE__
|
|
||||||
if (hasPrefix(getEnv("TMPDIR").value_or("/tmp"), "/var/folders/"))
|
|
||||||
unsetenv("TMPDIR");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
preloadNSS();
|
|
||||||
initLibStore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -306,11 +306,22 @@ StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, std::string_view n
|
||||||
unsupported("addToStoreFromDump");
|
unsupported("addToStoreFromDump");
|
||||||
return addToStoreCommon(dump, repair, CheckSigs, [&](HashResult nar) {
|
return addToStoreCommon(dump, repair, CheckSigs, [&](HashResult nar) {
|
||||||
ValidPathInfo info {
|
ValidPathInfo info {
|
||||||
makeFixedOutputPath(method, nar.first, name, references),
|
*this,
|
||||||
|
name,
|
||||||
|
FixedOutputInfo {
|
||||||
|
.hash = {
|
||||||
|
.method = method,
|
||||||
|
.hash = nar.first,
|
||||||
|
},
|
||||||
|
.references = {
|
||||||
|
.others = references,
|
||||||
|
// caller is not capable of creating a self-reference, because this is content-addressed without modulus
|
||||||
|
.self = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
nar.first,
|
nar.first,
|
||||||
};
|
};
|
||||||
info.narSize = nar.second;
|
info.narSize = nar.second;
|
||||||
info.references = references;
|
|
||||||
return info;
|
return info;
|
||||||
})->path;
|
})->path;
|
||||||
}
|
}
|
||||||
|
@ -414,15 +425,22 @@ StorePath BinaryCacheStore::addToStore(
|
||||||
});
|
});
|
||||||
return addToStoreCommon(*source, repair, CheckSigs, [&](HashResult nar) {
|
return addToStoreCommon(*source, repair, CheckSigs, [&](HashResult nar) {
|
||||||
ValidPathInfo info {
|
ValidPathInfo info {
|
||||||
makeFixedOutputPath(method, h, name, references),
|
*this,
|
||||||
|
name,
|
||||||
|
FixedOutputInfo {
|
||||||
|
.hash = {
|
||||||
|
.method = method,
|
||||||
|
.hash = h,
|
||||||
|
},
|
||||||
|
.references = {
|
||||||
|
.others = references,
|
||||||
|
// caller is not capable of creating a self-reference, because this is content-addressed without modulus
|
||||||
|
.self = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
nar.first,
|
nar.first,
|
||||||
};
|
};
|
||||||
info.narSize = nar.second;
|
info.narSize = nar.second;
|
||||||
info.references = references;
|
|
||||||
info.ca = FixedOutputHash {
|
|
||||||
.method = method,
|
|
||||||
.hash = h,
|
|
||||||
};
|
|
||||||
return info;
|
return info;
|
||||||
})->path;
|
})->path;
|
||||||
}
|
}
|
||||||
|
@ -434,7 +452,7 @@ StorePath BinaryCacheStore::addTextToStore(
|
||||||
RepairFlag repair)
|
RepairFlag repair)
|
||||||
{
|
{
|
||||||
auto textHash = hashString(htSHA256, s);
|
auto textHash = hashString(htSHA256, s);
|
||||||
auto path = makeTextPath(name, textHash, references);
|
auto path = makeTextPath(name, TextInfo { { textHash }, references });
|
||||||
|
|
||||||
if (!repair && isValidPath(path))
|
if (!repair && isValidPath(path))
|
||||||
return path;
|
return path;
|
||||||
|
@ -443,10 +461,16 @@ StorePath BinaryCacheStore::addTextToStore(
|
||||||
dumpString(s, sink);
|
dumpString(s, sink);
|
||||||
StringSource source(sink.s);
|
StringSource source(sink.s);
|
||||||
return addToStoreCommon(source, repair, CheckSigs, [&](HashResult nar) {
|
return addToStoreCommon(source, repair, CheckSigs, [&](HashResult nar) {
|
||||||
ValidPathInfo info { path, nar.first };
|
ValidPathInfo info {
|
||||||
|
*this,
|
||||||
|
std::string { name },
|
||||||
|
TextInfo {
|
||||||
|
{ .hash = textHash },
|
||||||
|
references,
|
||||||
|
},
|
||||||
|
nar.first,
|
||||||
|
};
|
||||||
info.narSize = nar.second;
|
info.narSize = nar.second;
|
||||||
info.ca = TextHash { textHash };
|
|
||||||
info.references = references;
|
|
||||||
return info;
|
return info;
|
||||||
})->path;
|
})->path;
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,16 +83,11 @@ struct BuildResult
|
||||||
*/
|
*/
|
||||||
bool isNonDeterministic = false;
|
bool isNonDeterministic = false;
|
||||||
|
|
||||||
/**
|
|
||||||
* The derivation we built or the store path we substituted.
|
|
||||||
*/
|
|
||||||
DerivedPath path;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For derivations, a mapping from the names of the wanted outputs
|
* For derivations, a mapping from the names of the wanted outputs
|
||||||
* to actual paths.
|
* to actual paths.
|
||||||
*/
|
*/
|
||||||
DrvOutputs builtOutputs;
|
SingleDrvOutputs builtOutputs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The start/stop times of the build (or one of the rounds, if it
|
* The start/stop times of the build (or one of the rounds, if it
|
||||||
|
@ -116,4 +111,15 @@ struct BuildResult
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A `BuildResult` together with its "primary key".
|
||||||
|
*/
|
||||||
|
struct KeyedBuildResult : BuildResult
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The derivation we built or the store path we substituted.
|
||||||
|
*/
|
||||||
|
DerivedPath path;
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,8 +145,20 @@ void DerivationGoal::work()
|
||||||
void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
void DerivationGoal::addWantedOutputs(const OutputsSpec & outputs)
|
||||||
{
|
{
|
||||||
auto newWanted = wantedOutputs.union_(outputs);
|
auto newWanted = wantedOutputs.union_(outputs);
|
||||||
if (!newWanted.isSubsetOf(wantedOutputs))
|
switch (needRestart) {
|
||||||
needRestart = true;
|
case NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed:
|
||||||
|
if (!newWanted.isSubsetOf(wantedOutputs))
|
||||||
|
needRestart = NeedRestartForMoreOutputs::OutputsAddedDoNeed;
|
||||||
|
break;
|
||||||
|
case NeedRestartForMoreOutputs::OutputsAddedDoNeed:
|
||||||
|
/* No need to check whether we added more outputs, because a
|
||||||
|
restart is already queued up. */
|
||||||
|
break;
|
||||||
|
case NeedRestartForMoreOutputs::BuildInProgressWillNotNeed:
|
||||||
|
/* We are already building all outputs, so it doesn't matter if
|
||||||
|
we now want more. */
|
||||||
|
break;
|
||||||
|
};
|
||||||
wantedOutputs = newWanted;
|
wantedOutputs = newWanted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -297,12 +309,29 @@ void DerivationGoal::outputsSubstitutionTried()
|
||||||
In particular, it may be the case that the hole in the closure is
|
In particular, it may be the case that the hole in the closure is
|
||||||
an output of the current derivation, which causes a loop if retried.
|
an output of the current derivation, which causes a loop if retried.
|
||||||
*/
|
*/
|
||||||
if (nrIncompleteClosure > 0 && nrIncompleteClosure == nrFailed) retrySubstitution = true;
|
{
|
||||||
|
bool substitutionFailed =
|
||||||
|
nrIncompleteClosure > 0 &&
|
||||||
|
nrIncompleteClosure == nrFailed;
|
||||||
|
switch (retrySubstitution) {
|
||||||
|
case RetrySubstitution::NoNeed:
|
||||||
|
if (substitutionFailed)
|
||||||
|
retrySubstitution = RetrySubstitution::YesNeed;
|
||||||
|
break;
|
||||||
|
case RetrySubstitution::YesNeed:
|
||||||
|
// Should not be able to reach this state from here.
|
||||||
|
assert(false);
|
||||||
|
break;
|
||||||
|
case RetrySubstitution::AlreadyRetried:
|
||||||
|
debug("substitution failed again, but we already retried once. Not retrying again.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
|
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
|
||||||
|
|
||||||
if (needRestart) {
|
if (needRestart == NeedRestartForMoreOutputs::OutputsAddedDoNeed) {
|
||||||
needRestart = false;
|
needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
|
||||||
haveDerivation();
|
haveDerivation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -330,6 +359,10 @@ void DerivationGoal::outputsSubstitutionTried()
|
||||||
produced using a substitute. So we have to build instead. */
|
produced using a substitute. So we have to build instead. */
|
||||||
void DerivationGoal::gaveUpOnSubstitution()
|
void DerivationGoal::gaveUpOnSubstitution()
|
||||||
{
|
{
|
||||||
|
/* At this point we are building all outputs, so if more are wanted there
|
||||||
|
is no need to restart. */
|
||||||
|
needRestart = NeedRestartForMoreOutputs::BuildInProgressWillNotNeed;
|
||||||
|
|
||||||
/* The inputs must be built before we can build this goal. */
|
/* The inputs must be built before we can build this goal. */
|
||||||
inputDrvOutputs.clear();
|
inputDrvOutputs.clear();
|
||||||
if (useDerivation)
|
if (useDerivation)
|
||||||
|
@ -451,8 +484,8 @@ void DerivationGoal::inputsRealised()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retrySubstitution && !retriedSubstitution) {
|
if (retrySubstitution == RetrySubstitution::YesNeed) {
|
||||||
retriedSubstitution = true;
|
retrySubstitution = RetrySubstitution::AlreadyRetried;
|
||||||
haveDerivation();
|
haveDerivation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -570,8 +603,6 @@ void DerivationGoal::inputsRealised()
|
||||||
build hook. */
|
build hook. */
|
||||||
state = &DerivationGoal::tryToBuild;
|
state = &DerivationGoal::tryToBuild;
|
||||||
worker.wakeUp(shared_from_this());
|
worker.wakeUp(shared_from_this());
|
||||||
|
|
||||||
buildResult = BuildResult { .path = buildResult.path };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::started()
|
void DerivationGoal::started()
|
||||||
|
@ -982,7 +1013,7 @@ void DerivationGoal::resolvedFinished()
|
||||||
auto resolvedDrv = *resolvedDrvGoal->drv;
|
auto resolvedDrv = *resolvedDrvGoal->drv;
|
||||||
auto & resolvedResult = resolvedDrvGoal->buildResult;
|
auto & resolvedResult = resolvedDrvGoal->buildResult;
|
||||||
|
|
||||||
DrvOutputs builtOutputs;
|
SingleDrvOutputs builtOutputs;
|
||||||
|
|
||||||
if (resolvedResult.success()) {
|
if (resolvedResult.success()) {
|
||||||
auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv);
|
auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv);
|
||||||
|
@ -1008,7 +1039,7 @@ void DerivationGoal::resolvedFinished()
|
||||||
worker.store.printStorePath(drvPath), wantedOutput);
|
worker.store.printStorePath(drvPath), wantedOutput);
|
||||||
|
|
||||||
auto realisation = [&]{
|
auto realisation = [&]{
|
||||||
auto take1 = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput });
|
auto take1 = get(resolvedResult.builtOutputs, wantedOutput);
|
||||||
if (take1) return *take1;
|
if (take1) return *take1;
|
||||||
|
|
||||||
/* The above `get` should work. But sateful tracking of
|
/* The above `get` should work. But sateful tracking of
|
||||||
|
@ -1033,7 +1064,7 @@ void DerivationGoal::resolvedFinished()
|
||||||
worker.store.registerDrvOutput(newRealisation);
|
worker.store.registerDrvOutput(newRealisation);
|
||||||
}
|
}
|
||||||
outputPaths.insert(realisation.outPath);
|
outputPaths.insert(realisation.outPath);
|
||||||
builtOutputs.emplace(realisation.id, realisation);
|
builtOutputs.emplace(wantedOutput, realisation);
|
||||||
}
|
}
|
||||||
|
|
||||||
runPostBuildHook(
|
runPostBuildHook(
|
||||||
|
@ -1158,7 +1189,7 @@ HookReply DerivationGoal::tryBuildHook()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DrvOutputs DerivationGoal::registerOutputs()
|
SingleDrvOutputs DerivationGoal::registerOutputs()
|
||||||
{
|
{
|
||||||
/* When using a build hook, the build hook can register the output
|
/* When using a build hook, the build hook can register the output
|
||||||
as valid (by doing `nix-store --import'). If so we don't have
|
as valid (by doing `nix-store --import'). If so we don't have
|
||||||
|
@ -1320,7 +1351,7 @@ OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
std::pair<bool, SingleDrvOutputs> DerivationGoal::checkPathValidity()
|
||||||
{
|
{
|
||||||
if (!drv->type().isPure()) return { false, {} };
|
if (!drv->type().isPure()) return { false, {} };
|
||||||
|
|
||||||
|
@ -1333,7 +1364,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
||||||
return static_cast<StringSet>(names);
|
return static_cast<StringSet>(names);
|
||||||
},
|
},
|
||||||
}, wantedOutputs.raw());
|
}, wantedOutputs.raw());
|
||||||
DrvOutputs validOutputs;
|
SingleDrvOutputs validOutputs;
|
||||||
|
|
||||||
for (auto & i : queryPartialDerivationOutputMap()) {
|
for (auto & i : queryPartialDerivationOutputMap()) {
|
||||||
auto initialOutput = get(initialOutputs, i.first);
|
auto initialOutput = get(initialOutputs, i.first);
|
||||||
|
@ -1376,7 +1407,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (info.wanted && info.known && info.known->isValid())
|
if (info.wanted && info.known && info.known->isValid())
|
||||||
validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path });
|
validOutputs.emplace(i.first, Realisation { drvOutput, info.known->path });
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we requested all the outputs, we are always fine.
|
// If we requested all the outputs, we are always fine.
|
||||||
|
@ -1400,7 +1431,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DrvOutputs DerivationGoal::assertPathValidity()
|
SingleDrvOutputs DerivationGoal::assertPathValidity()
|
||||||
{
|
{
|
||||||
auto [allValid, validOutputs] = checkPathValidity();
|
auto [allValid, validOutputs] = checkPathValidity();
|
||||||
if (!allValid)
|
if (!allValid)
|
||||||
|
@ -1411,7 +1442,7 @@ DrvOutputs DerivationGoal::assertPathValidity()
|
||||||
|
|
||||||
void DerivationGoal::done(
|
void DerivationGoal::done(
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
DrvOutputs builtOutputs,
|
SingleDrvOutputs builtOutputs,
|
||||||
std::optional<Error> ex)
|
std::optional<Error> ex)
|
||||||
{
|
{
|
||||||
buildResult.status = status;
|
buildResult.status = status;
|
||||||
|
@ -1452,12 +1483,28 @@ void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
|
||||||
{
|
{
|
||||||
Goal::waiteeDone(waitee, result);
|
Goal::waiteeDone(waitee, result);
|
||||||
|
|
||||||
if (waitee->buildResult.success())
|
if (!useDerivation) return;
|
||||||
if (auto bfd = std::get_if<DerivedPath::Built>(&waitee->buildResult.path))
|
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
|
||||||
for (auto & [output, realisation] : waitee->buildResult.builtOutputs)
|
|
||||||
|
auto * dg = dynamic_cast<DerivationGoal *>(&*waitee);
|
||||||
|
if (!dg) return;
|
||||||
|
|
||||||
|
auto outputs = fullDrv.inputDrvs.find(dg->drvPath);
|
||||||
|
if (outputs == fullDrv.inputDrvs.end()) return;
|
||||||
|
|
||||||
|
for (auto & outputName : outputs->second) {
|
||||||
|
auto buildResult = dg->getBuildResult(DerivedPath::Built {
|
||||||
|
.drvPath = dg->drvPath,
|
||||||
|
.outputs = OutputsSpec::Names { outputName },
|
||||||
|
});
|
||||||
|
if (buildResult.success()) {
|
||||||
|
auto i = buildResult.builtOutputs.find(outputName);
|
||||||
|
if (i != buildResult.builtOutputs.end())
|
||||||
inputDrvOutputs.insert_or_assign(
|
inputDrvOutputs.insert_or_assign(
|
||||||
{ bfd->drvPath, output.outputName },
|
{ dg->drvPath, outputName },
|
||||||
realisation.outPath);
|
i->second.outPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,22 +78,58 @@ struct DerivationGoal : public Goal
|
||||||
*/
|
*/
|
||||||
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
|
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See `needRestart`; just for that field.
|
||||||
|
*/
|
||||||
|
enum struct NeedRestartForMoreOutputs {
|
||||||
|
/**
|
||||||
|
* The goal state machine is progressing based on the current value of
|
||||||
|
* `wantedOutputs. No actions are needed.
|
||||||
|
*/
|
||||||
|
OutputsUnmodifedDontNeed,
|
||||||
|
/**
|
||||||
|
* `wantedOutputs` has been extended, but the state machine is
|
||||||
|
* proceeding according to its old value, so we need to restart.
|
||||||
|
*/
|
||||||
|
OutputsAddedDoNeed,
|
||||||
|
/**
|
||||||
|
* The goal state machine has progressed to the point of doing a build,
|
||||||
|
* in which case all outputs will be produced, so extensions to
|
||||||
|
* `wantedOutputs` no longer require a restart.
|
||||||
|
*/
|
||||||
|
BuildInProgressWillNotNeed,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether additional wanted outputs have been added.
|
* Whether additional wanted outputs have been added.
|
||||||
*/
|
*/
|
||||||
bool needRestart = false;
|
NeedRestartForMoreOutputs needRestart = NeedRestartForMoreOutputs::OutputsUnmodifedDontNeed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See `retrySubstitution`; just for that field.
|
||||||
|
*/
|
||||||
|
enum RetrySubstitution {
|
||||||
|
/**
|
||||||
|
* No issues have yet arose, no need to restart.
|
||||||
|
*/
|
||||||
|
NoNeed,
|
||||||
|
/**
|
||||||
|
* Something failed and there is an incomplete closure. Let's retry
|
||||||
|
* substituting.
|
||||||
|
*/
|
||||||
|
YesNeed,
|
||||||
|
/**
|
||||||
|
* We are current or have already retried substitution, and whether or
|
||||||
|
* not something goes wrong we will not retry again.
|
||||||
|
*/
|
||||||
|
AlreadyRetried,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to retry substituting the outputs after building the
|
* Whether to retry substituting the outputs after building the
|
||||||
* inputs. This is done in case of an incomplete closure.
|
* inputs. This is done in case of an incomplete closure.
|
||||||
*/
|
*/
|
||||||
bool retrySubstitution = false;
|
RetrySubstitution retrySubstitution = RetrySubstitution::NoNeed;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether we've retried substitution, in which case we won't try
|
|
||||||
* again.
|
|
||||||
*/
|
|
||||||
bool retriedSubstitution = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The derivation stored at drvPath.
|
* The derivation stored at drvPath.
|
||||||
|
@ -217,7 +253,7 @@ struct DerivationGoal : public Goal
|
||||||
* Check that the derivation outputs all exist and register them
|
* Check that the derivation outputs all exist and register them
|
||||||
* as valid.
|
* as valid.
|
||||||
*/
|
*/
|
||||||
virtual DrvOutputs registerOutputs();
|
virtual SingleDrvOutputs registerOutputs();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open a log file and a pipe to it.
|
* Open a log file and a pipe to it.
|
||||||
|
@ -270,17 +306,17 @@ struct DerivationGoal : public Goal
|
||||||
* Update 'initialOutputs' to determine the current status of the
|
* Update 'initialOutputs' to determine the current status of the
|
||||||
* outputs of the derivation. Also returns a Boolean denoting
|
* outputs of the derivation. Also returns a Boolean denoting
|
||||||
* whether all outputs are valid and non-corrupt, and a
|
* whether all outputs are valid and non-corrupt, and a
|
||||||
* 'DrvOutputs' structure containing the valid and wanted
|
* 'SingleDrvOutputs' structure containing the valid and wanted
|
||||||
* outputs.
|
* outputs.
|
||||||
*/
|
*/
|
||||||
std::pair<bool, DrvOutputs> checkPathValidity();
|
std::pair<bool, SingleDrvOutputs> checkPathValidity();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Aborts if any output is not valid or corrupt, and otherwise
|
* Aborts if any output is not valid or corrupt, and otherwise
|
||||||
* returns a 'DrvOutputs' structure containing the wanted
|
* returns a 'SingleDrvOutputs' structure containing the wanted
|
||||||
* outputs.
|
* outputs.
|
||||||
*/
|
*/
|
||||||
DrvOutputs assertPathValidity();
|
SingleDrvOutputs assertPathValidity();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Forcibly kill the child process, if any.
|
* Forcibly kill the child process, if any.
|
||||||
|
@ -293,7 +329,7 @@ struct DerivationGoal : public Goal
|
||||||
|
|
||||||
void done(
|
void done(
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
DrvOutputs builtOutputs = {},
|
SingleDrvOutputs builtOutputs = {},
|
||||||
std::optional<Error> ex = {});
|
std::optional<Error> ex = {});
|
||||||
|
|
||||||
void waiteeDone(GoalPtr waitee, ExitCode result) override;
|
void waiteeDone(GoalPtr waitee, ExitCode result) override;
|
||||||
|
|
|
@ -10,16 +10,8 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
|
||||||
Worker worker(*this, evalStore ? *evalStore : *this);
|
Worker worker(*this, evalStore ? *evalStore : *this);
|
||||||
|
|
||||||
Goals goals;
|
Goals goals;
|
||||||
for (const auto & br : reqs) {
|
for (auto & br : reqs)
|
||||||
std::visit(overloaded {
|
goals.insert(worker.makeGoal(br, buildMode));
|
||||||
[&](const DerivedPath::Built & bfd) {
|
|
||||||
goals.insert(worker.makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode));
|
|
||||||
},
|
|
||||||
[&](const DerivedPath::Opaque & bo) {
|
|
||||||
goals.insert(worker.makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair));
|
|
||||||
},
|
|
||||||
}, br.raw());
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.run(goals);
|
worker.run(goals);
|
||||||
|
|
||||||
|
@ -47,7 +39,7 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<BuildResult> Store::buildPathsWithResults(
|
std::vector<KeyedBuildResult> Store::buildPathsWithResults(
|
||||||
const std::vector<DerivedPath> & reqs,
|
const std::vector<DerivedPath> & reqs,
|
||||||
BuildMode buildMode,
|
BuildMode buildMode,
|
||||||
std::shared_ptr<Store> evalStore)
|
std::shared_ptr<Store> evalStore)
|
||||||
|
@ -55,23 +47,23 @@ std::vector<BuildResult> Store::buildPathsWithResults(
|
||||||
Worker worker(*this, evalStore ? *evalStore : *this);
|
Worker worker(*this, evalStore ? *evalStore : *this);
|
||||||
|
|
||||||
Goals goals;
|
Goals goals;
|
||||||
for (const auto & br : reqs) {
|
std::vector<std::pair<const DerivedPath &, GoalPtr>> state;
|
||||||
std::visit(overloaded {
|
|
||||||
[&](const DerivedPath::Built & bfd) {
|
for (const auto & req : reqs) {
|
||||||
goals.insert(worker.makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode));
|
auto goal = worker.makeGoal(req, buildMode);
|
||||||
},
|
goals.insert(goal);
|
||||||
[&](const DerivedPath::Opaque & bo) {
|
state.push_back({req, goal});
|
||||||
goals.insert(worker.makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair));
|
|
||||||
},
|
|
||||||
}, br.raw());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worker.run(goals);
|
worker.run(goals);
|
||||||
|
|
||||||
std::vector<BuildResult> results;
|
std::vector<KeyedBuildResult> results;
|
||||||
|
|
||||||
for (auto & i : goals)
|
for (auto & [req, goalPtr] : state)
|
||||||
results.push_back(i->buildResult);
|
results.emplace_back(KeyedBuildResult {
|
||||||
|
goalPtr->getBuildResult(req),
|
||||||
|
/* .path = */ req,
|
||||||
|
});
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
@ -84,15 +76,14 @@ BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivat
|
||||||
|
|
||||||
try {
|
try {
|
||||||
worker.run(Goals{goal});
|
worker.run(Goals{goal});
|
||||||
return goal->buildResult;
|
return goal->getBuildResult(DerivedPath::Built {
|
||||||
|
.drvPath = drvPath,
|
||||||
|
.outputs = OutputsSpec::All {},
|
||||||
|
});
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
return BuildResult {
|
return BuildResult {
|
||||||
.status = BuildResult::MiscFailure,
|
.status = BuildResult::MiscFailure,
|
||||||
.errorMsg = e.msg(),
|
.errorMsg = e.msg(),
|
||||||
.path = DerivedPath::Built {
|
|
||||||
.drvPath = drvPath,
|
|
||||||
.outputs = OutputsSpec::All { },
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,29 @@ bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BuildResult Goal::getBuildResult(const DerivedPath & req) {
|
||||||
|
BuildResult res { buildResult };
|
||||||
|
|
||||||
|
if (auto pbp = std::get_if<DerivedPath::Built>(&req)) {
|
||||||
|
auto & bp = *pbp;
|
||||||
|
|
||||||
|
/* Because goals are in general shared between derived paths
|
||||||
|
that share the same derivation, we need to filter their
|
||||||
|
results to get back just the results we care about.
|
||||||
|
*/
|
||||||
|
|
||||||
|
for (auto it = res.builtOutputs.begin(); it != res.builtOutputs.end();) {
|
||||||
|
if (bp.outputs.contains(it->first))
|
||||||
|
++it;
|
||||||
|
else
|
||||||
|
it = res.builtOutputs.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void addToWeakGoals(WeakGoals & goals, GoalPtr p)
|
void addToWeakGoals(WeakGoals & goals, GoalPtr p)
|
||||||
{
|
{
|
||||||
if (goals.find(p) != goals.end())
|
if (goals.find(p) != goals.end())
|
||||||
|
|
|
@ -81,11 +81,26 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
||||||
*/
|
*/
|
||||||
ExitCode exitCode = ecBusy;
|
ExitCode exitCode = ecBusy;
|
||||||
|
|
||||||
|
protected:
|
||||||
/**
|
/**
|
||||||
* Build result.
|
* Build result.
|
||||||
*/
|
*/
|
||||||
BuildResult buildResult;
|
BuildResult buildResult;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Project a `BuildResult` with just the information that pertains
|
||||||
|
* to the given request.
|
||||||
|
*
|
||||||
|
* In general, goals may be aliased between multiple requests, and
|
||||||
|
* the stored `BuildResult` has information for the union of all
|
||||||
|
* requests. We don't want to leak what the other request are for
|
||||||
|
* sake of both privacy and determinism, and this "safe accessor"
|
||||||
|
* ensures we don't.
|
||||||
|
*/
|
||||||
|
BuildResult getBuildResult(const DerivedPath &);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exception containing an error message, if any.
|
* Exception containing an error message, if any.
|
||||||
*/
|
*/
|
||||||
|
@ -93,7 +108,6 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
||||||
|
|
||||||
Goal(Worker & worker, DerivedPath path)
|
Goal(Worker & worker, DerivedPath path)
|
||||||
: worker(worker)
|
: worker(worker)
|
||||||
, buildResult { .path = std::move(path) }
|
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
virtual ~Goal()
|
virtual ~Goal()
|
||||||
|
|
|
@ -1335,7 +1335,7 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
||||||
result.rethrow();
|
result.rethrow();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<BuildResult> buildPathsWithResults(
|
std::vector<KeyedBuildResult> buildPathsWithResults(
|
||||||
const std::vector<DerivedPath> & paths,
|
const std::vector<DerivedPath> & paths,
|
||||||
BuildMode buildMode = bmNormal,
|
BuildMode buildMode = bmNormal,
|
||||||
std::shared_ptr<Store> evalStore = nullptr) override
|
std::shared_ptr<Store> evalStore = nullptr) override
|
||||||
|
@ -2174,7 +2174,7 @@ void LocalDerivationGoal::runChild()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DrvOutputs LocalDerivationGoal::registerOutputs()
|
SingleDrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
{
|
{
|
||||||
/* When using a build hook, the build hook can register the output
|
/* When using a build hook, the build hook can register the output
|
||||||
as valid (by doing `nix-store --import'). If so we don't have
|
as valid (by doing `nix-store --import'). If so we don't have
|
||||||
|
@ -2395,27 +2395,26 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
auto rewriteRefs = [&]() -> std::pair<bool, StorePathSet> {
|
auto rewriteRefs = [&]() -> StoreReferences {
|
||||||
/* In the CA case, we need the rewritten refs to calculate the
|
/* In the CA case, we need the rewritten refs to calculate the
|
||||||
final path, therefore we look for a *non-rewritten
|
final path, therefore we look for a *non-rewritten
|
||||||
self-reference, and use a bool rather try to solve the
|
self-reference, and use a bool rather try to solve the
|
||||||
computationally intractable fixed point. */
|
computationally intractable fixed point. */
|
||||||
std::pair<bool, StorePathSet> res {
|
StoreReferences res {
|
||||||
false,
|
.self = false,
|
||||||
{},
|
|
||||||
};
|
};
|
||||||
for (auto & r : references) {
|
for (auto & r : references) {
|
||||||
auto name = r.name();
|
auto name = r.name();
|
||||||
auto origHash = std::string { r.hashPart() };
|
auto origHash = std::string { r.hashPart() };
|
||||||
if (r == *scratchPath) {
|
if (r == *scratchPath) {
|
||||||
res.first = true;
|
res.self = true;
|
||||||
} else if (auto outputRewrite = get(outputRewrites, origHash)) {
|
} else if (auto outputRewrite = get(outputRewrites, origHash)) {
|
||||||
std::string newRef = *outputRewrite;
|
std::string newRef = *outputRewrite;
|
||||||
newRef += '-';
|
newRef += '-';
|
||||||
newRef += name;
|
newRef += name;
|
||||||
res.second.insert(StorePath { newRef });
|
res.others.insert(StorePath { newRef });
|
||||||
} else {
|
} else {
|
||||||
res.second.insert(r);
|
res.others.insert(r);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
|
@ -2448,18 +2447,22 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
auto got = caSink.finish().first;
|
auto got = caSink.finish().first;
|
||||||
auto refs = rewriteRefs();
|
ValidPathInfo newInfo0 {
|
||||||
|
worker.store,
|
||||||
auto finalPath = worker.store.makeFixedOutputPath(
|
outputPathName(drv->name, outputName),
|
||||||
outputHash.method,
|
FixedOutputInfo {
|
||||||
got,
|
.hash = {
|
||||||
outputPathName(drv->name, outputName),
|
.method = outputHash.method,
|
||||||
refs.second,
|
.hash = got,
|
||||||
refs.first);
|
},
|
||||||
if (*scratchPath != finalPath) {
|
.references = rewriteRefs(),
|
||||||
|
},
|
||||||
|
Hash::dummy,
|
||||||
|
};
|
||||||
|
if (*scratchPath != newInfo0.path) {
|
||||||
// Also rewrite the output path
|
// Also rewrite the output path
|
||||||
auto source = sinkToSource([&](Sink & nextSink) {
|
auto source = sinkToSource([&](Sink & nextSink) {
|
||||||
RewritingSink rsink2(oldHashPart, std::string(finalPath.hashPart()), nextSink);
|
RewritingSink rsink2(oldHashPart, std::string(newInfo0.path.hashPart()), nextSink);
|
||||||
dumpPath(actualPath, rsink2);
|
dumpPath(actualPath, rsink2);
|
||||||
rsink2.flush();
|
rsink2.flush();
|
||||||
});
|
});
|
||||||
|
@ -2470,19 +2473,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
}
|
}
|
||||||
|
|
||||||
HashResult narHashAndSize = hashPath(htSHA256, actualPath);
|
HashResult narHashAndSize = hashPath(htSHA256, actualPath);
|
||||||
ValidPathInfo newInfo0 {
|
newInfo0.narHash = narHashAndSize.first;
|
||||||
finalPath,
|
|
||||||
narHashAndSize.first,
|
|
||||||
};
|
|
||||||
|
|
||||||
newInfo0.narSize = narHashAndSize.second;
|
newInfo0.narSize = narHashAndSize.second;
|
||||||
newInfo0.ca = FixedOutputHash {
|
|
||||||
.method = outputHash.method,
|
|
||||||
.hash = got,
|
|
||||||
};
|
|
||||||
newInfo0.references = refs.second;
|
|
||||||
if (refs.first)
|
|
||||||
newInfo0.references.insert(newInfo0.path);
|
|
||||||
|
|
||||||
assert(newInfo0.ca);
|
assert(newInfo0.ca);
|
||||||
return newInfo0;
|
return newInfo0;
|
||||||
|
@ -2504,8 +2496,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
ValidPathInfo newInfo0 { requiredFinalPath, narHashAndSize.first };
|
ValidPathInfo newInfo0 { requiredFinalPath, narHashAndSize.first };
|
||||||
newInfo0.narSize = narHashAndSize.second;
|
newInfo0.narSize = narHashAndSize.second;
|
||||||
auto refs = rewriteRefs();
|
auto refs = rewriteRefs();
|
||||||
newInfo0.references = refs.second;
|
newInfo0.references = std::move(refs.others);
|
||||||
if (refs.first)
|
if (refs.self)
|
||||||
newInfo0.references.insert(newInfo0.path);
|
newInfo0.references.insert(newInfo0.path);
|
||||||
return newInfo0;
|
return newInfo0;
|
||||||
},
|
},
|
||||||
|
@ -2519,7 +2511,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
/* Check wanted hash */
|
/* Check wanted hash */
|
||||||
const Hash & wanted = dof.hash.hash;
|
const Hash & wanted = dof.hash.hash;
|
||||||
assert(newInfo0.ca);
|
assert(newInfo0.ca);
|
||||||
auto got = getContentAddressHash(*newInfo0.ca);
|
auto got = newInfo0.ca->getHash();
|
||||||
if (wanted != got) {
|
if (wanted != got) {
|
||||||
/* Throw an error after registering the path as
|
/* Throw an error after registering the path as
|
||||||
valid. */
|
valid. */
|
||||||
|
@ -2691,7 +2683,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
means it's safe to link the derivation to the output hash. We must do
|
means it's safe to link the derivation to the output hash. We must do
|
||||||
that for floating CA derivations, which otherwise couldn't be cached,
|
that for floating CA derivations, which otherwise couldn't be cached,
|
||||||
but it's fine to do in all cases. */
|
but it's fine to do in all cases. */
|
||||||
DrvOutputs builtOutputs;
|
SingleDrvOutputs builtOutputs;
|
||||||
|
|
||||||
for (auto & [outputName, newInfo] : infos) {
|
for (auto & [outputName, newInfo] : infos) {
|
||||||
auto oldinfo = get(initialOutputs, outputName);
|
auto oldinfo = get(initialOutputs, outputName);
|
||||||
|
@ -2710,7 +2702,7 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
worker.store.registerDrvOutput(thisRealisation);
|
worker.store.registerDrvOutput(thisRealisation);
|
||||||
}
|
}
|
||||||
if (wantedOutputs.contains(outputName))
|
if (wantedOutputs.contains(outputName))
|
||||||
builtOutputs.emplace(thisRealisation.id, thisRealisation);
|
builtOutputs.emplace(outputName, thisRealisation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builtOutputs;
|
return builtOutputs;
|
||||||
|
|
|
@ -237,7 +237,7 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||||
* Check that the derivation outputs all exist and register them
|
* Check that the derivation outputs all exist and register them
|
||||||
* as valid.
|
* as valid.
|
||||||
*/
|
*/
|
||||||
DrvOutputs registerOutputs() override;
|
SingleDrvOutputs registerOutputs() override;
|
||||||
|
|
||||||
void signRealisation(Realisation &) override;
|
void signRealisation(Realisation &) override;
|
||||||
|
|
||||||
|
|
|
@ -95,7 +95,9 @@ void PathSubstitutionGoal::tryNext()
|
||||||
subs.pop_front();
|
subs.pop_front();
|
||||||
|
|
||||||
if (ca) {
|
if (ca) {
|
||||||
subPath = sub->makeFixedOutputPathFromCA(storePath.name(), *ca);
|
subPath = sub->makeFixedOutputPathFromCA(
|
||||||
|
std::string { storePath.name() },
|
||||||
|
ContentAddressWithReferences::withoutRefs(*ca));
|
||||||
if (sub->storeDir == worker.store.storeDir)
|
if (sub->storeDir == worker.store.storeDir)
|
||||||
assert(subPath == storePath);
|
assert(subPath == storePath);
|
||||||
} else if (sub->storeDir != worker.store.storeDir) {
|
} else if (sub->storeDir != worker.store.storeDir) {
|
||||||
|
|
|
@ -92,6 +92,7 @@ std::shared_ptr<PathSubstitutionGoal> Worker::makePathSubstitutionGoal(const Sto
|
||||||
return goal;
|
return goal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::shared_ptr<DrvOutputSubstitutionGoal> Worker::makeDrvOutputSubstitutionGoal(const DrvOutput& id, RepairFlag repair, std::optional<ContentAddress> ca)
|
std::shared_ptr<DrvOutputSubstitutionGoal> Worker::makeDrvOutputSubstitutionGoal(const DrvOutput& id, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||||
{
|
{
|
||||||
std::weak_ptr<DrvOutputSubstitutionGoal> & goal_weak = drvOutputSubstitutionGoals[id];
|
std::weak_ptr<DrvOutputSubstitutionGoal> & goal_weak = drvOutputSubstitutionGoals[id];
|
||||||
|
@ -104,6 +105,20 @@ std::shared_ptr<DrvOutputSubstitutionGoal> Worker::makeDrvOutputSubstitutionGoal
|
||||||
return goal;
|
return goal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
GoalPtr Worker::makeGoal(const DerivedPath & req, BuildMode buildMode)
|
||||||
|
{
|
||||||
|
return std::visit(overloaded {
|
||||||
|
[&](const DerivedPath::Built & bfd) -> GoalPtr {
|
||||||
|
return makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode);
|
||||||
|
},
|
||||||
|
[&](const DerivedPath::Opaque & bo) -> GoalPtr {
|
||||||
|
return makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair);
|
||||||
|
},
|
||||||
|
}, req.raw());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
template<typename K, typename G>
|
template<typename K, typename G>
|
||||||
static void removeGoal(std::shared_ptr<G> goal, std::map<K, std::weak_ptr<G>> & goalMap)
|
static void removeGoal(std::shared_ptr<G> goal, std::map<K, std::weak_ptr<G>> & goalMap)
|
||||||
{
|
{
|
||||||
|
|
|
@ -181,7 +181,7 @@ public:
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* derivation goal
|
* @ref DerivationGoal "derivation goal"
|
||||||
*/
|
*/
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
|
std::shared_ptr<DerivationGoal> makeDerivationGoalCommon(
|
||||||
|
@ -196,11 +196,19 @@ public:
|
||||||
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
const OutputsSpec & wantedOutputs, BuildMode buildMode = bmNormal);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* substitution goal
|
* @ref SubstitutionGoal "substitution goal"
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
std::shared_ptr<PathSubstitutionGoal> makePathSubstitutionGoal(const StorePath & storePath, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||||
std::shared_ptr<DrvOutputSubstitutionGoal> makeDrvOutputSubstitutionGoal(const DrvOutput & id, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
std::shared_ptr<DrvOutputSubstitutionGoal> makeDrvOutputSubstitutionGoal(const DrvOutput & id, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make a goal corresponding to the `DerivedPath`.
|
||||||
|
*
|
||||||
|
* It will be a `DerivationGoal` for a `DerivedPath::Built` or
|
||||||
|
* a `SubstitutionGoal` for a `DerivedPath::Opaque`.
|
||||||
|
*/
|
||||||
|
GoalPtr makeGoal(const DerivedPath & req, BuildMode buildMode = bmNormal);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove a dead goal.
|
* Remove a dead goal.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -9,7 +9,7 @@ std::string FixedOutputHash::printMethodAlgo() const
|
||||||
return makeFileIngestionPrefix(method) + printHashType(hash.type);
|
return makeFileIngestionPrefix(method) + printHashType(hash.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string makeFileIngestionPrefix(const FileIngestionMethod m)
|
std::string makeFileIngestionPrefix(FileIngestionMethod m)
|
||||||
{
|
{
|
||||||
switch (m) {
|
switch (m) {
|
||||||
case FileIngestionMethod::Flat:
|
case FileIngestionMethod::Flat:
|
||||||
|
@ -21,39 +21,35 @@ std::string makeFileIngestionPrefix(const FileIngestionMethod m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string makeFixedOutputCA(FileIngestionMethod method, const Hash & hash)
|
std::string ContentAddress::render() const
|
||||||
{
|
|
||||||
return "fixed:"
|
|
||||||
+ makeFileIngestionPrefix(method)
|
|
||||||
+ hash.to_string(Base32, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string renderContentAddress(ContentAddress ca)
|
|
||||||
{
|
{
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[](TextHash & th) {
|
[](const TextHash & th) {
|
||||||
return "text:" + th.hash.to_string(Base32, true);
|
return "text:"
|
||||||
|
+ th.hash.to_string(Base32, true);
|
||||||
},
|
},
|
||||||
[](FixedOutputHash & fsh) {
|
[](const FixedOutputHash & fsh) {
|
||||||
return makeFixedOutputCA(fsh.method, fsh.hash);
|
return "fixed:"
|
||||||
|
+ makeFileIngestionPrefix(fsh.method)
|
||||||
|
+ fsh.hash.to_string(Base32, true);
|
||||||
}
|
}
|
||||||
}, ca);
|
}, raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string renderContentAddressMethod(ContentAddressMethod cam)
|
std::string ContentAddressMethod::render() const
|
||||||
{
|
{
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[](TextHashMethod & th) {
|
[](const TextHashMethod & th) {
|
||||||
return std::string{"text:"} + printHashType(htSHA256);
|
return std::string{"text:"} + printHashType(htSHA256);
|
||||||
},
|
},
|
||||||
[](FixedOutputHashMethod & fshm) {
|
[](const FixedOutputHashMethod & fshm) {
|
||||||
return "fixed:" + makeFileIngestionPrefix(fshm.fileIngestionMethod) + printHashType(fshm.hashType);
|
return "fixed:" + makeFileIngestionPrefix(fshm.fileIngestionMethod) + printHashType(fshm.hashType);
|
||||||
}
|
}
|
||||||
}, cam);
|
}, raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
Parses content address strings up to the hash.
|
* Parses content address strings up to the hash.
|
||||||
*/
|
*/
|
||||||
static ContentAddressMethod parseContentAddressMethodPrefix(std::string_view & rest)
|
static ContentAddressMethod parseContentAddressMethodPrefix(std::string_view & rest)
|
||||||
{
|
{
|
||||||
|
@ -97,7 +93,7 @@ static ContentAddressMethod parseContentAddressMethodPrefix(std::string_view & r
|
||||||
throw UsageError("content address prefix '%s' is unrecognized. Recogonized prefixes are 'text' or 'fixed'", prefix);
|
throw UsageError("content address prefix '%s' is unrecognized. Recogonized prefixes are 'text' or 'fixed'", prefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentAddress parseContentAddress(std::string_view rawCa) {
|
ContentAddress ContentAddress::parse(std::string_view rawCa) {
|
||||||
auto rest = rawCa;
|
auto rest = rawCa;
|
||||||
|
|
||||||
ContentAddressMethod caMethod = parseContentAddressMethodPrefix(rest);
|
ContentAddressMethod caMethod = parseContentAddressMethodPrefix(rest);
|
||||||
|
@ -115,10 +111,10 @@ ContentAddress parseContentAddress(std::string_view rawCa) {
|
||||||
.hash = Hash::parseNonSRIUnprefixed(rest, std::move(fohMethod.hashType)),
|
.hash = Hash::parseNonSRIUnprefixed(rest, std::move(fohMethod.hashType)),
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
}, caMethod);
|
}, caMethod.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentAddressMethod parseContentAddressMethod(std::string_view caMethod)
|
ContentAddressMethod ContentAddressMethod::parse(std::string_view caMethod)
|
||||||
{
|
{
|
||||||
std::string asPrefix = std::string{caMethod} + ":";
|
std::string asPrefix = std::string{caMethod} + ":";
|
||||||
// parseContentAddressMethodPrefix takes its argument by reference
|
// parseContentAddressMethodPrefix takes its argument by reference
|
||||||
|
@ -126,26 +122,55 @@ ContentAddressMethod parseContentAddressMethod(std::string_view caMethod)
|
||||||
return parseContentAddressMethodPrefix(asPrefixView);
|
return parseContentAddressMethodPrefix(asPrefixView);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<ContentAddress> parseContentAddressOpt(std::string_view rawCaOpt)
|
std::optional<ContentAddress> ContentAddress::parseOpt(std::string_view rawCaOpt)
|
||||||
{
|
{
|
||||||
return rawCaOpt == "" ? std::optional<ContentAddress>() : parseContentAddress(rawCaOpt);
|
return rawCaOpt == ""
|
||||||
|
? std::nullopt
|
||||||
|
: std::optional { ContentAddress::parse(rawCaOpt) };
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string renderContentAddress(std::optional<ContentAddress> ca)
|
std::string renderContentAddress(std::optional<ContentAddress> ca)
|
||||||
{
|
{
|
||||||
return ca ? renderContentAddress(*ca) : "";
|
return ca ? ca->render() : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
Hash getContentAddressHash(const ContentAddress & ca)
|
const Hash & ContentAddress::getHash() const
|
||||||
{
|
{
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[](const TextHash & th) {
|
[](const TextHash & th) -> auto & {
|
||||||
return th.hash;
|
return th.hash;
|
||||||
},
|
},
|
||||||
[](const FixedOutputHash & fsh) {
|
[](const FixedOutputHash & fsh) -> auto & {
|
||||||
return fsh.hash;
|
return fsh.hash;
|
||||||
}
|
},
|
||||||
}, ca);
|
}, raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StoreReferences::empty() const
|
||||||
|
{
|
||||||
|
return !self && others.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t StoreReferences::size() const
|
||||||
|
{
|
||||||
|
return (self ? 1 : 0) + others.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentAddressWithReferences ContentAddressWithReferences::withoutRefs(const ContentAddress & ca) {
|
||||||
|
return std::visit(overloaded {
|
||||||
|
[&](const TextHash & h) -> ContentAddressWithReferences {
|
||||||
|
return TextInfo {
|
||||||
|
.hash = h,
|
||||||
|
.references = {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
[&](const FixedOutputHash & h) -> ContentAddressWithReferences {
|
||||||
|
return FixedOutputInfo {
|
||||||
|
.hash = h,
|
||||||
|
.references = {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}, ca.raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,30 @@
|
||||||
|
|
||||||
#include <variant>
|
#include <variant>
|
||||||
#include "hash.hh"
|
#include "hash.hh"
|
||||||
|
#include "path.hh"
|
||||||
#include "comparator.hh"
|
#include "comparator.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Content addressing method
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* We only have one way to hash text with references, so this is a single-value
|
||||||
|
type, mainly useful with std::variant.
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An enumeration of the ways we can serialize file system objects.
|
* The single way we can serialize "text" file system objects.
|
||||||
|
*
|
||||||
|
* Somewhat obscure, used by \ref Derivation derivations and
|
||||||
|
* `builtins.toFile` currently.
|
||||||
|
*/
|
||||||
|
struct TextHashMethod : std::monostate { };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration of the main ways we can serialize file system
|
||||||
|
* objects.
|
||||||
*/
|
*/
|
||||||
enum struct FileIngestionMethod : uint8_t {
|
enum struct FileIngestionMethod : uint8_t {
|
||||||
/**
|
/**
|
||||||
|
@ -22,6 +40,53 @@ enum struct FileIngestionMethod : uint8_t {
|
||||||
Recursive = true
|
Recursive = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the prefix to the hash algorithm which indicates how the
|
||||||
|
* files were ingested.
|
||||||
|
*/
|
||||||
|
std::string makeFileIngestionPrefix(FileIngestionMethod m);
|
||||||
|
|
||||||
|
struct FixedOutputHashMethod {
|
||||||
|
FileIngestionMethod fileIngestionMethod;
|
||||||
|
HashType hashType;
|
||||||
|
|
||||||
|
GENERATE_CMP(FixedOutputHashMethod, me->fileIngestionMethod, me->hashType);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An enumeration of all the ways we can serialize file system objects.
|
||||||
|
*
|
||||||
|
* Just the type of a content address. Combine with the hash itself, and
|
||||||
|
* we have a `ContentAddress` as defined below. Combine that, in turn,
|
||||||
|
* with info on references, and we have `ContentAddressWithReferences`,
|
||||||
|
* as defined further below.
|
||||||
|
*/
|
||||||
|
struct ContentAddressMethod
|
||||||
|
{
|
||||||
|
typedef std::variant<
|
||||||
|
TextHashMethod,
|
||||||
|
FixedOutputHashMethod
|
||||||
|
> Raw;
|
||||||
|
|
||||||
|
Raw raw;
|
||||||
|
|
||||||
|
GENERATE_CMP(ContentAddressMethod, me->raw);
|
||||||
|
|
||||||
|
/* The moral equivalent of `using Raw::Raw;` */
|
||||||
|
ContentAddressMethod(auto &&... arg)
|
||||||
|
: raw(std::forward<decltype(arg)>(arg)...)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
static ContentAddressMethod parse(std::string_view rawCaMethod);
|
||||||
|
|
||||||
|
std::string render() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mini content address
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Somewhat obscure, used by \ref Derivation derivations and
|
* Somewhat obscure, used by \ref Derivation derivations and
|
||||||
* `builtins.toFile` currently.
|
* `builtins.toFile` currently.
|
||||||
|
@ -36,7 +101,7 @@ struct TextHash {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For path computed by makeFixedOutputPath.
|
* Used by most store objects that are content-addressed.
|
||||||
*/
|
*/
|
||||||
struct FixedOutputHash {
|
struct FixedOutputHash {
|
||||||
/**
|
/**
|
||||||
|
@ -65,41 +130,96 @@ struct FixedOutputHash {
|
||||||
* - ‘fixed:<r?>:<ht>:<h>’: For paths computed by
|
* - ‘fixed:<r?>:<ht>:<h>’: For paths computed by
|
||||||
* Store::makeFixedOutputPath() / Store::addToStore().
|
* Store::makeFixedOutputPath() / Store::addToStore().
|
||||||
*/
|
*/
|
||||||
typedef std::variant<
|
struct ContentAddress
|
||||||
TextHash,
|
{
|
||||||
FixedOutputHash
|
typedef std::variant<
|
||||||
> ContentAddress;
|
TextHash,
|
||||||
|
FixedOutputHash
|
||||||
|
> Raw;
|
||||||
|
|
||||||
/**
|
Raw raw;
|
||||||
* Compute the prefix to the hash algorithm which indicates how the
|
|
||||||
* files were ingested.
|
|
||||||
*/
|
|
||||||
std::string makeFileIngestionPrefix(const FileIngestionMethod m);
|
|
||||||
|
|
||||||
/**
|
GENERATE_CMP(ContentAddress, me->raw);
|
||||||
* Compute the content-addressability assertion (ValidPathInfo::ca) for
|
|
||||||
* paths created by Store::makeFixedOutputPath() / Store::addToStore().
|
|
||||||
*/
|
|
||||||
std::string makeFixedOutputCA(FileIngestionMethod method, const Hash & hash);
|
|
||||||
|
|
||||||
std::string renderContentAddress(ContentAddress ca);
|
/* The moral equivalent of `using Raw::Raw;` */
|
||||||
|
ContentAddress(auto &&... arg)
|
||||||
|
: raw(std::forward<decltype(arg)>(arg)...)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compute the content-addressability assertion (ValidPathInfo::ca) for
|
||||||
|
* paths created by Store::makeFixedOutputPath() / Store::addToStore().
|
||||||
|
*/
|
||||||
|
std::string render() const;
|
||||||
|
|
||||||
|
static ContentAddress parse(std::string_view rawCa);
|
||||||
|
|
||||||
|
static std::optional<ContentAddress> parseOpt(std::string_view rawCaOpt);
|
||||||
|
|
||||||
|
const Hash & getHash() const;
|
||||||
|
};
|
||||||
|
|
||||||
std::string renderContentAddress(std::optional<ContentAddress> ca);
|
std::string renderContentAddress(std::optional<ContentAddress> ca);
|
||||||
|
|
||||||
ContentAddress parseContentAddress(std::string_view rawCa);
|
|
||||||
|
|
||||||
std::optional<ContentAddress> parseContentAddressOpt(std::string_view rawCaOpt);
|
|
||||||
|
|
||||||
Hash getContentAddressHash(const ContentAddress & ca);
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
We only have one way to hash text with references, so this is single-value
|
* Full content address
|
||||||
type is only useful in std::variant.
|
*
|
||||||
*/
|
* See the schema for store paths in store-api.cc
|
||||||
struct TextHashMethod { };
|
*/
|
||||||
struct FixedOutputHashMethod {
|
|
||||||
FileIngestionMethod fileIngestionMethod;
|
/**
|
||||||
HashType hashType;
|
* A set of references to other store objects.
|
||||||
|
*
|
||||||
|
* References to other store objects are tracked with store paths, self
|
||||||
|
* references however are tracked with a boolean.
|
||||||
|
*/
|
||||||
|
struct StoreReferences {
|
||||||
|
/**
|
||||||
|
* References to other store objects
|
||||||
|
*/
|
||||||
|
StorePathSet others;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to this store object
|
||||||
|
*/
|
||||||
|
bool self = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true iff no references, i.e. others is empty and self is
|
||||||
|
* false.
|
||||||
|
*/
|
||||||
|
bool empty() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the numbers of references, i.e. the size of others + 1
|
||||||
|
* iff self is true.
|
||||||
|
*/
|
||||||
|
size_t size() const;
|
||||||
|
|
||||||
|
GENERATE_CMP(StoreReferences, me->self, me->others);
|
||||||
|
};
|
||||||
|
|
||||||
|
// This matches the additional info that we need for makeTextPath
|
||||||
|
struct TextInfo {
|
||||||
|
TextHash hash;
|
||||||
|
/**
|
||||||
|
* References to other store objects only; self references
|
||||||
|
* disallowed
|
||||||
|
*/
|
||||||
|
StorePathSet references;
|
||||||
|
|
||||||
|
GENERATE_CMP(TextInfo, me->hash, me->references);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct FixedOutputInfo {
|
||||||
|
FixedOutputHash hash;
|
||||||
|
/**
|
||||||
|
* References to other store objects or this one.
|
||||||
|
*/
|
||||||
|
StoreReferences references;
|
||||||
|
|
||||||
|
GENERATE_CMP(FixedOutputInfo, me->hash, me->references);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -107,13 +227,27 @@ struct FixedOutputHashMethod {
|
||||||
*
|
*
|
||||||
* A ContentAddress without a Hash.
|
* A ContentAddress without a Hash.
|
||||||
*/
|
*/
|
||||||
typedef std::variant<
|
struct ContentAddressWithReferences
|
||||||
TextHashMethod,
|
{
|
||||||
FixedOutputHashMethod
|
typedef std::variant<
|
||||||
> ContentAddressMethod;
|
TextInfo,
|
||||||
|
FixedOutputInfo
|
||||||
|
> Raw;
|
||||||
|
|
||||||
ContentAddressMethod parseContentAddressMethod(std::string_view rawCaMethod);
|
Raw raw;
|
||||||
|
|
||||||
std::string renderContentAddressMethod(ContentAddressMethod caMethod);
|
GENERATE_CMP(ContentAddressWithReferences, me->raw);
|
||||||
|
|
||||||
|
/* The moral equivalent of `using Raw::Raw;` */
|
||||||
|
ContentAddressWithReferences(auto &&... arg)
|
||||||
|
: raw(std::forward<decltype(arg)>(arg)...)
|
||||||
|
{ }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a ContentAddressWithReferences from a mere ContentAddress, by
|
||||||
|
* assuming no references in all cases.
|
||||||
|
*/
|
||||||
|
static ContentAddressWithReferences withoutRefs(const ContentAddress &);
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,21 +401,21 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
auto pathInfo = [&]() {
|
auto pathInfo = [&]() {
|
||||||
// NB: FramedSource must be out of scope before logger->stopWork();
|
// NB: FramedSource must be out of scope before logger->stopWork();
|
||||||
ContentAddressMethod contentAddressMethod = parseContentAddressMethod(camStr);
|
ContentAddressMethod contentAddressMethod = ContentAddressMethod::parse(camStr);
|
||||||
FramedSource source(from);
|
FramedSource source(from);
|
||||||
// TODO this is essentially RemoteStore::addCAToStore. Move it up to Store.
|
// TODO this is essentially RemoteStore::addCAToStore. Move it up to Store.
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[&](TextHashMethod &) {
|
[&](const TextHashMethod &) {
|
||||||
// We could stream this by changing Store
|
// We could stream this by changing Store
|
||||||
std::string contents = source.drain();
|
std::string contents = source.drain();
|
||||||
auto path = store->addTextToStore(name, contents, refs, repair);
|
auto path = store->addTextToStore(name, contents, refs, repair);
|
||||||
return store->queryPathInfo(path);
|
return store->queryPathInfo(path);
|
||||||
},
|
},
|
||||||
[&](FixedOutputHashMethod & fohm) {
|
[&](const FixedOutputHashMethod & fohm) {
|
||||||
auto path = store->addToStoreFromDump(source, name, fohm.fileIngestionMethod, fohm.hashType, repair, refs);
|
auto path = store->addToStoreFromDump(source, name, fohm.fileIngestionMethod, fohm.hashType, repair, refs);
|
||||||
return store->queryPathInfo(path);
|
return store->queryPathInfo(path);
|
||||||
},
|
},
|
||||||
}, contentAddressMethod);
|
}, contentAddressMethod.raw);
|
||||||
}();
|
}();
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
|
|
||||||
|
@ -637,7 +637,10 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
to << res.timesBuilt << res.isNonDeterministic << res.startTime << res.stopTime;
|
to << res.timesBuilt << res.isNonDeterministic << res.startTime << res.stopTime;
|
||||||
}
|
}
|
||||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 28) {
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 28) {
|
||||||
worker_proto::write(*store, to, res.builtOutputs);
|
DrvOutputs builtOutputs;
|
||||||
|
for (auto & [output, realisation] : res.builtOutputs)
|
||||||
|
builtOutputs.insert_or_assign(realisation.id, realisation);
|
||||||
|
worker_proto::write(*store, to, builtOutputs);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -880,7 +883,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
info.references = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
info.references = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||||
from >> info.registrationTime >> info.narSize >> info.ultimate;
|
from >> info.registrationTime >> info.narSize >> info.ultimate;
|
||||||
info.sigs = readStrings<StringSet>(from);
|
info.sigs = readStrings<StringSet>(from);
|
||||||
info.ca = parseContentAddressOpt(readString(from));
|
info.ca = ContentAddress::parseOpt(readString(from));
|
||||||
from >> repair >> dontCheckSigs;
|
from >> repair >> dontCheckSigs;
|
||||||
if (!trusted && dontCheckSigs)
|
if (!trusted && dontCheckSigs)
|
||||||
dontCheckSigs = false;
|
dontCheckSigs = false;
|
||||||
|
@ -1064,6 +1067,8 @@ void processConnection(
|
||||||
|
|
||||||
opCount++;
|
opCount++;
|
||||||
|
|
||||||
|
debug("performing daemon worker op: %d", op);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
performOp(tunnelLogger, store, trusted, recursive, clientVersion, from, to, op);
|
performOp(tunnelLogger, store, trusted, recursive, clientVersion, from, to, op);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
|
|
|
@ -36,8 +36,8 @@ std::optional<StorePath> DerivationOutput::path(const Store & store, std::string
|
||||||
StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const
|
StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const
|
||||||
{
|
{
|
||||||
return store.makeFixedOutputPath(
|
return store.makeFixedOutputPath(
|
||||||
hash.method, hash.hash,
|
outputPathName(drvName, outputName),
|
||||||
outputPathName(drvName, outputName));
|
{ hash, {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -313,6 +313,15 @@ Derivation parseDerivation(const Store & store, std::string && s, std::string_vi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Print a derivation string literal to an `std::string`.
|
||||||
|
*
|
||||||
|
* This syntax does not generalize to the expression language, which needs to
|
||||||
|
* escape `$`.
|
||||||
|
*
|
||||||
|
* @param res Where to print to
|
||||||
|
* @param s Which logical string to print
|
||||||
|
*/
|
||||||
static void printString(std::string & res, std::string_view s)
|
static void printString(std::string & res, std::string_view s)
|
||||||
{
|
{
|
||||||
boost::container::small_vector<char, 64 * 1024> buffer;
|
boost::container::small_vector<char, 64 * 1024> buffer;
|
||||||
|
@ -933,7 +942,7 @@ void Derivation::checkInvariants(Store & store, const StorePath & drvPath) const
|
||||||
envHasRightPath(doia.path, i.first);
|
envHasRightPath(doia.path, i.first);
|
||||||
},
|
},
|
||||||
[&](const DerivationOutput::CAFixed & dof) {
|
[&](const DerivationOutput::CAFixed & dof) {
|
||||||
StorePath path = store.makeFixedOutputPath(dof.hash.method, dof.hash.hash, drvName);
|
StorePath path = store.makeFixedOutputPath(drvName, { dof.hash, {} });
|
||||||
envHasRightPath(path, i.first);
|
envHasRightPath(path, i.first);
|
||||||
},
|
},
|
||||||
[&](const DerivationOutput::CAFloating &) {
|
[&](const DerivationOutput::CAFloating &) {
|
||||||
|
@ -980,7 +989,8 @@ nlohmann::json DerivationOutput::toJSON(
|
||||||
|
|
||||||
DerivationOutput DerivationOutput::fromJSON(
|
DerivationOutput DerivationOutput::fromJSON(
|
||||||
const Store & store, std::string_view drvName, std::string_view outputName,
|
const Store & store, std::string_view drvName, std::string_view outputName,
|
||||||
const nlohmann::json & _json)
|
const nlohmann::json & _json,
|
||||||
|
const ExperimentalFeatureSettings & xpSettings)
|
||||||
{
|
{
|
||||||
std::set<std::string_view> keys;
|
std::set<std::string_view> keys;
|
||||||
auto json = (std::map<std::string, nlohmann::json>) _json;
|
auto json = (std::map<std::string, nlohmann::json>) _json;
|
||||||
|
@ -1019,6 +1029,7 @@ DerivationOutput DerivationOutput::fromJSON(
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (keys == (std::set<std::string_view> { "hashAlgo" })) {
|
else if (keys == (std::set<std::string_view> { "hashAlgo" })) {
|
||||||
|
xpSettings.require(Xp::CaDerivations);
|
||||||
auto [method, hashType] = methodAlgo();
|
auto [method, hashType] = methodAlgo();
|
||||||
return DerivationOutput::CAFloating {
|
return DerivationOutput::CAFloating {
|
||||||
.method = method,
|
.method = method,
|
||||||
|
@ -1031,6 +1042,7 @@ DerivationOutput DerivationOutput::fromJSON(
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (keys == (std::set<std::string_view> { "hashAlgo", "impure" })) {
|
else if (keys == (std::set<std::string_view> { "hashAlgo", "impure" })) {
|
||||||
|
xpSettings.require(Xp::ImpureDerivations);
|
||||||
auto [method, hashType] = methodAlgo();
|
auto [method, hashType] = methodAlgo();
|
||||||
return DerivationOutput::Impure {
|
return DerivationOutput::Impure {
|
||||||
.method = method,
|
.method = method,
|
||||||
|
|
|
@ -136,11 +136,15 @@ struct DerivationOutput : _DerivationOutputRaw
|
||||||
const Store & store,
|
const Store & store,
|
||||||
std::string_view drvName,
|
std::string_view drvName,
|
||||||
std::string_view outputName) const;
|
std::string_view outputName) const;
|
||||||
|
/**
|
||||||
|
* @param xpSettings Stop-gap to avoid globals during unit tests.
|
||||||
|
*/
|
||||||
static DerivationOutput fromJSON(
|
static DerivationOutput fromJSON(
|
||||||
const Store & store,
|
const Store & store,
|
||||||
std::string_view drvName,
|
std::string_view drvName,
|
||||||
std::string_view outputName,
|
std::string_view outputName,
|
||||||
const nlohmann::json & json);
|
const nlohmann::json & json,
|
||||||
|
const ExperimentalFeatureSettings & xpSettings = experimentalFeatureSettings);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::map<std::string, DerivationOutput> DerivationOutputs;
|
typedef std::map<std::string, DerivationOutput> DerivationOutputs;
|
||||||
|
|
|
@ -62,15 +62,31 @@ std::string DerivedPath::Opaque::to_string(const Store & store) const
|
||||||
std::string DerivedPath::Built::to_string(const Store & store) const
|
std::string DerivedPath::Built::to_string(const Store & store) const
|
||||||
{
|
{
|
||||||
return store.printStorePath(drvPath)
|
return store.printStorePath(drvPath)
|
||||||
+ "!"
|
+ '^'
|
||||||
|
+ outputs.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DerivedPath::Built::to_string_legacy(const Store & store) const
|
||||||
|
{
|
||||||
|
return store.printStorePath(drvPath)
|
||||||
|
+ '!'
|
||||||
+ outputs.to_string();
|
+ outputs.to_string();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string DerivedPath::to_string(const Store & store) const
|
std::string DerivedPath::to_string(const Store & store) const
|
||||||
{
|
{
|
||||||
return std::visit(
|
return std::visit(overloaded {
|
||||||
[&](const auto & req) { return req.to_string(store); },
|
[&](const DerivedPath::Built & req) { return req.to_string(store); },
|
||||||
this->raw());
|
[&](const DerivedPath::Opaque & req) { return req.to_string(store); },
|
||||||
|
}, this->raw());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string DerivedPath::to_string_legacy(const Store & store) const
|
||||||
|
{
|
||||||
|
return std::visit(overloaded {
|
||||||
|
[&](const DerivedPath::Built & req) { return req.to_string_legacy(store); },
|
||||||
|
[&](const DerivedPath::Opaque & req) { return req.to_string(store); },
|
||||||
|
}, this->raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -87,14 +103,24 @@ DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_vi
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
|
static inline DerivedPath parseWith(const Store & store, std::string_view s, std::string_view separator)
|
||||||
{
|
{
|
||||||
size_t n = s.find("!");
|
size_t n = s.find(separator);
|
||||||
return n == s.npos
|
return n == s.npos
|
||||||
? (DerivedPath) DerivedPath::Opaque::parse(store, s)
|
? (DerivedPath) DerivedPath::Opaque::parse(store, s)
|
||||||
: (DerivedPath) DerivedPath::Built::parse(store, s.substr(0, n), s.substr(n + 1));
|
: (DerivedPath) DerivedPath::Built::parse(store, s.substr(0, n), s.substr(n + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DerivedPath DerivedPath::parse(const Store & store, std::string_view s)
|
||||||
|
{
|
||||||
|
return parseWith(store, s, "^");
|
||||||
|
}
|
||||||
|
|
||||||
|
DerivedPath DerivedPath::parseLegacy(const Store & store, std::string_view s)
|
||||||
|
{
|
||||||
|
return parseWith(store, s, "!");
|
||||||
|
}
|
||||||
|
|
||||||
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
|
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
|
||||||
{
|
{
|
||||||
RealisedPath::Set res;
|
RealisedPath::Set res;
|
||||||
|
|
|
@ -48,8 +48,18 @@ struct DerivedPathBuilt {
|
||||||
StorePath drvPath;
|
StorePath drvPath;
|
||||||
OutputsSpec outputs;
|
OutputsSpec outputs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses `^` as the separator
|
||||||
|
*/
|
||||||
std::string to_string(const Store & store) const;
|
std::string to_string(const Store & store) const;
|
||||||
static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view);
|
/**
|
||||||
|
* Uses `!` as the separator
|
||||||
|
*/
|
||||||
|
std::string to_string_legacy(const Store & store) const;
|
||||||
|
/**
|
||||||
|
* The caller splits on the separator, so it works for both variants.
|
||||||
|
*/
|
||||||
|
static DerivedPathBuilt parse(const Store & store, std::string_view drvPath, std::string_view outputs);
|
||||||
nlohmann::json toJSON(ref<Store> store) const;
|
nlohmann::json toJSON(ref<Store> store) const;
|
||||||
|
|
||||||
GENERATE_CMP(DerivedPathBuilt, me->drvPath, me->outputs);
|
GENERATE_CMP(DerivedPathBuilt, me->drvPath, me->outputs);
|
||||||
|
@ -81,8 +91,22 @@ struct DerivedPath : _DerivedPathRaw {
|
||||||
return static_cast<const Raw &>(*this);
|
return static_cast<const Raw &>(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses `^` as the separator
|
||||||
|
*/
|
||||||
std::string to_string(const Store & store) const;
|
std::string to_string(const Store & store) const;
|
||||||
|
/**
|
||||||
|
* Uses `!` as the separator
|
||||||
|
*/
|
||||||
|
std::string to_string_legacy(const Store & store) const;
|
||||||
|
/**
|
||||||
|
* Uses `^` as the separator
|
||||||
|
*/
|
||||||
static DerivedPath parse(const Store & store, std::string_view);
|
static DerivedPath parse(const Store & store, std::string_view);
|
||||||
|
/**
|
||||||
|
* Uses `!` as the separator
|
||||||
|
*/
|
||||||
|
static DerivedPath parseLegacy(const Store & store, std::string_view);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -71,6 +71,9 @@ struct DummyStore : public virtual DummyStoreConfig, public virtual Store
|
||||||
void queryRealisationUncached(const DrvOutput &,
|
void queryRealisationUncached(const DrvOutput &,
|
||||||
Callback<std::shared_ptr<const Realisation>> callback) noexcept override
|
Callback<std::shared_ptr<const Realisation>> callback) noexcept override
|
||||||
{ callback(nullptr); }
|
{ callback(nullptr); }
|
||||||
|
|
||||||
|
virtual ref<FSAccessor> getFSAccessor() override
|
||||||
|
{ unsupported("getFSAccessor"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
static RegisterStoreImplementation<DummyStore, DummyStoreConfig> regDummyStore;
|
static RegisterStoreImplementation<DummyStore, DummyStoreConfig> regDummyStore;
|
||||||
|
|
|
@ -7,12 +7,23 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#include <sys/utsname.h>
|
#include <sys/utsname.h>
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <sodium/core.h>
|
||||||
|
|
||||||
|
#ifdef __GLIBC__
|
||||||
|
#include <gnu/lib-names.h>
|
||||||
|
#include <nss.h>
|
||||||
|
#include <dlfcn.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "config-impl.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -41,7 +52,6 @@ Settings::Settings()
|
||||||
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
|
, nixDaemonSocketFile(canonPath(getEnvNonEmpty("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
|
||||||
{
|
{
|
||||||
buildUsersGroup = getuid() == 0 ? "nixbld" : "";
|
buildUsersGroup = getuid() == 0 ? "nixbld" : "";
|
||||||
lockCPU = getEnv("NIX_AFFINITY_HACK") == "1";
|
|
||||||
allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1";
|
allowSymlinkedStore = getEnv("NIX_IGNORE_SYMLINK_STORE") == "1";
|
||||||
|
|
||||||
auto sslOverride = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or(""));
|
auto sslOverride = getEnv("NIX_SSL_CERT_FILE").value_or(getEnv("SSL_CERT_FILE").value_or(""));
|
||||||
|
@ -185,18 +195,18 @@ NLOHMANN_JSON_SERIALIZE_ENUM(SandboxMode, {
|
||||||
{SandboxMode::smDisabled, false},
|
{SandboxMode::smDisabled, false},
|
||||||
});
|
});
|
||||||
|
|
||||||
template<> void BaseSetting<SandboxMode>::set(const std::string & str, bool append)
|
template<> SandboxMode BaseSetting<SandboxMode>::parse(const std::string & str) const
|
||||||
{
|
{
|
||||||
if (str == "true") value = smEnabled;
|
if (str == "true") return smEnabled;
|
||||||
else if (str == "relaxed") value = smRelaxed;
|
else if (str == "relaxed") return smRelaxed;
|
||||||
else if (str == "false") value = smDisabled;
|
else if (str == "false") return smDisabled;
|
||||||
else throw UsageError("option '%s' has invalid value '%s'", name, str);
|
else throw UsageError("option '%s' has invalid value '%s'", name, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
template<> bool BaseSetting<SandboxMode>::isAppendable()
|
template<> struct BaseSetting<SandboxMode>::trait
|
||||||
{
|
{
|
||||||
return false;
|
static constexpr bool appendable = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
template<> std::string BaseSetting<SandboxMode>::to_string() const
|
template<> std::string BaseSetting<SandboxMode>::to_string() const
|
||||||
{
|
{
|
||||||
|
@ -228,23 +238,23 @@ template<> void BaseSetting<SandboxMode>::convertToArg(Args & args, const std::s
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void MaxBuildJobsSetting::set(const std::string & str, bool append)
|
unsigned int MaxBuildJobsSetting::parse(const std::string & str) const
|
||||||
{
|
{
|
||||||
if (str == "auto") value = std::max(1U, std::thread::hardware_concurrency());
|
if (str == "auto") return std::max(1U, std::thread::hardware_concurrency());
|
||||||
else {
|
else {
|
||||||
if (auto n = string2Int<decltype(value)>(str))
|
if (auto n = string2Int<decltype(value)>(str))
|
||||||
value = *n;
|
return *n;
|
||||||
else
|
else
|
||||||
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
|
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PluginFilesSetting::set(const std::string & str, bool append)
|
Paths PluginFilesSetting::parse(const std::string & str) const
|
||||||
{
|
{
|
||||||
if (pluginsLoaded)
|
if (pluginsLoaded)
|
||||||
throw UsageError("plugin-files set after plugins were loaded, you may need to move the flag before the subcommand");
|
throw UsageError("plugin-files set after plugins were loaded, you may need to move the flag before the subcommand");
|
||||||
BaseSetting<Paths>::set(str, append);
|
return BaseSetting<Paths>::parse(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -281,6 +291,42 @@ void initPlugins()
|
||||||
settings.pluginFiles.pluginsLoaded = true;
|
settings.pluginFiles.pluginsLoaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void preloadNSS()
|
||||||
|
{
|
||||||
|
/* builtin:fetchurl can trigger a DNS lookup, which with glibc can trigger a dynamic library load of
|
||||||
|
one of the glibc NSS libraries in a sandboxed child, which will fail unless the library's already
|
||||||
|
been loaded in the parent. So we force a lookup of an invalid domain to force the NSS machinery to
|
||||||
|
load its lookup libraries in the parent before any child gets a chance to. */
|
||||||
|
static std::once_flag dns_resolve_flag;
|
||||||
|
|
||||||
|
std::call_once(dns_resolve_flag, []() {
|
||||||
|
#ifdef __GLIBC__
|
||||||
|
/* On linux, glibc will run every lookup through the nss layer.
|
||||||
|
* That means every lookup goes, by default, through nscd, which acts as a local
|
||||||
|
* cache.
|
||||||
|
* Because we run builds in a sandbox, we also remove access to nscd otherwise
|
||||||
|
* lookups would leak into the sandbox.
|
||||||
|
*
|
||||||
|
* But now we have a new problem, we need to make sure the nss_dns backend that
|
||||||
|
* does the dns lookups when nscd is not available is loaded or available.
|
||||||
|
*
|
||||||
|
* We can't make it available without leaking nix's environment, so instead we'll
|
||||||
|
* load the backend, and configure nss so it does not try to run dns lookups
|
||||||
|
* through nscd.
|
||||||
|
*
|
||||||
|
* This is technically only used for builtins:fetch* functions so we only care
|
||||||
|
* about dns.
|
||||||
|
*
|
||||||
|
* All other platforms are unaffected.
|
||||||
|
*/
|
||||||
|
if (!dlopen(LIBNSS_DNS_SO, RTLD_NOW))
|
||||||
|
warn("unable to load nss_dns backend");
|
||||||
|
// FIXME: get hosts entry from nsswitch.conf.
|
||||||
|
__nss_configure_lookup("hosts", "files dns");
|
||||||
|
#endif
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
static bool initLibStoreDone = false;
|
static bool initLibStoreDone = false;
|
||||||
|
|
||||||
void assertLibStoreInitialized() {
|
void assertLibStoreInitialized() {
|
||||||
|
@ -291,6 +337,24 @@ void assertLibStoreInitialized() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void initLibStore() {
|
void initLibStore() {
|
||||||
|
|
||||||
|
initLibUtil();
|
||||||
|
|
||||||
|
if (sodium_init() == -1)
|
||||||
|
throw Error("could not initialise libsodium");
|
||||||
|
|
||||||
|
loadConfFile();
|
||||||
|
|
||||||
|
preloadNSS();
|
||||||
|
|
||||||
|
/* On macOS, don't use the per-session TMPDIR (as set e.g. by
|
||||||
|
sshd). This breaks build users because they don't have access
|
||||||
|
to the TMPDIR, in particular in ‘nix-store --serve’. */
|
||||||
|
#if __APPLE__
|
||||||
|
if (hasPrefix(getEnv("TMPDIR").value_or("/tmp"), "/var/folders/"))
|
||||||
|
unsetenv("TMPDIR");
|
||||||
|
#endif
|
||||||
|
|
||||||
initLibStoreDone = true;
|
initLibStoreDone = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ struct MaxBuildJobsSetting : public BaseSetting<unsigned int>
|
||||||
options->addSetting(this);
|
options->addSetting(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set(const std::string & str, bool append = false) override;
|
unsigned int parse(const std::string & str) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PluginFilesSetting : public BaseSetting<Paths>
|
struct PluginFilesSetting : public BaseSetting<Paths>
|
||||||
|
@ -43,7 +43,7 @@ struct PluginFilesSetting : public BaseSetting<Paths>
|
||||||
options->addSetting(this);
|
options->addSetting(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set(const std::string & str, bool append = false) override;
|
Paths parse(const std::string & str) const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
const uint32_t maxIdsPerBuild =
|
const uint32_t maxIdsPerBuild =
|
||||||
|
@ -328,16 +328,6 @@ public:
|
||||||
users in `build-users-group`.
|
users in `build-users-group`.
|
||||||
|
|
||||||
UIDs are allocated starting at 872415232 (0x34000000) on Linux and 56930 on macOS.
|
UIDs are allocated starting at 872415232 (0x34000000) on Linux and 56930 on macOS.
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
> This is an experimental feature.
|
|
||||||
|
|
||||||
To enable it, add the following to [`nix.conf`](#):
|
|
||||||
|
|
||||||
```
|
|
||||||
extra-experimental-features = auto-allocate-uids
|
|
||||||
auto-allocate-uids = true
|
|
||||||
```
|
|
||||||
)"};
|
)"};
|
||||||
|
|
||||||
Setting<uint32_t> startId{this,
|
Setting<uint32_t> startId{this,
|
||||||
|
@ -367,16 +357,6 @@ public:
|
||||||
|
|
||||||
Cgroups are required and enabled automatically for derivations
|
Cgroups are required and enabled automatically for derivations
|
||||||
that require the `uid-range` system feature.
|
that require the `uid-range` system feature.
|
||||||
|
|
||||||
> **Warning**
|
|
||||||
> This is an experimental feature.
|
|
||||||
|
|
||||||
To enable it, add the following to [`nix.conf`](#):
|
|
||||||
|
|
||||||
```
|
|
||||||
extra-experimental-features = cgroups
|
|
||||||
use-cgroups = true
|
|
||||||
```
|
|
||||||
)"};
|
)"};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -478,11 +458,6 @@ public:
|
||||||
)",
|
)",
|
||||||
{"env-keep-derivations"}};
|
{"env-keep-derivations"}};
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether to lock the Nix client and worker to the same CPU.
|
|
||||||
*/
|
|
||||||
bool lockCPU;
|
|
||||||
|
|
||||||
Setting<SandboxMode> sandboxMode{
|
Setting<SandboxMode> sandboxMode{
|
||||||
this,
|
this,
|
||||||
#if __linux__
|
#if __linux__
|
||||||
|
|
|
@ -156,7 +156,7 @@ struct LegacySSHStore : public virtual LegacySSHStoreConfig, public virtual Stor
|
||||||
throw Error("NAR hash is now mandatory");
|
throw Error("NAR hash is now mandatory");
|
||||||
info->narHash = Hash::parseAnyPrefixed(s);
|
info->narHash = Hash::parseAnyPrefixed(s);
|
||||||
}
|
}
|
||||||
info->ca = parseContentAddressOpt(readString(conn->from));
|
info->ca = ContentAddress::parseOpt(readString(conn->from));
|
||||||
info->sigs = readStrings<StringSet>(conn->from);
|
info->sigs = readStrings<StringSet>(conn->from);
|
||||||
|
|
||||||
auto s = readString(conn->from);
|
auto s = readString(conn->from);
|
||||||
|
@ -287,19 +287,18 @@ public:
|
||||||
|
|
||||||
conn->to.flush();
|
conn->to.flush();
|
||||||
|
|
||||||
BuildResult status {
|
BuildResult status;
|
||||||
.path = DerivedPath::Built {
|
|
||||||
.drvPath = drvPath,
|
|
||||||
.outputs = OutputsSpec::All { },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
status.status = (BuildResult::Status) readInt(conn->from);
|
status.status = (BuildResult::Status) readInt(conn->from);
|
||||||
conn->from >> status.errorMsg;
|
conn->from >> status.errorMsg;
|
||||||
|
|
||||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3)
|
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 3)
|
||||||
conn->from >> status.timesBuilt >> status.isNonDeterministic >> status.startTime >> status.stopTime;
|
conn->from >> status.timesBuilt >> status.isNonDeterministic >> status.startTime >> status.stopTime;
|
||||||
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 6) {
|
if (GET_PROTOCOL_MINOR(conn->remoteVersion) >= 6) {
|
||||||
status.builtOutputs = worker_proto::read(*this, conn->from, Phantom<DrvOutputs> {});
|
auto builtOutputs = worker_proto::read(*this, conn->from, Phantom<DrvOutputs> {});
|
||||||
|
for (auto && [output, realisation] : builtOutputs)
|
||||||
|
status.builtOutputs.insert_or_assign(
|
||||||
|
std::move(output.outputName),
|
||||||
|
std::move(realisation));
|
||||||
}
|
}
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
@ -330,7 +329,7 @@ public:
|
||||||
|
|
||||||
conn->to.flush();
|
conn->to.flush();
|
||||||
|
|
||||||
BuildResult result { .path = DerivedPath::Opaque { StorePath::dummy } };
|
BuildResult result;
|
||||||
result.status = (BuildResult::Status) readInt(conn->from);
|
result.status = (BuildResult::Status) readInt(conn->from);
|
||||||
|
|
||||||
if (!result.success()) {
|
if (!result.success()) {
|
||||||
|
@ -342,6 +341,9 @@ public:
|
||||||
void ensurePath(const StorePath & path) override
|
void ensurePath(const StorePath & path) override
|
||||||
{ unsupported("ensurePath"); }
|
{ unsupported("ensurePath"); }
|
||||||
|
|
||||||
|
virtual ref<FSAccessor> getFSAccessor() override
|
||||||
|
{ unsupported("getFSAccessor"); }
|
||||||
|
|
||||||
void computeFSClosure(const StorePathSet & paths,
|
void computeFSClosure(const StorePathSet & paths,
|
||||||
StorePathSet & out, bool flipDirection = false,
|
StorePathSet & out, bool flipDirection = false,
|
||||||
bool includeOutputs = false, bool includeDerivers = false) override
|
bool includeOutputs = false, bool includeDerivers = false) override
|
||||||
|
|
|
@ -710,6 +710,7 @@ void canonicalisePathMetaData(const Path & path,
|
||||||
canonicalisePathMetaData(path, uidRange, inodesSeen);
|
canonicalisePathMetaData(path, uidRange, inodesSeen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
|
void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs)
|
||||||
{
|
{
|
||||||
experimentalFeatureSettings.require(Xp::CaDerivations);
|
experimentalFeatureSettings.require(Xp::CaDerivations);
|
||||||
|
@ -888,7 +889,7 @@ std::shared_ptr<const ValidPathInfo> LocalStore::queryPathInfoInternal(State & s
|
||||||
if (s) info->sigs = tokenizeString<StringSet>(s, " ");
|
if (s) info->sigs = tokenizeString<StringSet>(s, " ");
|
||||||
|
|
||||||
s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 7);
|
s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 7);
|
||||||
if (s) info->ca = parseContentAddressOpt(s);
|
if (s) info->ca = ContentAddress::parseOpt(s);
|
||||||
|
|
||||||
/* Get the references. */
|
/* Get the references. */
|
||||||
auto useQueryReferences(state.stmts->QueryReferences.use()(info->id));
|
auto useQueryReferences(state.stmts->QueryReferences.use()(info->id));
|
||||||
|
@ -1221,7 +1222,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
|
||||||
printStorePath(info.path), info.narSize, hashResult.second);
|
printStorePath(info.path), info.narSize, hashResult.second);
|
||||||
|
|
||||||
if (info.ca) {
|
if (info.ca) {
|
||||||
if (auto foHash = std::get_if<FixedOutputHash>(&*info.ca)) {
|
if (auto foHash = std::get_if<FixedOutputHash>(&info.ca->raw)) {
|
||||||
auto actualFoHash = hashCAPath(
|
auto actualFoHash = hashCAPath(
|
||||||
foHash->method,
|
foHash->method,
|
||||||
foHash->hash.type,
|
foHash->hash.type,
|
||||||
|
@ -1234,7 +1235,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source,
|
||||||
actualFoHash.hash.to_string(Base32, true));
|
actualFoHash.hash.to_string(Base32, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (auto textHash = std::get_if<TextHash>(&*info.ca)) {
|
if (auto textHash = std::get_if<TextHash>(&info.ca->raw)) {
|
||||||
auto actualTextHash = hashString(htSHA256, readFile(realPath));
|
auto actualTextHash = hashString(htSHA256, readFile(realPath));
|
||||||
if (textHash->hash != actualTextHash) {
|
if (textHash->hash != actualTextHash) {
|
||||||
throw Error("ca hash mismatch importing path '%s';\n specified: %s\n got: %s",
|
throw Error("ca hash mismatch importing path '%s';\n specified: %s\n got: %s",
|
||||||
|
@ -1320,7 +1321,19 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
|
||||||
|
|
||||||
auto [hash, size] = hashSink->finish();
|
auto [hash, size] = hashSink->finish();
|
||||||
|
|
||||||
auto dstPath = makeFixedOutputPath(method, hash, name, references);
|
ContentAddressWithReferences desc = FixedOutputInfo {
|
||||||
|
.hash = {
|
||||||
|
.method = method,
|
||||||
|
.hash = hash,
|
||||||
|
},
|
||||||
|
.references = {
|
||||||
|
.others = references,
|
||||||
|
// caller is not capable of creating a self-reference, because this is content-addressed without modulus
|
||||||
|
.self = false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
auto dstPath = makeFixedOutputPathFromCA(name, desc);
|
||||||
|
|
||||||
addTempRoot(dstPath);
|
addTempRoot(dstPath);
|
||||||
|
|
||||||
|
@ -1340,7 +1353,7 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
|
||||||
autoGC();
|
autoGC();
|
||||||
|
|
||||||
if (inMemory) {
|
if (inMemory) {
|
||||||
StringSource dumpSource { dump };
|
StringSource dumpSource { dump };
|
||||||
/* Restore from the NAR in memory. */
|
/* Restore from the NAR in memory. */
|
||||||
if (method == FileIngestionMethod::Recursive)
|
if (method == FileIngestionMethod::Recursive)
|
||||||
restorePath(realPath, dumpSource);
|
restorePath(realPath, dumpSource);
|
||||||
|
@ -1364,10 +1377,13 @@ StorePath LocalStore::addToStoreFromDump(Source & source0, std::string_view name
|
||||||
|
|
||||||
optimisePath(realPath, repair);
|
optimisePath(realPath, repair);
|
||||||
|
|
||||||
ValidPathInfo info { dstPath, narHash.first };
|
ValidPathInfo info {
|
||||||
|
*this,
|
||||||
|
name,
|
||||||
|
std::move(desc),
|
||||||
|
narHash.first
|
||||||
|
};
|
||||||
info.narSize = narHash.second;
|
info.narSize = narHash.second;
|
||||||
info.references = references;
|
|
||||||
info.ca = FixedOutputHash { .method = method, .hash = hash };
|
|
||||||
registerValidPath(info);
|
registerValidPath(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1384,7 +1400,10 @@ StorePath LocalStore::addTextToStore(
|
||||||
const StorePathSet & references, RepairFlag repair)
|
const StorePathSet & references, RepairFlag repair)
|
||||||
{
|
{
|
||||||
auto hash = hashString(htSHA256, s);
|
auto hash = hashString(htSHA256, s);
|
||||||
auto dstPath = makeTextPath(name, hash, references);
|
auto dstPath = makeTextPath(name, TextInfo {
|
||||||
|
{ .hash = hash },
|
||||||
|
references,
|
||||||
|
});
|
||||||
|
|
||||||
addTempRoot(dstPath);
|
addTempRoot(dstPath);
|
||||||
|
|
||||||
|
|
|
@ -27,18 +27,17 @@ std::map<StorePath, StorePath> makeContentAddressed(
|
||||||
|
|
||||||
StringMap rewrites;
|
StringMap rewrites;
|
||||||
|
|
||||||
StorePathSet references;
|
StoreReferences refs;
|
||||||
bool hasSelfReference = false;
|
|
||||||
for (auto & ref : oldInfo->references) {
|
for (auto & ref : oldInfo->references) {
|
||||||
if (ref == path)
|
if (ref == path)
|
||||||
hasSelfReference = true;
|
refs.self = true;
|
||||||
else {
|
else {
|
||||||
auto i = remappings.find(ref);
|
auto i = remappings.find(ref);
|
||||||
auto replacement = i != remappings.end() ? i->second : ref;
|
auto replacement = i != remappings.end() ? i->second : ref;
|
||||||
// FIXME: warn about unremapped paths?
|
// FIXME: warn about unremapped paths?
|
||||||
if (replacement != ref)
|
if (replacement != ref)
|
||||||
rewrites.insert_or_assign(srcStore.printStorePath(ref), srcStore.printStorePath(replacement));
|
rewrites.insert_or_assign(srcStore.printStorePath(ref), srcStore.printStorePath(replacement));
|
||||||
references.insert(std::move(replacement));
|
refs.others.insert(std::move(replacement));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,24 +48,28 @@ std::map<StorePath, StorePath> makeContentAddressed(
|
||||||
|
|
||||||
auto narModuloHash = hashModuloSink.finish().first;
|
auto narModuloHash = hashModuloSink.finish().first;
|
||||||
|
|
||||||
auto dstPath = dstStore.makeFixedOutputPath(
|
ValidPathInfo info {
|
||||||
FileIngestionMethod::Recursive, narModuloHash, path.name(), references, hasSelfReference);
|
dstStore,
|
||||||
|
path.name(),
|
||||||
|
FixedOutputInfo {
|
||||||
|
.hash = {
|
||||||
|
.method = FileIngestionMethod::Recursive,
|
||||||
|
.hash = narModuloHash,
|
||||||
|
},
|
||||||
|
.references = std::move(refs),
|
||||||
|
},
|
||||||
|
Hash::dummy,
|
||||||
|
};
|
||||||
|
|
||||||
printInfo("rewriting '%s' to '%s'", pathS, srcStore.printStorePath(dstPath));
|
printInfo("rewriting '%s' to '%s'", pathS, dstStore.printStorePath(info.path));
|
||||||
|
|
||||||
StringSink sink2;
|
StringSink sink2;
|
||||||
RewritingSink rsink2(oldHashPart, std::string(dstPath.hashPart()), sink2);
|
RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), sink2);
|
||||||
rsink2(sink.s);
|
rsink2(sink.s);
|
||||||
rsink2.flush();
|
rsink2.flush();
|
||||||
|
|
||||||
ValidPathInfo info { dstPath, hashString(htSHA256, sink2.s) };
|
info.narHash = hashString(htSHA256, sink2.s);
|
||||||
info.references = std::move(references);
|
|
||||||
if (hasSelfReference) info.references.insert(info.path);
|
|
||||||
info.narSize = sink.s.size();
|
info.narSize = sink.s.size();
|
||||||
info.ca = FixedOutputHash {
|
|
||||||
.method = FileIngestionMethod::Recursive,
|
|
||||||
.hash = narModuloHash,
|
|
||||||
};
|
|
||||||
|
|
||||||
StringSource source(sink2.s);
|
StringSource source(sink2.s);
|
||||||
dstStore.addToStore(info, source);
|
dstStore.addToStore(info, source);
|
||||||
|
|
|
@ -273,7 +273,7 @@ public:
|
||||||
narInfo->deriver = StorePath(queryNAR.getStr(9));
|
narInfo->deriver = StorePath(queryNAR.getStr(9));
|
||||||
for (auto & sig : tokenizeString<Strings>(queryNAR.getStr(10), " "))
|
for (auto & sig : tokenizeString<Strings>(queryNAR.getStr(10), " "))
|
||||||
narInfo->sigs.insert(sig);
|
narInfo->sigs.insert(sig);
|
||||||
narInfo->ca = parseContentAddressOpt(queryNAR.getStr(11));
|
narInfo->ca = ContentAddress::parseOpt(queryNAR.getStr(11));
|
||||||
|
|
||||||
return {oValid, narInfo};
|
return {oValid, narInfo};
|
||||||
});
|
});
|
||||||
|
|
|
@ -7,15 +7,18 @@ namespace nix {
|
||||||
NarInfo::NarInfo(const Store & store, const std::string & s, const std::string & whence)
|
NarInfo::NarInfo(const Store & store, const std::string & s, const std::string & whence)
|
||||||
: ValidPathInfo(StorePath(StorePath::dummy), Hash(Hash::dummy)) // FIXME: hack
|
: ValidPathInfo(StorePath(StorePath::dummy), Hash(Hash::dummy)) // FIXME: hack
|
||||||
{
|
{
|
||||||
auto corrupt = [&]() {
|
unsigned line = 1;
|
||||||
return Error("NAR info file '%1%' is corrupt", whence);
|
|
||||||
|
auto corrupt = [&](const char * reason) {
|
||||||
|
return Error("NAR info file '%1%' is corrupt: %2%", whence,
|
||||||
|
std::string(reason) + (line > 0 ? " at line " + std::to_string(line) : ""));
|
||||||
};
|
};
|
||||||
|
|
||||||
auto parseHashField = [&](const std::string & s) {
|
auto parseHashField = [&](const std::string & s) {
|
||||||
try {
|
try {
|
||||||
return Hash::parseAnyPrefixed(s);
|
return Hash::parseAnyPrefixed(s);
|
||||||
} catch (BadHash &) {
|
} catch (BadHash &) {
|
||||||
throw corrupt();
|
throw corrupt("bad hash");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -26,12 +29,12 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
|
||||||
while (pos < s.size()) {
|
while (pos < s.size()) {
|
||||||
|
|
||||||
size_t colon = s.find(':', pos);
|
size_t colon = s.find(':', pos);
|
||||||
if (colon == std::string::npos) throw corrupt();
|
if (colon == std::string::npos) throw corrupt("expecting ':'");
|
||||||
|
|
||||||
std::string name(s, pos, colon - pos);
|
std::string name(s, pos, colon - pos);
|
||||||
|
|
||||||
size_t eol = s.find('\n', colon + 2);
|
size_t eol = s.find('\n', colon + 2);
|
||||||
if (eol == std::string::npos) throw corrupt();
|
if (eol == std::string::npos) throw corrupt("expecting '\\n'");
|
||||||
|
|
||||||
std::string value(s, colon + 2, eol - colon - 2);
|
std::string value(s, colon + 2, eol - colon - 2);
|
||||||
|
|
||||||
|
@ -47,7 +50,7 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
|
||||||
fileHash = parseHashField(value);
|
fileHash = parseHashField(value);
|
||||||
else if (name == "FileSize") {
|
else if (name == "FileSize") {
|
||||||
auto n = string2Int<decltype(fileSize)>(value);
|
auto n = string2Int<decltype(fileSize)>(value);
|
||||||
if (!n) throw corrupt();
|
if (!n) throw corrupt("invalid FileSize");
|
||||||
fileSize = *n;
|
fileSize = *n;
|
||||||
}
|
}
|
||||||
else if (name == "NarHash") {
|
else if (name == "NarHash") {
|
||||||
|
@ -56,12 +59,12 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
|
||||||
}
|
}
|
||||||
else if (name == "NarSize") {
|
else if (name == "NarSize") {
|
||||||
auto n = string2Int<decltype(narSize)>(value);
|
auto n = string2Int<decltype(narSize)>(value);
|
||||||
if (!n) throw corrupt();
|
if (!n) throw corrupt("invalid NarSize");
|
||||||
narSize = *n;
|
narSize = *n;
|
||||||
}
|
}
|
||||||
else if (name == "References") {
|
else if (name == "References") {
|
||||||
auto refs = tokenizeString<Strings>(value, " ");
|
auto refs = tokenizeString<Strings>(value, " ");
|
||||||
if (!references.empty()) throw corrupt();
|
if (!references.empty()) throw corrupt("extra References");
|
||||||
for (auto & r : refs)
|
for (auto & r : refs)
|
||||||
references.insert(StorePath(r));
|
references.insert(StorePath(r));
|
||||||
}
|
}
|
||||||
|
@ -72,17 +75,26 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
|
||||||
else if (name == "Sig")
|
else if (name == "Sig")
|
||||||
sigs.insert(value);
|
sigs.insert(value);
|
||||||
else if (name == "CA") {
|
else if (name == "CA") {
|
||||||
if (ca) throw corrupt();
|
if (ca) throw corrupt("extra CA");
|
||||||
// FIXME: allow blank ca or require skipping field?
|
// FIXME: allow blank ca or require skipping field?
|
||||||
ca = parseContentAddressOpt(value);
|
ca = ContentAddress::parseOpt(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pos = eol + 1;
|
pos = eol + 1;
|
||||||
|
line += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (compression == "") compression = "bzip2";
|
if (compression == "") compression = "bzip2";
|
||||||
|
|
||||||
if (!havePath || !haveNarHash || url.empty() || narSize == 0) throw corrupt();
|
if (!havePath || !haveNarHash || url.empty() || narSize == 0) {
|
||||||
|
line = 0; // don't include line information in the error
|
||||||
|
throw corrupt(
|
||||||
|
!havePath ? "StorePath missing" :
|
||||||
|
!haveNarHash ? "NarHash missing" :
|
||||||
|
url.empty() ? "URL missing" :
|
||||||
|
narSize == 0 ? "NarSize missing or zero"
|
||||||
|
: "?");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string NarInfo::to_string(const Store & store) const
|
std::string NarInfo::to_string(const Store & store) const
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue