mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-23 06:26:15 +02:00
Merge remote-tracking branch 'upstream/master' into upstream-merge
This commit is contained in:
commit
1a93ac8133
262 changed files with 5827 additions and 2852 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
|
||||||
|
|
2
.version
2
.version
|
@ -1 +1 @@
|
||||||
2.7.0
|
2.8.0
|
|
@ -6,9 +6,9 @@ builtins:
|
||||||
concatStrings (map
|
concatStrings (map
|
||||||
(name:
|
(name:
|
||||||
let builtin = builtins.${name}; in
|
let builtin = builtins.${name}; in
|
||||||
"<dt><code>${name} "
|
"<dt id=\"builtins-${name}\"><a href=\"#builtins-${name}\"><code>${name} "
|
||||||
+ concatStringsSep " " (map (s: "<var>${s}</var>") builtin.args)
|
+ concatStringsSep " " (map (s: "<var>${s}</var>") builtin.args)
|
||||||
+ "</code></dt>"
|
+ "</code></a></dt>"
|
||||||
+ "<dd>\n\n"
|
+ "<dd>\n\n"
|
||||||
+ builtin.doc
|
+ builtin.doc
|
||||||
+ "\n\n</dd>"
|
+ "\n\n</dd>"
|
||||||
|
|
|
@ -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:** " + (
|
||||||
|
@ -20,7 +21,7 @@ concatStrings (map
|
||||||
# JSON, but that converts to "{ }" here.
|
# JSON, but that converts to "{ }" here.
|
||||||
(if isAttrs option.value then "`\"\"`"
|
(if isAttrs option.value then "`\"\"`"
|
||||||
else "`" + toString option.value + "`")) + "\n\n"
|
else "`" + toString option.value + "`")) + "\n\n"
|
||||||
else " **Default:** *machine-specific*")
|
else " **Default:** *machine-specific*\n")
|
||||||
+ (if option.aliases != []
|
+ (if option.aliases != []
|
||||||
then " **Deprecated alias:** " + (concatStringsSep ", " (map (s: "`${s}`") option.aliases)) + "\n\n"
|
then " **Deprecated alias:** " + (concatStringsSep ", " (map (s: "`${s}`") option.aliases)) + "\n\n"
|
||||||
else "")
|
else "")
|
||||||
|
|
|
@ -72,6 +72,7 @@ $(d)/builtins.json: $(bindir)/nix
|
||||||
@mv $@.tmp $@
|
@mv $@.tmp $@
|
||||||
|
|
||||||
# Generate the HTML manual.
|
# Generate the HTML manual.
|
||||||
|
html: $(docdir)/manual/index.html
|
||||||
install: $(docdir)/manual/index.html
|
install: $(docdir)/manual/index.html
|
||||||
|
|
||||||
# Generate 'nix' manpages.
|
# Generate 'nix' manpages.
|
||||||
|
|
|
@ -72,6 +72,7 @@
|
||||||
- [CLI guideline](contributing/cli-guideline.md)
|
- [CLI guideline](contributing/cli-guideline.md)
|
||||||
- [Release Notes](release-notes/release-notes.md)
|
- [Release Notes](release-notes/release-notes.md)
|
||||||
- [Release X.Y (202?-??-??)](release-notes/rl-next.md)
|
- [Release X.Y (202?-??-??)](release-notes/rl-next.md)
|
||||||
|
- [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md)
|
||||||
- [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md)
|
- [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md)
|
||||||
- [Release 2.5 (2021-12-13)](release-notes/rl-2.5.md)
|
- [Release 2.5 (2021-12-13)](release-notes/rl-2.5.md)
|
||||||
- [Release 2.4 (2021-11-01)](release-notes/rl-2.4.md)
|
- [Release 2.4 (2021-11-01)](release-notes/rl-2.4.md)
|
||||||
|
|
|
@ -110,7 +110,7 @@ default, set it to `-`.
|
||||||
7. A comma-separated list of *mandatory features*. A machine will only
|
7. A comma-separated list of *mandatory features*. A machine will only
|
||||||
be used to build a derivation if all of the machine’s mandatory
|
be used to build a derivation if all of the machine’s mandatory
|
||||||
features appear in the derivation’s `requiredSystemFeatures`
|
features appear in the derivation’s `requiredSystemFeatures`
|
||||||
attribute..
|
attribute.
|
||||||
|
|
||||||
8. The (base64-encoded) public host key of the remote machine. If omitted, SSH
|
8. The (base64-encoded) public host key of the remote machine. If omitted, SSH
|
||||||
will use its regular known-hosts file. Specifically, the field is calculated
|
will use its regular known-hosts file. Specifically, the field is calculated
|
||||||
|
|
|
@ -321,8 +321,8 @@ symlink.
|
||||||
This query has one option:
|
This query has one option:
|
||||||
|
|
||||||
- `--include-outputs`
|
- `--include-outputs`
|
||||||
Also include the output path of store derivations, and their
|
Also include the existing output paths of store derivations,
|
||||||
closures.
|
and their closures.
|
||||||
|
|
||||||
This query can be used to implement various kinds of deployment. A
|
This query can be used to implement various kinds of deployment. A
|
||||||
*source deployment* is obtained by distributing the closure of a
|
*source deployment* is obtained by distributing the closure of a
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
||||||
|
|
33
doc/manual/src/release-notes/rl-2.7.md
Normal file
33
doc/manual/src/release-notes/rl-2.7.md
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# Release 2.7 (2022-03-07)
|
||||||
|
|
||||||
|
* Nix will now make some helpful suggestions when you mistype
|
||||||
|
something on the command line. For instance, if you type `nix build
|
||||||
|
nixpkgs#thunderbrd`, it will suggest `thunderbird`.
|
||||||
|
|
||||||
|
* A number of "default" flake output attributes have been
|
||||||
|
renamed. These are:
|
||||||
|
|
||||||
|
* `defaultPackage.<system>` → `packages.<system>.default`
|
||||||
|
* `defaultApps.<system>` → `apps.<system>.default`
|
||||||
|
* `defaultTemplate` → `templates.default`
|
||||||
|
* `defaultBundler.<system>` → `bundlers.<system>.default`
|
||||||
|
* `overlay` → `overlays.default`
|
||||||
|
* `devShell.<system>` → `devShells.<system>.default`
|
||||||
|
|
||||||
|
The old flake output attributes still work, but `nix flake check`
|
||||||
|
will warn about them.
|
||||||
|
|
||||||
|
* Breaking API change: `nix bundle` now supports bundlers of the form
|
||||||
|
`bundler.<system>.<name>= derivation: another-derivation;`. This
|
||||||
|
supports additional functionality to inspect evaluation information
|
||||||
|
during bundling. A new
|
||||||
|
[repository](https://github.com/NixOS/bundlers) has various bundlers
|
||||||
|
implemented.
|
||||||
|
|
||||||
|
* `nix store ping` now reports the version of the remote Nix daemon.
|
||||||
|
|
||||||
|
* `nix flake {init,new}` now display information about which files have been
|
||||||
|
created.
|
||||||
|
|
||||||
|
* Templates can now define a `welcomeText` attribute, which is printed out by
|
||||||
|
`nix flake {init,new} --template <template>`.
|
|
@ -1,9 +1,42 @@
|
||||||
# Release X.Y (202?-??-??)
|
# Release X.Y (202?-??-??)
|
||||||
|
|
||||||
* `nix bundle` breaking API change now supports bundlers of the form
|
* Various nix commands can now read expressions from stdin with `--file -`.
|
||||||
`bundler.<system>.<name>= derivation: another-derivation;`. This supports
|
|
||||||
additional functionality to inspect evaluation information during bundling. A
|
|
||||||
new [repository](https://github.com/NixOS/bundlers) has various bundlers
|
|
||||||
implemented.
|
|
||||||
|
|
||||||
* `nix store ping` now reports the version of the remote Nix daemon.
|
* `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.
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
15
flake.nix
15
flake.nix
|
@ -501,6 +501,12 @@
|
||||||
inherit (self) overlay;
|
inherit (self) overlay;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tests.sourcehutFlakes = (import ./tests/sourcehut-flakes.nix rec {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
inherit nixpkgs;
|
||||||
|
inherit (self) overlay;
|
||||||
|
});
|
||||||
|
|
||||||
tests.setuid = nixpkgs.lib.genAttrs
|
tests.setuid = nixpkgs.lib.genAttrs
|
||||||
["i686-linux" "x86_64-linux"]
|
["i686-linux" "x86_64-linux"]
|
||||||
(system:
|
(system:
|
||||||
|
@ -648,11 +654,10 @@
|
||||||
installCheckFlags = "sysconfdir=$(out)/etc";
|
installCheckFlags = "sysconfdir=$(out)/etc";
|
||||||
};
|
};
|
||||||
}) crossSystems)) // (builtins.listToAttrs (map (stdenvName:
|
}) crossSystems)) // (builtins.listToAttrs (map (stdenvName:
|
||||||
nixpkgsFor.${system}.lib.nameValuePair
|
nixpkgsFor.${system}.lib.nameValuePair
|
||||||
"nix-${stdenvName}"
|
"nix-${stdenvName}"
|
||||||
nixpkgsFor.${system}."${stdenvName}Packages".nix
|
nixpkgsFor.${system}."${stdenvName}Packages".nix
|
||||||
) stdenvs))
|
) stdenvs)));
|
||||||
);
|
|
||||||
|
|
||||||
defaultPackage = forAllSystems (system: self.packages.${system}.nix);
|
defaultPackage = forAllSystems (system: self.packages.${system}.nix);
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,11 @@ my $releaseDir = "nix/$releaseName";
|
||||||
my $tmpDir = "$TMPDIR/nix-release/$releaseName";
|
my $tmpDir = "$TMPDIR/nix-release/$releaseName";
|
||||||
File::Path::make_path($tmpDir);
|
File::Path::make_path($tmpDir);
|
||||||
|
|
||||||
|
my $narCache = "$TMPDIR/nar-cache";
|
||||||
|
File::Path::make_path($narCache);
|
||||||
|
|
||||||
|
my $binaryCache = "https://cache.nixos.org/?local-nar-cache=$narCache";
|
||||||
|
|
||||||
# S3 setup.
|
# S3 setup.
|
||||||
my $aws_access_key_id = $ENV{'AWS_ACCESS_KEY_ID'} or die "No AWS_ACCESS_KEY_ID given.";
|
my $aws_access_key_id = $ENV{'AWS_ACCESS_KEY_ID'} or die "No AWS_ACCESS_KEY_ID given.";
|
||||||
my $aws_secret_access_key = $ENV{'AWS_SECRET_ACCESS_KEY'} or die "No AWS_SECRET_ACCESS_KEY given.";
|
my $aws_secret_access_key = $ENV{'AWS_SECRET_ACCESS_KEY'} or die "No AWS_SECRET_ACCESS_KEY given.";
|
||||||
|
@ -80,6 +85,7 @@ sub downloadFile {
|
||||||
my ($jobName, $productNr, $dstName) = @_;
|
my ($jobName, $productNr, $dstName) = @_;
|
||||||
|
|
||||||
my $buildInfo = decode_json(fetch("$evalUrl/job/$jobName", 'application/json'));
|
my $buildInfo = decode_json(fetch("$evalUrl/job/$jobName", 'application/json'));
|
||||||
|
#print STDERR "$jobName: ", Dumper($buildInfo), "\n";
|
||||||
|
|
||||||
my $srcFile = $buildInfo->{buildproducts}->{$productNr}->{path} or die "job '$jobName' lacks product $productNr\n";
|
my $srcFile = $buildInfo->{buildproducts}->{$productNr}->{path} or die "job '$jobName' lacks product $productNr\n";
|
||||||
$dstName //= basename($srcFile);
|
$dstName //= basename($srcFile);
|
||||||
|
@ -87,19 +93,27 @@ sub downloadFile {
|
||||||
|
|
||||||
if (!-e $tmpFile) {
|
if (!-e $tmpFile) {
|
||||||
print STDERR "downloading $srcFile to $tmpFile...\n";
|
print STDERR "downloading $srcFile to $tmpFile...\n";
|
||||||
system("NIX_REMOTE=https://cache.nixos.org/ nix store cat '$srcFile' > '$tmpFile'") == 0
|
|
||||||
|
my $fileInfo = decode_json(`NIX_REMOTE=$binaryCache nix store ls --json '$srcFile'`);
|
||||||
|
|
||||||
|
$srcFile = $fileInfo->{target} if $fileInfo->{type} eq 'symlink';
|
||||||
|
|
||||||
|
#print STDERR $srcFile, " ", Dumper($fileInfo), "\n";
|
||||||
|
|
||||||
|
system("NIX_REMOTE=$binaryCache nix store cat '$srcFile' > '$tmpFile'.tmp") == 0
|
||||||
or die "unable to fetch $srcFile\n";
|
or die "unable to fetch $srcFile\n";
|
||||||
|
rename("$tmpFile.tmp", $tmpFile) or die;
|
||||||
}
|
}
|
||||||
|
|
||||||
my $sha256_expected = $buildInfo->{buildproducts}->{$productNr}->{sha256hash} or die;
|
my $sha256_expected = $buildInfo->{buildproducts}->{$productNr}->{sha256hash};
|
||||||
my $sha256_actual = `nix hash file --base16 --type sha256 '$tmpFile'`;
|
my $sha256_actual = `nix hash file --base16 --type sha256 '$tmpFile'`;
|
||||||
chomp $sha256_actual;
|
chomp $sha256_actual;
|
||||||
if ($sha256_expected ne $sha256_actual) {
|
if (defined($sha256_expected) && $sha256_expected ne $sha256_actual) {
|
||||||
print STDERR "file $tmpFile is corrupt, got $sha256_actual, expected $sha256_expected\n";
|
print STDERR "file $tmpFile is corrupt, got $sha256_actual, expected $sha256_expected\n";
|
||||||
exit 1;
|
exit 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
write_file("$tmpFile.sha256", $sha256_expected);
|
write_file("$tmpFile.sha256", $sha256_actual);
|
||||||
|
|
||||||
if (! -e "$tmpFile.asc") {
|
if (! -e "$tmpFile.asc") {
|
||||||
system("gpg2 --detach-sign --armor $tmpFile") == 0 or die "unable to sign $tmpFile\n";
|
system("gpg2 --detach-sign --armor $tmpFile") == 0 or die "unable to sign $tmpFile\n";
|
||||||
|
@ -117,6 +131,60 @@ downloadFile("binaryTarballCross.x86_64-linux.armv6l-linux", "1");
|
||||||
downloadFile("binaryTarballCross.x86_64-linux.armv7l-linux", "1");
|
downloadFile("binaryTarballCross.x86_64-linux.armv7l-linux", "1");
|
||||||
downloadFile("installerScript", "1");
|
downloadFile("installerScript", "1");
|
||||||
|
|
||||||
|
# Upload docker images to dockerhub.
|
||||||
|
my $dockerManifest = "";
|
||||||
|
my $dockerManifestLatest = "";
|
||||||
|
|
||||||
|
for my $platforms (["x86_64-linux", "amd64"], ["aarch64-linux", "arm64"]) {
|
||||||
|
my $system = $platforms->[0];
|
||||||
|
my $dockerPlatform = $platforms->[1];
|
||||||
|
my $fn = "nix-$version-docker-image-$dockerPlatform.tar.gz";
|
||||||
|
downloadFile("dockerImage.$system", "1", $fn);
|
||||||
|
|
||||||
|
print STDERR "loading docker image for $dockerPlatform...\n";
|
||||||
|
system("docker load -i $tmpDir/$fn") == 0 or die;
|
||||||
|
|
||||||
|
my $tag = "nixos/nix:$version-$dockerPlatform";
|
||||||
|
my $latestTag = "nixos/nix:latest-$dockerPlatform";
|
||||||
|
|
||||||
|
print STDERR "tagging $version docker image for $dockerPlatform...\n";
|
||||||
|
system("docker tag nix:$version $tag") == 0 or die;
|
||||||
|
|
||||||
|
if ($isLatest) {
|
||||||
|
print STDERR "tagging latest docker image for $dockerPlatform...\n";
|
||||||
|
system("docker tag nix:$version $latestTag") == 0 or die;
|
||||||
|
}
|
||||||
|
|
||||||
|
print STDERR "pushing $version docker image for $dockerPlatform...\n";
|
||||||
|
system("docker push -q $tag") == 0 or die;
|
||||||
|
|
||||||
|
if ($isLatest) {
|
||||||
|
print STDERR "pushing latest docker image for $dockerPlatform...\n";
|
||||||
|
system("docker push -q $latestTag") == 0 or die;
|
||||||
|
}
|
||||||
|
|
||||||
|
$dockerManifest .= " --amend $tag";
|
||||||
|
$dockerManifestLatest .= " --amend $latestTag"
|
||||||
|
}
|
||||||
|
|
||||||
|
print STDERR "creating multi-platform docker manifest...\n";
|
||||||
|
system("docker manifest rm nixos/nix:$version");
|
||||||
|
system("docker manifest create nixos/nix:$version $dockerManifest") == 0 or die;
|
||||||
|
if ($isLatest) {
|
||||||
|
print STDERR "creating latest multi-platform docker manifest...\n";
|
||||||
|
system("docker manifest rm nixos/nix:latest");
|
||||||
|
system("docker manifest create nixos/nix:latest $dockerManifestLatest") == 0 or die;
|
||||||
|
}
|
||||||
|
|
||||||
|
print STDERR "pushing multi-platform docker manifest...\n";
|
||||||
|
system("docker manifest push nixos/nix:$version") == 0 or die;
|
||||||
|
|
||||||
|
if ($isLatest) {
|
||||||
|
print STDERR "pushing latest multi-platform docker manifest...\n";
|
||||||
|
system("docker manifest push nixos/nix:latest") == 0 or die;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Upload release files to S3.
|
||||||
for my $fn (glob "$tmpDir/*") {
|
for my $fn (glob "$tmpDir/*") {
|
||||||
my $name = basename($fn);
|
my $name = basename($fn);
|
||||||
my $dstKey = "$releaseDir/" . $name;
|
my $dstKey = "$releaseDir/" . $name;
|
||||||
|
|
|
@ -15,7 +15,7 @@ function _complete_nix {
|
||||||
else
|
else
|
||||||
COMPREPLY+=("$completion")
|
COMPREPLY+=("$completion")
|
||||||
fi
|
fi
|
||||||
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}")
|
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}" 2>/dev/null)
|
||||||
__ltrim_colon_completions "$cur"
|
__ltrim_colon_completions "$cur"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 - -
|
|
@ -1,7 +1,9 @@
|
||||||
[Unit]
|
[Unit]
|
||||||
Description=Nix Daemon
|
Description=Nix Daemon
|
||||||
|
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]
|
||||||
|
|
|
@ -4,7 +4,7 @@ function _nix() {
|
||||||
local ifs_bk="$IFS"
|
local ifs_bk="$IFS"
|
||||||
local input=("${(Q)words[@]}")
|
local input=("${(Q)words[@]}")
|
||||||
IFS=$'\n'
|
IFS=$'\n'
|
||||||
local res=($(NIX_GET_COMPLETIONS=$((CURRENT - 1)) "$input[@]"))
|
local res=($(NIX_GET_COMPLETIONS=$((CURRENT - 1)) "$input[@]" 2>/dev/null))
|
||||||
IFS="$ifs_bk"
|
IFS="$ifs_bk"
|
||||||
local tpe="${${res[1]}%%> *}"
|
local tpe="${${res[1]}%%> *}"
|
||||||
local -a suggestions
|
local -a suggestions
|
||||||
|
|
|
@ -14,9 +14,27 @@ if [ -t 1 ]; then
|
||||||
yellow="[33;1m"
|
yellow="[33;1m"
|
||||||
normal="[m"
|
normal="[m"
|
||||||
fi
|
fi
|
||||||
(cd tests && env ${TESTS_ENVIRONMENT} init.sh 2>/dev/null > /dev/null)
|
|
||||||
log="$(cd $(dirname $1) && env ${TESTS_ENVIRONMENT} $(basename $1) 2>&1)"
|
run_test () {
|
||||||
status=$?
|
(cd tests && env ${TESTS_ENVIRONMENT} init.sh 2>/dev/null > /dev/null)
|
||||||
|
log="$(cd $(dirname $1) && env ${TESTS_ENVIRONMENT} $(basename $1) 2>&1)"
|
||||||
|
status=$?
|
||||||
|
}
|
||||||
|
|
||||||
|
run_test "$1"
|
||||||
|
|
||||||
|
# Hack: Retry the test if it fails with “unexpected EOF reading a line” as these
|
||||||
|
# appear randomly without anyone knowing why.
|
||||||
|
# See https://github.com/NixOS/nix/issues/3605 for more info
|
||||||
|
if [[ $status -ne 0 && $status -ne 99 && \
|
||||||
|
"$(uname)" == "Darwin" && \
|
||||||
|
"$log" =~ "unexpected EOF reading a line" \
|
||||||
|
]]; then
|
||||||
|
echo "$post_run_msg [${yellow}FAIL$normal] (possibly flaky, so will be retried)"
|
||||||
|
echo "$log" | sed 's/^/ /'
|
||||||
|
run_test "$1"
|
||||||
|
fi
|
||||||
|
|
||||||
if [ $status -eq 0 ]; then
|
if [ $status -eq 0 ]; then
|
||||||
echo "$post_run_msg [${green}PASS$normal]"
|
echo "$post_run_msg [${green}PASS$normal]"
|
||||||
elif [ $status -eq 99 ]; then
|
elif [ $status -eq 99 ]; then
|
||||||
|
|
|
@ -240,7 +240,7 @@ SV * convertHash(char * algo, char * s, int toBase32)
|
||||||
PPCODE:
|
PPCODE:
|
||||||
try {
|
try {
|
||||||
auto h = Hash::parseAny(s, parseHashType(algo));
|
auto h = Hash::parseAny(s, parseHashType(algo));
|
||||||
string s = h.to_string(toBase32 ? Base32 : Base16, false);
|
auto s = h.to_string(toBase32 ? Base32 : Base16, false);
|
||||||
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
|
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
croak("%s", e.what());
|
croak("%s", e.what());
|
||||||
|
|
|
@ -14,14 +14,19 @@ 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")
|
||||||
|
|
||||||
buildStatus=$(echo "$buildInfo" | \
|
finished=$(echo "$buildInfo" | jq -r '.finished')
|
||||||
jq -r '.buildstatus')
|
|
||||||
|
|
||||||
if [[ "$buildStatus" -ne 0 ]]; then
|
if [[ $finished = 0 ]]; then
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
|
||||||
|
buildStatus=$(echo "$buildInfo" | jq -r '.buildstatus')
|
||||||
|
|
||||||
|
if [[ $buildStatus != 0 ]]; then
|
||||||
someBuildFailed=1
|
someBuildFailed=1
|
||||||
echo "Job “$(echo "$buildInfo" | jq -r '.job')” failed on hydra"
|
echo "Job “$(echo "$buildInfo" | jq -r '.job')” failed on hydra: $buildInfo"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
@ -246,7 +246,8 @@ get_volume_pass() {
|
||||||
verify_volume_pass() {
|
verify_volume_pass() {
|
||||||
local volume_special="$1" # (i.e., disk1s7)
|
local volume_special="$1" # (i.e., disk1s7)
|
||||||
local volume_uuid="$2"
|
local volume_uuid="$2"
|
||||||
/usr/sbin/diskutil apfs unlockVolume "$volume_special" -verify -stdinpassphrase -user "$volume_uuid"
|
_sudo "to confirm the password actually unlocks the volume" \
|
||||||
|
/usr/sbin/diskutil apfs unlockVolume "$volume_special" -verify -stdinpassphrase -user "$volume_uuid"
|
||||||
}
|
}
|
||||||
|
|
||||||
volume_pass_works() {
|
volume_pass_works() {
|
||||||
|
@ -685,22 +686,27 @@ encrypt_volume() {
|
||||||
local volume_uuid="$1"
|
local volume_uuid="$1"
|
||||||
local volume_label="$2"
|
local volume_label="$2"
|
||||||
local password
|
local password
|
||||||
|
|
||||||
|
task "Encrypt the Nix volume" >&2
|
||||||
|
|
||||||
# Note: mount/unmount are late additions to support the right order
|
# Note: mount/unmount are late additions to support the right order
|
||||||
# of operations for creating the volume and then baking its uuid into
|
# of operations for creating the volume and then baking its uuid into
|
||||||
# other artifacts; not as well-trod wrt to potential errors, race
|
# other artifacts; not as well-trod wrt to potential errors, race
|
||||||
# conditions, etc.
|
# conditions, etc.
|
||||||
|
|
||||||
/usr/sbin/diskutil mount "$volume_label"
|
_sudo "to mount your Nix volume for encrypting" \
|
||||||
|
/usr/sbin/diskutil mount "$volume_label"
|
||||||
|
|
||||||
password="$(/usr/bin/xxd -l 32 -p -c 256 /dev/random)"
|
password="$(/usr/bin/xxd -l 32 -p -c 256 /dev/random)"
|
||||||
_sudo "to add your Nix volume's password to Keychain" \
|
_sudo "to add your Nix volume's password to Keychain" \
|
||||||
/usr/bin/security -i <<EOF
|
/usr/bin/security -i <<EOF
|
||||||
add-generic-password -a "$volume_label" -s "$volume_uuid" -l "$volume_label encryption password" -D "Encrypted volume password" -j "Added automatically by the Nix installer for use by $NIX_VOLUME_MOUNTD_DEST" -w "$password" -T /System/Library/CoreServices/APFSUserAgent -T /System/Library/CoreServices/CSUserAgent -T /usr/bin/security "/Library/Keychains/System.keychain"
|
add-generic-password -a "$volume_label" -s "$volume_uuid" -l "$volume_label encryption password" -D "Encrypted volume password" -j "Added automatically by the Nix installer for use by $NIX_VOLUME_MOUNTD_DEST" -w "$password" -T /System/Library/CoreServices/APFSUserAgent -T /System/Library/CoreServices/CSUserAgent -T /usr/bin/security "/Library/Keychains/System.keychain"
|
||||||
EOF
|
EOF
|
||||||
builtin printf "%s" "$password" | _sudo "to encrypt your Nix volume" \
|
builtin printf "%s" "$password" | _sudo "to actually encrypt your Nix volume" \
|
||||||
/usr/sbin/diskutil apfs encryptVolume "$volume_label" -user disk -stdinpassphrase
|
/usr/sbin/diskutil apfs encryptVolume "$volume_label" -user disk -stdinpassphrase
|
||||||
|
|
||||||
/usr/sbin/diskutil unmount force "$volume_label"
|
_sudo "to unmount the encrypted volume" \
|
||||||
|
/usr/sbin/diskutil unmount force "$volume_label"
|
||||||
}
|
}
|
||||||
|
|
||||||
create_volume() {
|
create_volume() {
|
||||||
|
|
|
@ -23,10 +23,10 @@ readonly RED='\033[31m'
|
||||||
# installer allows overriding build user count to speed up installation
|
# installer allows overriding build user count to speed up installation
|
||||||
# as creating each user takes non-trivial amount of time on macos
|
# as creating each user takes non-trivial amount of time on macos
|
||||||
readonly NIX_USER_COUNT=${NIX_USER_COUNT:-32}
|
readonly NIX_USER_COUNT=${NIX_USER_COUNT:-32}
|
||||||
readonly NIX_BUILD_GROUP_ID="30000"
|
readonly NIX_BUILD_GROUP_ID="${NIX_BUILD_GROUP_ID:-30000}"
|
||||||
readonly NIX_BUILD_GROUP_NAME="nixbld"
|
readonly NIX_BUILD_GROUP_NAME="nixbld"
|
||||||
# darwin installer needs to override these
|
# darwin installer needs to override these
|
||||||
NIX_FIRST_BUILD_UID="30001"
|
NIX_FIRST_BUILD_UID="${NIX_FIRST_BUILD_UID:-30001}"
|
||||||
NIX_BUILD_USER_NAME_TEMPLATE="nixbld%d"
|
NIX_BUILD_USER_NAME_TEMPLATE="nixbld%d"
|
||||||
# Please don't change this. We don't support it, because the
|
# Please don't change this. We don't support it, because the
|
||||||
# default shell profile that comes with Nix doesn't support it.
|
# default shell profile that comes with Nix doesn't support it.
|
||||||
|
@ -609,7 +609,7 @@ EOF
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
_sudo "to make the basic directory structure of Nix (part 1)" \
|
_sudo "to make the basic directory structure of Nix (part 1)" \
|
||||||
install -dv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool} /nix/var/nix/{gcroots,profiles}/per-user
|
install -dv -m 0755 /nix /nix/var /nix/var/log /nix/var/log/nix /nix/var/log/nix/drvs /nix/var/nix{,/db,/gcroots,/profiles,/temproots,/userpool,/daemon-socket} /nix/var/nix/{gcroots,profiles}/per-user
|
||||||
|
|
||||||
_sudo "to make the basic directory structure of Nix (part 2)" \
|
_sudo "to make the basic directory structure of Nix (part 2)" \
|
||||||
install -dv -g "$NIX_BUILD_GROUP_NAME" -m 1775 /nix/store
|
install -dv -g "$NIX_BUILD_GROUP_NAME" -m 1775 /nix/store
|
||||||
|
@ -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
|
||||||
|
|
|
@ -24,6 +24,9 @@ if [ -n "$HOME" ] && [ -n "$USER" ]; then
|
||||||
export NIX_SSL_CERT_FILE="$NIX_LINK/etc/ca-bundle.crt"
|
export NIX_SSL_CERT_FILE="$NIX_LINK/etc/ca-bundle.crt"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Only use MANPATH if it is already set. In general `man` will just simply
|
||||||
|
# pick up `.nix-profile/share/man` because is it close to `.nix-profile/bin`
|
||||||
|
# which is in the $PATH. For more info, run `manpath -d`.
|
||||||
if [ -n "${MANPATH-}" ]; then
|
if [ -n "${MANPATH-}" ]; then
|
||||||
export MANPATH="$NIX_LINK/share/man:$MANPATH"
|
export MANPATH="$NIX_LINK/share/man:$MANPATH"
|
||||||
fi
|
fi
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include "pathlocks.hh"
|
#include "pathlocks.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "serialise.hh"
|
#include "serialise.hh"
|
||||||
|
#include "build-result.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "local-store.hh"
|
#include "local-store.hh"
|
||||||
|
@ -32,7 +33,7 @@ std::string escapeUri(std::string uri)
|
||||||
return uri;
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
static string currentLoad;
|
static std::string currentLoad;
|
||||||
|
|
||||||
static AutoCloseFD openSlotLock(const Machine & m, uint64_t slot)
|
static AutoCloseFD openSlotLock(const Machine & m, uint64_t slot)
|
||||||
{
|
{
|
||||||
|
@ -97,7 +98,7 @@ static int main_build_remote(int argc, char * * argv)
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<StorePath> drvPath;
|
std::optional<StorePath> drvPath;
|
||||||
string storeUri;
|
std::string storeUri;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|
||||||
|
@ -183,7 +184,7 @@ static int main_build_remote(int argc, char * * argv)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// build the hint template.
|
// build the hint template.
|
||||||
string errorText =
|
std::string errorText =
|
||||||
"Failed to find a machine for remote build!\n"
|
"Failed to find a machine for remote build!\n"
|
||||||
"derivation: %s\nrequired (system, features): (%s, %s)";
|
"derivation: %s\nrequired (system, features): (%s, %s)";
|
||||||
errorText += "\n%s available machines:";
|
errorText += "\n%s available machines:";
|
||||||
|
@ -193,7 +194,7 @@ static int main_build_remote(int argc, char * * argv)
|
||||||
errorText += "\n(%s, %s, %s, %s)";
|
errorText += "\n(%s, %s, %s, %s)";
|
||||||
|
|
||||||
// add the template values.
|
// add the template values.
|
||||||
string drvstr;
|
std::string drvstr;
|
||||||
if (drvPath.has_value())
|
if (drvPath.has_value())
|
||||||
drvstr = drvPath->to_string();
|
drvstr = drvPath->to_string();
|
||||||
else
|
else
|
||||||
|
@ -208,7 +209,7 @@ static int main_build_remote(int argc, char * * argv)
|
||||||
|
|
||||||
for (auto & m : machines)
|
for (auto & m : machines)
|
||||||
error
|
error
|
||||||
% concatStringsSep<vector<string>>(", ", m.systemTypes)
|
% concatStringsSep<std::vector<std::string>>(", ", m.systemTypes)
|
||||||
% m.maxJobs
|
% m.maxJobs
|
||||||
% concatStringsSep<StringSet>(", ", m.supportedFeatures)
|
% concatStringsSep<StringSet>(", ", m.supportedFeatures)
|
||||||
% concatStringsSep<StringSet>(", ", m.mandatoryFeatures);
|
% concatStringsSep<StringSet>(", ", m.mandatoryFeatures);
|
||||||
|
@ -299,7 +300,7 @@ connected:
|
||||||
|
|
||||||
std::set<Realisation> missingRealisations;
|
std::set<Realisation> missingRealisations;
|
||||||
StorePathSet missingPaths;
|
StorePathSet missingPaths;
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) {
|
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !drv.type().hasKnownOutputPaths()) {
|
||||||
for (auto & outputName : wantedOutputs) {
|
for (auto & outputName : wantedOutputs) {
|
||||||
auto thisOutputHash = outputHashes.at(outputName);
|
auto thisOutputHash = outputHashes.at(outputName);
|
||||||
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
|
auto thisOutputId = DrvOutput{ thisOutputHash, outputName };
|
||||||
|
|
|
@ -209,7 +209,7 @@ void BuiltPathsCommand::run(ref<Store> store)
|
||||||
for (auto & p : store->queryAllValidPaths())
|
for (auto & p : store->queryAllValidPaths())
|
||||||
paths.push_back(BuiltPath::Opaque{p});
|
paths.push_back(BuiltPath::Opaque{p});
|
||||||
} else {
|
} else {
|
||||||
paths = toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
|
paths = Installable::toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
// XXX: This only computes the store path closure, ignoring
|
// XXX: This only computes the store path closure, ignoring
|
||||||
// intermediate realisations
|
// intermediate realisations
|
||||||
|
@ -260,7 +260,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;
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include "common-eval-args.hh"
|
#include "common-eval-args.hh"
|
||||||
#include "path.hh"
|
#include "path.hh"
|
||||||
#include "flake/lockfile.hh"
|
#include "flake/lockfile.hh"
|
||||||
#include "store-api.hh"
|
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
|
||||||
|
@ -84,14 +83,6 @@ struct MixFlakeOptions : virtual Args, EvalCommand
|
||||||
{ return {}; }
|
{ return {}; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/* How to handle derivations in commands that operate on store paths. */
|
|
||||||
enum class OperateOn {
|
|
||||||
/* Operate on the output path. */
|
|
||||||
Output,
|
|
||||||
/* Operate on the .drv path. */
|
|
||||||
Derivation
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SourceExprCommand : virtual Args, MixFlakeOptions
|
struct SourceExprCommand : virtual Args, MixFlakeOptions
|
||||||
{
|
{
|
||||||
std::optional<Path> file;
|
std::optional<Path> file;
|
||||||
|
@ -115,19 +106,6 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions
|
||||||
void completeInstallable(std::string_view prefix);
|
void completeInstallable(std::string_view prefix);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class Realise {
|
|
||||||
/* Build the derivation. Postcondition: the
|
|
||||||
derivation outputs exist. */
|
|
||||||
Outputs,
|
|
||||||
/* Don't build the derivation. Postcondition: the store derivation
|
|
||||||
exists. */
|
|
||||||
Derivation,
|
|
||||||
/* Evaluate in dry-run mode. Postcondition: nothing. */
|
|
||||||
// FIXME: currently unused, but could be revived if we can
|
|
||||||
// evaluate derivations in-memory.
|
|
||||||
Nothing
|
|
||||||
};
|
|
||||||
|
|
||||||
/* A command that operates on a list of "installables", which can be
|
/* A command that operates on a list of "installables", which can be
|
||||||
store paths, attribute paths, Nix expressions, etc. */
|
store paths, attribute paths, Nix expressions, etc. */
|
||||||
struct InstallablesCommand : virtual Args, SourceExprCommand
|
struct InstallablesCommand : virtual Args, SourceExprCommand
|
||||||
|
@ -240,38 +218,6 @@ static RegisterCommand registerCommand2(std::vector<std::string> && name)
|
||||||
return RegisterCommand(std::move(name), [](){ return make_ref<T>(); });
|
return RegisterCommand(std::move(name), [](){ return make_ref<T>(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltPaths build(
|
|
||||||
ref<Store> evalStore,
|
|
||||||
ref<Store> store, Realise mode,
|
|
||||||
const std::vector<std::shared_ptr<Installable>> & installables,
|
|
||||||
BuildMode bMode = bmNormal);
|
|
||||||
|
|
||||||
std::set<StorePath> toStorePaths(
|
|
||||||
ref<Store> evalStore,
|
|
||||||
ref<Store> store,
|
|
||||||
Realise mode,
|
|
||||||
OperateOn operateOn,
|
|
||||||
const std::vector<std::shared_ptr<Installable>> & installables);
|
|
||||||
|
|
||||||
StorePath toStorePath(
|
|
||||||
ref<Store> evalStore,
|
|
||||||
ref<Store> store,
|
|
||||||
Realise mode,
|
|
||||||
OperateOn operateOn,
|
|
||||||
std::shared_ptr<Installable> installable);
|
|
||||||
|
|
||||||
std::set<StorePath> toDerivations(
|
|
||||||
ref<Store> store,
|
|
||||||
const std::vector<std::shared_ptr<Installable>> & installables,
|
|
||||||
bool useDeriver = false);
|
|
||||||
|
|
||||||
BuiltPaths toBuiltPaths(
|
|
||||||
ref<Store> evalStore,
|
|
||||||
ref<Store> store,
|
|
||||||
Realise mode,
|
|
||||||
OperateOn operateOn,
|
|
||||||
const std::vector<std::shared_ptr<Installable>> & installables);
|
|
||||||
|
|
||||||
/* Helper function to generate args that invoke $EDITOR on
|
/* Helper function to generate args that invoke $EDITOR on
|
||||||
filename:lineno. */
|
filename:lineno. */
|
||||||
Strings editorFor(const Pos & pos);
|
Strings editorFor(const Pos & pos);
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include "eval-cache.hh"
|
#include "eval-cache.hh"
|
||||||
#include "url.hh"
|
#include "url.hh"
|
||||||
#include "registry.hh"
|
#include "registry.hh"
|
||||||
|
#include "build-result.hh"
|
||||||
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
@ -97,7 +98,7 @@ MixFlakeOptions::MixFlakeOptions()
|
||||||
lockFlags.writeLockFile = false;
|
lockFlags.writeLockFile = false;
|
||||||
lockFlags.inputOverrides.insert_or_assign(
|
lockFlags.inputOverrides.insert_or_assign(
|
||||||
flake::parseInputPath(inputPath),
|
flake::parseInputPath(inputPath),
|
||||||
parseFlakeRef(flakeRef, absPath(".")));
|
parseFlakeRef(flakeRef, absPath("."), true));
|
||||||
}}
|
}}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -133,7 +134,9 @@ SourceExprCommand::SourceExprCommand()
|
||||||
addFlag({
|
addFlag({
|
||||||
.longName = "file",
|
.longName = "file",
|
||||||
.shortName = 'f',
|
.shortName = 'f',
|
||||||
.description = "Interpret installables as attribute paths relative to the Nix expression stored in *file*.",
|
.description =
|
||||||
|
"Interpret installables as attribute paths relative to the Nix expression stored in *file*. "
|
||||||
|
"If *file* is the character -, then a Nix expression will be read from standard input.",
|
||||||
.category = installablesCategory,
|
.category = installablesCategory,
|
||||||
.labels = {"file"},
|
.labels = {"file"},
|
||||||
.handler = {&file},
|
.handler = {&file},
|
||||||
|
@ -158,7 +161,10 @@ SourceExprCommand::SourceExprCommand()
|
||||||
|
|
||||||
Strings SourceExprCommand::getDefaultFlakeAttrPaths()
|
Strings SourceExprCommand::getDefaultFlakeAttrPaths()
|
||||||
{
|
{
|
||||||
return {"defaultPackage." + settings.thisSystem.get()};
|
return {
|
||||||
|
"packages." + settings.thisSystem.get() + ".default",
|
||||||
|
"defaultPackage." + settings.thisSystem.get()
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
|
Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
|
||||||
|
@ -269,9 +275,9 @@ void completeFlakeRefWithFragment(
|
||||||
auto attr = root->findAlongAttrPath(attrPath);
|
auto attr = root->findAlongAttrPath(attrPath);
|
||||||
if (!attr) continue;
|
if (!attr) continue;
|
||||||
|
|
||||||
for (auto & attr2 : attr->getAttrs()) {
|
for (auto & attr2 : (*attr)->getAttrs()) {
|
||||||
if (hasPrefix(attr2, lastAttr)) {
|
if (hasPrefix(attr2, lastAttr)) {
|
||||||
auto attrPath2 = attr->getAttrPath(attr2);
|
auto attrPath2 = (*attr)->getAttrPath(attr2);
|
||||||
/* Strip the attrpath prefix. */
|
/* Strip the attrpath prefix. */
|
||||||
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
|
attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
|
||||||
completions->add(flakeRefS + "#" + concatStringsSep(".", attrPath2));
|
completions->add(flakeRefS + "#" + concatStringsSep(".", attrPath2));
|
||||||
|
@ -465,11 +471,10 @@ std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations
|
||||||
|
|
||||||
std::vector<DerivationInfo> res;
|
std::vector<DerivationInfo> res;
|
||||||
for (auto & drvInfo : drvInfos) {
|
for (auto & drvInfo : drvInfos) {
|
||||||
res.push_back({
|
auto drvPath = drvInfo.queryDrvPath();
|
||||||
state->store->parseStorePath(drvInfo.queryDrvPath()),
|
if (!drvPath)
|
||||||
state->store->maybeParseStorePath(drvInfo.queryOutPath()),
|
throw Error("'%s' is not a derivation", what());
|
||||||
drvInfo.queryOutputName()
|
res.push_back({ *drvPath, drvInfo.queryOutputName() });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -545,13 +550,14 @@ InstallableFlake::InstallableFlake(
|
||||||
SourceExprCommand * cmd,
|
SourceExprCommand * cmd,
|
||||||
ref<EvalState> state,
|
ref<EvalState> state,
|
||||||
FlakeRef && flakeRef,
|
FlakeRef && flakeRef,
|
||||||
Strings && attrPaths,
|
std::string_view fragment,
|
||||||
Strings && prefixes,
|
Strings attrPaths,
|
||||||
|
Strings prefixes,
|
||||||
const flake::LockFlags & lockFlags)
|
const flake::LockFlags & lockFlags)
|
||||||
: InstallableValue(state),
|
: InstallableValue(state),
|
||||||
flakeRef(flakeRef),
|
flakeRef(flakeRef),
|
||||||
attrPaths(attrPaths),
|
attrPaths(fragment == "" ? attrPaths : Strings{(std::string) fragment}),
|
||||||
prefixes(prefixes),
|
prefixes(fragment == "" ? Strings{} : prefixes),
|
||||||
lockFlags(lockFlags)
|
lockFlags(lockFlags)
|
||||||
{
|
{
|
||||||
if (cmd && cmd->getAutoArgs(*state)->size())
|
if (cmd && cmd->getAutoArgs(*state)->size())
|
||||||
|
@ -565,29 +571,37 @@ std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableF
|
||||||
auto cache = openEvalCache(*state, lockedFlake);
|
auto cache = openEvalCache(*state, lockedFlake);
|
||||||
auto root = cache->getRoot();
|
auto root = cache->getRoot();
|
||||||
|
|
||||||
|
Suggestions suggestions;
|
||||||
|
|
||||||
for (auto & attrPath : getActualAttrPaths()) {
|
for (auto & attrPath : getActualAttrPaths()) {
|
||||||
auto attr = root->findAlongAttrPath(
|
debug("trying flake output attribute '%s'", attrPath);
|
||||||
|
|
||||||
|
auto attrOrSuggestions = root->findAlongAttrPath(
|
||||||
parseAttrPath(*state, attrPath),
|
parseAttrPath(*state, attrPath),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!attr) continue;
|
if (!attrOrSuggestions) {
|
||||||
|
suggestions += attrOrSuggestions.getSuggestions();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto attr = *attrOrSuggestions;
|
||||||
|
|
||||||
if (!attr->isDerivation())
|
if (!attr->isDerivation())
|
||||||
throw Error("flake output attribute '%s' is not a derivation", attrPath);
|
throw Error("flake output attribute '%s' is not a derivation", attrPath);
|
||||||
|
|
||||||
auto drvPath = attr->forceDerivation();
|
auto drvPath = attr->forceDerivation();
|
||||||
|
|
||||||
auto drvInfo = DerivationInfo{
|
auto drvInfo = DerivationInfo {
|
||||||
std::move(drvPath),
|
std::move(drvPath),
|
||||||
state->store->maybeParseStorePath(attr->getAttr(state->sOutPath)->getString()),
|
|
||||||
attr->getAttr(state->sOutputName)->getString()
|
attr->getAttr(state->sOutputName)->getString()
|
||||||
};
|
};
|
||||||
|
|
||||||
return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
|
return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Error("flake '%s' does not provide attribute %s",
|
throw Error(suggestions, "flake '%s' does not provide attribute %s",
|
||||||
flakeRef, showAttrPaths(getActualAttrPaths()));
|
flakeRef, showAttrPaths(getActualAttrPaths()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,17 +620,24 @@ std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
|
||||||
|
|
||||||
auto emptyArgs = state.allocBindings(0);
|
auto emptyArgs = state.allocBindings(0);
|
||||||
|
|
||||||
|
Suggestions suggestions;
|
||||||
|
|
||||||
for (auto & attrPath : getActualAttrPaths()) {
|
for (auto & attrPath : getActualAttrPaths()) {
|
||||||
try {
|
try {
|
||||||
auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
|
auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
|
||||||
state.forceValue(*v, pos);
|
state.forceValue(*v, pos);
|
||||||
return {v, pos};
|
return {v, pos};
|
||||||
} catch (AttrPathNotFound & e) {
|
} catch (AttrPathNotFound & e) {
|
||||||
|
suggestions += e.info().suggestions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw Error("flake '%s' does not provide attribute %s",
|
throw Error(
|
||||||
flakeRef, showAttrPaths(getActualAttrPaths()));
|
suggestions,
|
||||||
|
"flake '%s' does not provide attribute %s",
|
||||||
|
flakeRef,
|
||||||
|
showAttrPaths(getActualAttrPaths())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
||||||
|
@ -631,7 +652,7 @@ InstallableFlake::getCursors(EvalState & state)
|
||||||
|
|
||||||
for (auto & attrPath : getActualAttrPaths()) {
|
for (auto & attrPath : getActualAttrPaths()) {
|
||||||
auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
|
auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
|
||||||
if (attr) res.push_back({attr, attrPath});
|
if (attr) res.push_back({*attr, attrPath});
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
@ -676,7 +697,10 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
|
||||||
auto state = getEvalState();
|
auto state = getEvalState();
|
||||||
auto vFile = state->allocValue();
|
auto vFile = state->allocValue();
|
||||||
|
|
||||||
if (file)
|
if (file == "-") {
|
||||||
|
auto e = state->parseStdin();
|
||||||
|
state->eval(e, *vFile);
|
||||||
|
} else if (file)
|
||||||
state->evalFile(lookupFileArg(*state, *file), *vFile);
|
state->evalFile(lookupFileArg(*state, *file), *vFile);
|
||||||
else {
|
else {
|
||||||
auto e = state->parseExprFromString(*expr, absPath("."));
|
auto e = state->parseExprFromString(*expr, absPath("."));
|
||||||
|
@ -708,7 +732,8 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
|
||||||
this,
|
this,
|
||||||
getEvalState(),
|
getEvalState(),
|
||||||
std::move(flakeRef),
|
std::move(flakeRef),
|
||||||
fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment},
|
fragment,
|
||||||
|
getDefaultFlakeAttrPaths(),
|
||||||
getDefaultFlakeAttrPathPrefixes(),
|
getDefaultFlakeAttrPathPrefixes(),
|
||||||
lockFlags));
|
lockFlags));
|
||||||
continue;
|
continue;
|
||||||
|
@ -731,56 +756,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 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,
|
||||||
|
@ -791,21 +780,96 @@ BuiltPaths 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == Realise::Nothing || mode == Realise::Derivation)
|
std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> res;
|
||||||
printMissing(store, pathsToBuild, lvlError);
|
|
||||||
else if (mode == Realise::Outputs)
|
|
||||||
store->buildPaths(pathsToBuild, bMode, evalStore);
|
|
||||||
|
|
||||||
return getBuiltPaths(evalStore, store, pathsToBuild);
|
switch (mode) {
|
||||||
|
|
||||||
|
case Realise::Nothing:
|
||||||
|
case Realise::Derivation:
|
||||||
|
printMissing(store, pathsToBuild, lvlError);
|
||||||
|
|
||||||
|
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: {
|
||||||
|
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
|
||||||
|
if (!buildResult.success())
|
||||||
|
buildResult.rethrow();
|
||||||
|
|
||||||
|
for (auto & installable : backmap[buildResult.path]) {
|
||||||
|
std::visit(overloaded {
|
||||||
|
[&](const DerivedPath::Built & bfd) {
|
||||||
|
std::map<std::string, StorePath> outputs;
|
||||||
|
for (auto & path : buildResult.builtOutputs)
|
||||||
|
outputs.emplace(path.first.outputName, path.second.outPath);
|
||||||
|
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
|
||||||
|
},
|
||||||
|
[&](const DerivedPath::Opaque & bo) {
|
||||||
|
res.push_back({installable, BuiltPath::Opaque { bo.path }});
|
||||||
|
},
|
||||||
|
}, buildResult.path.raw());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltPaths toBuiltPaths(
|
BuiltPaths Installable::toBuiltPaths(
|
||||||
ref<Store> evalStore,
|
ref<Store> evalStore,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
Realise mode,
|
Realise mode,
|
||||||
|
@ -813,19 +877,19 @@ BuiltPaths toBuiltPaths(
|
||||||
const std::vector<std::shared_ptr<Installable>> & installables)
|
const std::vector<std::shared_ptr<Installable>> & installables)
|
||||||
{
|
{
|
||||||
if (operateOn == OperateOn::Output)
|
if (operateOn == OperateOn::Output)
|
||||||
return build(evalStore, store, mode, installables);
|
return Installable::build(evalStore, store, mode, installables);
|
||||||
else {
|
else {
|
||||||
if (mode == Realise::Nothing)
|
if (mode == Realise::Nothing)
|
||||||
settings.readOnlyMode = true;
|
settings.readOnlyMode = true;
|
||||||
|
|
||||||
BuiltPaths res;
|
BuiltPaths res;
|
||||||
for (auto & drvPath : toDerivations(store, installables, true))
|
for (auto & drvPath : Installable::toDerivations(store, installables, true))
|
||||||
res.push_back(BuiltPath::Opaque{drvPath});
|
res.push_back(BuiltPath::Opaque{drvPath});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePathSet toStorePaths(
|
StorePathSet Installable::toStorePaths(
|
||||||
ref<Store> evalStore,
|
ref<Store> evalStore,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
Realise mode, OperateOn operateOn,
|
Realise mode, OperateOn operateOn,
|
||||||
|
@ -839,7 +903,7 @@ StorePathSet toStorePaths(
|
||||||
return outPaths;
|
return outPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePath toStorePath(
|
StorePath Installable::toStorePath(
|
||||||
ref<Store> evalStore,
|
ref<Store> evalStore,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
Realise mode, OperateOn operateOn,
|
Realise mode, OperateOn operateOn,
|
||||||
|
@ -853,7 +917,7 @@ StorePath toStorePath(
|
||||||
return *paths.begin();
|
return *paths.begin();
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePathSet toDerivations(
|
StorePathSet Installable::toDerivations(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
const std::vector<std::shared_ptr<Installable>> & installables,
|
const std::vector<std::shared_ptr<Installable>> & installables,
|
||||||
bool useDeriver)
|
bool useDeriver)
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include "path-with-outputs.hh"
|
#include "path-with-outputs.hh"
|
||||||
#include "derived-path.hh"
|
#include "derived-path.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
#include "flake/flake.hh"
|
#include "flake/flake.hh"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
|
@ -29,6 +30,27 @@ struct UnresolvedApp
|
||||||
App resolve(ref<Store> evalStore, ref<Store> store);
|
App resolve(ref<Store> evalStore, ref<Store> store);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class Realise {
|
||||||
|
/* Build the derivation. Postcondition: the
|
||||||
|
derivation outputs exist. */
|
||||||
|
Outputs,
|
||||||
|
/* Don't build the derivation. Postcondition: the store derivation
|
||||||
|
exists. */
|
||||||
|
Derivation,
|
||||||
|
/* Evaluate in dry-run mode. Postcondition: nothing. */
|
||||||
|
// FIXME: currently unused, but could be revived if we can
|
||||||
|
// evaluate derivations in-memory.
|
||||||
|
Nothing
|
||||||
|
};
|
||||||
|
|
||||||
|
/* How to handle derivations in commands that operate on store paths. */
|
||||||
|
enum class OperateOn {
|
||||||
|
/* Operate on the output path. */
|
||||||
|
Output,
|
||||||
|
/* Operate on the .drv path. */
|
||||||
|
Derivation
|
||||||
|
};
|
||||||
|
|
||||||
struct Installable
|
struct Installable
|
||||||
{
|
{
|
||||||
virtual ~Installable() { }
|
virtual ~Installable() { }
|
||||||
|
@ -68,6 +90,46 @@ struct Installable
|
||||||
{
|
{
|
||||||
return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
|
return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static BuiltPaths build(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
Realise mode,
|
||||||
|
const std::vector<std::shared_ptr<Installable>> & installables,
|
||||||
|
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(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
Realise mode,
|
||||||
|
OperateOn operateOn,
|
||||||
|
const std::vector<std::shared_ptr<Installable>> & installables);
|
||||||
|
|
||||||
|
static StorePath toStorePath(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
Realise mode,
|
||||||
|
OperateOn operateOn,
|
||||||
|
std::shared_ptr<Installable> installable);
|
||||||
|
|
||||||
|
static std::set<StorePath> toDerivations(
|
||||||
|
ref<Store> store,
|
||||||
|
const std::vector<std::shared_ptr<Installable>> & installables,
|
||||||
|
bool useDeriver = false);
|
||||||
|
|
||||||
|
static BuiltPaths toBuiltPaths(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
Realise mode,
|
||||||
|
OperateOn operateOn,
|
||||||
|
const std::vector<std::shared_ptr<Installable>> & installables);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InstallableValue : Installable
|
struct InstallableValue : Installable
|
||||||
|
@ -79,7 +141,6 @@ struct InstallableValue : Installable
|
||||||
struct DerivationInfo
|
struct DerivationInfo
|
||||||
{
|
{
|
||||||
StorePath drvPath;
|
StorePath drvPath;
|
||||||
std::optional<StorePath> outPath;
|
|
||||||
std::string outputName;
|
std::string outputName;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -102,8 +163,9 @@ struct InstallableFlake : InstallableValue
|
||||||
SourceExprCommand * cmd,
|
SourceExprCommand * cmd,
|
||||||
ref<EvalState> state,
|
ref<EvalState> state,
|
||||||
FlakeRef && flakeRef,
|
FlakeRef && flakeRef,
|
||||||
Strings && attrPaths,
|
std::string_view fragment,
|
||||||
Strings && prefixes,
|
Strings attrPaths,
|
||||||
|
Strings prefixes,
|
||||||
const flake::LockFlags & lockFlags);
|
const flake::LockFlags & lockFlags);
|
||||||
|
|
||||||
std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }
|
std::string what() const override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }
|
||||||
|
|
|
@ -25,6 +25,7 @@ extern "C" {
|
||||||
#include "eval-inline.hh"
|
#include "eval-inline.hh"
|
||||||
#include "attr-path.hh"
|
#include "attr-path.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "log-store.hh"
|
||||||
#include "common-eval-args.hh"
|
#include "common-eval-args.hh"
|
||||||
#include "get-drvs.hh"
|
#include "get-drvs.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
|
@ -45,7 +46,7 @@ struct NixRepl
|
||||||
: gc
|
: gc
|
||||||
#endif
|
#endif
|
||||||
{
|
{
|
||||||
string curDir;
|
std::string curDir;
|
||||||
ref<EvalState> state;
|
ref<EvalState> state;
|
||||||
Bindings * autoArgs;
|
Bindings * autoArgs;
|
||||||
|
|
||||||
|
@ -65,10 +66,10 @@ struct NixRepl
|
||||||
NixRepl(ref<EvalState> state);
|
NixRepl(ref<EvalState> state);
|
||||||
~NixRepl();
|
~NixRepl();
|
||||||
void mainLoop(const std::vector<std::string> & files);
|
void mainLoop(const std::vector<std::string> & files);
|
||||||
StringSet completePrefix(string prefix);
|
StringSet completePrefix(const std::string & prefix);
|
||||||
bool getLine(string & input, const std::string &prompt);
|
bool getLine(std::string & input, const std::string &prompt);
|
||||||
StorePath getDerivationPath(Value & v);
|
StorePath getDerivationPath(Value & v);
|
||||||
bool processLine(string line);
|
bool processLine(std::string line);
|
||||||
void loadFile(const Path & path);
|
void loadFile(const Path & path);
|
||||||
void loadFlake(const std::string & flakeRef);
|
void loadFlake(const std::string & flakeRef);
|
||||||
void initEnv();
|
void initEnv();
|
||||||
|
@ -76,22 +77,21 @@ struct NixRepl
|
||||||
void reloadFiles();
|
void reloadFiles();
|
||||||
void addAttrsToScope(Value & attrs);
|
void addAttrsToScope(Value & attrs);
|
||||||
void addVarToScope(const Symbol & name, Value & v);
|
void addVarToScope(const Symbol & name, Value & v);
|
||||||
Expr * parseString(string s);
|
Expr * parseString(std::string s);
|
||||||
void evalString(string s, Value & v);
|
void evalString(std::string s, Value & v);
|
||||||
void loadDebugTraceEnv(DebugTrace &dt);
|
void loadDebugTraceEnv(DebugTrace &dt);
|
||||||
|
|
||||||
typedef set<Value *> ValuesSeen;
|
typedef std::set<Value *> ValuesSeen;
|
||||||
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
|
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
|
||||||
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
|
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
string removeWhitespace(string s)
|
std::string removeWhitespace(std::string s)
|
||||||
{
|
{
|
||||||
s = chomp(s);
|
s = chomp(s);
|
||||||
size_t n = s.find_first_not_of(" \n\r\t");
|
size_t n = s.find_first_not_of(" \n\r\t");
|
||||||
if (n != string::npos) s = string(s, n);
|
if (n != std::string::npos) s = std::string(s, n);
|
||||||
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,7 +111,7 @@ NixRepl::~NixRepl()
|
||||||
write_history(historyFile.c_str());
|
write_history(historyFile.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
string runNix(Path program, const Strings & args,
|
std::string runNix(Path program, const Strings & args,
|
||||||
const std::optional<std::string> & input = {})
|
const std::optional<std::string> & input = {})
|
||||||
{
|
{
|
||||||
auto subprocessEnv = getEnv();
|
auto subprocessEnv = getEnv();
|
||||||
|
@ -229,7 +229,7 @@ std::ostream& showDebugTrace(std::ostream &out, const DebugTrace &dt)
|
||||||
|
|
||||||
void NixRepl::mainLoop(const std::vector<std::string> & files)
|
void NixRepl::mainLoop(const std::vector<std::string> & files)
|
||||||
{
|
{
|
||||||
string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
||||||
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
|
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
|
||||||
|
|
||||||
if (!files.empty()) {
|
if (!files.empty()) {
|
||||||
|
@ -297,7 +297,7 @@ void NixRepl::mainLoop(const std::vector<std::string> & files)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool NixRepl::getLine(string & input, const std::string &prompt)
|
bool NixRepl::getLine(std::string & input, const std::string & prompt)
|
||||||
{
|
{
|
||||||
struct sigaction act, old;
|
struct sigaction act, old;
|
||||||
sigset_t savedSignalMask, set;
|
sigset_t savedSignalMask, set;
|
||||||
|
@ -342,7 +342,7 @@ bool NixRepl::getLine(string & input, const std::string &prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StringSet NixRepl::completePrefix(string prefix)
|
StringSet NixRepl::completePrefix(const std::string & prefix)
|
||||||
{
|
{
|
||||||
StringSet completions;
|
StringSet completions;
|
||||||
|
|
||||||
|
@ -358,7 +358,7 @@ StringSet NixRepl::completePrefix(string prefix)
|
||||||
|
|
||||||
size_t slash, dot;
|
size_t slash, dot;
|
||||||
|
|
||||||
if ((slash = cur.rfind('/')) != string::npos) {
|
if ((slash = cur.rfind('/')) != std::string::npos) {
|
||||||
try {
|
try {
|
||||||
auto dir = std::string(cur, 0, slash);
|
auto dir = std::string(cur, 0, slash);
|
||||||
auto prefix2 = std::string(cur, slash + 1);
|
auto prefix2 = std::string(cur, slash + 1);
|
||||||
|
@ -368,11 +368,11 @@ StringSet NixRepl::completePrefix(string prefix)
|
||||||
}
|
}
|
||||||
} catch (Error &) {
|
} catch (Error &) {
|
||||||
}
|
}
|
||||||
} else if ((dot = cur.rfind('.')) == string::npos) {
|
} else if ((dot = cur.rfind('.')) == std::string::npos) {
|
||||||
/* This is a variable name; look it up in the current scope. */
|
/* This is a variable name; look it up in the current scope. */
|
||||||
StringSet::iterator i = varNames.lower_bound(cur);
|
StringSet::iterator i = varNames.lower_bound(cur);
|
||||||
while (i != varNames.end()) {
|
while (i != varNames.end()) {
|
||||||
if (string(*i, 0, cur.size()) != cur) break;
|
if (i->substr(0, cur.size()) != cur) break;
|
||||||
completions.insert(prev + *i);
|
completions.insert(prev + *i);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
@ -381,8 +381,8 @@ StringSet NixRepl::completePrefix(string prefix)
|
||||||
/* This is an expression that should evaluate to an
|
/* This is an expression that should evaluate to an
|
||||||
attribute set. Evaluate it to get the names of the
|
attribute set. Evaluate it to get the names of the
|
||||||
attributes. */
|
attributes. */
|
||||||
string expr(cur, 0, dot);
|
auto expr = cur.substr(0, dot);
|
||||||
string cur2 = string(cur, dot + 1);
|
auto cur2 = cur.substr(dot + 1);
|
||||||
|
|
||||||
Expr * e = parseString(expr);
|
Expr * e = parseString(expr);
|
||||||
Value v;
|
Value v;
|
||||||
|
@ -390,8 +390,8 @@ StringSet NixRepl::completePrefix(string prefix)
|
||||||
state->forceAttrs(v, noPos);
|
state->forceAttrs(v, noPos);
|
||||||
|
|
||||||
for (auto & i : *v.attrs) {
|
for (auto & i : *v.attrs) {
|
||||||
string name = i.name;
|
std::string name = i.name;
|
||||||
if (string(name, 0, cur2.size()) != cur2) continue;
|
if (name.substr(0, cur2.size()) != cur2) continue;
|
||||||
completions.insert(prev + expr + "." + name);
|
completions.insert(prev + expr + "." + name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,7 +410,7 @@ StringSet NixRepl::completePrefix(string prefix)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool isVarName(const string & s)
|
static bool isVarName(std::string_view s)
|
||||||
{
|
{
|
||||||
if (s.size() == 0) return false;
|
if (s.size() == 0) return false;
|
||||||
char c = s[0];
|
char c = s[0];
|
||||||
|
@ -429,13 +429,12 @@ StorePath NixRepl::getDerivationPath(Value & v) {
|
||||||
auto drvInfo = getDerivation(*state, v, false);
|
auto drvInfo = getDerivation(*state, v, false);
|
||||||
if (!drvInfo)
|
if (!drvInfo)
|
||||||
throw Error("expression does not evaluate to a derivation, so I can't build it");
|
throw Error("expression does not evaluate to a derivation, so I can't build it");
|
||||||
Path drvPathRaw = drvInfo->queryDrvPath();
|
auto drvPath = drvInfo->queryDrvPath();
|
||||||
if (drvPathRaw == "")
|
if (!drvPath)
|
||||||
throw Error("expression did not evaluate to a valid derivation (no drv path)");
|
throw Error("expression did not evaluate to a valid derivation (no 'drvPath' attribute)");
|
||||||
StorePath drvPath = state->store->parseStorePath(drvPathRaw);
|
if (!state->store->isValidPath(*drvPath))
|
||||||
if (!state->store->isValidPath(drvPath))
|
throw Error("expression evaluated to invalid derivation '%s'", state->store->printStorePath(*drvPath));
|
||||||
throw Error("expression did not evaluate to a valid derivation (invalid drv path)");
|
return *drvPath;
|
||||||
return drvPath;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void NixRepl::loadDebugTraceEnv(DebugTrace &dt)
|
void NixRepl::loadDebugTraceEnv(DebugTrace &dt)
|
||||||
|
@ -457,18 +456,19 @@ void NixRepl::loadDebugTraceEnv(DebugTrace &dt)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NixRepl::processLine(string line)
|
bool NixRepl::processLine(std::string line)
|
||||||
{
|
{
|
||||||
|
line = trim(line);
|
||||||
if (line == "") return true;
|
if (line == "") return true;
|
||||||
|
|
||||||
_isInterrupted = false;
|
_isInterrupted = false;
|
||||||
|
|
||||||
string command, arg;
|
std::string command, arg;
|
||||||
|
|
||||||
if (line[0] == ':') {
|
if (line[0] == ':') {
|
||||||
size_t p = line.find_first_of(" \n\r\t");
|
size_t p = line.find_first_of(" \n\r\t");
|
||||||
command = string(line, 0, p);
|
command = line.substr(0, p);
|
||||||
if (p != string::npos) arg = removeWhitespace(string(line, p));
|
if (p != std::string::npos) arg = removeWhitespace(line.substr(p));
|
||||||
} else {
|
} else {
|
||||||
arg = line;
|
arg = line;
|
||||||
}
|
}
|
||||||
|
@ -667,9 +667,16 @@ bool NixRepl::processLine(string line)
|
||||||
bool foundLog = false;
|
bool foundLog = false;
|
||||||
RunPager pager;
|
RunPager pager;
|
||||||
for (auto & sub : subs) {
|
for (auto & sub : subs) {
|
||||||
auto log = sub->getBuildLog(drvPath);
|
auto * logSubP = dynamic_cast<LogStore *>(&*sub);
|
||||||
|
if (!logSubP) {
|
||||||
|
printInfo("Skipped '%s' which does not support retrieving build logs", sub->getUri());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto & logSub = *logSubP;
|
||||||
|
|
||||||
|
auto log = logSub.getBuildLog(drvPath);
|
||||||
if (log) {
|
if (log) {
|
||||||
printInfo("got build log for '%s' from '%s'", drvPathRaw, sub->getUri());
|
printInfo("got build log for '%s' from '%s'", drvPathRaw, logSub.getUri());
|
||||||
logger->writeToStdout(*log);
|
logger->writeToStdout(*log);
|
||||||
foundLog = true;
|
foundLog = true;
|
||||||
break;
|
break;
|
||||||
|
@ -733,13 +740,13 @@ bool NixRepl::processLine(string line)
|
||||||
|
|
||||||
else {
|
else {
|
||||||
size_t p = line.find('=');
|
size_t p = line.find('=');
|
||||||
string name;
|
std::string name;
|
||||||
if (p != string::npos &&
|
if (p != std::string::npos &&
|
||||||
p < line.size() &&
|
p < line.size() &&
|
||||||
line[p + 1] != '=' &&
|
line[p + 1] != '=' &&
|
||||||
isVarName(name = removeWhitespace(string(line, 0, p))))
|
isVarName(name = removeWhitespace(line.substr(0, p))))
|
||||||
{
|
{
|
||||||
Expr * e = parseString(string(line, p + 1));
|
Expr * e = parseString(line.substr(p + 1));
|
||||||
Value & v(*state->allocValue());
|
Value & v(*state->allocValue());
|
||||||
v.mkThunk(env, e);
|
v.mkThunk(env, e);
|
||||||
addVarToScope(state->symbols.create(name), v);
|
addVarToScope(state->symbols.create(name), v);
|
||||||
|
@ -766,9 +773,12 @@ void NixRepl::loadFile(const Path & path)
|
||||||
|
|
||||||
void NixRepl::loadFlake(const std::string & flakeRefS)
|
void NixRepl::loadFlake(const std::string & flakeRefS)
|
||||||
{
|
{
|
||||||
|
if (flakeRefS.empty())
|
||||||
|
throw Error("cannot use ':load-flake' without a path specified. (Use '.' for the current working directory.)");
|
||||||
|
|
||||||
auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true);
|
auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true);
|
||||||
if (evalSettings.pureEval && !flakeRef.input.isImmutable())
|
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||||
throw Error("cannot use ':load-flake' on mutable flake reference '%s' (use --impure to override)", flakeRefS);
|
throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS);
|
||||||
|
|
||||||
Value v;
|
Value v;
|
||||||
|
|
||||||
|
@ -829,7 +839,7 @@ void NixRepl::addAttrsToScope(Value & attrs)
|
||||||
for (auto & i : *attrs.attrs) {
|
for (auto & i : *attrs.attrs) {
|
||||||
staticEnv->vars.emplace_back(i.name, displ);
|
staticEnv->vars.emplace_back(i.name, displ);
|
||||||
env->values[displ++] = i.value;
|
env->values[displ++] = i.value;
|
||||||
varNames.insert((string) i.name);
|
varNames.insert((std::string) i.name);
|
||||||
}
|
}
|
||||||
staticEnv->sort();
|
staticEnv->sort();
|
||||||
staticEnv->deduplicate();
|
staticEnv->deduplicate();
|
||||||
|
@ -846,17 +856,18 @@ void NixRepl::addVarToScope(const Symbol & name, Value & v)
|
||||||
staticEnv->vars.emplace_back(name, displ);
|
staticEnv->vars.emplace_back(name, displ);
|
||||||
staticEnv->sort();
|
staticEnv->sort();
|
||||||
env->values[displ++] = &v;
|
env->values[displ++] = &v;
|
||||||
varNames.insert((string) name);
|
varNames.insert((std::string) name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Expr * NixRepl::parseString(string s)
|
|
||||||
|
Expr * NixRepl::parseString(std::string s)
|
||||||
{
|
{
|
||||||
Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv);
|
Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv);
|
||||||
return e;
|
return e;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void NixRepl::evalString(string s, Value & v)
|
void NixRepl::evalString(std::string s, Value & v)
|
||||||
{
|
{
|
||||||
Expr * e = parseString(s);
|
Expr * e = parseString(s);
|
||||||
e->eval(*state, *env, v);
|
e->eval(*state, *env, v);
|
||||||
|
@ -925,14 +936,17 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
||||||
str << "«derivation ";
|
str << "«derivation ";
|
||||||
Bindings::iterator i = v.attrs->find(state->sDrvPath);
|
Bindings::iterator i = v.attrs->find(state->sDrvPath);
|
||||||
PathSet context;
|
PathSet context;
|
||||||
Path drvPath = i != v.attrs->end() ? state->coerceToPath(*i->pos, *i->value, context) : "???";
|
if (i != v.attrs->end())
|
||||||
str << drvPath << "»";
|
str << state->store->printStorePath(state->coerceToStorePath(*i->pos, *i->value, context));
|
||||||
|
else
|
||||||
|
str << "???";
|
||||||
|
str << "»";
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (maxDepth > 0) {
|
else if (maxDepth > 0) {
|
||||||
str << "{ ";
|
str << "{ ";
|
||||||
|
|
||||||
typedef std::map<string, Value *> Sorted;
|
typedef std::map<std::string, Value *> Sorted;
|
||||||
Sorted sorted;
|
Sorted sorted;
|
||||||
for (auto & i : *v.attrs)
|
for (auto & i : *v.attrs)
|
||||||
sorted[i.name] = i.value;
|
sorted[i.name] = i.value;
|
||||||
|
@ -943,7 +957,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m
|
||||||
else
|
else
|
||||||
printStringValue(str, i.first.c_str());
|
printStringValue(str, i.first.c_str());
|
||||||
str << " = ";
|
str << " = ";
|
||||||
if (seen.find(i.second) != seen.end())
|
if (seen.count(i.second))
|
||||||
str << "«repeated»";
|
str << "«repeated»";
|
||||||
else
|
else
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -9,7 +9,7 @@ namespace nix {
|
||||||
static Strings parseAttrPath(std::string_view s)
|
static Strings parseAttrPath(std::string_view s)
|
||||||
{
|
{
|
||||||
Strings res;
|
Strings res;
|
||||||
string cur;
|
std::string cur;
|
||||||
auto i = s.begin();
|
auto i = s.begin();
|
||||||
while (i != s.end()) {
|
while (i != s.end()) {
|
||||||
if (*i == '.') {
|
if (*i == '.') {
|
||||||
|
@ -41,7 +41,7 @@ std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attrPath,
|
std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string & attrPath,
|
||||||
Bindings & autoArgs, Value & vIn)
|
Bindings & autoArgs, Value & vIn)
|
||||||
{
|
{
|
||||||
Strings tokens = parseAttrPath(attrPath);
|
Strings tokens = parseAttrPath(attrPath);
|
||||||
|
@ -74,8 +74,14 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attr
|
||||||
throw Error("empty attribute name in selection path '%1%'", attrPath);
|
throw Error("empty attribute name in selection path '%1%'", attrPath);
|
||||||
|
|
||||||
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
|
Bindings::iterator a = v->attrs->find(state.symbols.create(attr));
|
||||||
if (a == v->attrs->end())
|
if (a == v->attrs->end()) {
|
||||||
throw AttrPathNotFound("attribute '%1%' in selection path '%2%' not found", attr, attrPath);
|
std::set<std::string> attrNames;
|
||||||
|
for (auto & attr : *v->attrs)
|
||||||
|
attrNames.insert(attr.name);
|
||||||
|
|
||||||
|
auto suggestions = Suggestions::bestMatches(attrNames, attr);
|
||||||
|
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
|
||||||
|
}
|
||||||
v = &*a->value;
|
v = &*a->value;
|
||||||
pos = *a->pos;
|
pos = *a->pos;
|
||||||
}
|
}
|
||||||
|
@ -121,7 +127,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what)
|
||||||
std::string filename(pos, 0, colon);
|
std::string filename(pos, 0, colon);
|
||||||
unsigned int lineno;
|
unsigned int lineno;
|
||||||
try {
|
try {
|
||||||
lineno = std::stoi(std::string(pos, colon + 1, string::npos));
|
lineno = std::stoi(std::string(pos, colon + 1, std::string::npos));
|
||||||
} catch (std::invalid_argument & e) {
|
} catch (std::invalid_argument & e) {
|
||||||
throw ParseError("cannot parse line number '%s'", pos);
|
throw ParseError("cannot parse line number '%s'", pos);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,11 @@ namespace nix {
|
||||||
MakeError(AttrPathNotFound, Error);
|
MakeError(AttrPathNotFound, Error);
|
||||||
MakeError(NoPositionInfo, Error);
|
MakeError(NoPositionInfo, Error);
|
||||||
|
|
||||||
std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const string & attrPath,
|
std::pair<Value *, Pos> findAlongAttrPath(
|
||||||
Bindings & autoArgs, Value & vIn);
|
EvalState & state,
|
||||||
|
const std::string & attrPath,
|
||||||
|
Bindings & autoArgs,
|
||||||
|
Value & vIn);
|
||||||
|
|
||||||
/* Heuristic to find the filename and lineno or a nix value. */
|
/* Heuristic to find the filename and lineno or a nix value. */
|
||||||
Pos findPackageFilename(EvalState & state, Value & v, std::string what);
|
Pos findPackageFilename(EvalState & state, Value & v, std::string what);
|
||||||
|
|
|
@ -105,7 +105,7 @@ public:
|
||||||
for (size_t n = 0; n < size_; n++)
|
for (size_t n = 0; n < size_; n++)
|
||||||
res.emplace_back(&attrs[n]);
|
res.emplace_back(&attrs[n]);
|
||||||
std::sort(res.begin(), res.end(), [](const Attr * a, const Attr * b) {
|
std::sort(res.begin(), res.end(), [](const Attr * a, const Attr * b) {
|
||||||
return (const string &) a->name < (const string &) b->name;
|
return (const std::string &) a->name < (const std::string &) b->name;
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
|
||||||
for (auto & i : autoArgs) {
|
for (auto & i : autoArgs) {
|
||||||
auto v = state.allocValue();
|
auto v = state.allocValue();
|
||||||
if (i.second[0] == 'E')
|
if (i.second[0] == 'E')
|
||||||
state.mkThunk_(*v, state.parseExprFromString(string(i.second, 1), absPath(".")));
|
state.mkThunk_(*v, state.parseExprFromString(i.second.substr(1), absPath(".")));
|
||||||
else
|
else
|
||||||
v->mkString(((std::string_view) i.second).substr(1));
|
v->mkString(((std::string_view) i.second).substr(1));
|
||||||
res.insert(state.symbols.create(i.first), v);
|
res.insert(state.symbols.create(i.first), v);
|
||||||
|
@ -85,17 +85,17 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
|
||||||
return res.finish();
|
return res.finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
Path lookupFileArg(EvalState & state, string s)
|
Path lookupFileArg(EvalState & state, std::string_view s)
|
||||||
{
|
{
|
||||||
if (isUri(s)) {
|
if (isUri(s)) {
|
||||||
return state.store->toRealPath(
|
return state.store->toRealPath(
|
||||||
fetchers::downloadTarball(
|
fetchers::downloadTarball(
|
||||||
state.store, resolveUri(s), "source", false).first.storePath);
|
state.store, resolveUri(s), "source", false).first.storePath);
|
||||||
} else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
|
} else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
|
||||||
Path p = s.substr(1, s.size() - 2);
|
Path p(s.substr(1, s.size() - 2));
|
||||||
return state.findFile(p);
|
return state.findFile(p);
|
||||||
} else
|
} else
|
||||||
return absPath(s);
|
return absPath(std::string(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,6 @@ private:
|
||||||
std::map<std::string, std::string> autoArgs;
|
std::map<std::string, std::string> autoArgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
Path lookupFileArg(EvalState & state, string s);
|
Path lookupFileArg(EvalState & state, std::string_view s);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
@ -406,6 +409,16 @@ Value & AttrCursor::forceValue()
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Suggestions AttrCursor::getSuggestionsForAttr(Symbol name)
|
||||||
|
{
|
||||||
|
auto attrNames = getAttrs();
|
||||||
|
std::set<std::string> strAttrNames;
|
||||||
|
for (auto & name : attrNames)
|
||||||
|
strAttrNames.insert(std::string(name));
|
||||||
|
|
||||||
|
return Suggestions::bestMatches(strAttrNames, name);
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErrors)
|
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErrors)
|
||||||
{
|
{
|
||||||
if (root->db) {
|
if (root->db) {
|
||||||
|
@ -446,6 +459,11 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
||||||
return nullptr;
|
return nullptr;
|
||||||
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
//throw TypeError("'%s' is not an attribute set", getAttrPathStr());
|
||||||
|
|
||||||
|
for (auto & attr : *v.attrs) {
|
||||||
|
if (root->db)
|
||||||
|
root->db->setPlaceholder({cachedValue->first, attr.name});
|
||||||
|
}
|
||||||
|
|
||||||
auto attr = v.attrs->get(name);
|
auto attr = v.attrs->get(name);
|
||||||
|
|
||||||
if (!attr) {
|
if (!attr) {
|
||||||
|
@ -464,7 +482,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
||||||
cachedValue2 = {root->db->setPlaceholder({cachedValue->first, name}), placeholder_t()};
|
cachedValue2 = {root->db->setPlaceholder({cachedValue->first, name}), placeholder_t()};
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::make_shared<AttrCursor>(
|
return make_ref<AttrCursor>(
|
||||||
root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
|
root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,27 +491,31 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name)
|
||||||
return maybeGetAttr(root->state.symbols.create(name));
|
return maybeGetAttr(root->state.symbols.create(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> AttrCursor::getAttr(Symbol name, bool forceErrors)
|
ref<AttrCursor> AttrCursor::getAttr(Symbol name, bool forceErrors)
|
||||||
{
|
{
|
||||||
auto p = maybeGetAttr(name, forceErrors);
|
auto p = maybeGetAttr(name, forceErrors);
|
||||||
if (!p)
|
if (!p)
|
||||||
throw Error("attribute '%s' does not exist", getAttrPathStr(name));
|
throw Error("attribute '%s' does not exist", getAttrPathStr(name));
|
||||||
return p;
|
return ref(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> AttrCursor::getAttr(std::string_view name)
|
ref<AttrCursor> AttrCursor::getAttr(std::string_view name)
|
||||||
{
|
{
|
||||||
return getAttr(root->state.symbols.create(name));
|
return getAttr(root->state.symbols.create(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force)
|
OrSuggestions<ref<AttrCursor>> AttrCursor::findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force)
|
||||||
{
|
{
|
||||||
auto res = shared_from_this();
|
auto res = shared_from_this();
|
||||||
for (auto & attr : attrPath) {
|
for (auto & attr : attrPath) {
|
||||||
res = res->maybeGetAttr(attr, force);
|
auto child = res->maybeGetAttr(attr, force);
|
||||||
if (!res) return {};
|
if (!child) {
|
||||||
|
auto suggestions = res->getSuggestionsForAttr(attr);
|
||||||
|
return OrSuggestions<ref<AttrCursor>>::failed(suggestions);
|
||||||
|
}
|
||||||
|
res = child;
|
||||||
}
|
}
|
||||||
return res;
|
return ref(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string AttrCursor::getString()
|
std::string AttrCursor::getString()
|
||||||
|
@ -527,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;
|
||||||
}
|
}
|
||||||
|
@ -544,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
|
||||||
|
@ -599,7 +621,7 @@ std::vector<Symbol> AttrCursor::getAttrs()
|
||||||
for (auto & attr : *getValue().attrs)
|
for (auto & attr : *getValue().attrs)
|
||||||
attrs.push_back(attr.name);
|
attrs.push_back(attr.name);
|
||||||
std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) {
|
std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) {
|
||||||
return (const string &) a < (const string &) b;
|
return (const std::string &) a < (const std::string &) b;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (root->db)
|
if (root->db)
|
||||||
|
|
|
@ -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>,
|
||||||
|
@ -94,15 +94,17 @@ public:
|
||||||
|
|
||||||
std::string getAttrPathStr(Symbol name) const;
|
std::string getAttrPathStr(Symbol name) const;
|
||||||
|
|
||||||
|
Suggestions getSuggestionsForAttr(Symbol name);
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> maybeGetAttr(Symbol name, bool forceErrors = false);
|
std::shared_ptr<AttrCursor> maybeGetAttr(Symbol name, bool forceErrors = false);
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name);
|
std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name);
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> getAttr(Symbol name, bool forceErrors = false);
|
ref<AttrCursor> getAttr(Symbol name, bool forceErrors = false);
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> getAttr(std::string_view name);
|
ref<AttrCursor> getAttr(std::string_view name);
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force = false);
|
OrSuggestions<ref<AttrCursor>> findAlongAttrPath(const std::vector<Symbol> & attrPath, bool force = false);
|
||||||
|
|
||||||
std::string getString();
|
std::string getString();
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,81 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* Note: Various places expect the allocated memory to be zeroed. */
|
||||||
|
[[gnu::always_inline]]
|
||||||
|
inline void * allocBytes(size_t n)
|
||||||
|
{
|
||||||
|
void * p;
|
||||||
|
#if HAVE_BOEHMGC
|
||||||
|
p = GC_MALLOC(n);
|
||||||
|
#else
|
||||||
|
p = calloc(n, 1);
|
||||||
|
#endif
|
||||||
|
if (!p) throw std::bad_alloc();
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[gnu::always_inline]]
|
||||||
|
Value * EvalState::allocValue()
|
||||||
|
{
|
||||||
|
#if HAVE_BOEHMGC
|
||||||
|
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
|
||||||
|
GC_malloc_many returns a linked list of objects of the given size, where the first word
|
||||||
|
of each object is also the pointer to the next object in the list. This also means that we
|
||||||
|
have to explicitly clear the first word of every object we take. */
|
||||||
|
if (!*valueAllocCache) {
|
||||||
|
*valueAllocCache = GC_malloc_many(sizeof(Value));
|
||||||
|
if (!*valueAllocCache) throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GC_NEXT is a convenience macro for accessing the first word of an object.
|
||||||
|
Take the first list item, advance the list to the next item, and clear the next pointer. */
|
||||||
|
void * p = *valueAllocCache;
|
||||||
|
*valueAllocCache = GC_NEXT(p);
|
||||||
|
GC_NEXT(p) = nullptr;
|
||||||
|
#else
|
||||||
|
void * p = allocBytes(sizeof(Value));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
nrValues++;
|
||||||
|
return (Value *) p;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[gnu::always_inline]]
|
||||||
|
Env & EvalState::allocEnv(size_t size)
|
||||||
|
{
|
||||||
|
nrEnvs++;
|
||||||
|
nrValuesInEnvs += size;
|
||||||
|
|
||||||
|
Env * env;
|
||||||
|
|
||||||
|
#if HAVE_BOEHMGC
|
||||||
|
if (size == 1) {
|
||||||
|
/* see allocValue for explanations. */
|
||||||
|
if (!*env1AllocCache) {
|
||||||
|
*env1AllocCache = GC_malloc_many(sizeof(Env) + sizeof(Value *));
|
||||||
|
if (!*env1AllocCache) throw std::bad_alloc();
|
||||||
|
}
|
||||||
|
|
||||||
|
void * p = *env1AllocCache;
|
||||||
|
*env1AllocCache = GC_NEXT(p);
|
||||||
|
GC_NEXT(p) = nullptr;
|
||||||
|
env = (Env *) p;
|
||||||
|
} else
|
||||||
|
#endif
|
||||||
|
env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
|
||||||
|
|
||||||
|
env->type = Env::Plain;
|
||||||
|
|
||||||
|
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
|
||||||
|
|
||||||
|
return *env;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[gnu::always_inline]]
|
||||||
void EvalState::forceValue(Value & v, const Pos & pos)
|
void EvalState::forceValue(Value & v, const Pos & pos)
|
||||||
{
|
{
|
||||||
forceValue(v, [&]() { return pos; });
|
forceValue(v, [&]() { return pos; });
|
||||||
|
@ -66,6 +141,7 @@ void EvalState::forceValue(Value & v, Callable getPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[gnu::always_inline]]
|
||||||
inline void EvalState::forceAttrs(Value & v, const Pos & pos)
|
inline void EvalState::forceAttrs(Value & v, const Pos & pos)
|
||||||
{
|
{
|
||||||
forceAttrs(v, [&]() { return pos; });
|
forceAttrs(v, [&]() { return pos; });
|
||||||
|
@ -73,6 +149,7 @@ inline void EvalState::forceAttrs(Value & v, const Pos & pos)
|
||||||
|
|
||||||
|
|
||||||
template <typename Callable>
|
template <typename Callable>
|
||||||
|
[[gnu::always_inline]]
|
||||||
inline void EvalState::forceAttrs(Value & v, Callable getPos)
|
inline void EvalState::forceAttrs(Value & v, Callable getPos)
|
||||||
{
|
{
|
||||||
forceValue(v, getPos);
|
forceValue(v, getPos);
|
||||||
|
@ -81,6 +158,7 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[[gnu::always_inline]]
|
||||||
inline void EvalState::forceList(Value & v, const Pos & pos)
|
inline void EvalState::forceList(Value & v, const Pos & pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
|
@ -88,18 +166,5 @@ inline void EvalState::forceList(Value & v, const Pos & pos)
|
||||||
throwTypeError(pos, "value is %1% while a list was expected", v, *this);
|
throwTypeError(pos, "value is %1% while a list was expected", v, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Note: Various places expect the allocated memory to be zeroed. */
|
|
||||||
inline void * allocBytes(size_t n)
|
|
||||||
{
|
|
||||||
void * p;
|
|
||||||
#if HAVE_BOEHMGC
|
|
||||||
p = GC_MALLOC(n);
|
|
||||||
#else
|
|
||||||
p = calloc(n, 1);
|
|
||||||
#endif
|
|
||||||
if (!p) throw std::bad_alloc();
|
|
||||||
return p;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,9 +63,15 @@ static char * dupString(const char * s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static char * dupStringWithLen(const char * s, size_t size)
|
// When there's no need to write to the string, we can optimize away empty
|
||||||
|
// string allocations.
|
||||||
|
// This function handles makeImmutableStringWithLen(null, 0) by returning the
|
||||||
|
// empty string.
|
||||||
|
static const char * makeImmutableStringWithLen(const char * s, size_t size)
|
||||||
{
|
{
|
||||||
char * t;
|
char * t;
|
||||||
|
if (size == 0)
|
||||||
|
return "";
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
t = GC_STRNDUP(s, size);
|
t = GC_STRNDUP(s, size);
|
||||||
#else
|
#else
|
||||||
|
@ -75,6 +81,10 @@ static char * dupStringWithLen(const char * s, size_t size)
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline const char * makeImmutableString(std::string_view s) {
|
||||||
|
return makeImmutableStringWithLen(s.data(), s.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
RootValue allocRootValue(Value * v)
|
RootValue allocRootValue(Value * v)
|
||||||
{
|
{
|
||||||
|
@ -86,25 +96,20 @@ RootValue allocRootValue(Value * v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void printValue(std::ostream & str, std::set<const Value *> & active, const Value & v)
|
void Value::print(std::ostream & str, std::set<const void *> * seen) const
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
if (!active.insert(&v).second) {
|
switch (internalType) {
|
||||||
str << "<CYCLE>";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (v.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";
|
||||||
|
@ -114,30 +119,38 @@ void printValue(std::ostream & str, std::set<const Value *> & active, const Valu
|
||||||
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: {
|
||||||
str << "{ ";
|
if (seen && !attrs->empty() && !seen->insert(attrs).second)
|
||||||
for (auto & i : v.attrs->lexicographicOrder()) {
|
str << "«repeated»";
|
||||||
str << i->name << " = ";
|
else {
|
||||||
printValue(str, active, *i->value);
|
str << "{ ";
|
||||||
str << "; ";
|
for (auto & i : attrs->lexicographicOrder()) {
|
||||||
|
str << i->name << " = ";
|
||||||
|
i->value->print(str, seen);
|
||||||
|
str << "; ";
|
||||||
|
}
|
||||||
|
str << "}";
|
||||||
}
|
}
|
||||||
str << "}";
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case tList1:
|
case tList1:
|
||||||
case tList2:
|
case tList2:
|
||||||
case tListN:
|
case tListN:
|
||||||
str << "[ ";
|
if (seen && listSize() && !seen->insert(listElems()).second)
|
||||||
for (auto v2 : v.listItems()) {
|
str << "«repeated»";
|
||||||
printValue(str, active, *v2);
|
else {
|
||||||
str << " ";
|
str << "[ ";
|
||||||
|
for (auto v2 : listItems()) {
|
||||||
|
v2->print(str, seen);
|
||||||
|
str << " ";
|
||||||
|
}
|
||||||
|
str << "]";
|
||||||
}
|
}
|
||||||
str << "]";
|
|
||||||
break;
|
break;
|
||||||
case tThunk:
|
case tThunk:
|
||||||
case tApp:
|
case tApp:
|
||||||
|
@ -153,28 +166,32 @@ void printValue(std::ostream & str, std::set<const Value *> & active, const Valu
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
active.erase(&v);
|
|
||||||
|
void Value::print(std::ostream & str, bool showRepeated) const
|
||||||
|
{
|
||||||
|
std::set<const void *> seen;
|
||||||
|
print(str, showRepeated ? nullptr : &seen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const Value & v)
|
std::ostream & operator << (std::ostream & str, const Value & v)
|
||||||
{
|
{
|
||||||
std::set<const Value *> active;
|
v.print(str, false);
|
||||||
printValue(str, active, v);
|
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const Value *getPrimOp(const Value &v) {
|
const Value * getPrimOp(const Value &v) {
|
||||||
const Value * primOp = &v;
|
const Value * primOp = &v;
|
||||||
while (primOp->isPrimOpApp()) {
|
while (primOp->isPrimOpApp()) {
|
||||||
primOp = primOp->primOpApp.left;
|
primOp = primOp->primOpApp.left;
|
||||||
|
@ -183,7 +200,7 @@ const Value *getPrimOp(const Value &v) {
|
||||||
return primOp;
|
return primOp;
|
||||||
}
|
}
|
||||||
|
|
||||||
string showType(ValueType type)
|
std::string_view showType(ValueType type)
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case nInt: return "an integer";
|
case nInt: return "an integer";
|
||||||
|
@ -202,20 +219,20 @@ string showType(ValueType type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string showType(const Value & v)
|
std::string showType(const Value & v)
|
||||||
{
|
{
|
||||||
switch (v.internalType) {
|
switch (v.internalType) {
|
||||||
case tString: return v.string.context ? "a string with context" : "a string";
|
case tString: return v.string.context ? "a string with context" : "a string";
|
||||||
case tPrimOp:
|
case tPrimOp:
|
||||||
return fmt("the built-in function '%s'", string(v.primOp->name));
|
return fmt("the built-in function '%s'", std::string(v.primOp->name));
|
||||||
case tPrimOpApp:
|
case tPrimOpApp:
|
||||||
return fmt("the partially applied built-in function '%s'", string(getPrimOp(v)->primOp->name));
|
return fmt("the partially applied built-in function '%s'", std::string(getPrimOp(v)->primOp->name));
|
||||||
case tExternal: return v.external->showType();
|
case tExternal: return v.external->showType();
|
||||||
case tThunk: return "a thunk";
|
case tThunk: return "a thunk";
|
||||||
case tApp: return "a function application";
|
case tApp: return "a function application";
|
||||||
case tBlackhole: return "a black hole";
|
case tBlackhole: return "a black hole";
|
||||||
default:
|
default:
|
||||||
return showType(v.type());
|
return std::string(showType(v.type()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +373,7 @@ void initGC()
|
||||||
|
|
||||||
/* Very hacky way to parse $NIX_PATH, which is colon-separated, but
|
/* Very hacky way to parse $NIX_PATH, which is colon-separated, but
|
||||||
can contain URLs (e.g. "nixpkgs=https://bla...:foo=https://"). */
|
can contain URLs (e.g. "nixpkgs=https://bla...:foo=https://"). */
|
||||||
static Strings parseNixPath(const string & s)
|
static Strings parseNixPath(const std::string & s)
|
||||||
{
|
{
|
||||||
Strings res;
|
Strings res;
|
||||||
|
|
||||||
|
@ -419,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"))
|
||||||
|
@ -440,8 +458,10 @@ EvalState::EvalState(
|
||||||
, regexCache(makeRegexCache())
|
, regexCache(makeRegexCache())
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||||
|
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||||
#else
|
#else
|
||||||
, valueAllocCache(std::make_shared<void *>(nullptr))
|
, valueAllocCache(std::make_shared<void *>(nullptr))
|
||||||
|
, env1AllocCache(std::make_shared<void *>(nullptr))
|
||||||
#endif
|
#endif
|
||||||
, baseEnv(allocEnv(128))
|
, baseEnv(allocEnv(128))
|
||||||
, staticBaseEnv(new StaticEnv(false, 0))
|
, staticBaseEnv(new StaticEnv(false, 0))
|
||||||
|
@ -490,23 +510,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)
|
||||||
|
@ -519,6 +522,14 @@ void EvalState::allowPath(const StorePath & storePath)
|
||||||
allowedPaths->insert(store->toRealPath(storePath));
|
allowedPaths->insert(store->toRealPath(storePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EvalState::allowAndSetStorePathString(const StorePath &storePath, Value & v)
|
||||||
|
{
|
||||||
|
allowPath(storePath);
|
||||||
|
|
||||||
|
auto path = store->printStorePath(storePath);
|
||||||
|
v.mkString(path, PathSet({path}));
|
||||||
|
}
|
||||||
|
|
||||||
Path EvalState::checkSourcePath(const Path & path_)
|
Path EvalState::checkSourcePath(const Path & path_)
|
||||||
{
|
{
|
||||||
if (!allowedPaths) return path_;
|
if (!allowedPaths) return path_;
|
||||||
|
@ -608,7 +619,7 @@ Path EvalState::toRealPath(const Path & path, const PathSet & context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Value * EvalState::addConstant(const string & name, Value & v)
|
Value * EvalState::addConstant(const std::string & name, Value & v)
|
||||||
{
|
{
|
||||||
Value * v2 = allocValue();
|
Value * v2 = allocValue();
|
||||||
*v2 = v;
|
*v2 = v;
|
||||||
|
@ -617,19 +628,19 @@ Value * EvalState::addConstant(const string & name, Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::addConstant(const string & name, Value * v)
|
void EvalState::addConstant(const std::string & name, Value * v)
|
||||||
{
|
{
|
||||||
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
|
staticBaseEnv->vars.emplace_back(symbols.create(name), baseEnvDispl);
|
||||||
baseEnv.values[baseEnvDispl++] = v;
|
baseEnv.values[baseEnvDispl++] = v;
|
||||||
string name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
|
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
|
||||||
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
|
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(name2), v));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Value * EvalState::addPrimOp(const string & name,
|
Value * EvalState::addPrimOp(const std::string & name,
|
||||||
size_t arity, PrimOpFun primOp)
|
size_t arity, PrimOpFun primOp)
|
||||||
{
|
{
|
||||||
auto name2 = string(name, 0, 2) == "__" ? string(name, 2) : name;
|
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
|
||||||
Symbol sym = symbols.create(name2);
|
Symbol sym = symbols.create(name2);
|
||||||
|
|
||||||
/* Hack to make constants lazy: turn them into a application of
|
/* Hack to make constants lazy: turn them into a application of
|
||||||
|
@ -677,7 +688,7 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Value & EvalState::getBuiltin(const string & name)
|
Value & EvalState::getBuiltin(const std::string & name)
|
||||||
{
|
{
|
||||||
return *baseEnv.values[0]->attrs->find(symbols.create(name))->value;
|
return *baseEnv.values[0]->attrs->find(symbols.create(name))->value;
|
||||||
}
|
}
|
||||||
|
@ -746,7 +757,7 @@ void printEnvBindings(const StaticEnv &se, const Env &env, int lvl)
|
||||||
// for the top level, don't print the double underscore ones; they are in builtins.
|
// for the top level, don't print the double underscore ones; they are in builtins.
|
||||||
for (auto i = se.vars.begin(); i != se.vars.end(); ++i)
|
for (auto i = se.vars.begin(); i != se.vars.end(); ++i)
|
||||||
{
|
{
|
||||||
if (((string)i->first).substr(0,2) != "__")
|
if (((std::string)i->first).substr(0,2) != "__")
|
||||||
std::cout << i->first << " ";
|
std::cout << i->first << " ";
|
||||||
}
|
}
|
||||||
std::cout << ANSI_NORMAL;
|
std::cout << ANSI_NORMAL;
|
||||||
|
@ -810,7 +821,7 @@ valmap * mapStaticEnvBindings(const StaticEnv &se, const Env &env)
|
||||||
evaluator. So here are some helper functions for throwing
|
evaluator. So here are some helper functions for throwing
|
||||||
exceptions. */
|
exceptions. */
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, EvalState &evalState))
|
LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2, EvalState &evalState))
|
||||||
{
|
{
|
||||||
auto error = EvalError(s, s2);
|
auto error = EvalError(s, s2);
|
||||||
|
|
||||||
|
@ -833,11 +844,12 @@ void EvalState::debug_throw(Error e) {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, Env & env, Expr &expr))
|
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const Suggestions & suggestions, const char * s, const std::string & s2, Env & env, Expr &expr))
|
||||||
{
|
{
|
||||||
auto error = EvalError({
|
auto error = EvalError({
|
||||||
.msg = hintfmt(s),
|
.msg = hintfmt(s, s2),
|
||||||
.errPos = pos
|
.errPos = pos,
|
||||||
|
.suggestions = suggestions,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (debuggerHook)
|
if (debuggerHook)
|
||||||
|
@ -846,7 +858,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, Env &
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, EvalState &evalState))
|
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, EvalState &evalState))
|
||||||
{
|
{
|
||||||
auto error = EvalError({
|
auto error = EvalError({
|
||||||
.msg = hintfmt(s, s2),
|
.msg = hintfmt(s, s2),
|
||||||
|
@ -861,7 +873,20 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, Env & env, Expr &expr))
|
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, Env & env, Expr &expr))
|
||||||
|
{
|
||||||
|
auto error = EvalError({
|
||||||
|
.msg = hintfmt(s),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
|
||||||
|
if (debuggerHook)
|
||||||
|
debuggerHook(&error, env, expr);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, Env & env, Expr &expr))
|
||||||
{
|
{
|
||||||
auto error = EvalError({
|
auto error = EvalError({
|
||||||
.msg = hintfmt(s, s2),
|
.msg = hintfmt(s, s2),
|
||||||
|
@ -874,7 +899,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const string & s2, const string & s3, EvalState &evalState))
|
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3, EvalState &evalState))
|
||||||
{
|
{
|
||||||
auto error = EvalError({
|
auto error = EvalError({
|
||||||
.msg = hintfmt(s, s2, s3),
|
.msg = hintfmt(s, s2, s3),
|
||||||
|
@ -889,7 +914,7 @@ LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const string & s2, const string & s3, EvalState &evalState))
|
LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2, const std::string & s3, EvalState &evalState))
|
||||||
{
|
{
|
||||||
auto error = EvalError({
|
auto error = EvalError({
|
||||||
.msg = hintfmt(s, s2, s3),
|
.msg = hintfmt(s, s2, s3),
|
||||||
|
@ -947,11 +972,13 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v));
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2, Env & env, Expr &expr))
|
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2, Env & env, Expr &expr))
|
||||||
{
|
{
|
||||||
auto error = TypeError({
|
auto error = TypeError({
|
||||||
.msg = hintfmt(s, fun.showNamePos(), s2),
|
.msg = hintfmt(s, fun.showNamePos(), s2),
|
||||||
.errPos = pos
|
.errPos = pos,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (debuggerHook)
|
if (debuggerHook)
|
||||||
|
@ -960,7 +987,21 @@ LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const string & s1, Env & env, Expr &expr))
|
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const Suggestions & suggestions, const char * s, const ExprLambda & fun, const Symbol & s2, Env & env, Expr &expr))
|
||||||
|
{
|
||||||
|
auto error = TypeError({
|
||||||
|
.msg = hintfmt(s, fun.showNamePos(), s2),
|
||||||
|
.errPos = pos,
|
||||||
|
.suggestions = suggestions,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (debuggerHook)
|
||||||
|
debuggerHook(&error, env, expr);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const std::string & s1, Env & env, Expr &expr))
|
||||||
{
|
{
|
||||||
auto error = AssertionError({
|
auto error = AssertionError({
|
||||||
.msg = hintfmt(s, s1),
|
.msg = hintfmt(s, s1),
|
||||||
|
@ -973,7 +1014,7 @@ LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s,
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const string & s1, Env & env, const Expr &expr))
|
LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1, Env & env, const Expr &expr))
|
||||||
{
|
{
|
||||||
auto error = UndefinedVarError({
|
auto error = UndefinedVarError({
|
||||||
.msg = hintfmt(s, s1),
|
.msg = hintfmt(s, s1),
|
||||||
|
@ -986,7 +1027,7 @@ LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char *
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const string & s1, Env & env, Expr &expr))
|
LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1, Env & env, Expr &expr))
|
||||||
{
|
{
|
||||||
auto error = MissingArgumentError({
|
auto error = MissingArgumentError({
|
||||||
.msg = hintfmt(s, s1),
|
.msg = hintfmt(s, s1),
|
||||||
|
@ -999,18 +1040,18 @@ LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInline(void addErrorTrace(Error & e, const char * s, const string & s2))
|
LocalNoInline(void addErrorTrace(Error & e, const char * s, const std::string & s2))
|
||||||
{
|
{
|
||||||
e.addTrace(std::nullopt, s, s2);
|
e.addTrace(std::nullopt, s, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, const string & s2))
|
LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, const std::string & s2))
|
||||||
{
|
{
|
||||||
e.addTrace(pos, s, s2);
|
e.addTrace(pos, s, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInline(std::unique_ptr<DebugTraceStacker>
|
LocalNoInline(std::unique_ptr<DebugTraceStacker>
|
||||||
makeDebugTraceStacker(EvalState &state, Expr &expr, Env &env, std::optional<ErrPos> pos, const char * s, const string & s2))
|
makeDebugTraceStacker(EvalState &state, Expr &expr, Env &env, std::optional<ErrPos> pos, const char * s, const std::string & s2))
|
||||||
{
|
{
|
||||||
return std::unique_ptr<DebugTraceStacker>(
|
return std::unique_ptr<DebugTraceStacker>(
|
||||||
new DebugTraceStacker(
|
new DebugTraceStacker(
|
||||||
|
@ -1034,7 +1075,7 @@ DebugTraceStacker::DebugTraceStacker(EvalState &evalState, DebugTrace t)
|
||||||
|
|
||||||
void Value::mkString(std::string_view s)
|
void Value::mkString(std::string_view s)
|
||||||
{
|
{
|
||||||
mkString(dupStringWithLen(s.data(), s.size()));
|
mkString(makeImmutableString(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1065,7 +1106,7 @@ void Value::mkStringMove(const char * s, const PathSet & context)
|
||||||
|
|
||||||
void Value::mkPath(std::string_view s)
|
void Value::mkPath(std::string_view s)
|
||||||
{
|
{
|
||||||
mkPath(dupStringWithLen(s.data(), s.size()));
|
mkPath(makeImmutableString(s));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1095,53 +1136,6 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Value * EvalState::allocValue()
|
|
||||||
{
|
|
||||||
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
|
|
||||||
GC_malloc_many returns a linked list of objects of the given size, where the first word
|
|
||||||
of each object is also the pointer to the next object in the list. This also means that we
|
|
||||||
have to explicitly clear the first word of every object we take. */
|
|
||||||
if (!*valueAllocCache) {
|
|
||||||
*valueAllocCache = GC_malloc_many(sizeof(Value));
|
|
||||||
if (!*valueAllocCache) throw std::bad_alloc();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* GC_NEXT is a convenience macro for accessing the first word of an object.
|
|
||||||
Take the first list item, advance the list to the next item, and clear the next pointer. */
|
|
||||||
void * p = *valueAllocCache;
|
|
||||||
GC_PTR_STORE_AND_DIRTY(&*valueAllocCache, GC_NEXT(p));
|
|
||||||
GC_NEXT(p) = nullptr;
|
|
||||||
|
|
||||||
nrValues++;
|
|
||||||
auto v = (Value *) p;
|
|
||||||
return v;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Env & EvalState::allocEnv(size_t size)
|
|
||||||
{
|
|
||||||
|
|
||||||
nrEnvs++;
|
|
||||||
nrValuesInEnvs += size;
|
|
||||||
Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
|
|
||||||
env->type = Env::Plain;
|
|
||||||
|
|
||||||
/* We assume that env->values has been cleared by the allocator; maybeThunk() and lookupVar fromWith expect this. */
|
|
||||||
|
|
||||||
return *env;
|
|
||||||
}
|
|
||||||
|
|
||||||
Env & fakeEnv(size_t size)
|
|
||||||
{
|
|
||||||
// making a fake Env so we'll have one to pass to exception ftns.
|
|
||||||
// a placeholder until we can pass real envs everywhere they're needed.
|
|
||||||
Env * env = (Env *) allocBytes(sizeof(Env) + size * sizeof(Value *));
|
|
||||||
env->type = Env::Plain;
|
|
||||||
|
|
||||||
return *env;
|
|
||||||
}
|
|
||||||
|
|
||||||
void EvalState::mkList(Value & v, size_t size)
|
void EvalState::mkList(Value & v, size_t size)
|
||||||
{
|
{
|
||||||
v.mkList(size);
|
v.mkList(size);
|
||||||
|
@ -1480,7 +1474,7 @@ void ExprVar::eval(EvalState & state, Env & env, Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static string showAttrPath(EvalState & state, Env & env, const AttrPath & attrPath)
|
static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & attrPath)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
@ -1531,8 +1525,15 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
state.forceAttrs(*vAttrs, pos);
|
state.forceAttrs(*vAttrs, pos);
|
||||||
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
|
if ((j = vAttrs->attrs->find(name)) == vAttrs->attrs->end()) {
|
||||||
throwEvalError(pos, "attribute '%1%' missing", name, env, *this);
|
std::set<std::string> allAttrNames;
|
||||||
|
for (auto & attr : *vAttrs->attrs)
|
||||||
|
allAttrNames.insert(attr.name);
|
||||||
|
throwEvalError(
|
||||||
|
pos,
|
||||||
|
Suggestions::bestMatches(allAttrNames, name),
|
||||||
|
"attribute '%1%' missing", name, env, *this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
vAttrs = j->value;
|
vAttrs = j->value;
|
||||||
pos2 = j->pos;
|
pos2 = j->pos;
|
||||||
|
@ -1647,9 +1648,16 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
/* Nope, so show the first unexpected argument to the
|
/* Nope, so show the first unexpected argument to the
|
||||||
user. */
|
user. */
|
||||||
for (auto & i : *args[0]->attrs)
|
for (auto & i : *args[0]->attrs)
|
||||||
if (!lambda.formals->has(i.name))
|
if (!lambda.formals->has(i.name)) {
|
||||||
throwTypeError(pos, "%1% called with unexpected argument '%2%'",
|
std::set<std::string> formalNames;
|
||||||
|
for (auto & formal : lambda.formals->formals)
|
||||||
|
formalNames.insert(formal.name);
|
||||||
|
throwTypeError(
|
||||||
|
pos,
|
||||||
|
Suggestions::bestMatches(formalNames, i.name),
|
||||||
|
"%1% called with unexpected argument '%2%'",
|
||||||
lambda, i.name, *fun.lambda.env, lambda);
|
lambda, i.name, *fun.lambda.env, lambda);
|
||||||
|
}
|
||||||
abort(); // can't happen
|
abort(); // can't happen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1664,7 +1672,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
makeDebugTraceStacker(*this, *lambda.body, env2, lambda.pos,
|
makeDebugTraceStacker(*this, *lambda.body, env2, lambda.pos,
|
||||||
"while evaluating %s",
|
"while evaluating %s",
|
||||||
(lambda.name.set()
|
(lambda.name.set()
|
||||||
? "'" + (string) lambda.name + "'"
|
? "'" + (std::string) lambda.name + "'"
|
||||||
: "anonymous lambda"))
|
: "anonymous lambda"))
|
||||||
: nullptr;
|
: nullptr;
|
||||||
|
|
||||||
|
@ -1673,7 +1681,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
if (loggerSettings.showTrace.get()) {
|
if (loggerSettings.showTrace.get()) {
|
||||||
addErrorTrace(e, lambda.pos, "while evaluating %s",
|
addErrorTrace(e, lambda.pos, "while evaluating %s",
|
||||||
(lambda.name.set()
|
(lambda.name.set()
|
||||||
? "'" + (string) lambda.name + "'"
|
? "'" + (const std::string &) lambda.name + "'"
|
||||||
: "anonymous lambda"));
|
: "anonymous lambda"));
|
||||||
addErrorTrace(e, pos, "from call site%s", "");
|
addErrorTrace(e, pos, "from call site%s", "");
|
||||||
}
|
}
|
||||||
|
@ -2175,13 +2183,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<string, 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)),
|
||||||
|
"",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2193,13 +2210,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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2238,7 +2255,7 @@ bool EvalState::isDerivation(Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::optional<string> EvalState::tryAttrsToString(const Pos & pos, Value & v,
|
std::optional<std::string> EvalState::tryAttrsToString(const Pos & pos, Value & v,
|
||||||
PathSet & context, bool coerceMore, bool copyToStore)
|
PathSet & context, bool coerceMore, bool copyToStore)
|
||||||
{
|
{
|
||||||
auto i = v.attrs->find(sToString);
|
auto i = v.attrs->find(sToString);
|
||||||
|
@ -2293,7 +2310,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
|
||||||
if (v.type() == nNull) return "";
|
if (v.type() == nNull) return "";
|
||||||
|
|
||||||
if (v.isList()) {
|
if (v.isList()) {
|
||||||
string result;
|
std::string result;
|
||||||
for (auto [n, v2] : enumerate(v.listItems())) {
|
for (auto [n, v2] : enumerate(v.listItems())) {
|
||||||
result += *coerceToString(pos, *v2, context, coerceMore, copyToStore);
|
result += *coerceToString(pos, *v2, context, coerceMore, copyToStore);
|
||||||
if (n < v.listSize() - 1
|
if (n < v.listSize() - 1
|
||||||
|
@ -2309,7 +2326,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
||||||
{
|
{
|
||||||
if (nix::isDerivation(path))
|
if (nix::isDerivation(path))
|
||||||
throwEvalError("file names are not allowed to end in '%1%'", drvExtension, *this);
|
throwEvalError("file names are not allowed to end in '%1%'", drvExtension, *this);
|
||||||
|
@ -2335,13 +2352,25 @@ string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
||||||
|
|
||||||
Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
|
Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
|
||||||
{
|
{
|
||||||
string path = coerceToString(pos, v, context, false, false).toOwned();
|
auto path = coerceToString(pos, v, context, false, false).toOwned();
|
||||||
if (path == "" || path[0] != '/')
|
if (path == "" || path[0] != '/')
|
||||||
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path, *this);
|
throwEvalError(pos, "string '%1%' doesn't represent an absolute path", path, *this);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context)
|
||||||
|
{
|
||||||
|
auto path = coerceToString(pos, v, context, false, false).toOwned();
|
||||||
|
if (auto storePath = store->maybeParseStorePath(path))
|
||||||
|
return *storePath;
|
||||||
|
throw EvalError({
|
||||||
|
.msg = hintfmt("path '%1%' is not in the Nix store", path),
|
||||||
|
.errPos = pos
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool EvalState::eqValues(Value & v1, Value & v2)
|
bool EvalState::eqValues(Value & v1, Value & v2)
|
||||||
{
|
{
|
||||||
forceValue(v1, noPos);
|
forceValue(v1, noPos);
|
||||||
|
@ -2508,11 +2537,11 @@ void EvalState::printStats()
|
||||||
for (auto & i : functionCalls) {
|
for (auto & i : functionCalls) {
|
||||||
auto obj = list.object();
|
auto obj = list.object();
|
||||||
if (i.first->name.set())
|
if (i.first->name.set())
|
||||||
obj.attr("name", (const string &) i.first->name);
|
obj.attr("name", (const std::string &) i.first->name);
|
||||||
else
|
else
|
||||||
obj.attr("name", nullptr);
|
obj.attr("name", nullptr);
|
||||||
if (i.first->pos) {
|
if (i.first->pos) {
|
||||||
obj.attr("file", (const string &) i.first->pos.file);
|
obj.attr("file", (const std::string &) i.first->pos.file);
|
||||||
obj.attr("line", i.first->pos.line);
|
obj.attr("line", i.first->pos.line);
|
||||||
obj.attr("column", i.first->pos.column);
|
obj.attr("column", i.first->pos.column);
|
||||||
}
|
}
|
||||||
|
@ -2524,7 +2553,7 @@ void EvalState::printStats()
|
||||||
for (auto & i : attrSelects) {
|
for (auto & i : attrSelects) {
|
||||||
auto obj = list.object();
|
auto obj = list.object();
|
||||||
if (i.first) {
|
if (i.first) {
|
||||||
obj.attr("file", (const string &) i.first.file);
|
obj.attr("file", (const std::string &) i.first.file);
|
||||||
obj.attr("line", i.first.line);
|
obj.attr("line", i.first.line);
|
||||||
obj.attr("column", i.first.column);
|
obj.attr("column", i.first.column);
|
||||||
}
|
}
|
||||||
|
@ -2541,7 +2570,7 @@ void EvalState::printStats()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
|
std::string ExternalValueBase::coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const
|
||||||
{
|
{
|
||||||
throw TypeError({
|
throw TypeError({
|
||||||
.msg = hintfmt("cannot coerce %1% to a string", showType()),
|
.msg = hintfmt("cannot coerce %1% to a string", showType()),
|
||||||
|
|
|
@ -89,7 +89,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,
|
||||||
|
@ -150,9 +150,14 @@ private:
|
||||||
/* Cache used by prim_match(). */
|
/* Cache used by prim_match(). */
|
||||||
std::shared_ptr<RegexCache> regexCache;
|
std::shared_ptr<RegexCache> regexCache;
|
||||||
|
|
||||||
|
#if HAVE_BOEHMGC
|
||||||
/* Allocation cache for GC'd Value objects. */
|
/* Allocation cache for GC'd Value objects. */
|
||||||
std::shared_ptr<void *> valueAllocCache;
|
std::shared_ptr<void *> valueAllocCache;
|
||||||
|
|
||||||
|
/* Allocation cache for size-1 Env objects. */
|
||||||
|
std::shared_ptr<void *> env1AllocCache;
|
||||||
|
#endif
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
EvalState(
|
EvalState(
|
||||||
|
@ -161,13 +166,7 @@ public:
|
||||||
std::shared_ptr<Store> buildStore = nullptr);
|
std::shared_ptr<Store> buildStore = nullptr);
|
||||||
~EvalState();
|
~EvalState();
|
||||||
|
|
||||||
void requireExperimentalFeatureOnEvaluation(
|
void addToSearchPath(const std::string & s);
|
||||||
const ExperimentalFeature &,
|
|
||||||
const std::string_view fName,
|
|
||||||
const Pos & pos
|
|
||||||
);
|
|
||||||
|
|
||||||
void addToSearchPath(const string & s);
|
|
||||||
|
|
||||||
SearchPath getSearchPath() { return searchPath; }
|
SearchPath getSearchPath() { return searchPath; }
|
||||||
|
|
||||||
|
@ -178,6 +177,9 @@ public:
|
||||||
the real store path if `store` is a chroot store. */
|
the real store path if `store` is a chroot store. */
|
||||||
void allowPath(const StorePath & storePath);
|
void allowPath(const StorePath & storePath);
|
||||||
|
|
||||||
|
/* Allow access to a store path and return it as a string. */
|
||||||
|
void allowAndSetStorePathString(const StorePath & storePath, Value & v);
|
||||||
|
|
||||||
/* Check whether access to a path is allowed and throw an error if
|
/* Check whether access to a path is allowed and throw an error if
|
||||||
not. Otherwise return the canonicalised path. */
|
not. Otherwise return the canonicalised path. */
|
||||||
Path checkSourcePath(const Path & path);
|
Path checkSourcePath(const Path & path);
|
||||||
|
@ -268,7 +270,7 @@ public:
|
||||||
set with attribute `type = "derivation"'). */
|
set with attribute `type = "derivation"'). */
|
||||||
bool isDerivation(Value & v);
|
bool isDerivation(Value & v);
|
||||||
|
|
||||||
std::optional<string> tryAttrsToString(const Pos & pos, Value & v,
|
std::optional<std::string> tryAttrsToString(const Pos & pos, Value & v,
|
||||||
PathSet & context, bool coerceMore = false, bool copyToStore = true);
|
PathSet & context, bool coerceMore = false, bool copyToStore = true);
|
||||||
|
|
||||||
/* String coercion. Converts strings, paths and derivations to a
|
/* String coercion. Converts strings, paths and derivations to a
|
||||||
|
@ -279,13 +281,16 @@ public:
|
||||||
bool coerceMore = false, bool copyToStore = true,
|
bool coerceMore = false, bool copyToStore = true,
|
||||||
bool canonicalizePath = true);
|
bool canonicalizePath = true);
|
||||||
|
|
||||||
string copyPathToStore(PathSet & context, const Path & path);
|
std::string copyPathToStore(PathSet & context, const Path & path);
|
||||||
|
|
||||||
/* Path coercion. Converts strings, paths and derivations to a
|
/* Path coercion. Converts strings, paths and derivations to a
|
||||||
path. The result is guaranteed to be a canonicalised, absolute
|
path. The result is guaranteed to be a canonicalised, absolute
|
||||||
path. Nothing is copied to the store. */
|
path. Nothing is copied to the store. */
|
||||||
Path coerceToPath(const Pos & pos, Value & v, PathSet & context);
|
Path coerceToPath(const Pos & pos, Value & v, PathSet & context);
|
||||||
|
|
||||||
|
/* Like coerceToPath, but the result must be a store path. */
|
||||||
|
StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/* The base environment, containing the builtin functions and
|
/* The base environment, containing the builtin functions and
|
||||||
|
@ -301,18 +306,18 @@ private:
|
||||||
|
|
||||||
void createBaseEnv();
|
void createBaseEnv();
|
||||||
|
|
||||||
Value * addConstant(const string & name, Value & v);
|
Value * addConstant(const std::string & name, Value & v);
|
||||||
|
|
||||||
void addConstant(const string & name, Value * v);
|
void addConstant(const std::string & name, Value * v);
|
||||||
|
|
||||||
Value * addPrimOp(const string & name,
|
Value * addPrimOp(const std::string & name,
|
||||||
size_t arity, PrimOpFun primOp);
|
size_t arity, PrimOpFun primOp);
|
||||||
|
|
||||||
Value * addPrimOp(PrimOp && primOp);
|
Value * addPrimOp(PrimOp && primOp);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Value & getBuiltin(const string & name);
|
Value & getBuiltin(const std::string & name);
|
||||||
|
|
||||||
struct Doc
|
struct Doc
|
||||||
{
|
{
|
||||||
|
@ -358,8 +363,8 @@ public:
|
||||||
void autoCallFunction(Bindings & args, Value & fun, Value & res);
|
void autoCallFunction(Bindings & args, Value & fun, Value & res);
|
||||||
|
|
||||||
/* Allocation primitives. */
|
/* Allocation primitives. */
|
||||||
Value * allocValue();
|
inline Value * allocValue();
|
||||||
Env & allocEnv(size_t size);
|
inline Env & allocEnv(size_t size);
|
||||||
|
|
||||||
Value * allocAttr(Value & vAttrs, const Symbol & name);
|
Value * allocAttr(Value & vAttrs, const Symbol & name);
|
||||||
Value * allocAttr(Value & vAttrs, std::string_view name);
|
Value * allocAttr(Value & vAttrs, std::string_view name);
|
||||||
|
@ -442,12 +447,12 @@ class DebugTraceStacker {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Return a string representing the type of the value `v'. */
|
/* Return a string representing the type of the value `v'. */
|
||||||
string showType(ValueType type);
|
std::string_view showType(ValueType type);
|
||||||
string showType(const Value & v);
|
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<string, 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);
|
||||||
|
@ -531,3 +536,5 @@ extern EvalSettings evalSettings;
|
||||||
static const std::string corepkgsPrefix{"/__corepkgs__/"};
|
static const std::string corepkgsPrefix{"/__corepkgs__/"};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "eval-inline.hh"
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "flake.hh"
|
#include "flake.hh"
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
|
#include "fetch-settings.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
@ -53,7 +54,7 @@ void ConfigFile::apply()
|
||||||
auto trustedList = readTrustedList();
|
auto trustedList = readTrustedList();
|
||||||
|
|
||||||
bool trusted = false;
|
bool trusted = false;
|
||||||
if (nix::settings.acceptFlakeConfig){
|
if (nix::fetchSettings.acceptFlakeConfig){
|
||||||
trusted = true;
|
trusted = true;
|
||||||
} else if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) {
|
} else if (auto saved = get(get(trustedList, name).value_or(std::map<std::string, bool>()), valueS)) {
|
||||||
trusted = *saved;
|
trusted = *saved;
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
|
#include "fetch-settings.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -254,7 +255,7 @@ static Flake getFlake(
|
||||||
for (auto & setting : *nixConfig->value->attrs) {
|
for (auto & setting : *nixConfig->value->attrs) {
|
||||||
forceTrivialValue(state, *setting.value, *setting.pos);
|
forceTrivialValue(state, *setting.value, *setting.pos);
|
||||||
if (setting.value->type() == nString)
|
if (setting.value->type() == nString)
|
||||||
flake.config.settings.insert({setting.name, string(state.forceStringNoCtx(*setting.value, *setting.pos))});
|
flake.config.settings.insert({setting.name, std::string(state.forceStringNoCtx(*setting.value, *setting.pos))});
|
||||||
else if (setting.value->type() == nPath) {
|
else if (setting.value->type() == nPath) {
|
||||||
PathSet emptyContext = {};
|
PathSet emptyContext = {};
|
||||||
flake.config.settings.emplace(
|
flake.config.settings.emplace(
|
||||||
|
@ -315,7 +316,7 @@ LockedFlake lockFlake(
|
||||||
|
|
||||||
FlakeCache flakeCache;
|
FlakeCache flakeCache;
|
||||||
|
|
||||||
auto useRegistries = lockFlags.useRegistries.value_or(settings.useRegistries);
|
auto useRegistries = lockFlags.useRegistries.value_or(fetchSettings.useRegistries);
|
||||||
|
|
||||||
auto flake = getFlake(state, topRef, useRegistries, flakeCache);
|
auto flake = getFlake(state, topRef, useRegistries, flakeCache);
|
||||||
|
|
||||||
|
@ -501,7 +502,7 @@ LockedFlake lockFlake(
|
||||||
this input. */
|
this input. */
|
||||||
debug("creating new input '%s'", inputPathS);
|
debug("creating new input '%s'", inputPathS);
|
||||||
|
|
||||||
if (!lockFlags.allowMutable && !input.ref->input.isImmutable())
|
if (!lockFlags.allowMutable && !input.ref->input.isLocked())
|
||||||
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
|
throw Error("cannot update flake input '%s' in pure mode", inputPathS);
|
||||||
|
|
||||||
if (input.isFlake) {
|
if (input.isFlake) {
|
||||||
|
@ -591,7 +592,7 @@ LockedFlake lockFlake(
|
||||||
if (lockFlags.writeLockFile) {
|
if (lockFlags.writeLockFile) {
|
||||||
if (auto sourcePath = topRef.input.getSourcePath()) {
|
if (auto sourcePath = topRef.input.getSourcePath()) {
|
||||||
if (!newLockFile.isImmutable()) {
|
if (!newLockFile.isImmutable()) {
|
||||||
if (settings.warnDirty)
|
if (fetchSettings.warnDirty)
|
||||||
warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
|
warn("will not write lock file of flake '%s' because it has a mutable input", topRef);
|
||||||
} else {
|
} else {
|
||||||
if (!lockFlags.updateLockFile)
|
if (!lockFlags.updateLockFile)
|
||||||
|
@ -618,7 +619,7 @@ LockedFlake lockFlake(
|
||||||
if (lockFlags.commitLockFile) {
|
if (lockFlags.commitLockFile) {
|
||||||
std::string cm;
|
std::string cm;
|
||||||
|
|
||||||
cm = settings.commitLockFileSummary.get();
|
cm = fetchSettings.commitLockFileSummary.get();
|
||||||
|
|
||||||
if (cm == "") {
|
if (cm == "") {
|
||||||
cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add");
|
cm = fmt("%s: %s", relPath, lockFileExists ? "Update" : "Add");
|
||||||
|
@ -650,7 +651,7 @@ LockedFlake lockFlake(
|
||||||
now. Corner case: we could have reverted from a
|
now. Corner case: we could have reverted from a
|
||||||
dirty to a clean tree! */
|
dirty to a clean tree! */
|
||||||
if (flake.lockedRef.input == prevLockedRef.input
|
if (flake.lockedRef.input == prevLockedRef.input
|
||||||
&& !flake.lockedRef.input.isImmutable())
|
&& !flake.lockedRef.input.isLocked())
|
||||||
throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
|
throw Error("'%s' did not change after I updated its 'flake.lock' file; is 'flake.lock' under version control?", flake.originalRef);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
|
@ -705,24 +706,45 @@ 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));
|
||||||
|
|
||||||
string flakeRefS(state.forceStringNoCtx(*args[0], pos));
|
|
||||||
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
||||||
if (evalSettings.pureEval && !flakeRef.input.isImmutable())
|
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||||
throw Error("cannot call 'getFlake' on mutable flake reference '%s', at %s (use --impure to override)", flakeRefS, pos);
|
throw Error("cannot call 'getFlake' on unlocked flake reference '%s', at %s (use --impure to override)", flakeRefS, pos);
|
||||||
|
|
||||||
callFlake(state,
|
callFlake(state,
|
||||||
lockFlake(state, flakeRef,
|
lockFlake(state, flakeRef,
|
||||||
LockFlags {
|
LockFlags {
|
||||||
.updateLockFile = false,
|
.updateLockFile = false,
|
||||||
.useRegistries = !evalSettings.pureEval && settings.useRegistries,
|
.useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries,
|
||||||
.allowMutable = !evalSettings.pureEval,
|
.allowMutable = !evalSettings.pureEval,
|
||||||
}),
|
}),
|
||||||
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,
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -98,7 +98,7 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
if (std::regex_match(url, match, flakeRegex)) {
|
if (std::regex_match(url, match, flakeRegex)) {
|
||||||
auto parsedURL = ParsedURL{
|
auto parsedURL = ParsedURL{
|
||||||
.url = url,
|
.url = url,
|
||||||
.base = "flake:" + std::string(match[1]),
|
.base = "flake:" + match.str(1),
|
||||||
.scheme = "flake",
|
.scheme = "flake",
|
||||||
.authority = "",
|
.authority = "",
|
||||||
.path = match[1],
|
.path = match[1],
|
||||||
|
@ -106,12 +106,12 @@ std::pair<FlakeRef, std::string> parseFlakeRefWithFragment(
|
||||||
|
|
||||||
return std::make_pair(
|
return std::make_pair(
|
||||||
FlakeRef(Input::fromURL(parsedURL), ""),
|
FlakeRef(Input::fromURL(parsedURL), ""),
|
||||||
percentDecode(std::string(match[6])));
|
percentDecode(match.str(6)));
|
||||||
}
|
}
|
||||||
|
|
||||||
else if (std::regex_match(url, match, pathUrlRegex)) {
|
else if (std::regex_match(url, match, pathUrlRegex)) {
|
||||||
std::string path = match[1];
|
std::string path = match[1];
|
||||||
std::string fragment = percentDecode(std::string(match[3]));
|
std::string fragment = percentDecode(match.str(3));
|
||||||
|
|
||||||
if (baseDir) {
|
if (baseDir) {
|
||||||
/* Check if 'url' is a path (either absolute or relative
|
/* Check if 'url' is a path (either absolute or relative
|
||||||
|
|
|
@ -35,7 +35,7 @@ LockedNode::LockedNode(const nlohmann::json & json)
|
||||||
, originalRef(getFlakeRef(json, "original", nullptr))
|
, originalRef(getFlakeRef(json, "original", nullptr))
|
||||||
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
|
, isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true)
|
||||||
{
|
{
|
||||||
if (!lockedRef.input.isImmutable())
|
if (!lockedRef.input.isLocked())
|
||||||
throw Error("lockfile contains mutable lock '%s'",
|
throw Error("lockfile contains mutable lock '%s'",
|
||||||
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
|
fetchers::attrsToJSON(lockedRef.input.toAttrs()));
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,7 @@ bool LockFile::isImmutable() const
|
||||||
for (auto & i : nodes) {
|
for (auto & i : nodes) {
|
||||||
if (i == root) continue;
|
if (i == root) continue;
|
||||||
auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(i);
|
auto lockedNode = std::dynamic_pointer_cast<const LockedNode>(i);
|
||||||
if (lockedNode && !lockedNode->lockedRef.input.isImmutable()) return false;
|
if (lockedNode && !lockedNode->lockedRef.input.isLocked()) return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "get-drvs.hh"
|
#include "get-drvs.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "eval-inline.hh"
|
#include "eval-inline.hh"
|
||||||
|
#include "derivations.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "path-with-outputs.hh"
|
#include "path-with-outputs.hh"
|
||||||
|
|
||||||
|
@ -11,8 +12,8 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
DrvInfo::DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs)
|
DrvInfo::DrvInfo(EvalState & state, std::string attrPath, Bindings * attrs)
|
||||||
: state(&state), attrs(attrs), attrPath(attrPath)
|
: state(&state), attrs(attrs), attrPath(std::move(attrPath))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,7 +23,7 @@ DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPat
|
||||||
{
|
{
|
||||||
auto [drvPath, selectedOutputs] = parsePathWithOutputs(*store, drvPathWithOutputs);
|
auto [drvPath, selectedOutputs] = parsePathWithOutputs(*store, drvPathWithOutputs);
|
||||||
|
|
||||||
this->drvPath = store->printStorePath(drvPath);
|
this->drvPath = drvPath;
|
||||||
|
|
||||||
auto drv = store->derivationFromPath(drvPath);
|
auto drv = store->derivationFromPath(drvPath);
|
||||||
|
|
||||||
|
@ -41,13 +42,11 @@ DrvInfo::DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPat
|
||||||
throw Error("derivation '%s' does not have output '%s'", store->printStorePath(drvPath), outputName);
|
throw Error("derivation '%s' does not have output '%s'", store->printStorePath(drvPath), outputName);
|
||||||
auto & [outputName, output] = *i;
|
auto & [outputName, output] = *i;
|
||||||
|
|
||||||
auto optStorePath = output.path(*store, drv.name, outputName);
|
outPath = {output.path(*store, drv.name, outputName)};
|
||||||
if (optStorePath)
|
|
||||||
outPath = store->printStorePath(*optStorePath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string DrvInfo::queryName() const
|
std::string DrvInfo::queryName() const
|
||||||
{
|
{
|
||||||
if (name == "" && attrs) {
|
if (name == "" && attrs) {
|
||||||
auto i = attrs->find(state->sName);
|
auto i = attrs->find(state->sName);
|
||||||
|
@ -58,7 +57,7 @@ string DrvInfo::queryName() const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string DrvInfo::querySystem() const
|
std::string DrvInfo::querySystem() const
|
||||||
{
|
{
|
||||||
if (system == "" && attrs) {
|
if (system == "" && attrs) {
|
||||||
auto i = attrs->find(state->sSystem);
|
auto i = attrs->find(state->sSystem);
|
||||||
|
@ -68,24 +67,35 @@ string DrvInfo::querySystem() const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string DrvInfo::queryDrvPath() const
|
std::optional<StorePath> DrvInfo::queryDrvPath() const
|
||||||
{
|
{
|
||||||
if (drvPath == "" && attrs) {
|
if (!drvPath && attrs) {
|
||||||
Bindings::iterator i = attrs->find(state->sDrvPath);
|
Bindings::iterator i = attrs->find(state->sDrvPath);
|
||||||
PathSet context;
|
PathSet context;
|
||||||
drvPath = i != attrs->end() ? state->coerceToPath(*i->pos, *i->value, context) : "";
|
if (i == attrs->end())
|
||||||
|
drvPath = {std::nullopt};
|
||||||
|
else
|
||||||
|
drvPath = {state->coerceToStorePath(*i->pos, *i->value, context)};
|
||||||
}
|
}
|
||||||
return drvPath;
|
return drvPath.value_or(std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string DrvInfo::queryOutPath() const
|
StorePath DrvInfo::requireDrvPath() const
|
||||||
|
{
|
||||||
|
if (auto drvPath = queryDrvPath())
|
||||||
|
return *drvPath;
|
||||||
|
throw Error("derivation does not contain a 'drvPath' attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
StorePath DrvInfo::queryOutPath() const
|
||||||
{
|
{
|
||||||
if (!outPath && attrs) {
|
if (!outPath && attrs) {
|
||||||
Bindings::iterator i = attrs->find(state->sOutPath);
|
Bindings::iterator i = attrs->find(state->sOutPath);
|
||||||
PathSet context;
|
PathSet context;
|
||||||
if (i != attrs->end())
|
if (i != attrs->end())
|
||||||
outPath = state->coerceToPath(*i->pos, *i->value, context);
|
outPath = state->coerceToStorePath(*i->pos, *i->value, context);
|
||||||
}
|
}
|
||||||
if (!outPath)
|
if (!outPath)
|
||||||
throw UnimplementedError("CA derivations are not yet supported");
|
throw UnimplementedError("CA derivations are not yet supported");
|
||||||
|
@ -93,7 +103,7 @@ string DrvInfo::queryOutPath() const
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
|
DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall)
|
||||||
{
|
{
|
||||||
if (outputs.empty()) {
|
if (outputs.empty()) {
|
||||||
/* Get the ‘outputs’ list. */
|
/* Get the ‘outputs’ list. */
|
||||||
|
@ -103,20 +113,24 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
|
||||||
|
|
||||||
/* For each output... */
|
/* For each output... */
|
||||||
for (auto elem : i->value->listItems()) {
|
for (auto elem : i->value->listItems()) {
|
||||||
/* Evaluate the corresponding set. */
|
std::string output(state->forceStringNoCtx(*elem, *i->pos));
|
||||||
string name(state->forceStringNoCtx(*elem, *i->pos));
|
|
||||||
Bindings::iterator out = attrs->find(state->symbols.create(name));
|
|
||||||
if (out == attrs->end()) continue; // FIXME: throw error?
|
|
||||||
state->forceAttrs(*out->value, *i->pos);
|
|
||||||
|
|
||||||
/* And evaluate its ‘outPath’ attribute. */
|
if (withPaths) {
|
||||||
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
|
/* Evaluate the corresponding set. */
|
||||||
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
|
Bindings::iterator out = attrs->find(state->symbols.create(output));
|
||||||
PathSet context;
|
if (out == attrs->end()) continue; // FIXME: throw error?
|
||||||
outputs[name] = state->coerceToPath(*outPath->pos, *outPath->value, context);
|
state->forceAttrs(*out->value, *i->pos);
|
||||||
|
|
||||||
|
/* And evaluate its ‘outPath’ attribute. */
|
||||||
|
Bindings::iterator outPath = out->value->attrs->find(state->sOutPath);
|
||||||
|
if (outPath == out->value->attrs->end()) continue; // FIXME: throw error?
|
||||||
|
PathSet context;
|
||||||
|
outputs.emplace(output, state->coerceToStorePath(*outPath->pos, *outPath->value, context));
|
||||||
|
} else
|
||||||
|
outputs.emplace(output, std::nullopt);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
outputs["out"] = queryOutPath();
|
outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
|
||||||
}
|
}
|
||||||
if (!onlyOutputsToInstall || !attrs)
|
if (!onlyOutputsToInstall || !attrs)
|
||||||
return outputs;
|
return outputs;
|
||||||
|
@ -138,7 +152,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool onlyOutputsToInstall)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string DrvInfo::queryOutputName() const
|
std::string DrvInfo::queryOutputName() const
|
||||||
{
|
{
|
||||||
if (outputName == "" && attrs) {
|
if (outputName == "" && attrs) {
|
||||||
Bindings::iterator i = attrs->find(state->sOutputName);
|
Bindings::iterator i = attrs->find(state->sOutputName);
|
||||||
|
@ -190,7 +204,7 @@ bool DrvInfo::checkMeta(Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Value * DrvInfo::queryMeta(const string & name)
|
Value * DrvInfo::queryMeta(const std::string & name)
|
||||||
{
|
{
|
||||||
if (!getMeta()) return 0;
|
if (!getMeta()) return 0;
|
||||||
Bindings::iterator a = meta->find(state->symbols.create(name));
|
Bindings::iterator a = meta->find(state->symbols.create(name));
|
||||||
|
@ -199,7 +213,7 @@ Value * DrvInfo::queryMeta(const string & name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string DrvInfo::queryMetaString(const string & name)
|
std::string DrvInfo::queryMetaString(const std::string & name)
|
||||||
{
|
{
|
||||||
Value * v = queryMeta(name);
|
Value * v = queryMeta(name);
|
||||||
if (!v || v->type() != nString) return "";
|
if (!v || v->type() != nString) return "";
|
||||||
|
@ -207,7 +221,7 @@ string DrvInfo::queryMetaString(const string & name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NixInt DrvInfo::queryMetaInt(const string & name, NixInt def)
|
NixInt DrvInfo::queryMetaInt(const std::string & name, NixInt def)
|
||||||
{
|
{
|
||||||
Value * v = queryMeta(name);
|
Value * v = queryMeta(name);
|
||||||
if (!v) return def;
|
if (!v) return def;
|
||||||
|
@ -221,7 +235,7 @@ NixInt DrvInfo::queryMetaInt(const string & name, NixInt def)
|
||||||
return def;
|
return def;
|
||||||
}
|
}
|
||||||
|
|
||||||
NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def)
|
NixFloat DrvInfo::queryMetaFloat(const std::string & name, NixFloat def)
|
||||||
{
|
{
|
||||||
Value * v = queryMeta(name);
|
Value * v = queryMeta(name);
|
||||||
if (!v) return def;
|
if (!v) return def;
|
||||||
|
@ -236,7 +250,7 @@ NixFloat DrvInfo::queryMetaFloat(const string & name, NixFloat def)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool DrvInfo::queryMetaBool(const string & name, bool def)
|
bool DrvInfo::queryMetaBool(const std::string & name, bool def)
|
||||||
{
|
{
|
||||||
Value * v = queryMeta(name);
|
Value * v = queryMeta(name);
|
||||||
if (!v) return def;
|
if (!v) return def;
|
||||||
|
@ -251,7 +265,7 @@ bool DrvInfo::queryMetaBool(const string & name, bool def)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DrvInfo::setMeta(const string & name, Value * v)
|
void DrvInfo::setMeta(const std::string & name, Value * v)
|
||||||
{
|
{
|
||||||
getMeta();
|
getMeta();
|
||||||
auto attrs = state->buildBindings(1 + (meta ? meta->size() : 0));
|
auto attrs = state->buildBindings(1 + (meta ? meta->size() : 0));
|
||||||
|
@ -266,7 +280,7 @@ void DrvInfo::setMeta(const string & name, Value * v)
|
||||||
|
|
||||||
|
|
||||||
/* Cache for already considered attrsets. */
|
/* Cache for already considered attrsets. */
|
||||||
typedef set<Bindings *> Done;
|
typedef std::set<Bindings *> Done;
|
||||||
|
|
||||||
|
|
||||||
/* Evaluate value `v'. If it evaluates to a set of type `derivation',
|
/* Evaluate value `v'. If it evaluates to a set of type `derivation',
|
||||||
|
@ -274,7 +288,7 @@ typedef set<Bindings *> Done;
|
||||||
The result boolean indicates whether it makes sense
|
The result boolean indicates whether it makes sense
|
||||||
for the caller to recursively search for derivations in `v'. */
|
for the caller to recursively search for derivations in `v'. */
|
||||||
static bool getDerivation(EvalState & state, Value & v,
|
static bool getDerivation(EvalState & state, Value & v,
|
||||||
const string & attrPath, DrvInfos & drvs, Done & done,
|
const std::string & attrPath, DrvInfos & drvs, Done & done,
|
||||||
bool ignoreAssertionFailures)
|
bool ignoreAssertionFailures)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
|
@ -311,7 +325,7 @@ std::optional<DrvInfo> getDerivation(EvalState & state, Value & v,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static string addToPath(const string & s1, const string & s2)
|
static std::string addToPath(const std::string & s1, const std::string & s2)
|
||||||
{
|
{
|
||||||
return s1.empty() ? s2 : s1 + "." + s2;
|
return s1.empty() ? s2 : s1 + "." + s2;
|
||||||
}
|
}
|
||||||
|
@ -321,7 +335,7 @@ static std::regex attrRegex("[A-Za-z_][A-Za-z0-9-_+]*");
|
||||||
|
|
||||||
|
|
||||||
static void getDerivations(EvalState & state, Value & vIn,
|
static void getDerivations(EvalState & state, Value & vIn,
|
||||||
const string & pathPrefix, Bindings & autoArgs,
|
const std::string & pathPrefix, Bindings & autoArgs,
|
||||||
DrvInfos & drvs, Done & done,
|
DrvInfos & drvs, Done & done,
|
||||||
bool ignoreAssertionFailures)
|
bool ignoreAssertionFailures)
|
||||||
{
|
{
|
||||||
|
@ -346,7 +360,7 @@ static void getDerivations(EvalState & state, Value & vIn,
|
||||||
debug("evaluating attribute '%1%'", i->name);
|
debug("evaluating attribute '%1%'", i->name);
|
||||||
if (!std::regex_match(std::string(i->name), attrRegex))
|
if (!std::regex_match(std::string(i->name), attrRegex))
|
||||||
continue;
|
continue;
|
||||||
string pathPrefix2 = addToPath(pathPrefix, i->name);
|
std::string pathPrefix2 = addToPath(pathPrefix, i->name);
|
||||||
if (combineChannels)
|
if (combineChannels)
|
||||||
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
||||||
else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
|
else if (getDerivation(state, *i->value, pathPrefix2, drvs, done, ignoreAssertionFailures)) {
|
||||||
|
@ -364,7 +378,7 @@ static void getDerivations(EvalState & state, Value & vIn,
|
||||||
|
|
||||||
else if (v.type() == nList) {
|
else if (v.type() == nList) {
|
||||||
for (auto [n, elem] : enumerate(v.listItems())) {
|
for (auto [n, elem] : enumerate(v.listItems())) {
|
||||||
string pathPrefix2 = addToPath(pathPrefix, fmt("%d", n));
|
std::string pathPrefix2 = addToPath(pathPrefix, fmt("%d", n));
|
||||||
if (getDerivation(state, *elem, pathPrefix2, drvs, done, ignoreAssertionFailures))
|
if (getDerivation(state, *elem, pathPrefix2, drvs, done, ignoreAssertionFailures))
|
||||||
getDerivations(state, *elem, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
getDerivations(state, *elem, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
||||||
}
|
}
|
||||||
|
@ -374,7 +388,7 @@ static void getDerivations(EvalState & state, Value & vIn,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void getDerivations(EvalState & state, Value & v, const string & pathPrefix,
|
void getDerivations(EvalState & state, Value & v, const std::string & pathPrefix,
|
||||||
Bindings & autoArgs, DrvInfos & drvs, bool ignoreAssertionFailures)
|
Bindings & autoArgs, DrvInfos & drvs, bool ignoreAssertionFailures)
|
||||||
{
|
{
|
||||||
Done done;
|
Done done;
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
#include "path.hh"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@ -12,16 +13,16 @@ namespace nix {
|
||||||
struct DrvInfo
|
struct DrvInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef std::map<string, Path> Outputs;
|
typedef std::map<std::string, std::optional<StorePath>> Outputs;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EvalState * state;
|
EvalState * state;
|
||||||
|
|
||||||
mutable string name;
|
mutable std::string name;
|
||||||
mutable string system;
|
mutable std::string system;
|
||||||
mutable string drvPath;
|
mutable std::optional<std::optional<StorePath>> drvPath;
|
||||||
mutable std::optional<string> outPath;
|
mutable std::optional<StorePath> outPath;
|
||||||
mutable string outputName;
|
mutable std::string outputName;
|
||||||
Outputs outputs;
|
Outputs outputs;
|
||||||
|
|
||||||
bool failed = false; // set if we get an AssertionError
|
bool failed = false; // set if we get an AssertionError
|
||||||
|
@ -33,36 +34,38 @@ private:
|
||||||
bool checkMeta(Value & v);
|
bool checkMeta(Value & v);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
string attrPath; /* path towards the derivation */
|
std::string attrPath; /* path towards the derivation */
|
||||||
|
|
||||||
DrvInfo(EvalState & state) : state(&state) { };
|
DrvInfo(EvalState & state) : state(&state) { };
|
||||||
DrvInfo(EvalState & state, const string & attrPath, Bindings * attrs);
|
DrvInfo(EvalState & state, std::string attrPath, Bindings * attrs);
|
||||||
DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs);
|
DrvInfo(EvalState & state, ref<Store> store, const std::string & drvPathWithOutputs);
|
||||||
|
|
||||||
string queryName() const;
|
std::string queryName() const;
|
||||||
string querySystem() const;
|
std::string querySystem() const;
|
||||||
string queryDrvPath() const;
|
std::optional<StorePath> queryDrvPath() const;
|
||||||
string queryOutPath() const;
|
StorePath requireDrvPath() const;
|
||||||
string queryOutputName() const;
|
StorePath queryOutPath() const;
|
||||||
/** Return the list of outputs. The "outputs to install" are determined by `meta.outputsToInstall`. */
|
std::string queryOutputName() const;
|
||||||
Outputs queryOutputs(bool onlyOutputsToInstall = false);
|
/** Return the unordered map of output names to (optional) output paths.
|
||||||
|
* The "outputs to install" are determined by `meta.outputsToInstall`. */
|
||||||
|
Outputs queryOutputs(bool withPaths = true, bool onlyOutputsToInstall = false);
|
||||||
|
|
||||||
StringSet queryMetaNames();
|
StringSet queryMetaNames();
|
||||||
Value * queryMeta(const string & name);
|
Value * queryMeta(const std::string & name);
|
||||||
string queryMetaString(const string & name);
|
std::string queryMetaString(const std::string & name);
|
||||||
NixInt queryMetaInt(const string & name, NixInt def);
|
NixInt queryMetaInt(const std::string & name, NixInt def);
|
||||||
NixFloat queryMetaFloat(const string & name, NixFloat def);
|
NixFloat queryMetaFloat(const std::string & name, NixFloat def);
|
||||||
bool queryMetaBool(const string & name, bool def);
|
bool queryMetaBool(const std::string & name, bool def);
|
||||||
void setMeta(const string & name, Value * v);
|
void setMeta(const std::string & name, Value * v);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
MetaInfo queryMetaInfo(EvalState & state) const;
|
MetaInfo queryMetaInfo(EvalState & state) const;
|
||||||
MetaValue queryMetaInfo(EvalState & state, const string & name) const;
|
MetaValue queryMetaInfo(EvalState & state, const string & name) const;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void setName(const string & s) { name = s; }
|
void setName(const std::string & s) { name = s; }
|
||||||
void setDrvPath(const string & s) { drvPath = s; }
|
void setDrvPath(StorePath path) { drvPath = {{std::move(path)}}; }
|
||||||
void setOutPath(const string & s) { outPath = s; }
|
void setOutPath(StorePath path) { outPath = {{std::move(path)}}; }
|
||||||
|
|
||||||
void setFailed() { failed = true; };
|
void setFailed() { failed = true; };
|
||||||
bool hasFailed() { return failed; };
|
bool hasFailed() { return failed; };
|
||||||
|
@ -70,9 +73,9 @@ public:
|
||||||
|
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
typedef list<DrvInfo, traceable_allocator<DrvInfo> > DrvInfos;
|
typedef std::list<DrvInfo, traceable_allocator<DrvInfo> > DrvInfos;
|
||||||
#else
|
#else
|
||||||
typedef list<DrvInfo> DrvInfos;
|
typedef std::list<DrvInfo> DrvInfos;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,7 +84,7 @@ typedef list<DrvInfo> DrvInfos;
|
||||||
std::optional<DrvInfo> getDerivation(EvalState & state,
|
std::optional<DrvInfo> getDerivation(EvalState & state,
|
||||||
Value & v, bool ignoreAssertionFailures);
|
Value & v, bool ignoreAssertionFailures);
|
||||||
|
|
||||||
void getDerivations(EvalState & state, Value & v, const string & pathPrefix,
|
void getDerivations(EvalState & state, Value & v, const std::string & pathPrefix,
|
||||||
Bindings & autoArgs, DrvInfos & drvs,
|
Bindings & autoArgs, DrvInfos & drvs,
|
||||||
bool ignoreAssertionFailures);
|
bool ignoreAssertionFailures);
|
||||||
|
|
||||||
|
|
|
@ -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; }
|
||||||
|
|
|
@ -18,10 +18,10 @@ std::ostream & operator << (std::ostream & str, const Expr & e)
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void showString(std::ostream & str, const string & s)
|
static void showString(std::ostream & str, std::string_view s)
|
||||||
{
|
{
|
||||||
str << '"';
|
str << '"';
|
||||||
for (auto c : (string) s)
|
for (auto c : s)
|
||||||
if (c == '"' || c == '\\' || c == '$') str << "\\" << c;
|
if (c == '"' || c == '\\' || c == '$') str << "\\" << c;
|
||||||
else if (c == '\n') str << "\\n";
|
else if (c == '\n') str << "\\n";
|
||||||
else if (c == '\r') str << "\\r";
|
else if (c == '\r') str << "\\r";
|
||||||
|
@ -30,7 +30,7 @@ static void showString(std::ostream & str, const string & s)
|
||||||
str << '"';
|
str << '"';
|
||||||
}
|
}
|
||||||
|
|
||||||
static void showId(std::ostream & str, const string & s)
|
static void showId(std::ostream & str, std::string_view s)
|
||||||
{
|
{
|
||||||
if (s.empty())
|
if (s.empty())
|
||||||
str << "\"\"";
|
str << "\"\"";
|
||||||
|
@ -105,11 +105,18 @@ void ExprAttrs::show(std::ostream & str) const
|
||||||
{
|
{
|
||||||
if (recursive) str << "rec ";
|
if (recursive) str << "rec ";
|
||||||
str << "{ ";
|
str << "{ ";
|
||||||
for (auto & i : attrs)
|
typedef const decltype(attrs)::value_type * Attr;
|
||||||
if (i.second.inherited)
|
std::vector<Attr> sorted;
|
||||||
str << "inherit " << i.first << " " << "; ";
|
for (auto & i : attrs) sorted.push_back(&i);
|
||||||
|
std::sort(sorted.begin(), sorted.end(), [](Attr a, Attr b) {
|
||||||
|
return (const std::string &) a->first < (const std::string &) b->first;
|
||||||
|
});
|
||||||
|
for (auto & i : sorted) {
|
||||||
|
if (i->second.inherited)
|
||||||
|
str << "inherit " << i->first << " " << "; ";
|
||||||
else
|
else
|
||||||
str << i.first << " = " << *i.second.e << "; ";
|
str << i->first << " = " << *i->second.e << "; ";
|
||||||
|
}
|
||||||
for (auto & i : dynamicAttrs)
|
for (auto & i : dynamicAttrs)
|
||||||
str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; ";
|
str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; ";
|
||||||
str << "}";
|
str << "}";
|
||||||
|
@ -213,7 +220,7 @@ std::ostream & operator << (std::ostream & str, const Pos & pos)
|
||||||
auto f = format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%");
|
auto f = format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%");
|
||||||
switch (pos.origin) {
|
switch (pos.origin) {
|
||||||
case foFile:
|
case foFile:
|
||||||
f % (string) pos.file;
|
f % (const std::string &) pos.file;
|
||||||
break;
|
break;
|
||||||
case foStdin:
|
case foStdin:
|
||||||
case foString:
|
case foString:
|
||||||
|
@ -229,7 +236,7 @@ std::ostream & operator << (std::ostream & str, const Pos & pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string showAttrPath(const AttrPath & attrPath)
|
std::string showAttrPath(const AttrPath & attrPath)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
@ -518,9 +525,9 @@ void ExprLambda::setName(Symbol & name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string ExprLambda::showNamePos() const
|
std::string ExprLambda::showNamePos() const
|
||||||
{
|
{
|
||||||
return (format("%1% at %2%") % (name.set() ? "'" + (string) name + "'" : "anonymous function") % pos).str();
|
return fmt("%1% at %2%", name.set() ? "'" + (std::string) name + "'" : "anonymous function", pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,21 +24,23 @@ extern std::function<void(const Error * error, const Env & env, const Expr & exp
|
||||||
|
|
||||||
struct Pos
|
struct Pos
|
||||||
{
|
{
|
||||||
FileOrigin origin;
|
|
||||||
Symbol file;
|
Symbol file;
|
||||||
unsigned int line, column;
|
uint32_t line;
|
||||||
Pos() : origin(foString), line(0), column(0) { };
|
FileOrigin origin:2;
|
||||||
Pos(FileOrigin origin, const Symbol & file, unsigned int line, unsigned int column)
|
uint32_t column:30;
|
||||||
: origin(origin), file(file), line(line), column(column) { };
|
Pos() : line(0), origin(foString), column(0) { };
|
||||||
|
Pos(FileOrigin origin, const Symbol & file, uint32_t line, uint32_t column)
|
||||||
|
: file(file), line(line), origin(origin), column(column) { };
|
||||||
operator bool() const
|
operator bool() const
|
||||||
{
|
{
|
||||||
return line != 0;
|
return line != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator < (const Pos & p2) const
|
bool operator < (const Pos & p2) const
|
||||||
{
|
{
|
||||||
if (!line) return p2.line;
|
if (!line) return p2.line;
|
||||||
if (!p2.line) return false;
|
if (!p2.line) return false;
|
||||||
int d = ((string) file).compare((string) p2.file);
|
int d = ((const std::string &) file).compare((const std::string &) p2.file);
|
||||||
if (d < 0) return true;
|
if (d < 0) return true;
|
||||||
if (d > 0) return false;
|
if (d > 0) return false;
|
||||||
if (line < p2.line) return true;
|
if (line < p2.line) return true;
|
||||||
|
@ -69,7 +71,7 @@ struct AttrName
|
||||||
|
|
||||||
typedef std::vector<AttrName> AttrPath;
|
typedef std::vector<AttrName> AttrPath;
|
||||||
|
|
||||||
string showAttrPath(const AttrPath & attrPath);
|
std::string showAttrPath(const AttrPath & attrPath);
|
||||||
|
|
||||||
|
|
||||||
/* Abstract syntax of Nix expressions. */
|
/* Abstract syntax of Nix expressions. */
|
||||||
|
@ -116,7 +118,7 @@ struct ExprFloat : Expr
|
||||||
|
|
||||||
struct ExprString : Expr
|
struct ExprString : Expr
|
||||||
{
|
{
|
||||||
string s;
|
std::string s;
|
||||||
Value v;
|
Value v;
|
||||||
ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); };
|
ExprString(std::string s) : s(std::move(s)) { v.mkString(this->s.data()); };
|
||||||
Value * maybeThunk(EvalState & state, Env & env);
|
Value * maybeThunk(EvalState & state, Env & env);
|
||||||
|
@ -126,9 +128,9 @@ struct ExprString : Expr
|
||||||
|
|
||||||
struct ExprPath : Expr
|
struct ExprPath : Expr
|
||||||
{
|
{
|
||||||
string s;
|
std::string s;
|
||||||
Value v;
|
Value v;
|
||||||
ExprPath(const string & s) : s(s) { v.mkPath(this->s.c_str()); };
|
ExprPath(std::string s) : s(std::move(s)) { v.mkPath(this->s.c_str()); };
|
||||||
Value * maybeThunk(EvalState & state, Env & env);
|
Value * maybeThunk(EvalState & state, Env & env);
|
||||||
const Pos* getPos() const { return 0; }
|
const Pos* getPos() const { return 0; }
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
|
@ -262,7 +264,7 @@ struct ExprLambda : Expr
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
void setName(Symbol & name);
|
void setName(Symbol & name);
|
||||||
string showNamePos() const;
|
std::string showNamePos() const;
|
||||||
inline bool hasFormals() const { return formals != nullptr; }
|
inline bool hasFormals() const { return formals != nullptr; }
|
||||||
const Pos* getPos() const { return &pos; }
|
const Pos* getPos() const { return &pos; }
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
|
@ -356,8 +358,8 @@ struct ExprConcatStrings : Expr
|
||||||
{
|
{
|
||||||
Pos pos;
|
Pos pos;
|
||||||
bool forceString;
|
bool forceString;
|
||||||
vector<std::pair<Pos, Expr *> > * es;
|
std::vector<std::pair<Pos, Expr *> > * es;
|
||||||
ExprConcatStrings(const Pos & pos, bool forceString, vector<std::pair<Pos, Expr *> > * es)
|
ExprConcatStrings(const Pos & pos, bool forceString, std::vector<std::pair<Pos, Expr *> > * es)
|
||||||
: pos(pos), forceString(forceString), es(es) { };
|
: pos(pos), forceString(forceString), es(es) { };
|
||||||
const Pos* getPos() const { return &pos; }
|
const Pos* getPos() const { return &pos; }
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
|
@ -390,15 +392,19 @@ struct StaticEnv
|
||||||
|
|
||||||
void sort()
|
void sort()
|
||||||
{
|
{
|
||||||
std::sort(vars.begin(), vars.end(),
|
std::stable_sort(vars.begin(), vars.end(),
|
||||||
[](const Vars::value_type & a, const Vars::value_type & b) { return a.first < b.first; });
|
[](const Vars::value_type & a, const Vars::value_type & b) { return a.first < b.first; });
|
||||||
}
|
}
|
||||||
|
|
||||||
void deduplicate()
|
void deduplicate()
|
||||||
{
|
{
|
||||||
const auto last = std::unique(vars.begin(), vars.end(),
|
auto it = vars.begin(), jt = it, end = vars.end();
|
||||||
[] (const Vars::value_type & a, const Vars::value_type & b) { return a.first == b.first; });
|
while (jt != end) {
|
||||||
vars.erase(last, vars.end());
|
*it = *jt++;
|
||||||
|
while (jt != end && it->first == jt->first) *it = *jt++;
|
||||||
|
it++;
|
||||||
|
}
|
||||||
|
vars.erase(it, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vars::const_iterator find(const Symbol & name) const
|
Vars::const_iterator find(const Symbol & name) const
|
||||||
|
|
|
@ -194,7 +194,7 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
|
||||||
|
|
||||||
|
|
||||||
static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
|
static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
|
||||||
vector<std::pair<Pos, std::variant<Expr *, StringToken> > > & es)
|
std::vector<std::pair<Pos, std::variant<Expr *, StringToken> > > & es)
|
||||||
{
|
{
|
||||||
if (es.empty()) return new ExprString("");
|
if (es.empty()) return new ExprString("");
|
||||||
|
|
||||||
|
@ -234,7 +234,7 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Strip spaces from each line. */
|
/* Strip spaces from each line. */
|
||||||
vector<std::pair<Pos, Expr *> > * es2 = new vector<std::pair<Pos, Expr *> >;
|
std::vector<std::pair<Pos, Expr *> > * es2 = new std::vector<std::pair<Pos, Expr *> >;
|
||||||
atStartOfLine = true;
|
atStartOfLine = true;
|
||||||
size_t curDropped = 0;
|
size_t curDropped = 0;
|
||||||
size_t n = es.size();
|
size_t n = es.size();
|
||||||
|
@ -245,7 +245,7 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
|
||||||
es2->emplace_back(i->first, e);
|
es2->emplace_back(i->first, e);
|
||||||
};
|
};
|
||||||
const auto trimString = [&] (const StringToken & t) {
|
const auto trimString = [&] (const StringToken & t) {
|
||||||
string s2;
|
std::string s2;
|
||||||
for (size_t j = 0; j < t.l; ++j) {
|
for (size_t j = 0; j < t.l; ++j) {
|
||||||
if (atStartOfLine) {
|
if (atStartOfLine) {
|
||||||
if (t.p[j] == ' ') {
|
if (t.p[j] == ' ') {
|
||||||
|
@ -269,9 +269,9 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
|
||||||
/* Remove the last line if it is empty and consists only of
|
/* Remove the last line if it is empty and consists only of
|
||||||
spaces. */
|
spaces. */
|
||||||
if (n == 1) {
|
if (n == 1) {
|
||||||
string::size_type p = s2.find_last_of('\n');
|
std::string::size_type p = s2.find_last_of('\n');
|
||||||
if (p != string::npos && s2.find_first_not_of(' ', p + 1) == string::npos)
|
if (p != std::string::npos && s2.find_first_not_of(' ', p + 1) == std::string::npos)
|
||||||
s2 = string(s2, 0, p + 1);
|
s2 = std::string(s2, 0, p + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
es2->emplace_back(i->first, new ExprString(s2));
|
es2->emplace_back(i->first, new ExprString(s2));
|
||||||
|
@ -416,7 +416,7 @@ expr_op
|
||||||
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); }
|
| expr_op UPDATE expr_op { $$ = new ExprOpUpdate(CUR_POS, $1, $3); }
|
||||||
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
|
| expr_op '?' attrpath { $$ = new ExprOpHasAttr($1, *$3); }
|
||||||
| expr_op '+' expr_op
|
| expr_op '+' expr_op
|
||||||
{ $$ = new ExprConcatStrings(CUR_POS, false, new vector<std::pair<Pos, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
|
{ $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<Pos, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
|
||||||
| expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
|
| expr_op '-' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__sub")), {$1, $3}); }
|
||||||
| expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
|
| expr_op '*' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__mul")), {$1, $3}); }
|
||||||
| expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); }
|
| expr_op '/' expr_op { $$ = new ExprCall(CUR_POS, new ExprVar(data->symbols.create("__div")), {$1, $3}); }
|
||||||
|
@ -467,7 +467,7 @@ expr_simple
|
||||||
$$ = new ExprConcatStrings(CUR_POS, false, $2);
|
$$ = new ExprConcatStrings(CUR_POS, false, $2);
|
||||||
}
|
}
|
||||||
| SPATH {
|
| SPATH {
|
||||||
string path($1.p + 1, $1.l - 2);
|
std::string path($1.p + 1, $1.l - 2);
|
||||||
$$ = new ExprCall(CUR_POS,
|
$$ = new ExprCall(CUR_POS,
|
||||||
new ExprVar(data->symbols.create("__findFile")),
|
new ExprVar(data->symbols.create("__findFile")),
|
||||||
{new ExprVar(data->symbols.create("__nixPath")),
|
{new ExprVar(data->symbols.create("__nixPath")),
|
||||||
|
@ -480,7 +480,7 @@ expr_simple
|
||||||
.msg = hintfmt("URL literals are disabled"),
|
.msg = hintfmt("URL literals are disabled"),
|
||||||
.errPos = CUR_POS
|
.errPos = CUR_POS
|
||||||
});
|
});
|
||||||
$$ = new ExprString(string($1));
|
$$ = new ExprString(std::string($1));
|
||||||
}
|
}
|
||||||
| '(' expr ')' { $$ = $2; }
|
| '(' expr ')' { $$ = $2; }
|
||||||
/* Let expressions `let {..., body = ...}' are just desugared
|
/* Let expressions `let {..., body = ...}' are just desugared
|
||||||
|
@ -495,19 +495,19 @@ expr_simple
|
||||||
;
|
;
|
||||||
|
|
||||||
string_parts
|
string_parts
|
||||||
: STR { $$ = new ExprString(string($1)); }
|
: STR { $$ = new ExprString(std::string($1)); }
|
||||||
| string_parts_interpolated { $$ = new ExprConcatStrings(CUR_POS, true, $1); }
|
| string_parts_interpolated { $$ = new ExprConcatStrings(CUR_POS, true, $1); }
|
||||||
| { $$ = new ExprString(""); }
|
| { $$ = new ExprString(""); }
|
||||||
;
|
;
|
||||||
|
|
||||||
string_parts_interpolated
|
string_parts_interpolated
|
||||||
: string_parts_interpolated STR
|
: string_parts_interpolated STR
|
||||||
{ $$ = $1; $1->emplace_back(makeCurPos(@2, data), new ExprString(string($2))); }
|
{ $$ = $1; $1->emplace_back(makeCurPos(@2, data), new ExprString(std::string($2))); }
|
||||||
| string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); }
|
| string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); }
|
||||||
| DOLLAR_CURLY expr '}' { $$ = new vector<std::pair<Pos, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); }
|
| DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<Pos, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); }
|
||||||
| STR DOLLAR_CURLY expr '}' {
|
| STR DOLLAR_CURLY expr '}' {
|
||||||
$$ = new vector<std::pair<Pos, Expr *> >;
|
$$ = new std::vector<std::pair<Pos, Expr *> >;
|
||||||
$$->emplace_back(makeCurPos(@1, data), new ExprString(string($1)));
|
$$->emplace_back(makeCurPos(@1, data), new ExprString(std::string($1)));
|
||||||
$$->emplace_back(makeCurPos(@2, data), $3);
|
$$->emplace_back(makeCurPos(@2, data), $3);
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
@ -521,7 +521,7 @@ path_start
|
||||||
$$ = new ExprPath(path);
|
$$ = new ExprPath(path);
|
||||||
}
|
}
|
||||||
| HPATH {
|
| HPATH {
|
||||||
Path path(getHome() + string($1.p + 1, $1.l - 1));
|
Path path(getHome() + std::string($1.p + 1, $1.l - 1));
|
||||||
$$ = new ExprPath(path);
|
$$ = new ExprPath(path);
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
@ -529,7 +529,7 @@ path_start
|
||||||
ind_string_parts
|
ind_string_parts
|
||||||
: ind_string_parts IND_STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $2); }
|
: ind_string_parts IND_STR { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $2); }
|
||||||
| ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); }
|
| ind_string_parts DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(makeCurPos(@2, data), $3); }
|
||||||
| { $$ = new vector<std::pair<Pos, std::variant<Expr *, StringToken> > >; }
|
| { $$ = new std::vector<std::pair<Pos, std::variant<Expr *, StringToken> > >; }
|
||||||
;
|
;
|
||||||
|
|
||||||
binds
|
binds
|
||||||
|
@ -583,9 +583,9 @@ attrpath
|
||||||
} else
|
} else
|
||||||
$$->push_back(AttrName($3));
|
$$->push_back(AttrName($3));
|
||||||
}
|
}
|
||||||
| attr { $$ = new vector<AttrName>; $$->push_back(AttrName(data->symbols.create($1))); }
|
| attr { $$ = new std::vector<AttrName>; $$->push_back(AttrName(data->symbols.create($1))); }
|
||||||
| string_attr
|
| string_attr
|
||||||
{ $$ = new vector<AttrName>;
|
{ $$ = new std::vector<AttrName>;
|
||||||
ExprString *str = dynamic_cast<ExprString *>($1);
|
ExprString *str = dynamic_cast<ExprString *>($1);
|
||||||
if (str) {
|
if (str) {
|
||||||
$$->push_back(AttrName(data->symbols.create(str->s)));
|
$$->push_back(AttrName(data->symbols.create(str->s)));
|
||||||
|
@ -739,16 +739,16 @@ Expr * EvalState::parseStdin()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::addToSearchPath(const string & s)
|
void EvalState::addToSearchPath(const std::string & s)
|
||||||
{
|
{
|
||||||
size_t pos = s.find('=');
|
size_t pos = s.find('=');
|
||||||
string prefix;
|
std::string prefix;
|
||||||
Path path;
|
Path path;
|
||||||
if (pos == string::npos) {
|
if (pos == std::string::npos) {
|
||||||
path = s;
|
path = s;
|
||||||
} else {
|
} else {
|
||||||
prefix = string(s, 0, pos);
|
prefix = std::string(s, 0, pos);
|
||||||
path = string(s, pos + 1);
|
path = std::string(s, pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchPath.emplace_back(prefix, path);
|
searchPath.emplace_back(prefix, path);
|
||||||
|
|
|
@ -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))
|
||||||
debug_throw(InvalidPathError(store->printStorePath(ctx)));
|
debug_throw(InvalidPathError(store->printStorePath(ctx)));
|
||||||
if (!outputName.empty() && ctx.isDerivation()) {
|
if (!outputName.empty() && ctx.isDerivation()) {
|
||||||
|
@ -141,7 +141,7 @@ static void mkOutputString(
|
||||||
BindingsBuilder & attrs,
|
BindingsBuilder & attrs,
|
||||||
const StorePath & drvPath,
|
const StorePath & drvPath,
|
||||||
const BasicDerivation & drv,
|
const BasicDerivation & drv,
|
||||||
const std::pair<string, DerivationOutput> & o)
|
const std::pair<std::string, DerivationOutput> & o)
|
||||||
{
|
{
|
||||||
auto optOutputPath = o.second.path(*state.store, drv.name, o.first);
|
auto optOutputPath = o.second.path(*state.store, drv.name, o.first);
|
||||||
attrs.alloc(o.first).mkString(
|
attrs.alloc(o.first).mkString(
|
||||||
|
@ -314,7 +314,7 @@ void prim_importNative(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
{
|
{
|
||||||
auto path = realisePath(state, pos, *args[0]);
|
auto path = realisePath(state, pos, *args[0]);
|
||||||
|
|
||||||
string sym(state.forceStringNoCtx(*args[1], pos));
|
std::string sym(state.forceStringNoCtx(*args[1], pos));
|
||||||
|
|
||||||
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
void *handle = dlopen(path.c_str(), RTLD_LAZY | RTLD_LOCAL);
|
||||||
if (!handle)
|
if (!handle)
|
||||||
|
@ -386,7 +386,7 @@ void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_typeOf(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
state.forceValue(*args[0], pos);
|
state.forceValue(*args[0], pos);
|
||||||
string t;
|
std::string t;
|
||||||
switch (args[0]->type()) {
|
switch (args[0]->type()) {
|
||||||
case nInt: t = "int"; break;
|
case nInt: t = "int"; break;
|
||||||
case nBool: t = "bool"; break;
|
case nBool: t = "bool"; break;
|
||||||
|
@ -575,9 +575,9 @@ struct CompareValues
|
||||||
|
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
typedef list<Value *, gc_allocator<Value *> > ValueList;
|
typedef std::list<Value *, gc_allocator<Value *> > ValueList;
|
||||||
#else
|
#else
|
||||||
typedef list<Value *> ValueList;
|
typedef std::list<Value *> ValueList;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
@ -655,7 +655,7 @@ static void prim_genericClosure(EvalState & state, const Pos & pos, Value * * ar
|
||||||
// `doneKeys' doesn't need to be a GC root, because its values are
|
// `doneKeys' doesn't need to be a GC root, because its values are
|
||||||
// reachable from res.
|
// reachable from res.
|
||||||
auto cmp = CompareValues(state);
|
auto cmp = CompareValues(state);
|
||||||
set<Value *, decltype(cmp)> doneKeys(cmp);
|
std::set<Value *, decltype(cmp)> doneKeys(cmp);
|
||||||
while (!workSet.empty()) {
|
while (!workSet.empty()) {
|
||||||
Value * e = *(workSet.begin());
|
Value * e = *(workSet.begin());
|
||||||
workSet.pop_front();
|
workSet.pop_front();
|
||||||
|
@ -695,7 +695,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,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -742,7 +767,7 @@ static RegisterPrimOp primop_abort({
|
||||||
.fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
|
.fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
string s = state.coerceToString(pos, *args[0], context).toOwned();
|
auto s = state.coerceToString(pos, *args[0], context).toOwned();
|
||||||
state.debug_throw(Abort("evaluation aborted with the following error message: '%1%'", s));
|
state.debug_throw(Abort("evaluation aborted with the following error message: '%1%'", s));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -760,7 +785,7 @@ static RegisterPrimOp primop_throw({
|
||||||
.fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
|
.fun = [](EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
string s = state.coerceToString(pos, *args[0], context).toOwned();
|
auto s = state.coerceToString(pos, *args[0], context).toOwned();
|
||||||
state.debug_throw(ThrownError(s));
|
state.debug_throw(ThrownError(s));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -861,7 +886,7 @@ static RegisterPrimOp primop_tryEval({
|
||||||
/* Return an environment variable. Use with care. */
|
/* Return an environment variable. Use with care. */
|
||||||
static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_getEnv(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
string name(state.forceStringNoCtx(*args[0], pos));
|
std::string name(state.forceStringNoCtx(*args[0], pos));
|
||||||
v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or(""));
|
v.mkString(evalSettings.restrictEval || evalSettings.pureEval ? "" : getEnv(name).value_or(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -970,7 +995,7 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
pos
|
pos
|
||||||
);
|
);
|
||||||
|
|
||||||
string drvName;
|
std::string drvName;
|
||||||
Pos & posDrvName(*attr->pos);
|
Pos & posDrvName(*attr->pos);
|
||||||
try {
|
try {
|
||||||
drvName = state.forceStringNoCtx(*attr->value, pos);
|
drvName = state.forceStringNoCtx(*attr->value, pos);
|
||||||
|
@ -999,16 +1024,17 @@ 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");
|
||||||
|
|
||||||
for (auto & i : args[0]->attrs->lexicographicOrder()) {
|
for (auto & i : args[0]->attrs->lexicographicOrder()) {
|
||||||
if (i->name == state.sIgnoreNulls) continue;
|
if (i->name == state.sIgnoreNulls) continue;
|
||||||
const string & key = i->name;
|
const std::string & key = i->name;
|
||||||
vomit("processing attribute '%1%'", key);
|
vomit("processing attribute '%1%'", key);
|
||||||
|
|
||||||
auto handleHashMode = [&](const std::string_view s) {
|
auto handleHashMode = [&](const std::string_view s) {
|
||||||
|
@ -1061,12 +1087,18 @@ 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) {
|
||||||
state.forceList(*i->value, pos);
|
state.forceList(*i->value, pos);
|
||||||
for (auto elem : i->value->listItems()) {
|
for (auto elem : i->value->listItems()) {
|
||||||
string s = state.coerceToString(posDrvName, *elem, context, true).toOwned();
|
auto s = state.coerceToString(posDrvName, *elem, context, true).toOwned();
|
||||||
drv.args.push_back(s);
|
drv.args.push_back(s);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1153,8 +1185,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) == '!') {
|
||||||
std::pair<string, string> 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. */
|
||||||
|
@ -1193,31 +1225,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", DerivationOutput {
|
drv.outputs.insert_or_assign("out",
|
||||||
.output = DerivationOutputCAFixed {
|
DerivationOutput::CAFixed {
|
||||||
.hash = FixedOutputHash {
|
.hash = FixedOutputHash {
|
||||||
.method = ingestionMethod,
|
.method = method,
|
||||||
.hash = std::move(h),
|
.hash = std::move(h),
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (contentAddressed) {
|
|
||||||
HashType ht = parseHashType(outputHashAlgo);
|
|
||||||
for (auto & i : outputs) {
|
|
||||||
drv.env[i] = hashPlaceholder(i);
|
|
||||||
drv.outputs.insert_or_assign(i, DerivationOutput {
|
|
||||||
.output = DerivationOutputCAFloating {
|
|
||||||
.method = ingestionMethod,
|
|
||||||
.hashType = ht,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (contentAddressed || isImpure) {
|
||||||
|
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) {
|
||||||
|
drv.env[i] = hashPlaceholder(i);
|
||||||
|
if (isImpure)
|
||||||
|
drv.outputs.insert_or_assign(i,
|
||||||
|
DerivationOutput::Impure {
|
||||||
|
.method = method,
|
||||||
|
.hashType = ht,
|
||||||
|
});
|
||||||
|
else
|
||||||
|
drv.outputs.insert_or_assign(i,
|
||||||
|
DerivationOutput::CAFloating {
|
||||||
|
.method = method,
|
||||||
|
.hashType = ht,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1231,44 +1276,29 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
for (auto & i : outputs) {
|
for (auto & i : outputs) {
|
||||||
drv.env[i] = "";
|
drv.env[i] = "";
|
||||||
drv.outputs.insert_or_assign(i,
|
drv.outputs.insert_or_assign(i,
|
||||||
DerivationOutput {
|
DerivationOutput::Deferred { });
|
||||||
.output = DerivationOutputInputAddressed {
|
|
||||||
.path = StorePath::dummy,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regular, non-CA derivation should always return a single hash and not
|
|
||||||
// hash per output.
|
|
||||||
auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true);
|
auto hashModulo = hashDerivationModulo(*state.store, Derivation(drv), true);
|
||||||
std::visit(overloaded {
|
switch (hashModulo.kind) {
|
||||||
[&](Hash & h) {
|
case DrvHash::Kind::Regular:
|
||||||
for (auto & i : outputs) {
|
for (auto & i : outputs) {
|
||||||
auto outPath = state.store->makeOutputPath(i, h, drvName);
|
auto h = hashModulo.hashes.at(i);
|
||||||
drv.env[i] = state.store->printStorePath(outPath);
|
auto outPath = state.store->makeOutputPath(i, h, drvName);
|
||||||
drv.outputs.insert_or_assign(i,
|
drv.env[i] = state.store->printStorePath(outPath);
|
||||||
DerivationOutput {
|
drv.outputs.insert_or_assign(
|
||||||
.output = DerivationOutputInputAddressed {
|
i,
|
||||||
.path = std::move(outPath),
|
DerivationOutputInputAddressed {
|
||||||
},
|
.path = std::move(outPath),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
break;
|
||||||
[&](CaOutputHashes &) {
|
;
|
||||||
// Shouldn't happen as the toplevel derivation is not CA.
|
case DrvHash::Kind::Deferred:
|
||||||
assert(false);
|
for (auto & i : outputs) {
|
||||||
},
|
drv.outputs.insert_or_assign(i, DerivationOutputDeferred {});
|
||||||
[&](DeferredHash &) {
|
}
|
||||||
for (auto & i : outputs) {
|
}
|
||||||
drv.outputs.insert_or_assign(i,
|
|
||||||
DerivationOutput {
|
|
||||||
.output = DerivationOutputDeferred{},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hashModulo);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Write the resulting term into the Nix store directory. */
|
/* Write the resulting term into the Nix store directory. */
|
||||||
|
@ -1279,12 +1309,9 @@ static void prim_derivationStrict(EvalState & state, const Pos & pos, Value * *
|
||||||
|
|
||||||
/* Optimisation, but required in read-only mode! because in that
|
/* Optimisation, but required in read-only mode! because in that
|
||||||
case we don't actually write store derivations, so we can't
|
case we don't actually write store derivations, so we can't
|
||||||
read them later.
|
read them later. */
|
||||||
|
{
|
||||||
However, we don't bother doing this for floating CA derivations because
|
auto h = hashDerivationModulo(*state.store, drv, false);
|
||||||
their "hash modulo" is indeterminate until built. */
|
|
||||||
if (drv.type() != DerivationType::CAFloating) {
|
|
||||||
auto h = hashDerivationModulo(*state.store, Derivation(drv), false);
|
|
||||||
drvHashes.lock()->insert_or_assign(drvPath, h);
|
drvHashes.lock()->insert_or_assign(drvPath, h);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1475,8 +1502,8 @@ static RegisterPrimOp primop_dirOf({
|
||||||
static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_readFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
auto path = realisePath(state, pos, *args[0]);
|
auto path = realisePath(state, pos, *args[0]);
|
||||||
string s = readFile(path);
|
auto s = readFile(path);
|
||||||
if (s.find((char) 0) != string::npos)
|
if (s.find((char) 0) != std::string::npos)
|
||||||
state.debug_throw(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
|
state.debug_throw(Error("the contents of the file '%1%' cannot be represented as a Nix string", path));
|
||||||
StorePathSet refs;
|
StorePathSet refs;
|
||||||
if (state.store->isInStore(path)) {
|
if (state.store->isInStore(path)) {
|
||||||
|
@ -1509,7 +1536,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
for (auto v2 : args[0]->listItems()) {
|
for (auto v2 : args[0]->listItems()) {
|
||||||
state.forceAttrs(*v2, pos);
|
state.forceAttrs(*v2, pos);
|
||||||
|
|
||||||
string prefix;
|
std::string prefix;
|
||||||
Bindings::iterator i = v2->attrs->find(state.sPrefix);
|
Bindings::iterator i = v2->attrs->find(state.sPrefix);
|
||||||
if (i != v2->attrs->end())
|
if (i != v2->attrs->end())
|
||||||
prefix = state.forceStringNoCtx(*i->value, pos);
|
prefix = state.forceStringNoCtx(*i->value, pos);
|
||||||
|
@ -1523,7 +1550,7 @@ static void prim_findFile(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
);
|
);
|
||||||
|
|
||||||
PathSet context;
|
PathSet context;
|
||||||
string path = state.coerceToString(pos, *i->value, context, false, false).toOwned();
|
auto path = state.coerceToString(pos, *i->value, context, false, false).toOwned();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto rewrites = state.realiseContext(context);
|
auto rewrites = state.realiseContext(context);
|
||||||
|
@ -1789,8 +1816,8 @@ static RegisterPrimOp primop_fromJSON({
|
||||||
static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_toFile(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
string name(state.forceStringNoCtx(*args[0], pos));
|
std::string name(state.forceStringNoCtx(*args[0], pos));
|
||||||
string contents(state.forceString(*args[1], context, pos));
|
std::string contents(state.forceString(*args[1], context, pos));
|
||||||
|
|
||||||
StorePathSet refs;
|
StorePathSet refs;
|
||||||
|
|
||||||
|
@ -1898,7 +1925,7 @@ static RegisterPrimOp primop_toFile({
|
||||||
static void addPath(
|
static void addPath(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const Pos & pos,
|
const Pos & pos,
|
||||||
const string & name,
|
const std::string & name,
|
||||||
Path path,
|
Path path,
|
||||||
Value * filterFun,
|
Value * filterFun,
|
||||||
FileIngestionMethod method,
|
FileIngestionMethod method,
|
||||||
|
@ -1954,20 +1981,15 @@ static void addPath(
|
||||||
if (expectedHash)
|
if (expectedHash)
|
||||||
expectedStorePath = state.store->makeFixedOutputPath(method, *expectedHash, name);
|
expectedStorePath = state.store->makeFixedOutputPath(method, *expectedHash, name);
|
||||||
|
|
||||||
Path dstPath;
|
|
||||||
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
|
if (!expectedHash || !state.store->isValidPath(*expectedStorePath)) {
|
||||||
dstPath = state.store->printStorePath(settings.readOnlyMode
|
StorePath dstPath = settings.readOnlyMode
|
||||||
? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first
|
? state.store->computeStorePathForPath(name, path, method, htSHA256, filter).first
|
||||||
: state.store->addToStore(name, path, method, htSHA256, filter, state.repair, refs));
|
: state.store->addToStore(name, path, method, htSHA256, filter, state.repair, refs);
|
||||||
if (expectedHash && expectedStorePath != state.store->parseStorePath(dstPath))
|
if (expectedHash && expectedStorePath != dstPath)
|
||||||
state.debug_throw(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
|
state.debug_throw(Error("store path mismatch in (possibly filtered) path added from '%s'", path));
|
||||||
|
state.allowAndSetStorePathString(dstPath, v);
|
||||||
} else
|
} else
|
||||||
dstPath = state.store->printStorePath(*expectedStorePath);
|
state.allowAndSetStorePathString(*expectedStorePath, v);
|
||||||
|
|
||||||
v.mkString(dstPath, {dstPath});
|
|
||||||
|
|
||||||
state.allowPath(dstPath);
|
|
||||||
|
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(pos, "while adding path '%s'", path);
|
e.addTrace(pos, "while adding path '%s'", path);
|
||||||
throw;
|
throw;
|
||||||
|
@ -2051,14 +2073,14 @@ static void prim_path(EvalState & state, const Pos & pos, Value * * args, Value
|
||||||
{
|
{
|
||||||
state.forceAttrs(*args[0], pos);
|
state.forceAttrs(*args[0], pos);
|
||||||
Path path;
|
Path path;
|
||||||
string name;
|
std::string name;
|
||||||
Value * filterFun = nullptr;
|
Value * filterFun = nullptr;
|
||||||
auto method = FileIngestionMethod::Recursive;
|
auto method = FileIngestionMethod::Recursive;
|
||||||
std::optional<Hash> expectedHash;
|
std::optional<Hash> expectedHash;
|
||||||
PathSet context;
|
PathSet context;
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
for (auto & attr : *args[0]->attrs) {
|
||||||
const string & n(attr.name);
|
auto & n(attr.name);
|
||||||
if (n == "path")
|
if (n == "path")
|
||||||
path = state.coerceToPath(*attr.pos, *attr.value, context);
|
path = state.coerceToPath(*attr.pos, *attr.value, context);
|
||||||
else if (attr.name == state.sName)
|
else if (attr.name == state.sName)
|
||||||
|
@ -3651,7 +3673,7 @@ static void prim_concatStringsSep(EvalState & state, const Pos & pos, Value * *
|
||||||
auto sep = state.forceString(*args[0], context, pos);
|
auto sep = state.forceString(*args[0], context, pos);
|
||||||
state.forceList(*args[1], pos);
|
state.forceList(*args[1], pos);
|
||||||
|
|
||||||
string res;
|
std::string res;
|
||||||
res.reserve((args[1]->listSize() + 32) * sep.size());
|
res.reserve((args[1]->listSize() + 32) * sep.size());
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
|
@ -3684,12 +3706,12 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar
|
||||||
.errPos = pos
|
.errPos = pos
|
||||||
}));
|
}));
|
||||||
|
|
||||||
vector<string> from;
|
std::vector<std::string> from;
|
||||||
from.reserve(args[0]->listSize());
|
from.reserve(args[0]->listSize());
|
||||||
for (auto elem : args[0]->listItems())
|
for (auto elem : args[0]->listItems())
|
||||||
from.emplace_back(state.forceString(*elem, pos));
|
from.emplace_back(state.forceString(*elem, pos));
|
||||||
|
|
||||||
vector<std::pair<string, PathSet>> to;
|
std::vector<std::pair<std::string, PathSet>> to;
|
||||||
to.reserve(args[1]->listSize());
|
to.reserve(args[1]->listSize());
|
||||||
for (auto elem : args[1]->listItems()) {
|
for (auto elem : args[1]->listItems()) {
|
||||||
PathSet ctx;
|
PathSet ctx;
|
||||||
|
@ -3700,7 +3722,7 @@ static void prim_replaceStrings(EvalState & state, const Pos & pos, Value * * ar
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto s = state.forceString(*args[2], context, pos);
|
auto s = state.forceString(*args[2], context, pos);
|
||||||
|
|
||||||
string res;
|
std::string res;
|
||||||
// Loops one past last character to handle the case where 'from' contains an empty string.
|
// Loops one past last character to handle the case where 'from' contains an empty string.
|
||||||
for (size_t p = 0; p <= s.size(); ) {
|
for (size_t p = 0; p <= s.size(); ) {
|
||||||
bool found = false;
|
bool found = false;
|
||||||
|
@ -3841,7 +3863,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,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3913,13 +3935,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);
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "primops.hh"
|
#include "primops.hh"
|
||||||
#include "eval-inline.hh"
|
#include "eval-inline.hh"
|
||||||
|
#include "derivations.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -37,7 +38,7 @@ static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & po
|
||||||
|
|
||||||
PathSet context2;
|
PathSet context2;
|
||||||
for (auto & p : context)
|
for (auto & p : context)
|
||||||
context2.insert(p.at(0) == '=' ? string(p, 1) : p);
|
context2.insert(p.at(0) == '=' ? std::string(p, 1) : p);
|
||||||
|
|
||||||
v.mkString(*s, context2);
|
v.mkString(*s, context2);
|
||||||
}
|
}
|
||||||
|
@ -76,14 +77,14 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args,
|
||||||
auto contextInfos = std::map<Path, ContextInfo>();
|
auto contextInfos = std::map<Path, ContextInfo>();
|
||||||
for (const auto & p : context) {
|
for (const auto & p : context) {
|
||||||
Path drv;
|
Path drv;
|
||||||
string output;
|
std::string output;
|
||||||
const Path * path = &p;
|
const Path * path = &p;
|
||||||
if (p.at(0) == '=') {
|
if (p.at(0) == '=') {
|
||||||
drv = string(p, 1);
|
drv = std::string(p, 1);
|
||||||
path = &drv;
|
path = &drv;
|
||||||
} else if (p.at(0) == '!') {
|
} else if (p.at(0) == '!') {
|
||||||
std::pair<string, 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;
|
||||||
}
|
}
|
||||||
|
@ -166,7 +167,7 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||||
.errPos = *i.pos
|
.errPos = *i.pos
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
context.insert("=" + string(i.name));
|
context.insert("=" + std::string(i.name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
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,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -62,7 +62,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
|
||||||
fetchers::Attrs attrs;
|
fetchers::Attrs attrs;
|
||||||
attrs.insert_or_assign("type", "hg");
|
attrs.insert_or_assign("type", "hg");
|
||||||
attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url);
|
attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url);
|
||||||
attrs.insert_or_assign("name", string(name));
|
attrs.insert_or_assign("name", std::string(name));
|
||||||
if (ref) attrs.insert_or_assign("ref", *ref);
|
if (ref) attrs.insert_or_assign("ref", *ref);
|
||||||
if (rev) attrs.insert_or_assign("rev", rev->gitRev());
|
if (rev) attrs.insert_or_assign("rev", rev->gitRev());
|
||||||
auto input = fetchers::Input::fromAttrs(std::move(attrs));
|
auto input = fetchers::Input::fromAttrs(std::move(attrs));
|
||||||
|
|
|
@ -19,7 +19,7 @@ void emitTreeAttrs(
|
||||||
bool emptyRevFallback,
|
bool emptyRevFallback,
|
||||||
bool forceDirty)
|
bool forceDirty)
|
||||||
{
|
{
|
||||||
assert(input.isImmutable());
|
assert(input.isLocked());
|
||||||
|
|
||||||
auto attrs = state.buildBindings(8);
|
auto attrs = state.buildBindings(8);
|
||||||
|
|
||||||
|
@ -166,8 +166,8 @@ static void fetchTree(
|
||||||
if (!evalSettings.pureEval && !input.isDirect())
|
if (!evalSettings.pureEval && !input.isDirect())
|
||||||
input = lookupInRegistries(state.store, input).first;
|
input = lookupInRegistries(state.store, input).first;
|
||||||
|
|
||||||
if (evalSettings.pureEval && !input.isImmutable())
|
if (evalSettings.pureEval && !input.isLocked())
|
||||||
state.debug_throw(EvalError("in pure evaluation mode, 'fetchTree' requires an immutable input, at %s", pos));
|
state.debug_throw(EvalError("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", pos));
|
||||||
|
|
||||||
auto [tree, input2] = input.fetch(state.store);
|
auto [tree, input2] = input.fetch(state.store);
|
||||||
|
|
||||||
|
@ -186,7 +186,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree);
|
static RegisterPrimOp primop_fetchTree("fetchTree", 1, prim_fetchTree);
|
||||||
|
|
||||||
static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||||
const string & who, bool unpack, std::string name)
|
const std::string & who, bool unpack, std::string name)
|
||||||
{
|
{
|
||||||
std::optional<std::string> url;
|
std::optional<std::string> url;
|
||||||
std::optional<Hash> expectedHash;
|
std::optional<Hash> expectedHash;
|
||||||
|
@ -198,7 +198,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||||
state.forceAttrs(*args[0], pos);
|
state.forceAttrs(*args[0], pos);
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
for (auto & attr : *args[0]->attrs) {
|
||||||
string n(attr.name);
|
std::string n(attr.name);
|
||||||
if (n == "url")
|
if (n == "url")
|
||||||
url = state.forceStringNoCtx(*attr.value, *attr.pos);
|
url = state.forceStringNoCtx(*attr.value, *attr.pos);
|
||||||
else if (n == "sha256")
|
else if (n == "sha256")
|
||||||
|
@ -230,6 +230,21 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||||
if (evalSettings.pureEval && !expectedHash)
|
if (evalSettings.pureEval && !expectedHash)
|
||||||
state.debug_throw(EvalError("in pure evaluation mode, '%s' requires a 'sha256' argument", who));
|
state.debug_throw(EvalError("in pure evaluation mode, '%s' requires a 'sha256' argument", who));
|
||||||
|
|
||||||
|
// early exit if pinned and already in the store
|
||||||
|
if (expectedHash && expectedHash->type == htSHA256) {
|
||||||
|
auto expectedPath =
|
||||||
|
unpack
|
||||||
|
? state.store->makeFixedOutputPath(FileIngestionMethod::Recursive, *expectedHash, name, {})
|
||||||
|
: state.store->makeFixedOutputPath(FileIngestionMethod::Flat, *expectedHash, name, {});
|
||||||
|
|
||||||
|
if (state.store->isValidPath(expectedPath)) {
|
||||||
|
state.allowAndSetStorePathString(expectedPath, v);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: fetching may fail, yet the path may be substitutable.
|
||||||
|
// https://github.com/NixOS/nix/issues/4313
|
||||||
auto storePath =
|
auto storePath =
|
||||||
unpack
|
unpack
|
||||||
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).first.storePath
|
? fetchers::downloadTarball(state.store, *url, name, (bool) expectedHash).first.storePath
|
||||||
|
@ -244,10 +259,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||||
*url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true)));
|
*url, expectedHash->to_string(Base32, true), hash.to_string(Base32, true)));
|
||||||
}
|
}
|
||||||
|
|
||||||
state.allowPath(storePath);
|
state.allowAndSetStorePathString(storePath, v);
|
||||||
|
|
||||||
auto path = state.store->printStorePath(storePath);
|
|
||||||
v.mkString(path, PathSet({path}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
||||||
|
@ -317,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 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
{
|
{
|
||||||
auto toml = state.forceStringNoCtx(*args[0], pos);
|
auto toml = state.forceStringNoCtx(*args[0], pos);
|
||||||
|
|
||||||
std::istringstream tomlStream(string{toml});
|
std::istringstream tomlStream(std::string{toml});
|
||||||
|
|
||||||
std::function<void(Value &, toml::value)> visit;
|
std::function<void(Value &, toml::value)> visit;
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@ namespace nix {
|
||||||
class Symbol
|
class Symbol
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
const string * s; // pointer into SymbolTable
|
const std::string * s; // pointer into SymbolTable
|
||||||
Symbol(const string * s) : s(s) { };
|
Symbol(const std::string * s) : s(s) { };
|
||||||
friend class SymbolTable;
|
friend class SymbolTable;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -72,7 +72,7 @@ class SymbolTable
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string_view, Symbol> symbols;
|
std::unordered_map<std::string_view, Symbol> symbols;
|
||||||
std::list<string> store;
|
std::list<std::string> store;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Symbol create(std::string_view s)
|
Symbol create(std::string_view s)
|
||||||
|
@ -84,7 +84,7 @@ public:
|
||||||
auto it = symbols.find(s);
|
auto it = symbols.find(s);
|
||||||
if (it != symbols.end()) return it->second;
|
if (it != symbols.end()) return it->second;
|
||||||
|
|
||||||
const string & rawSym = store.emplace_back(s);
|
auto & rawSym = store.emplace_back(s);
|
||||||
return symbols.emplace(rawSym, Symbol(&rawSym)).first->second;
|
return symbols.emplace(rawSym, Symbol(&rawSym)).first->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
static XMLAttrs singletonAttrs(const string & name, const string & value)
|
static XMLAttrs singletonAttrs(const std::string & name, const std::string & value)
|
||||||
{
|
{
|
||||||
XMLAttrs attrs;
|
XMLAttrs attrs;
|
||||||
attrs[name] = value;
|
attrs[name] = value;
|
||||||
|
|
|
@ -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
|
||||||
|
@ -77,20 +81,20 @@ class ExternalValueBase
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/* Return a simple string describing the type */
|
/* Return a simple string describing the type */
|
||||||
virtual string showType() const = 0;
|
virtual std::string showType() const = 0;
|
||||||
|
|
||||||
/* Return a string to be used in builtins.typeOf */
|
/* Return a string to be used in builtins.typeOf */
|
||||||
virtual string typeOf() const = 0;
|
virtual std::string typeOf() const = 0;
|
||||||
|
|
||||||
/* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
/* Coerce the value to a string. Defaults to uncoercable, i.e. throws an
|
||||||
* error
|
* error.
|
||||||
*/
|
*/
|
||||||
virtual string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const;
|
virtual std::string coerceToString(const Pos & pos, PathSet & context, bool copyMore, bool copyToStore) const;
|
||||||
|
|
||||||
/* Compare to another value of the same type. Defaults to uncomparable,
|
/* Compare to another value of the same type. Defaults to uncomparable,
|
||||||
* i.e. always false.
|
* i.e. always false.
|
||||||
*/
|
*/
|
||||||
virtual bool operator==(const ExternalValueBase & b) const;
|
virtual bool operator ==(const ExternalValueBase & b) const;
|
||||||
|
|
||||||
/* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */
|
/* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */
|
||||||
virtual void printValueAsJSON(EvalState & state, bool strict,
|
virtual void printValueAsJSON(EvalState & state, bool strict,
|
||||||
|
@ -114,11 +118,14 @@ struct Value
|
||||||
private:
|
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 Value *> & active, 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()
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,13 +52,13 @@ struct CacheImpl : Cache
|
||||||
const Attrs & inAttrs,
|
const Attrs & inAttrs,
|
||||||
const Attrs & infoAttrs,
|
const Attrs & infoAttrs,
|
||||||
const StorePath & storePath,
|
const StorePath & storePath,
|
||||||
bool immutable) override
|
bool locked) override
|
||||||
{
|
{
|
||||||
_state.lock()->add.use()
|
_state.lock()->add.use()
|
||||||
(attrsToJSON(inAttrs).dump())
|
(attrsToJSON(inAttrs).dump())
|
||||||
(attrsToJSON(infoAttrs).dump())
|
(attrsToJSON(infoAttrs).dump())
|
||||||
(store->printStorePath(storePath))
|
(store->printStorePath(storePath))
|
||||||
(immutable)
|
(locked)
|
||||||
(time(0)).exec();
|
(time(0)).exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,7 +91,7 @@ struct CacheImpl : Cache
|
||||||
|
|
||||||
auto infoJSON = stmt.getStr(0);
|
auto infoJSON = stmt.getStr(0);
|
||||||
auto storePath = store->parseStorePath(stmt.getStr(1));
|
auto storePath = store->parseStorePath(stmt.getStr(1));
|
||||||
auto immutable = stmt.getInt(2) != 0;
|
auto locked = stmt.getInt(2) != 0;
|
||||||
auto timestamp = stmt.getInt(3);
|
auto timestamp = stmt.getInt(3);
|
||||||
|
|
||||||
store->addTempRoot(storePath);
|
store->addTempRoot(storePath);
|
||||||
|
@ -105,7 +105,7 @@ struct CacheImpl : Cache
|
||||||
inAttrsJSON, infoJSON, store->printStorePath(storePath));
|
inAttrsJSON, infoJSON, store->printStorePath(storePath));
|
||||||
|
|
||||||
return Result {
|
return Result {
|
||||||
.expired = !immutable && (settings.tarballTtl.get() == 0 || timestamp + settings.tarballTtl < time(0)),
|
.expired = !locked && (settings.tarballTtl.get() == 0 || timestamp + settings.tarballTtl < time(0)),
|
||||||
.infoAttrs = jsonToAttrs(nlohmann::json::parse(infoJSON)),
|
.infoAttrs = jsonToAttrs(nlohmann::json::parse(infoJSON)),
|
||||||
.storePath = std::move(storePath)
|
.storePath = std::move(storePath)
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,7 +13,7 @@ struct Cache
|
||||||
const Attrs & inAttrs,
|
const Attrs & inAttrs,
|
||||||
const Attrs & infoAttrs,
|
const Attrs & infoAttrs,
|
||||||
const StorePath & storePath,
|
const StorePath & storePath,
|
||||||
bool immutable) = 0;
|
bool locked) = 0;
|
||||||
|
|
||||||
virtual std::optional<std::pair<Attrs, StorePath>> lookup(
|
virtual std::optional<std::pair<Attrs, StorePath>> lookup(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
|
|
13
src/libfetchers/fetch-settings.cc
Normal file
13
src/libfetchers/fetch-settings.cc
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
#include "fetch-settings.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
FetchSettings::FetchSettings()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
FetchSettings fetchSettings;
|
||||||
|
|
||||||
|
static GlobalConfig::Register rFetchSettings(&fetchSettings);
|
||||||
|
|
||||||
|
}
|
93
src/libfetchers/fetch-settings.hh
Normal file
93
src/libfetchers/fetch-settings.hh
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "types.hh"
|
||||||
|
#include "config.hh"
|
||||||
|
#include "util.hh"
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
struct FetchSettings : public Config
|
||||||
|
{
|
||||||
|
FetchSettings();
|
||||||
|
|
||||||
|
Setting<StringMap> accessTokens{this, {}, "access-tokens",
|
||||||
|
R"(
|
||||||
|
Access tokens used to access protected GitHub, GitLab, or
|
||||||
|
other locations requiring token-based authentication.
|
||||||
|
|
||||||
|
Access tokens are specified as a string made up of
|
||||||
|
space-separated `host=token` values. The specific token
|
||||||
|
used is selected by matching the `host` portion against the
|
||||||
|
"host" specification of the input. The actual use of the
|
||||||
|
`token` value is determined by the type of resource being
|
||||||
|
accessed:
|
||||||
|
|
||||||
|
* Github: the token value is the OAUTH-TOKEN string obtained
|
||||||
|
as the Personal Access Token from the Github server (see
|
||||||
|
https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps).
|
||||||
|
|
||||||
|
* Gitlab: the token value is either the OAuth2 token or the
|
||||||
|
Personal Access Token (these are different types tokens
|
||||||
|
for gitlab, see
|
||||||
|
https://docs.gitlab.com/12.10/ee/api/README.html#authentication).
|
||||||
|
The `token` value should be `type:tokenstring` where
|
||||||
|
`type` is either `OAuth2` or `PAT` to indicate which type
|
||||||
|
of token is being specified.
|
||||||
|
|
||||||
|
Example `~/.config/nix/nix.conf`:
|
||||||
|
|
||||||
|
```
|
||||||
|
access-tokens = github.com=23ac...b289 gitlab.mycompany.com=PAT:A123Bp_Cd..EfG gitlab.com=OAuth2:1jklw3jk
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `~/code/flake.nix`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
input.foo = {
|
||||||
|
type = "gitlab";
|
||||||
|
host = "gitlab.mycompany.com";
|
||||||
|
owner = "mycompany";
|
||||||
|
repo = "pro";
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
This example specifies three tokens, one each for accessing
|
||||||
|
github.com, gitlab.mycompany.com, and sourceforge.net.
|
||||||
|
|
||||||
|
The `input.foo` uses the "gitlab" fetcher, which might
|
||||||
|
requires specifying the token type along with the token
|
||||||
|
value.
|
||||||
|
)"};
|
||||||
|
|
||||||
|
Setting<bool> allowDirty{this, true, "allow-dirty",
|
||||||
|
"Whether to allow dirty Git/Mercurial trees."};
|
||||||
|
|
||||||
|
Setting<bool> warnDirty{this, true, "warn-dirty",
|
||||||
|
"Whether to warn about dirty Git/Mercurial trees."};
|
||||||
|
|
||||||
|
Setting<std::string> flakeRegistry{this, "https://github.com/NixOS/flake-registry/raw/master/flake-registry.json", "flake-registry",
|
||||||
|
"Path or URI of the global flake registry."};
|
||||||
|
|
||||||
|
Setting<bool> useRegistries{this, true, "use-registries",
|
||||||
|
"Whether to use flake registries to resolve flake references."};
|
||||||
|
|
||||||
|
Setting<bool> acceptFlakeConfig{this, false, "accept-flake-config",
|
||||||
|
"Whether to accept nix configuration from a flake without prompting."};
|
||||||
|
|
||||||
|
Setting<std::string> commitLockFileSummary{
|
||||||
|
this, "", "commit-lockfile-summary",
|
||||||
|
R"(
|
||||||
|
The commit summary to use when committing changed flake lock files. If
|
||||||
|
empty, the summary is generated based on the action performed.
|
||||||
|
)"};
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: don't use a global variable.
|
||||||
|
extern FetchSettings fetchSettings;
|
||||||
|
|
||||||
|
}
|
|
@ -24,11 +24,11 @@ static void fixupInput(Input & input)
|
||||||
input.getType();
|
input.getType();
|
||||||
input.getRef();
|
input.getRef();
|
||||||
if (input.getRev())
|
if (input.getRev())
|
||||||
input.immutable = true;
|
input.locked = true;
|
||||||
input.getRevCount();
|
input.getRevCount();
|
||||||
input.getLastModified();
|
input.getLastModified();
|
||||||
if (input.getNarHash())
|
if (input.getNarHash())
|
||||||
input.immutable = true;
|
input.locked = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Input Input::fromURL(const ParsedURL & url)
|
Input Input::fromURL(const ParsedURL & url)
|
||||||
|
@ -124,15 +124,13 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
|
||||||
debug("using substituted/cached input '%s' in '%s'",
|
debug("using substituted/cached input '%s' in '%s'",
|
||||||
to_string(), store->printStorePath(storePath));
|
to_string(), store->printStorePath(storePath));
|
||||||
|
|
||||||
auto actualPath = store->toRealPath(storePath);
|
return {Tree { .actualPath = store->toRealPath(storePath), .storePath = std::move(storePath) }, *this};
|
||||||
|
|
||||||
return {fetchers::Tree(std::move(actualPath), std::move(storePath)), *this};
|
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
debug("substitution of input '%s' failed: %s", to_string(), e.what());
|
debug("substitution of input '%s' failed: %s", to_string(), e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [tree, input] = [&]() -> std::pair<Tree, Input> {
|
auto [storePath, input] = [&]() -> std::pair<StorePath, Input> {
|
||||||
try {
|
try {
|
||||||
return scheme->fetch(store, *this);
|
return scheme->fetch(store, *this);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
|
@ -141,8 +139,10 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
|
|
||||||
if (tree.actualPath == "")
|
Tree tree {
|
||||||
tree.actualPath = store->toRealPath(tree.storePath);
|
.actualPath = store->toRealPath(storePath),
|
||||||
|
.storePath = storePath,
|
||||||
|
};
|
||||||
|
|
||||||
auto narHash = store->queryPathInfo(tree.storePath)->narHash;
|
auto narHash = store->queryPathInfo(tree.storePath)->narHash;
|
||||||
input.attrs.insert_or_assign("narHash", narHash.to_string(SRI, true));
|
input.attrs.insert_or_assign("narHash", narHash.to_string(SRI, true));
|
||||||
|
@ -165,7 +165,7 @@ std::pair<Tree, Input> Input::fetch(ref<Store> store) const
|
||||||
input.to_string(), *prevRevCount);
|
input.to_string(), *prevRevCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
input.immutable = true;
|
input.locked = true;
|
||||||
|
|
||||||
assert(input.hasAllInfo());
|
assert(input.hasAllInfo());
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ StorePath Input::computeStorePath(Store & store) const
|
||||||
{
|
{
|
||||||
auto narHash = getNarHash();
|
auto narHash = getNarHash();
|
||||||
if (!narHash)
|
if (!narHash)
|
||||||
throw Error("cannot compute store path for mutable input '%s'", to_string());
|
throw Error("cannot compute store path for unlocked input '%s'", to_string());
|
||||||
return store.makeFixedOutputPath(FileIngestionMethod::Recursive, *narHash, getName());
|
return store.makeFixedOutputPath(FileIngestionMethod::Recursive, *narHash, getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ struct Tree
|
||||||
{
|
{
|
||||||
Path actualPath;
|
Path actualPath;
|
||||||
StorePath storePath;
|
StorePath storePath;
|
||||||
Tree(Path && actualPath, StorePath && storePath) : actualPath(actualPath), storePath(std::move(storePath)) {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct InputScheme;
|
struct InputScheme;
|
||||||
|
@ -35,7 +34,7 @@ struct Input
|
||||||
|
|
||||||
std::shared_ptr<InputScheme> scheme; // note: can be null
|
std::shared_ptr<InputScheme> scheme; // note: can be null
|
||||||
Attrs attrs;
|
Attrs attrs;
|
||||||
bool immutable = false;
|
bool locked = false;
|
||||||
bool direct = true;
|
bool direct = true;
|
||||||
|
|
||||||
/* path of the parent of this input, used for relative path resolution */
|
/* path of the parent of this input, used for relative path resolution */
|
||||||
|
@ -60,9 +59,9 @@ public:
|
||||||
one that goes through a registry. */
|
one that goes through a registry. */
|
||||||
bool isDirect() const { return direct; }
|
bool isDirect() const { return direct; }
|
||||||
|
|
||||||
/* Check whether this is an "immutable" input, that is,
|
/* Check whether this is a "locked" input, that is,
|
||||||
one that contains a commit hash or content hash. */
|
one that contains a commit hash or content hash. */
|
||||||
bool isImmutable() const { return immutable; }
|
bool isLocked() const { return locked; }
|
||||||
|
|
||||||
bool hasAllInfo() const;
|
bool hasAllInfo() const;
|
||||||
|
|
||||||
|
@ -70,6 +69,8 @@ public:
|
||||||
|
|
||||||
bool contains(const Input & other) const;
|
bool contains(const Input & other) const;
|
||||||
|
|
||||||
|
/* Fetch the input into the Nix store, returning the location in
|
||||||
|
the Nix store and the locked input. */
|
||||||
std::pair<Tree, Input> fetch(ref<Store> store) const;
|
std::pair<Tree, Input> fetch(ref<Store> store) const;
|
||||||
|
|
||||||
Input applyOverrides(
|
Input applyOverrides(
|
||||||
|
@ -131,7 +132,7 @@ struct InputScheme
|
||||||
|
|
||||||
virtual void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg);
|
virtual void markChangedFile(const Input & input, std::string_view file, std::optional<std::string> commitMsg);
|
||||||
|
|
||||||
virtual std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) = 0;
|
virtual std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);
|
void registerInputScheme(std::shared_ptr<InputScheme> && fetcher);
|
||||||
|
@ -147,14 +148,14 @@ DownloadFileResult downloadFile(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::string & name,
|
const std::string & name,
|
||||||
bool immutable,
|
bool locked,
|
||||||
const Headers & headers = {});
|
const Headers & headers = {});
|
||||||
|
|
||||||
std::pair<Tree, time_t> downloadTarball(
|
std::pair<Tree, time_t> downloadTarball(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::string & name,
|
const std::string & name,
|
||||||
bool immutable,
|
bool locked,
|
||||||
const Headers & headers = {});
|
const Headers & headers = {});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
#include "url-parts.hh"
|
#include "url-parts.hh"
|
||||||
#include "pathlocks.hh"
|
#include "pathlocks.hh"
|
||||||
|
|
||||||
|
#include "fetch-settings.hh"
|
||||||
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
@ -172,7 +174,7 @@ struct GitInputScheme : InputScheme
|
||||||
return {isLocal, isLocal ? url.path : url.base};
|
return {isLocal, isLocal ? url.path : url.base};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Tree, Input> fetch(ref<Store> store, const Input & _input) override
|
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
|
||||||
{
|
{
|
||||||
Input input(_input);
|
Input input(_input);
|
||||||
|
|
||||||
|
@ -187,7 +189,7 @@ struct GitInputScheme : InputScheme
|
||||||
if (submodules) cacheType += "-submodules";
|
if (submodules) cacheType += "-submodules";
|
||||||
if (allRefs) cacheType += "-all-refs";
|
if (allRefs) cacheType += "-all-refs";
|
||||||
|
|
||||||
auto getImmutableAttrs = [&]()
|
auto getLockedAttrs = [&]()
|
||||||
{
|
{
|
||||||
return Attrs({
|
return Attrs({
|
||||||
{"type", cacheType},
|
{"type", cacheType},
|
||||||
|
@ -197,21 +199,18 @@ struct GitInputScheme : InputScheme
|
||||||
};
|
};
|
||||||
|
|
||||||
auto makeResult = [&](const Attrs & infoAttrs, StorePath && storePath)
|
auto makeResult = [&](const Attrs & infoAttrs, StorePath && storePath)
|
||||||
-> std::pair<Tree, Input>
|
-> std::pair<StorePath, Input>
|
||||||
{
|
{
|
||||||
assert(input.getRev());
|
assert(input.getRev());
|
||||||
assert(!_input.getRev() || _input.getRev() == input.getRev());
|
assert(!_input.getRev() || _input.getRev() == input.getRev());
|
||||||
if (!shallow)
|
if (!shallow)
|
||||||
input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount"));
|
input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount"));
|
||||||
input.attrs.insert_or_assign("lastModified", getIntAttr(infoAttrs, "lastModified"));
|
input.attrs.insert_or_assign("lastModified", getIntAttr(infoAttrs, "lastModified"));
|
||||||
return {
|
return {std::move(storePath), input};
|
||||||
Tree(store->toRealPath(storePath), std::move(storePath)),
|
|
||||||
input
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (input.getRev()) {
|
if (input.getRev()) {
|
||||||
if (auto res = getCache()->lookup(store, getImmutableAttrs()))
|
if (auto res = getCache()->lookup(store, getLockedAttrs()))
|
||||||
return makeResult(res->first, std::move(res->second));
|
return makeResult(res->first, std::move(res->second));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -223,22 +222,46 @@ struct GitInputScheme : InputScheme
|
||||||
if (!input.getRef() && !input.getRev() && isLocal) {
|
if (!input.getRef() && !input.getRev() && isLocal) {
|
||||||
bool clean = false;
|
bool clean = false;
|
||||||
|
|
||||||
/* Check whether this repo has any commits. There are
|
auto env = getEnv();
|
||||||
probably better ways to do this. */
|
// Set LC_ALL to C: because we rely on the error messages from git rev-parse to determine what went wrong
|
||||||
auto gitDir = actualUrl + "/.git";
|
// that way unknown errors can lead to a failure instead of continuing through the wrong code path
|
||||||
auto commonGitDir = chomp(runProgram(
|
env["LC_ALL"] = "C";
|
||||||
"git",
|
|
||||||
true,
|
|
||||||
{ "-C", actualUrl, "rev-parse", "--git-common-dir" }
|
|
||||||
));
|
|
||||||
if (commonGitDir != ".git")
|
|
||||||
gitDir = commonGitDir;
|
|
||||||
|
|
||||||
bool haveCommits = !readDirectory(gitDir + "/refs/heads").empty();
|
/* Check whether HEAD points to something that looks like a commit,
|
||||||
|
since that is the refrence we want to use later on. */
|
||||||
|
auto result = runProgram(RunOptions {
|
||||||
|
.program = "git",
|
||||||
|
.args = { "-C", actualUrl, "--git-dir=.git", "rev-parse", "--verify", "--no-revs", "HEAD^{commit}" },
|
||||||
|
.environment = env,
|
||||||
|
.mergeStderrToStdout = true
|
||||||
|
});
|
||||||
|
auto exitCode = WEXITSTATUS(result.first);
|
||||||
|
auto errorMessage = result.second;
|
||||||
|
|
||||||
|
if (errorMessage.find("fatal: not a git repository") != std::string::npos) {
|
||||||
|
throw Error("'%s' is not a Git repository", actualUrl);
|
||||||
|
} else if (errorMessage.find("fatal: Needed a single revision") != std::string::npos) {
|
||||||
|
// indicates that the repo does not have any commits
|
||||||
|
// we want to proceed and will consider it dirty later
|
||||||
|
} else if (exitCode != 0) {
|
||||||
|
// any other errors should lead to a failure
|
||||||
|
throw Error("getting the HEAD of the Git tree '%s' failed with exit code %d:\n%s", actualUrl, exitCode, errorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasHead = exitCode == 0;
|
||||||
try {
|
try {
|
||||||
if (haveCommits) {
|
if (hasHead) {
|
||||||
runProgram("git", true, { "-C", actualUrl, "diff-index", "--quiet", "HEAD", "--" });
|
// Using git diff is preferrable over lower-level operations here,
|
||||||
|
// because its conceptually simpler and we only need the exit code anyways.
|
||||||
|
auto gitDiffOpts = Strings({ "-C", actualUrl, "diff", "HEAD", "--quiet"});
|
||||||
|
if (!submodules) {
|
||||||
|
// Changes in submodules should only make the tree dirty
|
||||||
|
// when those submodules will be copied as well.
|
||||||
|
gitDiffOpts.emplace_back("--ignore-submodules");
|
||||||
|
}
|
||||||
|
gitDiffOpts.emplace_back("--");
|
||||||
|
runProgram("git", true, gitDiffOpts);
|
||||||
|
|
||||||
clean = true;
|
clean = true;
|
||||||
}
|
}
|
||||||
} catch (ExecError & e) {
|
} catch (ExecError & e) {
|
||||||
|
@ -249,10 +272,10 @@ struct GitInputScheme : InputScheme
|
||||||
|
|
||||||
/* This is an unclean working tree. So copy all tracked files. */
|
/* This is an unclean working tree. So copy all tracked files. */
|
||||||
|
|
||||||
if (!settings.allowDirty)
|
if (!fetchSettings.allowDirty)
|
||||||
throw Error("Git tree '%s' is dirty", actualUrl);
|
throw Error("Git tree '%s' is dirty", actualUrl);
|
||||||
|
|
||||||
if (settings.warnDirty)
|
if (fetchSettings.warnDirty)
|
||||||
warn("Git tree '%s' is dirty", actualUrl);
|
warn("Git tree '%s' is dirty", actualUrl);
|
||||||
|
|
||||||
auto gitOpts = Strings({ "-C", actualUrl, "ls-files", "-z" });
|
auto gitOpts = Strings({ "-C", actualUrl, "ls-files", "-z" });
|
||||||
|
@ -262,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);
|
||||||
|
|
||||||
|
@ -277,24 +302,21 @@ 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",
|
||||||
haveCommits ? 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 {
|
return {std::move(storePath), input};
|
||||||
Tree(store->toRealPath(storePath), std::move(storePath)),
|
|
||||||
input
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!input.getRef()) input.attrs.insert_or_assign("ref", isLocal ? readHead(actualUrl) : "master");
|
if (!input.getRef()) input.attrs.insert_or_assign("ref", isLocal ? readHead(actualUrl) : "master");
|
||||||
|
|
||||||
Attrs mutableAttrs({
|
Attrs unlockedAttrs({
|
||||||
{"type", cacheType},
|
{"type", cacheType},
|
||||||
{"name", name},
|
{"name", name},
|
||||||
{"url", actualUrl},
|
{"url", actualUrl},
|
||||||
|
@ -313,7 +335,7 @@ struct GitInputScheme : InputScheme
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (auto res = getCache()->lookup(store, mutableAttrs)) {
|
if (auto res = getCache()->lookup(store, unlockedAttrs)) {
|
||||||
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), htSHA1);
|
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), htSHA1);
|
||||||
if (!input.getRev() || input.getRev() == rev2) {
|
if (!input.getRev() || input.getRev() == rev2) {
|
||||||
input.attrs.insert_or_assign("rev", rev2.gitRev());
|
input.attrs.insert_or_assign("rev", rev2.gitRev());
|
||||||
|
@ -410,7 +432,7 @@ struct GitInputScheme : InputScheme
|
||||||
|
|
||||||
/* Now that we know the ref, check again whether we have it in
|
/* Now that we know the ref, check again whether we have it in
|
||||||
the store. */
|
the store. */
|
||||||
if (auto res = getCache()->lookup(store, getImmutableAttrs()))
|
if (auto res = getCache()->lookup(store, getLockedAttrs()))
|
||||||
return makeResult(res->first, std::move(res->second));
|
return makeResult(res->first, std::move(res->second));
|
||||||
|
|
||||||
Path tmpDir = createTempDir();
|
Path tmpDir = createTempDir();
|
||||||
|
@ -482,14 +504,14 @@ struct GitInputScheme : InputScheme
|
||||||
if (!_input.getRev())
|
if (!_input.getRev())
|
||||||
getCache()->add(
|
getCache()->add(
|
||||||
store,
|
store,
|
||||||
mutableAttrs,
|
unlockedAttrs,
|
||||||
infoAttrs,
|
infoAttrs,
|
||||||
storePath,
|
storePath,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
getCache()->add(
|
getCache()->add(
|
||||||
store,
|
store,
|
||||||
getImmutableAttrs(),
|
getLockedAttrs(),
|
||||||
infoAttrs,
|
infoAttrs,
|
||||||
storePath,
|
storePath,
|
||||||
true);
|
true);
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
#include "filetransfer.hh"
|
#include "filetransfer.hh"
|
||||||
#include "cache.hh"
|
#include "cache.hh"
|
||||||
#include "fetchers.hh"
|
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "url-parts.hh"
|
#include "url-parts.hh"
|
||||||
|
|
||||||
|
#include "fetchers.hh"
|
||||||
|
#include "fetch-settings.hh"
|
||||||
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
namespace nix::fetchers {
|
namespace nix::fetchers {
|
||||||
|
|
||||||
|
@ -17,7 +20,7 @@ struct DownloadUrl
|
||||||
Headers headers;
|
Headers headers;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A github or gitlab host
|
// A github, gitlab, or sourcehut host
|
||||||
const static std::string hostRegexS = "[a-zA-Z0-9.]*"; // FIXME: check
|
const static std::string hostRegexS = "[a-zA-Z0-9.]*"; // FIXME: check
|
||||||
std::regex hostRegex(hostRegexS, std::regex::ECMAScript);
|
std::regex hostRegex(hostRegexS, std::regex::ECMAScript);
|
||||||
|
|
||||||
|
@ -156,7 +159,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
|
|
||||||
std::optional<std::string> getAccessToken(const std::string & host) const
|
std::optional<std::string> getAccessToken(const std::string & host) const
|
||||||
{
|
{
|
||||||
auto tokens = settings.accessTokens.get();
|
auto tokens = fetchSettings.accessTokens.get();
|
||||||
if (auto token = get(tokens, host))
|
if (auto token = get(tokens, host))
|
||||||
return *token;
|
return *token;
|
||||||
return {};
|
return {};
|
||||||
|
@ -180,7 +183,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
|
|
||||||
virtual DownloadUrl getDownloadUrl(const Input & input) const = 0;
|
virtual DownloadUrl getDownloadUrl(const Input & input) const = 0;
|
||||||
|
|
||||||
std::pair<Tree, Input> fetch(ref<Store> store, const Input & _input) override
|
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
|
||||||
{
|
{
|
||||||
Input input(_input);
|
Input input(_input);
|
||||||
|
|
||||||
|
@ -192,17 +195,14 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
input.attrs.erase("ref");
|
input.attrs.erase("ref");
|
||||||
input.attrs.insert_or_assign("rev", rev->gitRev());
|
input.attrs.insert_or_assign("rev", rev->gitRev());
|
||||||
|
|
||||||
Attrs immutableAttrs({
|
Attrs lockedAttrs({
|
||||||
{"type", "git-tarball"},
|
{"type", "git-tarball"},
|
||||||
{"rev", rev->gitRev()},
|
{"rev", rev->gitRev()},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (auto res = getCache()->lookup(store, immutableAttrs)) {
|
if (auto res = getCache()->lookup(store, lockedAttrs)) {
|
||||||
input.attrs.insert_or_assign("lastModified", getIntAttr(res->first, "lastModified"));
|
input.attrs.insert_or_assign("lastModified", getIntAttr(res->first, "lastModified"));
|
||||||
return {
|
return {std::move(res->second), input};
|
||||||
Tree(store->toRealPath(res->second), std::move(res->second)),
|
|
||||||
input
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto url = getDownloadUrl(input);
|
auto url = getDownloadUrl(input);
|
||||||
|
@ -213,7 +213,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
|
|
||||||
getCache()->add(
|
getCache()->add(
|
||||||
store,
|
store,
|
||||||
immutableAttrs,
|
lockedAttrs,
|
||||||
{
|
{
|
||||||
{"rev", rev->gitRev()},
|
{"rev", rev->gitRev()},
|
||||||
{"lastModified", uint64_t(lastModified)}
|
{"lastModified", uint64_t(lastModified)}
|
||||||
|
@ -221,7 +221,7 @@ struct GitArchiveInputScheme : InputScheme
|
||||||
tree.storePath,
|
tree.storePath,
|
||||||
true);
|
true);
|
||||||
|
|
||||||
return {std::move(tree), input};
|
return {std::move(tree.storePath), input};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -348,7 +348,97 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct SourceHutInputScheme : GitArchiveInputScheme
|
||||||
|
{
|
||||||
|
std::string type() override { return "sourcehut"; }
|
||||||
|
|
||||||
|
std::optional<std::pair<std::string, std::string>> accessHeaderFromToken(const std::string & token) const override
|
||||||
|
{
|
||||||
|
// SourceHut supports both PAT and OAuth2. See
|
||||||
|
// https://man.sr.ht/meta.sr.ht/oauth.md
|
||||||
|
return std::pair<std::string, std::string>("Authorization", fmt("Bearer %s", token));
|
||||||
|
// Note: This currently serves no purpose, as this kind of authorization
|
||||||
|
// does not allow for downloading tarballs on sourcehut private repos.
|
||||||
|
// Once it is implemented, however, should work as expected.
|
||||||
|
}
|
||||||
|
|
||||||
|
Hash getRevFromRef(nix::ref<Store> store, const Input & input) const override
|
||||||
|
{
|
||||||
|
// TODO: In the future, when the sourcehut graphql API is implemented for mercurial
|
||||||
|
// and with anonymous access, this method should use it instead.
|
||||||
|
|
||||||
|
auto ref = *input.getRef();
|
||||||
|
|
||||||
|
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
|
||||||
|
auto base_url = fmt("https://%s/%s/%s",
|
||||||
|
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"));
|
||||||
|
|
||||||
|
Headers headers = makeHeadersWithAuthTokens(host);
|
||||||
|
|
||||||
|
std::string ref_uri;
|
||||||
|
if (ref == "HEAD") {
|
||||||
|
auto file = store->toRealPath(
|
||||||
|
downloadFile(store, fmt("%s/HEAD", base_url), "source", false, headers).storePath);
|
||||||
|
std::ifstream is(file);
|
||||||
|
std::string line;
|
||||||
|
getline(is, line);
|
||||||
|
|
||||||
|
auto ref_index = line.find("ref: ");
|
||||||
|
if (ref_index == std::string::npos) {
|
||||||
|
throw BadURL("in '%d', couldn't resolve HEAD ref '%d'", input.to_string(), ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
ref_uri = line.substr(ref_index+5, line.length()-1);
|
||||||
|
} else
|
||||||
|
ref_uri = fmt("refs/(heads|tags)/%s", ref);
|
||||||
|
|
||||||
|
auto file = store->toRealPath(
|
||||||
|
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
|
||||||
|
std::ifstream is(file);
|
||||||
|
|
||||||
|
std::string line;
|
||||||
|
std::string id;
|
||||||
|
while(getline(is, line)) {
|
||||||
|
// Append $ to avoid partial name matches
|
||||||
|
std::regex pattern(fmt("%s$", ref_uri));
|
||||||
|
|
||||||
|
if (std::regex_search(line, pattern)) {
|
||||||
|
id = line.substr(0, line.find('\t'));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(id.empty())
|
||||||
|
throw BadURL("in '%d', couldn't find ref '%d'", input.to_string(), ref);
|
||||||
|
|
||||||
|
auto rev = Hash::parseAny(id, htSHA1);
|
||||||
|
debug("HEAD revision for '%s' is %s", fmt("%s/%s", base_url, ref), rev.gitRev());
|
||||||
|
return rev;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadUrl getDownloadUrl(const Input & input) const override
|
||||||
|
{
|
||||||
|
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
|
||||||
|
auto url = fmt("https://%s/%s/%s/archive/%s.tar.gz",
|
||||||
|
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"),
|
||||||
|
input.getRev()->to_string(Base16, false));
|
||||||
|
|
||||||
|
Headers headers = makeHeadersWithAuthTokens(host);
|
||||||
|
return DownloadUrl { url, headers };
|
||||||
|
}
|
||||||
|
|
||||||
|
void clone(const Input & input, const Path & destDir) override
|
||||||
|
{
|
||||||
|
auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht");
|
||||||
|
Input::fromURL(fmt("git+https://%s/%s/%s",
|
||||||
|
host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo")))
|
||||||
|
.applyOverrides(input.getRef(), input.getRev())
|
||||||
|
.clone(destDir);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
static auto rGitHubInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GitHubInputScheme>()); });
|
static auto rGitHubInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GitHubInputScheme>()); });
|
||||||
static auto rGitLabInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GitLabInputScheme>()); });
|
static auto rGitLabInputScheme = OnStartup([] { registerInputScheme(std::make_unique<GitLabInputScheme>()); });
|
||||||
|
static auto rSourceHutInputScheme = OnStartup([] { registerInputScheme(std::make_unique<SourceHutInputScheme>()); });
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,7 +94,7 @@ struct IndirectInputScheme : InputScheme
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) override
|
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
|
||||||
{
|
{
|
||||||
throw Error("indirect input '%s' cannot be fetched directly", input.to_string());
|
throw Error("indirect input '%s' cannot be fetched directly", input.to_string());
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "url-parts.hh"
|
#include "url-parts.hh"
|
||||||
|
|
||||||
|
#include "fetch-settings.hh"
|
||||||
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
||||||
using namespace std::string_literals;
|
using namespace std::string_literals;
|
||||||
|
@ -26,7 +28,7 @@ static RunOptions hgOptions(const Strings & args)
|
||||||
}
|
}
|
||||||
|
|
||||||
// runProgram wrapper that uses hgOptions instead of stock RunOptions.
|
// runProgram wrapper that uses hgOptions instead of stock RunOptions.
|
||||||
static string runHg(const Strings & args, const std::optional<std::string> & input = {})
|
static std::string runHg(const Strings & args, const std::optional<std::string> & input = {})
|
||||||
{
|
{
|
||||||
RunOptions opts = hgOptions(args);
|
RunOptions opts = hgOptions(args);
|
||||||
opts.input = input;
|
opts.input = input;
|
||||||
|
@ -143,7 +145,7 @@ struct MercurialInputScheme : InputScheme
|
||||||
return {isLocal, isLocal ? url.path : url.base};
|
return {isLocal, isLocal ? url.path : url.base};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Tree, Input> fetch(ref<Store> store, const Input & _input) override
|
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
|
||||||
{
|
{
|
||||||
Input input(_input);
|
Input input(_input);
|
||||||
|
|
||||||
|
@ -165,10 +167,10 @@ struct MercurialInputScheme : InputScheme
|
||||||
/* This is an unclean working tree. So copy all tracked
|
/* This is an unclean working tree. So copy all tracked
|
||||||
files. */
|
files. */
|
||||||
|
|
||||||
if (!settings.allowDirty)
|
if (!fetchSettings.allowDirty)
|
||||||
throw Error("Mercurial tree '%s' is unclean", actualUrl);
|
throw Error("Mercurial tree '%s' is unclean", actualUrl);
|
||||||
|
|
||||||
if (settings.warnDirty)
|
if (fetchSettings.warnDirty)
|
||||||
warn("Mercurial tree '%s' is unclean", actualUrl);
|
warn("Mercurial tree '%s' is unclean", actualUrl);
|
||||||
|
|
||||||
input.attrs.insert_or_assign("ref", chomp(runHg({ "branch", "-R", actualUrl })));
|
input.attrs.insert_or_assign("ref", chomp(runHg({ "branch", "-R", actualUrl })));
|
||||||
|
@ -193,16 +195,13 @@ struct MercurialInputScheme : InputScheme
|
||||||
|
|
||||||
auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
|
auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
|
||||||
|
|
||||||
return {
|
return {std::move(storePath), input};
|
||||||
Tree(store->toRealPath(storePath), std::move(storePath)),
|
|
||||||
input
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!input.getRef()) input.attrs.insert_or_assign("ref", "default");
|
if (!input.getRef()) input.attrs.insert_or_assign("ref", "default");
|
||||||
|
|
||||||
auto getImmutableAttrs = [&]()
|
auto getLockedAttrs = [&]()
|
||||||
{
|
{
|
||||||
return Attrs({
|
return Attrs({
|
||||||
{"type", "hg"},
|
{"type", "hg"},
|
||||||
|
@ -212,32 +211,29 @@ struct MercurialInputScheme : InputScheme
|
||||||
};
|
};
|
||||||
|
|
||||||
auto makeResult = [&](const Attrs & infoAttrs, StorePath && storePath)
|
auto makeResult = [&](const Attrs & infoAttrs, StorePath && storePath)
|
||||||
-> std::pair<Tree, Input>
|
-> std::pair<StorePath, Input>
|
||||||
{
|
{
|
||||||
assert(input.getRev());
|
assert(input.getRev());
|
||||||
assert(!_input.getRev() || _input.getRev() == input.getRev());
|
assert(!_input.getRev() || _input.getRev() == input.getRev());
|
||||||
input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount"));
|
input.attrs.insert_or_assign("revCount", getIntAttr(infoAttrs, "revCount"));
|
||||||
return {
|
return {std::move(storePath), input};
|
||||||
Tree(store->toRealPath(storePath), std::move(storePath)),
|
|
||||||
input
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (input.getRev()) {
|
if (input.getRev()) {
|
||||||
if (auto res = getCache()->lookup(store, getImmutableAttrs()))
|
if (auto res = getCache()->lookup(store, getLockedAttrs()))
|
||||||
return makeResult(res->first, std::move(res->second));
|
return makeResult(res->first, std::move(res->second));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto revOrRef = input.getRev() ? input.getRev()->gitRev() : *input.getRef();
|
auto revOrRef = input.getRev() ? input.getRev()->gitRev() : *input.getRef();
|
||||||
|
|
||||||
Attrs mutableAttrs({
|
Attrs unlockedAttrs({
|
||||||
{"type", "hg"},
|
{"type", "hg"},
|
||||||
{"name", name},
|
{"name", name},
|
||||||
{"url", actualUrl},
|
{"url", actualUrl},
|
||||||
{"ref", *input.getRef()},
|
{"ref", *input.getRef()},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (auto res = getCache()->lookup(store, mutableAttrs)) {
|
if (auto res = getCache()->lookup(store, unlockedAttrs)) {
|
||||||
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), htSHA1);
|
auto rev2 = Hash::parseAny(getStrAttr(res->first, "rev"), htSHA1);
|
||||||
if (!input.getRev() || input.getRev() == rev2) {
|
if (!input.getRev() || input.getRev() == rev2) {
|
||||||
input.attrs.insert_or_assign("rev", rev2.gitRev());
|
input.attrs.insert_or_assign("rev", rev2.gitRev());
|
||||||
|
@ -260,7 +256,7 @@ struct MercurialInputScheme : InputScheme
|
||||||
runHg({ "pull", "-R", cacheDir, "--", actualUrl });
|
runHg({ "pull", "-R", cacheDir, "--", actualUrl });
|
||||||
}
|
}
|
||||||
catch (ExecError & e) {
|
catch (ExecError & e) {
|
||||||
string transJournal = cacheDir + "/.hg/store/journal";
|
auto transJournal = cacheDir + "/.hg/store/journal";
|
||||||
/* hg throws "abandoned transaction" error only if this file exists */
|
/* hg throws "abandoned transaction" error only if this file exists */
|
||||||
if (pathExists(transJournal)) {
|
if (pathExists(transJournal)) {
|
||||||
runHg({ "recover", "-R", cacheDir });
|
runHg({ "recover", "-R", cacheDir });
|
||||||
|
@ -283,7 +279,7 @@ struct MercurialInputScheme : InputScheme
|
||||||
auto revCount = std::stoull(tokens[1]);
|
auto revCount = std::stoull(tokens[1]);
|
||||||
input.attrs.insert_or_assign("ref", tokens[2]);
|
input.attrs.insert_or_assign("ref", tokens[2]);
|
||||||
|
|
||||||
if (auto res = getCache()->lookup(store, getImmutableAttrs()))
|
if (auto res = getCache()->lookup(store, getLockedAttrs()))
|
||||||
return makeResult(res->first, std::move(res->second));
|
return makeResult(res->first, std::move(res->second));
|
||||||
|
|
||||||
Path tmpDir = createTempDir();
|
Path tmpDir = createTempDir();
|
||||||
|
@ -303,14 +299,14 @@ struct MercurialInputScheme : InputScheme
|
||||||
if (!_input.getRev())
|
if (!_input.getRev())
|
||||||
getCache()->add(
|
getCache()->add(
|
||||||
store,
|
store,
|
||||||
mutableAttrs,
|
unlockedAttrs,
|
||||||
infoAttrs,
|
infoAttrs,
|
||||||
storePath,
|
storePath,
|
||||||
false);
|
false);
|
||||||
|
|
||||||
getCache()->add(
|
getCache()->add(
|
||||||
store,
|
store,
|
||||||
getImmutableAttrs(),
|
getLockedAttrs(),
|
||||||
infoAttrs,
|
infoAttrs,
|
||||||
storePath,
|
storePath,
|
||||||
true);
|
true);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#include "fetchers.hh"
|
#include "fetchers.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "archive.hh"
|
||||||
|
|
||||||
namespace nix::fetchers {
|
namespace nix::fetchers {
|
||||||
|
|
||||||
|
@ -80,8 +81,9 @@ struct PathInputScheme : InputScheme
|
||||||
// nothing to do
|
// nothing to do
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) override
|
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & _input) override
|
||||||
{
|
{
|
||||||
|
Input input(_input);
|
||||||
std::string absPath;
|
std::string absPath;
|
||||||
auto path = getStrAttr(input.attrs, "path");
|
auto path = getStrAttr(input.attrs, "path");
|
||||||
|
|
||||||
|
@ -111,14 +113,17 @@ struct PathInputScheme : InputScheme
|
||||||
if (storePath)
|
if (storePath)
|
||||||
store->addTempRoot(*storePath);
|
store->addTempRoot(*storePath);
|
||||||
|
|
||||||
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath))
|
time_t mtime = 0;
|
||||||
|
if (!storePath || storePath->name() != "source" || !store->isValidPath(*storePath)) {
|
||||||
// FIXME: try to substitute storePath.
|
// FIXME: try to substitute storePath.
|
||||||
storePath = store->addToStore("source", absPath);
|
auto src = sinkToSource([&](Sink & sink) {
|
||||||
|
mtime = dumpPathAndGetMtime(absPath, sink, defaultPathFilter);
|
||||||
|
});
|
||||||
|
storePath = store->addToStoreFromDump(*src, "source");
|
||||||
|
}
|
||||||
|
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
|
||||||
|
|
||||||
return {
|
return {std::move(*storePath), input};
|
||||||
Tree(store->toRealPath(*storePath), std::move(*storePath)),
|
|
||||||
input
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,8 @@
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "local-fs-store.hh"
|
#include "local-fs-store.hh"
|
||||||
|
|
||||||
|
#include "fetch-settings.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
namespace nix::fetchers {
|
namespace nix::fetchers {
|
||||||
|
@ -150,7 +152,7 @@ void overrideRegistry(
|
||||||
static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
|
static std::shared_ptr<Registry> getGlobalRegistry(ref<Store> store)
|
||||||
{
|
{
|
||||||
static auto reg = [&]() {
|
static auto reg = [&]() {
|
||||||
auto path = settings.flakeRegistry.get();
|
auto path = fetchSettings.flakeRegistry.get();
|
||||||
|
|
||||||
if (!hasPrefix(path, "/")) {
|
if (!hasPrefix(path, "/")) {
|
||||||
auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath;
|
auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath;
|
||||||
|
|
|
@ -13,7 +13,7 @@ DownloadFileResult downloadFile(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::string & name,
|
const std::string & name,
|
||||||
bool immutable,
|
bool locked,
|
||||||
const Headers & headers)
|
const Headers & headers)
|
||||||
{
|
{
|
||||||
// FIXME: check store
|
// FIXME: check store
|
||||||
|
@ -88,7 +88,7 @@ DownloadFileResult downloadFile(
|
||||||
inAttrs,
|
inAttrs,
|
||||||
infoAttrs,
|
infoAttrs,
|
||||||
*storePath,
|
*storePath,
|
||||||
immutable);
|
locked);
|
||||||
|
|
||||||
if (url != res.effectiveUri)
|
if (url != res.effectiveUri)
|
||||||
getCache()->add(
|
getCache()->add(
|
||||||
|
@ -100,7 +100,7 @@ DownloadFileResult downloadFile(
|
||||||
},
|
},
|
||||||
infoAttrs,
|
infoAttrs,
|
||||||
*storePath,
|
*storePath,
|
||||||
immutable);
|
locked);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
.storePath = std::move(*storePath),
|
.storePath = std::move(*storePath),
|
||||||
|
@ -113,7 +113,7 @@ std::pair<Tree, time_t> downloadTarball(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
const std::string & url,
|
const std::string & url,
|
||||||
const std::string & name,
|
const std::string & name,
|
||||||
bool immutable,
|
bool locked,
|
||||||
const Headers & headers)
|
const Headers & headers)
|
||||||
{
|
{
|
||||||
Attrs inAttrs({
|
Attrs inAttrs({
|
||||||
|
@ -126,11 +126,11 @@ std::pair<Tree, time_t> downloadTarball(
|
||||||
|
|
||||||
if (cached && !cached->expired)
|
if (cached && !cached->expired)
|
||||||
return {
|
return {
|
||||||
Tree(store->toRealPath(cached->storePath), std::move(cached->storePath)),
|
Tree { .actualPath = store->toRealPath(cached->storePath), .storePath = std::move(cached->storePath) },
|
||||||
getIntAttr(cached->infoAttrs, "lastModified")
|
getIntAttr(cached->infoAttrs, "lastModified")
|
||||||
};
|
};
|
||||||
|
|
||||||
auto res = downloadFile(store, url, name, immutable, headers);
|
auto res = downloadFile(store, url, name, locked, headers);
|
||||||
|
|
||||||
std::optional<StorePath> unpackedStorePath;
|
std::optional<StorePath> unpackedStorePath;
|
||||||
time_t lastModified;
|
time_t lastModified;
|
||||||
|
@ -160,10 +160,10 @@ std::pair<Tree, time_t> downloadTarball(
|
||||||
inAttrs,
|
inAttrs,
|
||||||
infoAttrs,
|
infoAttrs,
|
||||||
*unpackedStorePath,
|
*unpackedStorePath,
|
||||||
immutable);
|
locked);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
Tree(store->toRealPath(*unpackedStorePath), std::move(*unpackedStorePath)),
|
Tree { .actualPath = store->toRealPath(*unpackedStorePath), .storePath = std::move(*unpackedStorePath) },
|
||||||
lastModified,
|
lastModified,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ struct TarballInputScheme : InputScheme
|
||||||
|
|
||||||
Input input;
|
Input input;
|
||||||
input.attrs = attrs;
|
input.attrs = attrs;
|
||||||
//input.immutable = (bool) maybeGetStrAttr(input.attrs, "hash");
|
//input.locked = (bool) maybeGetStrAttr(input.attrs, "hash");
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,10 +225,10 @@ struct TarballInputScheme : InputScheme
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) override
|
std::pair<StorePath, Input> fetch(ref<Store> store, const Input & input) override
|
||||||
{
|
{
|
||||||
auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first;
|
auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first;
|
||||||
return {std::move(tree), input};
|
return {std::move(tree.storePath), input};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
MixCommonArgs::MixCommonArgs(const string & programName)
|
MixCommonArgs::MixCommonArgs(const std::string & programName)
|
||||||
: programName(programName)
|
: programName(programName)
|
||||||
{
|
{
|
||||||
addFlag({
|
addFlag({
|
||||||
|
|
|
@ -11,8 +11,8 @@ class MixCommonArgs : public virtual Args
|
||||||
{
|
{
|
||||||
void initialFlagsProcessed() override;
|
void initialFlagsProcessed() override;
|
||||||
public:
|
public:
|
||||||
string programName;
|
std::string programName;
|
||||||
MixCommonArgs(const string & programName);
|
MixCommonArgs(const std::string & programName);
|
||||||
protected:
|
protected:
|
||||||
virtual void pluginsInited() {}
|
virtual void pluginsInited() {}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "globals.hh"
|
#include "globals.hh"
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "gc-store.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
#include "loggers.hh"
|
#include "loggers.hh"
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ void printMissing(ref<Store> store, const StorePathSet & willBuild,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string getArg(const string & opt,
|
std::string getArg(const std::string & opt,
|
||||||
Strings::iterator & i, const Strings::iterator & end)
|
Strings::iterator & i, const Strings::iterator & end)
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
|
@ -322,14 +323,14 @@ void parseCmdLine(int argc, char * * argv,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void parseCmdLine(const string & programName, const Strings & args,
|
void parseCmdLine(const std::string & programName, const Strings & args,
|
||||||
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg)
|
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg)
|
||||||
{
|
{
|
||||||
LegacyArgs(programName, parseArg).parseCmdline(args);
|
LegacyArgs(programName, parseArg).parseCmdline(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void printVersion(const string & programName)
|
void printVersion(const std::string & programName)
|
||||||
{
|
{
|
||||||
std::cout << format("%1% (Nix) %2%") % programName % nixVersion << std::endl;
|
std::cout << format("%1% (Nix) %2%") % programName % nixVersion << std::endl;
|
||||||
if (verbosity > lvlInfo) {
|
if (verbosity > lvlInfo) {
|
||||||
|
@ -352,7 +353,7 @@ void printVersion(const string & programName)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void showManPage(const string & name)
|
void showManPage(const std::string & name)
|
||||||
{
|
{
|
||||||
restoreProcessContext();
|
restoreProcessContext();
|
||||||
setenv("MANPATH", settings.nixManDir.c_str(), 1);
|
setenv("MANPATH", settings.nixManDir.c_str(), 1);
|
||||||
|
@ -361,13 +362,13 @@ void showManPage(const string & name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int handleExceptions(const string & programName, std::function<void()> fun)
|
int handleExceptions(const std::string & programName, std::function<void()> fun)
|
||||||
{
|
{
|
||||||
ReceiveInterrupts receiveInterrupts; // FIXME: need better place for this
|
ReceiveInterrupts receiveInterrupts; // FIXME: need better place for this
|
||||||
|
|
||||||
ErrorInfo::programName = baseNameOf(programName);
|
ErrorInfo::programName = baseNameOf(programName);
|
||||||
|
|
||||||
string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
fun();
|
fun();
|
||||||
|
@ -407,7 +408,7 @@ RunPager::RunPager()
|
||||||
if (!isatty(STDOUT_FILENO)) return;
|
if (!isatty(STDOUT_FILENO)) return;
|
||||||
char * pager = getenv("NIX_PAGER");
|
char * pager = getenv("NIX_PAGER");
|
||||||
if (!pager) pager = getenv("PAGER");
|
if (!pager) pager = getenv("PAGER");
|
||||||
if (pager && ((string) pager == "" || (string) pager == "cat")) return;
|
if (pager && ((std::string) pager == "" || (std::string) pager == "cat")) return;
|
||||||
|
|
||||||
Pipe toPager;
|
Pipe toPager;
|
||||||
toPager.create();
|
toPager.create();
|
||||||
|
|
|
@ -22,7 +22,7 @@ public:
|
||||||
virtual ~Exit();
|
virtual ~Exit();
|
||||||
};
|
};
|
||||||
|
|
||||||
int handleExceptions(const string & programName, std::function<void()> fun);
|
int handleExceptions(const std::string & programName, std::function<void()> fun);
|
||||||
|
|
||||||
/* Don't forget to call initPlugins() after settings are initialized! */
|
/* Don't forget to call initPlugins() after settings are initialized! */
|
||||||
void initNix();
|
void initNix();
|
||||||
|
@ -30,10 +30,10 @@ void initNix();
|
||||||
void parseCmdLine(int argc, char * * argv,
|
void parseCmdLine(int argc, char * * argv,
|
||||||
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg);
|
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg);
|
||||||
|
|
||||||
void parseCmdLine(const string & programName, const Strings & args,
|
void parseCmdLine(const std::string & programName, const Strings & args,
|
||||||
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg);
|
std::function<bool(Strings::iterator & arg, const Strings::iterator & end)> parseArg);
|
||||||
|
|
||||||
void printVersion(const string & programName);
|
void printVersion(const std::string & programName);
|
||||||
|
|
||||||
/* Ugh. No better place to put this. */
|
/* Ugh. No better place to put this. */
|
||||||
void printGCWarning();
|
void printGCWarning();
|
||||||
|
@ -50,10 +50,10 @@ void printMissing(ref<Store> store, const StorePathSet & willBuild,
|
||||||
const StorePathSet & willSubstitute, const StorePathSet & unknown,
|
const StorePathSet & willSubstitute, const StorePathSet & unknown,
|
||||||
uint64_t downloadSize, uint64_t narSize, Verbosity lvl = lvlInfo);
|
uint64_t downloadSize, uint64_t narSize, Verbosity lvl = lvlInfo);
|
||||||
|
|
||||||
string getArg(const string & opt,
|
std::string getArg(const std::string & opt,
|
||||||
Strings::iterator & i, const Strings::iterator & end);
|
Strings::iterator & i, const Strings::iterator & end);
|
||||||
|
|
||||||
template<class N> N getIntArg(const string & opt,
|
template<class N> N getIntArg(const std::string & opt,
|
||||||
Strings::iterator & i, const Strings::iterator & end, bool allowUnit)
|
Strings::iterator & i, const Strings::iterator & end, bool allowUnit)
|
||||||
{
|
{
|
||||||
++i;
|
++i;
|
||||||
|
@ -76,7 +76,7 @@ struct LegacyArgs : public MixCommonArgs
|
||||||
|
|
||||||
|
|
||||||
/* Show the manual page for the specified program. */
|
/* Show the manual page for the specified program. */
|
||||||
void showManPage(const string & name);
|
void showManPage(const std::string & name);
|
||||||
|
|
||||||
/* The constructor of this class starts a pager if stdout is a
|
/* The constructor of this class starts a pager if stdout is a
|
||||||
terminal and $PAGER is set. Stdout is redirected to the pager. */
|
terminal and $PAGER is set. Stdout is redirected to the pager. */
|
||||||
|
@ -96,7 +96,7 @@ extern volatile ::sig_atomic_t blockInt;
|
||||||
|
|
||||||
/* GC helpers. */
|
/* GC helpers. */
|
||||||
|
|
||||||
string showBytes(uint64_t bytes);
|
std::string showBytes(uint64_t bytes);
|
||||||
|
|
||||||
struct GCResults;
|
struct GCResults;
|
||||||
|
|
||||||
|
|
|
@ -307,7 +307,7 @@ void BinaryCacheStore::addToStore(const ValidPathInfo & info, Source & narSource
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, const string & name,
|
StorePath BinaryCacheStore::addToStoreFromDump(Source & dump, std::string_view name,
|
||||||
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references)
|
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references)
|
||||||
{
|
{
|
||||||
if (method != FileIngestionMethod::Recursive || hashAlgo != htSHA256)
|
if (method != FileIngestionMethod::Recursive || hashAlgo != htSHA256)
|
||||||
|
@ -385,8 +385,14 @@ void BinaryCacheStore::queryPathInfoUncached(const StorePath & storePath,
|
||||||
}});
|
}});
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePath BinaryCacheStore::addToStore(const string & name, const Path & srcPath,
|
StorePath BinaryCacheStore::addToStore(
|
||||||
FileIngestionMethod method, HashType hashAlgo, PathFilter & filter, RepairFlag repair, const StorePathSet & references)
|
std::string_view name,
|
||||||
|
const Path & srcPath,
|
||||||
|
FileIngestionMethod method,
|
||||||
|
HashType hashAlgo,
|
||||||
|
PathFilter & filter,
|
||||||
|
RepairFlag repair,
|
||||||
|
const StorePathSet & references)
|
||||||
{
|
{
|
||||||
/* FIXME: Make BinaryCacheStore::addToStoreCommon support
|
/* FIXME: Make BinaryCacheStore::addToStoreCommon support
|
||||||
non-recursive+sha256 so we can just use the default
|
non-recursive+sha256 so we can just use the default
|
||||||
|
@ -418,8 +424,11 @@ StorePath BinaryCacheStore::addToStore(const string & name, const Path & srcPath
|
||||||
})->path;
|
})->path;
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePath BinaryCacheStore::addTextToStore(const string & name, const string & s,
|
StorePath BinaryCacheStore::addTextToStore(
|
||||||
const StorePathSet & references, RepairFlag repair)
|
std::string_view name,
|
||||||
|
std::string_view s,
|
||||||
|
const StorePathSet & references,
|
||||||
|
RepairFlag repair)
|
||||||
{
|
{
|
||||||
auto textHash = hashString(htSHA256, s);
|
auto textHash = hashString(htSHA256, s);
|
||||||
auto path = makeTextPath(name, textHash, references);
|
auto path = makeTextPath(name, textHash, references);
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "crypto.hh"
|
#include "crypto.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "log-store.hh"
|
||||||
|
|
||||||
#include "pool.hh"
|
#include "pool.hh"
|
||||||
|
|
||||||
|
@ -28,7 +29,9 @@ struct BinaryCacheStoreConfig : virtual StoreConfig
|
||||||
"other than -1 which we reserve to indicate Nix defaults should be used"};
|
"other than -1 which we reserve to indicate Nix defaults should be used"};
|
||||||
};
|
};
|
||||||
|
|
||||||
class BinaryCacheStore : public virtual BinaryCacheStoreConfig, public virtual Store
|
class BinaryCacheStore : public virtual BinaryCacheStoreConfig,
|
||||||
|
public virtual Store,
|
||||||
|
public virtual LogStore
|
||||||
{
|
{
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -98,15 +101,23 @@ public:
|
||||||
void addToStore(const ValidPathInfo & info, Source & narSource,
|
void addToStore(const ValidPathInfo & info, Source & narSource,
|
||||||
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
RepairFlag repair, CheckSigsFlag checkSigs) override;
|
||||||
|
|
||||||
StorePath addToStoreFromDump(Source & dump, const string & name,
|
StorePath addToStoreFromDump(Source & dump, std::string_view name,
|
||||||
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references ) override;
|
FileIngestionMethod method, HashType hashAlgo, RepairFlag repair, const StorePathSet & references) override;
|
||||||
|
|
||||||
StorePath addToStore(const string & name, const Path & srcPath,
|
StorePath addToStore(
|
||||||
FileIngestionMethod method, HashType hashAlgo,
|
std::string_view name,
|
||||||
PathFilter & filter, RepairFlag repair, const StorePathSet & references) override;
|
const Path & srcPath,
|
||||||
|
FileIngestionMethod method,
|
||||||
|
HashType hashAlgo,
|
||||||
|
PathFilter & filter,
|
||||||
|
RepairFlag repair,
|
||||||
|
const StorePathSet & references) override;
|
||||||
|
|
||||||
StorePath addTextToStore(const string & name, const string & s,
|
StorePath addTextToStore(
|
||||||
const StorePathSet & references, RepairFlag repair) override;
|
std::string_view name,
|
||||||
|
std::string_view s,
|
||||||
|
const StorePathSet & references,
|
||||||
|
RepairFlag repair) override;
|
||||||
|
|
||||||
void registerDrvOutput(const Realisation & info) override;
|
void registerDrvOutput(const Realisation & info) override;
|
||||||
|
|
||||||
|
|
90
src/libstore/build-result.hh
Normal file
90
src/libstore/build-result.hh
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "realisation.hh"
|
||||||
|
#include "derived-path.hh"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
struct BuildResult
|
||||||
|
{
|
||||||
|
/* Note: don't remove status codes, and only add new status codes
|
||||||
|
at the end of the list, to prevent client/server
|
||||||
|
incompatibilities in the nix-store --serve protocol. */
|
||||||
|
enum Status {
|
||||||
|
Built = 0,
|
||||||
|
Substituted,
|
||||||
|
AlreadyValid,
|
||||||
|
PermanentFailure,
|
||||||
|
InputRejected,
|
||||||
|
OutputRejected,
|
||||||
|
TransientFailure, // possibly transient
|
||||||
|
CachedFailure, // no longer used
|
||||||
|
TimedOut,
|
||||||
|
MiscFailure,
|
||||||
|
DependencyFailed,
|
||||||
|
LogLimitExceeded,
|
||||||
|
NotDeterministic,
|
||||||
|
ResolvesToAlreadyValid,
|
||||||
|
NoSubstituters,
|
||||||
|
} status = MiscFailure;
|
||||||
|
std::string errorMsg;
|
||||||
|
|
||||||
|
std::string toString() const {
|
||||||
|
auto strStatus = [&]() {
|
||||||
|
switch (status) {
|
||||||
|
case Built: return "Built";
|
||||||
|
case Substituted: return "Substituted";
|
||||||
|
case AlreadyValid: return "AlreadyValid";
|
||||||
|
case PermanentFailure: return "PermanentFailure";
|
||||||
|
case InputRejected: return "InputRejected";
|
||||||
|
case OutputRejected: return "OutputRejected";
|
||||||
|
case TransientFailure: return "TransientFailure";
|
||||||
|
case CachedFailure: return "CachedFailure";
|
||||||
|
case TimedOut: return "TimedOut";
|
||||||
|
case MiscFailure: return "MiscFailure";
|
||||||
|
case DependencyFailed: return "DependencyFailed";
|
||||||
|
case LogLimitExceeded: return "LogLimitExceeded";
|
||||||
|
case NotDeterministic: return "NotDeterministic";
|
||||||
|
case ResolvesToAlreadyValid: return "ResolvesToAlreadyValid";
|
||||||
|
default: return "Unknown";
|
||||||
|
};
|
||||||
|
}();
|
||||||
|
return strStatus + ((errorMsg == "") ? "" : " : " + errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* How many times this build was performed. */
|
||||||
|
unsigned int timesBuilt = 0;
|
||||||
|
|
||||||
|
/* If timesBuilt > 1, whether some builds did not produce the same
|
||||||
|
result. (Note that 'isNonDeterministic = false' does not mean
|
||||||
|
the build is deterministic, just that we don't have evidence of
|
||||||
|
non-determinism.) */
|
||||||
|
bool isNonDeterministic = false;
|
||||||
|
|
||||||
|
/* The derivation we built or the store path we substituted. */
|
||||||
|
DerivedPath path;
|
||||||
|
|
||||||
|
/* For derivations, a mapping from the names of the wanted outputs
|
||||||
|
to actual paths. */
|
||||||
|
DrvOutputs builtOutputs;
|
||||||
|
|
||||||
|
/* The start/stop times of the build (or one of the rounds, if it
|
||||||
|
was repeated). */
|
||||||
|
time_t startTime = 0, stopTime = 0;
|
||||||
|
|
||||||
|
bool success()
|
||||||
|
{
|
||||||
|
return status == Built || status == Substituted || status == AlreadyValid || status == ResolvesToAlreadyValid;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rethrow()
|
||||||
|
{
|
||||||
|
throw Error("%s", errorMsg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -66,7 +66,7 @@ namespace nix {
|
||||||
|
|
||||||
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||||
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
|
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||||
: Goal(worker)
|
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
|
||||||
, useDerivation(true)
|
, useDerivation(true)
|
||||||
, drvPath(drvPath)
|
, drvPath(drvPath)
|
||||||
, wantedOutputs(wantedOutputs)
|
, wantedOutputs(wantedOutputs)
|
||||||
|
@ -85,7 +85,7 @@ DerivationGoal::DerivationGoal(const StorePath & drvPath,
|
||||||
|
|
||||||
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
DerivationGoal::DerivationGoal(const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
|
const StringSet & wantedOutputs, Worker & worker, BuildMode buildMode)
|
||||||
: Goal(worker)
|
: Goal(worker, DerivedPath::Built { .drvPath = drvPath, .outputs = wantedOutputs })
|
||||||
, useDerivation(false)
|
, useDerivation(false)
|
||||||
, drvPath(drvPath)
|
, drvPath(drvPath)
|
||||||
, wantedOutputs(wantedOutputs)
|
, wantedOutputs(wantedOutputs)
|
||||||
|
@ -116,7 +116,7 @@ DerivationGoal::~DerivationGoal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
string DerivationGoal::key()
|
std::string DerivationGoal::key()
|
||||||
{
|
{
|
||||||
/* Ensure that derivations get built in order of their name,
|
/* Ensure that derivations get built in order of their name,
|
||||||
i.e. a derivation named "aardvark" always comes before
|
i.e. a derivation named "aardvark" always comes before
|
||||||
|
@ -135,7 +135,7 @@ void DerivationGoal::killChild()
|
||||||
void DerivationGoal::timedOut(Error && ex)
|
void DerivationGoal::timedOut(Error && ex)
|
||||||
{
|
{
|
||||||
killChild();
|
killChild();
|
||||||
done(BuildResult::TimedOut, ex);
|
done(BuildResult::TimedOut, {}, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -182,7 +182,7 @@ void DerivationGoal::loadDerivation()
|
||||||
trace("loading derivation");
|
trace("loading derivation");
|
||||||
|
|
||||||
if (nrFailed != 0) {
|
if (nrFailed != 0) {
|
||||||
done(BuildResult::MiscFailure, Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)));
|
done(BuildResult::MiscFailure, {}, Error("cannot build missing derivation '%s'", worker.store.printStorePath(drvPath)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,10 +204,33 @@ void DerivationGoal::haveDerivation()
|
||||||
{
|
{
|
||||||
trace("have derivation");
|
trace("have derivation");
|
||||||
|
|
||||||
if (drv->type() == DerivationType::CAFloating)
|
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
|
||||||
|
|
||||||
|
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)
|
||||||
|
@ -215,34 +238,23 @@ void DerivationGoal::haveDerivation()
|
||||||
|
|
||||||
auto outputHashes = staticOutputHashes(worker.evalStore, *drv);
|
auto outputHashes = staticOutputHashes(worker.evalStore, *drv);
|
||||||
for (auto & [outputName, outputHash] : outputHashes)
|
for (auto & [outputName, outputHash] : outputHashes)
|
||||||
initialOutputs.insert({
|
initialOutputs.insert({
|
||||||
outputName,
|
outputName,
|
||||||
InitialOutput{
|
InitialOutput {
|
||||||
.wanted = true, // Will be refined later
|
.wanted = true, // Will be refined later
|
||||||
.outputHash = outputHash
|
.outputHash = outputHash
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
/* Check what outputs paths are not already valid. */
|
/* Check what outputs paths are not already valid. */
|
||||||
checkPathValidity();
|
auto [allValid, validOutputs] = checkPathValidity();
|
||||||
bool allValid = true;
|
|
||||||
for (auto & [_, status] : initialOutputs) {
|
|
||||||
if (!status.wanted) continue;
|
|
||||||
if (!status.known || !status.known->isValid()) {
|
|
||||||
allValid = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* If they are all valid, then we're done. */
|
/* If they are all valid, then we're done. */
|
||||||
if (allValid && buildMode == bmNormal) {
|
if (allValid && buildMode == bmNormal) {
|
||||||
done(BuildResult::AlreadyValid);
|
done(BuildResult::AlreadyValid, std::move(validOutputs));
|
||||||
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. */
|
||||||
|
@ -276,8 +288,10 @@ 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 ",
|
||||||
worker.store.printStorePath(drvPath)));
|
worker.store.printStorePath(drvPath)));
|
||||||
return;
|
return;
|
||||||
|
@ -301,23 +315,17 @@ void DerivationGoal::outputsSubstitutionTried()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkPathValidity();
|
auto [allValid, validOutputs] = checkPathValidity();
|
||||||
size_t nrInvalid = 0;
|
|
||||||
for (auto & [_, status] : initialOutputs) {
|
|
||||||
if (!status.wanted) continue;
|
|
||||||
if (!status.known || !status.known->isValid())
|
|
||||||
nrInvalid++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (buildMode == bmNormal && nrInvalid == 0) {
|
if (buildMode == bmNormal && allValid) {
|
||||||
done(BuildResult::Substituted);
|
done(BuildResult::Substituted, std::move(validOutputs));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (buildMode == bmRepair && nrInvalid == 0) {
|
if (buildMode == bmRepair && allValid) {
|
||||||
repairClosure();
|
repairClosure();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (buildMode == bmCheck && nrInvalid > 0)
|
if (buildMode == bmCheck && !allValid)
|
||||||
throw Error("some outputs of '%s' are not valid, so checking is not possible",
|
throw Error("some outputs of '%s' are not valid, so checking is not possible",
|
||||||
worker.store.printStorePath(drvPath));
|
worker.store.printStorePath(drvPath));
|
||||||
|
|
||||||
|
@ -325,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. */
|
||||||
|
@ -364,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
|
||||||
|
@ -409,7 +428,7 @@ void DerivationGoal::repairClosure()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waitees.empty()) {
|
if (waitees.empty()) {
|
||||||
done(BuildResult::AlreadyValid);
|
done(BuildResult::AlreadyValid, assertPathValidity());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -423,7 +442,7 @@ void DerivationGoal::closureRepaired()
|
||||||
if (nrFailed > 0)
|
if (nrFailed > 0)
|
||||||
throw Error("some paths in the output closure of derivation '%s' could not be repaired",
|
throw Error("some paths in the output closure of derivation '%s' could not be repaired",
|
||||||
worker.store.printStorePath(drvPath));
|
worker.store.printStorePath(drvPath));
|
||||||
done(BuildResult::AlreadyValid);
|
done(BuildResult::AlreadyValid, assertPathValidity());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -434,13 +453,14 @@ void DerivationGoal::inputsRealised()
|
||||||
if (nrFailed != 0) {
|
if (nrFailed != 0) {
|
||||||
if (!useDerivation)
|
if (!useDerivation)
|
||||||
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
|
throw Error("some dependencies of '%s' are missing", worker.store.printStorePath(drvPath));
|
||||||
done(BuildResult::DependencyFailed, Error(
|
done(BuildResult::DependencyFailed, {}, Error(
|
||||||
"%s dependencies of derivation '%s' failed to build",
|
"%s dependencies of derivation '%s' failed to build",
|
||||||
nrFailed, worker.store.printStorePath(drvPath)));
|
nrFailed, worker.store.printStorePath(drvPath)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retrySubstitution) {
|
if (retrySubstitution && !retriedSubstitution) {
|
||||||
|
retriedSubstitution = true;
|
||||||
haveDerivation();
|
haveDerivation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -454,19 +474,40 @@ void DerivationGoal::inputsRealised()
|
||||||
if (useDerivation) {
|
if (useDerivation) {
|
||||||
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
|
auto & fullDrv = *dynamic_cast<Derivation *>(drv.get());
|
||||||
|
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) &&
|
auto drvType = fullDrv.type();
|
||||||
((!fullDrv.inputDrvs.empty() && derivationIsCA(fullDrv.type()))
|
bool resolveDrv = std::visit(overloaded {
|
||||||
|| fullDrv.type() == DerivationType::DeferredInputAddressed)) {
|
[&](const DerivationType::InputAddressed & ia) {
|
||||||
|
/* must resolve if deferred. */
|
||||||
|
return ia.deferred;
|
||||||
|
},
|
||||||
|
[&](const DerivationType::ContentAddressed & ca) {
|
||||||
|
return !fullDrv.inputDrvs.empty() && (
|
||||||
|
ca.fixed
|
||||||
|
/* Can optionally resolve if fixed, which is good
|
||||||
|
for avoiding unnecessary rebuilds. */
|
||||||
|
? settings.isExperimentalFeatureEnabled(Xp::CaDerivations)
|
||||||
|
/* Must resolve if floating and there are any inputs
|
||||||
|
drvs. */
|
||||||
|
: true);
|
||||||
|
},
|
||||||
|
[&](const DerivationType::Impure &) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}, drvType.raw());
|
||||||
|
|
||||||
|
if (resolveDrv && !fullDrv.inputDrvs.empty()) {
|
||||||
|
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,
|
||||||
|
@ -487,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));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,7 +548,7 @@ void DerivationGoal::inputsRealised()
|
||||||
|
|
||||||
/* Don't repeat fixed-output derivations since they're already
|
/* Don't repeat fixed-output derivations since they're already
|
||||||
verified by their output hash.*/
|
verified by their output hash.*/
|
||||||
nrRounds = derivationIsFixed(derivationType) ? 1 : settings.buildRepeat + 1;
|
nrRounds = derivationType.isFixed() ? 1 : settings.buildRepeat + 1;
|
||||||
|
|
||||||
/* Okay, try to build. Note that here we don't wait for a build
|
/* Okay, try to build. Note that here we don't wait for a build
|
||||||
slot to become available, since we don't need one if there is a
|
slot to become available, since we don't need one if there is a
|
||||||
|
@ -523,10 +556,11 @@ void DerivationGoal::inputsRealised()
|
||||||
state = &DerivationGoal::tryToBuild;
|
state = &DerivationGoal::tryToBuild;
|
||||||
worker.wakeUp(shared_from_this());
|
worker.wakeUp(shared_from_this());
|
||||||
|
|
||||||
result = BuildResult();
|
buildResult = BuildResult { .path = buildResult.path };
|
||||||
}
|
}
|
||||||
|
|
||||||
void DerivationGoal::started() {
|
void DerivationGoal::started()
|
||||||
|
{
|
||||||
auto msg = fmt(
|
auto msg = fmt(
|
||||||
buildMode == bmRepair ? "repairing outputs of '%s'" :
|
buildMode == bmRepair ? "repairing outputs of '%s'" :
|
||||||
buildMode == bmCheck ? "checking outputs of '%s'" :
|
buildMode == bmCheck ? "checking outputs of '%s'" :
|
||||||
|
@ -588,19 +622,12 @@ void DerivationGoal::tryToBuild()
|
||||||
omitted, but that would be less efficient.) Note that since we
|
omitted, but that would be less efficient.) Note that since we
|
||||||
now hold the locks on the output paths, no other process can
|
now hold the locks on the output paths, no other process can
|
||||||
build this derivation, so no further checks are necessary. */
|
build this derivation, so no further checks are necessary. */
|
||||||
checkPathValidity();
|
auto [allValid, validOutputs] = checkPathValidity();
|
||||||
bool allValid = true;
|
|
||||||
for (auto & [_, status] : initialOutputs) {
|
|
||||||
if (!status.wanted) continue;
|
|
||||||
if (!status.known || !status.known->isValid()) {
|
|
||||||
allValid = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (buildMode != bmCheck && allValid) {
|
if (buildMode != bmCheck && allValid) {
|
||||||
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
|
debug("skipping build of derivation '%s', someone beat us to it", worker.store.printStorePath(drvPath));
|
||||||
outputLocks.setDeletion(true);
|
outputLocks.setDeletion(true);
|
||||||
done(BuildResult::AlreadyValid);
|
done(BuildResult::AlreadyValid, std::move(validOutputs));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -626,7 +653,7 @@ void DerivationGoal::tryToBuild()
|
||||||
/* Yes, it has started doing so. Wait until we get
|
/* Yes, it has started doing so. Wait until we get
|
||||||
EOF from the hook. */
|
EOF from the hook. */
|
||||||
actLock.reset();
|
actLock.reset();
|
||||||
result.startTime = time(0); // inexact
|
buildResult.startTime = time(0); // inexact
|
||||||
state = &DerivationGoal::buildDone;
|
state = &DerivationGoal::buildDone;
|
||||||
started();
|
started();
|
||||||
return;
|
return;
|
||||||
|
@ -830,8 +857,8 @@ void DerivationGoal::buildDone()
|
||||||
|
|
||||||
debug("builder process for '%s' finished", worker.store.printStorePath(drvPath));
|
debug("builder process for '%s' finished", worker.store.printStorePath(drvPath));
|
||||||
|
|
||||||
result.timesBuilt++;
|
buildResult.timesBuilt++;
|
||||||
result.stopTime = time(0);
|
buildResult.stopTime = time(0);
|
||||||
|
|
||||||
/* So the child is gone now. */
|
/* So the child is gone now. */
|
||||||
worker.childTerminated(this);
|
worker.childTerminated(this);
|
||||||
|
@ -876,11 +903,11 @@ void DerivationGoal::buildDone()
|
||||||
|
|
||||||
/* Compute the FS closure of the outputs and register them as
|
/* Compute the FS closure of the outputs and register them as
|
||||||
being valid. */
|
being valid. */
|
||||||
registerOutputs();
|
auto builtOutputs = registerOutputs();
|
||||||
|
|
||||||
StorePathSet outputPaths;
|
StorePathSet outputPaths;
|
||||||
for (auto & [_, path] : finalOutputs)
|
for (auto & [_, output] : buildResult.builtOutputs)
|
||||||
outputPaths.insert(path);
|
outputPaths.insert(output.outPath);
|
||||||
runPostBuildHook(
|
runPostBuildHook(
|
||||||
worker.store,
|
worker.store,
|
||||||
*logger,
|
*logger,
|
||||||
|
@ -890,7 +917,7 @@ void DerivationGoal::buildDone()
|
||||||
|
|
||||||
if (buildMode == bmCheck) {
|
if (buildMode == bmCheck) {
|
||||||
cleanupPostOutputsRegisteredModeCheck();
|
cleanupPostOutputsRegisteredModeCheck();
|
||||||
done(BuildResult::Built);
|
done(BuildResult::Built, std::move(builtOutputs));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -911,6 +938,8 @@ void DerivationGoal::buildDone()
|
||||||
outputLocks.setDeletion(true);
|
outputLocks.setDeletion(true);
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
|
|
||||||
|
done(BuildResult::Built, std::move(builtOutputs));
|
||||||
|
|
||||||
} catch (BuildError & e) {
|
} catch (BuildError & e) {
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
|
|
||||||
|
@ -926,71 +955,66 @@ 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 :
|
||||||
derivationIsImpure(derivationType) || diskFull ? BuildResult::TransientFailure :
|
!derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure :
|
||||||
BuildResult::PermanentFailure;
|
BuildResult::PermanentFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
done(st, e);
|
done(st, {}, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
done(BuildResult::Built);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
DrvOutputs builtOutputs;
|
||||||
|
|
||||||
StorePathSet outputPaths;
|
if (resolvedResult.success()) {
|
||||||
|
auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv);
|
||||||
|
|
||||||
// `wantedOutputs` might be empty, which means “all the outputs”
|
StorePathSet outputPaths;
|
||||||
auto realWantedOutputs = wantedOutputs;
|
|
||||||
if (realWantedOutputs.empty())
|
|
||||||
realWantedOutputs = resolvedDrv.outputNames();
|
|
||||||
|
|
||||||
for (auto & wantedOutput : realWantedOutputs) {
|
// `wantedOutputs` might be empty, which means “all the outputs”
|
||||||
assert(initialOutputs.count(wantedOutput) != 0);
|
auto realWantedOutputs = wantedOutputs;
|
||||||
assert(resolvedHashes.count(wantedOutput) != 0);
|
if (realWantedOutputs.empty())
|
||||||
auto realisation = worker.store.queryRealisation(
|
realWantedOutputs = resolvedDrv.outputNames();
|
||||||
DrvOutput{resolvedHashes.at(wantedOutput), wantedOutput}
|
|
||||||
);
|
for (auto & wantedOutput : realWantedOutputs) {
|
||||||
// We've just built it, but maybe the build failed, in which case the
|
assert(initialOutputs.count(wantedOutput) != 0);
|
||||||
// realisation won't be there
|
assert(resolvedHashes.count(wantedOutput) != 0);
|
||||||
if (realisation) {
|
auto realisation = resolvedResult.builtOutputs.at(
|
||||||
auto newRealisation = *realisation;
|
DrvOutput { resolvedHashes.at(wantedOutput), wantedOutput });
|
||||||
newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput};
|
if (drv->type().isPure()) {
|
||||||
newRealisation.signatures.clear();
|
auto newRealisation = realisation;
|
||||||
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath);
|
newRealisation.id = DrvOutput { initialOutputs.at(wantedOutput).outputHash, wantedOutput };
|
||||||
signRealisation(newRealisation);
|
newRealisation.signatures.clear();
|
||||||
worker.store.registerDrvOutput(newRealisation);
|
if (!drv->type().isFixed())
|
||||||
outputPaths.insert(realisation->outPath);
|
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath);
|
||||||
} else {
|
signRealisation(newRealisation);
|
||||||
// If we don't have a realisation, then it must mean that something
|
worker.store.registerDrvOutput(newRealisation);
|
||||||
// failed when building the resolved drv
|
}
|
||||||
assert(!result.success());
|
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 = [&]() {
|
done(status, std::move(builtOutputs));
|
||||||
auto resolvedResult = resolvedDrvGoal->getResult();
|
|
||||||
switch (resolvedResult.status) {
|
|
||||||
case BuildResult::AlreadyValid:
|
|
||||||
return BuildResult::ResolvesToAlreadyValid;
|
|
||||||
default:
|
|
||||||
return resolvedResult.status;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
||||||
done(status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HookReply DerivationGoal::tryBuildHook()
|
HookReply DerivationGoal::tryBuildHook()
|
||||||
|
@ -1013,7 +1037,7 @@ HookReply DerivationGoal::tryBuildHook()
|
||||||
|
|
||||||
/* Read the first line of input, which should be a word indicating
|
/* Read the first line of input, which should be a word indicating
|
||||||
whether the hook wishes to perform the build. */
|
whether the hook wishes to perform the build. */
|
||||||
string reply;
|
std::string reply;
|
||||||
while (true) {
|
while (true) {
|
||||||
auto s = [&]() {
|
auto s = [&]() {
|
||||||
try {
|
try {
|
||||||
|
@ -1025,8 +1049,8 @@ HookReply DerivationGoal::tryBuildHook()
|
||||||
}();
|
}();
|
||||||
if (handleJSONLogMessage(s, worker.act, worker.hook->activities, true))
|
if (handleJSONLogMessage(s, worker.act, worker.hook->activities, true))
|
||||||
;
|
;
|
||||||
else if (string(s, 0, 2) == "# ") {
|
else if (s.substr(0, 2) == "# ") {
|
||||||
reply = string(s, 2);
|
reply = s.substr(2);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -1091,7 +1115,7 @@ HookReply DerivationGoal::tryBuildHook()
|
||||||
/* Create the log file and pipe. */
|
/* Create the log file and pipe. */
|
||||||
Path logFile = openLogFile();
|
Path logFile = openLogFile();
|
||||||
|
|
||||||
set<int> fds;
|
std::set<int> fds;
|
||||||
fds.insert(hook->fromHook.readSide.get());
|
fds.insert(hook->fromHook.readSide.get());
|
||||||
fds.insert(hook->builderOut.readSide.get());
|
fds.insert(hook->builderOut.readSide.get());
|
||||||
worker.childStarted(shared_from_this(), fds, false, false);
|
worker.childStarted(shared_from_this(), fds, false, false);
|
||||||
|
@ -1100,7 +1124,7 @@ HookReply DerivationGoal::tryBuildHook()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::registerOutputs()
|
DrvOutputs DerivationGoal::registerOutputs()
|
||||||
{
|
{
|
||||||
/* When using a build hook, the build hook can register the output
|
/* When using a build hook, the build hook can register the output
|
||||||
as valid (by doing `nix-store --import'). If so we don't have
|
as valid (by doing `nix-store --import'). If so we don't have
|
||||||
|
@ -1109,21 +1133,7 @@ void DerivationGoal::registerOutputs()
|
||||||
We can only early return when the outputs are known a priori. For
|
We can only early return when the outputs are known a priori. For
|
||||||
floating content-addressed derivations this isn't the case.
|
floating content-addressed derivations this isn't the case.
|
||||||
*/
|
*/
|
||||||
for (auto & [outputName, optOutputPath] : worker.store.queryPartialDerivationOutputMap(drvPath)) {
|
return assertPathValidity();
|
||||||
if (!wantOutput(outputName, wantedOutputs))
|
|
||||||
continue;
|
|
||||||
if (!optOutputPath)
|
|
||||||
throw BuildError(
|
|
||||||
"output '%s' from derivation '%s' does not have a known output path",
|
|
||||||
outputName, worker.store.printStorePath(drvPath));
|
|
||||||
auto & outputPath = *optOutputPath;
|
|
||||||
if (!worker.store.isValidPath(outputPath))
|
|
||||||
throw BuildError(
|
|
||||||
"output '%s' from derivation '%s' is supposed to be at '%s' but that path is not valid",
|
|
||||||
outputName, worker.store.printStorePath(drvPath), worker.store.printStorePath(outputPath));
|
|
||||||
|
|
||||||
finalOutputs.insert_or_assign(outputName, outputPath);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Path DerivationGoal::openLogFile()
|
Path DerivationGoal::openLogFile()
|
||||||
|
@ -1140,10 +1150,10 @@ Path DerivationGoal::openLogFile()
|
||||||
logDir = localStore->logDir;
|
logDir = localStore->logDir;
|
||||||
else
|
else
|
||||||
logDir = settings.nixLogDir;
|
logDir = settings.nixLogDir;
|
||||||
Path dir = fmt("%s/%s/%s/", logDir, LocalFSStore::drvsLogDir, string(baseName, 0, 2));
|
Path dir = fmt("%s/%s/%s/", logDir, LocalFSStore::drvsLogDir, baseName.substr(0, 2));
|
||||||
createDirs(dir);
|
createDirs(dir);
|
||||||
|
|
||||||
Path logFileName = fmt("%s/%s%s", dir, string(baseName, 2),
|
Path logFileName = fmt("%s/%s%s", dir, baseName.substr(2),
|
||||||
settings.compressLog ? ".bz2" : "");
|
settings.compressLog ? ".bz2" : "");
|
||||||
|
|
||||||
fdLogFile = open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666);
|
fdLogFile = open(logFileName.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0666);
|
||||||
|
@ -1175,16 +1185,17 @@ bool DerivationGoal::isReadDesc(int fd)
|
||||||
return fd == hook->builderOut.readSide.get();
|
return fd == hook->builderOut.readSide.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DerivationGoal::handleChildOutput(int fd, std::string_view data)
|
||||||
void DerivationGoal::handleChildOutput(int fd, const string & data)
|
|
||||||
{
|
{
|
||||||
if (isReadDesc(fd))
|
// local & `ssh://`-builds are dealt with here.
|
||||||
|
auto isWrittenToLog = isReadDesc(fd);
|
||||||
|
if (isWrittenToLog)
|
||||||
{
|
{
|
||||||
logSize += data.size();
|
logSize += data.size();
|
||||||
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
if (settings.maxLogSize && logSize > settings.maxLogSize) {
|
||||||
killChild();
|
killChild();
|
||||||
done(
|
done(
|
||||||
BuildResult::LogLimitExceeded,
|
BuildResult::LogLimitExceeded, {},
|
||||||
Error("%s killed after writing more than %d bytes of log output",
|
Error("%s killed after writing more than %d bytes of log output",
|
||||||
getName(), settings.maxLogSize));
|
getName(), settings.maxLogSize));
|
||||||
return;
|
return;
|
||||||
|
@ -1207,7 +1218,16 @@ void DerivationGoal::handleChildOutput(int fd, const string & data)
|
||||||
if (hook && fd == hook->fromHook.readSide.get()) {
|
if (hook && fd == hook->fromHook.readSide.get()) {
|
||||||
for (auto c : data)
|
for (auto c : data)
|
||||||
if (c == '\n') {
|
if (c == '\n') {
|
||||||
handleJSONLogMessage(currentHookLine, worker.act, hook->activities, true);
|
auto json = parseJSONMessage(currentHookLine);
|
||||||
|
if (json) {
|
||||||
|
auto s = handleJSONLogMessage(*json, worker.act, hook->activities, true);
|
||||||
|
// ensure that logs from a builder using `ssh-ng://` as protocol
|
||||||
|
// are also available to `nix log`.
|
||||||
|
if (s && !isWrittenToLog && logSink && (*json)["type"] == resBuildLogLine) {
|
||||||
|
auto f = (*json)["fields"];
|
||||||
|
(*logSink)((f.size() > 0 ? f.at(0).get<std::string>() : "") + "\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
currentHookLine.clear();
|
currentHookLine.clear();
|
||||||
} else
|
} else
|
||||||
currentHookLine += c;
|
currentHookLine += c;
|
||||||
|
@ -1241,7 +1261,8 @@ void DerivationGoal::flushLine()
|
||||||
|
|
||||||
std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap()
|
std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDerivationOutputMap()
|
||||||
{
|
{
|
||||||
if (!useDerivation || drv->type() != DerivationType::CAFloating) {
|
assert(drv->type().isPure());
|
||||||
|
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)
|
||||||
res.insert_or_assign(name, output.path(worker.store, drv->name, name));
|
res.insert_or_assign(name, output.path(worker.store, drv->name, name));
|
||||||
|
@ -1253,7 +1274,8 @@ std::map<std::string, std::optional<StorePath>> DerivationGoal::queryPartialDeri
|
||||||
|
|
||||||
OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
||||||
{
|
{
|
||||||
if (!useDerivation || drv->type() != DerivationType::CAFloating) {
|
assert(drv->type().isPure());
|
||||||
|
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))
|
||||||
res.insert_or_assign(name, *output.second);
|
res.insert_or_assign(name, *output.second);
|
||||||
|
@ -1264,10 +1286,14 @@ OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void 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;
|
||||||
|
|
||||||
for (auto & i : queryPartialDerivationOutputMap()) {
|
for (auto & i : queryPartialDerivationOutputMap()) {
|
||||||
InitialOutput & info = initialOutputs.at(i.first);
|
InitialOutput & info = initialOutputs.at(i.first);
|
||||||
info.wanted = wantOutput(i.first, wantedOutputs);
|
info.wanted = wantOutput(i.first, wantedOutputs);
|
||||||
|
@ -1284,27 +1310,30 @@ void DerivationGoal::checkPathValidity()
|
||||||
: PathStatus::Corrupt,
|
: PathStatus::Corrupt,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
auto drvOutput = DrvOutput{initialOutputs.at(i.first).outputHash, i.first};
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||||
auto drvOutput = DrvOutput{initialOutputs.at(i.first).outputHash, i.first};
|
|
||||||
if (auto real = worker.store.queryRealisation(drvOutput)) {
|
if (auto real = worker.store.queryRealisation(drvOutput)) {
|
||||||
info.known = {
|
info.known = {
|
||||||
.path = real->outPath,
|
.path = real->outPath,
|
||||||
.status = PathStatus::Valid,
|
.status = PathStatus::Valid,
|
||||||
};
|
};
|
||||||
} else if (info.known && info.known->status == PathStatus::Valid) {
|
} else if (info.known && info.known->isValid()) {
|
||||||
// We know the output because it' a static output of the
|
// We know the output because it's a static output of the
|
||||||
// derivation, and the output path is valid, but we don't have
|
// derivation, and the output path is valid, but we don't have
|
||||||
// its realisation stored (probably because it has been built
|
// its realisation stored (probably because it has been built
|
||||||
// without the `ca-derivations` experimental flag)
|
// without the `ca-derivations` experimental flag).
|
||||||
worker.store.registerDrvOutput(
|
worker.store.registerDrvOutput(
|
||||||
Realisation{
|
Realisation {
|
||||||
drvOutput,
|
drvOutput,
|
||||||
info.known->path,
|
info.known->path,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (info.wanted && info.known && info.known->isValid())
|
||||||
|
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.
|
||||||
|
@ -1312,24 +1341,49 @@ void DerivationGoal::checkPathValidity()
|
||||||
throw Error("derivation '%s' does not have wanted outputs %s",
|
throw Error("derivation '%s' does not have wanted outputs %s",
|
||||||
worker.store.printStorePath(drvPath),
|
worker.store.printStorePath(drvPath),
|
||||||
concatStringsSep(", ", quoteStrings(wantedOutputsLeft)));
|
concatStringsSep(", ", quoteStrings(wantedOutputsLeft)));
|
||||||
|
|
||||||
|
bool allValid = true;
|
||||||
|
for (auto & [_, status] : initialOutputs) {
|
||||||
|
if (!status.wanted) continue;
|
||||||
|
if (!status.known || !status.known->isValid()) {
|
||||||
|
allValid = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { allValid, validOutputs };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void DerivationGoal::done(BuildResult::Status status, std::optional<Error> ex)
|
DrvOutputs DerivationGoal::assertPathValidity()
|
||||||
{
|
{
|
||||||
result.status = status;
|
auto [allValid, validOutputs] = checkPathValidity();
|
||||||
|
if (!allValid)
|
||||||
|
throw Error("some outputs are unexpectedly invalid");
|
||||||
|
return validOutputs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DerivationGoal::done(
|
||||||
|
BuildResult::Status status,
|
||||||
|
DrvOutputs builtOutputs,
|
||||||
|
std::optional<Error> ex)
|
||||||
|
{
|
||||||
|
buildResult.status = status;
|
||||||
if (ex)
|
if (ex)
|
||||||
result.errorMsg = ex->what();
|
// FIXME: strip: "error: "
|
||||||
amDone(result.success() ? ecSuccess : ecFailed, ex);
|
buildResult.errorMsg = ex->what();
|
||||||
if (result.status == BuildResult::TimedOut)
|
if (buildResult.status == BuildResult::TimedOut)
|
||||||
worker.timedOut = true;
|
worker.timedOut = true;
|
||||||
if (result.status == BuildResult::PermanentFailure)
|
if (buildResult.status == BuildResult::PermanentFailure)
|
||||||
worker.permanentFailure = true;
|
worker.permanentFailure = true;
|
||||||
|
|
||||||
mcExpectedBuilds.reset();
|
mcExpectedBuilds.reset();
|
||||||
mcRunningBuilds.reset();
|
mcRunningBuilds.reset();
|
||||||
|
|
||||||
if (result.success()) {
|
if (buildResult.success()) {
|
||||||
|
assert(!builtOutputs.empty());
|
||||||
|
buildResult.builtOutputs = std::move(builtOutputs);
|
||||||
if (status == BuildResult::Built)
|
if (status == BuildResult::Built)
|
||||||
worker.doneBuilds++;
|
worker.doneBuilds++;
|
||||||
} else {
|
} else {
|
||||||
|
@ -1343,9 +1397,23 @@ void DerivationGoal::done(BuildResult::Status status, std::optional<Error> ex)
|
||||||
if (traceBuiltOutputsFile != "") {
|
if (traceBuiltOutputsFile != "") {
|
||||||
std::fstream fs;
|
std::fstream fs;
|
||||||
fs.open(traceBuiltOutputsFile, std::fstream::out);
|
fs.open(traceBuiltOutputsFile, std::fstream::out);
|
||||||
fs << worker.store.printStorePath(drvPath) << "\t" << result.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;
|
||||||
|
@ -104,20 +113,8 @@ struct DerivationGoal : public Goal
|
||||||
typedef void (DerivationGoal::*GoalState)();
|
typedef void (DerivationGoal::*GoalState)();
|
||||||
GoalState state;
|
GoalState state;
|
||||||
|
|
||||||
/* The final output paths of the build.
|
|
||||||
|
|
||||||
- For input-addressed derivations, always the precomputed paths
|
|
||||||
|
|
||||||
- For content-addressed derivations, calcuated from whatever the hash
|
|
||||||
ends up being. (Note that fixed outputs derivations that produce the
|
|
||||||
"wrong" output still install that data under its true content-address.)
|
|
||||||
*/
|
|
||||||
OutputPathMap finalOutputs;
|
|
||||||
|
|
||||||
BuildMode buildMode;
|
BuildMode buildMode;
|
||||||
|
|
||||||
BuildResult result;
|
|
||||||
|
|
||||||
/* The current round, if we're building multiple times. */
|
/* The current round, if we're building multiple times. */
|
||||||
size_t curRound = 1;
|
size_t curRound = 1;
|
||||||
|
|
||||||
|
@ -145,15 +142,13 @@ struct DerivationGoal : public Goal
|
||||||
|
|
||||||
void timedOut(Error && ex) override;
|
void timedOut(Error && ex) override;
|
||||||
|
|
||||||
string key() override;
|
std::string key() override;
|
||||||
|
|
||||||
void work() override;
|
void work() override;
|
||||||
|
|
||||||
/* Add wanted outputs to an already existing derivation goal. */
|
/* Add wanted outputs to an already existing derivation goal. */
|
||||||
void addWantedOutputs(const StringSet & outputs);
|
void addWantedOutputs(const StringSet & outputs);
|
||||||
|
|
||||||
BuildResult getResult() { return result; }
|
|
||||||
|
|
||||||
/* The states. */
|
/* The states. */
|
||||||
void getDerivation();
|
void getDerivation();
|
||||||
void loadDerivation();
|
void loadDerivation();
|
||||||
|
@ -175,7 +170,7 @@ struct DerivationGoal : public Goal
|
||||||
|
|
||||||
/* Check that the derivation outputs all exist and register them
|
/* Check that the derivation outputs all exist and register them
|
||||||
as valid. */
|
as valid. */
|
||||||
virtual void registerOutputs();
|
virtual DrvOutputs registerOutputs();
|
||||||
|
|
||||||
/* Open a log file and a pipe to it. */
|
/* Open a log file and a pipe to it. */
|
||||||
Path openLogFile();
|
Path openLogFile();
|
||||||
|
@ -200,7 +195,7 @@ struct DerivationGoal : public Goal
|
||||||
virtual bool isReadDesc(int fd);
|
virtual bool isReadDesc(int fd);
|
||||||
|
|
||||||
/* Callback used by the worker to write to the log. */
|
/* Callback used by the worker to write to the log. */
|
||||||
void handleChildOutput(int fd, const string & data) override;
|
void handleChildOutput(int fd, std::string_view data) override;
|
||||||
void handleEOF(int fd) override;
|
void handleEOF(int fd) override;
|
||||||
void flushLine();
|
void flushLine();
|
||||||
|
|
||||||
|
@ -210,8 +205,17 @@ struct DerivationGoal : public Goal
|
||||||
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap();
|
std::map<std::string, std::optional<StorePath>> queryPartialDerivationOutputMap();
|
||||||
OutputPathMap queryDerivationOutputMap();
|
OutputPathMap queryDerivationOutputMap();
|
||||||
|
|
||||||
/* Return the set of (in)valid paths. */
|
/* Update 'initialOutputs' to determine the current status of the
|
||||||
void checkPathValidity();
|
outputs of the derivation. Also returns a Boolean denoting
|
||||||
|
whether all outputs are valid and non-corrupt, and a
|
||||||
|
'DrvOutputs' structure containing the valid and wanted
|
||||||
|
outputs. */
|
||||||
|
std::pair<bool, DrvOutputs> checkPathValidity();
|
||||||
|
|
||||||
|
/* Aborts if any output is not valid or corrupt, and otherwise
|
||||||
|
returns a 'DrvOutputs' structure containing the wanted
|
||||||
|
outputs. */
|
||||||
|
DrvOutputs assertPathValidity();
|
||||||
|
|
||||||
/* Forcibly kill the child process, if any. */
|
/* Forcibly kill the child process, if any. */
|
||||||
virtual void killChild();
|
virtual void killChild();
|
||||||
|
@ -222,8 +226,11 @@ struct DerivationGoal : public Goal
|
||||||
|
|
||||||
void done(
|
void done(
|
||||||
BuildResult::Status status,
|
BuildResult::Status status,
|
||||||
|
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);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,12 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(const DrvOutput& id, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(
|
||||||
: Goal(worker)
|
const DrvOutput & id,
|
||||||
|
Worker & worker,
|
||||||
|
RepairFlag repair,
|
||||||
|
std::optional<ContentAddress> ca)
|
||||||
|
: Goal(worker, DerivedPath::Opaque { StorePath::dummy })
|
||||||
, id(id)
|
, id(id)
|
||||||
{
|
{
|
||||||
state = &DrvOutputSubstitutionGoal::init;
|
state = &DrvOutputSubstitutionGoal::init;
|
||||||
|
@ -32,12 +36,12 @@ void DrvOutputSubstitutionGoal::init()
|
||||||
|
|
||||||
void DrvOutputSubstitutionGoal::tryNext()
|
void DrvOutputSubstitutionGoal::tryNext()
|
||||||
{
|
{
|
||||||
trace("Trying next substituter");
|
trace("trying next substituter");
|
||||||
|
|
||||||
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
|
||||||
|
@ -119,7 +123,7 @@ void DrvOutputSubstitutionGoal::realisationFetched()
|
||||||
void DrvOutputSubstitutionGoal::outPathValid()
|
void DrvOutputSubstitutionGoal::outPathValid()
|
||||||
{
|
{
|
||||||
assert(outputInfo);
|
assert(outputInfo);
|
||||||
trace("Output path substituted");
|
trace("output path substituted");
|
||||||
|
|
||||||
if (nrFailed > 0) {
|
if (nrFailed > 0) {
|
||||||
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
|
debug("The output path of the derivation output '%s' could not be substituted", id.to_string());
|
||||||
|
@ -137,7 +141,7 @@ void DrvOutputSubstitutionGoal::finished()
|
||||||
amDone(ecSuccess);
|
amDone(ecSuccess);
|
||||||
}
|
}
|
||||||
|
|
||||||
string DrvOutputSubstitutionGoal::key()
|
std::string DrvOutputSubstitutionGoal::key()
|
||||||
{
|
{
|
||||||
/* "a$" ensures substitution goals happen before derivation
|
/* "a$" ensures substitution goals happen before derivation
|
||||||
goals. */
|
goals. */
|
||||||
|
|
|
@ -51,7 +51,7 @@ public:
|
||||||
|
|
||||||
void timedOut(Error && ex) override { abort(); };
|
void timedOut(Error && ex) override { abort(); };
|
||||||
|
|
||||||
string key() override;
|
std::string key() override;
|
||||||
|
|
||||||
void work() override;
|
void work() override;
|
||||||
void handleEOF(int fd) override;
|
void handleEOF(int fd) override;
|
||||||
|
|
|
@ -47,43 +47,51 @@ void Store::buildPaths(const std::vector<DerivedPath> & reqs, BuildMode buildMod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<BuildResult> Store::buildPathsWithResults(
|
||||||
|
const std::vector<DerivedPath> & reqs,
|
||||||
|
BuildMode buildMode,
|
||||||
|
std::shared_ptr<Store> evalStore)
|
||||||
|
{
|
||||||
|
Worker worker(*this, evalStore ? *evalStore : *this);
|
||||||
|
|
||||||
|
Goals goals;
|
||||||
|
for (const auto & br : reqs) {
|
||||||
|
std::visit(overloaded {
|
||||||
|
[&](const DerivedPath::Built & bfd) {
|
||||||
|
goals.insert(worker.makeDerivationGoal(bfd.drvPath, bfd.outputs, buildMode));
|
||||||
|
},
|
||||||
|
[&](const DerivedPath::Opaque & bo) {
|
||||||
|
goals.insert(worker.makePathSubstitutionGoal(bo.path, buildMode == bmRepair ? Repair : NoRepair));
|
||||||
|
},
|
||||||
|
}, br.raw());
|
||||||
|
}
|
||||||
|
|
||||||
|
worker.run(goals);
|
||||||
|
|
||||||
|
std::vector<BuildResult> results;
|
||||||
|
|
||||||
|
for (auto & i : goals)
|
||||||
|
results.push_back(i->buildResult);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
BuildResult Store::buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
BuildMode buildMode)
|
BuildMode buildMode)
|
||||||
{
|
{
|
||||||
Worker worker(*this, *this);
|
Worker worker(*this, *this);
|
||||||
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, {}, buildMode);
|
auto goal = worker.makeBasicDerivationGoal(drvPath, drv, {}, buildMode);
|
||||||
|
|
||||||
BuildResult result;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
worker.run(Goals{goal});
|
worker.run(Goals{goal});
|
||||||
result = goal->getResult();
|
return goal->buildResult;
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
result.status = BuildResult::MiscFailure;
|
return BuildResult {
|
||||||
result.errorMsg = e.msg();
|
.status = BuildResult::MiscFailure,
|
||||||
}
|
.errorMsg = e.msg(),
|
||||||
// XXX: Should use `goal->queryPartialDerivationOutputMap()` once it's
|
.path = DerivedPath::Built { .drvPath = drvPath },
|
||||||
// extended to return the full realisation for each output
|
};
|
||||||
auto staticDrvOutputs = drv.outputsAndOptPaths(*this);
|
};
|
||||||
auto outputHashes = staticOutputHashes(*this, drv);
|
|
||||||
for (auto & [outputName, staticOutput] : staticDrvOutputs) {
|
|
||||||
auto outputId = DrvOutput{outputHashes.at(outputName), outputName};
|
|
||||||
if (staticOutput.second)
|
|
||||||
result.builtOutputs.insert_or_assign(
|
|
||||||
outputId,
|
|
||||||
Realisation{ outputId, *staticOutput.second}
|
|
||||||
);
|
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations) && !derivationHasKnownOutputPaths(drv.type())) {
|
|
||||||
auto realisation = this->queryRealisation(outputId);
|
|
||||||
if (realisation)
|
|
||||||
result.builtOutputs.insert_or_assign(
|
|
||||||
outputId,
|
|
||||||
*realisation
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ namespace nix {
|
||||||
|
|
||||||
|
|
||||||
bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
|
bool CompareGoalPtrs::operator() (const GoalPtr & a, const GoalPtr & b) const {
|
||||||
string s1 = a->key();
|
std::string s1 = a->key();
|
||||||
string s2 = b->key();
|
std::string s2 = b->key();
|
||||||
return s1 < s2;
|
return s1 < s2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()));
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "build-result.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -18,8 +19,8 @@ struct CompareGoalPtrs {
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Set of goals. */
|
/* Set of goals. */
|
||||||
typedef set<GoalPtr, CompareGoalPtrs> Goals;
|
typedef std::set<GoalPtr, CompareGoalPtrs> Goals;
|
||||||
typedef set<WeakGoalPtr, std::owner_less<WeakGoalPtr>> WeakGoals;
|
typedef std::set<WeakGoalPtr, std::owner_less<WeakGoalPtr>> WeakGoals;
|
||||||
|
|
||||||
/* A map of paths to goals (and the other way around). */
|
/* A map of paths to goals (and the other way around). */
|
||||||
typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;
|
typedef std::map<StorePath, WeakGoalPtr> WeakGoalMap;
|
||||||
|
@ -39,30 +40,32 @@ 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. */
|
||||||
string name;
|
std::string name;
|
||||||
|
|
||||||
/* Whether the goal is finished. */
|
/* Whether the goal is finished. */
|
||||||
ExitCode exitCode;
|
ExitCode exitCode = ecBusy;
|
||||||
|
|
||||||
|
/* Build result. */
|
||||||
|
BuildResult buildResult;
|
||||||
|
|
||||||
/* Exception containing an error message, if any. */
|
/* Exception containing an error message, if any. */
|
||||||
std::optional<Error> ex;
|
std::optional<Error> ex;
|
||||||
|
|
||||||
Goal(Worker & worker) : worker(worker)
|
Goal(Worker & worker, DerivedPath path)
|
||||||
{
|
: worker(worker)
|
||||||
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
|
, buildResult { .path = std::move(path) }
|
||||||
exitCode = ecBusy;
|
{ }
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~Goal()
|
virtual ~Goal()
|
||||||
{
|
{
|
||||||
|
@ -75,7 +78,7 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
||||||
|
|
||||||
virtual void waiteeDone(GoalPtr waitee, ExitCode result);
|
virtual void waiteeDone(GoalPtr waitee, ExitCode result);
|
||||||
|
|
||||||
virtual void handleChildOutput(int fd, const string & data)
|
virtual void handleChildOutput(int fd, std::string_view data)
|
||||||
{
|
{
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
@ -87,7 +90,7 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
||||||
|
|
||||||
void trace(const FormatOrString & fs);
|
void trace(const FormatOrString & fs);
|
||||||
|
|
||||||
string getName()
|
std::string getName()
|
||||||
{
|
{
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
@ -97,7 +100,7 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
||||||
by the worker (important!), etc. */
|
by the worker (important!), etc. */
|
||||||
virtual void timedOut(Error && ex) = 0;
|
virtual void timedOut(Error && ex) = 0;
|
||||||
|
|
||||||
virtual string key() = 0;
|
virtual std::string key() = 0;
|
||||||
|
|
||||||
void amDone(ExitCode result, std::optional<Error> ex = {});
|
void amDone(ExitCode result, std::optional<Error> ex = {});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "local-derivation-goal.hh"
|
#include "local-derivation-goal.hh"
|
||||||
|
#include "gc-store.hh"
|
||||||
#include "hook-instance.hh"
|
#include "hook-instance.hh"
|
||||||
#include "worker.hh"
|
#include "worker.hh"
|
||||||
#include "builtins.hh"
|
#include "builtins.hh"
|
||||||
|
@ -193,7 +194,7 @@ void LocalDerivationGoal::tryLocalBuild() {
|
||||||
outputLocks.unlock();
|
outputLocks.unlock();
|
||||||
buildUser.reset();
|
buildUser.reset();
|
||||||
worker.permanentFailure = true;
|
worker.permanentFailure = true;
|
||||||
done(BuildResult::InputRejected, e);
|
done(BuildResult::InputRejected, {}, e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -394,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 = !(derivationIsImpure(derivationType)) && !noChroot;
|
useChroot = derivationType.isSandboxed() && !noChroot;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & localStore = getLocalStore();
|
auto & localStore = getLocalStore();
|
||||||
|
@ -481,12 +482,12 @@ void LocalDerivationGoal::startBuilder()
|
||||||
temporary build directory. The text files have the format used
|
temporary build directory. The text files have the format used
|
||||||
by `nix-store --register-validity'. However, the deriver
|
by `nix-store --register-validity'. However, the deriver
|
||||||
fields are left empty. */
|
fields are left empty. */
|
||||||
string s = get(drv->env, "exportReferencesGraph").value_or("");
|
auto s = get(drv->env, "exportReferencesGraph").value_or("");
|
||||||
Strings ss = tokenizeString<Strings>(s);
|
Strings ss = tokenizeString<Strings>(s);
|
||||||
if (ss.size() % 2 != 0)
|
if (ss.size() % 2 != 0)
|
||||||
throw BuildError("odd number of tokens in 'exportReferencesGraph': '%1%'", s);
|
throw BuildError("odd number of tokens in 'exportReferencesGraph': '%1%'", s);
|
||||||
for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
|
for (Strings::iterator i = ss.begin(); i != ss.end(); ) {
|
||||||
string fileName = *i++;
|
auto fileName = *i++;
|
||||||
static std::regex regex("[A-Za-z_][A-Za-z0-9_.-]*");
|
static std::regex regex("[A-Za-z_][A-Za-z0-9_.-]*");
|
||||||
if (!std::regex_match(fileName, regex))
|
if (!std::regex_match(fileName, regex))
|
||||||
throw Error("invalid file name '%s' in 'exportReferencesGraph'", fileName);
|
throw Error("invalid file name '%s' in 'exportReferencesGraph'", fileName);
|
||||||
|
@ -517,10 +518,10 @@ void LocalDerivationGoal::startBuilder()
|
||||||
i.pop_back();
|
i.pop_back();
|
||||||
}
|
}
|
||||||
size_t p = i.find('=');
|
size_t p = i.find('=');
|
||||||
if (p == string::npos)
|
if (p == std::string::npos)
|
||||||
dirsInChroot[i] = {i, optional};
|
dirsInChroot[i] = {i, optional};
|
||||||
else
|
else
|
||||||
dirsInChroot[string(i, 0, p)] = {string(i, p + 1), optional};
|
dirsInChroot[i.substr(0, p)] = {i.substr(p + 1), optional};
|
||||||
}
|
}
|
||||||
dirsInChroot[tmpDirInSandbox] = tmpDir;
|
dirsInChroot[tmpDirInSandbox] = tmpDir;
|
||||||
|
|
||||||
|
@ -607,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 (!(derivationIsImpure(derivationType)))
|
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,
|
||||||
|
@ -671,9 +672,10 @@ void LocalDerivationGoal::startBuilder()
|
||||||
auto state = stBegin;
|
auto state = stBegin;
|
||||||
auto lines = runProgram(settings.preBuildHook, false, args);
|
auto lines = runProgram(settings.preBuildHook, false, args);
|
||||||
auto lastPos = std::string::size_type{0};
|
auto lastPos = std::string::size_type{0};
|
||||||
for (auto nlPos = lines.find('\n'); nlPos != string::npos;
|
for (auto nlPos = lines.find('\n'); nlPos != std::string::npos;
|
||||||
nlPos = lines.find('\n', lastPos)) {
|
nlPos = lines.find('\n', lastPos))
|
||||||
auto line = std::string{lines, lastPos, nlPos - lastPos};
|
{
|
||||||
|
auto line = lines.substr(lastPos, nlPos - lastPos);
|
||||||
lastPos = nlPos + 1;
|
lastPos = nlPos + 1;
|
||||||
if (state == stBegin) {
|
if (state == stBegin) {
|
||||||
if (line == "extra-sandbox-paths" || line == "extra-chroot-dirs") {
|
if (line == "extra-sandbox-paths" || line == "extra-chroot-dirs") {
|
||||||
|
@ -686,10 +688,10 @@ void LocalDerivationGoal::startBuilder()
|
||||||
state = stBegin;
|
state = stBegin;
|
||||||
} else {
|
} else {
|
||||||
auto p = line.find('=');
|
auto p = line.find('=');
|
||||||
if (p == string::npos)
|
if (p == std::string::npos)
|
||||||
dirsInChroot[line] = line;
|
dirsInChroot[line] = line;
|
||||||
else
|
else
|
||||||
dirsInChroot[string(line, 0, p)] = string(line, p + 1);
|
dirsInChroot[line.substr(0, p)] = line.substr(p + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -754,7 +756,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
if (tcsetattr(builderOut.writeSide.get(), TCSANOW, &term))
|
if (tcsetattr(builderOut.writeSide.get(), TCSANOW, &term))
|
||||||
throw SysError("putting pseudoterminal into raw mode");
|
throw SysError("putting pseudoterminal into raw mode");
|
||||||
|
|
||||||
result.startTime = time(0);
|
buildResult.startTime = time(0);
|
||||||
|
|
||||||
/* Fork a child to build the package. */
|
/* Fork a child to build the package. */
|
||||||
|
|
||||||
|
@ -794,7 +796,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
us.
|
us.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!(derivationIsImpure(derivationType)))
|
if (derivationType.isSandboxed())
|
||||||
privateNetwork = true;
|
privateNetwork = true;
|
||||||
|
|
||||||
userNamespaceSync.create();
|
userNamespaceSync.create();
|
||||||
|
@ -912,9 +914,12 @@ void LocalDerivationGoal::startBuilder()
|
||||||
sandboxMountNamespace = open(fmt("/proc/%d/ns/mnt", (pid_t) pid).c_str(), O_RDONLY);
|
sandboxMountNamespace = open(fmt("/proc/%d/ns/mnt", (pid_t) pid).c_str(), O_RDONLY);
|
||||||
if (sandboxMountNamespace.get() == -1)
|
if (sandboxMountNamespace.get() == -1)
|
||||||
throw SysError("getting sandbox mount namespace");
|
throw SysError("getting sandbox mount namespace");
|
||||||
sandboxUserNamespace = open(fmt("/proc/%d/ns/user", (pid_t) pid).c_str(), O_RDONLY);
|
|
||||||
if (sandboxUserNamespace.get() == -1)
|
if (usingUserNamespace) {
|
||||||
throw SysError("getting sandbox user namespace");
|
sandboxUserNamespace = open(fmt("/proc/%d/ns/user", (pid_t) pid).c_str(), O_RDONLY);
|
||||||
|
if (sandboxUserNamespace.get() == -1)
|
||||||
|
throw SysError("getting sandbox user namespace");
|
||||||
|
}
|
||||||
|
|
||||||
/* Signal the builder that we've updated its user namespace. */
|
/* Signal the builder that we've updated its user namespace. */
|
||||||
writeFull(userNamespaceSync.writeSide.get(), "1");
|
writeFull(userNamespaceSync.writeSide.get(), "1");
|
||||||
|
@ -938,7 +943,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
/* Check if setting up the build environment failed. */
|
/* Check if setting up the build environment failed. */
|
||||||
std::vector<std::string> msgs;
|
std::vector<std::string> msgs;
|
||||||
while (true) {
|
while (true) {
|
||||||
string msg = [&]() {
|
std::string msg = [&]() {
|
||||||
try {
|
try {
|
||||||
return readLine(builderOut.readSide.get());
|
return readLine(builderOut.readSide.get());
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
|
@ -950,8 +955,8 @@ void LocalDerivationGoal::startBuilder()
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}();
|
}();
|
||||||
if (string(msg, 0, 1) == "\2") break;
|
if (msg.substr(0, 1) == "\2") break;
|
||||||
if (string(msg, 0, 1) == "\1") {
|
if (msg.substr(0, 1) == "\1") {
|
||||||
FdSource source(builderOut.readSide.get());
|
FdSource source(builderOut.readSide.get());
|
||||||
auto ex = readError(source);
|
auto ex = readError(source);
|
||||||
ex.addTrace({}, "while setting up the build environment");
|
ex.addTrace({}, "while setting up the build environment");
|
||||||
|
@ -987,7 +992,7 @@ void LocalDerivationGoal::initTmpDir() {
|
||||||
env[i.first] = i.second;
|
env[i.first] = i.second;
|
||||||
} else {
|
} else {
|
||||||
auto hash = hashString(htSHA256, i.first);
|
auto hash = hashString(htSHA256, i.first);
|
||||||
string fn = ".attr-" + hash.to_string(Base32, false);
|
std::string fn = ".attr-" + hash.to_string(Base32, false);
|
||||||
Path p = tmpDir + "/" + fn;
|
Path p = tmpDir + "/" + fn;
|
||||||
writeFile(p, rewriteStrings(i.second, inputRewrites));
|
writeFile(p, rewriteStrings(i.second, inputRewrites));
|
||||||
chownToBuilder(p);
|
chownToBuilder(p);
|
||||||
|
@ -1044,7 +1049,7 @@ void LocalDerivationGoal::initEnv()
|
||||||
derivation, tell the builder, so that for instance `fetchurl'
|
derivation, tell the builder, so that for instance `fetchurl'
|
||||||
can skip checking the output. On older Nixes, this environment
|
can skip checking the output. On older Nixes, this environment
|
||||||
variable won't be set, so `fetchurl' will do the check. */
|
variable won't be set, so `fetchurl' will do the check. */
|
||||||
if (derivationIsFixed(derivationType)) env["NIX_OUTPUT_CHECKED"] = "1";
|
if (derivationType.isFixed()) env["NIX_OUTPUT_CHECKED"] = "1";
|
||||||
|
|
||||||
/* *Only* if this is a fixed-output derivation, propagate the
|
/* *Only* if this is a fixed-output derivation, propagate the
|
||||||
values of the environment variables specified in the
|
values of the environment variables specified in the
|
||||||
|
@ -1055,7 +1060,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 (derivationIsImpure(derivationType)) {
|
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("");
|
||||||
}
|
}
|
||||||
|
@ -1078,7 +1083,7 @@ void LocalDerivationGoal::writeStructuredAttrs()
|
||||||
for (auto & [i, v] : json["outputs"].get<nlohmann::json::object_t>()) {
|
for (auto & [i, v] : json["outputs"].get<nlohmann::json::object_t>()) {
|
||||||
/* The placeholder must have a rewrite, so we use it to cover both the
|
/* The placeholder must have a rewrite, so we use it to cover both the
|
||||||
cases where we know or don't know the output path ahead of time. */
|
cases where we know or don't know the output path ahead of time. */
|
||||||
rewritten[i] = rewriteStrings(v, inputRewrites);
|
rewritten[i] = rewriteStrings((std::string) v, inputRewrites);
|
||||||
}
|
}
|
||||||
|
|
||||||
json["outputs"] = rewritten;
|
json["outputs"] = rewritten;
|
||||||
|
@ -1123,7 +1128,7 @@ struct RestrictedStoreConfig : virtual LocalFSStoreConfig
|
||||||
/* A wrapper around LocalStore that only allows building/querying of
|
/* A wrapper around LocalStore that only allows building/querying of
|
||||||
paths that are in the input closures of the build or were added via
|
paths that are in the input closures of the build or were added via
|
||||||
recursive Nix calls. */
|
recursive Nix calls. */
|
||||||
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual LocalFSStore
|
struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual LocalFSStore, public virtual GcStore
|
||||||
{
|
{
|
||||||
ref<LocalStore> next;
|
ref<LocalStore> next;
|
||||||
|
|
||||||
|
@ -1184,10 +1189,14 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
||||||
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
|
std::optional<StorePath> queryPathFromHashPart(const std::string & hashPart) override
|
||||||
{ throw Error("queryPathFromHashPart"); }
|
{ throw Error("queryPathFromHashPart"); }
|
||||||
|
|
||||||
StorePath addToStore(const string & name, const Path & srcPath,
|
StorePath addToStore(
|
||||||
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256,
|
std::string_view name,
|
||||||
PathFilter & filter = defaultPathFilter, RepairFlag repair = NoRepair,
|
const Path & srcPath,
|
||||||
const StorePathSet & references = StorePathSet()) override
|
FileIngestionMethod method,
|
||||||
|
HashType hashAlgo,
|
||||||
|
PathFilter & filter,
|
||||||
|
RepairFlag repair,
|
||||||
|
const StorePathSet & references) override
|
||||||
{ throw Error("addToStore"); }
|
{ throw Error("addToStore"); }
|
||||||
|
|
||||||
void addToStore(const ValidPathInfo & info, Source & narSource,
|
void addToStore(const ValidPathInfo & info, Source & narSource,
|
||||||
|
@ -1197,17 +1206,24 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
||||||
goal.addDependency(info.path);
|
goal.addDependency(info.path);
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePath addTextToStore(const string & name, const string & s,
|
StorePath addTextToStore(
|
||||||
const StorePathSet & references, RepairFlag repair = NoRepair) override
|
std::string_view name,
|
||||||
|
std::string_view s,
|
||||||
|
const StorePathSet & references,
|
||||||
|
RepairFlag repair = NoRepair) override
|
||||||
{
|
{
|
||||||
auto path = next->addTextToStore(name, s, references, repair);
|
auto path = next->addTextToStore(name, s, references, repair);
|
||||||
goal.addDependency(path);
|
goal.addDependency(path);
|
||||||
return path;
|
return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePath addToStoreFromDump(Source & dump, const string & name,
|
StorePath addToStoreFromDump(
|
||||||
FileIngestionMethod method = FileIngestionMethod::Recursive, HashType hashAlgo = htSHA256, RepairFlag repair = NoRepair,
|
Source & dump,
|
||||||
const StorePathSet & references = StorePathSet()) override
|
std::string_view name,
|
||||||
|
FileIngestionMethod method,
|
||||||
|
HashType hashAlgo,
|
||||||
|
RepairFlag repair,
|
||||||
|
const StorePathSet & references) override
|
||||||
{
|
{
|
||||||
auto path = next->addToStoreFromDump(dump, name, method, hashAlgo, repair, references);
|
auto path = next->addToStoreFromDump(dump, name, method, hashAlgo, repair, references);
|
||||||
goal.addDependency(path);
|
goal.addDependency(path);
|
||||||
|
@ -1244,6 +1260,16 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
||||||
}
|
}
|
||||||
|
|
||||||
void buildPaths(const std::vector<DerivedPath> & paths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override
|
void buildPaths(const std::vector<DerivedPath> & paths, BuildMode buildMode, std::shared_ptr<Store> evalStore) override
|
||||||
|
{
|
||||||
|
for (auto & result : buildPathsWithResults(paths, buildMode, evalStore))
|
||||||
|
if (!result.success())
|
||||||
|
result.rethrow();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<BuildResult> buildPathsWithResults(
|
||||||
|
const std::vector<DerivedPath> & paths,
|
||||||
|
BuildMode buildMode = bmNormal,
|
||||||
|
std::shared_ptr<Store> evalStore = nullptr) override
|
||||||
{
|
{
|
||||||
assert(!evalStore);
|
assert(!evalStore);
|
||||||
|
|
||||||
|
@ -1257,26 +1283,13 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
||||||
throw InvalidPath("cannot build '%s' in recursive Nix because path is unknown", req.to_string(*next));
|
throw InvalidPath("cannot build '%s' in recursive Nix because path is unknown", req.to_string(*next));
|
||||||
}
|
}
|
||||||
|
|
||||||
next->buildPaths(paths, buildMode);
|
auto results = next->buildPathsWithResults(paths, buildMode);
|
||||||
|
|
||||||
for (auto & path : paths) {
|
for (auto & result : results) {
|
||||||
auto p = std::get_if<DerivedPath::Built>(&path);
|
for (auto & [outputName, output] : result.builtOutputs) {
|
||||||
if (!p) continue;
|
newPaths.insert(output.outPath);
|
||||||
auto & bfd = *p;
|
newRealisations.insert(output);
|
||||||
auto drv = readDerivation(bfd.drvPath);
|
}
|
||||||
auto drvHashes = staticOutputHashes(*this, drv);
|
|
||||||
auto outputs = next->queryDerivationOutputMap(bfd.drvPath);
|
|
||||||
for (auto & [outputName, outputPath] : outputs)
|
|
||||||
if (wantOutput(outputName, bfd.outputs)) {
|
|
||||||
newPaths.insert(outputPath);
|
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
|
||||||
auto thisRealisation = next->queryRealisation(
|
|
||||||
DrvOutput{drvHashes.at(outputName), outputName}
|
|
||||||
);
|
|
||||||
assert(thisRealisation);
|
|
||||||
newRealisations.insert(*thisRealisation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StorePathSet closure;
|
StorePathSet closure;
|
||||||
|
@ -1285,6 +1298,8 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
||||||
goal.addDependency(path);
|
goal.addDependency(path);
|
||||||
for (auto & real : Realisation::closure(*next, newRealisations))
|
for (auto & real : Realisation::closure(*next, newRealisations))
|
||||||
goal.addedDrvOutputs.insert(real.id);
|
goal.addedDrvOutputs.insert(real.id);
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
|
||||||
|
@ -1325,6 +1340,12 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
|
||||||
next->queryMissing(allowed, willBuild, willSubstitute,
|
next->queryMissing(allowed, willBuild, willSubstitute,
|
||||||
unknown, downloadSize, narSize);
|
unknown, downloadSize, narSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual std::optional<std::string> getBuildLog(const StorePath & path) override
|
||||||
|
{ return std::nullopt; }
|
||||||
|
|
||||||
|
virtual void addBuildLog(const StorePath & path, std::string_view log) override
|
||||||
|
{ unsupported("addBuildLog"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1653,7 +1674,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 (derivationIsImpure(derivationType)) {
|
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
|
||||||
|
@ -1897,7 +1918,7 @@ void LocalDerivationGoal::runChild()
|
||||||
|
|
||||||
sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
|
sandboxProfile += "(import \"sandbox-defaults.sb\")\n";
|
||||||
|
|
||||||
if (derivationIsImpure(derivationType))
|
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 */
|
||||||
|
@ -1918,7 +1939,7 @@ void LocalDerivationGoal::runChild()
|
||||||
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin",
|
"can't map '%1%' to '%2%': mismatched impure paths not supported on Darwin",
|
||||||
i.first, i.second.source);
|
i.first, i.second.source);
|
||||||
|
|
||||||
string path = i.first;
|
std::string path = i.first;
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(path.c_str(), &st)) {
|
if (lstat(path.c_str(), &st)) {
|
||||||
if (i.second.optional && errno == ENOENT)
|
if (i.second.optional && errno == ENOENT)
|
||||||
|
@ -1970,7 +1991,7 @@ void LocalDerivationGoal::runChild()
|
||||||
args.push_back("IMPORT_DIR=" + settings.nixDataDir + "/nix/sandbox/");
|
args.push_back("IMPORT_DIR=" + settings.nixDataDir + "/nix/sandbox/");
|
||||||
if (allowLocalNetworking) {
|
if (allowLocalNetworking) {
|
||||||
args.push_back("-D");
|
args.push_back("-D");
|
||||||
args.push_back(string("_ALLOW_LOCAL_NETWORKING=1"));
|
args.push_back(std::string("_ALLOW_LOCAL_NETWORKING=1"));
|
||||||
}
|
}
|
||||||
args.push_back(drv->builder);
|
args.push_back(drv->builder);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1989,7 +2010,7 @@ void LocalDerivationGoal::runChild()
|
||||||
args.push_back(rewriteStrings(i, inputRewrites));
|
args.push_back(rewriteStrings(i, inputRewrites));
|
||||||
|
|
||||||
/* Indicate that we managed to set up the build environment. */
|
/* Indicate that we managed to set up the build environment. */
|
||||||
writeFull(STDERR_FILENO, string("\2\n"));
|
writeFull(STDERR_FILENO, std::string("\2\n"));
|
||||||
|
|
||||||
/* Execute the program. This should not return. */
|
/* Execute the program. This should not return. */
|
||||||
if (drv->isBuiltin()) {
|
if (drv->isBuiltin()) {
|
||||||
|
@ -2007,7 +2028,7 @@ void LocalDerivationGoal::runChild()
|
||||||
else if (drv->builder == "builtin:unpack-channel")
|
else if (drv->builder == "builtin:unpack-channel")
|
||||||
builtinUnpackChannel(drv2);
|
builtinUnpackChannel(drv2);
|
||||||
else
|
else
|
||||||
throw Error("unsupported builtin builder '%1%'", string(drv->builder, 8));
|
throw Error("unsupported builtin builder '%1%'", drv->builder.substr(8));
|
||||||
_exit(0);
|
_exit(0);
|
||||||
} catch (std::exception & e) {
|
} catch (std::exception & e) {
|
||||||
writeFull(STDERR_FILENO, e.what() + std::string("\n"));
|
writeFull(STDERR_FILENO, e.what() + std::string("\n"));
|
||||||
|
@ -2053,7 +2074,7 @@ void LocalDerivationGoal::runChild()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalDerivationGoal::registerOutputs()
|
DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
{
|
{
|
||||||
/* When using a build hook, the build hook can register the output
|
/* When using a build hook, the build hook can register the output
|
||||||
as valid (by doing `nix-store --import'). If so we don't have
|
as valid (by doing `nix-store --import'). If so we don't have
|
||||||
|
@ -2062,10 +2083,8 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
We can only early return when the outputs are known a priori. For
|
We can only early return when the outputs are known a priori. For
|
||||||
floating content-addressed derivations this isn't the case.
|
floating content-addressed derivations this isn't the case.
|
||||||
*/
|
*/
|
||||||
if (hook) {
|
if (hook)
|
||||||
DerivationGoal::registerOutputs();
|
return DerivationGoal::registerOutputs();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::map<std::string, ValidPathInfo> infos;
|
std::map<std::string, ValidPathInfo> infos;
|
||||||
|
|
||||||
|
@ -2188,6 +2207,8 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
|
|
||||||
std::reverse(sortedOutputNames.begin(), sortedOutputNames.end());
|
std::reverse(sortedOutputNames.begin(), sortedOutputNames.end());
|
||||||
|
|
||||||
|
OutputPathMap finalOutputs;
|
||||||
|
|
||||||
for (auto & outputName : sortedOutputNames) {
|
for (auto & outputName : sortedOutputNames) {
|
||||||
auto output = drv->outputs.at(outputName);
|
auto output = drv->outputs.at(outputName);
|
||||||
auto & scratchPath = scratchOutputs.at(outputName);
|
auto & scratchPath = scratchOutputs.at(outputName);
|
||||||
|
@ -2258,7 +2279,7 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
return res;
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto newInfoFromCA = [&](const DerivationOutputCAFloating outputHash) -> ValidPathInfo {
|
auto newInfoFromCA = [&](const DerivationOutput::CAFloating outputHash) -> ValidPathInfo {
|
||||||
auto & st = outputStats.at(outputName);
|
auto & st = outputStats.at(outputName);
|
||||||
if (outputHash.method == FileIngestionMethod::Flat) {
|
if (outputHash.method == FileIngestionMethod::Flat) {
|
||||||
/* The output path should be a regular file without execute permission. */
|
/* The output path should be a regular file without execute permission. */
|
||||||
|
@ -2324,7 +2345,8 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
};
|
};
|
||||||
|
|
||||||
ValidPathInfo newInfo = std::visit(overloaded {
|
ValidPathInfo newInfo = std::visit(overloaded {
|
||||||
[&](const DerivationOutputInputAddressed & output) {
|
|
||||||
|
[&](const DerivationOutput::InputAddressed & output) {
|
||||||
/* input-addressed case */
|
/* input-addressed case */
|
||||||
auto requiredFinalPath = output.path;
|
auto requiredFinalPath = output.path;
|
||||||
/* Preemptively add rewrite rule for final hash, as that is
|
/* Preemptively add rewrite rule for final hash, as that is
|
||||||
|
@ -2343,8 +2365,9 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
newInfo0.references.insert(newInfo0.path);
|
newInfo0.references.insert(newInfo0.path);
|
||||||
return newInfo0;
|
return newInfo0;
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFixed & dof) {
|
|
||||||
auto newInfo0 = newInfoFromCA(DerivationOutputCAFloating {
|
[&](const DerivationOutput::CAFixed & dof) {
|
||||||
|
auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating {
|
||||||
.method = dof.hash.method,
|
.method = dof.hash.method,
|
||||||
.hashType = dof.hash.hash.type,
|
.hashType = dof.hash.hash.type,
|
||||||
});
|
});
|
||||||
|
@ -2365,19 +2388,25 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
}
|
}
|
||||||
return newInfo0;
|
return newInfo0;
|
||||||
},
|
},
|
||||||
[&](DerivationOutputCAFloating dof) {
|
|
||||||
|
[&](const DerivationOutput::CAFloating & dof) {
|
||||||
return newInfoFromCA(dof);
|
return newInfoFromCA(dof);
|
||||||
},
|
},
|
||||||
[&](DerivationOutputDeferred) {
|
|
||||||
|
[&](const DerivationOutput::Deferred &) -> ValidPathInfo {
|
||||||
// No derivation should reach that point without having been
|
// No derivation should reach that point without having been
|
||||||
// rewritten first
|
// rewritten first
|
||||||
assert(false);
|
assert(false);
|
||||||
// Ugly, but the compiler insists on having this return a value
|
|
||||||
// of type `ValidPathInfo` despite the `assert(false)`, so
|
|
||||||
// let's provide it
|
|
||||||
return *(ValidPathInfo*)0;
|
|
||||||
},
|
},
|
||||||
}, output.output);
|
|
||||||
|
[&](const DerivationOutput::Impure & doi) {
|
||||||
|
return newInfoFromCA(DerivationOutput::CAFloating {
|
||||||
|
.method = doi.method,
|
||||||
|
.hashType = doi.hashType,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
}, output.raw());
|
||||||
|
|
||||||
/* FIXME: set proper permissions in restorePath() so
|
/* FIXME: set proper permissions in restorePath() so
|
||||||
we don't have to do another traversal. */
|
we don't have to do another traversal. */
|
||||||
|
@ -2487,11 +2516,12 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buildMode == bmCheck) {
|
if (buildMode == bmCheck) {
|
||||||
// In case of FOD mismatches on `--check` an error must be thrown as this is also
|
/* In case of fixed-output derivations, if there are
|
||||||
// a source for non-determinism.
|
mismatches on `--check` an error must be thrown as this is
|
||||||
|
also a source for non-determinism. */
|
||||||
if (delayedException)
|
if (delayedException)
|
||||||
std::rethrow_exception(delayedException);
|
std::rethrow_exception(delayedException);
|
||||||
return;
|
return assertPathValidity();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Apply output checks. */
|
/* Apply output checks. */
|
||||||
|
@ -2503,7 +2533,7 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
assert(prevInfos.size() == infos.size());
|
assert(prevInfos.size() == infos.size());
|
||||||
for (auto i = prevInfos.begin(), j = infos.begin(); i != prevInfos.end(); ++i, ++j)
|
for (auto i = prevInfos.begin(), j = infos.begin(); i != prevInfos.end(); ++i, ++j)
|
||||||
if (!(*i == *j)) {
|
if (!(*i == *j)) {
|
||||||
result.isNonDeterministic = true;
|
buildResult.isNonDeterministic = true;
|
||||||
Path prev = worker.store.printStorePath(i->second.path) + checkSuffix;
|
Path prev = worker.store.printStorePath(i->second.path) + checkSuffix;
|
||||||
bool prevExists = keepPreviousRound && pathExists(prev);
|
bool prevExists = keepPreviousRound && pathExists(prev);
|
||||||
hintformat hint = prevExists
|
hintformat hint = prevExists
|
||||||
|
@ -2541,7 +2571,7 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
|
|
||||||
if (curRound < nrRounds) {
|
if (curRound < nrRounds) {
|
||||||
prevInfos = std::move(infos);
|
prevInfos = std::move(infos);
|
||||||
return;
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Remove the .check directories if we're done. FIXME: keep them
|
/* Remove the .check directories if we're done. FIXME: keep them
|
||||||
|
@ -2576,17 +2606,27 @@ void LocalDerivationGoal::registerOutputs()
|
||||||
means it's safe to link the derivation to the output hash. We must do
|
means it's safe to link the derivation to the output hash. We must do
|
||||||
that for floating CA derivations, which otherwise couldn't be cached,
|
that for floating CA derivations, which otherwise couldn't be cached,
|
||||||
but it's fine to do in all cases. */
|
but it's fine to do in all cases. */
|
||||||
|
DrvOutputs builtOutputs;
|
||||||
|
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
for (auto & [outputName, newInfo] : infos) {
|
||||||
for (auto& [outputName, newInfo] : infos) {
|
auto thisRealisation = Realisation {
|
||||||
auto thisRealisation = Realisation{
|
.id = DrvOutput {
|
||||||
.id = DrvOutput{initialOutputs.at(outputName).outputHash,
|
initialOutputs.at(outputName).outputHash,
|
||||||
outputName},
|
outputName
|
||||||
.outPath = newInfo.path};
|
},
|
||||||
|
.outPath = newInfo.path
|
||||||
|
};
|
||||||
|
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)
|
||||||
|
&& drv->type().isPure())
|
||||||
|
{
|
||||||
signRealisation(thisRealisation);
|
signRealisation(thisRealisation);
|
||||||
worker.store.registerDrvOutput(thisRealisation);
|
worker.store.registerDrvOutput(thisRealisation);
|
||||||
}
|
}
|
||||||
|
if (wantOutput(outputName, wantedOutputs))
|
||||||
|
builtOutputs.emplace(thisRealisation.id, thisRealisation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return builtOutputs;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LocalDerivationGoal::signRealisation(Realisation & realisation)
|
void LocalDerivationGoal::signRealisation(Realisation & realisation)
|
||||||
|
@ -2595,7 +2635,7 @@ void LocalDerivationGoal::signRealisation(Realisation & realisation)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void LocalDerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & outputs)
|
void LocalDerivationGoal::checkOutputs(const std::map<std::string, ValidPathInfo> & outputs)
|
||||||
{
|
{
|
||||||
std::map<Path, const ValidPathInfo &> outputsByPath;
|
std::map<Path, const ValidPathInfo &> outputsByPath;
|
||||||
for (auto & output : outputs)
|
for (auto & output : outputs)
|
||||||
|
@ -2667,8 +2707,8 @@ void LocalDerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & out
|
||||||
for (auto & i : *value) {
|
for (auto & i : *value) {
|
||||||
if (worker.store.isStorePath(i))
|
if (worker.store.isStorePath(i))
|
||||||
spec.insert(worker.store.parseStorePath(i));
|
spec.insert(worker.store.parseStorePath(i));
|
||||||
else if (finalOutputs.count(i))
|
else if (outputs.count(i))
|
||||||
spec.insert(finalOutputs.at(i));
|
spec.insert(outputs.at(i).path);
|
||||||
else throw BuildError("derivation contains an illegal reference specifier '%s'", i);
|
else throw BuildError("derivation contains an illegal reference specifier '%s'", i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2691,7 +2731,7 @@ void LocalDerivationGoal::checkOutputs(const std::map<Path, ValidPathInfo> & out
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!badPaths.empty()) {
|
if (!badPaths.empty()) {
|
||||||
string badPathsStr;
|
std::string badPathsStr;
|
||||||
for (auto & i : badPaths) {
|
for (auto & i : badPaths) {
|
||||||
badPathsStr += "\n ";
|
badPathsStr += "\n ";
|
||||||
badPathsStr += worker.store.printStorePath(i);
|
badPathsStr += worker.store.printStorePath(i);
|
||||||
|
|
|
@ -58,11 +58,11 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||||
typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
|
typedef map<Path, ChrootPath> DirsInChroot; // maps target path to source path
|
||||||
DirsInChroot dirsInChroot;
|
DirsInChroot dirsInChroot;
|
||||||
|
|
||||||
typedef map<string, string> Environment;
|
typedef map<std::string, std::string> Environment;
|
||||||
Environment env;
|
Environment env;
|
||||||
|
|
||||||
#if __APPLE__
|
#if __APPLE__
|
||||||
typedef string SandboxProfile;
|
typedef std::string SandboxProfile;
|
||||||
SandboxProfile additionalSandboxProfile;
|
SandboxProfile additionalSandboxProfile;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ struct LocalDerivationGoal : public DerivationGoal
|
||||||
|
|
||||||
/* Check that the derivation outputs all exist and register them
|
/* Check that the derivation outputs all exist and register them
|
||||||
as valid. */
|
as valid. */
|
||||||
void registerOutputs() override;
|
DrvOutputs registerOutputs() override;
|
||||||
|
|
||||||
void signRealisation(Realisation &) override;
|
void signRealisation(Realisation &) override;
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
PathSubstitutionGoal::PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair, std::optional<ContentAddress> ca)
|
||||||
: Goal(worker)
|
: Goal(worker, DerivedPath::Opaque { storePath })
|
||||||
, storePath(storePath)
|
, storePath(storePath)
|
||||||
, repair(repair)
|
, repair(repair)
|
||||||
, ca(ca)
|
, ca(ca)
|
||||||
|
@ -24,6 +24,20 @@ PathSubstitutionGoal::~PathSubstitutionGoal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void PathSubstitutionGoal::done(
|
||||||
|
ExitCode result,
|
||||||
|
BuildResult::Status status,
|
||||||
|
std::optional<std::string> errorMsg)
|
||||||
|
{
|
||||||
|
buildResult.status = status;
|
||||||
|
if (errorMsg) {
|
||||||
|
debug(*errorMsg);
|
||||||
|
buildResult.errorMsg = *errorMsg;
|
||||||
|
}
|
||||||
|
amDone(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::work()
|
void PathSubstitutionGoal::work()
|
||||||
{
|
{
|
||||||
(this->*state)();
|
(this->*state)();
|
||||||
|
@ -38,7 +52,7 @@ void PathSubstitutionGoal::init()
|
||||||
|
|
||||||
/* If the path already exists we're done. */
|
/* If the path already exists we're done. */
|
||||||
if (!repair && worker.store.isValidPath(storePath)) {
|
if (!repair && worker.store.isValidPath(storePath)) {
|
||||||
amDone(ecSuccess);
|
done(ecSuccess, BuildResult::AlreadyValid);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,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. */
|
||||||
amDone(substituterFailed ? ecFailed : ecNoSubstituters);
|
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++;
|
||||||
|
@ -162,8 +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(
|
||||||
amDone(nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed);
|
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
||||||
|
BuildResult::DependencyFailed,
|
||||||
|
fmt("some references of path '%s' could not be realised", worker.store.printStorePath(storePath)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,11 +286,11 @@ void PathSubstitutionGoal::finished()
|
||||||
|
|
||||||
worker.updateProgress();
|
worker.updateProgress();
|
||||||
|
|
||||||
amDone(ecSuccess);
|
done(ecSuccess, BuildResult::Substituted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::handleChildOutput(int fd, const string & data)
|
void PathSubstitutionGoal::handleChildOutput(int fd, std::string_view data)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,13 +53,18 @@ struct PathSubstitutionGoal : public Goal
|
||||||
/* Content address for recomputing store path */
|
/* Content address for recomputing store path */
|
||||||
std::optional<ContentAddress> ca;
|
std::optional<ContentAddress> ca;
|
||||||
|
|
||||||
|
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);
|
||||||
~PathSubstitutionGoal();
|
~PathSubstitutionGoal();
|
||||||
|
|
||||||
void timedOut(Error && ex) override { abort(); };
|
void timedOut(Error && ex) override { abort(); };
|
||||||
|
|
||||||
string key() override
|
std::string key() override
|
||||||
{
|
{
|
||||||
/* "a$" ensures substitution goals happen before derivation
|
/* "a$" ensures substitution goals happen before derivation
|
||||||
goals. */
|
goals. */
|
||||||
|
@ -77,7 +82,7 @@ public:
|
||||||
void finished();
|
void finished();
|
||||||
|
|
||||||
/* Callback used by the worker to write to the log. */
|
/* Callback used by the worker to write to the log. */
|
||||||
void handleChildOutput(int fd, const string & data) override;
|
void handleChildOutput(int fd, std::string_view data) override;
|
||||||
void handleEOF(int fd) override;
|
void handleEOF(int fd) override;
|
||||||
|
|
||||||
void cleanup() override;
|
void cleanup() override;
|
||||||
|
|
|
@ -161,7 +161,7 @@ unsigned Worker::getNrLocalBuilds()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void Worker::childStarted(GoalPtr goal, const set<int> & fds,
|
void Worker::childStarted(GoalPtr goal, const std::set<int> & fds,
|
||||||
bool inBuildSlot, bool respectTimeouts)
|
bool inBuildSlot, bool respectTimeouts)
|
||||||
{
|
{
|
||||||
Child child;
|
Child child;
|
||||||
|
@ -377,7 +377,7 @@ void Worker::waitForInput()
|
||||||
GoalPtr goal = j->goal.lock();
|
GoalPtr goal = j->goal.lock();
|
||||||
assert(goal);
|
assert(goal);
|
||||||
|
|
||||||
set<int> fds2(j->fds);
|
std::set<int> fds2(j->fds);
|
||||||
std::vector<unsigned char> buffer(4096);
|
std::vector<unsigned char> buffer(4096);
|
||||||
for (auto & k : fds2) {
|
for (auto & k : fds2) {
|
||||||
if (pollStatus.at(fdToPollStatus.at(k)).revents) {
|
if (pollStatus.at(fdToPollStatus.at(k)).revents) {
|
||||||
|
@ -394,7 +394,7 @@ void Worker::waitForInput()
|
||||||
} else {
|
} else {
|
||||||
printMsg(lvlVomit, "%1%: read %2% bytes",
|
printMsg(lvlVomit, "%1%: read %2% bytes",
|
||||||
goal->getName(), rd);
|
goal->getName(), rd);
|
||||||
string data((char *) buffer.data(), rd);
|
std::string data((char *) buffer.data(), rd);
|
||||||
j->lastOutput = after;
|
j->lastOutput = after;
|
||||||
goal->handleChildOutput(k, data);
|
goal->handleChildOutput(k, data);
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ struct Child
|
||||||
{
|
{
|
||||||
WeakGoalPtr goal;
|
WeakGoalPtr goal;
|
||||||
Goal * goal2; // ugly hackery
|
Goal * goal2; // ugly hackery
|
||||||
set<int> fds;
|
std::set<int> fds;
|
||||||
bool respectTimeouts;
|
bool respectTimeouts;
|
||||||
bool inBuildSlot;
|
bool inBuildSlot;
|
||||||
steady_time_point lastOutput; /* time we last got output on stdout/stderr */
|
steady_time_point lastOutput; /* time we last got output on stdout/stderr */
|
||||||
|
@ -167,7 +167,7 @@ public:
|
||||||
|
|
||||||
/* Registers a running child process. `inBuildSlot' means that
|
/* Registers a running child process. `inBuildSlot' means that
|
||||||
the process counts towards the jobs limit. */
|
the process counts towards the jobs limit. */
|
||||||
void childStarted(GoalPtr goal, const set<int> & fds,
|
void childStarted(GoalPtr goal, const std::set<int> & fds,
|
||||||
bool inBuildSlot, bool respectTimeouts);
|
bool inBuildSlot, bool respectTimeouts);
|
||||||
|
|
||||||
/* Unregisters a running child process. `wakeSleepers' should be
|
/* Unregisters a running child process. `wakeSleepers' should be
|
||||||
|
|
|
@ -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)) {
|
||||||
|
@ -123,7 +125,7 @@ void buildProfile(const Path & out, Packages && pkgs)
|
||||||
createLinks(state, pkgDir, out, priority);
|
createLinks(state, pkgDir, out, priority);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
for (const auto & p : tokenizeString<std::vector<string>>(
|
for (const auto & p : tokenizeString<std::vector<std::string>>(
|
||||||
readFile(pkgDir + "/nix-support/propagated-user-env-packages"), " \n"))
|
readFile(pkgDir + "/nix-support/propagated-user-env-packages"), " \n"))
|
||||||
if (!done.count(p))
|
if (!done.count(p))
|
||||||
postponed.insert(p);
|
postponed.insert(p);
|
||||||
|
@ -161,7 +163,7 @@ void buildProfile(const Path & out, Packages && pkgs)
|
||||||
|
|
||||||
void builtinBuildenv(const BasicDerivation & drv)
|
void builtinBuildenv(const BasicDerivation & drv)
|
||||||
{
|
{
|
||||||
auto getAttr = [&](const string & name) {
|
auto getAttr = [&](const std::string & name) {
|
||||||
auto i = drv.env.find(name);
|
auto i = drv.env.find(name);
|
||||||
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
||||||
return i->second;
|
return i->second;
|
||||||
|
|
|
@ -16,7 +16,7 @@ void builtinFetchurl(const BasicDerivation & drv, const std::string & netrcData)
|
||||||
writeFile(settings.netrcFile, netrcData, 0600);
|
writeFile(settings.netrcFile, netrcData, 0600);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto getAttr = [&](const string & name) {
|
auto getAttr = [&](const std::string & name) {
|
||||||
auto i = drv.env.find(name);
|
auto i = drv.env.find(name);
|
||||||
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
||||||
return i->second;
|
return i->second;
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace nix {
|
||||||
|
|
||||||
void builtinUnpackChannel(const BasicDerivation & drv)
|
void builtinUnpackChannel(const BasicDerivation & drv)
|
||||||
{
|
{
|
||||||
auto getAttr = [&](const string & name) {
|
auto getAttr = [&](const std::string & name) {
|
||||||
auto i = drv.env.find(name);
|
auto i = drv.env.find(name);
|
||||||
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
if (i == drv.env.end()) throw Error("attribute '%s' missing", name);
|
||||||
return i->second;
|
return i->second;
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
#include "daemon.hh"
|
#include "daemon.hh"
|
||||||
#include "monitor-fd.hh"
|
#include "monitor-fd.hh"
|
||||||
#include "worker-protocol.hh"
|
#include "worker-protocol.hh"
|
||||||
|
#include "build-result.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "store-cast.hh"
|
||||||
|
#include "gc-store.hh"
|
||||||
|
#include "log-store.hh"
|
||||||
#include "path-with-outputs.hh"
|
#include "path-with-outputs.hh"
|
||||||
#include "finally.hh"
|
#include "finally.hh"
|
||||||
#include "archive.hh"
|
#include "archive.hh"
|
||||||
|
@ -479,8 +483,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
}
|
}
|
||||||
|
|
||||||
case wopAddTextToStore: {
|
case wopAddTextToStore: {
|
||||||
string suffix = readString(from);
|
std::string suffix = readString(from);
|
||||||
string s = readString(from);
|
std::string s = readString(from);
|
||||||
auto refs = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
auto refs = worker_proto::read(*store, from, Phantom<StorePathSet> {});
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
auto path = store->addTextToStore(suffix, s, refs, NoRepair);
|
auto path = store->addTextToStore(suffix, s, refs, NoRepair);
|
||||||
|
@ -530,6 +534,25 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case wopBuildPathsWithResults: {
|
||||||
|
auto drvs = readDerivedPaths(*store, clientVersion, from);
|
||||||
|
BuildMode mode = bmNormal;
|
||||||
|
mode = (BuildMode) readInt(from);
|
||||||
|
|
||||||
|
/* Repairing is not atomic, so disallowed for "untrusted"
|
||||||
|
clients. */
|
||||||
|
if (mode == bmRepair && !trusted)
|
||||||
|
throw Error("repairing is not allowed because you are not in 'trusted-users'");
|
||||||
|
|
||||||
|
logger->startWork();
|
||||||
|
auto results = store->buildPathsWithResults(drvs, mode);
|
||||||
|
logger->stopWork();
|
||||||
|
|
||||||
|
worker_proto::write(*store, to, results);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case wopBuildDerivation: {
|
case wopBuildDerivation: {
|
||||||
auto drvPath = store->parseStorePath(readString(from));
|
auto drvPath = store->parseStorePath(readString(from));
|
||||||
BasicDerivation drv;
|
BasicDerivation drv;
|
||||||
|
@ -537,6 +560,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
BuildMode buildMode = (BuildMode) readInt(from);
|
BuildMode buildMode = (BuildMode) readInt(from);
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
|
|
||||||
|
auto drvType = drv.type();
|
||||||
|
|
||||||
/* Content-addressed derivations are trustless because their output paths
|
/* Content-addressed derivations are trustless because their output paths
|
||||||
are verified by their content alone, so any derivation is free to
|
are verified by their content alone, so any derivation is free to
|
||||||
try to produce such a path.
|
try to produce such a path.
|
||||||
|
@ -569,12 +594,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
derivations, we throw out the precomputed output paths and just
|
derivations, we throw out the precomputed output paths and just
|
||||||
store the hashes, so there aren't two competing sources of truth an
|
store the hashes, so there aren't two competing sources of truth an
|
||||||
attacker could exploit. */
|
attacker could exploit. */
|
||||||
if (drv.type() == DerivationType::InputAddressed && !trusted)
|
if (!(drvType.isCA() || trusted))
|
||||||
throw Error("you are not privileged to build input-addressed derivations");
|
throw Error("you are not privileged to build input-addressed derivations");
|
||||||
|
|
||||||
/* Make sure that the non-input-addressed derivations that got this far
|
/* Make sure that the non-input-addressed derivations that got this far
|
||||||
are in fact content-addressed if we don't trust them. */
|
are in fact content-addressed if we don't trust them. */
|
||||||
assert(derivationIsCA(drv.type()) || trusted);
|
assert(drvType.isCA() || trusted);
|
||||||
|
|
||||||
/* Recompute the derivation path when we cannot trust the original. */
|
/* Recompute the derivation path when we cannot trust the original. */
|
||||||
if (!trusted) {
|
if (!trusted) {
|
||||||
|
@ -583,7 +608,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
original not-necessarily-resolved derivation to verify the drv
|
original not-necessarily-resolved derivation to verify the drv
|
||||||
derivation as adequate claim to the input-addressed output
|
derivation as adequate claim to the input-addressed output
|
||||||
paths. */
|
paths. */
|
||||||
assert(derivationIsCA(drv.type()));
|
assert(drvType.isCA());
|
||||||
|
|
||||||
Derivation drv2;
|
Derivation drv2;
|
||||||
static_cast<BasicDerivation &>(drv2) = drv;
|
static_cast<BasicDerivation &>(drv2) = drv;
|
||||||
|
@ -622,9 +647,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
|
|
||||||
case wopAddIndirectRoot: {
|
case wopAddIndirectRoot: {
|
||||||
Path path = absPath(readString(from));
|
Path path = absPath(readString(from));
|
||||||
|
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
store->addIndirectRoot(path);
|
auto & gcStore = require<GcStore>(*store);
|
||||||
|
gcStore.addIndirectRoot(path);
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
|
|
||||||
to << 1;
|
to << 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -639,7 +667,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
|
|
||||||
case wopFindRoots: {
|
case wopFindRoots: {
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
Roots roots = store->findRoots(!trusted);
|
auto & gcStore = require<GcStore>(*store);
|
||||||
|
Roots roots = gcStore.findRoots(!trusted);
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
|
|
||||||
size_t size = 0;
|
size_t size = 0;
|
||||||
|
@ -670,7 +699,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
if (options.ignoreLiveness)
|
if (options.ignoreLiveness)
|
||||||
throw Error("you are not allowed to ignore liveness");
|
throw Error("you are not allowed to ignore liveness");
|
||||||
store->collectGarbage(options, results);
|
auto & gcStore = require<GcStore>(*store);
|
||||||
|
gcStore.collectGarbage(options, results);
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
|
|
||||||
to << results.paths << results.bytesFreed << 0 /* obsolete */;
|
to << results.paths << results.bytesFreed << 0 /* obsolete */;
|
||||||
|
@ -698,8 +728,8 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
|
if (GET_PROTOCOL_MINOR(clientVersion) >= 12) {
|
||||||
unsigned int n = readInt(from);
|
unsigned int n = readInt(from);
|
||||||
for (unsigned int i = 0; i < n; i++) {
|
for (unsigned int i = 0; i < n; i++) {
|
||||||
string name = readString(from);
|
auto name = readString(from);
|
||||||
string value = readString(from);
|
auto value = readString(from);
|
||||||
clientSettings.overrides.emplace(name, value);
|
clientSettings.overrides.emplace(name, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -927,11 +957,12 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
if (!trusted)
|
if (!trusted)
|
||||||
throw Error("you are not privileged to add logs");
|
throw Error("you are not privileged to add logs");
|
||||||
|
auto & logStore = require<LogStore>(*store);
|
||||||
{
|
{
|
||||||
FramedSource source(from);
|
FramedSource source(from);
|
||||||
StringSink sink;
|
StringSink sink;
|
||||||
source.drainInto(sink);
|
source.drainInto(sink);
|
||||||
store->addBuildLog(path, sink.s);
|
logStore.addBuildLog(path, sink.s);
|
||||||
}
|
}
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
to << 1;
|
to << 1;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue