mirror of
https://github.com/NixOS/nix.git
synced 2025-11-23 18:59:35 +01:00
Merge commit '734019ce56' into progress-bar
This commit is contained in:
commit
37e74bb69b
92 changed files with 1593 additions and 687 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -90,6 +90,7 @@ perl/Makefile.config
|
||||||
|
|
||||||
/misc/systemd/nix-daemon.service
|
/misc/systemd/nix-daemon.service
|
||||||
/misc/systemd/nix-daemon.socket
|
/misc/systemd/nix-daemon.socket
|
||||||
|
/misc/systemd/nix-daemon.conf
|
||||||
/misc/upstart/nix-daemon.conf
|
/misc/upstart/nix-daemon.conf
|
||||||
|
|
||||||
/src/resolve-system-dependencies/resolve-system-dependencies
|
/src/resolve-system-dependencies/resolve-system-dependencies
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@ options:
|
||||||
concatStrings (map
|
concatStrings (map
|
||||||
(name:
|
(name:
|
||||||
let option = options.${name}; in
|
let option = options.${name}; in
|
||||||
" - `${name}` \n\n"
|
" - [`${name}`](#conf-${name})"
|
||||||
|
+ "<p id=\"conf-${name}\"></p>\n\n"
|
||||||
+ concatStrings (map (s: " ${s}\n") (splitLines option.description)) + "\n\n"
|
+ concatStrings (map (s: " ${s}\n") (splitLines option.description)) + "\n\n"
|
||||||
+ (if option.documentDefault
|
+ (if option.documentDefault
|
||||||
then " **Default:** " + (
|
then " **Default:** " + (
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,9 @@ 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`.
|
||||||
|
|
||||||
You can uninstall Nix with the following commands:
|
## Uninstalling
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
```console
|
```console
|
||||||
sudo rm -rf /etc/profile/nix.sh /etc/nix /nix ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
|
sudo rm -rf /etc/profile/nix.sh /etc/nix /nix ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
|
||||||
|
|
@ -95,15 +97,95 @@ sudo systemctl stop nix-daemon.service
|
||||||
sudo systemctl disable nix-daemon.socket
|
sudo systemctl disable nix-daemon.socket
|
||||||
sudo systemctl disable nix-daemon.service
|
sudo systemctl disable nix-daemon.service
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
# If you are on macOS, you will need to run:
|
|
||||||
sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
|
||||||
sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
|
||||||
```
|
```
|
||||||
|
|
||||||
There may also be references to Nix in `/etc/profile`, `/etc/bashrc`,
|
There may also be references to Nix in `/etc/profile`, `/etc/bashrc`,
|
||||||
and `/etc/zshrc` which you may remove.
|
and `/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 this,
|
||||||
|
`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.
|
||||||
|
|
||||||
|
> **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 <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a>
|
# macOS Installation <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a>
|
||||||
<!-- Note: anchors above to catch permalinks to old explanations -->
|
<!-- Note: anchors above to catch permalinks to old explanations -->
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Release X.Y (2022-03-07)
|
# Release 2.7 (2022-03-07)
|
||||||
|
|
||||||
* Nix will now make some helpful suggestions when you mistype
|
* Nix will now make some helpful suggestions when you mistype
|
||||||
something on the command line. For instance, if you type `nix build
|
something on the command line. For instance, if you type `nix build
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,42 @@
|
||||||
# Release X.Y (202?-??-??)
|
# Release X.Y (202?-??-??)
|
||||||
|
|
||||||
* Various nix commands can now read expressions from stdin with `--file -`.
|
* Various nix commands can now read expressions from stdin with `--file -`.
|
||||||
|
|
||||||
|
* `nix store make-content-addressable` has been renamed to `nix store
|
||||||
|
make-content-addressed`.
|
||||||
|
|
||||||
|
* New experimental builtin function `builtins.fetchClosure` that
|
||||||
|
copies a closure from a binary cache at evaluation time and rewrites
|
||||||
|
it to content-addressed form (if it isn't already). Like
|
||||||
|
`builtins.storePath`, this allows importing pre-built store paths;
|
||||||
|
the difference is that it doesn't require the user to configure
|
||||||
|
binary caches and trusted public keys.
|
||||||
|
|
||||||
|
This function is only available if you enable the experimental
|
||||||
|
feature `fetch-closure`.
|
||||||
|
|
||||||
|
* New experimental feature: *impure derivations*. These are
|
||||||
|
derivations that can produce a different result every time they're
|
||||||
|
built. Here is an example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
stdenv.mkDerivation {
|
||||||
|
name = "impure";
|
||||||
|
__impure = true; # marks this derivation as impure
|
||||||
|
buildCommand = "date > $out";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Running `nix build` twice on this expression will build the
|
||||||
|
derivation twice, producing two different content-addressed store
|
||||||
|
paths. Like fixed-output derivations, impure derivations have access
|
||||||
|
to the network. Only fixed-output derivations and impure derivations
|
||||||
|
can depend on an impure derivation.
|
||||||
|
|
||||||
|
* The `nixosModule` flake output attribute has been renamed consistent
|
||||||
|
with the `.default` renames in nix 2.7.
|
||||||
|
|
||||||
|
* `nixosModule` → `nixosModules.default`
|
||||||
|
|
||||||
|
As before, the old output will continue to work, but `nix flake check` will
|
||||||
|
issue a warning about it.
|
||||||
|
|
|
||||||
6
flake.lock
generated
6
flake.lock
generated
|
|
@ -18,11 +18,11 @@
|
||||||
},
|
},
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1632864508,
|
"lastModified": 1645296114,
|
||||||
"narHash": "sha256-d127FIvGR41XbVRDPVvozUPQ/uRHbHwvfyKHwEt5xFM=",
|
"narHash": "sha256-y53N7TyIkXsjMpOG7RhvqJFGDacLs9HlyHeSTBioqYU=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "82891b5e2c2359d7e58d08849e4c89511ab94234",
|
"rev": "530a53dcbc9437363471167a5e4762c5fcfa34a1",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
ifdef HOST_LINUX
|
ifdef HOST_LINUX
|
||||||
|
|
||||||
$(foreach n, nix-daemon.socket nix-daemon.service, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644)))
|
$(foreach n, nix-daemon.socket nix-daemon.service, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644)))
|
||||||
|
$(foreach n, nix-daemon.conf, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/tmpfiles.d, 0644)))
|
||||||
|
|
||||||
clean-files += $(d)/nix-daemon.socket $(d)/nix-daemon.service
|
clean-files += $(d)/nix-daemon.socket $(d)/nix-daemon.service $(d)/nix-daemon.conf
|
||||||
|
|
||||||
endif
|
endif
|
||||||
|
|
|
||||||
1
misc/systemd/nix-daemon.conf.in
Normal file
1
misc/systemd/nix-daemon.conf.in
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
d @localstatedir@/nix/daemon-socket 0755 root root - -
|
||||||
|
|
@ -3,6 +3,7 @@ Description=Nix Daemon
|
||||||
Documentation=man:nix-daemon https://nixos.org/manual
|
Documentation=man:nix-daemon https://nixos.org/manual
|
||||||
RequiresMountsFor=@storedir@
|
RequiresMountsFor=@storedir@
|
||||||
RequiresMountsFor=@localstatedir@
|
RequiresMountsFor=@localstatedir@
|
||||||
|
RequiresMountsFor=@localstatedir@/nix/db
|
||||||
ConditionPathIsReadWrite=@localstatedir@/nix/daemon-socket
|
ConditionPathIsReadWrite=@localstatedir@/nix/daemon-socket
|
||||||
|
|
||||||
[Service]
|
[Service]
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ curl -sS -H 'Accept: application/json' https://hydra.nixos.org/jobset/nix/master
|
||||||
someBuildFailed=0
|
someBuildFailed=0
|
||||||
|
|
||||||
for buildId in $BUILDS_FOR_LATEST_EVAL; do
|
for buildId in $BUILDS_FOR_LATEST_EVAL; do
|
||||||
buildInfo=$(curl -sS -H 'Accept: application/json' "https://hydra.nixos.org/build/$buildId")
|
buildInfo=$(curl --fail -sS -H 'Accept: application/json' "https://hydra.nixos.org/build/$buildId")
|
||||||
|
|
||||||
finished=$(echo "$buildInfo" | jq -r '.finished')
|
finished=$(echo "$buildInfo" | jq -r '.finished')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -739,7 +739,7 @@ install_from_extracted_nix() {
|
||||||
cd "$EXTRACTED_NIX_PATH"
|
cd "$EXTRACTED_NIX_PATH"
|
||||||
|
|
||||||
_sudo "to copy the basic Nix files to the new store at $NIX_ROOT/store" \
|
_sudo "to copy the basic Nix files to the new store at $NIX_ROOT/store" \
|
||||||
cp -RLp ./store/* "$NIX_ROOT/store/"
|
cp -RPp ./store/* "$NIX_ROOT/store/"
|
||||||
|
|
||||||
_sudo "to make the new store non-writable at $NIX_ROOT/store" \
|
_sudo "to make the new store non-writable at $NIX_ROOT/store" \
|
||||||
chmod -R ugo-w "$NIX_ROOT/store/"
|
chmod -R ugo-w "$NIX_ROOT/store/"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,8 @@ readonly SERVICE_DEST=/etc/systemd/system/nix-daemon.service
|
||||||
readonly SOCKET_SRC=/lib/systemd/system/nix-daemon.socket
|
readonly SOCKET_SRC=/lib/systemd/system/nix-daemon.socket
|
||||||
readonly SOCKET_DEST=/etc/systemd/system/nix-daemon.socket
|
readonly SOCKET_DEST=/etc/systemd/system/nix-daemon.socket
|
||||||
|
|
||||||
|
readonly TMPFILES_SRC=/lib/tmpfiles.d/nix-daemon.conf
|
||||||
|
readonly TMPFILES_DEST=/etc/tmpfiles.d/nix-daemon.conf
|
||||||
|
|
||||||
# Path for the systemd override unit file to contain the proxy settings
|
# Path for the systemd override unit file to contain the proxy settings
|
||||||
readonly SERVICE_OVERRIDE=${SERVICE_DEST}.d/override.conf
|
readonly SERVICE_OVERRIDE=${SERVICE_DEST}.d/override.conf
|
||||||
|
|
@ -83,6 +85,13 @@ EOF
|
||||||
poly_configure_nix_daemon_service() {
|
poly_configure_nix_daemon_service() {
|
||||||
if [ -e /run/systemd/system ]; then
|
if [ -e /run/systemd/system ]; then
|
||||||
task "Setting up the nix-daemon systemd service"
|
task "Setting up the nix-daemon systemd service"
|
||||||
|
|
||||||
|
_sudo "to create the nix-daemon tmpfiles config" \
|
||||||
|
ln -sfn /nix/var/nix/profiles/default/$TMPFILES_SRC $TMPFILES_DEST
|
||||||
|
|
||||||
|
_sudo "to run systemd-tmpfiles once to pick that path up" \
|
||||||
|
systemd-tmpfiles --create --prefix=/nix/var/nix
|
||||||
|
|
||||||
_sudo "to set up the nix-daemon service" \
|
_sudo "to set up the nix-daemon service" \
|
||||||
systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC"
|
systemctl link "/nix/var/nix/profiles/default$SERVICE_SRC"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ if [ "$(uname -s)" != "Darwin" ]; then
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if command -v curl > /dev/null 2>&1; then
|
if command -v curl > /dev/null 2>&1; then
|
||||||
fetch() { curl -L "$1" -o "$2"; }
|
fetch() { curl --fail -L "$1" -o "$2"; }
|
||||||
elif command -v wget > /dev/null 2>&1; then
|
elif command -v wget > /dev/null 2>&1; then
|
||||||
fetch() { wget "$1" -O "$2"; }
|
fetch() { wget "$1" -O "$2"; }
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -204,7 +204,8 @@ Strings editorFor(const Pos & pos)
|
||||||
if (pos.line > 0 && (
|
if (pos.line > 0 && (
|
||||||
editor.find("emacs") != std::string::npos ||
|
editor.find("emacs") != std::string::npos ||
|
||||||
editor.find("nano") != std::string::npos ||
|
editor.find("nano") != std::string::npos ||
|
||||||
editor.find("vim") != std::string::npos))
|
editor.find("vim") != std::string::npos ||
|
||||||
|
editor.find("kak") != std::string::npos))
|
||||||
args.push_back(fmt("+%d", pos.line));
|
args.push_back(fmt("+%d", pos.line));
|
||||||
args.push_back(pos.file);
|
args.push_back(pos.file);
|
||||||
return args;
|
return args;
|
||||||
|
|
|
||||||
|
|
@ -758,55 +758,20 @@ std::shared_ptr<Installable> SourceExprCommand::parseInstallable(
|
||||||
return installables.front();
|
return installables.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltPaths getBuiltPaths(ref<Store> evalStore, ref<Store> store, const DerivedPaths & hopefullyBuiltPaths)
|
BuiltPaths Installable::build(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
Realise mode,
|
||||||
|
const std::vector<std::shared_ptr<Installable>> & installables,
|
||||||
|
BuildMode bMode)
|
||||||
{
|
{
|
||||||
BuiltPaths res;
|
BuiltPaths res;
|
||||||
for (const auto & b : hopefullyBuiltPaths)
|
for (auto & [_, builtPath] : build2(evalStore, store, mode, installables, bMode))
|
||||||
std::visit(
|
res.push_back(builtPath);
|
||||||
overloaded{
|
|
||||||
[&](const DerivedPath::Opaque & bo) {
|
|
||||||
res.push_back(BuiltPath::Opaque{bo.path});
|
|
||||||
},
|
|
||||||
[&](const DerivedPath::Built & bfd) {
|
|
||||||
OutputPathMap outputs;
|
|
||||||
auto drv = evalStore->readDerivation(bfd.drvPath);
|
|
||||||
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
|
||||||
auto drvOutputs = drv.outputsAndOptPaths(*store);
|
|
||||||
for (auto & output : bfd.outputs) {
|
|
||||||
if (!outputHashes.count(output))
|
|
||||||
throw Error(
|
|
||||||
"the derivation '%s' doesn't have an output named '%s'",
|
|
||||||
store->printStorePath(bfd.drvPath), output);
|
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
|
||||||
auto outputId =
|
|
||||||
DrvOutput{outputHashes.at(output), output};
|
|
||||||
auto realisation =
|
|
||||||
store->queryRealisation(outputId);
|
|
||||||
if (!realisation)
|
|
||||||
throw Error(
|
|
||||||
"cannot operate on an output of unbuilt "
|
|
||||||
"content-addressed derivation '%s'",
|
|
||||||
outputId.to_string());
|
|
||||||
outputs.insert_or_assign(
|
|
||||||
output, realisation->outPath);
|
|
||||||
} else {
|
|
||||||
// If ca-derivations isn't enabled, assume that
|
|
||||||
// the output path is statically known.
|
|
||||||
assert(drvOutputs.count(output));
|
|
||||||
assert(drvOutputs.at(output).second);
|
|
||||||
outputs.insert_or_assign(
|
|
||||||
output, *drvOutputs.at(output).second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.push_back(BuiltPath::Built{bfd.drvPath, outputs});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
b.raw());
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltPaths Installable::build(
|
std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::build2(
|
||||||
ref<Store> evalStore,
|
ref<Store> evalStore,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
Realise mode,
|
Realise mode,
|
||||||
|
|
@ -817,39 +782,93 @@ BuiltPaths Installable::build(
|
||||||
settings.readOnlyMode = true;
|
settings.readOnlyMode = true;
|
||||||
|
|
||||||
std::vector<DerivedPath> pathsToBuild;
|
std::vector<DerivedPath> pathsToBuild;
|
||||||
|
std::map<DerivedPath, std::vector<std::shared_ptr<Installable>>> backmap;
|
||||||
|
|
||||||
for (auto & i : installables) {
|
for (auto & i : installables) {
|
||||||
auto b = i->toDerivedPaths();
|
for (auto b : i->toDerivedPaths()) {
|
||||||
pathsToBuild.insert(pathsToBuild.end(), b.begin(), b.end());
|
pathsToBuild.push_back(b);
|
||||||
|
backmap[b].push_back(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> res;
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
|
|
||||||
case Realise::Nothing:
|
case Realise::Nothing:
|
||||||
case Realise::Derivation:
|
case Realise::Derivation:
|
||||||
printMissing(store, pathsToBuild, lvlError);
|
printMissing(store, pathsToBuild, lvlError);
|
||||||
return getBuiltPaths(evalStore, store, pathsToBuild);
|
|
||||||
|
for (auto & path : pathsToBuild) {
|
||||||
|
for (auto & installable : backmap[path]) {
|
||||||
|
std::visit(overloaded {
|
||||||
|
[&](const DerivedPath::Built & bfd) {
|
||||||
|
OutputPathMap outputs;
|
||||||
|
auto drv = evalStore->readDerivation(bfd.drvPath);
|
||||||
|
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
||||||
|
auto drvOutputs = drv.outputsAndOptPaths(*store);
|
||||||
|
for (auto & output : bfd.outputs) {
|
||||||
|
if (!outputHashes.count(output))
|
||||||
|
throw Error(
|
||||||
|
"the derivation '%s' doesn't have an output named '%s'",
|
||||||
|
store->printStorePath(bfd.drvPath), output);
|
||||||
|
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||||
|
DrvOutput outputId { outputHashes.at(output), output };
|
||||||
|
auto realisation = store->queryRealisation(outputId);
|
||||||
|
if (!realisation)
|
||||||
|
throw Error(
|
||||||
|
"cannot operate on an output of unbuilt "
|
||||||
|
"content-addressed derivation '%s'",
|
||||||
|
outputId.to_string());
|
||||||
|
outputs.insert_or_assign(output, realisation->outPath);
|
||||||
|
} else {
|
||||||
|
// If ca-derivations isn't enabled, assume that
|
||||||
|
// the output path is statically known.
|
||||||
|
assert(drvOutputs.count(output));
|
||||||
|
assert(drvOutputs.at(output).second);
|
||||||
|
outputs.insert_or_assign(
|
||||||
|
output, *drvOutputs.at(output).second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
|
||||||
|
},
|
||||||
|
[&](const DerivedPath::Opaque & bo) {
|
||||||
|
res.push_back({installable, BuiltPath::Opaque { bo.path }});
|
||||||
|
},
|
||||||
|
}, path.raw());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case Realise::Outputs: {
|
case Realise::Outputs: {
|
||||||
BuiltPaths res;
|
|
||||||
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
|
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
|
||||||
if (!buildResult.success())
|
if (!buildResult.success())
|
||||||
buildResult.rethrow();
|
buildResult.rethrow();
|
||||||
std::visit(overloaded {
|
|
||||||
[&](const DerivedPath::Built & bfd) {
|
for (auto & installable : backmap[buildResult.path]) {
|
||||||
std::map<std::string, StorePath> outputs;
|
std::visit(overloaded {
|
||||||
for (auto & path : buildResult.builtOutputs)
|
[&](const DerivedPath::Built & bfd) {
|
||||||
outputs.emplace(path.first.outputName, path.second.outPath);
|
std::map<std::string, StorePath> outputs;
|
||||||
res.push_back(BuiltPath::Built { bfd.drvPath, outputs });
|
for (auto & path : buildResult.builtOutputs)
|
||||||
},
|
outputs.emplace(path.first.outputName, path.second.outPath);
|
||||||
[&](const DerivedPath::Opaque & bo) {
|
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
|
||||||
res.push_back(BuiltPath::Opaque { bo.path });
|
},
|
||||||
},
|
[&](const DerivedPath::Opaque & bo) {
|
||||||
}, buildResult.path.raw());
|
res.push_back({installable, BuiltPath::Opaque { bo.path }});
|
||||||
|
},
|
||||||
|
}, buildResult.path.raw());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltPaths Installable::toBuiltPaths(
|
BuiltPaths Installable::toBuiltPaths(
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,13 @@ struct Installable
|
||||||
const std::vector<std::shared_ptr<Installable>> & installables,
|
const std::vector<std::shared_ptr<Installable>> & installables,
|
||||||
BuildMode bMode = bmNormal);
|
BuildMode bMode = bmNormal);
|
||||||
|
|
||||||
|
static std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> build2(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
Realise mode,
|
||||||
|
const std::vector<std::shared_ptr<Installable>> & installables,
|
||||||
|
BuildMode bMode = bmNormal);
|
||||||
|
|
||||||
static std::set<StorePath> toStorePaths(
|
static std::set<StorePath> toStorePaths(
|
||||||
ref<Store> evalStore,
|
ref<Store> evalStore,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
|
|
@ -185,9 +192,4 @@ ref<eval_cache::EvalCache> openEvalCache(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
std::shared_ptr<flake::LockedFlake> lockedFlake);
|
std::shared_ptr<flake::LockedFlake> lockedFlake);
|
||||||
|
|
||||||
BuiltPaths getBuiltPaths(
|
|
||||||
ref<Store> evalStore,
|
|
||||||
ref<Store> store,
|
|
||||||
const DerivedPaths & hopefullyBuiltPaths);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ struct AttrDb
|
||||||
{
|
{
|
||||||
std::atomic_bool failed{false};
|
std::atomic_bool failed{false};
|
||||||
|
|
||||||
|
const Store & cfg;
|
||||||
|
|
||||||
struct State
|
struct State
|
||||||
{
|
{
|
||||||
SQLite db;
|
SQLite db;
|
||||||
|
|
@ -33,8 +35,9 @@ struct AttrDb
|
||||||
|
|
||||||
std::unique_ptr<Sync<State>> _state;
|
std::unique_ptr<Sync<State>> _state;
|
||||||
|
|
||||||
AttrDb(const Hash & fingerprint)
|
AttrDb(const Store & cfg, const Hash & fingerprint)
|
||||||
: _state(std::make_unique<Sync<State>>())
|
: cfg(cfg)
|
||||||
|
, _state(std::make_unique<Sync<State>>())
|
||||||
{
|
{
|
||||||
auto state(_state->lock());
|
auto state(_state->lock());
|
||||||
|
|
||||||
|
|
@ -254,10 +257,10 @@ struct AttrDb
|
||||||
return {{rowId, attrs}};
|
return {{rowId, attrs}};
|
||||||
}
|
}
|
||||||
case AttrType::String: {
|
case AttrType::String: {
|
||||||
std::vector<std::pair<Path, std::string>> 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(decodeContext(s));
|
context.push_back(decodeContext(cfg, s));
|
||||||
return {{rowId, string_t{queryAttribute.getStr(2), context}}};
|
return {{rowId, string_t{queryAttribute.getStr(2), context}}};
|
||||||
}
|
}
|
||||||
case AttrType::Bool:
|
case AttrType::Bool:
|
||||||
|
|
@ -274,10 +277,10 @@ struct AttrDb
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::shared_ptr<AttrDb> makeAttrDb(const Hash & fingerprint)
|
static std::shared_ptr<AttrDb> makeAttrDb(const Store & cfg, const Hash & fingerprint)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
return std::make_shared<AttrDb>(fingerprint);
|
return std::make_shared<AttrDb>(cfg, fingerprint);
|
||||||
} catch (SQLiteError &) {
|
} catch (SQLiteError &) {
|
||||||
ignoreException();
|
ignoreException();
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
@ -288,7 +291,7 @@ EvalCache::EvalCache(
|
||||||
std::optional<std::reference_wrapper<const Hash>> useCache,
|
std::optional<std::reference_wrapper<const Hash>> useCache,
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
RootLoader rootLoader)
|
RootLoader rootLoader)
|
||||||
: db(useCache ? makeAttrDb(*useCache) : nullptr)
|
: db(useCache ? makeAttrDb(*state.store, *useCache) : nullptr)
|
||||||
, state(state)
|
, state(state)
|
||||||
, rootLoader(rootLoader)
|
, rootLoader(rootLoader)
|
||||||
{
|
{
|
||||||
|
|
@ -546,7 +549,7 @@ string_t AttrCursor::getStringWithContext()
|
||||||
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
|
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
for (auto & c : s->second) {
|
for (auto & c : s->second) {
|
||||||
if (!root->state.store->isValidPath(root->state.store->parseStorePath(c.first))) {
|
if (!root->state.store->isValidPath(c.first)) {
|
||||||
valid = false;
|
valid = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -563,7 +566,7 @@ string_t AttrCursor::getStringWithContext()
|
||||||
auto & v = forceValue();
|
auto & v = forceValue();
|
||||||
|
|
||||||
if (v.type() == nString)
|
if (v.type() == nString)
|
||||||
return {v.string.s, v.getContext()};
|
return {v.string.s, v.getContext(*root->state.store)};
|
||||||
else if (v.type() == nPath)
|
else if (v.type() == nPath)
|
||||||
return {v.path, {}};
|
return {v.path, {}};
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ struct misc_t {};
|
||||||
struct failed_t {};
|
struct failed_t {};
|
||||||
typedef uint64_t AttrId;
|
typedef uint64_t AttrId;
|
||||||
typedef std::pair<AttrId, Symbol> AttrKey;
|
typedef std::pair<AttrId, Symbol> AttrKey;
|
||||||
typedef std::pair<std::string, std::vector<std::pair<Path, std::string>>> string_t;
|
typedef std::pair<std::string, NixStringContext> string_t;
|
||||||
|
|
||||||
typedef std::variant<
|
typedef std::variant<
|
||||||
std::vector<Symbol>,
|
std::vector<Symbol>,
|
||||||
|
|
|
||||||
|
|
@ -96,20 +96,20 @@ RootValue allocRootValue(Value * v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void printValue(std::ostream & str, std::set<const void *> & seen, const Value & v)
|
void Value::print(std::ostream & str, std::set<const void *> * seen) const
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
switch (v.internalType) {
|
switch (internalType) {
|
||||||
case tInt:
|
case tInt:
|
||||||
str << v.integer;
|
str << integer;
|
||||||
break;
|
break;
|
||||||
case tBool:
|
case tBool:
|
||||||
str << (v.boolean ? "true" : "false");
|
str << (boolean ? "true" : "false");
|
||||||
break;
|
break;
|
||||||
case tString:
|
case tString:
|
||||||
str << "\"";
|
str << "\"";
|
||||||
for (const char * i = v.string.s; *i; i++)
|
for (const char * i = string.s; *i; i++)
|
||||||
if (*i == '\"' || *i == '\\') str << "\\" << *i;
|
if (*i == '\"' || *i == '\\') str << "\\" << *i;
|
||||||
else if (*i == '\n') str << "\\n";
|
else if (*i == '\n') str << "\\n";
|
||||||
else if (*i == '\r') str << "\\r";
|
else if (*i == '\r') str << "\\r";
|
||||||
|
|
@ -119,19 +119,19 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
|
||||||
str << "\"";
|
str << "\"";
|
||||||
break;
|
break;
|
||||||
case tPath:
|
case tPath:
|
||||||
str << v.path; // !!! escaping?
|
str << path; // !!! escaping?
|
||||||
break;
|
break;
|
||||||
case tNull:
|
case tNull:
|
||||||
str << "null";
|
str << "null";
|
||||||
break;
|
break;
|
||||||
case tAttrs: {
|
case tAttrs: {
|
||||||
if (!v.attrs->empty() && !seen.insert(v.attrs).second)
|
if (seen && !attrs->empty() && !seen->insert(attrs).second)
|
||||||
str << "<REPEAT>";
|
str << "«repeated»";
|
||||||
else {
|
else {
|
||||||
str << "{ ";
|
str << "{ ";
|
||||||
for (auto & i : v.attrs->lexicographicOrder()) {
|
for (auto & i : attrs->lexicographicOrder()) {
|
||||||
str << i->name << " = ";
|
str << i->name << " = ";
|
||||||
printValue(str, seen, *i->value);
|
i->value->print(str, seen);
|
||||||
str << "; ";
|
str << "; ";
|
||||||
}
|
}
|
||||||
str << "}";
|
str << "}";
|
||||||
|
|
@ -141,12 +141,12 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
|
||||||
case tList1:
|
case tList1:
|
||||||
case tList2:
|
case tList2:
|
||||||
case tListN:
|
case tListN:
|
||||||
if (v.listSize() && !seen.insert(v.listElems()).second)
|
if (seen && listSize() && !seen->insert(listElems()).second)
|
||||||
str << "<REPEAT>";
|
str << "«repeated»";
|
||||||
else {
|
else {
|
||||||
str << "[ ";
|
str << "[ ";
|
||||||
for (auto v2 : v.listItems()) {
|
for (auto v2 : listItems()) {
|
||||||
printValue(str, seen, *v2);
|
v2->print(str, seen);
|
||||||
str << " ";
|
str << " ";
|
||||||
}
|
}
|
||||||
str << "]";
|
str << "]";
|
||||||
|
|
@ -166,10 +166,10 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
|
||||||
str << "<PRIMOP-APP>";
|
str << "<PRIMOP-APP>";
|
||||||
break;
|
break;
|
||||||
case tExternal:
|
case tExternal:
|
||||||
str << *v.external;
|
str << *external;
|
||||||
break;
|
break;
|
||||||
case tFloat:
|
case tFloat:
|
||||||
str << v.fpoint;
|
str << fpoint;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
abort();
|
abort();
|
||||||
|
|
@ -177,10 +177,16 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const Value & v)
|
void Value::print(std::ostream & str, bool showRepeated) const
|
||||||
{
|
{
|
||||||
std::set<const void *> seen;
|
std::set<const void *> seen;
|
||||||
printValue(str, seen, v);
|
print(str, showRepeated ? nullptr : &seen);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::ostream & operator << (std::ostream & str, const Value & v)
|
||||||
|
{
|
||||||
|
v.print(str, false);
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -430,6 +436,7 @@ EvalState::EvalState(
|
||||||
, sBuilder(symbols.create("builder"))
|
, sBuilder(symbols.create("builder"))
|
||||||
, sArgs(symbols.create("args"))
|
, sArgs(symbols.create("args"))
|
||||||
, sContentAddressed(symbols.create("__contentAddressed"))
|
, sContentAddressed(symbols.create("__contentAddressed"))
|
||||||
|
, sImpure(symbols.create("__impure"))
|
||||||
, sOutputHash(symbols.create("outputHash"))
|
, sOutputHash(symbols.create("outputHash"))
|
||||||
, sOutputHashAlgo(symbols.create("outputHashAlgo"))
|
, sOutputHashAlgo(symbols.create("outputHashAlgo"))
|
||||||
, sOutputHashMode(symbols.create("outputHashMode"))
|
, sOutputHashMode(symbols.create("outputHashMode"))
|
||||||
|
|
@ -501,23 +508,6 @@ EvalState::~EvalState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::requireExperimentalFeatureOnEvaluation(
|
|
||||||
const ExperimentalFeature & feature,
|
|
||||||
const std::string_view fName,
|
|
||||||
const Pos & pos)
|
|
||||||
{
|
|
||||||
if (!settings.isExperimentalFeatureEnabled(feature)) {
|
|
||||||
throw EvalError({
|
|
||||||
.msg = hintfmt(
|
|
||||||
"Cannot call '%2%' because experimental Nix feature '%1%' is disabled. You can enable it via '--extra-experimental-features %1%'.",
|
|
||||||
feature,
|
|
||||||
fName
|
|
||||||
),
|
|
||||||
.errPos = pos
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::allowPath(const Path & path)
|
void EvalState::allowPath(const Path & path)
|
||||||
{
|
{
|
||||||
if (allowedPaths)
|
if (allowedPaths)
|
||||||
|
|
@ -1903,13 +1893,22 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos)
|
||||||
|
|
||||||
/* Decode a context string ‘!<name>!<path>’ into a pair <path,
|
/* Decode a context string ‘!<name>!<path>’ into a pair <path,
|
||||||
name>. */
|
name>. */
|
||||||
std::pair<std::string, std::string> decodeContext(std::string_view s)
|
NixStringContextElem decodeContext(const Store & store, std::string_view s)
|
||||||
{
|
{
|
||||||
if (s.at(0) == '!') {
|
if (s.at(0) == '!') {
|
||||||
size_t index = s.find("!", 1);
|
size_t index = s.find("!", 1);
|
||||||
return {std::string(s.substr(index + 1)), std::string(s.substr(1, index - 1))};
|
return {
|
||||||
|
store.parseStorePath(s.substr(index + 1)),
|
||||||
|
std::string(s.substr(1, index - 1)),
|
||||||
|
};
|
||||||
} else
|
} else
|
||||||
return {s.at(0) == '/' ? std::string(s) : std::string(s.substr(1)), ""};
|
return {
|
||||||
|
store.parseStorePath(
|
||||||
|
s.at(0) == '/'
|
||||||
|
? s
|
||||||
|
: s.substr(1)),
|
||||||
|
"",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1921,13 +1920,13 @@ void copyContext(const Value & v, PathSet & context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::vector<std::pair<Path, std::string>> Value::getContext()
|
NixStringContext Value::getContext(const Store & store)
|
||||||
{
|
{
|
||||||
std::vector<std::pair<Path, std::string>> res;
|
NixStringContext res;
|
||||||
assert(internalType == tString);
|
assert(internalType == tString);
|
||||||
if (string.context)
|
if (string.context)
|
||||||
for (const char * * p = string.context; *p; ++p)
|
for (const char * * p = string.context; *p; ++p)
|
||||||
res.push_back(decodeContext(*p));
|
res.push_back(decodeContext(store, *p));
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ public:
|
||||||
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
|
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
|
||||||
sFile, sLine, sColumn, sFunctor, sToString,
|
sFile, sLine, sColumn, sFunctor, sToString,
|
||||||
sRight, sWrong, sStructuredAttrs, sBuilder, sArgs,
|
sRight, sWrong, sStructuredAttrs, sBuilder, sArgs,
|
||||||
sContentAddressed,
|
sContentAddressed, sImpure,
|
||||||
sOutputHash, sOutputHashAlgo, sOutputHashMode,
|
sOutputHash, sOutputHashAlgo, sOutputHashMode,
|
||||||
sRecurseForDerivations,
|
sRecurseForDerivations,
|
||||||
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
|
sDescription, sSelf, sEpsilon, sStartSet, sOperator, sKey, sPath,
|
||||||
|
|
@ -149,12 +149,6 @@ public:
|
||||||
std::shared_ptr<Store> buildStore = nullptr);
|
std::shared_ptr<Store> buildStore = nullptr);
|
||||||
~EvalState();
|
~EvalState();
|
||||||
|
|
||||||
void requireExperimentalFeatureOnEvaluation(
|
|
||||||
const ExperimentalFeature &,
|
|
||||||
const std::string_view fName,
|
|
||||||
const Pos & pos
|
|
||||||
);
|
|
||||||
|
|
||||||
void addToSearchPath(const std::string & s);
|
void addToSearchPath(const std::string & s);
|
||||||
|
|
||||||
SearchPath getSearchPath() { return searchPath; }
|
SearchPath getSearchPath() { return searchPath; }
|
||||||
|
|
@ -430,7 +424,7 @@ std::string showType(const Value & v);
|
||||||
|
|
||||||
/* Decode a context string ‘!<name>!<path>’ into a pair <path,
|
/* Decode a context string ‘!<name>!<path>’ into a pair <path,
|
||||||
name>. */
|
name>. */
|
||||||
std::pair<std::string, std::string> decodeContext(std::string_view s);
|
NixStringContextElem decodeContext(const Store & store, std::string_view s);
|
||||||
|
|
||||||
/* If `path' refers to a directory, then append "/default.nix". */
|
/* If `path' refers to a directory, then append "/default.nix". */
|
||||||
Path resolveExprPath(Path path);
|
Path resolveExprPath(Path path);
|
||||||
|
|
|
||||||
|
|
@ -708,8 +708,6 @@ void callFlake(EvalState & state,
|
||||||
|
|
||||||
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos);
|
|
||||||
|
|
||||||
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
|
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
|
||||||
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
||||||
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||||
|
|
@ -725,7 +723,30 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
v);
|
v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp r2("__getFlake", 1, prim_getFlake);
|
static RegisterPrimOp r2({
|
||||||
|
.name = "__getFlake",
|
||||||
|
.args = {"args"},
|
||||||
|
.doc = R"(
|
||||||
|
Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
Unless impure evaluation is allowed (`--impure`), the flake reference
|
||||||
|
must be "locked", e.g. contain a Git revision or content hash. An
|
||||||
|
example of an unlocked usage is:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(builtins.getFlake "github:edolstra/dwarffs").rev
|
||||||
|
```
|
||||||
|
|
||||||
|
This function is only available if you enable the experimental feature
|
||||||
|
`flakes`.
|
||||||
|
)",
|
||||||
|
.fun = prim_getFlake,
|
||||||
|
.experimentalFeature = Xp::Flakes,
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,13 @@ using namespace nix;
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
|
||||||
|
{
|
||||||
|
return Pos(data->origin, data->file, loc.first_line, loc.first_column);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define CUR_POS makeCurPos(*yylloc, data)
|
||||||
|
|
||||||
// backup to recover from yyless(0)
|
// backup to recover from yyless(0)
|
||||||
YYLTYPE prev_yylloc;
|
YYLTYPE prev_yylloc;
|
||||||
|
|
||||||
|
|
@ -37,7 +44,6 @@ static void initLoc(YYLTYPE * loc)
|
||||||
loc->first_column = loc->last_column = 1;
|
loc->first_column = loc->last_column = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
|
static void adjustLoc(YYLTYPE * loc, const char * s, size_t len)
|
||||||
{
|
{
|
||||||
prev_yylloc = *loc;
|
prev_yylloc = *loc;
|
||||||
|
|
@ -147,14 +153,20 @@ or { return OR_KW; }
|
||||||
try {
|
try {
|
||||||
yylval->n = boost::lexical_cast<int64_t>(yytext);
|
yylval->n = boost::lexical_cast<int64_t>(yytext);
|
||||||
} catch (const boost::bad_lexical_cast &) {
|
} catch (const boost::bad_lexical_cast &) {
|
||||||
throw ParseError("invalid integer '%1%'", yytext);
|
throw ParseError({
|
||||||
|
.msg = hintfmt("invalid integer '%1%'", yytext),
|
||||||
|
.errPos = CUR_POS,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return INT;
|
return INT;
|
||||||
}
|
}
|
||||||
{FLOAT} { errno = 0;
|
{FLOAT} { errno = 0;
|
||||||
yylval->nf = strtod(yytext, 0);
|
yylval->nf = strtod(yytext, 0);
|
||||||
if (errno != 0)
|
if (errno != 0)
|
||||||
throw ParseError("invalid float '%1%'", yytext);
|
throw ParseError({
|
||||||
|
.msg = hintfmt("invalid float '%1%'", yytext),
|
||||||
|
.errPos = CUR_POS,
|
||||||
|
});
|
||||||
return FLOAT;
|
return FLOAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -280,7 +292,10 @@ or { return OR_KW; }
|
||||||
|
|
||||||
<INPATH_SLASH>{ANY} |
|
<INPATH_SLASH>{ANY} |
|
||||||
<INPATH_SLASH><<EOF>> {
|
<INPATH_SLASH><<EOF>> {
|
||||||
throw ParseError("path has a trailing slash");
|
throw ParseError({
|
||||||
|
.msg = hintfmt("path has a trailing slash"),
|
||||||
|
.errPos = CUR_POS,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
|
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
|
||||||
|
|
|
||||||
|
|
@ -43,8 +43,8 @@ StringMap EvalState::realiseContext(const PathSet & context)
|
||||||
StringMap res;
|
StringMap res;
|
||||||
|
|
||||||
for (auto & i : context) {
|
for (auto & i : context) {
|
||||||
auto [ctxS, outputName] = decodeContext(i);
|
auto [ctx, outputName] = decodeContext(*store, i);
|
||||||
auto ctx = store->parseStorePath(ctxS);
|
auto ctxS = store->printStorePath(ctx);
|
||||||
if (!store->isValidPath(ctx))
|
if (!store->isValidPath(ctx))
|
||||||
throw InvalidPathError(store->printStorePath(ctx));
|
throw InvalidPathError(store->printStorePath(ctx));
|
||||||
if (!outputName.empty() && ctx.isDerivation()) {
|
if (!outputName.empty() && ctx.isDerivation()) {
|
||||||
|
|
@ -694,7 +694,32 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||||
|
|
||||||
static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
|
static RegisterPrimOp primop_genericClosure(RegisterPrimOp::Info {
|
||||||
.name = "__genericClosure",
|
.name = "__genericClosure",
|
||||||
|
.args = {"attrset"},
|
||||||
.arity = 1,
|
.arity = 1,
|
||||||
|
.doc = R"(
|
||||||
|
Take an *attrset* with values named `startSet` and `operator` in order to
|
||||||
|
return a *list of attrsets* by starting with the `startSet`, recursively
|
||||||
|
applying the `operator` function to each element. The *attrsets* in the
|
||||||
|
`startSet` and produced by the `operator` must each contain value named
|
||||||
|
`key` which are comparable to each other. The result is produced by
|
||||||
|
repeatedly calling the operator for each element encountered with a
|
||||||
|
unique key, terminating when no new elements are produced. For example,
|
||||||
|
|
||||||
|
```
|
||||||
|
builtins.genericClosure {
|
||||||
|
startSet = [ {key = 5;} ];
|
||||||
|
operator = item: [{
|
||||||
|
key = if (item.key / 2 ) * 2 == item.key
|
||||||
|
then item.key / 2
|
||||||
|
else 3 * item.key + 1;
|
||||||
|
}];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
evaluates to
|
||||||
|
```
|
||||||
|
[ { key = 5; } { key = 16; } { key = 8; } { key = 4; } { key = 2; } { key = 1; } ]
|
||||||
|
```
|
||||||
|
)",
|
||||||
.fun = prim_genericClosure,
|
.fun = prim_genericClosure,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -964,9 +989,10 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
PathSet context;
|
PathSet context;
|
||||||
|
|
||||||
bool contentAddressed = false;
|
bool contentAddressed = false;
|
||||||
|
bool isImpure = false;
|
||||||
std::optional<std::string> outputHash;
|
std::optional<std::string> outputHash;
|
||||||
std::string outputHashAlgo;
|
std::string outputHashAlgo;
|
||||||
auto ingestionMethod = FileIngestionMethod::Flat;
|
std::optional<FileIngestionMethod> ingestionMethod;
|
||||||
|
|
||||||
StringSet outputs;
|
StringSet outputs;
|
||||||
outputs.insert("out");
|
outputs.insert("out");
|
||||||
|
|
@ -1026,6 +1052,12 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
settings.requireExperimentalFeature(Xp::CaDerivations);
|
settings.requireExperimentalFeature(Xp::CaDerivations);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else if (i->name == state.sImpure) {
|
||||||
|
isImpure = state.forceBool(*i->value, pos);
|
||||||
|
if (isImpure)
|
||||||
|
settings.requireExperimentalFeature(Xp::ImpureDerivations);
|
||||||
|
}
|
||||||
|
|
||||||
/* The `args' attribute is special: it supplies the
|
/* The `args' attribute is special: it supplies the
|
||||||
command-line arguments to the builder. */
|
command-line arguments to the builder. */
|
||||||
else if (i->name == state.sArgs) {
|
else if (i->name == state.sArgs) {
|
||||||
|
|
@ -1118,8 +1150,8 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
|
|
||||||
/* Handle derivation outputs of the form ‘!<name>!<path>’. */
|
/* Handle derivation outputs of the form ‘!<name>!<path>’. */
|
||||||
else if (path.at(0) == '!') {
|
else if (path.at(0) == '!') {
|
||||||
auto ctx = decodeContext(path);
|
auto ctx = decodeContext(*state.store, path);
|
||||||
drv.inputDrvs[state.store->parseStorePath(ctx.first)].insert(ctx.second);
|
drv.inputDrvs[ctx.first].insert(ctx.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Otherwise it's a source file. */
|
/* Otherwise it's a source file. */
|
||||||
|
|
@ -1158,29 +1190,44 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
.errPos = posDrvName
|
.errPos = posDrvName
|
||||||
});
|
});
|
||||||
|
|
||||||
std::optional<HashType> ht = parseHashTypeOpt(outputHashAlgo);
|
auto h = newHashAllowEmpty(*outputHash, parseHashTypeOpt(outputHashAlgo));
|
||||||
Hash h = newHashAllowEmpty(*outputHash, ht);
|
|
||||||
|
|
||||||
auto outPath = state.store->makeFixedOutputPath(ingestionMethod, h, drvName);
|
auto method = ingestionMethod.value_or(FileIngestionMethod::Flat);
|
||||||
|
auto outPath = state.store->makeFixedOutputPath(method, h, drvName);
|
||||||
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 {
|
||||||
.hash = FixedOutputHash {
|
.hash = FixedOutputHash {
|
||||||
.method = ingestionMethod,
|
.method = method,
|
||||||
.hash = std::move(h),
|
.hash = std::move(h),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (contentAddressed) {
|
else if (contentAddressed || isImpure) {
|
||||||
HashType ht = parseHashType(outputHashAlgo);
|
if (contentAddressed && isImpure)
|
||||||
|
throw EvalError({
|
||||||
|
.msg = hintfmt("derivation cannot be both content-addressed and impure"),
|
||||||
|
.errPos = posDrvName
|
||||||
|
});
|
||||||
|
|
||||||
|
auto ht = parseHashTypeOpt(outputHashAlgo).value_or(htSHA256);
|
||||||
|
auto method = ingestionMethod.value_or(FileIngestionMethod::Recursive);
|
||||||
|
|
||||||
for (auto & i : outputs) {
|
for (auto & i : outputs) {
|
||||||
drv.env[i] = hashPlaceholder(i);
|
drv.env[i] = hashPlaceholder(i);
|
||||||
drv.outputs.insert_or_assign(i,
|
if (isImpure)
|
||||||
DerivationOutput::CAFloating {
|
drv.outputs.insert_or_assign(i,
|
||||||
.method = ingestionMethod,
|
DerivationOutput::Impure {
|
||||||
.hashType = ht,
|
.method = method,
|
||||||
});
|
.hashType = ht,
|
||||||
|
});
|
||||||
|
else
|
||||||
|
drv.outputs.insert_or_assign(i,
|
||||||
|
DerivationOutput::CAFloating {
|
||||||
|
.method = method,
|
||||||
|
.hashType = ht,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1197,34 +1244,26 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
DerivationOutput::Deferred { });
|
DerivationOutput::Deferred { });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular, non-CA derivation should always return a single hash and not
|
auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true);
|
||||||
// hash per output.
|
switch (hashModulo.kind) {
|
||||||
auto hashModulo = hashDerivationModulo(*state.store, drv, true);
|
case DrvHash::Kind::Regular:
|
||||||
std::visit(overloaded {
|
for (auto & i : outputs) {
|
||||||
[&](const DrvHash & drvHash) {
|
auto h = hashModulo.hashes.at(i);
|
||||||
auto & h = drvHash.hash;
|
auto outPath = state.store->makeOutputPath(i, h, drvName);
|
||||||
switch (drvHash.kind) {
|
drv.env[i] = state.store->printStorePath(outPath);
|
||||||
case DrvHash::Kind::Deferred:
|
drv.outputs.insert_or_assign(
|
||||||
/* Outputs already deferred, nothing to do */
|
i,
|
||||||
break;
|
DerivationOutputInputAddressed {
|
||||||
case DrvHash::Kind::Regular:
|
.path = std::move(outPath),
|
||||||
for (auto & [outputName, output] : drv.outputs) {
|
});
|
||||||
auto outPath = state.store->makeOutputPath(outputName, h, drvName);
|
}
|
||||||
drv.env[outputName] = state.store->printStorePath(outPath);
|
break;
|
||||||
output = DerivationOutput::InputAddressed {
|
;
|
||||||
.path = std::move(outPath),
|
case DrvHash::Kind::Deferred:
|
||||||
};
|
for (auto & i : outputs) {
|
||||||
}
|
drv.outputs.insert_or_assign(i, DerivationOutputDeferred {});
|
||||||
break;
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
[&](const CaOutputHashes &) {
|
|
||||||
// Shouldn't happen as the toplevel derivation is not CA.
|
|
||||||
assert(false);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hashModulo.raw());
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write the resulting term into the Nix store directory. */
|
/* Write the resulting term into the Nix store directory. */
|
||||||
|
|
@ -3789,7 +3828,7 @@ RegisterPrimOp::RegisterPrimOp(std::string name, size_t arity, PrimOpFun fun)
|
||||||
.name = name,
|
.name = name,
|
||||||
.args = {},
|
.args = {},
|
||||||
.arity = arity,
|
.arity = arity,
|
||||||
.fun = fun
|
.fun = fun,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -3861,13 +3900,17 @@ void EvalState::createBaseEnv()
|
||||||
|
|
||||||
if (RegisterPrimOp::primOps)
|
if (RegisterPrimOp::primOps)
|
||||||
for (auto & primOp : *RegisterPrimOp::primOps)
|
for (auto & primOp : *RegisterPrimOp::primOps)
|
||||||
addPrimOp({
|
if (!primOp.experimentalFeature
|
||||||
.fun = primOp.fun,
|
|| settings.isExperimentalFeatureEnabled(*primOp.experimentalFeature))
|
||||||
.arity = std::max(primOp.args.size(), primOp.arity),
|
{
|
||||||
.name = symbols.create(primOp.name),
|
addPrimOp({
|
||||||
.args = primOp.args,
|
.fun = primOp.fun,
|
||||||
.doc = primOp.doc,
|
.arity = std::max(primOp.args.size(), primOp.arity),
|
||||||
});
|
.name = symbols.create(primOp.name),
|
||||||
|
.args = primOp.args,
|
||||||
|
.doc = primOp.doc,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/* 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. */
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ struct RegisterPrimOp
|
||||||
size_t arity = 0;
|
size_t arity = 0;
|
||||||
const char * doc;
|
const char * doc;
|
||||||
PrimOpFun fun;
|
PrimOpFun fun;
|
||||||
|
std::optional<ExperimentalFeature> experimentalFeature;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<Info> PrimOps;
|
typedef std::vector<Info> PrimOps;
|
||||||
|
|
@ -35,6 +36,7 @@ struct RegisterPrimOp
|
||||||
/* These primops are disabled without enableNativeCode, but plugins
|
/* These primops are disabled without enableNativeCode, but plugins
|
||||||
may wish to use them in limited contexts without globally enabling
|
may wish to use them in limited contexts without globally enabling
|
||||||
them. */
|
them. */
|
||||||
|
|
||||||
/* Load a ValueInitializer from a DSO and return whatever it initializes */
|
/* Load a ValueInitializer from a DSO and return whatever it initializes */
|
||||||
void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args,
|
||||||
drv = std::string(p, 1);
|
drv = std::string(p, 1);
|
||||||
path = &drv;
|
path = &drv;
|
||||||
} else if (p.at(0) == '!') {
|
} else if (p.at(0) == '!') {
|
||||||
std::pair<std::string, std::string> ctx = decodeContext(p);
|
NixStringContextElem ctx = decodeContext(*state.store, p);
|
||||||
drv = ctx.first;
|
drv = state.store->printStorePath(ctx.first);
|
||||||
output = ctx.second;
|
output = ctx.second;
|
||||||
path = &drv;
|
path = &drv;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
161
src/libexpr/primops/fetchClosure.cc
Normal file
161
src/libexpr/primops/fetchClosure.cc
Normal file
|
|
@ -0,0 +1,161 @@
|
||||||
|
#include "primops.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
#include "make-content-addressed.hh"
|
||||||
|
#include "url.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
static void prim_fetchClosure(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
|
{
|
||||||
|
state.forceAttrs(*args[0], pos);
|
||||||
|
|
||||||
|
std::optional<std::string> fromStoreUrl;
|
||||||
|
std::optional<StorePath> fromPath;
|
||||||
|
bool toCA = false;
|
||||||
|
std::optional<StorePath> toPath;
|
||||||
|
|
||||||
|
for (auto & attr : *args[0]->attrs) {
|
||||||
|
if (attr.name == "fromPath") {
|
||||||
|
PathSet context;
|
||||||
|
fromPath = state.coerceToStorePath(*attr.pos, *attr.value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (attr.name == "toPath") {
|
||||||
|
state.forceValue(*attr.value, *attr.pos);
|
||||||
|
toCA = true;
|
||||||
|
if (attr.value->type() != nString || attr.value->string.s != std::string("")) {
|
||||||
|
PathSet context;
|
||||||
|
toPath = state.coerceToStorePath(*attr.pos, *attr.value, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (attr.name == "fromStore")
|
||||||
|
fromStoreUrl = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||||||
|
|
||||||
|
else
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attr.name),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fromPath)
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!fromStoreUrl)
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
|
||||||
|
auto parsedURL = parseURL(*fromStoreUrl);
|
||||||
|
|
||||||
|
if (parsedURL.scheme != "http" &&
|
||||||
|
parsedURL.scheme != "https" &&
|
||||||
|
!(getEnv("_NIX_IN_TEST").has_value() && parsedURL.scheme == "file"))
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("'fetchClosure' only supports http:// and https:// stores"),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!parsedURL.query.empty())
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
|
||||||
|
auto fromStore = openStore(parsedURL.to_string());
|
||||||
|
|
||||||
|
if (toCA) {
|
||||||
|
if (!toPath || !state.store->isValidPath(*toPath)) {
|
||||||
|
auto remappings = makeContentAddressed(*fromStore, *state.store, { *fromPath });
|
||||||
|
auto i = remappings.find(*fromPath);
|
||||||
|
assert(i != remappings.end());
|
||||||
|
if (toPath && *toPath != i->second)
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("rewriting '%s' to content-addressed form yielded '%s', while '%s' was expected",
|
||||||
|
state.store->printStorePath(*fromPath),
|
||||||
|
state.store->printStorePath(i->second),
|
||||||
|
state.store->printStorePath(*toPath)),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
if (!toPath)
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt(
|
||||||
|
"rewriting '%s' to content-addressed form yielded '%s'; "
|
||||||
|
"please set this in the 'toPath' attribute passed to 'fetchClosure'",
|
||||||
|
state.store->printStorePath(*fromPath),
|
||||||
|
state.store->printStorePath(i->second)),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (!state.store->isValidPath(*fromPath))
|
||||||
|
copyClosure(*fromStore, *state.store, RealisedPath::Set { *fromPath });
|
||||||
|
toPath = fromPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* In pure mode, require a CA path. */
|
||||||
|
if (evalSettings.pureEval) {
|
||||||
|
auto info = state.store->queryPathInfo(*toPath);
|
||||||
|
if (!info->isContentAddressed(*state.store))
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("in pure mode, 'fetchClosure' requires a content-addressed path, which '%s' isn't",
|
||||||
|
state.store->printStorePath(*toPath)),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
auto toPathS = state.store->printStorePath(*toPath);
|
||||||
|
v.mkString(toPathS, {toPathS});
|
||||||
|
}
|
||||||
|
|
||||||
|
static RegisterPrimOp primop_fetchClosure({
|
||||||
|
.name = "__fetchClosure",
|
||||||
|
.args = {"args"},
|
||||||
|
.doc = R"(
|
||||||
|
Fetch a Nix store closure from a binary cache, rewriting it into
|
||||||
|
content-addressed form. For example,
|
||||||
|
|
||||||
|
```nix
|
||||||
|
builtins.fetchClosure {
|
||||||
|
fromStore = "https://cache.nixos.org";
|
||||||
|
fromPath = /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1;
|
||||||
|
toPath = /nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
fetches `/nix/store/r2jd...` from the specified binary cache,
|
||||||
|
and rewrites it into the content-addressed store path
|
||||||
|
`/nix/store/ldbh...`.
|
||||||
|
|
||||||
|
If `fromPath` is already content-addressed, or if you are
|
||||||
|
allowing impure evaluation (`--impure`), then `toPath` may be
|
||||||
|
omitted.
|
||||||
|
|
||||||
|
To find out the correct value for `toPath` given a `fromPath`,
|
||||||
|
you can use `nix store make-content-addressed`:
|
||||||
|
|
||||||
|
```console
|
||||||
|
# nix store make-content-addressed --from https://cache.nixos.org /nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1
|
||||||
|
rewrote '/nix/store/r2jd6ygnmirm2g803mksqqjm4y39yi6i-git-2.33.1' to '/nix/store/ldbhlwhh39wha58rm61bkiiwm6j7211j-git-2.33.1'
|
||||||
|
```
|
||||||
|
|
||||||
|
This function is similar to `builtins.storePath` in that it
|
||||||
|
allows you to use a previously built store path in a Nix
|
||||||
|
expression. However, it is more reproducible because it requires
|
||||||
|
specifying a binary cache from which the path can be fetched.
|
||||||
|
Also, requiring a content-addressed final store path avoids the
|
||||||
|
need for users to configure binary cache public keys.
|
||||||
|
|
||||||
|
This function is only available if you enable the experimental
|
||||||
|
feature `fetch-closure`.
|
||||||
|
)",
|
||||||
|
.fun = prim_fetchClosure,
|
||||||
|
.experimentalFeature = Xp::FetchClosure,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -145,7 +145,7 @@ static void fetchTree(
|
||||||
if (!params.allowNameArgument)
|
if (!params.allowNameArgument)
|
||||||
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
|
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
|
||||||
throw Error({
|
throw Error({
|
||||||
.msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"),
|
.msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"),
|
||||||
.errPos = pos
|
.errPos = pos
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -329,7 +329,7 @@ static RegisterPrimOp primop_fetchTarball({
|
||||||
.fun = prim_fetchTarball,
|
.fun = prim_fetchTarball,
|
||||||
});
|
});
|
||||||
|
|
||||||
static void prim_fetchGit(EvalState &state, const Pos &pos, Value **args, Value &v)
|
static void prim_fetchGit(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
|
fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,8 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||||
.msg = hintfmt("cannot convert %1% to JSON", showType(v)),
|
.msg = hintfmt("cannot convert %1% to JSON", showType(v)),
|
||||||
.errPos = v.determinePos(pos)
|
.errPos = v.determinePos(pos)
|
||||||
});
|
});
|
||||||
throw e.addTrace(pos, hintfmt("message for the trace"));
|
e.addTrace(pos, hintfmt("message for the trace"));
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,6 +57,8 @@ struct ExprLambda;
|
||||||
struct PrimOp;
|
struct PrimOp;
|
||||||
class Symbol;
|
class Symbol;
|
||||||
struct Pos;
|
struct Pos;
|
||||||
|
class StorePath;
|
||||||
|
class Store;
|
||||||
class EvalState;
|
class EvalState;
|
||||||
class XMLWriter;
|
class XMLWriter;
|
||||||
class JSONPlaceholder;
|
class JSONPlaceholder;
|
||||||
|
|
@ -64,6 +66,8 @@ class JSONPlaceholder;
|
||||||
|
|
||||||
typedef int64_t NixInt;
|
typedef int64_t NixInt;
|
||||||
typedef double NixFloat;
|
typedef double NixFloat;
|
||||||
|
typedef std::pair<StorePath, std::string> NixStringContextElem;
|
||||||
|
typedef std::vector<NixStringContextElem> NixStringContext;
|
||||||
|
|
||||||
/* External values must descend from ExternalValueBase, so that
|
/* External values must descend from ExternalValueBase, so that
|
||||||
* type-agnostic nix functions (e.g. showType) can be implemented
|
* type-agnostic nix functions (e.g. showType) can be implemented
|
||||||
|
|
@ -115,10 +119,13 @@ private:
|
||||||
InternalType internalType;
|
InternalType internalType;
|
||||||
|
|
||||||
friend std::string showType(const Value & v);
|
friend std::string showType(const Value & v);
|
||||||
friend void printValue(std::ostream & str, std::set<const void *> & seen, const Value & v);
|
|
||||||
|
void print(std::ostream & str, std::set<const void *> * seen) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
void print(std::ostream & str, bool showRepeated = false) const;
|
||||||
|
|
||||||
// Functions needed to distinguish the type
|
// Functions needed to distinguish the type
|
||||||
// These should be removed eventually, by putting the functionality that's
|
// These should be removed eventually, by putting the functionality that's
|
||||||
// needed by callers into methods of this type
|
// needed by callers into methods of this type
|
||||||
|
|
@ -368,7 +375,7 @@ public:
|
||||||
non-trivial. */
|
non-trivial. */
|
||||||
bool isTrivial() const;
|
bool isTrivial() const;
|
||||||
|
|
||||||
std::vector<std::pair<Path, std::string>> getContext();
|
NixStringContext getContext(const Store &);
|
||||||
|
|
||||||
auto listItems()
|
auto listItems()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -238,9 +238,18 @@ std::optional<std::string> Input::getRef() const
|
||||||
|
|
||||||
std::optional<Hash> Input::getRev() const
|
std::optional<Hash> Input::getRev() const
|
||||||
{
|
{
|
||||||
if (auto s = maybeGetStrAttr(attrs, "rev"))
|
std::optional<Hash> hash = {};
|
||||||
return Hash::parseAny(*s, htSHA1);
|
|
||||||
return {};
|
if (auto s = maybeGetStrAttr(attrs, "rev")) {
|
||||||
|
try {
|
||||||
|
hash = Hash::parseAnyPrefixed(*s);
|
||||||
|
} catch (BadHash &e) {
|
||||||
|
// Default to sha1 for backwards compatibility with existing flakes
|
||||||
|
hash = Hash::parseAny(*s, htSHA1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<uint64_t> Input::getRevCount() const
|
std::optional<uint64_t> Input::getRevCount() const
|
||||||
|
|
|
||||||
|
|
@ -285,9 +285,11 @@ struct GitInputScheme : InputScheme
|
||||||
auto files = tokenizeString<std::set<std::string>>(
|
auto files = tokenizeString<std::set<std::string>>(
|
||||||
runProgram("git", true, gitOpts), "\0"s);
|
runProgram("git", true, gitOpts), "\0"s);
|
||||||
|
|
||||||
|
Path actualPath(absPath(actualUrl));
|
||||||
|
|
||||||
PathFilter filter = [&](const Path & p) -> bool {
|
PathFilter filter = [&](const Path & p) -> bool {
|
||||||
assert(hasPrefix(p, actualUrl));
|
assert(hasPrefix(p, actualPath));
|
||||||
std::string file(p, actualUrl.size() + 1);
|
std::string file(p, actualPath.size() + 1);
|
||||||
|
|
||||||
auto st = lstat(p);
|
auto st = lstat(p);
|
||||||
|
|
||||||
|
|
@ -300,13 +302,13 @@ struct GitInputScheme : InputScheme
|
||||||
return files.count(file);
|
return files.count(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
|
auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter);
|
||||||
|
|
||||||
// FIXME: maybe we should use the timestamp of the last
|
// FIXME: maybe we should use the timestamp of the last
|
||||||
// modified dirty file?
|
// modified dirty file?
|
||||||
input.attrs.insert_or_assign(
|
input.attrs.insert_or_assign(
|
||||||
"lastModified",
|
"lastModified",
|
||||||
hasHead ? std::stoull(runProgram("git", true, { "-C", actualUrl, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
|
hasHead ? std::stoull(runProgram("git", true, { "-C", actualPath, "log", "-1", "--format=%ct", "--no-show-signature", "HEAD" })) : 0);
|
||||||
|
|
||||||
return {std::move(storePath), input};
|
return {std::move(storePath), input};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -390,7 +390,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||||
|
|
||||||
ref_uri = line.substr(ref_index+5, line.length()-1);
|
ref_uri = line.substr(ref_index+5, line.length()-1);
|
||||||
} else
|
} else
|
||||||
ref_uri = fmt("refs/heads/%s", ref);
|
ref_uri = fmt("refs/(heads|tags)/%s", ref);
|
||||||
|
|
||||||
auto file = store->toRealPath(
|
auto file = store->toRealPath(
|
||||||
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
|
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
|
||||||
|
|
@ -399,9 +399,11 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||||
std::string line;
|
std::string line;
|
||||||
std::string id;
|
std::string id;
|
||||||
while(getline(is, line)) {
|
while(getline(is, line)) {
|
||||||
auto index = line.find(ref_uri);
|
// Append $ to avoid partial name matches
|
||||||
if (index != std::string::npos) {
|
std::regex pattern(fmt("%s$", ref_uri));
|
||||||
id = line.substr(0, index-1);
|
|
||||||
|
if (std::regex_search(line, pattern)) {
|
||||||
|
id = line.substr(0, line.find('\t'));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "realisation.hh"
|
#include "realisation.hh"
|
||||||
|
#include "derived-path.hh"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
@ -30,6 +31,8 @@ struct BuildResult
|
||||||
ResolvesToAlreadyValid,
|
ResolvesToAlreadyValid,
|
||||||
NoSubstituters,
|
NoSubstituters,
|
||||||
} status = MiscFailure;
|
} status = MiscFailure;
|
||||||
|
|
||||||
|
// FIXME: include entire ErrorInfo object.
|
||||||
std::string errorMsg;
|
std::string errorMsg;
|
||||||
|
|
||||||
std::string toString() const {
|
std::string toString() const {
|
||||||
|
|
|
||||||
|
|
@ -204,10 +204,33 @@ void DerivationGoal::haveDerivation()
|
||||||
{
|
{
|
||||||
trace("have derivation");
|
trace("have derivation");
|
||||||
|
|
||||||
|
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
|
||||||
|
|
||||||
if (!drv->type().hasKnownOutputPaths())
|
if (!drv->type().hasKnownOutputPaths())
|
||||||
settings.requireExperimentalFeature(Xp::CaDerivations);
|
settings.requireExperimentalFeature(Xp::CaDerivations);
|
||||||
|
|
||||||
retrySubstitution = false;
|
if (!drv->type().isPure()) {
|
||||||
|
settings.requireExperimentalFeature(Xp::ImpureDerivations);
|
||||||
|
|
||||||
|
for (auto & [outputName, output] : drv->outputs) {
|
||||||
|
auto randomPath = StorePath::random(outputPathName(drv->name, outputName));
|
||||||
|
assert(!worker.store.isValidPath(randomPath));
|
||||||
|
initialOutputs.insert({
|
||||||
|
outputName,
|
||||||
|
InitialOutput {
|
||||||
|
.wanted = true,
|
||||||
|
.outputHash = impureOutputHash,
|
||||||
|
.known = InitialOutputStatus {
|
||||||
|
.path = randomPath,
|
||||||
|
.status = PathStatus::Absent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
gaveUpOnSubstitution();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto & i : drv->outputsAndOptPaths(worker.store))
|
for (auto & i : drv->outputsAndOptPaths(worker.store))
|
||||||
if (i.second.second)
|
if (i.second.second)
|
||||||
|
|
@ -232,9 +255,6 @@ void DerivationGoal::haveDerivation()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
|
|
||||||
|
|
||||||
|
|
||||||
/* We are first going to try to create the invalid output paths
|
/* We are first going to try to create the invalid output paths
|
||||||
through substitutes. If that doesn't work, we'll build
|
through substitutes. If that doesn't work, we'll build
|
||||||
them. */
|
them. */
|
||||||
|
|
@ -268,6 +288,8 @@ void DerivationGoal::outputsSubstitutionTried()
|
||||||
{
|
{
|
||||||
trace("all outputs substituted (maybe)");
|
trace("all outputs substituted (maybe)");
|
||||||
|
|
||||||
|
assert(drv->type().isPure());
|
||||||
|
|
||||||
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) {
|
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) {
|
||||||
done(BuildResult::TransientFailure, {},
|
done(BuildResult::TransientFailure, {},
|
||||||
Error("some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
|
Error("some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
|
||||||
|
|
@ -311,18 +333,27 @@ void DerivationGoal::outputsSubstitutionTried()
|
||||||
gaveUpOnSubstitution();
|
gaveUpOnSubstitution();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* At least one of the output paths could not be
|
/* At least one of the output paths could not be
|
||||||
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()
|
||||||
{
|
{
|
||||||
/* Make sure checkPathValidity() from now on checks all
|
|
||||||
outputs. */
|
|
||||||
wantedOutputs.clear();
|
|
||||||
|
|
||||||
/* The inputs must be built before we can build this goal. */
|
/* The inputs must be built before we can build this goal. */
|
||||||
|
inputDrvOutputs.clear();
|
||||||
if (useDerivation)
|
if (useDerivation)
|
||||||
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs)
|
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
|
||||||
|
/* Ensure that pure, non-fixed-output derivations don't
|
||||||
|
depend on impure derivations. */
|
||||||
|
if (drv->type().isPure() && !drv->type().isFixed()) {
|
||||||
|
auto inputDrv = worker.evalStore.readDerivation(i.first);
|
||||||
|
if (!inputDrv.type().isPure())
|
||||||
|
throw Error("pure derivation '%s' depends on impure derivation '%s'",
|
||||||
|
worker.store.printStorePath(drvPath),
|
||||||
|
worker.store.printStorePath(i.first));
|
||||||
|
}
|
||||||
|
|
||||||
addWaitee(worker.makeDerivationGoal(i.first, i.second, buildMode == bmRepair ? bmRepair : bmNormal));
|
addWaitee(worker.makeDerivationGoal(i.first, i.second, buildMode == bmRepair ? bmRepair : bmNormal));
|
||||||
|
}
|
||||||
|
|
||||||
/* Copy the input sources from the eval store to the build
|
/* Copy the input sources from the eval store to the build
|
||||||
store. */
|
store. */
|
||||||
|
|
@ -350,6 +381,8 @@ void DerivationGoal::gaveUpOnSubstitution()
|
||||||
|
|
||||||
void DerivationGoal::repairClosure()
|
void DerivationGoal::repairClosure()
|
||||||
{
|
{
|
||||||
|
assert(drv->type().isPure());
|
||||||
|
|
||||||
/* If we're repairing, we now know that our own outputs are valid.
|
/* If we're repairing, we now know that our own outputs are valid.
|
||||||
Now check whether the other paths in the outputs closure are
|
Now check whether the other paths in the outputs closure are
|
||||||
good. If not, then start derivation goals for the derivations
|
good. If not, then start derivation goals for the derivations
|
||||||
|
|
@ -426,7 +459,8 @@ void DerivationGoal::inputsRealised()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retrySubstitution) {
|
if (retrySubstitution && !retriedSubstitution) {
|
||||||
|
retriedSubstitution = true;
|
||||||
haveDerivation();
|
haveDerivation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -456,22 +490,24 @@ void DerivationGoal::inputsRealised()
|
||||||
drvs. */
|
drvs. */
|
||||||
: true);
|
: true);
|
||||||
},
|
},
|
||||||
|
[&](const DerivationType::Impure &) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}, drvType.raw());
|
}, drvType.raw());
|
||||||
|
|
||||||
if (resolveDrv)
|
if (resolveDrv && !fullDrv.inputDrvs.empty()) {
|
||||||
{
|
|
||||||
settings.requireExperimentalFeature(Xp::CaDerivations);
|
settings.requireExperimentalFeature(Xp::CaDerivations);
|
||||||
|
|
||||||
/* We are be able to resolve this derivation based on the
|
/* We are be able to resolve this derivation based on the
|
||||||
now-known results of dependencies. If so, we become a stub goal
|
now-known results of dependencies. If so, we become a
|
||||||
aliasing that resolved derivation goal */
|
stub goal aliasing that resolved derivation goal. */
|
||||||
std::optional attempt = fullDrv.tryResolve(worker.store);
|
std::optional attempt = fullDrv.tryResolve(worker.store, inputDrvOutputs);
|
||||||
assert(attempt);
|
assert(attempt);
|
||||||
Derivation drvResolved { *std::move(attempt) };
|
Derivation drvResolved { *std::move(attempt) };
|
||||||
|
|
||||||
auto pathResolved = writeDerivation(worker.store, drvResolved);
|
auto pathResolved = writeDerivation(worker.store, drvResolved);
|
||||||
|
|
||||||
auto msg = fmt("Resolved derivation: '%s' -> '%s'",
|
auto msg = fmt("resolved derivation: '%s' -> '%s'",
|
||||||
worker.store.printStorePath(drvPath),
|
worker.store.printStorePath(drvPath),
|
||||||
worker.store.printStorePath(pathResolved));
|
worker.store.printStorePath(pathResolved));
|
||||||
act = std::make_unique<Activity>(*logger, lvlInfo, actBuildWaiting, msg,
|
act = std::make_unique<Activity>(*logger, lvlInfo, actBuildWaiting, msg,
|
||||||
|
|
@ -492,21 +528,13 @@ void DerivationGoal::inputsRealised()
|
||||||
/* Add the relevant output closures of the input derivation
|
/* Add the relevant output closures of the input derivation
|
||||||
`i' as input paths. Only add the closures of output paths
|
`i' as input paths. Only add the closures of output paths
|
||||||
that are specified as inputs. */
|
that are specified as inputs. */
|
||||||
assert(worker.evalStore.isValidPath(drvPath));
|
for (auto & j : wantedDepOutputs)
|
||||||
auto outputs = worker.evalStore.queryPartialDerivationOutputMap(depDrvPath);
|
if (auto outPath = get(inputDrvOutputs, { depDrvPath, j }))
|
||||||
for (auto & j : wantedDepOutputs) {
|
worker.store.computeFSClosure(*outPath, inputPaths);
|
||||||
if (outputs.count(j) > 0) {
|
else
|
||||||
auto optRealizedInput = outputs.at(j);
|
|
||||||
if (!optRealizedInput)
|
|
||||||
throw Error(
|
|
||||||
"derivation '%s' requires output '%s' from input derivation '%s', which is supposedly realized already, yet we still don't know what path corresponds to that output",
|
|
||||||
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath));
|
|
||||||
worker.store.computeFSClosure(*optRealizedInput, inputPaths);
|
|
||||||
} else
|
|
||||||
throw Error(
|
throw Error(
|
||||||
"derivation '%s' requires non-existent output '%s' from input derivation '%s'",
|
"derivation '%s' requires non-existent output '%s' from input derivation '%s'",
|
||||||
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath));
|
worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -928,7 +956,7 @@ void DerivationGoal::buildDone()
|
||||||
st =
|
st =
|
||||||
dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
|
dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
|
||||||
statusOk(status) ? BuildResult::OutputRejected :
|
statusOk(status) ? BuildResult::OutputRejected :
|
||||||
derivationType.isImpure() || diskFull ? BuildResult::TransientFailure :
|
!derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure :
|
||||||
BuildResult::PermanentFailure;
|
BuildResult::PermanentFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -949,60 +977,53 @@ void DerivationGoal::buildDone()
|
||||||
|
|
||||||
void DerivationGoal::resolvedFinished()
|
void DerivationGoal::resolvedFinished()
|
||||||
{
|
{
|
||||||
|
trace("resolved derivation finished");
|
||||||
|
|
||||||
assert(resolvedDrvGoal);
|
assert(resolvedDrvGoal);
|
||||||
auto resolvedDrv = *resolvedDrvGoal->drv;
|
auto resolvedDrv = *resolvedDrvGoal->drv;
|
||||||
|
auto & resolvedResult = resolvedDrvGoal->buildResult;
|
||||||
auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv);
|
|
||||||
|
|
||||||
StorePathSet outputPaths;
|
|
||||||
|
|
||||||
// `wantedOutputs` might be empty, which means “all the outputs”
|
|
||||||
auto realWantedOutputs = wantedOutputs;
|
|
||||||
if (realWantedOutputs.empty())
|
|
||||||
realWantedOutputs = resolvedDrv.outputNames();
|
|
||||||
|
|
||||||
DrvOutputs builtOutputs;
|
DrvOutputs builtOutputs;
|
||||||
|
|
||||||
for (auto & wantedOutput : realWantedOutputs) {
|
if (resolvedResult.success()) {
|
||||||
assert(initialOutputs.count(wantedOutput) != 0);
|
auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv);
|
||||||
assert(resolvedHashes.count(wantedOutput) != 0);
|
|
||||||
auto realisation = worker.store.queryRealisation(
|
StorePathSet outputPaths;
|
||||||
DrvOutput{resolvedHashes.at(wantedOutput), wantedOutput}
|
|
||||||
);
|
// `wantedOutputs` might be empty, which means “all the outputs”
|
||||||
// We've just built it, but maybe the build failed, in which case the
|
auto realWantedOutputs = wantedOutputs;
|
||||||
// realisation won't be there
|
if (realWantedOutputs.empty())
|
||||||
if (realisation) {
|
realWantedOutputs = resolvedDrv.outputNames();
|
||||||
auto newRealisation = *realisation;
|
|
||||||
newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput};
|
for (auto & wantedOutput : realWantedOutputs) {
|
||||||
newRealisation.signatures.clear();
|
assert(initialOutputs.count(wantedOutput) != 0);
|
||||||
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath);
|
assert(resolvedHashes.count(wantedOutput) != 0);
|
||||||
signRealisation(newRealisation);
|
auto realisation = resolvedResult.builtOutputs.at(
|
||||||
worker.store.registerDrvOutput(newRealisation);
|
DrvOutput { resolvedHashes.at(wantedOutput), wantedOutput });
|
||||||
outputPaths.insert(realisation->outPath);
|
if (drv->type().isPure()) {
|
||||||
builtOutputs.emplace(realisation->id, *realisation);
|
auto newRealisation = realisation;
|
||||||
} else {
|
newRealisation.id = DrvOutput { initialOutputs.at(wantedOutput).outputHash, wantedOutput };
|
||||||
// If we don't have a realisation, then it must mean that something
|
newRealisation.signatures.clear();
|
||||||
// failed when building the resolved drv
|
if (!drv->type().isFixed())
|
||||||
assert(!buildResult.success());
|
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath);
|
||||||
|
signRealisation(newRealisation);
|
||||||
|
worker.store.registerDrvOutput(newRealisation);
|
||||||
|
}
|
||||||
|
outputPaths.insert(realisation.outPath);
|
||||||
|
builtOutputs.emplace(realisation.id, realisation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runPostBuildHook(
|
||||||
|
worker.store,
|
||||||
|
*logger,
|
||||||
|
drvPath,
|
||||||
|
outputPaths
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
runPostBuildHook(
|
auto status = resolvedResult.status;
|
||||||
worker.store,
|
if (status == BuildResult::AlreadyValid)
|
||||||
*logger,
|
status = BuildResult::ResolvesToAlreadyValid;
|
||||||
drvPath,
|
|
||||||
outputPaths
|
|
||||||
);
|
|
||||||
|
|
||||||
auto status = [&]() {
|
|
||||||
auto & resolvedResult = resolvedDrvGoal->buildResult;
|
|
||||||
switch (resolvedResult.status) {
|
|
||||||
case BuildResult::AlreadyValid:
|
|
||||||
return BuildResult::ResolvesToAlreadyValid;
|
|
||||||
default:
|
|
||||||
return resolvedResult.status;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
||||||
done(status, std::move(builtOutputs));
|
done(status, std::move(builtOutputs));
|
||||||
}
|
}
|
||||||
|
|
@ -1251,6 +1272,7 @@ void DerivationGoal::flushLine()
|
||||||
|
|
||||||
std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap()
|
std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap()
|
||||||
{
|
{
|
||||||
|
assert(drv->type().isPure());
|
||||||
if (!useDerivation || drv->type().hasKnownOutputPaths()) {
|
if (!useDerivation || drv->type().hasKnownOutputPaths()) {
|
||||||
std::map<std::string, std::optional<StorePath>> res;
|
std::map<std::string, std::optional<StorePath>> res;
|
||||||
for (auto & [name, output] : drv->outputs)
|
for (auto & [name, output] : drv->outputs)
|
||||||
|
|
@ -1263,6 +1285,7 @@ std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDeri
|
||||||
|
|
||||||
OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
||||||
{
|
{
|
||||||
|
assert(drv->type().isPure());
|
||||||
if (!useDerivation || drv->type().hasKnownOutputPaths()) {
|
if (!useDerivation || drv->type().hasKnownOutputPaths()) {
|
||||||
OutputPathMap res;
|
OutputPathMap res;
|
||||||
for (auto & [name, output] : drv->outputsAndOptPaths(worker.store))
|
for (auto & [name, output] : drv->outputsAndOptPaths(worker.store))
|
||||||
|
|
@ -1276,6 +1299,8 @@ OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
||||||
|
|
||||||
std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
||||||
{
|
{
|
||||||
|
if (!drv->type().isPure()) return { false, {} };
|
||||||
|
|
||||||
bool checkHash = buildMode == bmRepair;
|
bool checkHash = buildMode == bmRepair;
|
||||||
auto wantedOutputsLeft = wantedOutputs;
|
auto wantedOutputsLeft = wantedOutputs;
|
||||||
DrvOutputs validOutputs;
|
DrvOutputs validOutputs;
|
||||||
|
|
@ -1319,6 +1344,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(drvOutput, Realisation { drvOutput, info.known->path });
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we requested all the outputs via the empty set, we are always fine.
|
// If we requested all the outputs via the empty set, we are always fine.
|
||||||
// If we requested specific elements, the loop above removes all the valid
|
// If we requested specific elements, the loop above removes all the valid
|
||||||
// ones, so any that are left must be invalid.
|
// ones, so any that are left must be invalid.
|
||||||
|
|
@ -1356,9 +1382,7 @@ void DerivationGoal::done(
|
||||||
{
|
{
|
||||||
buildResult.status = status;
|
buildResult.status = status;
|
||||||
if (ex)
|
if (ex)
|
||||||
// FIXME: strip: "error: "
|
buildResult.errorMsg = fmt("%s", normaltxt(ex->info().msg));
|
||||||
buildResult.errorMsg = ex->what();
|
|
||||||
amDone(buildResult.success() ? ecSuccess : ecFailed, ex);
|
|
||||||
if (buildResult.status == BuildResult::TimedOut)
|
if (buildResult.status == BuildResult::TimedOut)
|
||||||
worker.timedOut = true;
|
worker.timedOut = true;
|
||||||
if (buildResult.status == BuildResult::PermanentFailure)
|
if (buildResult.status == BuildResult::PermanentFailure)
|
||||||
|
|
@ -1385,7 +1409,21 @@ void DerivationGoal::done(
|
||||||
fs.open(traceBuiltOutputsFile, std::fstream::out);
|
fs.open(traceBuiltOutputsFile, std::fstream::out);
|
||||||
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
amDone(buildResult.success() ? ecSuccess : ecFailed, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
|
||||||
|
{
|
||||||
|
Goal::waiteeDone(waitee, result);
|
||||||
|
|
||||||
|
if (waitee->buildResult.success())
|
||||||
|
if (auto bfd = std::get_if<DerivedPath::Built>(&waitee->buildResult.path))
|
||||||
|
for (auto & [output, realisation] : waitee->buildResult.builtOutputs)
|
||||||
|
inputDrvOutputs.insert_or_assign(
|
||||||
|
{ bfd->drvPath, output.outputName },
|
||||||
|
realisation.outPath);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -57,12 +57,21 @@ struct DerivationGoal : public Goal
|
||||||
them. */
|
them. */
|
||||||
StringSet wantedOutputs;
|
StringSet wantedOutputs;
|
||||||
|
|
||||||
|
/* Mapping from input derivations + output names to actual store
|
||||||
|
paths. This is filled in by waiteeDone() as each dependency
|
||||||
|
finishes, before inputsRealised() is reached, */
|
||||||
|
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
|
||||||
|
|
||||||
/* Whether additional wanted outputs have been added. */
|
/* Whether additional wanted outputs have been added. */
|
||||||
bool needRestart = false;
|
bool needRestart = false;
|
||||||
|
|
||||||
/* Whether to retry substituting the outputs after building the
|
/* Whether to retry substituting the outputs after building the
|
||||||
inputs. */
|
inputs. This is done in case of an incomplete closure. */
|
||||||
bool retrySubstitution;
|
bool retrySubstitution = false;
|
||||||
|
|
||||||
|
/* 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. */
|
||||||
std::unique_ptr<Derivation> drv;
|
std::unique_ptr<Derivation> drv;
|
||||||
|
|
@ -225,6 +234,8 @@ struct DerivationGoal : public Goal
|
||||||
DrvOutputs builtOutputs = {},
|
DrvOutputs builtOutputs = {},
|
||||||
std::optional<Error> ex = {});
|
std::optional<Error> ex = {});
|
||||||
|
|
||||||
|
void waiteeDone(GoalPtr waitee, ExitCode result) override;
|
||||||
|
|
||||||
StorePathSet exportReferences(const StorePathSet & storePaths);
|
StorePathSet exportReferences(const StorePathSet & storePaths);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ void DrvOutputSubstitutionGoal::tryNext()
|
||||||
if (subs.size() == 0) {
|
if (subs.size() == 0) {
|
||||||
/* None left. Terminate this goal and let someone else deal
|
/* None left. Terminate this goal and let someone else deal
|
||||||
with it. */
|
with it. */
|
||||||
debug("drv output '%s' is required, but there is no substituter that can provide it", id.to_string());
|
debug("derivation output '%s' is required, but there is no substituter that can provide it", id.to_string());
|
||||||
|
|
||||||
/* Hack: don't indicate failure if there were no substituters.
|
/* Hack: don't indicate failure if there were no substituters.
|
||||||
In that case the calling derivation should just do a
|
In that case the calling derivation should just do a
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ void Goal::addWaitee(GoalPtr waitee)
|
||||||
|
|
||||||
void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
|
void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
|
||||||
{
|
{
|
||||||
assert(waitees.find(waitee) != waitees.end());
|
assert(waitees.count(waitee));
|
||||||
waitees.erase(waitee);
|
waitees.erase(waitee);
|
||||||
|
|
||||||
trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size()));
|
trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size()));
|
||||||
|
|
|
||||||
|
|
@ -40,21 +40,21 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
||||||
WeakGoals waiters;
|
WeakGoals waiters;
|
||||||
|
|
||||||
/* Number of goals we are/were waiting for that have failed. */
|
/* Number of goals we are/were waiting for that have failed. */
|
||||||
unsigned int nrFailed;
|
size_t nrFailed = 0;
|
||||||
|
|
||||||
/* Number of substitution goals we are/were waiting for that
|
/* Number of substitution goals we are/were waiting for that
|
||||||
failed because there are no substituters. */
|
failed because there are no substituters. */
|
||||||
unsigned int nrNoSubstituters;
|
size_t nrNoSubstituters = 0;
|
||||||
|
|
||||||
/* Number of substitution goals we are/were waiting for that
|
/* Number of substitution goals we are/were waiting for that
|
||||||
failed because they had unsubstitutable references. */
|
failed because they had unsubstitutable references. */
|
||||||
unsigned int nrIncompleteClosure;
|
size_t nrIncompleteClosure = 0;
|
||||||
|
|
||||||
/* Name of this goal for debugging purposes. */
|
/* Name of this goal for debugging purposes. */
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
/* Whether the goal is finished. */
|
/* Whether the goal is finished. */
|
||||||
ExitCode exitCode;
|
ExitCode exitCode = ecBusy;
|
||||||
|
|
||||||
/* Build result. */
|
/* Build result. */
|
||||||
BuildResult buildResult;
|
BuildResult buildResult;
|
||||||
|
|
@ -65,10 +65,7 @@ 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) }
|
, buildResult { .path = std::move(path) }
|
||||||
{
|
{ }
|
||||||
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
|
|
||||||
exitCode = ecBusy;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~Goal()
|
virtual ~Goal()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -395,7 +395,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
else if (settings.sandboxMode == smDisabled)
|
else if (settings.sandboxMode == smDisabled)
|
||||||
useChroot = false;
|
useChroot = false;
|
||||||
else if (settings.sandboxMode == smRelaxed)
|
else if (settings.sandboxMode == smRelaxed)
|
||||||
useChroot = !(derivationType.isImpure()) && !noChroot;
|
useChroot = derivationType.isSandboxed() && !noChroot;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & localStore = getLocalStore();
|
auto & localStore = getLocalStore();
|
||||||
|
|
@ -608,7 +608,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
"nogroup:x:65534:\n", sandboxGid()));
|
"nogroup:x:65534:\n", sandboxGid()));
|
||||||
|
|
||||||
/* Create /etc/hosts with localhost entry. */
|
/* Create /etc/hosts with localhost entry. */
|
||||||
if (!(derivationType.isImpure()))
|
if (derivationType.isSandboxed())
|
||||||
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
|
writeFile(chrootRootDir + "/etc/hosts", "127.0.0.1 localhost\n::1 localhost\n");
|
||||||
|
|
||||||
/* Make the closure of the inputs available in the chroot,
|
/* Make the closure of the inputs available in the chroot,
|
||||||
|
|
@ -704,6 +704,9 @@ void LocalDerivationGoal::startBuilder()
|
||||||
|
|
||||||
/* Run the builder. */
|
/* Run the builder. */
|
||||||
printMsg(lvlChatty, "executing builder '%1%'", drv->builder);
|
printMsg(lvlChatty, "executing builder '%1%'", drv->builder);
|
||||||
|
printMsg(lvlChatty, "using builder args '%1%'", concatStringsSep(" ", drv->args));
|
||||||
|
for (auto & i : drv->env)
|
||||||
|
printMsg(lvlVomit, "setting builder env variable '%1%'='%2%'", i.first, i.second);
|
||||||
|
|
||||||
/* Create the log file. */
|
/* Create the log file. */
|
||||||
Path logFile = openLogFile();
|
Path logFile = openLogFile();
|
||||||
|
|
@ -797,7 +800,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
us.
|
us.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!(derivationType.isImpure()))
|
if (derivationType.isSandboxed())
|
||||||
privateNetwork = true;
|
privateNetwork = true;
|
||||||
|
|
||||||
userNamespaceSync.create();
|
userNamespaceSync.create();
|
||||||
|
|
@ -1061,7 +1064,7 @@ void LocalDerivationGoal::initEnv()
|
||||||
to the builder is generally impure, but the output of
|
to the builder is generally impure, but the output of
|
||||||
fixed-output derivations is by definition pure (since we
|
fixed-output derivations is by definition pure (since we
|
||||||
already know the cryptographic hash of the output). */
|
already know the cryptographic hash of the output). */
|
||||||
if (derivationType.isImpure()) {
|
if (!derivationType.isSandboxed()) {
|
||||||
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings()))
|
for (auto & i : parsedDrv->getStringsAttr("impureEnvVars").value_or(Strings()))
|
||||||
env[i] = getEnv(i).value_or("");
|
env[i] = getEnv(i).value_or("");
|
||||||
}
|
}
|
||||||
|
|
@ -1675,7 +1678,7 @@ void LocalDerivationGoal::runChild()
|
||||||
/* Fixed-output derivations typically need to access the
|
/* Fixed-output derivations typically need to access the
|
||||||
network, so give them access to /etc/resolv.conf and so
|
network, so give them access to /etc/resolv.conf and so
|
||||||
on. */
|
on. */
|
||||||
if (derivationType.isImpure()) {
|
if (!derivationType.isSandboxed()) {
|
||||||
// Only use nss functions to resolve hosts and
|
// Only use nss functions to resolve hosts and
|
||||||
// services. Don’t use it for anything else that may
|
// services. Don’t use it for anything else that may
|
||||||
// be configured for this system. This limits the
|
// be configured for this system. This limits the
|
||||||
|
|
@ -1919,7 +1922,7 @@ void LocalDerivationGoal::runChild()
|
||||||
|
|
||||||
sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
|
sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
|
||||||
|
|
||||||
if (derivationType.isImpure())
|
if (!derivationType.isSandboxed())
|
||||||
sandboxProfile += "(import \"sandbox-network.sb\")\n";
|
sandboxProfile += "(import \"sandbox-network.sb\")\n";
|
||||||
|
|
||||||
/* Add the output paths we'll use at build-time to the chroot */
|
/* Add the output paths we'll use at build-time to the chroot */
|
||||||
|
|
@ -2400,6 +2403,13 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
assert(false);
|
assert(false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[&](const DerivationOutput::Impure & doi) {
|
||||||
|
return newInfoFromCA(DerivationOutput::CAFloating {
|
||||||
|
.method = doi.method,
|
||||||
|
.hashType = doi.hashType,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
}, output.raw());
|
}, output.raw());
|
||||||
|
|
||||||
/* FIXME: set proper permissions in restorePath() so
|
/* FIXME: set proper permissions in restorePath() so
|
||||||
|
|
@ -2610,11 +2620,14 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
},
|
},
|
||||||
.outPath = newInfo.path
|
.outPath = newInfo.path
|
||||||
};
|
};
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)
|
||||||
|
&& drv->type().isPure())
|
||||||
|
{
|
||||||
signRealisation(thisRealisation);
|
signRealisation(thisRealisation);
|
||||||
worker.store.registerDrvOutput(thisRealisation);
|
worker.store.registerDrvOutput(thisRealisation);
|
||||||
}
|
}
|
||||||
builtOutputs.emplace(thisRealisation.id, thisRealisation);
|
if (wantOutput(outputName, wantedOutputs))
|
||||||
|
builtOutputs.emplace(thisRealisation.id, thisRealisation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builtOutputs;
|
return builtOutputs;
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,16 @@ PathSubstitutionGoal::~PathSubstitutionGoal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::done(ExitCode result, BuildResult::Status status)
|
void PathSubstitutionGoal::done(
|
||||||
|
ExitCode result,
|
||||||
|
BuildResult::Status status,
|
||||||
|
std::optional<std::string> errorMsg)
|
||||||
{
|
{
|
||||||
buildResult.status = status;
|
buildResult.status = status;
|
||||||
|
if (errorMsg) {
|
||||||
|
debug(*errorMsg);
|
||||||
|
buildResult.errorMsg = *errorMsg;
|
||||||
|
}
|
||||||
amDone(result);
|
amDone(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,12 +74,14 @@ void PathSubstitutionGoal::tryNext()
|
||||||
if (subs.size() == 0) {
|
if (subs.size() == 0) {
|
||||||
/* None left. Terminate this goal and let someone else deal
|
/* None left. Terminate this goal and let someone else deal
|
||||||
with it. */
|
with it. */
|
||||||
debug("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath));
|
|
||||||
|
|
||||||
/* Hack: don't indicate failure if there were no substituters.
|
/* Hack: don't indicate failure if there were no substituters.
|
||||||
In that case the calling derivation should just do a
|
In that case the calling derivation should just do a
|
||||||
build. */
|
build. */
|
||||||
done(substituterFailed ? ecFailed : ecNoSubstituters, BuildResult::NoSubstituters);
|
done(
|
||||||
|
substituterFailed ? ecFailed : ecNoSubstituters,
|
||||||
|
BuildResult::NoSubstituters,
|
||||||
|
fmt("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath)));
|
||||||
|
|
||||||
if (substituterFailed) {
|
if (substituterFailed) {
|
||||||
worker.failedSubstitutions++;
|
worker.failedSubstitutions++;
|
||||||
|
|
@ -169,10 +178,10 @@ void PathSubstitutionGoal::referencesValid()
|
||||||
trace("all references realised");
|
trace("all references realised");
|
||||||
|
|
||||||
if (nrFailed > 0) {
|
if (nrFailed > 0) {
|
||||||
debug("some references of path '%s' could not be realised", worker.store.printStorePath(storePath));
|
|
||||||
done(
|
done(
|
||||||
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
||||||
BuildResult::DependencyFailed);
|
BuildResult::DependencyFailed,
|
||||||
|
fmt("some references of path '%s' could not be realised", worker.store.printStorePath(storePath)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,10 @@ struct PathSubstitutionGoal : public Goal
|
||||||
/* Time substitution started. */
|
/* Time substitution started. */
|
||||||
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
std::chrono::time_point<std::chrono::steady_clock> startTime;
|
||||||
|
|
||||||
void done(ExitCode result, BuildResult::Status status);
|
void done(
|
||||||
|
ExitCode result,
|
||||||
|
BuildResult::Status status,
|
||||||
|
std::optional<std::string> errorMsg = {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||||
|
|
|
||||||
|
|
@ -47,9 +47,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The files below are special-cased to that they don't show up
|
/* The files below are special-cased to that they don't show
|
||||||
* in user profiles, either because they are useless, or
|
* up in user profiles, either because they are useless, or
|
||||||
* because they would cauase pointless collisions (e.g., each
|
* because they would cause pointless collisions (e.g., each
|
||||||
* Python package brings its own
|
* Python package brings its own
|
||||||
* `$out/lib/pythonX.Y/site-packages/easy-install.pth'.)
|
* `$out/lib/pythonX.Y/site-packages/easy-install.pth'.)
|
||||||
*/
|
*/
|
||||||
|
|
@ -57,7 +57,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
||||||
hasSuffix(srcFile, "/nix-support") ||
|
hasSuffix(srcFile, "/nix-support") ||
|
||||||
hasSuffix(srcFile, "/perllocal.pod") ||
|
hasSuffix(srcFile, "/perllocal.pod") ||
|
||||||
hasSuffix(srcFile, "/info/dir") ||
|
hasSuffix(srcFile, "/info/dir") ||
|
||||||
hasSuffix(srcFile, "/log"))
|
hasSuffix(srcFile, "/log") ||
|
||||||
|
hasSuffix(srcFile, "/manifest.nix") ||
|
||||||
|
hasSuffix(srcFile, "/manifest.json"))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
else if (S_ISDIR(srcSt.st_mode)) {
|
else if (S_ISDIR(srcSt.st_mode)) {
|
||||||
|
|
|
||||||
|
|
@ -25,26 +25,42 @@ std::optional<StorePath> DerivationOutput::path(const Store & store, std::string
|
||||||
[](const DerivationOutput::Deferred &) -> std::optional<StorePath> {
|
[](const DerivationOutput::Deferred &) -> std::optional<StorePath> {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
},
|
},
|
||||||
|
[](const DerivationOutput::Impure &) -> std::optional<StorePath> {
|
||||||
|
return std::nullopt;
|
||||||
|
},
|
||||||
}, raw());
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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,
|
hash.method, hash.hash,
|
||||||
outputPathName(drvName, outputName));
|
outputPathName(drvName, outputName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool DerivationType::isCA() const {
|
bool DerivationType::isCA() const
|
||||||
|
{
|
||||||
/* Normally we do the full `std::visit` to make sure we have
|
/* Normally we do the full `std::visit` to make sure we have
|
||||||
exhaustively handled all variants, but so long as there is a
|
exhaustively handled all variants, but so long as there is a
|
||||||
variant called `ContentAddressed`, it must be the only one for
|
variant called `ContentAddressed`, it must be the only one for
|
||||||
which `isCA` is true for this to make sense!. */
|
which `isCA` is true for this to make sense!. */
|
||||||
return std::holds_alternative<ContentAddressed>(raw());
|
return std::visit(overloaded {
|
||||||
|
[](const InputAddressed & ia) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
[](const ContentAddressed & ca) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DerivationType::isFixed() const {
|
bool DerivationType::isFixed() const
|
||||||
|
{
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[](const InputAddressed & ia) {
|
[](const InputAddressed & ia) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -52,10 +68,14 @@ bool DerivationType::isFixed() const {
|
||||||
[](const ContentAddressed & ca) {
|
[](const ContentAddressed & ca) {
|
||||||
return ca.fixed;
|
return ca.fixed;
|
||||||
},
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
}, raw());
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DerivationType::hasKnownOutputPaths() const {
|
bool DerivationType::hasKnownOutputPaths() const
|
||||||
|
{
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[](const InputAddressed & ia) {
|
[](const InputAddressed & ia) {
|
||||||
return !ia.deferred;
|
return !ia.deferred;
|
||||||
|
|
@ -63,17 +83,40 @@ bool DerivationType::hasKnownOutputPaths() const {
|
||||||
[](const ContentAddressed & ca) {
|
[](const ContentAddressed & ca) {
|
||||||
return ca.fixed;
|
return ca.fixed;
|
||||||
},
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
}, raw());
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool DerivationType::isImpure() const {
|
bool DerivationType::isSandboxed() const
|
||||||
|
{
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[](const InputAddressed & ia) {
|
[](const InputAddressed & ia) {
|
||||||
return false;
|
return true;
|
||||||
},
|
},
|
||||||
[](const ContentAddressed & ca) {
|
[](const ContentAddressed & ca) {
|
||||||
return !ca.pure;
|
return ca.sandboxed;
|
||||||
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}, raw());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool DerivationType::isPure() const
|
||||||
|
{
|
||||||
|
return std::visit(overloaded {
|
||||||
|
[](const InputAddressed & ia) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[](const ContentAddressed & ca) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
}, raw());
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
@ -176,7 +219,14 @@ static DerivationOutput parseDerivationOutput(const Store & store,
|
||||||
hashAlgo = hashAlgo.substr(2);
|
hashAlgo = hashAlgo.substr(2);
|
||||||
}
|
}
|
||||||
const auto hashType = parseHashType(hashAlgo);
|
const auto hashType = parseHashType(hashAlgo);
|
||||||
if (hash != "") {
|
if (hash == "impure") {
|
||||||
|
settings.requireExperimentalFeature(Xp::ImpureDerivations);
|
||||||
|
assert(pathS == "");
|
||||||
|
return DerivationOutput::Impure {
|
||||||
|
.method = std::move(method),
|
||||||
|
.hashType = std::move(hashType),
|
||||||
|
};
|
||||||
|
} else if (hash != "") {
|
||||||
validatePath(pathS);
|
validatePath(pathS);
|
||||||
return DerivationOutput::CAFixed {
|
return DerivationOutput::CAFixed {
|
||||||
.hash = FixedOutputHash {
|
.hash = FixedOutputHash {
|
||||||
|
|
@ -345,6 +395,12 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
|
},
|
||||||
|
[&](const DerivationOutputImpure & doi) {
|
||||||
|
// FIXME
|
||||||
|
s += ','; printUnquotedString(s, "");
|
||||||
|
s += ','; printUnquotedString(s, makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType));
|
||||||
|
s += ','; printUnquotedString(s, "impure");
|
||||||
}
|
}
|
||||||
}, i.second.raw());
|
}, i.second.raw());
|
||||||
s += ')';
|
s += ')';
|
||||||
|
|
@ -410,8 +466,14 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName
|
||||||
|
|
||||||
DerivationType BasicDerivation::type() const
|
DerivationType BasicDerivation::type() const
|
||||||
{
|
{
|
||||||
std::set<std::string_view> inputAddressedOutputs, fixedCAOutputs, floatingCAOutputs, deferredIAOutputs;
|
std::set<std::string_view>
|
||||||
|
inputAddressedOutputs,
|
||||||
|
fixedCAOutputs,
|
||||||
|
floatingCAOutputs,
|
||||||
|
deferredIAOutputs,
|
||||||
|
impureOutputs;
|
||||||
std::optional<HashType> floatingHashType;
|
std::optional<HashType> floatingHashType;
|
||||||
|
|
||||||
for (auto & i : outputs) {
|
for (auto & i : outputs) {
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const DerivationOutput::InputAddressed &) {
|
[&](const DerivationOutput::InputAddressed &) {
|
||||||
|
|
@ -426,43 +488,78 @@ DerivationType BasicDerivation::type() const
|
||||||
floatingHashType = dof.hashType;
|
floatingHashType = dof.hashType;
|
||||||
} else {
|
} else {
|
||||||
if (*floatingHashType != dof.hashType)
|
if (*floatingHashType != dof.hashType)
|
||||||
throw Error("All floating outputs must use the same hash type");
|
throw Error("all floating outputs must use the same hash type");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[&](const DerivationOutput::Deferred &) {
|
[&](const DerivationOutput::Deferred &) {
|
||||||
deferredIAOutputs.insert(i.first);
|
deferredIAOutputs.insert(i.first);
|
||||||
|
},
|
||||||
|
[&](const DerivationOutput::Impure &) {
|
||||||
|
impureOutputs.insert(i.first);
|
||||||
},
|
},
|
||||||
}, i.second.raw());
|
}, i.second.raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
if (inputAddressedOutputs.empty()
|
||||||
throw Error("Must have at least one output");
|
&& fixedCAOutputs.empty()
|
||||||
} else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
&& floatingCAOutputs.empty()
|
||||||
|
&& deferredIAOutputs.empty()
|
||||||
|
&& impureOutputs.empty())
|
||||||
|
throw Error("must have at least one output");
|
||||||
|
|
||||||
|
if (!inputAddressedOutputs.empty()
|
||||||
|
&& fixedCAOutputs.empty()
|
||||||
|
&& floatingCAOutputs.empty()
|
||||||
|
&& deferredIAOutputs.empty()
|
||||||
|
&& impureOutputs.empty())
|
||||||
return DerivationType::InputAddressed {
|
return DerivationType::InputAddressed {
|
||||||
.deferred = false,
|
.deferred = false,
|
||||||
};
|
};
|
||||||
} else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
|
||||||
|
if (inputAddressedOutputs.empty()
|
||||||
|
&& !fixedCAOutputs.empty()
|
||||||
|
&& floatingCAOutputs.empty()
|
||||||
|
&& deferredIAOutputs.empty()
|
||||||
|
&& impureOutputs.empty())
|
||||||
|
{
|
||||||
if (fixedCAOutputs.size() > 1)
|
if (fixedCAOutputs.size() > 1)
|
||||||
// FIXME: Experimental feature?
|
// FIXME: Experimental feature?
|
||||||
throw Error("Only one fixed output is allowed for now");
|
throw Error("only one fixed output is allowed for now");
|
||||||
if (*fixedCAOutputs.begin() != "out")
|
if (*fixedCAOutputs.begin() != "out")
|
||||||
throw Error("Single fixed output must be named \"out\"");
|
throw Error("single fixed output must be named \"out\"");
|
||||||
return DerivationType::ContentAddressed {
|
return DerivationType::ContentAddressed {
|
||||||
.pure = false,
|
.sandboxed = false,
|
||||||
.fixed = true,
|
.fixed = true,
|
||||||
};
|
};
|
||||||
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
}
|
||||||
|
|
||||||
|
if (inputAddressedOutputs.empty()
|
||||||
|
&& fixedCAOutputs.empty()
|
||||||
|
&& !floatingCAOutputs.empty()
|
||||||
|
&& deferredIAOutputs.empty()
|
||||||
|
&& impureOutputs.empty())
|
||||||
return DerivationType::ContentAddressed {
|
return DerivationType::ContentAddressed {
|
||||||
.pure = true,
|
.sandboxed = true,
|
||||||
.fixed = false,
|
.fixed = false,
|
||||||
};
|
};
|
||||||
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) {
|
|
||||||
|
if (inputAddressedOutputs.empty()
|
||||||
|
&& fixedCAOutputs.empty()
|
||||||
|
&& floatingCAOutputs.empty()
|
||||||
|
&& !deferredIAOutputs.empty()
|
||||||
|
&& impureOutputs.empty())
|
||||||
return DerivationType::InputAddressed {
|
return DerivationType::InputAddressed {
|
||||||
.deferred = true,
|
.deferred = true,
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
throw Error("Can't mix derivation output types");
|
if (inputAddressedOutputs.empty()
|
||||||
}
|
&& fixedCAOutputs.empty()
|
||||||
|
&& floatingCAOutputs.empty()
|
||||||
|
&& deferredIAOutputs.empty()
|
||||||
|
&& !impureOutputs.empty())
|
||||||
|
return DerivationType::Impure { };
|
||||||
|
|
||||||
|
throw Error("can't mix derivation output types");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -474,7 +571,7 @@ Sync<DrvHashes> drvHashes;
|
||||||
/* Look up the derivation by value and memoize the
|
/* Look up the derivation by value and memoize the
|
||||||
`hashDerivationModulo` call.
|
`hashDerivationModulo` call.
|
||||||
*/
|
*/
|
||||||
static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath & drvPath)
|
static const DrvHash pathDerivationModulo(Store & store, const StorePath & drvPath)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto hashes = drvHashes.lock();
|
auto hashes = drvHashes.lock();
|
||||||
|
|
@ -509,7 +606,7 @@ static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath &
|
||||||
don't leak the provenance of fixed outputs, reducing pointless cache
|
don't leak the provenance of fixed outputs, reducing pointless cache
|
||||||
misses as the build itself won't know this.
|
misses as the build itself won't know this.
|
||||||
*/
|
*/
|
||||||
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
|
DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
|
||||||
{
|
{
|
||||||
auto type = drv.type();
|
auto type = drv.type();
|
||||||
|
|
||||||
|
|
@ -524,7 +621,20 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
|
||||||
+ store.printStorePath(dof.path(store, drv.name, i.first)));
|
+ store.printStorePath(dof.path(store, drv.name, i.first)));
|
||||||
outputHashes.insert_or_assign(i.first, std::move(hash));
|
outputHashes.insert_or_assign(i.first, std::move(hash));
|
||||||
}
|
}
|
||||||
return outputHashes;
|
return DrvHash {
|
||||||
|
.hashes = outputHashes,
|
||||||
|
.kind = DrvHash::Kind::Regular,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!type.isPure()) {
|
||||||
|
std::map<std::string, Hash> outputHashes;
|
||||||
|
for (const auto & [outputName, _] : drv.outputs)
|
||||||
|
outputHashes.insert_or_assign(outputName, impureOutputHash);
|
||||||
|
return DrvHash {
|
||||||
|
.hashes = outputHashes,
|
||||||
|
.kind = DrvHash::Kind::Deferred,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto kind = std::visit(overloaded {
|
auto kind = std::visit(overloaded {
|
||||||
|
|
@ -538,67 +648,41 @@ DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool m
|
||||||
? DrvHash::Kind::Regular
|
? DrvHash::Kind::Regular
|
||||||
: DrvHash::Kind::Deferred;
|
: DrvHash::Kind::Deferred;
|
||||||
},
|
},
|
||||||
|
[](const DerivationType::Impure &) -> DrvHash::Kind {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
}, drv.type().raw());
|
}, drv.type().raw());
|
||||||
|
|
||||||
/* For other derivations, replace the inputs paths with recursive
|
|
||||||
calls to this function. */
|
|
||||||
std::map<std::string, StringSet> inputs2;
|
std::map<std::string, StringSet> inputs2;
|
||||||
for (auto & [drvPath, inputOutputs0] : drv.inputDrvs) {
|
for (auto & [drvPath, inputOutputs0] : drv.inputDrvs) {
|
||||||
// Avoid lambda capture restriction with standard / Clang
|
// Avoid lambda capture restriction with standard / Clang
|
||||||
auto & inputOutputs = inputOutputs0;
|
auto & inputOutputs = inputOutputs0;
|
||||||
const auto & res = pathDerivationModulo(store, drvPath);
|
const auto & res = pathDerivationModulo(store, drvPath);
|
||||||
std::visit(overloaded {
|
if (res.kind == DrvHash::Kind::Deferred)
|
||||||
// Regular non-CA derivation, replace derivation
|
kind = DrvHash::Kind::Deferred;
|
||||||
[&](const DrvHash & drvHash) {
|
for (auto & outputName : inputOutputs) {
|
||||||
kind |= drvHash.kind;
|
const auto h = res.hashes.at(outputName);
|
||||||
inputs2.insert_or_assign(drvHash.hash.to_string(Base16, false), inputOutputs);
|
inputs2[h.to_string(Base16, false)].insert(outputName);
|
||||||
},
|
}
|
||||||
// CA derivation's output hashes
|
|
||||||
[&](const CaOutputHashes & outputHashes) {
|
|
||||||
std::set<std::string> justOut = { "out" };
|
|
||||||
for (auto & output : inputOutputs) {
|
|
||||||
/* Put each one in with a single "out" output.. */
|
|
||||||
const auto h = outputHashes.at(output);
|
|
||||||
inputs2.insert_or_assign(
|
|
||||||
h.to_string(Base16, false),
|
|
||||||
justOut);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}, res.raw());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
|
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
|
||||||
|
|
||||||
return DrvHash { .hash = hash, .kind = kind };
|
std::map<std::string, Hash> outputHashes;
|
||||||
}
|
for (const auto & [outputName, _] : drv.outputs) {
|
||||||
|
outputHashes.insert_or_assign(outputName, hash);
|
||||||
|
|
||||||
void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept
|
|
||||||
{
|
|
||||||
switch (other) {
|
|
||||||
case DrvHash::Kind::Regular:
|
|
||||||
break;
|
|
||||||
case DrvHash::Kind::Deferred:
|
|
||||||
self = other;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return DrvHash {
|
||||||
|
.hashes = outputHashes,
|
||||||
|
.kind = kind,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation & drv)
|
std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation & drv)
|
||||||
{
|
{
|
||||||
std::map<std::string, Hash> res;
|
return hashDerivationModulo(store, drv, true).hashes;
|
||||||
std::visit(overloaded {
|
|
||||||
[&](const DrvHash & drvHash) {
|
|
||||||
for (auto & outputName : drv.outputNames()) {
|
|
||||||
res.insert({outputName, drvHash.hash});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[&](const CaOutputHashes & outputHashes) {
|
|
||||||
res = outputHashes;
|
|
||||||
},
|
|
||||||
}, hashDerivationModulo(store, drv, true).raw());
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -625,7 +709,8 @@ StringSet BasicDerivation::outputNames() const
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & store) const {
|
DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & store) const
|
||||||
|
{
|
||||||
DerivationOutputsAndOptPaths outsAndOptPaths;
|
DerivationOutputsAndOptPaths outsAndOptPaths;
|
||||||
for (auto output : outputs)
|
for (auto output : outputs)
|
||||||
outsAndOptPaths.insert(std::make_pair(
|
outsAndOptPaths.insert(std::make_pair(
|
||||||
|
|
@ -636,7 +721,8 @@ DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & s
|
||||||
return outsAndOptPaths;
|
return outsAndOptPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view BasicDerivation::nameFromPath(const StorePath & drvPath) {
|
std::string_view BasicDerivation::nameFromPath(const StorePath & drvPath)
|
||||||
|
{
|
||||||
auto nameWithSuffix = drvPath.name();
|
auto nameWithSuffix = drvPath.name();
|
||||||
constexpr std::string_view extension = ".drv";
|
constexpr std::string_view extension = ".drv";
|
||||||
assert(hasSuffix(nameWithSuffix, extension));
|
assert(hasSuffix(nameWithSuffix, extension));
|
||||||
|
|
@ -698,6 +784,11 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
|
||||||
<< ""
|
<< ""
|
||||||
<< "";
|
<< "";
|
||||||
},
|
},
|
||||||
|
[&](const DerivationOutput::Impure & doi) {
|
||||||
|
out << ""
|
||||||
|
<< (makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType))
|
||||||
|
<< "impure";
|
||||||
|
},
|
||||||
}, i.second.raw());
|
}, i.second.raw());
|
||||||
}
|
}
|
||||||
worker_proto::write(store, out, drv.inputSrcs);
|
worker_proto::write(store, out, drv.inputSrcs);
|
||||||
|
|
@ -723,21 +814,19 @@ std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites) {
|
static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites)
|
||||||
|
{
|
||||||
debug("Rewriting the derivation");
|
for (auto & rewrite : rewrites) {
|
||||||
|
|
||||||
for (auto &rewrite: rewrites) {
|
|
||||||
debug("rewriting %s as %s", rewrite.first, rewrite.second);
|
debug("rewriting %s as %s", rewrite.first, rewrite.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
drv.builder = rewriteStrings(drv.builder, rewrites);
|
drv.builder = rewriteStrings(drv.builder, rewrites);
|
||||||
for (auto & arg: drv.args) {
|
for (auto & arg : drv.args) {
|
||||||
arg = rewriteStrings(arg, rewrites);
|
arg = rewriteStrings(arg, rewrites);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringPairs newEnv;
|
StringPairs newEnv;
|
||||||
for (auto & envVar: drv.env) {
|
for (auto & envVar : drv.env) {
|
||||||
auto envName = rewriteStrings(envVar.first, rewrites);
|
auto envName = rewriteStrings(envVar.first, rewrites);
|
||||||
auto envValue = rewriteStrings(envVar.second, rewrites);
|
auto envValue = rewriteStrings(envVar.second, rewrites);
|
||||||
newEnv.emplace(envName, envValue);
|
newEnv.emplace(envName, envValue);
|
||||||
|
|
@ -747,7 +836,7 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
|
||||||
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
|
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
|
||||||
for (auto & [outputName, output] : drv.outputs) {
|
for (auto & [outputName, output] : drv.outputs) {
|
||||||
if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) {
|
if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) {
|
||||||
auto & h = hashModulo.requireNoFixedNonDeferred();
|
auto & h = hashModulo.hashes.at(outputName);
|
||||||
auto outPath = store.makeOutputPath(outputName, h, drv.name);
|
auto outPath = store.makeOutputPath(outputName, h, drv.name);
|
||||||
drv.env[outputName] = store.printStorePath(outPath);
|
drv.env[outputName] = store.printStorePath(outPath);
|
||||||
output = DerivationOutput::InputAddressed {
|
output = DerivationOutput::InputAddressed {
|
||||||
|
|
@ -758,55 +847,48 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const Hash & DrvHashModulo::requireNoFixedNonDeferred() const {
|
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) const
|
||||||
auto * drvHashOpt = std::get_if<DrvHash>(&raw());
|
|
||||||
assert(drvHashOpt);
|
|
||||||
assert(drvHashOpt->kind == DrvHash::Kind::Regular);
|
|
||||||
return drvHashOpt->hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool tryResolveInput(
|
|
||||||
Store & store, StorePathSet & inputSrcs, StringMap & inputRewrites,
|
|
||||||
const StorePath & inputDrv, const StringSet & inputOutputs)
|
|
||||||
{
|
{
|
||||||
auto inputDrvOutputs = store.queryPartialDerivationOutputMap(inputDrv);
|
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
|
||||||
|
|
||||||
auto getOutput = [&](const std::string & outputName) {
|
for (auto & input : inputDrvs)
|
||||||
auto & actualPathOpt = inputDrvOutputs.at(outputName);
|
for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(input.first))
|
||||||
if (!actualPathOpt)
|
if (outputPath)
|
||||||
warn("output %s of input %s missing, aborting the resolving",
|
inputDrvOutputs.insert_or_assign({input.first, outputName}, *outputPath);
|
||||||
outputName,
|
|
||||||
store.printStorePath(inputDrv)
|
|
||||||
);
|
|
||||||
return actualPathOpt;
|
|
||||||
};
|
|
||||||
|
|
||||||
for (auto & outputName : inputOutputs) {
|
return tryResolve(store, inputDrvOutputs);
|
||||||
auto actualPathOpt = getOutput(outputName);
|
|
||||||
if (!actualPathOpt) return false;
|
|
||||||
auto actualPath = *actualPathOpt;
|
|
||||||
inputRewrites.emplace(
|
|
||||||
downstreamPlaceholder(store, inputDrv, outputName),
|
|
||||||
store.printStorePath(actualPath));
|
|
||||||
inputSrcs.insert(std::move(actualPath));
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
|
std::optional<BasicDerivation> Derivation::tryResolve(
|
||||||
|
Store & store,
|
||||||
|
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const
|
||||||
|
{
|
||||||
BasicDerivation resolved { *this };
|
BasicDerivation resolved { *this };
|
||||||
|
|
||||||
// Input paths that we'll want to rewrite in the derivation
|
// Input paths that we'll want to rewrite in the derivation
|
||||||
StringMap inputRewrites;
|
StringMap inputRewrites;
|
||||||
|
|
||||||
for (auto & [inputDrv, inputOutputs] : inputDrvs)
|
for (auto & [inputDrv, inputOutputs] : inputDrvs) {
|
||||||
if (!tryResolveInput(store, resolved.inputSrcs, inputRewrites, inputDrv, inputOutputs))
|
for (auto & outputName : inputOutputs) {
|
||||||
return std::nullopt;
|
if (auto actualPath = get(inputDrvOutputs, { inputDrv, outputName })) {
|
||||||
|
inputRewrites.emplace(
|
||||||
|
downstreamPlaceholder(store, inputDrv, outputName),
|
||||||
|
store.printStorePath(*actualPath));
|
||||||
|
resolved.inputSrcs.insert(*actualPath);
|
||||||
|
} else {
|
||||||
|
warn("output '%s' of input '%s' missing, aborting the resolving",
|
||||||
|
outputName,
|
||||||
|
store.printStorePath(inputDrv));
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rewriteDerivation(store, resolved, inputRewrites);
|
rewriteDerivation(store, resolved, inputRewrites);
|
||||||
|
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Hash impureOutputHash = hashString(htSHA256, "impure");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,15 +41,26 @@ struct DerivationOutputCAFloating
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Input-addressed output which depends on a (CA) derivation whose hash isn't
|
/* Input-addressed output which depends on a (CA) derivation whose hash isn't
|
||||||
* known atm
|
* known yet.
|
||||||
*/
|
*/
|
||||||
struct DerivationOutputDeferred {};
|
struct DerivationOutputDeferred {};
|
||||||
|
|
||||||
|
/* Impure output which is moved to a content-addressed location (like
|
||||||
|
CAFloating) but isn't registered as a realization.
|
||||||
|
*/
|
||||||
|
struct DerivationOutputImpure
|
||||||
|
{
|
||||||
|
/* information used for expected hash computation */
|
||||||
|
FileIngestionMethod method;
|
||||||
|
HashType hashType;
|
||||||
|
};
|
||||||
|
|
||||||
typedef std::variant<
|
typedef std::variant<
|
||||||
DerivationOutputInputAddressed,
|
DerivationOutputInputAddressed,
|
||||||
DerivationOutputCAFixed,
|
DerivationOutputCAFixed,
|
||||||
DerivationOutputCAFloating,
|
DerivationOutputCAFloating,
|
||||||
DerivationOutputDeferred
|
DerivationOutputDeferred,
|
||||||
|
DerivationOutputImpure
|
||||||
> _DerivationOutputRaw;
|
> _DerivationOutputRaw;
|
||||||
|
|
||||||
struct DerivationOutput : _DerivationOutputRaw
|
struct DerivationOutput : _DerivationOutputRaw
|
||||||
|
|
@ -61,6 +72,7 @@ struct DerivationOutput : _DerivationOutputRaw
|
||||||
using CAFixed = DerivationOutputCAFixed;
|
using CAFixed = DerivationOutputCAFixed;
|
||||||
using CAFloating = DerivationOutputCAFloating;
|
using CAFloating = DerivationOutputCAFloating;
|
||||||
using Deferred = DerivationOutputDeferred;
|
using Deferred = DerivationOutputDeferred;
|
||||||
|
using Impure = DerivationOutputImpure;
|
||||||
|
|
||||||
/* Note, when you use this function you should make sure that you're passing
|
/* Note, when you use this function you should make sure that you're passing
|
||||||
the right derivation name. When in doubt, you should use the safer
|
the right derivation name. When in doubt, you should use the safer
|
||||||
|
|
@ -90,13 +102,17 @@ struct DerivationType_InputAddressed {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DerivationType_ContentAddressed {
|
struct DerivationType_ContentAddressed {
|
||||||
bool pure;
|
bool sandboxed;
|
||||||
bool fixed;
|
bool fixed;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct DerivationType_Impure {
|
||||||
|
};
|
||||||
|
|
||||||
typedef std::variant<
|
typedef std::variant<
|
||||||
DerivationType_InputAddressed,
|
DerivationType_InputAddressed,
|
||||||
DerivationType_ContentAddressed
|
DerivationType_ContentAddressed,
|
||||||
|
DerivationType_Impure
|
||||||
> _DerivationTypeRaw;
|
> _DerivationTypeRaw;
|
||||||
|
|
||||||
struct DerivationType : _DerivationTypeRaw {
|
struct DerivationType : _DerivationTypeRaw {
|
||||||
|
|
@ -104,7 +120,7 @@ struct DerivationType : _DerivationTypeRaw {
|
||||||
using Raw::Raw;
|
using Raw::Raw;
|
||||||
using InputAddressed = DerivationType_InputAddressed;
|
using InputAddressed = DerivationType_InputAddressed;
|
||||||
using ContentAddressed = DerivationType_ContentAddressed;
|
using ContentAddressed = DerivationType_ContentAddressed;
|
||||||
|
using Impure = DerivationType_Impure;
|
||||||
|
|
||||||
/* Do the outputs of the derivation have paths calculated from their content,
|
/* Do the outputs of the derivation have paths calculated from their content,
|
||||||
or from the derivation itself? */
|
or from the derivation itself? */
|
||||||
|
|
@ -114,10 +130,18 @@ struct DerivationType : _DerivationTypeRaw {
|
||||||
non-CA derivations. */
|
non-CA derivations. */
|
||||||
bool isFixed() const;
|
bool isFixed() const;
|
||||||
|
|
||||||
/* Is the derivation impure and needs to access non-deterministic resources, or
|
/* Whether the derivation is fully sandboxed. If false, the
|
||||||
pure and can be sandboxed? Note that whether or not we actually sandbox the
|
sandbox is opened up, e.g. the derivation has access to the
|
||||||
derivation is controlled separately. Never true for non-CA derivations. */
|
network. Note that whether or not we actually sandbox the
|
||||||
bool isImpure() const;
|
derivation is controlled separately. Always true for non-CA
|
||||||
|
derivations. */
|
||||||
|
bool isSandboxed() const;
|
||||||
|
|
||||||
|
/* Whether the derivation is expected to produce the same result
|
||||||
|
every time, and therefore it only needs to be built once. This
|
||||||
|
is only false for derivations that have the attribute '__impure
|
||||||
|
= true'. */
|
||||||
|
bool isPure() const;
|
||||||
|
|
||||||
/* Does the derivation knows its own output paths?
|
/* Does the derivation knows its own output paths?
|
||||||
Only true when there's no floating-ca derivation involved in the
|
Only true when there's no floating-ca derivation involved in the
|
||||||
|
|
@ -173,7 +197,14 @@ struct Derivation : BasicDerivation
|
||||||
added directly to input sources.
|
added directly to input sources.
|
||||||
|
|
||||||
2. Input placeholders are replaced with realized input store paths. */
|
2. Input placeholders are replaced with realized input store paths. */
|
||||||
std::optional<BasicDerivation> tryResolve(Store & store);
|
std::optional<BasicDerivation> tryResolve(Store & store) const;
|
||||||
|
|
||||||
|
/* Like the above, but instead of querying the Nix database for
|
||||||
|
realisations, uses a given mapping from input derivation paths
|
||||||
|
+ output names to actual output store paths. */
|
||||||
|
std::optional<BasicDerivation> tryResolve(
|
||||||
|
Store & store,
|
||||||
|
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const;
|
||||||
|
|
||||||
Derivation() = default;
|
Derivation() = default;
|
||||||
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
|
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
|
||||||
|
|
@ -202,14 +233,16 @@ bool isDerivation(const std::string & fileName);
|
||||||
the output name is "out". */
|
the output name is "out". */
|
||||||
std::string outputPathName(std::string_view drvName, std::string_view outputName);
|
std::string outputPathName(std::string_view drvName, std::string_view outputName);
|
||||||
|
|
||||||
// known CA drv's output hashes, current just for fixed-output derivations
|
|
||||||
// whose output hashes are always known since they are fixed up-front.
|
|
||||||
typedef std::map<std::string, Hash> CaOutputHashes;
|
|
||||||
|
|
||||||
|
// The hashes modulo of a derivation.
|
||||||
|
//
|
||||||
|
// Each output is given a hash, although in practice only the content-addressed
|
||||||
|
// derivations (fixed-output or not) will have a different hash for each
|
||||||
|
// output.
|
||||||
struct DrvHash {
|
struct DrvHash {
|
||||||
Hash hash;
|
std::map<std::string, Hash> hashes;
|
||||||
|
|
||||||
enum struct Kind: bool {
|
enum struct Kind : bool {
|
||||||
// Statically determined derivations.
|
// Statically determined derivations.
|
||||||
// This hash will be directly used to compute the output paths
|
// This hash will be directly used to compute the output paths
|
||||||
Regular,
|
Regular,
|
||||||
|
|
@ -222,28 +255,6 @@ struct DrvHash {
|
||||||
|
|
||||||
void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept;
|
void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept;
|
||||||
|
|
||||||
typedef std::variant<
|
|
||||||
// Regular normalized derivation hash, and whether it was deferred (because
|
|
||||||
// an ancestor derivation is a floating content addressed derivation).
|
|
||||||
DrvHash,
|
|
||||||
// Fixed-output derivation hashes
|
|
||||||
CaOutputHashes
|
|
||||||
> _DrvHashModuloRaw;
|
|
||||||
|
|
||||||
struct DrvHashModulo : _DrvHashModuloRaw {
|
|
||||||
using Raw = _DrvHashModuloRaw;
|
|
||||||
using Raw::Raw;
|
|
||||||
|
|
||||||
/* Get hash, throwing if it is per-output CA hashes or a
|
|
||||||
deferred Drv hash.
|
|
||||||
*/
|
|
||||||
const Hash & requireNoFixedNonDeferred() const;
|
|
||||||
|
|
||||||
inline const Raw & raw() const {
|
|
||||||
return static_cast<const Raw &>(*this);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/* Returns hashes with the details of fixed-output subderivations
|
/* Returns hashes with the details of fixed-output subderivations
|
||||||
expunged.
|
expunged.
|
||||||
|
|
||||||
|
|
@ -267,16 +278,18 @@ struct DrvHashModulo : _DrvHashModuloRaw {
|
||||||
ATerm, after subderivations have been likewise expunged from that
|
ATerm, after subderivations have been likewise expunged from that
|
||||||
derivation.
|
derivation.
|
||||||
*/
|
*/
|
||||||
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs);
|
DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Return a map associating each output to a hash that uniquely identifies its
|
Return a map associating each output to a hash that uniquely identifies its
|
||||||
derivation (modulo the self-references).
|
derivation (modulo the self-references).
|
||||||
|
|
||||||
|
FIXME: what is the Hash in this map?
|
||||||
*/
|
*/
|
||||||
std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv);
|
std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation & drv);
|
||||||
|
|
||||||
/* Memoisation of hashDerivationModulo(). */
|
/* Memoisation of hashDerivationModulo(). */
|
||||||
typedef std::map<StorePath, DrvHashModulo> DrvHashes;
|
typedef std::map<StorePath, DrvHash> DrvHashes;
|
||||||
|
|
||||||
// FIXME: global, though at least thread-safe.
|
// FIXME: global, though at least thread-safe.
|
||||||
extern Sync<DrvHashes> drvHashes;
|
extern Sync<DrvHashes> drvHashes;
|
||||||
|
|
@ -306,4 +319,6 @@ std::string hashPlaceholder(const std::string_view outputName);
|
||||||
dependency which is a CA derivation. */
|
dependency which is a CA derivation. */
|
||||||
std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath, std::string_view outputName);
|
std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath, std::string_view outputName);
|
||||||
|
|
||||||
|
extern const Hash impureOutputHash;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,9 @@ struct DerivedPathOpaque {
|
||||||
nlohmann::json toJSON(ref<Store> store) const;
|
nlohmann::json toJSON(ref<Store> store) const;
|
||||||
std::string to_string(const Store & store) const;
|
std::string to_string(const Store & store) const;
|
||||||
static DerivedPathOpaque parse(const Store & store, std::string_view);
|
static DerivedPathOpaque parse(const Store & store, std::string_view);
|
||||||
|
|
||||||
|
bool operator < (const DerivedPathOpaque & b) const
|
||||||
|
{ return path < b.path; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -46,6 +49,9 @@ struct DerivedPathBuilt {
|
||||||
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);
|
static DerivedPathBuilt parse(const Store & store, std::string_view);
|
||||||
nlohmann::json toJSON(ref<Store> store) const;
|
nlohmann::json toJSON(ref<Store> store) const;
|
||||||
|
|
||||||
|
bool operator < (const DerivedPathBuilt & b) const
|
||||||
|
{ return std::make_pair(drvPath, outputs) < std::make_pair(b.drvPath, b.outputs); }
|
||||||
};
|
};
|
||||||
|
|
||||||
using _DerivedPathRaw = std::variant<
|
using _DerivedPathRaw = std::variant<
|
||||||
|
|
|
||||||
|
|
@ -123,8 +123,6 @@ public:
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args);
|
FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args);
|
||||||
|
|
||||||
virtual const char* sname() const override { return "FileTransferError"; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isUri(std::string_view s);
|
bool isUri(std::string_view s);
|
||||||
|
|
|
||||||
|
|
@ -695,16 +695,15 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
|
||||||
// combinations that are currently prohibited.
|
// combinations that are currently prohibited.
|
||||||
drv.type();
|
drv.type();
|
||||||
|
|
||||||
std::optional<Hash> h;
|
std::optional<DrvHash> hashesModulo;
|
||||||
for (auto & i : drv.outputs) {
|
for (auto & i : drv.outputs) {
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const DerivationOutput::InputAddressed & doia) {
|
[&](const DerivationOutput::InputAddressed & doia) {
|
||||||
if (!h) {
|
if (!hashesModulo) {
|
||||||
// somewhat expensive so we do lazily
|
// somewhat expensive so we do lazily
|
||||||
auto h0 = hashDerivationModulo(*this, drv, true);
|
hashesModulo = hashDerivationModulo(*this, drv, true);
|
||||||
h = h0.requireNoFixedNonDeferred();
|
|
||||||
}
|
}
|
||||||
StorePath recomputed = makeOutputPath(i.first, *h, drvName);
|
StorePath recomputed = makeOutputPath(i.first, hashesModulo->hashes.at(i.first), drvName);
|
||||||
if (doia.path != recomputed)
|
if (doia.path != recomputed)
|
||||||
throw Error("derivation '%s' has incorrect output '%s', should be '%s'",
|
throw Error("derivation '%s' has incorrect output '%s', should be '%s'",
|
||||||
printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed));
|
printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed));
|
||||||
|
|
@ -720,6 +719,9 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
|
||||||
[&](const DerivationOutput::Deferred &) {
|
[&](const DerivationOutput::Deferred &) {
|
||||||
/* Nothing to check */
|
/* Nothing to check */
|
||||||
},
|
},
|
||||||
|
[&](const DerivationOutput::Impure &) {
|
||||||
|
/* Nothing to check */
|
||||||
|
},
|
||||||
}, i.second.raw());
|
}, i.second.raw());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
80
src/libstore/make-content-addressed.cc
Normal file
80
src/libstore/make-content-addressed.cc
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
#include "make-content-addressed.hh"
|
||||||
|
#include "references.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
std::map<StorePath, StorePath> makeContentAddressed(
|
||||||
|
Store & srcStore,
|
||||||
|
Store & dstStore,
|
||||||
|
const StorePathSet & storePaths)
|
||||||
|
{
|
||||||
|
StorePathSet closure;
|
||||||
|
srcStore.computeFSClosure(storePaths, closure);
|
||||||
|
|
||||||
|
auto paths = srcStore.topoSortPaths(closure);
|
||||||
|
|
||||||
|
std::reverse(paths.begin(), paths.end());
|
||||||
|
|
||||||
|
std::map<StorePath, StorePath> remappings;
|
||||||
|
|
||||||
|
for (auto & path : paths) {
|
||||||
|
auto pathS = srcStore.printStorePath(path);
|
||||||
|
auto oldInfo = srcStore.queryPathInfo(path);
|
||||||
|
std::string oldHashPart(path.hashPart());
|
||||||
|
|
||||||
|
StringSink sink;
|
||||||
|
srcStore.narFromPath(path, sink);
|
||||||
|
|
||||||
|
StringMap rewrites;
|
||||||
|
|
||||||
|
StorePathSet references;
|
||||||
|
bool hasSelfReference = false;
|
||||||
|
for (auto & ref : oldInfo->references) {
|
||||||
|
if (ref == path)
|
||||||
|
hasSelfReference = true;
|
||||||
|
else {
|
||||||
|
auto i = remappings.find(ref);
|
||||||
|
auto replacement = i != remappings.end() ? i->second : ref;
|
||||||
|
// FIXME: warn about unremapped paths?
|
||||||
|
if (replacement != ref)
|
||||||
|
rewrites.insert_or_assign(srcStore.printStorePath(ref), srcStore.printStorePath(replacement));
|
||||||
|
references.insert(std::move(replacement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sink.s = rewriteStrings(sink.s, rewrites);
|
||||||
|
|
||||||
|
HashModuloSink hashModuloSink(htSHA256, oldHashPart);
|
||||||
|
hashModuloSink(sink.s);
|
||||||
|
|
||||||
|
auto narModuloHash = hashModuloSink.finish().first;
|
||||||
|
|
||||||
|
auto dstPath = dstStore.makeFixedOutputPath(
|
||||||
|
FileIngestionMethod::Recursive, narModuloHash, path.name(), references, hasSelfReference);
|
||||||
|
|
||||||
|
printInfo("rewriting '%s' to '%s'", pathS, srcStore.printStorePath(dstPath));
|
||||||
|
|
||||||
|
StringSink sink2;
|
||||||
|
RewritingSink rsink2(oldHashPart, std::string(dstPath.hashPart()), sink2);
|
||||||
|
rsink2(sink.s);
|
||||||
|
rsink2.flush();
|
||||||
|
|
||||||
|
ValidPathInfo info { dstPath, hashString(htSHA256, sink2.s) };
|
||||||
|
info.references = std::move(references);
|
||||||
|
if (hasSelfReference) info.references.insert(info.path);
|
||||||
|
info.narSize = sink.s.size();
|
||||||
|
info.ca = FixedOutputHash {
|
||||||
|
.method = FileIngestionMethod::Recursive,
|
||||||
|
.hash = narModuloHash,
|
||||||
|
};
|
||||||
|
|
||||||
|
StringSource source(sink2.s);
|
||||||
|
dstStore.addToStore(info, source);
|
||||||
|
|
||||||
|
remappings.insert_or_assign(std::move(path), std::move(info.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
return remappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
12
src/libstore/make-content-addressed.hh
Normal file
12
src/libstore/make-content-addressed.hh
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
std::map<StorePath, StorePath> makeContentAddressed(
|
||||||
|
Store & srcStore,
|
||||||
|
Store & dstStore,
|
||||||
|
const StorePathSet & storePaths);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -277,15 +277,15 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
|
||||||
{
|
{
|
||||||
std::set<Realisation> inputRealisations;
|
std::set<Realisation> inputRealisations;
|
||||||
|
|
||||||
for (const auto& [inputDrv, outputNames] : drv.inputDrvs) {
|
for (const auto & [inputDrv, outputNames] : drv.inputDrvs) {
|
||||||
auto outputHashes =
|
auto outputHashes =
|
||||||
staticOutputHashes(store, store.readDerivation(inputDrv));
|
staticOutputHashes(store, store.readDerivation(inputDrv));
|
||||||
for (const auto& outputName : outputNames) {
|
for (const auto & outputName : outputNames) {
|
||||||
auto thisRealisation = store.queryRealisation(
|
auto thisRealisation = store.queryRealisation(
|
||||||
DrvOutput{outputHashes.at(outputName), outputName});
|
DrvOutput{outputHashes.at(outputName), outputName});
|
||||||
if (!thisRealisation)
|
if (!thisRealisation)
|
||||||
throw Error(
|
throw Error(
|
||||||
"output '%s' of derivation '%s' isn’t built", outputName,
|
"output '%s' of derivation '%s' isn't built", outputName,
|
||||||
store.printStorePath(inputDrv));
|
store.printStorePath(inputDrv));
|
||||||
inputRealisations.insert(*thisRealisation);
|
inputRealisations.insert(*thisRealisation);
|
||||||
}
|
}
|
||||||
|
|
@ -295,4 +295,5 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
|
||||||
|
|
||||||
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references);
|
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
static void checkName(std::string_view path, std::string_view name)
|
static void checkName(std::string_view path, std::string_view name)
|
||||||
|
|
@ -41,6 +43,13 @@ bool StorePath::isDerivation() const
|
||||||
|
|
||||||
StorePath StorePath::dummy("ffffffffffffffffffffffffffffffff-x");
|
StorePath StorePath::dummy("ffffffffffffffffffffffffffffffff-x");
|
||||||
|
|
||||||
|
StorePath StorePath::random(std::string_view name)
|
||||||
|
{
|
||||||
|
Hash hash(htSHA1);
|
||||||
|
randombytes_buf(hash.hash, hash.hashSize);
|
||||||
|
return StorePath(hash, name);
|
||||||
|
}
|
||||||
|
|
||||||
StorePath Store::parseStorePath(std::string_view path) const
|
StorePath Store::parseStorePath(std::string_view path) const
|
||||||
{
|
{
|
||||||
auto p = canonPath(std::string(path));
|
auto p = canonPath(std::string(path));
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
static StorePath dummy;
|
static StorePath dummy;
|
||||||
|
|
||||||
|
static StorePath random(std::string_view name);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::set<StorePath> StorePathSet;
|
typedef std::set<StorePath> StorePathSet;
|
||||||
|
|
|
||||||
|
|
@ -215,7 +215,6 @@ void handleSQLiteBusy(const SQLiteBusy & e)
|
||||||
if (now > lastWarned + 10) {
|
if (now > lastWarned + 10) {
|
||||||
lastWarned = now;
|
lastWarned = now;
|
||||||
logWarning({
|
logWarning({
|
||||||
.name = "Sqlite busy",
|
|
||||||
.msg = hintfmt(e.what())
|
.msg = hintfmt(e.what())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,9 @@ namespace nix {
|
||||||
|
|
||||||
const std::string nativeSystem = SYSTEM;
|
const std::string nativeSystem = SYSTEM;
|
||||||
|
|
||||||
BaseError & BaseError::addTrace(std::optional<ErrPos> e, hintformat hint)
|
void BaseError::addTrace(std::optional<ErrPos> e, hintformat hint)
|
||||||
{
|
{
|
||||||
err.traces.push_front(Trace { .pos = e, .hint = hint });
|
err.traces.push_front(Trace { .pos = e, .hint = hint });
|
||||||
return *this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// c++ std::exception descendants must have a 'const char* what()' function.
|
// c++ std::exception descendants must have a 'const char* what()' function.
|
||||||
|
|
@ -22,12 +21,9 @@ const std::string & BaseError::calcWhat() const
|
||||||
if (what_.has_value())
|
if (what_.has_value())
|
||||||
return *what_;
|
return *what_;
|
||||||
else {
|
else {
|
||||||
err.name = sname();
|
|
||||||
|
|
||||||
std::ostringstream oss;
|
std::ostringstream oss;
|
||||||
showErrorInfo(oss, err, loggerSettings.showTrace);
|
showErrorInfo(oss, err, loggerSettings.showTrace);
|
||||||
what_ = oss.str();
|
what_ = oss.str();
|
||||||
|
|
||||||
return *what_;
|
return *what_;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,6 @@ struct Trace {
|
||||||
|
|
||||||
struct ErrorInfo {
|
struct ErrorInfo {
|
||||||
Verbosity level;
|
Verbosity level;
|
||||||
std::string name; // FIXME: rename
|
|
||||||
hintformat msg;
|
hintformat msg;
|
||||||
std::optional<ErrPos> errPos;
|
std::optional<ErrPos> errPos;
|
||||||
std::list<Trace> traces;
|
std::list<Trace> traces;
|
||||||
|
|
@ -162,8 +161,6 @@ public:
|
||||||
: err(e)
|
: err(e)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
virtual const char* sname() const { return "BaseError"; }
|
|
||||||
|
|
||||||
#ifdef EXCEPTION_NEEDS_THROW_SPEC
|
#ifdef EXCEPTION_NEEDS_THROW_SPEC
|
||||||
~BaseError() throw () { };
|
~BaseError() throw () { };
|
||||||
const char * what() const throw () { return calcWhat().c_str(); }
|
const char * what() const throw () { return calcWhat().c_str(); }
|
||||||
|
|
@ -175,12 +172,12 @@ public:
|
||||||
const ErrorInfo & info() const { calcWhat(); return err; }
|
const ErrorInfo & info() const { calcWhat(); return err; }
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
BaseError & addTrace(std::optional<ErrPos> e, const std::string & fs, const Args & ... args)
|
void addTrace(std::optional<ErrPos> e, const std::string & fs, const Args & ... args)
|
||||||
{
|
{
|
||||||
return addTrace(e, hintfmt(fs, args...));
|
addTrace(e, hintfmt(fs, args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseError & addTrace(std::optional<ErrPos> e, hintformat hint);
|
void addTrace(std::optional<ErrPos> e, hintformat hint);
|
||||||
|
|
||||||
bool hasTrace() const { return !err.traces.empty(); }
|
bool hasTrace() const { return !err.traces.empty(); }
|
||||||
};
|
};
|
||||||
|
|
@ -190,7 +187,6 @@ public:
|
||||||
{ \
|
{ \
|
||||||
public: \
|
public: \
|
||||||
using superClass::superClass; \
|
using superClass::superClass; \
|
||||||
virtual const char* sname() const override { return #newClass; } \
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MakeError(Error, BaseError);
|
MakeError(Error, BaseError);
|
||||||
|
|
@ -210,8 +206,6 @@ public:
|
||||||
auto hf = hintfmt(args...);
|
auto hf = hintfmt(args...);
|
||||||
err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
|
err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual const char* sname() const override { return "SysError"; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,10 +7,12 @@ namespace nix {
|
||||||
|
|
||||||
std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
|
std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
|
||||||
{ Xp::CaDerivations, "ca-derivations" },
|
{ Xp::CaDerivations, "ca-derivations" },
|
||||||
|
{ Xp::ImpureDerivations, "impure-derivations" },
|
||||||
{ Xp::Flakes, "flakes" },
|
{ Xp::Flakes, "flakes" },
|
||||||
{ Xp::NixCommand, "nix-command" },
|
{ Xp::NixCommand, "nix-command" },
|
||||||
{ Xp::RecursiveNix, "recursive-nix" },
|
{ Xp::RecursiveNix, "recursive-nix" },
|
||||||
{ Xp::NoUrlLiterals, "no-url-literals" },
|
{ Xp::NoUrlLiterals, "no-url-literals" },
|
||||||
|
{ Xp::FetchClosure, "fetch-closure" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
|
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
|
||||||
|
|
|
||||||
|
|
@ -16,10 +16,12 @@ namespace nix {
|
||||||
enum struct ExperimentalFeature
|
enum struct ExperimentalFeature
|
||||||
{
|
{
|
||||||
CaDerivations,
|
CaDerivations,
|
||||||
|
ImpureDerivations,
|
||||||
Flakes,
|
Flakes,
|
||||||
NixCommand,
|
NixCommand,
|
||||||
RecursiveNix,
|
RecursiveNix,
|
||||||
NoUrlLiterals
|
NoUrlLiterals,
|
||||||
|
FetchClosure,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -47,10 +49,6 @@ public:
|
||||||
ExperimentalFeature missingFeature;
|
ExperimentalFeature missingFeature;
|
||||||
|
|
||||||
MissingExperimentalFeature(ExperimentalFeature);
|
MissingExperimentalFeature(ExperimentalFeature);
|
||||||
virtual const char * sname() const override
|
|
||||||
{
|
|
||||||
return "MissingExperimentalFeature";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ static std::pair<std::optional<HashType>, bool> getParsedTypeAndSRI(std::string_
|
||||||
{
|
{
|
||||||
bool isSRI = false;
|
bool isSRI = false;
|
||||||
|
|
||||||
// Parse the has type before the separater, if there was one.
|
// Parse the hash type before the separator, if there was one.
|
||||||
std::optional<HashType> optParsedType;
|
std::optional<HashType> optParsedType;
|
||||||
{
|
{
|
||||||
auto hashRaw = splitPrefixTo(rest, ':');
|
auto hashRaw = splitPrefixTo(rest, ':');
|
||||||
|
|
|
||||||
|
|
@ -93,13 +93,11 @@ public:
|
||||||
|
|
||||||
std::string gitRev() const
|
std::string gitRev() const
|
||||||
{
|
{
|
||||||
assert(type == htSHA1);
|
|
||||||
return to_string(Base16, false);
|
return to_string(Base16, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string gitShortRev() const
|
std::string gitShortRev() const
|
||||||
{
|
{
|
||||||
assert(type == htSHA1);
|
|
||||||
return std::string(to_string(Base16, false), 0, 7);
|
return std::string(to_string(Base16, false), 0, 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,7 @@ Sink & operator << (Sink & sink, const Error & ex)
|
||||||
sink
|
sink
|
||||||
<< "Error"
|
<< "Error"
|
||||||
<< info.level
|
<< info.level
|
||||||
<< info.name
|
<< "Error" // removed
|
||||||
<< info.msg.str()
|
<< info.msg.str()
|
||||||
<< 0 // FIXME: info.errPos
|
<< 0 // FIXME: info.errPos
|
||||||
<< info.traces.size();
|
<< info.traces.size();
|
||||||
|
|
@ -426,11 +426,10 @@ Error readError(Source & source)
|
||||||
auto type = readString(source);
|
auto type = readString(source);
|
||||||
assert(type == "Error");
|
assert(type == "Error");
|
||||||
auto level = (Verbosity) readInt(source);
|
auto level = (Verbosity) readInt(source);
|
||||||
auto name = readString(source);
|
auto name = readString(source); // removed
|
||||||
auto msg = readString(source);
|
auto msg = readString(source);
|
||||||
ErrorInfo info {
|
ErrorInfo info {
|
||||||
.level = level,
|
.level = level,
|
||||||
.name = name,
|
|
||||||
.msg = hintformat(std::move(format("%s") % msg)),
|
.msg = hintformat(std::move(format("%s") % msg)),
|
||||||
};
|
};
|
||||||
auto havePos = readNum<size_t>(source);
|
auto havePos = readNum<size_t>(source);
|
||||||
|
|
|
||||||
|
|
@ -39,30 +39,32 @@ void TarArchive::check(int err, const std::string & reason)
|
||||||
throw Error(reason, archive_error_string(this->archive));
|
throw Error(reason, archive_error_string(this->archive));
|
||||||
}
|
}
|
||||||
|
|
||||||
TarArchive::TarArchive(Source & source, bool raw)
|
TarArchive::TarArchive(Source & source, bool raw) : buffer(4096)
|
||||||
: source(&source), buffer(4096)
|
|
||||||
{
|
{
|
||||||
init();
|
this->archive = archive_read_new();
|
||||||
if (!raw)
|
this->source = &source;
|
||||||
|
|
||||||
|
if (!raw) {
|
||||||
|
archive_read_support_filter_all(archive);
|
||||||
archive_read_support_format_all(archive);
|
archive_read_support_format_all(archive);
|
||||||
else
|
} else {
|
||||||
|
archive_read_support_filter_all(archive);
|
||||||
archive_read_support_format_raw(archive);
|
archive_read_support_format_raw(archive);
|
||||||
|
archive_read_support_format_empty(archive);
|
||||||
|
}
|
||||||
check(archive_read_open(archive, (void *)this, callback_open, callback_read, callback_close), "Failed to open archive (%s)");
|
check(archive_read_open(archive, (void *)this, callback_open, callback_read, callback_close), "Failed to open archive (%s)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
TarArchive::TarArchive(const Path & path)
|
TarArchive::TarArchive(const Path & path)
|
||||||
{
|
{
|
||||||
init();
|
this->archive = archive_read_new();
|
||||||
|
|
||||||
|
archive_read_support_filter_all(archive);
|
||||||
archive_read_support_format_all(archive);
|
archive_read_support_format_all(archive);
|
||||||
check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s");
|
check(archive_read_open_filename(archive, path.c_str(), 16384), "failed to open archive: %s");
|
||||||
}
|
}
|
||||||
|
|
||||||
void TarArchive::init()
|
|
||||||
{
|
|
||||||
archive = archive_read_new();
|
|
||||||
archive_read_support_filter_all(archive);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TarArchive::close()
|
void TarArchive::close()
|
||||||
{
|
{
|
||||||
check(archive_read_close(this->archive), "Failed to close archive (%s)");
|
check(archive_read_close(this->archive), "Failed to close archive (%s)");
|
||||||
|
|
|
||||||
|
|
@ -17,13 +17,10 @@ struct TarArchive {
|
||||||
// disable copy constructor
|
// disable copy constructor
|
||||||
TarArchive(const TarArchive &) = delete;
|
TarArchive(const TarArchive &) = delete;
|
||||||
|
|
||||||
void init();
|
|
||||||
|
|
||||||
void close();
|
void close();
|
||||||
|
|
||||||
~TarArchive();
|
~TarArchive();
|
||||||
};
|
};
|
||||||
|
|
||||||
void unpackTarfile(Source & source, const Path & destDir);
|
void unpackTarfile(Source & source, const Path & destDir);
|
||||||
|
|
||||||
void unpackTarfile(const Path & tarFile, const Path & destDir);
|
void unpackTarfile(const Path & tarFile, const Path & destDir);
|
||||||
|
|
|
||||||
|
|
@ -71,13 +71,11 @@ void clearEnv()
|
||||||
unsetenv(name.first.c_str());
|
unsetenv(name.first.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void replaceEnv(std::map<std::string, std::string> newEnv)
|
void replaceEnv(const std::map<std::string, std::string> & newEnv)
|
||||||
{
|
{
|
||||||
clearEnv();
|
clearEnv();
|
||||||
for (auto newEnvVar : newEnv)
|
for (auto & newEnvVar : newEnv)
|
||||||
{
|
|
||||||
setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1);
|
setenv(newEnvVar.first.c_str(), newEnvVar.second.c_str(), 1);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1261,9 +1259,9 @@ template<class C> C tokenizeString(std::string_view s, std::string_view separato
|
||||||
{
|
{
|
||||||
C result;
|
C result;
|
||||||
auto pos = s.find_first_not_of(separators, 0);
|
auto pos = s.find_first_not_of(separators, 0);
|
||||||
while (pos != std::string::npos) {
|
while (pos != std::string_view::npos) {
|
||||||
auto end = s.find_first_of(separators, pos + 1);
|
auto end = s.find_first_of(separators, pos + 1);
|
||||||
if (end == std::string::npos) end = s.size();
|
if (end == std::string_view::npos) end = s.size();
|
||||||
result.insert(result.end(), std::string(s, pos, end - pos));
|
result.insert(result.end(), std::string(s, pos, end - pos));
|
||||||
pos = s.find_first_not_of(separators, end);
|
pos = s.find_first_not_of(separators, end);
|
||||||
}
|
}
|
||||||
|
|
@ -1473,6 +1471,7 @@ constexpr char base64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuv
|
||||||
std::string base64Encode(std::string_view s)
|
std::string base64Encode(std::string_view s)
|
||||||
{
|
{
|
||||||
std::string res;
|
std::string res;
|
||||||
|
res.reserve((s.size() + 2) / 3 * 4);
|
||||||
int data = 0, nbits = 0;
|
int data = 0, nbits = 0;
|
||||||
|
|
||||||
for (char c : s) {
|
for (char c : s) {
|
||||||
|
|
@ -1504,6 +1503,9 @@ std::string base64Decode(std::string_view s)
|
||||||
}();
|
}();
|
||||||
|
|
||||||
std::string res;
|
std::string res;
|
||||||
|
// Some sequences are missing the padding consisting of up to two '='.
|
||||||
|
// vvv
|
||||||
|
res.reserve((s.size() + 2) / 4 * 3);
|
||||||
unsigned int d = 0, bits = 0;
|
unsigned int d = 0, bits = 0;
|
||||||
|
|
||||||
for (char c : s) {
|
for (char c : s) {
|
||||||
|
|
@ -1690,7 +1692,9 @@ void setStackSize(size_t stackSize)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if __linux__
|
||||||
static AutoCloseFD fdSavedMountNamespace;
|
static AutoCloseFD fdSavedMountNamespace;
|
||||||
|
#endif
|
||||||
|
|
||||||
void saveMountNamespace()
|
void saveMountNamespace()
|
||||||
{
|
{
|
||||||
|
|
@ -1709,8 +1713,13 @@ void restoreMountNamespace()
|
||||||
{
|
{
|
||||||
#if __linux__
|
#if __linux__
|
||||||
try {
|
try {
|
||||||
|
auto savedCwd = absPath(".");
|
||||||
|
|
||||||
if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1)
|
if (fdSavedMountNamespace && setns(fdSavedMountNamespace.get(), CLONE_NEWNS) == -1)
|
||||||
throw SysError("restoring parent mount namespace");
|
throw SysError("restoring parent mount namespace");
|
||||||
|
if (chdir(savedCwd.c_str()) == -1) {
|
||||||
|
throw SysError("restoring cwd");
|
||||||
|
}
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
debug(e.msg());
|
debug(e.msg());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -105,8 +105,10 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||||
/* Also write a copy of the list of user environment elements to
|
/* Also write a copy of the list of user environment elements to
|
||||||
the store; we need it for future modifications of the
|
the store; we need it for future modifications of the
|
||||||
environment. */
|
environment. */
|
||||||
|
std::ostringstream str;
|
||||||
|
manifest.print(str, true);
|
||||||
auto manifestFile = state.store->addTextToStore("env-manifest.nix",
|
auto manifestFile = state.store->addTextToStore("env-manifest.nix",
|
||||||
fmt("%s", manifest), references);
|
str.str(), references);
|
||||||
|
|
||||||
/* Get the environment builder expression. */
|
/* Get the environment builder expression. */
|
||||||
Value envBuilder;
|
Value envBuilder;
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ UnresolvedApp Installable::toApp(EvalState & state)
|
||||||
|
|
||||||
std::vector<StorePathWithOutputs> context2;
|
std::vector<StorePathWithOutputs> context2;
|
||||||
for (auto & [path, name] : context)
|
for (auto & [path, name] : context)
|
||||||
context2.push_back({state.store->parseStorePath(path), {name}});
|
context2.push_back({path, {name}});
|
||||||
|
|
||||||
return UnresolvedApp{App {
|
return UnresolvedApp{App {
|
||||||
.context = std::move(context2),
|
.context = std::move(context2),
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ using namespace nix;
|
||||||
|
|
||||||
struct CmdBundle : InstallableCommand
|
struct CmdBundle : InstallableCommand
|
||||||
{
|
{
|
||||||
std::string bundler = "github:matthewbauer/nix-bundle";
|
std::string bundler = "github:NixOS/bundlers";
|
||||||
std::optional<Path> outLink;
|
std::optional<Path> outLink;
|
||||||
|
|
||||||
CmdBundle()
|
CmdBundle()
|
||||||
|
|
|
||||||
|
|
@ -204,10 +204,10 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore
|
||||||
output.second = DerivationOutput::Deferred { };
|
output.second = DerivationOutput::Deferred { };
|
||||||
drv.env[output.first] = "";
|
drv.env[output.first] = "";
|
||||||
}
|
}
|
||||||
auto h0 = hashDerivationModulo(*evalStore, drv, true);
|
auto hashesModulo = hashDerivationModulo(*evalStore, drv, true);
|
||||||
const Hash & h = h0.requireNoFixedNonDeferred();
|
|
||||||
|
|
||||||
for (auto & output : drv.outputs) {
|
for (auto & output : drv.outputs) {
|
||||||
|
Hash h = hashesModulo.hashes.at(output.first);
|
||||||
auto outPath = store->makeOutputPath(output.first, h, drv.name);
|
auto outPath = store->makeOutputPath(output.first, h, drv.name);
|
||||||
output.second = DerivationOutput::InputAddressed {
|
output.second = DerivationOutput::InputAddressed {
|
||||||
.path = outPath,
|
.path = outPath,
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,12 @@ std::string formatProtocol(unsigned int proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkPass(const std::string & msg) {
|
bool checkPass(const std::string & msg) {
|
||||||
logger->log(ANSI_GREEN "[PASS] " ANSI_NORMAL + msg);
|
notice(ANSI_GREEN "[PASS] " ANSI_NORMAL + msg);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool checkFail(const std::string & msg) {
|
bool checkFail(const std::string & msg) {
|
||||||
logger->log(ANSI_RED "[FAIL] " ANSI_NORMAL + msg);
|
notice(ANSI_RED "[FAIL] " ANSI_NORMAL + msg);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,8 +24,8 @@ this attribute to the location of the definition of the
|
||||||
`meta.description`, `version` or `name` derivation attributes.
|
`meta.description`, `version` or `name` derivation attributes.
|
||||||
|
|
||||||
The editor to invoke is specified by the `EDITOR` environment
|
The editor to invoke is specified by the `EDITOR` environment
|
||||||
variable. It defaults to `cat`. If the editor is `emacs`, `nano` or
|
variable. It defaults to `cat`. If the editor is `emacs`, `nano`,
|
||||||
`vim`, it is passed the line number of the derivation using the
|
`vim` or `kak`, it is passed the line number of the derivation using
|
||||||
argument `+<lineno>`.
|
the argument `+<lineno>`.
|
||||||
|
|
||||||
)""
|
)""
|
||||||
|
|
|
||||||
|
|
@ -463,7 +463,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
|
|
||||||
for (auto & attr : *v.attrs) {
|
for (auto & attr : *v.attrs) {
|
||||||
std::string name(attr.name);
|
std::string name(attr.name);
|
||||||
if (name != "path" && name != "description")
|
if (name != "path" && name != "description" && name != "welcomeText")
|
||||||
throw Error("template '%s' has unsupported attribute '%s'", attrPath, name);
|
throw Error("template '%s' has unsupported attribute '%s'", attrPath, name);
|
||||||
}
|
}
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
|
|
@ -508,6 +508,7 @@ struct CmdFlakeCheck : FlakeCommand
|
||||||
name == "defaultBundler" ? "bundlers.<system>.default" :
|
name == "defaultBundler" ? "bundlers.<system>.default" :
|
||||||
name == "overlay" ? "overlays.default" :
|
name == "overlay" ? "overlays.default" :
|
||||||
name == "devShell" ? "devShells.<system>.default" :
|
name == "devShell" ? "devShells.<system>.default" :
|
||||||
|
name == "nixosModule" ? "nixosModules.default" :
|
||||||
"";
|
"";
|
||||||
if (replacement != "")
|
if (replacement != "")
|
||||||
warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement);
|
warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement);
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
|
||||||
{"hash-path", {"hash", "path"}},
|
{"hash-path", {"hash", "path"}},
|
||||||
{"ls-nar", {"nar", "ls"}},
|
{"ls-nar", {"nar", "ls"}},
|
||||||
{"ls-store", {"store", "ls"}},
|
{"ls-store", {"store", "ls"}},
|
||||||
{"make-content-addressable", {"store", "make-content-addressable"}},
|
{"make-content-addressable", {"store", "make-content-addressed"}},
|
||||||
{"optimise-store", {"store", "optimise"}},
|
{"optimise-store", {"store", "optimise"}},
|
||||||
{"ping-store", {"store", "ping"}},
|
{"ping-store", {"store", "ping"}},
|
||||||
{"sign-paths", {"store", "sign"}},
|
{"sign-paths", {"store", "sign"}},
|
||||||
|
|
@ -287,6 +287,7 @@ void mainWrapped(int argc, char * * argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (argc == 2 && std::string(argv[1]) == "__dump-builtins") {
|
if (argc == 2 && std::string(argv[1]) == "__dump-builtins") {
|
||||||
|
settings.experimentalFeatures = {Xp::Flakes, Xp::FetchClosure};
|
||||||
evalSettings.pureEval = false;
|
evalSettings.pureEval = false;
|
||||||
EvalState state({}, openStore("dummy://"));
|
EvalState state({}, openStore("dummy://"));
|
||||||
auto res = nlohmann::json::object();
|
auto res = nlohmann::json::object();
|
||||||
|
|
|
||||||
|
|
@ -1,102 +0,0 @@
|
||||||
#include "command.hh"
|
|
||||||
#include "store-api.hh"
|
|
||||||
#include "references.hh"
|
|
||||||
#include "common-args.hh"
|
|
||||||
#include "json.hh"
|
|
||||||
|
|
||||||
using namespace nix;
|
|
||||||
|
|
||||||
struct CmdMakeContentAddressable : StorePathsCommand, MixJSON
|
|
||||||
{
|
|
||||||
CmdMakeContentAddressable()
|
|
||||||
{
|
|
||||||
realiseMode = Realise::Outputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string description() override
|
|
||||||
{
|
|
||||||
return "rewrite a path or closure to content-addressed form";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string doc() override
|
|
||||||
{
|
|
||||||
return
|
|
||||||
#include "make-content-addressable.md"
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
void run(ref<Store> store, StorePaths && storePaths) override
|
|
||||||
{
|
|
||||||
auto paths = store->topoSortPaths(StorePathSet(storePaths.begin(), storePaths.end()));
|
|
||||||
|
|
||||||
std::reverse(paths.begin(), paths.end());
|
|
||||||
|
|
||||||
std::map<StorePath, StorePath> remappings;
|
|
||||||
|
|
||||||
auto jsonRoot = json ? std::make_unique<JSONObject>(std::cout) : nullptr;
|
|
||||||
auto jsonRewrites = json ? std::make_unique<JSONObject>(jsonRoot->object("rewrites")) : nullptr;
|
|
||||||
|
|
||||||
for (auto & path : paths) {
|
|
||||||
auto pathS = store->printStorePath(path);
|
|
||||||
auto oldInfo = store->queryPathInfo(path);
|
|
||||||
std::string oldHashPart(path.hashPart());
|
|
||||||
|
|
||||||
StringSink sink;
|
|
||||||
store->narFromPath(path, sink);
|
|
||||||
|
|
||||||
StringMap rewrites;
|
|
||||||
|
|
||||||
StorePathSet references;
|
|
||||||
bool hasSelfReference = false;
|
|
||||||
for (auto & ref : oldInfo->references) {
|
|
||||||
if (ref == path)
|
|
||||||
hasSelfReference = true;
|
|
||||||
else {
|
|
||||||
auto i = remappings.find(ref);
|
|
||||||
auto replacement = i != remappings.end() ? i->second : ref;
|
|
||||||
// FIXME: warn about unremapped paths?
|
|
||||||
if (replacement != ref)
|
|
||||||
rewrites.insert_or_assign(store->printStorePath(ref), store->printStorePath(replacement));
|
|
||||||
references.insert(std::move(replacement));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sink.s = rewriteStrings(sink.s, rewrites);
|
|
||||||
|
|
||||||
HashModuloSink hashModuloSink(htSHA256, oldHashPart);
|
|
||||||
hashModuloSink(sink.s);
|
|
||||||
|
|
||||||
auto narHash = hashModuloSink.finish().first;
|
|
||||||
|
|
||||||
ValidPathInfo info {
|
|
||||||
store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, path.name(), references, hasSelfReference),
|
|
||||||
narHash,
|
|
||||||
};
|
|
||||||
info.references = std::move(references);
|
|
||||||
if (hasSelfReference) info.references.insert(info.path);
|
|
||||||
info.narSize = sink.s.size();
|
|
||||||
info.ca = FixedOutputHash {
|
|
||||||
.method = FileIngestionMethod::Recursive,
|
|
||||||
.hash = info.narHash,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!json)
|
|
||||||
notice("rewrote '%s' to '%s'", pathS, store->printStorePath(info.path));
|
|
||||||
|
|
||||||
auto source = sinkToSource([&](Sink & nextSink) {
|
|
||||||
RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), nextSink);
|
|
||||||
rsink2(sink.s);
|
|
||||||
rsink2.flush();
|
|
||||||
});
|
|
||||||
|
|
||||||
store->addToStore(info, *source);
|
|
||||||
|
|
||||||
if (json)
|
|
||||||
jsonRewrites->attr(store->printStorePath(path), store->printStorePath(info.path));
|
|
||||||
|
|
||||||
remappings.insert_or_assign(std::move(path), std::move(info.path));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static auto rCmdMakeContentAddressable = registerCommand2<CmdMakeContentAddressable>({"store", "make-content-addressable"});
|
|
||||||
55
src/nix/make-content-addressed.cc
Normal file
55
src/nix/make-content-addressed.cc
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
#include "command.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
#include "make-content-addressed.hh"
|
||||||
|
#include "common-args.hh"
|
||||||
|
#include "json.hh"
|
||||||
|
|
||||||
|
using namespace nix;
|
||||||
|
|
||||||
|
struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand, MixJSON
|
||||||
|
{
|
||||||
|
CmdMakeContentAddressed()
|
||||||
|
{
|
||||||
|
realiseMode = Realise::Outputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string description() override
|
||||||
|
{
|
||||||
|
return "rewrite a path or closure to content-addressed form";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string doc() override
|
||||||
|
{
|
||||||
|
return
|
||||||
|
#include "make-content-addressed.md"
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run(ref<Store> srcStore, StorePaths && storePaths) override
|
||||||
|
{
|
||||||
|
auto dstStore = dstUri.empty() ? openStore() : openStore(dstUri);
|
||||||
|
|
||||||
|
auto remappings = makeContentAddressed(*srcStore, *dstStore,
|
||||||
|
StorePathSet(storePaths.begin(), storePaths.end()));
|
||||||
|
|
||||||
|
if (json) {
|
||||||
|
JSONObject jsonRoot(std::cout);
|
||||||
|
JSONObject jsonRewrites(jsonRoot.object("rewrites"));
|
||||||
|
for (auto & path : storePaths) {
|
||||||
|
auto i = remappings.find(path);
|
||||||
|
assert(i != remappings.end());
|
||||||
|
jsonRewrites.attr(srcStore->printStorePath(path), srcStore->printStorePath(i->second));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (auto & path : storePaths) {
|
||||||
|
auto i = remappings.find(path);
|
||||||
|
assert(i != remappings.end());
|
||||||
|
notice("rewrote '%s' to '%s'",
|
||||||
|
srcStore->printStorePath(path),
|
||||||
|
srcStore->printStorePath(i->second));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static auto rCmdMakeContentAddressed = registerCommand2<CmdMakeContentAddressed>({"store", "make-content-addressed"});
|
||||||
|
|
@ -5,7 +5,7 @@ R""(
|
||||||
* Create a content-addressed representation of the closure of GNU Hello:
|
* Create a content-addressed representation of the closure of GNU Hello:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix store make-content-addressable -r nixpkgs#hello
|
# nix store make-content-addressed nixpkgs#hello
|
||||||
…
|
…
|
||||||
rewrote '/nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10' to '/nix/store/5skmmcb9svys5lj3kbsrjg7vf2irid63-hello-2.10'
|
rewrote '/nix/store/v5sv61sszx301i0x6xysaqzla09nksnd-hello-2.10' to '/nix/store/5skmmcb9svys5lj3kbsrjg7vf2irid63-hello-2.10'
|
||||||
```
|
```
|
||||||
|
|
@ -29,7 +29,7 @@ R""(
|
||||||
system closure:
|
system closure:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix store make-content-addressable -r /run/current-system
|
# nix store make-content-addressed /run/current-system
|
||||||
```
|
```
|
||||||
|
|
||||||
# Description
|
# Description
|
||||||
|
|
@ -62,22 +62,21 @@ struct ProfileElement
|
||||||
return std::tuple(describe(), storePaths) < std::tuple(other.describe(), other.storePaths);
|
return std::tuple(describe(), storePaths) < std::tuple(other.describe(), other.storePaths);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateStorePaths(ref<Store> evalStore, ref<Store> store, Installable & installable)
|
void updateStorePaths(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
const BuiltPaths & builtPaths)
|
||||||
{
|
{
|
||||||
// FIXME: respect meta.outputsToInstall
|
// FIXME: respect meta.outputsToInstall
|
||||||
storePaths.clear();
|
storePaths.clear();
|
||||||
for (auto & buildable : getBuiltPaths(evalStore, store, installable.toDerivedPaths())) {
|
for (auto & buildable : builtPaths) {
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const BuiltPath::Opaque & bo) {
|
[&](const BuiltPath::Opaque & bo) {
|
||||||
storePaths.insert(bo.path);
|
storePaths.insert(bo.path);
|
||||||
},
|
},
|
||||||
[&](const BuiltPath::Built & bfd) {
|
[&](const BuiltPath::Built & bfd) {
|
||||||
// TODO: Why are we querying if we know the output
|
for (auto & output : bfd.outputs)
|
||||||
// names already? Is it just to figure out what the
|
|
||||||
// default one is?
|
|
||||||
for (auto & output : store->queryDerivationOutputMap(bfd.drvPath)) {
|
|
||||||
storePaths.insert(output.second);
|
storePaths.insert(output.second);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}, buildable.raw());
|
}, buildable.raw());
|
||||||
}
|
}
|
||||||
|
|
@ -98,18 +97,30 @@ struct ProfileManifest
|
||||||
auto json = nlohmann::json::parse(readFile(manifestPath));
|
auto json = nlohmann::json::parse(readFile(manifestPath));
|
||||||
|
|
||||||
auto version = json.value("version", 0);
|
auto version = json.value("version", 0);
|
||||||
if (version != 1)
|
std::string sUrl;
|
||||||
throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version);
|
std::string sOriginalUrl;
|
||||||
|
switch(version){
|
||||||
|
case 1:
|
||||||
|
sUrl = "uri";
|
||||||
|
sOriginalUrl = "originalUri";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
sUrl = "url";
|
||||||
|
sOriginalUrl = "originalUrl";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version);
|
||||||
|
}
|
||||||
|
|
||||||
for (auto & e : json["elements"]) {
|
for (auto & e : json["elements"]) {
|
||||||
ProfileElement element;
|
ProfileElement element;
|
||||||
for (auto & p : e["storePaths"])
|
for (auto & p : e["storePaths"])
|
||||||
element.storePaths.insert(state.store->parseStorePath((std::string) p));
|
element.storePaths.insert(state.store->parseStorePath((std::string) p));
|
||||||
element.active = e["active"];
|
element.active = e["active"];
|
||||||
if (e.value("uri", "") != "") {
|
if (e.value(sUrl,"") != "") {
|
||||||
element.source = ProfileElementSource{
|
element.source = ProfileElementSource{
|
||||||
parseFlakeRef(e["originalUri"]),
|
parseFlakeRef(e[sOriginalUrl]),
|
||||||
parseFlakeRef(e["uri"]),
|
parseFlakeRef(e[sUrl]),
|
||||||
e["attrPath"]
|
e["attrPath"]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -143,14 +154,14 @@ struct ProfileManifest
|
||||||
obj["storePaths"] = paths;
|
obj["storePaths"] = paths;
|
||||||
obj["active"] = element.active;
|
obj["active"] = element.active;
|
||||||
if (element.source) {
|
if (element.source) {
|
||||||
obj["originalUri"] = element.source->originalRef.to_string();
|
obj["originalUrl"] = element.source->originalRef.to_string();
|
||||||
obj["uri"] = element.source->resolvedRef.to_string();
|
obj["url"] = element.source->resolvedRef.to_string();
|
||||||
obj["attrPath"] = element.source->attrPath;
|
obj["attrPath"] = element.source->attrPath;
|
||||||
}
|
}
|
||||||
array.push_back(obj);
|
array.push_back(obj);
|
||||||
}
|
}
|
||||||
nlohmann::json json;
|
nlohmann::json json;
|
||||||
json["version"] = 1;
|
json["version"] = 2;
|
||||||
json["elements"] = array;
|
json["elements"] = array;
|
||||||
return json.dump();
|
return json.dump();
|
||||||
}
|
}
|
||||||
|
|
@ -235,6 +246,16 @@ struct ProfileManifest
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::map<Installable *, BuiltPaths>
|
||||||
|
builtPathsPerInstallable(
|
||||||
|
const std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> & builtPaths)
|
||||||
|
{
|
||||||
|
std::map<Installable *, BuiltPaths> res;
|
||||||
|
for (auto & [installable, builtPath] : builtPaths)
|
||||||
|
res[installable.get()].push_back(builtPath);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
||||||
{
|
{
|
||||||
std::string description() override
|
std::string description() override
|
||||||
|
|
@ -253,7 +274,9 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
||||||
{
|
{
|
||||||
ProfileManifest manifest(*getEvalState(), *profile);
|
ProfileManifest manifest(*getEvalState(), *profile);
|
||||||
|
|
||||||
auto builtPaths = Installable::build(getEvalStore(), store, Realise::Outputs, installables, bmNormal);
|
auto builtPaths = builtPathsPerInstallable(
|
||||||
|
Installable::build2(
|
||||||
|
getEvalStore(), store, Realise::Outputs, installables, bmNormal));
|
||||||
|
|
||||||
for (auto & installable : installables) {
|
for (auto & installable : installables) {
|
||||||
ProfileElement element;
|
ProfileElement element;
|
||||||
|
|
@ -268,7 +291,7 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
element.updateStorePaths(getEvalStore(), store, *installable);
|
element.updateStorePaths(getEvalStore(), store, builtPaths[installable.get()]);
|
||||||
|
|
||||||
manifest.elements.push_back(std::move(element));
|
manifest.elements.push_back(std::move(element));
|
||||||
}
|
}
|
||||||
|
|
@ -456,12 +479,14 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
|
||||||
warn ("Use 'nix profile list' to see the current profile.");
|
warn ("Use 'nix profile list' to see the current profile.");
|
||||||
}
|
}
|
||||||
|
|
||||||
auto builtPaths = Installable::build(getEvalStore(), store, Realise::Outputs, installables, bmNormal);
|
auto builtPaths = builtPathsPerInstallable(
|
||||||
|
Installable::build2(
|
||||||
|
getEvalStore(), store, Realise::Outputs, installables, bmNormal));
|
||||||
|
|
||||||
for (size_t i = 0; i < installables.size(); ++i) {
|
for (size_t i = 0; i < installables.size(); ++i) {
|
||||||
auto & installable = installables.at(i);
|
auto & installable = installables.at(i);
|
||||||
auto & element = manifest.elements[indices.at(i)];
|
auto & element = manifest.elements[indices.at(i)];
|
||||||
element.updateStorePaths(getEvalStore(), store, *installable);
|
element.updateStorePaths(getEvalStore(), store, builtPaths[installable.get()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateProfile(manifest.build(store));
|
updateProfile(manifest.build(store));
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ are installed in this version of the profile. It looks like this:
|
||||||
{
|
{
|
||||||
"active": true,
|
"active": true,
|
||||||
"attrPath": "legacyPackages.x86_64-linux.zoom-us",
|
"attrPath": "legacyPackages.x86_64-linux.zoom-us",
|
||||||
"originalUri": "flake:nixpkgs",
|
"originalUrl": "flake:nixpkgs",
|
||||||
"storePaths": [
|
"storePaths": [
|
||||||
"/nix/store/wbhg2ga8f3h87s9h5k0slxk0m81m4cxl-zoom-us-5.3.469451.0927"
|
"/nix/store/wbhg2ga8f3h87s9h5k0slxk0m81m4cxl-zoom-us-5.3.469451.0927"
|
||||||
],
|
],
|
||||||
|
|
@ -84,11 +84,11 @@ are installed in this version of the profile. It looks like this:
|
||||||
Each object in the array `elements` denotes an installed package and
|
Each object in the array `elements` denotes an installed package and
|
||||||
has the following fields:
|
has the following fields:
|
||||||
|
|
||||||
* `originalUri`: The [flake reference](./nix3-flake.md) specified by
|
* `originalUrl`: The [flake reference](./nix3-flake.md) specified by
|
||||||
the user at the time of installation (e.g. `nixpkgs`). This is also
|
the user at the time of installation (e.g. `nixpkgs`). This is also
|
||||||
the flake reference that will be used by `nix profile upgrade`.
|
the flake reference that will be used by `nix profile upgrade`.
|
||||||
|
|
||||||
* `uri`: The immutable flake reference to which `originalUri`
|
* `uri`: The immutable flake reference to which `originalUrl`
|
||||||
resolved.
|
resolved.
|
||||||
|
|
||||||
* `attrPath`: The flake output attribute that provided this
|
* `attrPath`: The flake output attribute that provided this
|
||||||
|
|
|
||||||
|
|
@ -396,6 +396,7 @@ StorePath NixRepl::getDerivationPath(Value & v) {
|
||||||
|
|
||||||
bool NixRepl::processLine(std::string line)
|
bool NixRepl::processLine(std::string line)
|
||||||
{
|
{
|
||||||
|
line = trim(line);
|
||||||
if (line == "") return true;
|
if (line == "") return true;
|
||||||
|
|
||||||
_isInterrupted = false;
|
_isInterrupted = false;
|
||||||
|
|
|
||||||
|
|
@ -38,9 +38,12 @@ void runProgramInStore(ref<Store> store,
|
||||||
unshare(CLONE_NEWUSER) doesn't work in a multithreaded program
|
unshare(CLONE_NEWUSER) doesn't work in a multithreaded program
|
||||||
(which "nix" is), so we exec() a single-threaded helper program
|
(which "nix" is), so we exec() a single-threaded helper program
|
||||||
(chrootHelper() below) to do the work. */
|
(chrootHelper() below) to do the work. */
|
||||||
auto store2 = store.dynamic_pointer_cast<LocalStore>();
|
auto store2 = store.dynamic_pointer_cast<LocalFSStore>();
|
||||||
|
|
||||||
if (store2 && store->storeDir != store2->getRealStoreDir()) {
|
if (!store2)
|
||||||
|
throw Error("store '%s' is not a local store so it does not support command execution", store->getUri());
|
||||||
|
|
||||||
|
if (store->storeDir != store2->getRealStoreDir()) {
|
||||||
Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program };
|
Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program };
|
||||||
for (auto & arg : args) helperArgs.push_back(arg);
|
for (auto & arg : args) helperArgs.push_back(arg);
|
||||||
|
|
||||||
|
|
@ -179,6 +182,7 @@ struct CmdRun : InstallableCommand
|
||||||
{
|
{
|
||||||
auto state = getEvalState();
|
auto state = getEvalState();
|
||||||
|
|
||||||
|
lockFlags.applyNixConfig = true;
|
||||||
auto app = installable->toApp(*state).resolve(getEvalStore(), store);
|
auto app = installable->toApp(*state).resolve(getEvalStore(), store);
|
||||||
|
|
||||||
Strings allArgs{app.program};
|
Strings allArgs{app.program};
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,10 @@ struct CmdShowDerivation : InstallablesCommand
|
||||||
outputObj.attr("hashAlgo", makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
|
outputObj.attr("hashAlgo", makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
|
||||||
},
|
},
|
||||||
[&](const DerivationOutput::Deferred &) {},
|
[&](const DerivationOutput::Deferred &) {},
|
||||||
|
[&](const DerivationOutput::Impure & doi) {
|
||||||
|
outputObj.attr("hashAlgo", makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType));
|
||||||
|
outputObj.attr("impure", true);
|
||||||
|
},
|
||||||
}, output.raw());
|
}, output.raw());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
source common.sh
|
source common.sh
|
||||||
|
|
||||||
needLocalStore "“--no-require-sigs” can’t be used with the daemon"
|
needLocalStore "'--no-require-sigs' can’t be used with the daemon"
|
||||||
|
|
||||||
# We can produce drvs directly into the binary cache
|
# We can produce drvs directly into the binary cache
|
||||||
clearStore
|
clearStore
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,27 @@
|
||||||
source common.sh
|
source common.sh
|
||||||
|
|
||||||
expectedJSONRegex='\[\{"drvPath":".*multiple-outputs-a.drv","outputs":\{"first":".*multiple-outputs-a-first","second":".*multiple-outputs-a-second"}},\{"drvPath":".*multiple-outputs-b.drv","outputs":\{"out":".*multiple-outputs-b"}}]'
|
clearStore
|
||||||
|
|
||||||
|
# Make sure that 'nix build' only returns the outputs we asked for.
|
||||||
|
nix build -f multiple-outputs.nix --json a --no-link | jq --exit-status '
|
||||||
|
(.[0] |
|
||||||
|
(.drvPath | match(".*multiple-outputs-a.drv")) and
|
||||||
|
(.outputs | keys | length == 1) and
|
||||||
|
(.outputs.first | match(".*multiple-outputs-a-first")))
|
||||||
|
'
|
||||||
|
|
||||||
nix build -f multiple-outputs.nix --json a.all b.all --no-link | jq --exit-status '
|
nix build -f multiple-outputs.nix --json a.all b.all --no-link | jq --exit-status '
|
||||||
(.[0] |
|
(.[0] |
|
||||||
(.drvPath | match(".*multiple-outputs-a.drv")) and
|
(.drvPath | match(".*multiple-outputs-a.drv")) and
|
||||||
|
(.outputs | keys | length == 2) and
|
||||||
(.outputs.first | match(".*multiple-outputs-a-first")) and
|
(.outputs.first | match(".*multiple-outputs-a-first")) and
|
||||||
(.outputs.second | match(".*multiple-outputs-a-second")))
|
(.outputs.second | match(".*multiple-outputs-a-second")))
|
||||||
and (.[1] |
|
and (.[1] |
|
||||||
(.drvPath | match(".*multiple-outputs-b.drv")) and
|
(.drvPath | match(".*multiple-outputs-b.drv")) and
|
||||||
|
(.outputs | keys | length == 1) and
|
||||||
(.outputs.out | match(".*multiple-outputs-b")))
|
(.outputs.out | match(".*multiple-outputs-b")))
|
||||||
'
|
'
|
||||||
|
|
||||||
testNormalization () {
|
testNormalization () {
|
||||||
clearStore
|
clearStore
|
||||||
outPath=$(nix-build ./simple.nix --no-out-link)
|
outPath=$(nix-build ./simple.nix --no-out-link)
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,7 @@ rec {
|
||||||
dependentFixedOutput = mkDerivation {
|
dependentFixedOutput = mkDerivation {
|
||||||
name = "dependent-fixed-output";
|
name = "dependent-fixed-output";
|
||||||
outputHashMode = "recursive";
|
outputHashMode = "recursive";
|
||||||
outputHashAlgo = "sha256";
|
outputHash = "sha512-7aJcmSuEuYP5tGKcmGY8bRr/lrCjJlOxP2mIUjO/vMQeg6gx/65IbzRWES8EKiPDOs9z+wF30lEfcwxM/cT4pw==";
|
||||||
outputHash = "sha256-QvtAMbUl/uvi+LCObmqOhvNOapHdA2raiI4xG5zI5pA=";
|
|
||||||
buildCommand = ''
|
buildCommand = ''
|
||||||
cat ${dependentCA}/dep
|
cat ${dependentCA}/dep
|
||||||
echo foo > $out
|
echo foo > $out
|
||||||
|
|
|
||||||
70
tests/fetchClosure.sh
Normal file
70
tests/fetchClosure.sh
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
source common.sh
|
||||||
|
|
||||||
|
enableFeatures "fetch-closure"
|
||||||
|
needLocalStore "'--no-require-sigs' can’t be used with the daemon"
|
||||||
|
|
||||||
|
clearStore
|
||||||
|
clearCacheCache
|
||||||
|
|
||||||
|
# Initialize binary cache.
|
||||||
|
nonCaPath=$(nix build --json --file ./dependencies.nix | jq -r .[].outputs.out)
|
||||||
|
caPath=$(nix store make-content-addressed --json $nonCaPath | jq -r '.rewrites | map(.) | .[]')
|
||||||
|
nix copy --to file://$cacheDir $nonCaPath
|
||||||
|
|
||||||
|
# Test basic fetchClosure rewriting from non-CA to CA.
|
||||||
|
clearStore
|
||||||
|
|
||||||
|
[ ! -e $nonCaPath ]
|
||||||
|
[ ! -e $caPath ]
|
||||||
|
|
||||||
|
[[ $(nix eval -v --raw --expr "
|
||||||
|
builtins.fetchClosure {
|
||||||
|
fromStore = \"file://$cacheDir\";
|
||||||
|
fromPath = $nonCaPath;
|
||||||
|
toPath = $caPath;
|
||||||
|
}
|
||||||
|
") = $caPath ]]
|
||||||
|
|
||||||
|
[ ! -e $nonCaPath ]
|
||||||
|
[ -e $caPath ]
|
||||||
|
|
||||||
|
# In impure mode, we can use non-CA paths.
|
||||||
|
[[ $(nix eval --raw --no-require-sigs --impure --expr "
|
||||||
|
builtins.fetchClosure {
|
||||||
|
fromStore = \"file://$cacheDir\";
|
||||||
|
fromPath = $nonCaPath;
|
||||||
|
}
|
||||||
|
") = $nonCaPath ]]
|
||||||
|
|
||||||
|
[ -e $nonCaPath ]
|
||||||
|
|
||||||
|
# 'toPath' set to empty string should fail but print the expected path.
|
||||||
|
nix eval -v --json --expr "
|
||||||
|
builtins.fetchClosure {
|
||||||
|
fromStore = \"file://$cacheDir\";
|
||||||
|
fromPath = $nonCaPath;
|
||||||
|
toPath = \"\";
|
||||||
|
}
|
||||||
|
" 2>&1 | grep "error: rewriting.*$nonCaPath.*yielded.*$caPath"
|
||||||
|
|
||||||
|
# If fromPath is CA, then toPath isn't needed.
|
||||||
|
nix copy --to file://$cacheDir $caPath
|
||||||
|
|
||||||
|
[[ $(nix eval -v --raw --expr "
|
||||||
|
builtins.fetchClosure {
|
||||||
|
fromStore = \"file://$cacheDir\";
|
||||||
|
fromPath = $caPath;
|
||||||
|
}
|
||||||
|
") = $caPath ]]
|
||||||
|
|
||||||
|
# Check that URL query parameters aren't allowed.
|
||||||
|
clearStore
|
||||||
|
narCache=$TEST_ROOT/nar-cache
|
||||||
|
rm -rf $narCache
|
||||||
|
(! nix eval -v --raw --expr "
|
||||||
|
builtins.fetchClosure {
|
||||||
|
fromStore = \"file://$cacheDir?local-nar-cache=$narCache\";
|
||||||
|
fromPath = $caPath;
|
||||||
|
}
|
||||||
|
")
|
||||||
|
(! [ -e $narCache ])
|
||||||
|
|
@ -7,7 +7,9 @@ fi
|
||||||
|
|
||||||
clearStore
|
clearStore
|
||||||
|
|
||||||
repo=$TEST_ROOT/git
|
# Intentionally not in a canonical form
|
||||||
|
# See https://github.com/NixOS/nix/issues/6195
|
||||||
|
repo=$TEST_ROOT/./git
|
||||||
|
|
||||||
export _NIX_FORCE_HTTP=1
|
export _NIX_FORCE_HTTP=1
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
source common.sh
|
source common.sh
|
||||||
|
|
||||||
touch foo -t 202211111111
|
touch $TEST_ROOT/foo -t 202211111111
|
||||||
# We only check whether 2022-11-1* **:**:** is the last modified date since
|
# We only check whether 2022-11-1* **:**:** is the last modified date since
|
||||||
# `lastModified` is transformed into UTC in `builtins.fetchTarball`.
|
# `lastModified` is transformed into UTC in `builtins.fetchTarball`.
|
||||||
[[ "$(nix eval --impure --raw --expr "(builtins.fetchTree \"path://$PWD/foo\").lastModifiedDate")" =~ 2022111.* ]]
|
[[ "$(nix eval --impure --raw --expr "(builtins.fetchTree \"path://$TEST_ROOT/foo\").lastModifiedDate")" =~ 2022111.* ]]
|
||||||
|
|
|
||||||
|
|
@ -376,6 +376,9 @@ cat > $templatesDir/flake.nix <<EOF
|
||||||
trivial = {
|
trivial = {
|
||||||
path = ./trivial;
|
path = ./trivial;
|
||||||
description = "A trivial flake";
|
description = "A trivial flake";
|
||||||
|
welcomeText = ''
|
||||||
|
Welcome to my trivial flake
|
||||||
|
'';
|
||||||
};
|
};
|
||||||
default = trivial;
|
default = trivial;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
63
tests/impure-derivations.nix
Normal file
63
tests/impure-derivations.nix
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
with import ./config.nix;
|
||||||
|
|
||||||
|
rec {
|
||||||
|
|
||||||
|
impure = mkDerivation {
|
||||||
|
name = "impure";
|
||||||
|
outputs = [ "out" "stuff" ];
|
||||||
|
buildCommand =
|
||||||
|
''
|
||||||
|
echo impure
|
||||||
|
x=$(< $TEST_ROOT/counter)
|
||||||
|
mkdir $out $stuff
|
||||||
|
echo $x > $out/n
|
||||||
|
ln -s $out/n $stuff/bla
|
||||||
|
printf $((x + 1)) > $TEST_ROOT/counter
|
||||||
|
'';
|
||||||
|
__impure = true;
|
||||||
|
impureEnvVars = [ "TEST_ROOT" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
impureOnImpure = mkDerivation {
|
||||||
|
name = "impure-on-impure";
|
||||||
|
buildCommand =
|
||||||
|
''
|
||||||
|
echo impure-on-impure
|
||||||
|
x=$(< ${impure}/n)
|
||||||
|
mkdir $out
|
||||||
|
printf X$x > $out/n
|
||||||
|
ln -s ${impure.stuff} $out/symlink
|
||||||
|
ln -s $out $out/self
|
||||||
|
'';
|
||||||
|
__impure = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
# This is not allowed.
|
||||||
|
inputAddressed = mkDerivation {
|
||||||
|
name = "input-addressed";
|
||||||
|
buildCommand =
|
||||||
|
''
|
||||||
|
cat ${impure} > $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
contentAddressed = mkDerivation {
|
||||||
|
name = "content-addressed";
|
||||||
|
buildCommand =
|
||||||
|
''
|
||||||
|
echo content-addressed
|
||||||
|
x=$(< ${impureOnImpure}/n)
|
||||||
|
printf ''${x:0:1} > $out
|
||||||
|
'';
|
||||||
|
outputHashMode = "recursive";
|
||||||
|
outputHash = "sha256-eBYxcgkuWuiqs4cKNgKwkb3vY/HR0vVsJnqe8itJGcQ=";
|
||||||
|
};
|
||||||
|
|
||||||
|
inputAddressedAfterCA = mkDerivation {
|
||||||
|
name = "input-addressed-after-ca";
|
||||||
|
buildCommand =
|
||||||
|
''
|
||||||
|
cat ${contentAddressed} > $out
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
}
|
||||||
57
tests/impure-derivations.sh
Normal file
57
tests/impure-derivations.sh
Normal file
|
|
@ -0,0 +1,57 @@
|
||||||
|
source common.sh
|
||||||
|
|
||||||
|
requireDaemonNewerThan "2.8pre20220311"
|
||||||
|
|
||||||
|
enableFeatures "ca-derivations ca-references impure-derivations"
|
||||||
|
restartDaemon
|
||||||
|
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
clearStore
|
||||||
|
|
||||||
|
# Basic test of impure derivations: building one a second time should not use the previous result.
|
||||||
|
printf 0 > $TEST_ROOT/counter
|
||||||
|
|
||||||
|
json=$(nix build -L --no-link --json --file ./impure-derivations.nix impure.all)
|
||||||
|
path1=$(echo $json | jq -r .[].outputs.out)
|
||||||
|
path1_stuff=$(echo $json | jq -r .[].outputs.stuff)
|
||||||
|
[[ $(< $path1/n) = 0 ]]
|
||||||
|
[[ $(< $path1_stuff/bla) = 0 ]]
|
||||||
|
|
||||||
|
[[ $(nix path-info --json $path1 | jq .[].ca) =~ fixed:r:sha256: ]]
|
||||||
|
|
||||||
|
path2=$(nix build -L --no-link --json --file ./impure-derivations.nix impure | jq -r .[].outputs.out)
|
||||||
|
[[ $(< $path2/n) = 1 ]]
|
||||||
|
|
||||||
|
# Test impure derivations that depend on impure derivations.
|
||||||
|
path3=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnImpure | jq -r .[].outputs.out)
|
||||||
|
[[ $(< $path3/n) = X2 ]]
|
||||||
|
|
||||||
|
path4=$(nix build -L --no-link --json --file ./impure-derivations.nix impureOnImpure | jq -r .[].outputs.out)
|
||||||
|
[[ $(< $path4/n) = X3 ]]
|
||||||
|
|
||||||
|
# Test that (self-)references work.
|
||||||
|
[[ $(< $path4/symlink/bla) = 3 ]]
|
||||||
|
[[ $(< $path4/self/n) = X3 ]]
|
||||||
|
|
||||||
|
# Input-addressed derivations cannot depend on impure derivations directly.
|
||||||
|
(! nix build -L --no-link --json --file ./impure-derivations.nix inputAddressed 2>&1) | grep 'depends on impure derivation'
|
||||||
|
|
||||||
|
drvPath=$(nix eval --json --file ./impure-derivations.nix impure.drvPath | jq -r .)
|
||||||
|
[[ $(nix show-derivation $drvPath | jq ".[\"$drvPath\"].outputs.out.impure") = true ]]
|
||||||
|
[[ $(nix show-derivation $drvPath | jq ".[\"$drvPath\"].outputs.stuff.impure") = true ]]
|
||||||
|
|
||||||
|
# Fixed-output derivations *can* depend on impure derivations.
|
||||||
|
path5=$(nix build -L --no-link --json --file ./impure-derivations.nix contentAddressed | jq -r .[].outputs.out)
|
||||||
|
[[ $(< $path5) = X ]]
|
||||||
|
[[ $(< $TEST_ROOT/counter) = 5 ]]
|
||||||
|
|
||||||
|
# And they should not be rebuilt.
|
||||||
|
path5=$(nix build -L --no-link --json --file ./impure-derivations.nix contentAddressed | jq -r .[].outputs.out)
|
||||||
|
[[ $(< $path5) = X ]]
|
||||||
|
[[ $(< $TEST_ROOT/counter) = 5 ]]
|
||||||
|
|
||||||
|
# Input-addressed derivations can depend on fixed-output derivations that depend on impure derivations.
|
||||||
|
path6=$(nix build -L --no-link --json --file ./impure-derivations.nix inputAddressedAfterCA | jq -r .[].outputs.out)
|
||||||
|
[[ $(< $path6) = X ]]
|
||||||
|
[[ $(< $TEST_ROOT/counter) = 5 ]]
|
||||||
|
|
@ -96,7 +96,9 @@ nix_tests = \
|
||||||
describe-stores.sh \
|
describe-stores.sh \
|
||||||
nix-profile.sh \
|
nix-profile.sh \
|
||||||
suggestions.sh \
|
suggestions.sh \
|
||||||
store-ping.sh
|
store-ping.sh \
|
||||||
|
fetchClosure.sh \
|
||||||
|
impure-derivations.sh
|
||||||
|
|
||||||
ifeq ($(HAVE_LIBCPUID), 1)
|
ifeq ($(HAVE_LIBCPUID), 1)
|
||||||
nix_tests += compute-levels.sh
|
nix_tests += compute-levels.sh
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,14 @@ rm -rf $NIX_LOG_DIR
|
||||||
(! nix-store -l $path)
|
(! nix-store -l $path)
|
||||||
nix-build dependencies.nix --no-out-link --compress-build-log
|
nix-build dependencies.nix --no-out-link --compress-build-log
|
||||||
[ "$(nix-store -l $path)" = FOO ]
|
[ "$(nix-store -l $path)" = FOO ]
|
||||||
|
|
||||||
|
# test whether empty logs work fine with `nix log`.
|
||||||
|
builder="$(mktemp)"
|
||||||
|
echo -e "#!/bin/sh\nmkdir \$out" > "$builder"
|
||||||
|
outp="$(nix-build -E \
|
||||||
|
'with import ./config.nix; mkDerivation { name = "fnord"; builder = '"$builder"'; }' \
|
||||||
|
--out-link "$(mktemp -d)/result")"
|
||||||
|
|
||||||
|
test -d "$outp"
|
||||||
|
|
||||||
|
nix log "$outp"
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ let
|
||||||
echo 'ref: refs/heads/master' > $out/HEAD
|
echo 'ref: refs/heads/master' > $out/HEAD
|
||||||
|
|
||||||
mkdir -p $out/info
|
mkdir -p $out/info
|
||||||
echo '${nixpkgs.rev} refs/heads/master' > $out/info/refs
|
echo -e '${nixpkgs.rev}\trefs/heads/master' > $out/info/refs
|
||||||
'';
|
'';
|
||||||
|
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ assert foo == "foo";
|
||||||
|
|
||||||
let
|
let
|
||||||
|
|
||||||
|
platforms = let x = "foobar"; in [ x x ];
|
||||||
|
|
||||||
makeDrv = name: progName: (mkDerivation {
|
makeDrv = name: progName: (mkDerivation {
|
||||||
name = assert progName != "fail"; name;
|
name = assert progName != "fail"; name;
|
||||||
inherit progName system;
|
inherit progName system;
|
||||||
|
|
@ -15,6 +17,7 @@ let
|
||||||
} // {
|
} // {
|
||||||
meta = {
|
meta = {
|
||||||
description = "A silly test package with some \${escaped anti-quotation} in it";
|
description = "A silly test package with some \${escaped anti-quotation} in it";
|
||||||
|
inherit platforms;
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue