forked from lix-project/lix
Merge branch 'master' into lto
This commit is contained in:
commit
d6d6bbd9ef
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
|
@ -8,7 +8,7 @@ jobs:
|
||||||
if: github.repository_owner == 'NixOS' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
|
if: github.repository_owner == 'NixOS' && github.event.pull_request.merged == true && (github.event_name != 'labeled' || startsWith('backport', github.event.label.name))
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
# required to find all branches
|
# required to find all branches
|
||||||
|
|
16
.github/workflows/ci.yml
vendored
16
.github/workflows/ci.yml
vendored
|
@ -14,10 +14,10 @@ jobs:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: cachix/install-nix-action@v16
|
- uses: cachix/install-nix-action@v17
|
||||||
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
|
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
|
||||||
- uses: cachix/cachix-action@v10
|
- uses: cachix/cachix-action@v10
|
||||||
if: needs.check_cachix.outputs.secret == 'true'
|
if: needs.check_cachix.outputs.secret == 'true'
|
||||||
|
@ -46,11 +46,11 @@ jobs:
|
||||||
outputs:
|
outputs:
|
||||||
installerURL: ${{ steps.prepare-installer.outputs.installerURL }}
|
installerURL: ${{ steps.prepare-installer.outputs.installerURL }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
|
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
|
||||||
- uses: cachix/install-nix-action@v16
|
- uses: cachix/install-nix-action@v17
|
||||||
- uses: cachix/cachix-action@v10
|
- uses: cachix/cachix-action@v10
|
||||||
with:
|
with:
|
||||||
name: '${{ env.CACHIX_NAME }}'
|
name: '${{ env.CACHIX_NAME }}'
|
||||||
|
@ -67,9 +67,9 @@ jobs:
|
||||||
os: [ubuntu-latest, macos-latest]
|
os: [ubuntu-latest, macos-latest]
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3
|
||||||
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
|
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
|
||||||
- uses: cachix/install-nix-action@v16
|
- uses: cachix/install-nix-action@v17
|
||||||
with:
|
with:
|
||||||
install_url: '${{needs.installer.outputs.installerURL}}'
|
install_url: '${{needs.installer.outputs.installerURL}}'
|
||||||
install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve"
|
install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve"
|
||||||
|
@ -83,10 +83,10 @@ jobs:
|
||||||
needs.check_cachix.outputs.secret == 'true'
|
needs.check_cachix.outputs.secret == 'true'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- uses: cachix/install-nix-action@v16
|
- uses: cachix/install-nix-action@v17
|
||||||
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
|
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
|
||||||
- run: echo NIX_VERSION="$(nix-instantiate --eval -E '(import ./default.nix).defaultPackage.${builtins.currentSystem}.version' | tr -d \")" >> $GITHUB_ENV
|
- run: echo NIX_VERSION="$(nix-instantiate --eval -E '(import ./default.nix).defaultPackage.${builtins.currentSystem}.version' | tr -d \")" >> $GITHUB_ENV
|
||||||
- uses: cachix/cachix-action@v10
|
- uses: cachix/cachix-action@v10
|
||||||
|
|
2
.github/workflows/hydra_status.yml
vendored
2
.github/workflows/hydra_status.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
||||||
if: github.repository_owner == 'NixOS'
|
if: github.repository_owner == 'NixOS'
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2.4.0
|
- uses: actions/checkout@v3
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- run: bash scripts/check-hydra-status.sh
|
- run: bash scripts/check-hydra-status.sh
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -79,6 +79,7 @@ perl/Makefile.config
|
||||||
/tests/shell.drv
|
/tests/shell.drv
|
||||||
/tests/config.nix
|
/tests/config.nix
|
||||||
/tests/ca/config.nix
|
/tests/ca/config.nix
|
||||||
|
/tests/repl-result-out
|
||||||
|
|
||||||
# /tests/lang/
|
# /tests/lang/
|
||||||
/tests/lang/*.out
|
/tests/lang/*.out
|
||||||
|
@ -90,6 +91,7 @@ perl/Makefile.config
|
||||||
|
|
||||||
/misc/systemd/nix-daemon.service
|
/misc/systemd/nix-daemon.service
|
||||||
/misc/systemd/nix-daemon.socket
|
/misc/systemd/nix-daemon.socket
|
||||||
|
/misc/systemd/nix-daemon.conf
|
||||||
/misc/upstart/nix-daemon.conf
|
/misc/upstart/nix-daemon.conf
|
||||||
|
|
||||||
/src/resolve-system-dependencies/resolve-system-dependencies
|
/src/resolve-system-dependencies/resolve-system-dependencies
|
||||||
|
|
|
@ -6,7 +6,8 @@ options:
|
||||||
concatStrings (map
|
concatStrings (map
|
||||||
(name:
|
(name:
|
||||||
let option = options.${name}; in
|
let option = options.${name}; in
|
||||||
" - `${name}` \n\n"
|
" - [`${name}`](#conf-${name})"
|
||||||
|
+ "<p id=\"conf-${name}\"></p>\n\n"
|
||||||
+ concatStrings (map (s: " ${s}\n") (splitLines option.description)) + "\n\n"
|
+ concatStrings (map (s: " ${s}\n") (splitLines option.description)) + "\n\n"
|
||||||
+ (if option.documentDefault
|
+ (if option.documentDefault
|
||||||
then " **Default:** " + (
|
then " **Default:** " + (
|
||||||
|
|
|
@ -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.8 (2022-04-19)](release-notes/rl-2.8.md)
|
||||||
- [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md)
|
- [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md)
|
||||||
- [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md)
|
- [Release 2.6 (2022-01-24)](release-notes/rl-2.6.md)
|
||||||
- [Release 2.5 (2021-12-13)](release-notes/rl-2.5.md)
|
- [Release 2.5 (2021-12-13)](release-notes/rl-2.5.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
|
||||||
|
|
|
@ -84,7 +84,9 @@ The installer will modify `/etc/bashrc`, and `/etc/zshrc` if they exist.
|
||||||
The installer will first back up these files with a `.backup-before-nix`
|
The installer will first back up these files with a `.backup-before-nix`
|
||||||
extension. The installer will also create `/etc/profile.d/nix.sh`.
|
extension. The installer will also create `/etc/profile.d/nix.sh`.
|
||||||
|
|
||||||
You can uninstall Nix with the following commands:
|
## Uninstalling
|
||||||
|
|
||||||
|
### Linux
|
||||||
|
|
||||||
```console
|
```console
|
||||||
sudo rm -rf /etc/profile/nix.sh /etc/nix /nix ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
|
sudo rm -rf /etc/profile/nix.sh /etc/nix /nix ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
|
||||||
|
@ -95,15 +97,95 @@ sudo systemctl stop nix-daemon.service
|
||||||
sudo systemctl disable nix-daemon.socket
|
sudo systemctl disable nix-daemon.socket
|
||||||
sudo systemctl disable nix-daemon.service
|
sudo systemctl disable nix-daemon.service
|
||||||
sudo systemctl daemon-reload
|
sudo systemctl daemon-reload
|
||||||
|
|
||||||
# If you are on macOS, you will need to run:
|
|
||||||
sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
|
||||||
sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
|
||||||
```
|
```
|
||||||
|
|
||||||
There may also be references to Nix in `/etc/profile`, `/etc/bashrc`,
|
There may also be references to Nix in `/etc/profile`, `/etc/bashrc`,
|
||||||
and `/etc/zshrc` which you may remove.
|
and `/etc/zshrc` which you may remove.
|
||||||
|
|
||||||
|
### macOS
|
||||||
|
|
||||||
|
1. Edit `/etc/zshrc` and `/etc/bashrc` to remove the lines sourcing
|
||||||
|
`nix-daemon.sh`, which should look like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Nix
|
||||||
|
if [ -e '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh' ]; then
|
||||||
|
. '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'
|
||||||
|
fi
|
||||||
|
# End Nix
|
||||||
|
```
|
||||||
|
|
||||||
|
If these files haven't been altered since installing Nix you can simply put
|
||||||
|
the backups back in place:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo mv /etc/zshrc.backup-before-nix /etc/zshrc
|
||||||
|
sudo mv /etc/bashrc.backup-before-nix /etc/bashrc
|
||||||
|
```
|
||||||
|
|
||||||
|
This will stop shells from sourcing the file and bringing everything you
|
||||||
|
installed using Nix in scope.
|
||||||
|
|
||||||
|
2. Stop and remove the Nix daemon services:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo launchctl unload /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
||||||
|
sudo rm /Library/LaunchDaemons/org.nixos.nix-daemon.plist
|
||||||
|
sudo launchctl unload /Library/LaunchDaemons/org.nixos.darwin-store.plist
|
||||||
|
sudo rm /Library/LaunchDaemons/org.nixos.darwin-store.plist
|
||||||
|
```
|
||||||
|
|
||||||
|
This stops the Nix daemon and prevents it from being started next time you
|
||||||
|
boot the system.
|
||||||
|
|
||||||
|
3. Remove the `nixbld` group and the `_nixbuildN` users:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo dscl . -delete /Groups/nixbld
|
||||||
|
for u in $(sudo dscl . -list /Users | grep _nixbld); do sudo dscl . -delete /Users/$u; done
|
||||||
|
```
|
||||||
|
|
||||||
|
This will remove all the build users that no longer serve a purpose.
|
||||||
|
|
||||||
|
4. Edit fstab using `sudo vifs` to remove the line mounting the Nix Store
|
||||||
|
volume on `/nix`, which looks like this,
|
||||||
|
`LABEL=Nix\040Store /nix apfs rw,nobrowse`. This will prevent automatic
|
||||||
|
mounting of the Nix Store volume.
|
||||||
|
|
||||||
|
5. Edit `/etc/synthetic.conf` to remove the `nix` line. If this is the only
|
||||||
|
line in the file you can remove it entirely, `sudo rm /etc/synthetic.conf`.
|
||||||
|
This will prevent the creation of the empty `/nix` directory to provide a
|
||||||
|
mountpoint for the Nix Store volume.
|
||||||
|
|
||||||
|
6. Remove the files Nix added to your system:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo rm -rf /etc/nix /var/root/.nix-profile /var/root/.nix-defexpr /var/root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels
|
||||||
|
```
|
||||||
|
|
||||||
|
This gets rid of any data Nix may have created except for the store which is
|
||||||
|
removed next.
|
||||||
|
|
||||||
|
7. Remove the Nix Store volume:
|
||||||
|
|
||||||
|
```console
|
||||||
|
sudo diskutil apfs deleteVolume /nix
|
||||||
|
```
|
||||||
|
|
||||||
|
This will remove the Nix Store volume and everything that was added to the
|
||||||
|
store.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> After you complete the steps here, you will still have an empty `/nix`
|
||||||
|
> directory. This is an expected sign of a successful uninstall. The empty
|
||||||
|
> `/nix` directory will disappear the next time you reboot.
|
||||||
|
>
|
||||||
|
> You do not have to reboot to finish uninstalling Nix. The uninstall is
|
||||||
|
> complete. macOS (Catalina+) directly controls root directories and its
|
||||||
|
> read-only root will prevent you from manually deleting the empty `/nix`
|
||||||
|
> mountpoint.
|
||||||
|
|
||||||
# macOS Installation <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a>
|
# macOS Installation <a name="sect-macos-installation-change-store-prefix"></a><a name="sect-macos-installation-encrypted-volume"></a><a name="sect-macos-installation-symlink"></a><a name="sect-macos-installation-recommended-notes"></a>
|
||||||
<!-- Note: anchors above to catch permalinks to old explanations -->
|
<!-- Note: anchors above to catch permalinks to old explanations -->
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Release X.Y (2022-03-07)
|
# Release 2.7 (2022-03-07)
|
||||||
|
|
||||||
* Nix will now make some helpful suggestions when you mistype
|
* Nix will now make some helpful suggestions when you mistype
|
||||||
something on the command line. For instance, if you type `nix build
|
something on the command line. For instance, if you type `nix build
|
||||||
|
|
53
doc/manual/src/release-notes/rl-2.8.md
Normal file
53
doc/manual/src/release-notes/rl-2.8.md
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# Release 2.8 (2022-04-19)
|
||||||
|
|
||||||
|
* New experimental command: `nix fmt`, which applies a formatter
|
||||||
|
defined by the `formatter.<system>` flake output to the Nix
|
||||||
|
expressions in a flake.
|
||||||
|
|
||||||
|
* Various Nix commands can now read expressions from standard input
|
||||||
|
using `--file -`.
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
* `nix store make-content-addressable` has been renamed to `nix store
|
||||||
|
make-content-addressed`.
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
|
* `nix run` is now stricter in what it accepts: members of the `apps`
|
||||||
|
flake output are now required to be apps (as defined in [the
|
||||||
|
manual](https://nixos.org/manual/nix/stable/command-ref/new-cli/nix3-run.html#apps)),
|
||||||
|
and members of `packages` or `legacyPackages` must be derivations
|
||||||
|
(not apps).
|
|
@ -1,4 +1,14 @@
|
||||||
# Release X.Y (202?-??-??)
|
# Release X.Y (202?-??-??)
|
||||||
|
|
||||||
|
* `nix repl` has a new build-'n-link (`:bl`) command that builds a derivation
|
||||||
|
while creating GC root symlinks.
|
||||||
|
|
||||||
|
* The path produced by `builtins.toFile` is now allowed to be imported or read
|
||||||
|
even with restricted evaluation. Note that this will not work with a
|
||||||
|
read-only store.
|
||||||
|
|
||||||
|
* `nix build` has a new `--print-out-paths` flag to print the resulting output paths.
|
||||||
|
This matches the default behaviour of `nix-build`.
|
||||||
|
|
||||||
* Nix can now be built with LTO by passing `--enable-lto` to `configure`.
|
* Nix can now be built with LTO by passing `--enable-lto` to `configure`.
|
||||||
LTO is currently only supported when building with GCC.
|
LTO is currently only supported when building with GCC.
|
|
@ -22,6 +22,7 @@ let
|
||||||
findutils
|
findutils
|
||||||
iana-etc
|
iana-etc
|
||||||
git
|
git
|
||||||
|
openssh
|
||||||
];
|
];
|
||||||
|
|
||||||
users = {
|
users = {
|
||||||
|
|
|
@ -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,7 +15,7 @@ function _complete_nix {
|
||||||
else
|
else
|
||||||
COMPREPLY+=("$completion")
|
COMPREPLY+=("$completion")
|
||||||
fi
|
fi
|
||||||
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]/#\~/$HOME}" 2>/dev/null)
|
done < <(NIX_GET_COMPLETIONS=$cword "${words[@]}" 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]
|
||||||
|
|
|
@ -14,7 +14,7 @@ curl -sS -H 'Accept: application/json' https://hydra.nixos.org/jobset/nix/master
|
||||||
someBuildFailed=0
|
someBuildFailed=0
|
||||||
|
|
||||||
for buildId in $BUILDS_FOR_LATEST_EVAL; do
|
for buildId in $BUILDS_FOR_LATEST_EVAL; do
|
||||||
buildInfo=$(curl -sS -H 'Accept: application/json' "https://hydra.nixos.org/build/$buildId")
|
buildInfo=$(curl --fail -sS -H 'Accept: application/json' "https://hydra.nixos.org/build/$buildId")
|
||||||
|
|
||||||
finished=$(echo "$buildInfo" | jq -r '.finished')
|
finished=$(echo "$buildInfo" | jq -r '.finished')
|
||||||
|
|
||||||
|
|
|
@ -423,6 +423,18 @@ EOF
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
if [ "$(uname -s)" = "Linux" ] && [ ! -e /run/systemd/system ]; then
|
||||||
|
warning <<EOF
|
||||||
|
We did not detect systemd on your system. With a multi-user install
|
||||||
|
without systemd you will have to manually configure your init system to
|
||||||
|
launch the Nix daemon after installation.
|
||||||
|
EOF
|
||||||
|
if ! ui_confirm "Do you want to proceed with a multi-user installation?"; then
|
||||||
|
failure <<EOF
|
||||||
|
You have aborted the installation.
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
setup_report() {
|
setup_report() {
|
||||||
|
@ -739,7 +751,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
|
||||||
|
|
|
@ -300,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 };
|
||||||
|
|
|
@ -197,16 +197,17 @@ void StorePathCommand::run(ref<Store> store, std::vector<StorePath> && storePath
|
||||||
run(store, *storePaths.begin());
|
run(store, *storePaths.begin());
|
||||||
}
|
}
|
||||||
|
|
||||||
Strings editorFor(const Pos & pos)
|
Strings editorFor(const Path & file, uint32_t line)
|
||||||
{
|
{
|
||||||
auto editor = getEnv("EDITOR").value_or("cat");
|
auto editor = getEnv("EDITOR").value_or("cat");
|
||||||
auto args = tokenizeString<Strings>(editor);
|
auto args = tokenizeString<Strings>(editor);
|
||||||
if (pos.line > 0 && (
|
if (line > 0 && (
|
||||||
editor.find("emacs") != std::string::npos ||
|
editor.find("emacs") != std::string::npos ||
|
||||||
editor.find("nano") != std::string::npos ||
|
editor.find("nano") != std::string::npos ||
|
||||||
editor.find("vim") != std::string::npos))
|
editor.find("vim") != std::string::npos ||
|
||||||
args.push_back(fmt("+%d", pos.line));
|
editor.find("kak") != std::string::npos))
|
||||||
args.push_back(pos.file);
|
args.push_back(fmt("+%d", line));
|
||||||
|
args.push_back(file);
|
||||||
return args;
|
return args;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,11 +85,12 @@ struct SourceExprCommand : virtual Args, MixFlakeOptions
|
||||||
{
|
{
|
||||||
std::optional<Path> file;
|
std::optional<Path> file;
|
||||||
std::optional<std::string> expr;
|
std::optional<std::string> expr;
|
||||||
|
bool readOnlyMode = false;
|
||||||
|
|
||||||
// FIXME: move this; not all commands (e.g. 'nix run') use it.
|
// FIXME: move this; not all commands (e.g. 'nix run') use it.
|
||||||
OperateOn operateOn = OperateOn::Output;
|
OperateOn operateOn = OperateOn::Output;
|
||||||
|
|
||||||
SourceExprCommand();
|
SourceExprCommand(bool supportReadOnlyMode = false);
|
||||||
|
|
||||||
std::vector<std::shared_ptr<Installable>> parseInstallables(
|
std::vector<std::shared_ptr<Installable>> parseInstallables(
|
||||||
ref<Store> store, std::vector<std::string> ss);
|
ref<Store> store, std::vector<std::string> ss);
|
||||||
|
@ -128,13 +129,13 @@ struct InstallableCommand : virtual Args, SourceExprCommand
|
||||||
{
|
{
|
||||||
std::shared_ptr<Installable> installable;
|
std::shared_ptr<Installable> installable;
|
||||||
|
|
||||||
InstallableCommand();
|
InstallableCommand(bool supportReadOnlyMode = false);
|
||||||
|
|
||||||
void prepare() override;
|
void prepare() override;
|
||||||
|
|
||||||
std::optional<FlakeRef> getFlakeRefForCompletion() override
|
std::optional<FlakeRef> getFlakeRefForCompletion() override
|
||||||
{
|
{
|
||||||
return parseFlakeRef(_installable, absPath("."));
|
return parseFlakeRefWithFragment(_installable, absPath(".")).first;
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -218,7 +219,7 @@ static RegisterCommand registerCommand2(std::vector<std::string> && name)
|
||||||
|
|
||||||
/* 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 Path & file, uint32_t line);
|
||||||
|
|
||||||
struct MixProfile : virtual StoreCommand
|
struct MixProfile : virtual StoreCommand
|
||||||
{
|
{
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include "registry.hh"
|
#include "registry.hh"
|
||||||
#include "flake/flakeref.hh"
|
#include "flake/flakeref.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "command.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -59,6 +60,9 @@ MixEvalArgs::MixEvalArgs()
|
||||||
fetchers::Attrs extraAttrs;
|
fetchers::Attrs extraAttrs;
|
||||||
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
|
if (to.subdir != "") extraAttrs["dir"] = to.subdir;
|
||||||
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
|
fetchers::overrideRegistry(from.input, to.input, extraAttrs);
|
||||||
|
}},
|
||||||
|
.completer = {[&](size_t, std::string_view prefix) {
|
||||||
|
completeFlakeRef(openStore(), prefix);
|
||||||
}}
|
}}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
#include "globals.hh"
|
||||||
#include "installables.hh"
|
#include "installables.hh"
|
||||||
|
#include "util.hh"
|
||||||
#include "command.hh"
|
#include "command.hh"
|
||||||
#include "attr-path.hh"
|
#include "attr-path.hh"
|
||||||
#include "common-eval-args.hh"
|
#include "common-eval-args.hh"
|
||||||
|
@ -99,6 +101,14 @@ MixFlakeOptions::MixFlakeOptions()
|
||||||
lockFlags.inputOverrides.insert_or_assign(
|
lockFlags.inputOverrides.insert_or_assign(
|
||||||
flake::parseInputPath(inputPath),
|
flake::parseInputPath(inputPath),
|
||||||
parseFlakeRef(flakeRef, absPath("."), true));
|
parseFlakeRef(flakeRef, absPath("."), true));
|
||||||
|
}},
|
||||||
|
.completer = {[&](size_t n, std::string_view prefix) {
|
||||||
|
if (n == 0) {
|
||||||
|
if (auto flakeRef = getFlakeRefForCompletion())
|
||||||
|
completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
|
||||||
|
} else if (n == 1) {
|
||||||
|
completeFlakeRef(getEvalState()->store, prefix);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -129,12 +139,14 @@ MixFlakeOptions::MixFlakeOptions()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceExprCommand::SourceExprCommand()
|
SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
|
||||||
{
|
{
|
||||||
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},
|
||||||
|
@ -155,6 +167,17 @@ SourceExprCommand::SourceExprCommand()
|
||||||
.category = installablesCategory,
|
.category = installablesCategory,
|
||||||
.handler = {&operateOn, OperateOn::Derivation},
|
.handler = {&operateOn, OperateOn::Derivation},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (supportReadOnlyMode) {
|
||||||
|
addFlag({
|
||||||
|
.longName = "read-only",
|
||||||
|
.description =
|
||||||
|
"Do not instantiate each evaluated derivation. "
|
||||||
|
"This improves performance, but can cause errors when accessing "
|
||||||
|
"store paths of derivations during evaluation.",
|
||||||
|
.handler = {&readOnlyMode, true},
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Strings SourceExprCommand::getDefaultFlakeAttrPaths()
|
Strings SourceExprCommand::getDefaultFlakeAttrPaths()
|
||||||
|
@ -180,6 +203,8 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
|
||||||
void SourceExprCommand::completeInstallable(std::string_view prefix)
|
void SourceExprCommand::completeInstallable(std::string_view prefix)
|
||||||
{
|
{
|
||||||
if (file) {
|
if (file) {
|
||||||
|
completionType = ctAttrs;
|
||||||
|
|
||||||
evalSettings.pureEval = false;
|
evalSettings.pureEval = false;
|
||||||
auto state = getEvalState();
|
auto state = getEvalState();
|
||||||
Expr *e = state->parseExprFromFile(
|
Expr *e = state->parseExprFromFile(
|
||||||
|
@ -208,13 +233,14 @@ void SourceExprCommand::completeInstallable(std::string_view prefix)
|
||||||
Value v2;
|
Value v2;
|
||||||
state->autoCallFunction(*autoArgs, v1, v2);
|
state->autoCallFunction(*autoArgs, v1, v2);
|
||||||
|
|
||||||
completionType = ctAttrs;
|
|
||||||
|
|
||||||
if (v2.type() == nAttrs) {
|
if (v2.type() == nAttrs) {
|
||||||
for (auto & i : *v2.attrs) {
|
for (auto & i : *v2.attrs) {
|
||||||
std::string name = i.name;
|
std::string name = state->symbols[i.name];
|
||||||
if (name.find(searchWord) == 0) {
|
if (name.find(searchWord) == 0) {
|
||||||
completions->add(i.name);
|
if (prefix_ == "")
|
||||||
|
completions->add(name);
|
||||||
|
else
|
||||||
|
completions->add(prefix_ + "." + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -242,10 +268,11 @@ void completeFlakeRefWithFragment(
|
||||||
if (hash == std::string::npos) {
|
if (hash == std::string::npos) {
|
||||||
completeFlakeRef(evalState->store, prefix);
|
completeFlakeRef(evalState->store, prefix);
|
||||||
} else {
|
} else {
|
||||||
|
completionType = ctAttrs;
|
||||||
|
|
||||||
auto fragment = prefix.substr(hash + 1);
|
auto fragment = prefix.substr(hash + 1);
|
||||||
auto flakeRefS = std::string(prefix.substr(0, hash));
|
auto flakeRefS = std::string(prefix.substr(0, hash));
|
||||||
// FIXME: do tilde expansion.
|
auto flakeRef = parseFlakeRef(expandTilde(flakeRefS), absPath("."));
|
||||||
auto flakeRef = parseFlakeRef(flakeRefS, absPath("."));
|
|
||||||
|
|
||||||
auto evalCache = openEvalCache(*evalState,
|
auto evalCache = openEvalCache(*evalState,
|
||||||
std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
|
std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
|
||||||
|
@ -257,8 +284,6 @@ void completeFlakeRefWithFragment(
|
||||||
flake. */
|
flake. */
|
||||||
attrPathPrefixes.push_back("");
|
attrPathPrefixes.push_back("");
|
||||||
|
|
||||||
completionType = ctAttrs;
|
|
||||||
|
|
||||||
for (auto & attrPathPrefixS : attrPathPrefixes) {
|
for (auto & attrPathPrefixS : attrPathPrefixes) {
|
||||||
auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
|
auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
|
||||||
auto attrPathS = attrPathPrefixS + std::string(fragment);
|
auto attrPathS = attrPathPrefixS + std::string(fragment);
|
||||||
|
@ -332,16 +357,16 @@ DerivedPath Installable::toDerivedPath()
|
||||||
return std::move(buildables[0]);
|
return std::move(buildables[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
std::vector<ref<eval_cache::AttrCursor>>
|
||||||
Installable::getCursors(EvalState & state)
|
Installable::getCursors(EvalState & state)
|
||||||
{
|
{
|
||||||
auto evalCache =
|
auto evalCache =
|
||||||
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state,
|
std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state,
|
||||||
[&]() { return toValue(state).first; });
|
[&]() { return toValue(state).first; });
|
||||||
return {{evalCache->getRoot(), ""}};
|
return {evalCache->getRoot()};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
|
ref<eval_cache::AttrCursor>
|
||||||
Installable::getCursor(EvalState & state)
|
Installable::getCursor(EvalState & state)
|
||||||
{
|
{
|
||||||
auto cursors = getCursors(state);
|
auto cursors = getCursors(state);
|
||||||
|
@ -448,7 +473,7 @@ struct InstallableAttrPath : InstallableValue
|
||||||
|
|
||||||
std::string what() const override { return attrPath; }
|
std::string what() const override { return attrPath; }
|
||||||
|
|
||||||
std::pair<Value *, Pos> toValue(EvalState & state) override
|
std::pair<Value *, PosIdx> toValue(EvalState & state) override
|
||||||
{
|
{
|
||||||
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
|
auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
|
||||||
state.forceValue(*vRes, pos);
|
state.forceValue(*vRes, pos);
|
||||||
|
@ -564,43 +589,21 @@ InstallableFlake::InstallableFlake(
|
||||||
|
|
||||||
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
|
std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
|
||||||
{
|
{
|
||||||
auto lockedFlake = getLockedFlake();
|
auto attr = getCursor(*state);
|
||||||
|
|
||||||
auto cache = openEvalCache(*state, lockedFlake);
|
auto attrPath = attr->getAttrPathStr();
|
||||||
auto root = cache->getRoot();
|
|
||||||
|
|
||||||
Suggestions suggestions;
|
if (!attr->isDerivation())
|
||||||
|
throw Error("flake output attribute '%s' is not a derivation", attrPath);
|
||||||
|
|
||||||
for (auto & attrPath : getActualAttrPaths()) {
|
auto drvPath = attr->forceDerivation();
|
||||||
debug("trying flake output attribute '%s'", attrPath);
|
|
||||||
|
|
||||||
auto attrOrSuggestions = root->findAlongAttrPath(
|
auto drvInfo = DerivationInfo {
|
||||||
parseAttrPath(*state, attrPath),
|
std::move(drvPath),
|
||||||
true
|
attr->getAttr("outputName")->getString()
|
||||||
);
|
};
|
||||||
|
|
||||||
if (!attrOrSuggestions) {
|
return {attrPath, getLockedFlake()->flake.lockedRef, std::move(drvInfo)};
|
||||||
suggestions += attrOrSuggestions.getSuggestions();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto attr = *attrOrSuggestions;
|
|
||||||
|
|
||||||
if (!attr->isDerivation())
|
|
||||||
throw Error("flake output attribute '%s' is not a derivation", attrPath);
|
|
||||||
|
|
||||||
auto drvPath = attr->forceDerivation();
|
|
||||||
|
|
||||||
auto drvInfo = DerivationInfo {
|
|
||||||
std::move(drvPath),
|
|
||||||
attr->getAttr(state->sOutputName)->getString()
|
|
||||||
};
|
|
||||||
|
|
||||||
return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Error(suggestions, "flake '%s' does not provide attribute %s",
|
|
||||||
flakeRef, showAttrPaths(getActualAttrPaths()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
|
std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
|
||||||
|
@ -610,35 +613,12 @@ std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
|
std::pair<Value *, PosIdx> InstallableFlake::toValue(EvalState & state)
|
||||||
{
|
{
|
||||||
auto lockedFlake = getLockedFlake();
|
return {&getCursor(state)->forceValue(), noPos};
|
||||||
|
|
||||||
auto vOutputs = getFlakeOutputs(state, *lockedFlake);
|
|
||||||
|
|
||||||
auto emptyArgs = state.allocBindings(0);
|
|
||||||
|
|
||||||
Suggestions suggestions;
|
|
||||||
|
|
||||||
for (auto & attrPath : getActualAttrPaths()) {
|
|
||||||
try {
|
|
||||||
auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
|
|
||||||
state.forceValue(*v, pos);
|
|
||||||
return {v, pos};
|
|
||||||
} catch (AttrPathNotFound & e) {
|
|
||||||
suggestions += e.info().suggestions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw Error(
|
|
||||||
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<ref<eval_cache::AttrCursor>>
|
||||||
InstallableFlake::getCursors(EvalState & state)
|
InstallableFlake::getCursors(EvalState & state)
|
||||||
{
|
{
|
||||||
auto evalCache = openEvalCache(state,
|
auto evalCache = openEvalCache(state,
|
||||||
|
@ -646,21 +626,55 @@ InstallableFlake::getCursors(EvalState & state)
|
||||||
|
|
||||||
auto root = evalCache->getRoot();
|
auto root = evalCache->getRoot();
|
||||||
|
|
||||||
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> res;
|
std::vector<ref<eval_cache::AttrCursor>> res;
|
||||||
|
|
||||||
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(ref(*attr));
|
||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ref<eval_cache::AttrCursor> InstallableFlake::getCursor(EvalState & state)
|
||||||
|
{
|
||||||
|
auto lockedFlake = getLockedFlake();
|
||||||
|
|
||||||
|
auto cache = openEvalCache(state, lockedFlake);
|
||||||
|
auto root = cache->getRoot();
|
||||||
|
|
||||||
|
Suggestions suggestions;
|
||||||
|
|
||||||
|
auto attrPaths = getActualAttrPaths();
|
||||||
|
|
||||||
|
for (auto & attrPath : attrPaths) {
|
||||||
|
debug("trying flake output attribute '%s'", attrPath);
|
||||||
|
|
||||||
|
auto attrOrSuggestions = root->findAlongAttrPath(
|
||||||
|
parseAttrPath(state, attrPath),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!attrOrSuggestions) {
|
||||||
|
suggestions += attrOrSuggestions.getSuggestions();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return *attrOrSuggestions;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Error(
|
||||||
|
suggestions,
|
||||||
|
"flake '%s' does not provide attribute %s",
|
||||||
|
flakeRef,
|
||||||
|
showAttrPaths(attrPaths));
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
|
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
|
||||||
{
|
{
|
||||||
flake::LockFlags lockFlagsApplyConfig = lockFlags;
|
|
||||||
lockFlagsApplyConfig.applyNixConfig = true;
|
|
||||||
if (!_lockedFlake) {
|
if (!_lockedFlake) {
|
||||||
|
flake::LockFlags lockFlagsApplyConfig = lockFlags;
|
||||||
|
lockFlagsApplyConfig.applyNixConfig = true;
|
||||||
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
|
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
|
||||||
}
|
}
|
||||||
return _lockedFlake;
|
return _lockedFlake;
|
||||||
|
@ -685,6 +699,10 @@ std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
|
||||||
{
|
{
|
||||||
std::vector<std::shared_ptr<Installable>> result;
|
std::vector<std::shared_ptr<Installable>> result;
|
||||||
|
|
||||||
|
if (readOnlyMode) {
|
||||||
|
settings.readOnlyMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (file || expr) {
|
if (file || expr) {
|
||||||
if (file && expr)
|
if (file && expr)
|
||||||
throw UsageError("'--file' and '--expr' are exclusive");
|
throw UsageError("'--file' and '--expr' are exclusive");
|
||||||
|
@ -695,7 +713,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("."));
|
||||||
|
@ -751,55 +772,20 @@ std::shared_ptr<Installable> SourceExprCommand::parseInstallable(
|
||||||
return installables.front();
|
return installables.front();
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltPaths getBuiltPaths(ref<Store> evalStore, ref<Store> store, const DerivedPaths & hopefullyBuiltPaths)
|
BuiltPaths Installable::build(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
Realise mode,
|
||||||
|
const std::vector<std::shared_ptr<Installable>> & installables,
|
||||||
|
BuildMode bMode)
|
||||||
{
|
{
|
||||||
BuiltPaths res;
|
BuiltPaths res;
|
||||||
for (const auto & b : hopefullyBuiltPaths)
|
for (auto & [_, builtPath] : build2(evalStore, store, mode, installables, bMode))
|
||||||
std::visit(
|
res.push_back(builtPath);
|
||||||
overloaded{
|
|
||||||
[&](const DerivedPath::Opaque & bo) {
|
|
||||||
res.push_back(BuiltPath::Opaque{bo.path});
|
|
||||||
},
|
|
||||||
[&](const DerivedPath::Built & bfd) {
|
|
||||||
OutputPathMap outputs;
|
|
||||||
auto drv = evalStore->readDerivation(bfd.drvPath);
|
|
||||||
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
|
||||||
auto drvOutputs = drv.outputsAndOptPaths(*store);
|
|
||||||
for (auto & output : bfd.outputs) {
|
|
||||||
if (!outputHashes.count(output))
|
|
||||||
throw Error(
|
|
||||||
"the derivation '%s' doesn't have an output named '%s'",
|
|
||||||
store->printStorePath(bfd.drvPath), output);
|
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
|
||||||
auto outputId =
|
|
||||||
DrvOutput{outputHashes.at(output), output};
|
|
||||||
auto realisation =
|
|
||||||
store->queryRealisation(outputId);
|
|
||||||
if (!realisation)
|
|
||||||
throw Error(
|
|
||||||
"cannot operate on an output of unbuilt "
|
|
||||||
"content-addressed derivation '%s'",
|
|
||||||
outputId.to_string());
|
|
||||||
outputs.insert_or_assign(
|
|
||||||
output, realisation->outPath);
|
|
||||||
} else {
|
|
||||||
// If ca-derivations isn't enabled, assume that
|
|
||||||
// the output path is statically known.
|
|
||||||
assert(drvOutputs.count(output));
|
|
||||||
assert(drvOutputs.at(output).second);
|
|
||||||
outputs.insert_or_assign(
|
|
||||||
output, *drvOutputs.at(output).second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
res.push_back(BuiltPath::Built{bfd.drvPath, outputs});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
b.raw());
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltPaths Installable::build(
|
std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> Installable::build2(
|
||||||
ref<Store> evalStore,
|
ref<Store> evalStore,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
Realise mode,
|
Realise mode,
|
||||||
|
@ -810,39 +796,93 @@ BuiltPaths Installable::build(
|
||||||
settings.readOnlyMode = true;
|
settings.readOnlyMode = true;
|
||||||
|
|
||||||
std::vector<DerivedPath> pathsToBuild;
|
std::vector<DerivedPath> pathsToBuild;
|
||||||
|
std::map<DerivedPath, std::vector<std::shared_ptr<Installable>>> backmap;
|
||||||
|
|
||||||
for (auto & i : installables) {
|
for (auto & i : installables) {
|
||||||
auto b = i->toDerivedPaths();
|
for (auto b : i->toDerivedPaths()) {
|
||||||
pathsToBuild.insert(pathsToBuild.end(), b.begin(), b.end());
|
pathsToBuild.push_back(b);
|
||||||
|
backmap[b].push_back(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> res;
|
||||||
|
|
||||||
switch (mode) {
|
switch (mode) {
|
||||||
|
|
||||||
case Realise::Nothing:
|
case Realise::Nothing:
|
||||||
case Realise::Derivation:
|
case Realise::Derivation:
|
||||||
printMissing(store, pathsToBuild, lvlError);
|
printMissing(store, pathsToBuild, lvlError);
|
||||||
return getBuiltPaths(evalStore, store, pathsToBuild);
|
|
||||||
|
for (auto & path : pathsToBuild) {
|
||||||
|
for (auto & installable : backmap[path]) {
|
||||||
|
std::visit(overloaded {
|
||||||
|
[&](const DerivedPath::Built & bfd) {
|
||||||
|
OutputPathMap outputs;
|
||||||
|
auto drv = evalStore->readDerivation(bfd.drvPath);
|
||||||
|
auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
|
||||||
|
auto drvOutputs = drv.outputsAndOptPaths(*store);
|
||||||
|
for (auto & output : bfd.outputs) {
|
||||||
|
if (!outputHashes.count(output))
|
||||||
|
throw Error(
|
||||||
|
"the derivation '%s' doesn't have an output named '%s'",
|
||||||
|
store->printStorePath(bfd.drvPath), output);
|
||||||
|
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
||||||
|
DrvOutput outputId { outputHashes.at(output), output };
|
||||||
|
auto realisation = store->queryRealisation(outputId);
|
||||||
|
if (!realisation)
|
||||||
|
throw Error(
|
||||||
|
"cannot operate on an output of the "
|
||||||
|
"unbuilt derivation '%s'",
|
||||||
|
outputId.to_string());
|
||||||
|
outputs.insert_or_assign(output, realisation->outPath);
|
||||||
|
} else {
|
||||||
|
// If ca-derivations isn't enabled, assume that
|
||||||
|
// the output path is statically known.
|
||||||
|
assert(drvOutputs.count(output));
|
||||||
|
assert(drvOutputs.at(output).second);
|
||||||
|
outputs.insert_or_assign(
|
||||||
|
output, *drvOutputs.at(output).second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
|
||||||
|
},
|
||||||
|
[&](const DerivedPath::Opaque & bo) {
|
||||||
|
res.push_back({installable, BuiltPath::Opaque { bo.path }});
|
||||||
|
},
|
||||||
|
}, path.raw());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case Realise::Outputs: {
|
case Realise::Outputs: {
|
||||||
BuiltPaths res;
|
|
||||||
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
|
for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) {
|
||||||
if (!buildResult.success())
|
if (!buildResult.success())
|
||||||
buildResult.rethrow();
|
buildResult.rethrow();
|
||||||
std::visit(overloaded {
|
|
||||||
[&](const DerivedPath::Built & bfd) {
|
for (auto & installable : backmap[buildResult.path]) {
|
||||||
std::map<std::string, StorePath> outputs;
|
std::visit(overloaded {
|
||||||
for (auto & path : buildResult.builtOutputs)
|
[&](const DerivedPath::Built & bfd) {
|
||||||
outputs.emplace(path.first.outputName, path.second.outPath);
|
std::map<std::string, StorePath> outputs;
|
||||||
res.push_back(BuiltPath::Built { bfd.drvPath, outputs });
|
for (auto & path : buildResult.builtOutputs)
|
||||||
},
|
outputs.emplace(path.first.outputName, path.second.outPath);
|
||||||
[&](const DerivedPath::Opaque & bo) {
|
res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }});
|
||||||
res.push_back(BuiltPath::Opaque { bo.path });
|
},
|
||||||
},
|
[&](const DerivedPath::Opaque & bo) {
|
||||||
}, buildResult.path.raw());
|
res.push_back({installable, BuiltPath::Opaque { bo.path }});
|
||||||
|
},
|
||||||
|
}, buildResult.path.raw());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return res;
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
BuiltPaths Installable::toBuiltPaths(
|
BuiltPaths Installable::toBuiltPaths(
|
||||||
|
@ -930,7 +970,7 @@ InstallablesCommand::InstallablesCommand()
|
||||||
void InstallablesCommand::prepare()
|
void InstallablesCommand::prepare()
|
||||||
{
|
{
|
||||||
if (_installables.empty() && useDefaultInstallables())
|
if (_installables.empty() && useDefaultInstallables())
|
||||||
// FIXME: commands like "nix install" should not have a
|
// FIXME: commands like "nix profile install" should not have a
|
||||||
// default, probably.
|
// default, probably.
|
||||||
_installables.push_back(".");
|
_installables.push_back(".");
|
||||||
installables = parseInstallables(getStore(), _installables);
|
installables = parseInstallables(getStore(), _installables);
|
||||||
|
@ -940,13 +980,14 @@ std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
|
||||||
{
|
{
|
||||||
if (_installables.empty()) {
|
if (_installables.empty()) {
|
||||||
if (useDefaultInstallables())
|
if (useDefaultInstallables())
|
||||||
return parseFlakeRef(".", absPath("."));
|
return parseFlakeRefWithFragment(".", absPath(".")).first;
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return parseFlakeRef(_installables.front(), absPath("."));
|
return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first;
|
||||||
}
|
}
|
||||||
|
|
||||||
InstallableCommand::InstallableCommand()
|
InstallableCommand::InstallableCommand(bool supportReadOnlyMode)
|
||||||
|
: SourceExprCommand(supportReadOnlyMode)
|
||||||
{
|
{
|
||||||
expectArgs({
|
expectArgs({
|
||||||
.label = "installable",
|
.label = "installable",
|
||||||
|
|
|
@ -68,7 +68,7 @@ struct Installable
|
||||||
|
|
||||||
UnresolvedApp toApp(EvalState & state);
|
UnresolvedApp toApp(EvalState & state);
|
||||||
|
|
||||||
virtual std::pair<Value *, Pos> toValue(EvalState & state)
|
virtual std::pair<Value *, PosIdx> toValue(EvalState & state)
|
||||||
{
|
{
|
||||||
throw Error("argument '%s' cannot be evaluated", what());
|
throw Error("argument '%s' cannot be evaluated", what());
|
||||||
}
|
}
|
||||||
|
@ -80,10 +80,10 @@ struct Installable
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
virtual std::vector<ref<eval_cache::AttrCursor>>
|
||||||
getCursors(EvalState & state);
|
getCursors(EvalState & state);
|
||||||
|
|
||||||
std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
|
virtual ref<eval_cache::AttrCursor>
|
||||||
getCursor(EvalState & state);
|
getCursor(EvalState & state);
|
||||||
|
|
||||||
virtual FlakeRef nixpkgsFlakeRef() const
|
virtual FlakeRef nixpkgsFlakeRef() const
|
||||||
|
@ -98,6 +98,13 @@ struct Installable
|
||||||
const std::vector<std::shared_ptr<Installable>> & installables,
|
const std::vector<std::shared_ptr<Installable>> & installables,
|
||||||
BuildMode bMode = bmNormal);
|
BuildMode bMode = bmNormal);
|
||||||
|
|
||||||
|
static std::vector<std::pair<std::shared_ptr<Installable>, BuiltPath>> build2(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
Realise mode,
|
||||||
|
const std::vector<std::shared_ptr<Installable>> & installables,
|
||||||
|
BuildMode bMode = bmNormal);
|
||||||
|
|
||||||
static std::set<StorePath> toStorePaths(
|
static std::set<StorePath> toStorePaths(
|
||||||
ref<Store> evalStore,
|
ref<Store> evalStore,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
|
@ -171,11 +178,17 @@ struct InstallableFlake : InstallableValue
|
||||||
|
|
||||||
std::vector<DerivationInfo> toDerivations() override;
|
std::vector<DerivationInfo> toDerivations() override;
|
||||||
|
|
||||||
std::pair<Value *, Pos> toValue(EvalState & state) override;
|
std::pair<Value *, PosIdx> toValue(EvalState & state) override;
|
||||||
|
|
||||||
std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
|
/* Get a cursor to every attrpath in getActualAttrPaths() that
|
||||||
|
exists. */
|
||||||
|
std::vector<ref<eval_cache::AttrCursor>>
|
||||||
getCursors(EvalState & state) override;
|
getCursors(EvalState & state) override;
|
||||||
|
|
||||||
|
/* Get a cursor to the first attrpath in getActualAttrPaths() that
|
||||||
|
exists, or throw an exception with suggestions if none exists. */
|
||||||
|
ref<eval_cache::AttrCursor> getCursor(EvalState & state) override;
|
||||||
|
|
||||||
std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
|
std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
|
||||||
|
|
||||||
FlakeRef nixpkgsFlakeRef() const override;
|
FlakeRef nixpkgsFlakeRef() const override;
|
||||||
|
@ -185,9 +198,4 @@ ref<eval_cache::EvalCache> openEvalCache(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
std::shared_ptr<flake::LockedFlake> lockedFlake);
|
std::shared_ptr<flake::LockedFlake> lockedFlake);
|
||||||
|
|
||||||
BuiltPaths getBuiltPaths(
|
|
||||||
ref<Store> evalStore,
|
|
||||||
ref<Store> store,
|
|
||||||
const DerivedPaths & hopefullyBuiltPaths);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,18 +36,18 @@ std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s)
|
||||||
{
|
{
|
||||||
std::vector<Symbol> res;
|
std::vector<Symbol> res;
|
||||||
for (auto & a : parseAttrPath(s))
|
for (auto & a : parseAttrPath(s))
|
||||||
res.push_back(state.symbols.create(a));
|
res.emplace_back(a);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string & attrPath,
|
std::pair<Value *, PosIdx> findAlongAttrPath(EvalState & state, const std::string & attrPath,
|
||||||
Bindings & autoArgs, Value & vIn)
|
Bindings & autoArgs, Value & vIn)
|
||||||
{
|
{
|
||||||
Strings tokens = parseAttrPath(attrPath);
|
Strings tokens = parseAttrPath(attrPath);
|
||||||
|
|
||||||
Value * v = &vIn;
|
Value * v = &vIn;
|
||||||
Pos pos = noPos;
|
PosIdx pos = noPos;
|
||||||
|
|
||||||
for (auto & attr : tokens) {
|
for (auto & attr : tokens) {
|
||||||
|
|
||||||
|
@ -77,13 +77,13 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string &
|
||||||
if (a == v->attrs->end()) {
|
if (a == v->attrs->end()) {
|
||||||
std::set<std::string> attrNames;
|
std::set<std::string> attrNames;
|
||||||
for (auto & attr : *v->attrs)
|
for (auto & attr : *v->attrs)
|
||||||
attrNames.insert(attr.name);
|
attrNames.insert(state.symbols[attr.name]);
|
||||||
|
|
||||||
auto suggestions = Suggestions::bestMatches(attrNames, attr);
|
auto suggestions = Suggestions::bestMatches(attrNames, attr);
|
||||||
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
|
throw AttrPathNotFound(suggestions, "attribute '%1%' in selection path '%2%' not found", attr, attrPath);
|
||||||
}
|
}
|
||||||
v = &*a->value;
|
v = &*a->value;
|
||||||
pos = *a->pos;
|
pos = a->pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
else {
|
else {
|
||||||
|
@ -106,7 +106,7 @@ std::pair<Value *, Pos> findAlongAttrPath(EvalState & state, const std::string &
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Pos findPackageFilename(EvalState & state, Value & v, std::string what)
|
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what)
|
||||||
{
|
{
|
||||||
Value * v2;
|
Value * v2;
|
||||||
try {
|
try {
|
||||||
|
@ -132,9 +132,7 @@ Pos findPackageFilename(EvalState & state, Value & v, std::string what)
|
||||||
throw ParseError("cannot parse line number '%s'", pos);
|
throw ParseError("cannot parse line number '%s'", pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
Symbol file = state.symbols.create(filename);
|
return { std::move(filename), lineno };
|
||||||
|
|
||||||
return { foFile, file, lineno, 0 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,14 +10,14 @@ namespace nix {
|
||||||
MakeError(AttrPathNotFound, Error);
|
MakeError(AttrPathNotFound, Error);
|
||||||
MakeError(NoPositionInfo, Error);
|
MakeError(NoPositionInfo, Error);
|
||||||
|
|
||||||
std::pair<Value *, Pos> findAlongAttrPath(
|
std::pair<Value *, PosIdx> findAlongAttrPath(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const std::string & attrPath,
|
const std::string & attrPath,
|
||||||
Bindings & autoArgs,
|
Bindings & autoArgs,
|
||||||
Value & vIn);
|
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);
|
std::pair<std::string, uint32_t> findPackageFilename(EvalState & state, Value & v, std::string what);
|
||||||
|
|
||||||
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);
|
std::vector<Symbol> parseAttrPath(EvalState & state, std::string_view s);
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ Bindings * EvalState::allocBindings(size_t capacity)
|
||||||
/* Create a new attribute named 'name' on an existing attribute set stored
|
/* Create a new attribute named 'name' on an existing attribute set stored
|
||||||
in 'vAttrs' and return the newly allocated Value which is associated with
|
in 'vAttrs' and return the newly allocated Value which is associated with
|
||||||
this attribute. */
|
this attribute. */
|
||||||
Value * EvalState::allocAttr(Value & vAttrs, const Symbol & name)
|
Value * EvalState::allocAttr(Value & vAttrs, const SymbolIdx & name)
|
||||||
{
|
{
|
||||||
Value * v = allocValue();
|
Value * v = allocValue();
|
||||||
vAttrs.attrs->push_back(Attr(name, v));
|
vAttrs.attrs->push_back(Attr(name, v));
|
||||||
|
@ -40,7 +40,7 @@ Value * EvalState::allocAttr(Value & vAttrs, std::string_view name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Value & BindingsBuilder::alloc(const Symbol & name, ptr<Pos> pos)
|
Value & BindingsBuilder::alloc(const SymbolIdx & name, PosIdx pos)
|
||||||
{
|
{
|
||||||
auto value = state.allocValue();
|
auto value = state.allocValue();
|
||||||
bindings->push_back(Attr(name, value, pos));
|
bindings->push_back(Attr(name, value, pos));
|
||||||
|
@ -48,7 +48,7 @@ Value & BindingsBuilder::alloc(const Symbol & name, ptr<Pos> pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Value & BindingsBuilder::alloc(std::string_view name, ptr<Pos> pos)
|
Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos)
|
||||||
{
|
{
|
||||||
return alloc(state.symbols.create(name), pos);
|
return alloc(state.symbols.create(name), pos);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,18 +15,27 @@ struct Value;
|
||||||
/* Map one attribute name to its value. */
|
/* Map one attribute name to its value. */
|
||||||
struct Attr
|
struct Attr
|
||||||
{
|
{
|
||||||
Symbol name;
|
/* the placement of `name` and `pos` in this struct is important.
|
||||||
|
both of them are uint32 wrappers, they are next to each other
|
||||||
|
to make sure that Attr has no padding on 64 bit machines. that
|
||||||
|
way we keep Attr size at two words with no wasted space. */
|
||||||
|
SymbolIdx name;
|
||||||
|
PosIdx pos;
|
||||||
Value * value;
|
Value * value;
|
||||||
ptr<Pos> pos;
|
Attr(SymbolIdx name, Value * value, PosIdx pos = noPos)
|
||||||
Attr(Symbol name, Value * value, ptr<Pos> pos = ptr(&noPos))
|
: name(name), pos(pos), value(value) { };
|
||||||
: name(name), value(value), pos(pos) { };
|
Attr() { };
|
||||||
Attr() : pos(&noPos) { };
|
|
||||||
bool operator < (const Attr & a) const
|
bool operator < (const Attr & a) const
|
||||||
{
|
{
|
||||||
return name < a.name;
|
return name < a.name;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(Attr) == 2 * sizeof(uint32_t) + sizeof(Value *),
|
||||||
|
"performance of the evaluator is highly sensitive to the size of Attr. "
|
||||||
|
"avoid introducing any padding into Attr if at all possible, and do not "
|
||||||
|
"introduce new fields that need not be present for almost every instance.");
|
||||||
|
|
||||||
/* Bindings contains all the attributes of an attribute set. It is defined
|
/* Bindings contains all the attributes of an attribute set. It is defined
|
||||||
by its size and its capacity, the capacity being the number of Attr
|
by its size and its capacity, the capacity being the number of Attr
|
||||||
elements allocated after this structure, while the size corresponds to
|
elements allocated after this structure, while the size corresponds to
|
||||||
|
@ -35,13 +44,13 @@ class Bindings
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef uint32_t size_t;
|
typedef uint32_t size_t;
|
||||||
ptr<Pos> pos;
|
PosIdx pos;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t size_, capacity_;
|
size_t size_, capacity_;
|
||||||
Attr attrs[0];
|
Attr attrs[0];
|
||||||
|
|
||||||
Bindings(size_t capacity) : pos(&noPos), size_(0), capacity_(capacity) { }
|
Bindings(size_t capacity) : size_(0), capacity_(capacity) { }
|
||||||
Bindings(const Bindings & bindings) = delete;
|
Bindings(const Bindings & bindings) = delete;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -57,7 +66,7 @@ public:
|
||||||
attrs[size_++] = attr;
|
attrs[size_++] = attr;
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator find(const Symbol & name)
|
iterator find(const SymbolIdx & name)
|
||||||
{
|
{
|
||||||
Attr key(name, 0);
|
Attr key(name, 0);
|
||||||
iterator i = std::lower_bound(begin(), end(), key);
|
iterator i = std::lower_bound(begin(), end(), key);
|
||||||
|
@ -65,7 +74,7 @@ public:
|
||||||
return end();
|
return end();
|
||||||
}
|
}
|
||||||
|
|
||||||
Attr * get(const Symbol & name)
|
Attr * get(const SymbolIdx & name)
|
||||||
{
|
{
|
||||||
Attr key(name, 0);
|
Attr key(name, 0);
|
||||||
iterator i = std::lower_bound(begin(), end(), key);
|
iterator i = std::lower_bound(begin(), end(), key);
|
||||||
|
@ -73,18 +82,6 @@ public:
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
Attr & need(const Symbol & name, const Pos & pos = noPos)
|
|
||||||
{
|
|
||||||
auto a = get(name);
|
|
||||||
if (!a)
|
|
||||||
throw Error({
|
|
||||||
.msg = hintfmt("attribute '%s' missing", name),
|
|
||||||
.errPos = pos
|
|
||||||
});
|
|
||||||
|
|
||||||
return *a;
|
|
||||||
}
|
|
||||||
|
|
||||||
iterator begin() { return &attrs[0]; }
|
iterator begin() { return &attrs[0]; }
|
||||||
iterator end() { return &attrs[size_]; }
|
iterator end() { return &attrs[size_]; }
|
||||||
|
|
||||||
|
@ -98,14 +95,15 @@ public:
|
||||||
size_t capacity() { return capacity_; }
|
size_t capacity() { return capacity_; }
|
||||||
|
|
||||||
/* Returns the attributes in lexicographically sorted order. */
|
/* Returns the attributes in lexicographically sorted order. */
|
||||||
std::vector<const Attr *> lexicographicOrder() const
|
std::vector<const Attr *> lexicographicOrder(const SymbolTable & symbols) const
|
||||||
{
|
{
|
||||||
std::vector<const Attr *> res;
|
std::vector<const Attr *> res;
|
||||||
res.reserve(size_);
|
res.reserve(size_);
|
||||||
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 std::string &) a->name < (const std::string &) b->name;
|
std::string_view sa = symbols[a->name], sb = symbols[b->name];
|
||||||
|
return sa < sb;
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
@ -130,7 +128,7 @@ public:
|
||||||
: bindings(bindings), state(state)
|
: bindings(bindings), state(state)
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
void insert(Symbol name, Value * value, ptr<Pos> pos = ptr(&noPos))
|
void insert(SymbolIdx name, Value * value, PosIdx pos = noPos)
|
||||||
{
|
{
|
||||||
insert(Attr(name, value, pos));
|
insert(Attr(name, value, pos));
|
||||||
}
|
}
|
||||||
|
@ -145,9 +143,9 @@ public:
|
||||||
bindings->push_back(attr);
|
bindings->push_back(attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value & alloc(const Symbol & name, ptr<Pos> pos = ptr(&noPos));
|
Value & alloc(const SymbolIdx & name, PosIdx pos = noPos);
|
||||||
|
|
||||||
Value & alloc(std::string_view name, ptr<Pos> pos = ptr(&noPos));
|
Value & alloc(std::string_view name, PosIdx pos = noPos);
|
||||||
|
|
||||||
Bindings * finish()
|
Bindings * finish()
|
||||||
{
|
{
|
||||||
|
|
|
@ -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());
|
||||||
|
|
||||||
|
@ -250,14 +253,14 @@ struct AttrDb
|
||||||
std::vector<Symbol> attrs;
|
std::vector<Symbol> attrs;
|
||||||
auto queryAttributes(state->queryAttributes.use()(rowId));
|
auto queryAttributes(state->queryAttributes.use()(rowId));
|
||||||
while (queryAttributes.next())
|
while (queryAttributes.next())
|
||||||
attrs.push_back(symbols.create(queryAttributes.getStr(0)));
|
attrs.emplace_back(queryAttributes.getStr(0));
|
||||||
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)
|
||||||
{
|
{
|
||||||
|
@ -303,9 +306,9 @@ Value * EvalCache::getRootValue()
|
||||||
return *value;
|
return *value;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> EvalCache::getRoot()
|
ref<AttrCursor> EvalCache::getRoot()
|
||||||
{
|
{
|
||||||
return std::make_shared<AttrCursor>(ref(shared_from_this()), std::nullopt);
|
return make_ref<AttrCursor>(ref(shared_from_this()), std::nullopt);
|
||||||
}
|
}
|
||||||
|
|
||||||
AttrCursor::AttrCursor(
|
AttrCursor::AttrCursor(
|
||||||
|
@ -322,7 +325,7 @@ AttrCursor::AttrCursor(
|
||||||
AttrKey AttrCursor::getKey()
|
AttrKey AttrCursor::getKey()
|
||||||
{
|
{
|
||||||
if (!parent)
|
if (!parent)
|
||||||
return {0, root->state.sEpsilon};
|
return {0, {""}};
|
||||||
if (!parent->first->cachedValue) {
|
if (!parent->first->cachedValue) {
|
||||||
parent->first->cachedValue = root->db->getAttr(
|
parent->first->cachedValue = root->db->getAttr(
|
||||||
parent->first->getKey(), root->state.symbols);
|
parent->first->getKey(), root->state.symbols);
|
||||||
|
@ -337,7 +340,7 @@ Value & AttrCursor::getValue()
|
||||||
if (parent) {
|
if (parent) {
|
||||||
auto & vParent = parent->first->getValue();
|
auto & vParent = parent->first->getValue();
|
||||||
root->state.forceAttrs(vParent, noPos);
|
root->state.forceAttrs(vParent, noPos);
|
||||||
auto attr = vParent.attrs->get(parent->second);
|
auto attr = vParent.attrs->get(root->state.symbols.create(parent->second));
|
||||||
if (!attr)
|
if (!attr)
|
||||||
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
|
throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
|
||||||
_value = allocRootValue(attr->value);
|
_value = allocRootValue(attr->value);
|
||||||
|
@ -416,7 +419,7 @@ Suggestions AttrCursor::getSuggestionsForAttr(Symbol name)
|
||||||
return Suggestions::bestMatches(strAttrNames, name);
|
return Suggestions::bestMatches(strAttrNames, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErrors)
|
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name, bool forceErrors)
|
||||||
{
|
{
|
||||||
if (root->db) {
|
if (root->db) {
|
||||||
if (!cachedValue)
|
if (!cachedValue)
|
||||||
|
@ -458,10 +461,10 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
||||||
|
|
||||||
for (auto & attr : *v.attrs) {
|
for (auto & attr : *v.attrs) {
|
||||||
if (root->db)
|
if (root->db)
|
||||||
root->db->setPlaceholder({cachedValue->first, attr.name});
|
root->db->setPlaceholder({cachedValue->first, root->state.symbols[attr.name]});
|
||||||
}
|
}
|
||||||
|
|
||||||
auto attr = v.attrs->get(name);
|
auto attr = v.attrs->get(root->state.symbols.create(name));
|
||||||
|
|
||||||
if (!attr) {
|
if (!attr) {
|
||||||
if (root->db) {
|
if (root->db) {
|
||||||
|
@ -483,12 +486,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
|
||||||
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));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(std::string_view name)
|
ref<AttrCursor> AttrCursor::getAttr(std::string_view name, bool forceErrors)
|
||||||
{
|
|
||||||
return maybeGetAttr(root->state.symbols.create(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
ref<AttrCursor> AttrCursor::getAttr(Symbol name, bool forceErrors)
|
|
||||||
{
|
{
|
||||||
auto p = maybeGetAttr(name, forceErrors);
|
auto p = maybeGetAttr(name, forceErrors);
|
||||||
if (!p)
|
if (!p)
|
||||||
|
@ -496,11 +494,6 @@ ref<AttrCursor> AttrCursor::getAttr(Symbol name, bool forceErrors)
|
||||||
return ref(p);
|
return ref(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
ref<AttrCursor> AttrCursor::getAttr(std::string_view name)
|
|
||||||
{
|
|
||||||
return getAttr(root->state.symbols.create(name));
|
|
||||||
}
|
|
||||||
|
|
||||||
OrSuggestions<ref<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();
|
||||||
|
@ -546,7 +539,7 @@ string_t AttrCursor::getStringWithContext()
|
||||||
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
|
if (auto s = std::get_if<string_t>(&cachedValue->second)) {
|
||||||
bool valid = true;
|
bool valid = true;
|
||||||
for (auto & c : s->second) {
|
for (auto & c : s->second) {
|
||||||
if (!root->state.store->isValidPath(root->state.store->parseStorePath(c.first))) {
|
if (!root->state.store->isValidPath(c.first)) {
|
||||||
valid = false;
|
valid = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -563,7 +556,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
|
||||||
|
@ -613,7 +606,7 @@ std::vector<Symbol> AttrCursor::getAttrs()
|
||||||
|
|
||||||
std::vector<Symbol> attrs;
|
std::vector<Symbol> attrs;
|
||||||
for (auto & attr : *getValue().attrs)
|
for (auto & attr : *getValue().attrs)
|
||||||
attrs.push_back(attr.name);
|
attrs.push_back(root->state.symbols[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 std::string &) a < (const std::string &) b;
|
return (const std::string &) a < (const std::string &) b;
|
||||||
});
|
});
|
||||||
|
@ -632,7 +625,7 @@ bool AttrCursor::isDerivation()
|
||||||
|
|
||||||
StorePath AttrCursor::forceDerivation()
|
StorePath AttrCursor::forceDerivation()
|
||||||
{
|
{
|
||||||
auto aDrvPath = getAttr(root->state.sDrvPath, true);
|
auto aDrvPath = getAttr("drvPath", true);
|
||||||
auto drvPath = root->state.store->parseStorePath(aDrvPath->getString());
|
auto drvPath = root->state.store->parseStorePath(aDrvPath->getString());
|
||||||
if (!root->state.store->isValidPath(drvPath) && !settings.readOnlyMode) {
|
if (!root->state.store->isValidPath(drvPath) && !settings.readOnlyMode) {
|
||||||
/* The eval cache contains 'drvPath', but the actual path has
|
/* The eval cache contains 'drvPath', but the actual path has
|
||||||
|
|
|
@ -33,7 +33,7 @@ public:
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
RootLoader rootLoader);
|
RootLoader rootLoader);
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> getRoot();
|
ref<AttrCursor> getRoot();
|
||||||
};
|
};
|
||||||
|
|
||||||
enum AttrType {
|
enum AttrType {
|
||||||
|
@ -52,7 +52,7 @@ struct misc_t {};
|
||||||
struct failed_t {};
|
struct failed_t {};
|
||||||
typedef uint64_t AttrId;
|
typedef uint64_t AttrId;
|
||||||
typedef std::pair<AttrId, Symbol> AttrKey;
|
typedef std::pair<AttrId, Symbol> AttrKey;
|
||||||
typedef std::pair<std::string, std::vector<std::pair<Path, std::string>>> string_t;
|
typedef std::pair<std::string, NixStringContext> string_t;
|
||||||
|
|
||||||
typedef std::variant<
|
typedef std::variant<
|
||||||
std::vector<Symbol>,
|
std::vector<Symbol>,
|
||||||
|
@ -96,14 +96,12 @@ public:
|
||||||
|
|
||||||
Suggestions getSuggestionsForAttr(Symbol name);
|
Suggestions getSuggestionsForAttr(Symbol name);
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> maybeGetAttr(Symbol name, bool forceErrors = false);
|
std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name, bool forceErrors = false);
|
||||||
|
|
||||||
std::shared_ptr<AttrCursor> maybeGetAttr(std::string_view name);
|
ref<AttrCursor> getAttr(std::string_view name, bool forceErrors = false);
|
||||||
|
|
||||||
ref<AttrCursor> getAttr(Symbol name, bool forceErrors = false);
|
|
||||||
|
|
||||||
ref<AttrCursor> getAttr(std::string_view name);
|
|
||||||
|
|
||||||
|
/* Get an attribute along a chain of attrsets. Note that this does
|
||||||
|
not auto-call functors or functions. */
|
||||||
OrSuggestions<ref<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();
|
||||||
|
|
|
@ -2,29 +2,85 @@
|
||||||
|
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
|
||||||
#define LocalNoInline(f) static f __attribute__((noinline)); f
|
|
||||||
#define LocalNoInlineNoReturn(f) static f __attribute__((noinline, noreturn)); f
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s))
|
|
||||||
{
|
|
||||||
throw EvalError({
|
|
||||||
.msg = hintfmt(s),
|
|
||||||
.errPos = pos
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const Value & v))
|
/* Note: Various places expect the allocated memory to be zeroed. */
|
||||||
|
[[gnu::always_inline]]
|
||||||
|
inline void * allocBytes(size_t n)
|
||||||
{
|
{
|
||||||
throw TypeError({
|
void * p;
|
||||||
.msg = hintfmt(s, showType(v)),
|
#if HAVE_BOEHMGC
|
||||||
.errPos = pos
|
p = GC_MALLOC(n);
|
||||||
});
|
#else
|
||||||
|
p = calloc(n, 1);
|
||||||
|
#endif
|
||||||
|
if (!p) throw std::bad_alloc();
|
||||||
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::forceValue(Value & v, const Pos & pos)
|
[[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 PosIdx pos)
|
||||||
{
|
{
|
||||||
forceValue(v, [&]() { return pos; });
|
forceValue(v, [&]() { return pos; });
|
||||||
}
|
}
|
||||||
|
@ -52,13 +108,15 @@ void EvalState::forceValue(Value & v, Callable getPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline void EvalState::forceAttrs(Value & v, const Pos & pos)
|
[[gnu::always_inline]]
|
||||||
|
inline void EvalState::forceAttrs(Value & v, const PosIdx pos)
|
||||||
{
|
{
|
||||||
forceAttrs(v, [&]() { return pos; });
|
forceAttrs(v, [&]() { return 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);
|
||||||
|
@ -67,25 +125,13 @@ inline void EvalState::forceAttrs(Value & v, Callable getPos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline void EvalState::forceList(Value & v, const Pos & pos)
|
[[gnu::always_inline]]
|
||||||
|
inline void EvalState::forceList(Value & v, const PosIdx pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (!v.isList())
|
if (!v.isList())
|
||||||
throwTypeError(pos, "value is %1% while a list was expected", v);
|
throwTypeError(pos, "value is %1% while a list was expected", v);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,20 +96,21 @@ RootValue allocRootValue(Value * v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void printValue(std::ostream & str, std::set<const void *> & seen, const Value & v)
|
void Value::print(const SymbolTable & symbols, std::ostream & str,
|
||||||
|
std::set<const void *> * seen) const
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
switch (v.internalType) {
|
switch (internalType) {
|
||||||
case tInt:
|
case tInt:
|
||||||
str << v.integer;
|
str << integer;
|
||||||
break;
|
break;
|
||||||
case tBool:
|
case tBool:
|
||||||
str << (v.boolean ? "true" : "false");
|
str << (boolean ? "true" : "false");
|
||||||
break;
|
break;
|
||||||
case tString:
|
case tString:
|
||||||
str << "\"";
|
str << "\"";
|
||||||
for (const char * i = v.string.s; *i; i++)
|
for (const char * i = string.s; *i; i++)
|
||||||
if (*i == '\"' || *i == '\\') str << "\\" << *i;
|
if (*i == '\"' || *i == '\\') str << "\\" << *i;
|
||||||
else if (*i == '\n') str << "\\n";
|
else if (*i == '\n') str << "\\n";
|
||||||
else if (*i == '\r') str << "\\r";
|
else if (*i == '\r') str << "\\r";
|
||||||
|
@ -119,19 +120,19 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
|
||||||
str << "\"";
|
str << "\"";
|
||||||
break;
|
break;
|
||||||
case tPath:
|
case tPath:
|
||||||
str << v.path; // !!! escaping?
|
str << path; // !!! escaping?
|
||||||
break;
|
break;
|
||||||
case tNull:
|
case tNull:
|
||||||
str << "null";
|
str << "null";
|
||||||
break;
|
break;
|
||||||
case tAttrs: {
|
case tAttrs: {
|
||||||
if (!v.attrs->empty() && !seen.insert(v.attrs).second)
|
if (seen && !attrs->empty() && !seen->insert(attrs).second)
|
||||||
str << "<REPEAT>";
|
str << "«repeated»";
|
||||||
else {
|
else {
|
||||||
str << "{ ";
|
str << "{ ";
|
||||||
for (auto & i : v.attrs->lexicographicOrder()) {
|
for (auto & i : attrs->lexicographicOrder(symbols)) {
|
||||||
str << i->name << " = ";
|
str << symbols[i->name] << " = ";
|
||||||
printValue(str, seen, *i->value);
|
i->value->print(symbols, str, seen);
|
||||||
str << "; ";
|
str << "; ";
|
||||||
}
|
}
|
||||||
str << "}";
|
str << "}";
|
||||||
|
@ -141,12 +142,12 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
|
||||||
case tList1:
|
case tList1:
|
||||||
case tList2:
|
case tList2:
|
||||||
case tListN:
|
case tListN:
|
||||||
if (v.listSize() && !seen.insert(v.listElems()).second)
|
if (seen && listSize() && !seen->insert(listElems()).second)
|
||||||
str << "<REPEAT>";
|
str << "«repeated»";
|
||||||
else {
|
else {
|
||||||
str << "[ ";
|
str << "[ ";
|
||||||
for (auto v2 : v.listItems()) {
|
for (auto v2 : listItems()) {
|
||||||
printValue(str, seen, *v2);
|
v2->print(symbols, str, seen);
|
||||||
str << " ";
|
str << " ";
|
||||||
}
|
}
|
||||||
str << "]";
|
str << "]";
|
||||||
|
@ -166,10 +167,10 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
|
||||||
str << "<PRIMOP-APP>";
|
str << "<PRIMOP-APP>";
|
||||||
break;
|
break;
|
||||||
case tExternal:
|
case tExternal:
|
||||||
str << *v.external;
|
str << *external;
|
||||||
break;
|
break;
|
||||||
case tFloat:
|
case tFloat:
|
||||||
str << v.fpoint;
|
str << fpoint;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
abort();
|
abort();
|
||||||
|
@ -177,11 +178,18 @@ void printValue(std::ostream & str, std::set<const void *> & seen, const Value &
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const Value & v)
|
void Value::print(const SymbolTable & symbols, std::ostream & str, bool showRepeated) const
|
||||||
{
|
{
|
||||||
std::set<const void *> seen;
|
std::set<const void *> seen;
|
||||||
printValue(str, seen, v);
|
print(symbols, str, showRepeated ? nullptr : &seen);
|
||||||
return str;
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::string printValue(const EvalState & state, const Value & v)
|
||||||
|
{
|
||||||
|
std::ostringstream out;
|
||||||
|
v.print(state.symbols, out);
|
||||||
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -230,10 +238,10 @@ std::string showType(const Value & v)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Pos Value::determinePos(const Pos & pos) const
|
PosIdx Value::determinePos(const PosIdx pos) const
|
||||||
{
|
{
|
||||||
switch (internalType) {
|
switch (internalType) {
|
||||||
case tAttrs: return *attrs->pos;
|
case tAttrs: return attrs->pos;
|
||||||
case tLambda: return lambda.fun->pos;
|
case tLambda: return lambda.fun->pos;
|
||||||
case tApp: return app.left->determinePos(pos);
|
case tApp: return app.left->determinePos(pos);
|
||||||
default: return pos;
|
default: return pos;
|
||||||
|
@ -300,9 +308,9 @@ static BoehmGCStackAllocator boehmGCStackAllocator;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
static Symbol getName(const AttrName & name, EvalState & state, Env & env)
|
static SymbolIdx getName(const AttrName & name, EvalState & state, Env & env)
|
||||||
{
|
{
|
||||||
if (name.symbol.set()) {
|
if (name.symbol) {
|
||||||
return name.symbol;
|
return name.symbol;
|
||||||
} else {
|
} else {
|
||||||
Value nameValue;
|
Value nameValue;
|
||||||
|
@ -430,6 +438,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"))
|
||||||
|
@ -449,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(false, 0)
|
, staticBaseEnv(false, 0)
|
||||||
|
@ -499,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)
|
||||||
|
@ -647,20 +641,20 @@ Value * EvalState::addPrimOp(const std::string & name,
|
||||||
size_t arity, PrimOpFun primOp)
|
size_t arity, PrimOpFun primOp)
|
||||||
{
|
{
|
||||||
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
|
auto name2 = name.substr(0, 2) == "__" ? name.substr(2) : name;
|
||||||
Symbol sym = symbols.create(name2);
|
auto 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
|
||||||
the primop to a dummy value. */
|
the primop to a dummy value. */
|
||||||
if (arity == 0) {
|
if (arity == 0) {
|
||||||
auto vPrimOp = allocValue();
|
auto vPrimOp = allocValue();
|
||||||
vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = sym });
|
vPrimOp->mkPrimOp(new PrimOp { .fun = primOp, .arity = 1, .name = name2 });
|
||||||
Value v;
|
Value v;
|
||||||
v.mkApp(vPrimOp, vPrimOp);
|
v.mkApp(vPrimOp, vPrimOp);
|
||||||
return addConstant(name, v);
|
return addConstant(name, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value * v = allocValue();
|
Value * v = allocValue();
|
||||||
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = sym });
|
v->mkPrimOp(new PrimOp { .fun = primOp, .arity = arity, .name = name2 });
|
||||||
staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
|
staticBaseEnv.vars.emplace_back(symbols.create(name), baseEnvDispl);
|
||||||
baseEnv.values[baseEnvDispl++] = v;
|
baseEnv.values[baseEnvDispl++] = v;
|
||||||
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
|
baseEnv.values[0]->attrs->push_back(Attr(sym, v));
|
||||||
|
@ -675,21 +669,21 @@ Value * EvalState::addPrimOp(PrimOp && primOp)
|
||||||
if (primOp.arity == 0) {
|
if (primOp.arity == 0) {
|
||||||
primOp.arity = 1;
|
primOp.arity = 1;
|
||||||
auto vPrimOp = allocValue();
|
auto vPrimOp = allocValue();
|
||||||
vPrimOp->mkPrimOp(new PrimOp(std::move(primOp)));
|
vPrimOp->mkPrimOp(new PrimOp(primOp));
|
||||||
Value v;
|
Value v;
|
||||||
v.mkApp(vPrimOp, vPrimOp);
|
v.mkApp(vPrimOp, vPrimOp);
|
||||||
return addConstant(primOp.name, v);
|
return addConstant(primOp.name, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
Symbol envName = primOp.name;
|
auto envName = symbols.create(primOp.name);
|
||||||
if (hasPrefix(primOp.name, "__"))
|
if (hasPrefix(primOp.name, "__"))
|
||||||
primOp.name = symbols.create(std::string(primOp.name, 2));
|
primOp.name = primOp.name.substr(2);
|
||||||
|
|
||||||
Value * v = allocValue();
|
Value * v = allocValue();
|
||||||
v->mkPrimOp(new PrimOp(std::move(primOp)));
|
v->mkPrimOp(new PrimOp(primOp));
|
||||||
staticBaseEnv.vars.emplace_back(envName, baseEnvDispl);
|
staticBaseEnv.vars.emplace_back(envName, baseEnvDispl);
|
||||||
baseEnv.values[baseEnvDispl++] = v;
|
baseEnv.values[baseEnvDispl++] = v;
|
||||||
baseEnv.values[0]->attrs->push_back(Attr(primOp.name, v));
|
baseEnv.values[0]->attrs->push_back(Attr(symbols.create(primOp.name), v));
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -706,7 +700,7 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
||||||
auto v2 = &v;
|
auto v2 = &v;
|
||||||
if (v2->primOp->doc)
|
if (v2->primOp->doc)
|
||||||
return Doc {
|
return Doc {
|
||||||
.pos = noPos,
|
.pos = {},
|
||||||
.name = v2->primOp->name,
|
.name = v2->primOp->name,
|
||||||
.arity = v2->primOp->arity,
|
.arity = v2->primOp->arity,
|
||||||
.args = v2->primOp->args,
|
.args = v2->primOp->args,
|
||||||
|
@ -722,94 +716,133 @@ std::optional<EvalState::Doc> EvalState::getDoc(Value & v)
|
||||||
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 std::string & s2))
|
void EvalState::throwEvalError(const PosIdx pos, const char * s) const
|
||||||
|
{
|
||||||
|
throw EvalError({
|
||||||
|
.msg = hintfmt(s),
|
||||||
|
.errPos = positions[pos]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvalState::throwTypeError(const PosIdx pos, const char * s, const Value & v) const
|
||||||
|
{
|
||||||
|
throw TypeError({
|
||||||
|
.msg = hintfmt(s, showType(v)),
|
||||||
|
.errPos = positions[pos]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvalState::throwEvalError(const char * s, const std::string & s2) const
|
||||||
{
|
{
|
||||||
throw EvalError(s, s2);
|
throw EvalError(s, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2))
|
void EvalState::throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
|
||||||
|
const std::string & s2) const
|
||||||
{
|
{
|
||||||
throw EvalError({
|
throw EvalError(ErrorInfo {
|
||||||
.msg = hintfmt(s, s2),
|
.msg = hintfmt(s, s2),
|
||||||
.errPos = pos
|
.errPos = positions[pos],
|
||||||
|
.suggestions = suggestions,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const char * s, const std::string & s2, const std::string & s3))
|
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const
|
||||||
|
{
|
||||||
|
throw EvalError(ErrorInfo {
|
||||||
|
.msg = hintfmt(s, s2),
|
||||||
|
.errPos = positions[pos]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void EvalState::throwEvalError(const char * s, const std::string & s2, const std::string & s3) const
|
||||||
{
|
{
|
||||||
throw EvalError(s, s2, s3);
|
throw EvalError(s, s2, s3);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & pos, const char * s, const std::string & s2, const std::string & s3))
|
void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::string & s2,
|
||||||
|
const std::string & s3) const
|
||||||
{
|
{
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt(s, s2, s3),
|
.msg = hintfmt(s, s2, s3),
|
||||||
.errPos = pos
|
.errPos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwEvalError(const Pos & p1, const char * s, const Symbol & sym, const Pos & p2))
|
void EvalState::throwEvalError(const PosIdx p1, const char * s, const SymbolIdx sym, const PosIdx p2) const
|
||||||
{
|
{
|
||||||
// p1 is where the error occurred; p2 is a position mentioned in the message.
|
// p1 is where the error occurred; p2 is a position mentioned in the message.
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt(s, sym, p2),
|
.msg = hintfmt(s, symbols[sym], positions[p2]),
|
||||||
.errPos = p1
|
.errPos = positions[p1]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s))
|
void EvalState::throwTypeError(const PosIdx pos, const char * s) const
|
||||||
{
|
{
|
||||||
throw TypeError({
|
throw TypeError({
|
||||||
.msg = hintfmt(s),
|
.msg = hintfmt(s),
|
||||||
.errPos = pos
|
.errPos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const Pos & pos, const char * s, const ExprLambda & fun, const Symbol & s2))
|
void EvalState::throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun,
|
||||||
|
const SymbolIdx s2) const
|
||||||
{
|
{
|
||||||
throw TypeError({
|
throw TypeError({
|
||||||
.msg = hintfmt(s, fun.showNamePos(), s2),
|
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
|
||||||
.errPos = pos
|
.errPos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwTypeError(const char * s, const Value & v))
|
void EvalState::throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s,
|
||||||
|
const ExprLambda & fun, const SymbolIdx s2) const
|
||||||
|
{
|
||||||
|
throw TypeError(ErrorInfo {
|
||||||
|
.msg = hintfmt(s, fun.showNamePos(*this), symbols[s2]),
|
||||||
|
.errPos = positions[pos],
|
||||||
|
.suggestions = suggestions,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void EvalState::throwTypeError(const char * s, const Value & v) const
|
||||||
{
|
{
|
||||||
throw TypeError(s, showType(v));
|
throw TypeError(s, showType(v));
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwAssertionError(const Pos & pos, const char * s, const std::string & s1))
|
void EvalState::throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const
|
||||||
{
|
{
|
||||||
throw AssertionError({
|
throw AssertionError({
|
||||||
.msg = hintfmt(s, s1),
|
.msg = hintfmt(s, s1),
|
||||||
.errPos = pos
|
.errPos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwUndefinedVarError(const Pos & pos, const char * s, const std::string & s1))
|
void EvalState::throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const
|
||||||
{
|
{
|
||||||
throw UndefinedVarError({
|
throw UndefinedVarError({
|
||||||
.msg = hintfmt(s, s1),
|
.msg = hintfmt(s, s1),
|
||||||
.errPos = pos
|
.errPos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInlineNoReturn(void throwMissingArgumentError(const Pos & pos, const char * s, const std::string & s1))
|
void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const
|
||||||
{
|
{
|
||||||
throw MissingArgumentError({
|
throw MissingArgumentError({
|
||||||
.msg = hintfmt(s, s1),
|
.msg = hintfmt(s, s1),
|
||||||
.errPos = pos
|
.errPos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInline(void addErrorTrace(Error & e, const char * s, const std::string & s2))
|
void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const
|
||||||
{
|
{
|
||||||
e.addTrace(std::nullopt, s, s2);
|
e.addTrace(std::nullopt, s, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
LocalNoInline(void addErrorTrace(Error & e, const Pos & pos, const char * s, const std::string & s2))
|
void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const
|
||||||
{
|
{
|
||||||
e.addTrace(pos, s, s2);
|
e.addTrace(positions[pos], s, s2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -866,52 +899,16 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||||
}
|
}
|
||||||
Bindings::iterator j = env->values[0]->attrs->find(var.name);
|
Bindings::iterator j = env->values[0]->attrs->find(var.name);
|
||||||
if (j != env->values[0]->attrs->end()) {
|
if (j != env->values[0]->attrs->end()) {
|
||||||
if (countCalls) attrSelects[*j->pos]++;
|
if (countCalls) attrSelects[j->pos]++;
|
||||||
return j->value;
|
return j->value;
|
||||||
}
|
}
|
||||||
if (!env->prevWith)
|
if (!env->prevWith)
|
||||||
throwUndefinedVarError(var.pos, "undefined variable '%1%'", var.name);
|
throwUndefinedVarError(var.pos, "undefined variable '%1%'", symbols[var.name]);
|
||||||
for (size_t l = env->prevWith; l; --l, env = env->up) ;
|
for (size_t l = env->prevWith; l; --l, env = env->up) ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void EvalState::mkList(Value & v, size_t size)
|
void EvalState::mkList(Value & v, size_t size)
|
||||||
{
|
{
|
||||||
v.mkList(size);
|
v.mkList(size);
|
||||||
|
@ -936,13 +933,14 @@ void EvalState::mkThunk_(Value & v, Expr * expr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::mkPos(Value & v, ptr<Pos> pos)
|
void EvalState::mkPos(Value & v, PosIdx p)
|
||||||
{
|
{
|
||||||
if (pos->file.set()) {
|
auto pos = positions[p];
|
||||||
|
if (!pos.file.empty()) {
|
||||||
auto attrs = buildBindings(3);
|
auto attrs = buildBindings(3);
|
||||||
attrs.alloc(sFile).mkString(pos->file);
|
attrs.alloc(sFile).mkString(pos.file);
|
||||||
attrs.alloc(sLine).mkInt(pos->line);
|
attrs.alloc(sLine).mkInt(pos.line);
|
||||||
attrs.alloc(sColumn).mkInt(pos->column);
|
attrs.alloc(sColumn).mkInt(pos.column);
|
||||||
v.mkAttrs(attrs);
|
v.mkAttrs(attrs);
|
||||||
} else
|
} else
|
||||||
v.mkNull();
|
v.mkNull();
|
||||||
|
@ -1075,7 +1073,7 @@ inline bool EvalState::evalBool(Env & env, Expr * e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
inline bool EvalState::evalBool(Env & env, Expr * e, const Pos & pos)
|
inline bool EvalState::evalBool(Env & env, Expr * e, const PosIdx pos)
|
||||||
{
|
{
|
||||||
Value v;
|
Value v;
|
||||||
e->eval(*this, env, v);
|
e->eval(*this, env, v);
|
||||||
|
@ -1149,7 +1147,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||||
} else
|
} else
|
||||||
vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
|
vAttr = i.second.e->maybeThunk(state, i.second.inherited ? env : env2);
|
||||||
env2.values[displ++] = vAttr;
|
env2.values[displ++] = vAttr;
|
||||||
v.attrs->push_back(Attr(i.first, vAttr, ptr(&i.second.pos)));
|
v.attrs->push_back(Attr(i.first, vAttr, i.second.pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* If the rec contains an attribute called `__overrides', then
|
/* If the rec contains an attribute called `__overrides', then
|
||||||
|
@ -1181,7 +1179,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||||
|
|
||||||
else
|
else
|
||||||
for (auto & i : attrs)
|
for (auto & i : attrs)
|
||||||
v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), ptr(&i.second.pos)));
|
v.attrs->push_back(Attr(i.first, i.second.e->maybeThunk(state, env), i.second.pos));
|
||||||
|
|
||||||
/* Dynamic attrs apply *after* rec and __overrides. */
|
/* Dynamic attrs apply *after* rec and __overrides. */
|
||||||
for (auto & i : dynamicAttrs) {
|
for (auto & i : dynamicAttrs) {
|
||||||
|
@ -1191,18 +1189,18 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||||
if (nameVal.type() == nNull)
|
if (nameVal.type() == nNull)
|
||||||
continue;
|
continue;
|
||||||
state.forceStringNoCtx(nameVal);
|
state.forceStringNoCtx(nameVal);
|
||||||
Symbol nameSym = state.symbols.create(nameVal.string.s);
|
auto nameSym = state.symbols.create(nameVal.string.s);
|
||||||
Bindings::iterator j = v.attrs->find(nameSym);
|
Bindings::iterator j = v.attrs->find(nameSym);
|
||||||
if (j != v.attrs->end())
|
if (j != v.attrs->end())
|
||||||
throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, *j->pos);
|
state.throwEvalError(i.pos, "dynamic attribute '%1%' already defined at %2%", nameSym, j->pos);
|
||||||
|
|
||||||
i.valueExpr->setName(nameSym);
|
i.valueExpr->setName(nameSym);
|
||||||
/* Keep sorted order so find can catch duplicates */
|
/* Keep sorted order so find can catch duplicates */
|
||||||
v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), ptr(&i.pos)));
|
v.attrs->push_back(Attr(nameSym, i.valueExpr->maybeThunk(state, *dynamicEnv), i.pos));
|
||||||
v.attrs->sort(); // FIXME: inefficient
|
v.attrs->sort(); // FIXME: inefficient
|
||||||
}
|
}
|
||||||
|
|
||||||
v.attrs->pos = ptr(&pos);
|
v.attrs->pos = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1247,10 +1245,12 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a
|
||||||
for (auto & i : attrPath) {
|
for (auto & i : attrPath) {
|
||||||
if (!first) out << '.'; else first = false;
|
if (!first) out << '.'; else first = false;
|
||||||
try {
|
try {
|
||||||
out << getName(i, state, env);
|
out << state.symbols[getName(i, state, env)];
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
assert(!i.symbol.set());
|
assert(!i.symbol);
|
||||||
out << "\"${" << *i.expr << "}\"";
|
out << "\"${";
|
||||||
|
i.expr->show(state.symbols, out);
|
||||||
|
out << "}\"";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return out.str();
|
return out.str();
|
||||||
|
@ -1260,7 +1260,7 @@ static std::string showAttrPath(EvalState & state, Env & env, const AttrPath & a
|
||||||
void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
Value vTmp;
|
Value vTmp;
|
||||||
ptr<Pos> pos2(&noPos);
|
PosIdx pos2;
|
||||||
Value * vAttrs = &vTmp;
|
Value * vAttrs = &vTmp;
|
||||||
|
|
||||||
e->eval(state, env, vTmp);
|
e->eval(state, env, vTmp);
|
||||||
|
@ -1270,7 +1270,7 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v)
|
||||||
for (auto & i : attrPath) {
|
for (auto & i : attrPath) {
|
||||||
state.nrLookups++;
|
state.nrLookups++;
|
||||||
Bindings::iterator j;
|
Bindings::iterator j;
|
||||||
Symbol name = getName(i, state, env);
|
auto name = getName(i, state, env);
|
||||||
if (def) {
|
if (def) {
|
||||||
state.forceValue(*vAttrs, pos);
|
state.forceValue(*vAttrs, pos);
|
||||||
if (vAttrs->type() != nAttrs ||
|
if (vAttrs->type() != nAttrs ||
|
||||||
|
@ -1281,19 +1281,27 @@ 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);
|
std::set<std::string> allAttrNames;
|
||||||
|
for (auto & attr : *vAttrs->attrs)
|
||||||
|
allAttrNames.insert(state.symbols[attr.name]);
|
||||||
|
state.throwEvalError(
|
||||||
|
pos,
|
||||||
|
Suggestions::bestMatches(allAttrNames, state.symbols[name]),
|
||||||
|
"attribute '%1%' missing", state.symbols[name]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
vAttrs = j->value;
|
vAttrs = j->value;
|
||||||
pos2 = j->pos;
|
pos2 = j->pos;
|
||||||
if (state.countCalls) state.attrSelects[*pos2]++;
|
if (state.countCalls) state.attrSelects[pos2]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.forceValue(*vAttrs, (*pos2 != noPos ? *pos2 : this->pos ) );
|
state.forceValue(*vAttrs, (pos2 ? pos2 : this->pos ) );
|
||||||
|
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
if (*pos2 != noPos && pos2->file != state.sDerivationNix)
|
auto pos2r = state.positions[pos2];
|
||||||
addErrorTrace(e, *pos2, "while evaluating the attribute '%1%'",
|
if (pos2 && pos2r.file != state.derivationNixPath)
|
||||||
|
state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'",
|
||||||
showAttrPath(state, env, attrPath));
|
showAttrPath(state, env, attrPath));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
@ -1312,7 +1320,7 @@ void ExprOpHasAttr::eval(EvalState & state, Env & env, Value & v)
|
||||||
for (auto & i : attrPath) {
|
for (auto & i : attrPath) {
|
||||||
state.forceValue(*vAttrs, noPos);
|
state.forceValue(*vAttrs, noPos);
|
||||||
Bindings::iterator j;
|
Bindings::iterator j;
|
||||||
Symbol name = getName(i, state, env);
|
auto name = getName(i, state, env);
|
||||||
if (vAttrs->type() != nAttrs ||
|
if (vAttrs->type() != nAttrs ||
|
||||||
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
|
(j = vAttrs->attrs->find(name)) == vAttrs->attrs->end())
|
||||||
{
|
{
|
||||||
|
@ -1333,9 +1341,11 @@ void ExprLambda::eval(EvalState & state, Env & env, Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos)
|
void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos)
|
||||||
{
|
{
|
||||||
auto trace = evalSettings.traceFunctionCalls ? std::make_unique<FunctionCallTrace>(pos) : nullptr;
|
auto trace = evalSettings.traceFunctionCalls
|
||||||
|
? std::make_unique<FunctionCallTrace>(positions[pos])
|
||||||
|
: nullptr;
|
||||||
|
|
||||||
forceValue(fun, pos);
|
forceValue(fun, pos);
|
||||||
|
|
||||||
|
@ -1360,7 +1370,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
ExprLambda & lambda(*vCur.lambda.fun);
|
ExprLambda & lambda(*vCur.lambda.fun);
|
||||||
|
|
||||||
auto size =
|
auto size =
|
||||||
(lambda.arg.empty() ? 0 : 1) +
|
(!lambda.arg ? 0 : 1) +
|
||||||
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
|
(lambda.hasFormals() ? lambda.formals->formals.size() : 0);
|
||||||
Env & env2(allocEnv(size));
|
Env & env2(allocEnv(size));
|
||||||
env2.up = vCur.lambda.env;
|
env2.up = vCur.lambda.env;
|
||||||
|
@ -1373,7 +1383,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
else {
|
else {
|
||||||
forceAttrs(*args[0], pos);
|
forceAttrs(*args[0], pos);
|
||||||
|
|
||||||
if (!lambda.arg.empty())
|
if (lambda.arg)
|
||||||
env2.values[displ++] = args[0];
|
env2.values[displ++] = args[0];
|
||||||
|
|
||||||
/* For each formal argument, get the actual argument. If
|
/* For each formal argument, get the actual argument. If
|
||||||
|
@ -1398,8 +1408,17 @@ 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%'", lambda, i.name);
|
std::set<std::string> formalNames;
|
||||||
|
for (auto & formal : lambda.formals->formals)
|
||||||
|
formalNames.insert(symbols[formal.name]);
|
||||||
|
throwTypeError(
|
||||||
|
pos,
|
||||||
|
Suggestions::bestMatches(formalNames, symbols[i.name]),
|
||||||
|
"%1% called with unexpected argument '%2%'",
|
||||||
|
lambda,
|
||||||
|
i.name);
|
||||||
|
}
|
||||||
abort(); // can't happen
|
abort(); // can't happen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1413,8 +1432,8 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value &
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
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
|
||||||
? "'" + (const std::string &) lambda.name + "'"
|
? concatStrings("'", symbols[lambda.name], "'")
|
||||||
: "anonymous lambda"));
|
: "anonymous lambda"));
|
||||||
addErrorTrace(e, pos, "from call site%s", "");
|
addErrorTrace(e, pos, "from call site%s", "");
|
||||||
}
|
}
|
||||||
|
@ -1563,7 +1582,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res)
|
||||||
Nix attempted to evaluate a function as a top level expression; in
|
Nix attempted to evaluate a function as a top level expression; in
|
||||||
this case it must have its arguments supplied either by default
|
this case it must have its arguments supplied either by default
|
||||||
values, or passed explicitly with '--arg' or '--argstr'. See
|
values, or passed explicitly with '--arg' or '--argstr'. See
|
||||||
https://nixos.org/manual/nix/stable/#ss-functions.)", i.name);
|
https://nixos.org/manual/nix/stable/#ss-functions.)", symbols[i.name]);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1595,8 +1614,8 @@ void ExprAssert::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
if (!state.evalBool(env, cond, pos)) {
|
if (!state.evalBool(env, cond, pos)) {
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
cond->show(out);
|
cond->show(state.symbols, out);
|
||||||
throwAssertionError(pos, "assertion '%1%' failed", out.str());
|
state.throwAssertionError(pos, "assertion '%1%' failed", out.str());
|
||||||
}
|
}
|
||||||
body->eval(state, env, v);
|
body->eval(state, env, v);
|
||||||
}
|
}
|
||||||
|
@ -1689,7 +1708,7 @@ void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos)
|
void EvalState::concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos)
|
||||||
{
|
{
|
||||||
nrListConcats++;
|
nrListConcats++;
|
||||||
|
|
||||||
|
@ -1773,14 +1792,14 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
nf = n;
|
nf = n;
|
||||||
nf += vTmp.fpoint;
|
nf += vTmp.fpoint;
|
||||||
} else
|
} else
|
||||||
throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp));
|
state.throwEvalError(i_pos, "cannot add %1% to an integer", showType(vTmp));
|
||||||
} else if (firstType == nFloat) {
|
} else if (firstType == nFloat) {
|
||||||
if (vTmp.type() == nInt) {
|
if (vTmp.type() == nInt) {
|
||||||
nf += vTmp.integer;
|
nf += vTmp.integer;
|
||||||
} else if (vTmp.type() == nFloat) {
|
} else if (vTmp.type() == nFloat) {
|
||||||
nf += vTmp.fpoint;
|
nf += vTmp.fpoint;
|
||||||
} else
|
} else
|
||||||
throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp));
|
state.throwEvalError(i_pos, "cannot add %1% to a float", showType(vTmp));
|
||||||
} else {
|
} else {
|
||||||
if (s.empty()) s.reserve(es->size());
|
if (s.empty()) s.reserve(es->size());
|
||||||
/* skip canonization of first path, which would only be not
|
/* skip canonization of first path, which would only be not
|
||||||
|
@ -1800,7 +1819,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
v.mkFloat(nf);
|
v.mkFloat(nf);
|
||||||
else if (firstType == nPath) {
|
else if (firstType == nPath) {
|
||||||
if (!context.empty())
|
if (!context.empty())
|
||||||
throwEvalError(pos, "a string that refers to a store path cannot be appended to a path");
|
state.throwEvalError(pos, "a string that refers to a store path cannot be appended to a path");
|
||||||
v.mkPath(canonPath(str()));
|
v.mkPath(canonPath(str()));
|
||||||
} else
|
} else
|
||||||
v.mkStringMove(c_str(), context);
|
v.mkStringMove(c_str(), context);
|
||||||
|
@ -1809,7 +1828,7 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
|
||||||
|
|
||||||
void ExprPos::eval(EvalState & state, Env & env, Value & v)
|
void ExprPos::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
state.mkPos(v, ptr(&pos));
|
state.mkPos(v, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1829,7 +1848,7 @@ void EvalState::forceValueDeep(Value & v)
|
||||||
try {
|
try {
|
||||||
recurse(*i.value);
|
recurse(*i.value);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
addErrorTrace(e, *i.pos, "while evaluating the attribute '%1%'", i.name);
|
addErrorTrace(e, i.pos, "while evaluating the attribute '%1%'", symbols[i.name]);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1844,7 +1863,7 @@ void EvalState::forceValueDeep(Value & v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NixInt EvalState::forceInt(Value & v, const Pos & pos)
|
NixInt EvalState::forceInt(Value & v, const PosIdx pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() != nInt)
|
if (v.type() != nInt)
|
||||||
|
@ -1853,7 +1872,7 @@ NixInt EvalState::forceInt(Value & v, const Pos & pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
|
NixFloat EvalState::forceFloat(Value & v, const PosIdx pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() == nInt)
|
if (v.type() == nInt)
|
||||||
|
@ -1864,7 +1883,7 @@ NixFloat EvalState::forceFloat(Value & v, const Pos & pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool EvalState::forceBool(Value & v, const Pos & pos)
|
bool EvalState::forceBool(Value & v, const PosIdx pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() != nBool)
|
if (v.type() != nBool)
|
||||||
|
@ -1879,7 +1898,7 @@ bool EvalState::isFunctor(Value & fun)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void EvalState::forceFunction(Value & v, const Pos & pos)
|
void EvalState::forceFunction(Value & v, const PosIdx pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() != nFunction && !isFunctor(v))
|
if (v.type() != nFunction && !isFunctor(v))
|
||||||
|
@ -1887,7 +1906,7 @@ void EvalState::forceFunction(Value & v, const Pos & pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string_view EvalState::forceString(Value & v, const Pos & pos)
|
std::string_view EvalState::forceString(Value & v, const PosIdx pos)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
if (v.type() != nString) {
|
if (v.type() != nString) {
|
||||||
|
@ -1902,13 +1921,22 @@ std::string_view EvalState::forceString(Value & v, const Pos & pos)
|
||||||
|
|
||||||
/* Decode a context string ‘!<name>!<path>’ into a pair <path,
|
/* Decode a context string ‘!<name>!<path>’ into a pair <path,
|
||||||
name>. */
|
name>. */
|
||||||
std::pair<std::string, std::string> decodeContext(std::string_view s)
|
NixStringContextElem decodeContext(const Store & store, std::string_view s)
|
||||||
{
|
{
|
||||||
if (s.at(0) == '!') {
|
if (s.at(0) == '!') {
|
||||||
size_t index = s.find("!", 1);
|
size_t index = s.find("!", 1);
|
||||||
return {std::string(s.substr(index + 1)), std::string(s.substr(1, index - 1))};
|
return {
|
||||||
|
store.parseStorePath(s.substr(index + 1)),
|
||||||
|
std::string(s.substr(1, index - 1)),
|
||||||
|
};
|
||||||
} else
|
} else
|
||||||
return {s.at(0) == '/' ? std::string(s) : std::string(s.substr(1)), ""};
|
return {
|
||||||
|
store.parseStorePath(
|
||||||
|
s.at(0) == '/'
|
||||||
|
? s
|
||||||
|
: s.substr(1)),
|
||||||
|
"",
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1920,18 +1948,18 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos & pos)
|
std::string_view EvalState::forceString(Value & v, PathSet & context, const PosIdx pos)
|
||||||
{
|
{
|
||||||
auto s = forceString(v, pos);
|
auto s = forceString(v, pos);
|
||||||
copyContext(v, context);
|
copyContext(v, context);
|
||||||
|
@ -1939,7 +1967,7 @@ std::string_view EvalState::forceString(Value & v, PathSet & context, const Pos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string_view EvalState::forceStringNoCtx(Value & v, const Pos & pos)
|
std::string_view EvalState::forceStringNoCtx(Value & v, const PosIdx pos)
|
||||||
{
|
{
|
||||||
auto s = forceString(v, pos);
|
auto s = forceString(v, pos);
|
||||||
if (v.string.context) {
|
if (v.string.context) {
|
||||||
|
@ -1959,13 +1987,13 @@ bool EvalState::isDerivation(Value & v)
|
||||||
if (v.type() != nAttrs) return false;
|
if (v.type() != nAttrs) return false;
|
||||||
Bindings::iterator i = v.attrs->find(sType);
|
Bindings::iterator i = v.attrs->find(sType);
|
||||||
if (i == v.attrs->end()) return false;
|
if (i == v.attrs->end()) return false;
|
||||||
forceValue(*i->value, *i->pos);
|
forceValue(*i->value, i->pos);
|
||||||
if (i->value->type() != nString) return false;
|
if (i->value->type() != nString) return false;
|
||||||
return strcmp(i->value->string.s, "derivation") == 0;
|
return strcmp(i->value->string.s, "derivation") == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::optional<std::string> EvalState::tryAttrsToString(const Pos & pos, Value & v,
|
std::optional<std::string> EvalState::tryAttrsToString(const PosIdx 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);
|
||||||
|
@ -1978,7 +2006,7 @@ std::optional<std::string> EvalState::tryAttrsToString(const Pos & pos, Value &
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet & context,
|
BackedStringView EvalState::coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
||||||
bool coerceMore, bool copyToStore, bool canonicalizePath)
|
bool coerceMore, bool copyToStore, bool canonicalizePath)
|
||||||
{
|
{
|
||||||
forceValue(v, pos);
|
forceValue(v, pos);
|
||||||
|
@ -2007,7 +2035,7 @@ BackedStringView EvalState::coerceToString(const Pos & pos, Value & v, PathSet &
|
||||||
}
|
}
|
||||||
|
|
||||||
if (v.type() == nExternal)
|
if (v.type() == nExternal)
|
||||||
return v.external->coerceToString(pos, context, coerceMore, copyToStore);
|
return v.external->coerceToString(positions[pos], context, coerceMore, copyToStore);
|
||||||
|
|
||||||
if (coerceMore) {
|
if (coerceMore) {
|
||||||
|
|
||||||
|
@ -2060,7 +2088,7 @@ std::string EvalState::copyPathToStore(PathSet & context, const Path & path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
|
Path EvalState::coerceToPath(const PosIdx pos, Value & v, PathSet & context)
|
||||||
{
|
{
|
||||||
auto 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] != '/')
|
||||||
|
@ -2069,14 +2097,14 @@ Path EvalState::coerceToPath(const Pos & pos, Value & v, PathSet & context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StorePath EvalState::coerceToStorePath(const Pos & pos, Value & v, PathSet & context)
|
StorePath EvalState::coerceToStorePath(const PosIdx pos, Value & v, PathSet & context)
|
||||||
{
|
{
|
||||||
auto path = coerceToString(pos, v, context, false, false).toOwned();
|
auto path = coerceToString(pos, v, context, false, false).toOwned();
|
||||||
if (auto storePath = store->maybeParseStorePath(path))
|
if (auto storePath = store->maybeParseStorePath(path))
|
||||||
return *storePath;
|
return *storePath;
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("path '%1%' is not in the Nix store", path),
|
.msg = hintfmt("path '%1%' is not in the Nix store", path),
|
||||||
.errPos = pos
|
.errPos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2243,14 +2271,14 @@ void EvalState::printStats()
|
||||||
auto list = topObj.list("functions");
|
auto list = topObj.list("functions");
|
||||||
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)
|
||||||
obj.attr("name", (const std::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 (auto pos = positions[i.first->pos]) {
|
||||||
obj.attr("file", (const std::string &) i.first->pos.file);
|
obj.attr("file", (const std::string &) pos.file);
|
||||||
obj.attr("line", i.first->pos.line);
|
obj.attr("line", pos.line);
|
||||||
obj.attr("column", i.first->pos.column);
|
obj.attr("column", pos.column);
|
||||||
}
|
}
|
||||||
obj.attr("count", i.second);
|
obj.attr("count", i.second);
|
||||||
}
|
}
|
||||||
|
@ -2259,10 +2287,10 @@ void EvalState::printStats()
|
||||||
auto list = topObj.list("attributes");
|
auto list = topObj.list("attributes");
|
||||||
for (auto & i : attrSelects) {
|
for (auto & i : attrSelects) {
|
||||||
auto obj = list.object();
|
auto obj = list.object();
|
||||||
if (i.first) {
|
if (auto pos = positions[i.first]) {
|
||||||
obj.attr("file", (const std::string &) i.first.file);
|
obj.attr("file", (const std::string &) pos.file);
|
||||||
obj.attr("line", i.first.line);
|
obj.attr("line", pos.line);
|
||||||
obj.attr("column", i.first.column);
|
obj.attr("column", pos.column);
|
||||||
}
|
}
|
||||||
obj.attr("count", i.second);
|
obj.attr("count", i.second);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,14 +23,14 @@ class StorePath;
|
||||||
enum RepairFlag : bool;
|
enum RepairFlag : bool;
|
||||||
|
|
||||||
|
|
||||||
typedef void (* PrimOpFun) (EvalState & state, const Pos & pos, Value * * args, Value & v);
|
typedef void (* PrimOpFun) (EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
||||||
|
|
||||||
|
|
||||||
struct PrimOp
|
struct PrimOp
|
||||||
{
|
{
|
||||||
PrimOpFun fun;
|
PrimOpFun fun;
|
||||||
size_t arity;
|
size_t arity;
|
||||||
Symbol name;
|
std::string name;
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
const char * doc = nullptr;
|
const char * doc = nullptr;
|
||||||
};
|
};
|
||||||
|
@ -53,7 +53,8 @@ void copyContext(const Value & v, PathSet & context);
|
||||||
typedef std::map<Path, StorePath> SrcToStore;
|
typedef std::map<Path, StorePath> SrcToStore;
|
||||||
|
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const Value & v);
|
std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v);
|
||||||
|
std::string printValue(const EvalState & state, const Value & v);
|
||||||
|
|
||||||
|
|
||||||
typedef std::pair<std::string, std::string> SearchPathElem;
|
typedef std::pair<std::string, std::string> SearchPathElem;
|
||||||
|
@ -73,17 +74,20 @@ class EvalState
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
SymbolTable symbols;
|
SymbolTable symbols;
|
||||||
|
PosTable positions;
|
||||||
|
|
||||||
const Symbol sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
|
static inline std::string derivationNixPath = "//builtin/derivation.nix";
|
||||||
|
|
||||||
|
const SymbolIdx sWith, sOutPath, sDrvPath, sType, sMeta, sName, sValue,
|
||||||
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
|
sSystem, sOverrides, sOutputs, sOutputName, sIgnoreNulls,
|
||||||
sFile, sLine, sColumn, sFunctor, sToString,
|
sFile, sLine, sColumn, sFunctor, sToString,
|
||||||
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,
|
||||||
sPrefix;
|
sPrefix;
|
||||||
Symbol sDerivationNix;
|
SymbolIdx sDerivationNix;
|
||||||
|
|
||||||
/* If set, force copying files to the Nix store even if they
|
/* If set, force copying files to the Nix store even if they
|
||||||
already exist there. */
|
already exist there. */
|
||||||
|
@ -133,9 +137,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(
|
||||||
|
@ -144,12 +153,6 @@ public:
|
||||||
std::shared_ptr<Store> buildStore = nullptr);
|
std::shared_ptr<Store> buildStore = nullptr);
|
||||||
~EvalState();
|
~EvalState();
|
||||||
|
|
||||||
void requireExperimentalFeatureOnEvaluation(
|
|
||||||
const ExperimentalFeature &,
|
|
||||||
const std::string_view fName,
|
|
||||||
const Pos & pos
|
|
||||||
);
|
|
||||||
|
|
||||||
void addToSearchPath(const std::string & s);
|
void addToSearchPath(const std::string & s);
|
||||||
|
|
||||||
SearchPath getSearchPath() { return searchPath; }
|
SearchPath getSearchPath() { return searchPath; }
|
||||||
|
@ -206,7 +209,7 @@ public:
|
||||||
|
|
||||||
/* Look up a file in the search path. */
|
/* Look up a file in the search path. */
|
||||||
Path findFile(const std::string_view path);
|
Path findFile(const std::string_view path);
|
||||||
Path findFile(SearchPath & searchPath, const std::string_view path, const Pos & pos = noPos);
|
Path findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos = noPos);
|
||||||
|
|
||||||
/* If the specified search path element is a URI, download it. */
|
/* If the specified search path element is a URI, download it. */
|
||||||
std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem);
|
std::pair<bool, std::string> resolveSearchPathElem(const SearchPathElem & elem);
|
||||||
|
@ -218,14 +221,14 @@ public:
|
||||||
/* Evaluation the expression, then verify that it has the expected
|
/* Evaluation the expression, then verify that it has the expected
|
||||||
type. */
|
type. */
|
||||||
inline bool evalBool(Env & env, Expr * e);
|
inline bool evalBool(Env & env, Expr * e);
|
||||||
inline bool evalBool(Env & env, Expr * e, const Pos & pos);
|
inline bool evalBool(Env & env, Expr * e, const PosIdx pos);
|
||||||
inline void evalAttrs(Env & env, Expr * e, Value & v);
|
inline void evalAttrs(Env & env, Expr * e, Value & v);
|
||||||
|
|
||||||
/* If `v' is a thunk, enter it and overwrite `v' with the result
|
/* If `v' is a thunk, enter it and overwrite `v' with the result
|
||||||
of the evaluation of the thunk. If `v' is a delayed function
|
of the evaluation of the thunk. If `v' is a delayed function
|
||||||
application, call the function and overwrite `v' with the
|
application, call the function and overwrite `v' with the
|
||||||
result. Otherwise, this is a no-op. */
|
result. Otherwise, this is a no-op. */
|
||||||
inline void forceValue(Value & v, const Pos & pos);
|
inline void forceValue(Value & v, const PosIdx pos);
|
||||||
|
|
||||||
template <typename Callable>
|
template <typename Callable>
|
||||||
inline void forceValue(Value & v, Callable getPos);
|
inline void forceValue(Value & v, Callable getPos);
|
||||||
|
@ -235,33 +238,72 @@ public:
|
||||||
void forceValueDeep(Value & v);
|
void forceValueDeep(Value & v);
|
||||||
|
|
||||||
/* Force `v', and then verify that it has the expected type. */
|
/* Force `v', and then verify that it has the expected type. */
|
||||||
NixInt forceInt(Value & v, const Pos & pos);
|
NixInt forceInt(Value & v, const PosIdx pos);
|
||||||
NixFloat forceFloat(Value & v, const Pos & pos);
|
NixFloat forceFloat(Value & v, const PosIdx pos);
|
||||||
bool forceBool(Value & v, const Pos & pos);
|
bool forceBool(Value & v, const PosIdx pos);
|
||||||
|
|
||||||
void forceAttrs(Value & v, const Pos & pos);
|
void forceAttrs(Value & v, const PosIdx pos);
|
||||||
|
|
||||||
template <typename Callable>
|
template <typename Callable>
|
||||||
inline void forceAttrs(Value & v, Callable getPos);
|
inline void forceAttrs(Value & v, Callable getPos);
|
||||||
|
|
||||||
inline void forceList(Value & v, const Pos & pos);
|
inline void forceList(Value & v, const PosIdx pos);
|
||||||
void forceFunction(Value & v, const Pos & pos); // either lambda or primop
|
void forceFunction(Value & v, const PosIdx pos); // either lambda or primop
|
||||||
std::string_view forceString(Value & v, const Pos & pos = noPos);
|
std::string_view forceString(Value & v, const PosIdx pos = noPos);
|
||||||
std::string_view forceString(Value & v, PathSet & context, const Pos & pos = noPos);
|
std::string_view forceString(Value & v, PathSet & context, const PosIdx pos = noPos);
|
||||||
std::string_view forceStringNoCtx(Value & v, const Pos & pos = noPos);
|
std::string_view forceStringNoCtx(Value & v, const PosIdx pos = noPos);
|
||||||
|
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwEvalError(const PosIdx pos, const char * s) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwTypeError(const PosIdx pos, const char * s, const Value & v) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwEvalError(const char * s, const std::string & s2) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwEvalError(const PosIdx pos, const Suggestions & suggestions, const char * s,
|
||||||
|
const std::string & s2) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwEvalError(const char * s, const std::string & s2, const std::string & s3) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwEvalError(const PosIdx pos, const char * s, const std::string & s2, const std::string & s3) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwEvalError(const PosIdx p1, const char * s, const SymbolIdx sym, const PosIdx p2) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwTypeError(const PosIdx pos, const char * s) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwTypeError(const PosIdx pos, const char * s, const ExprLambda & fun, const SymbolIdx s2) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwTypeError(const PosIdx pos, const Suggestions & suggestions, const char * s,
|
||||||
|
const ExprLambda & fun, const SymbolIdx s2) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwTypeError(const char * s, const Value & v) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwAssertionError(const PosIdx pos, const char * s, const std::string & s1) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwUndefinedVarError(const PosIdx pos, const char * s, const std::string & s1) const;
|
||||||
|
[[gnu::noinline, gnu::noreturn]]
|
||||||
|
void throwMissingArgumentError(const PosIdx pos, const char * s, const std::string & s1) const;
|
||||||
|
|
||||||
|
[[gnu::noinline]]
|
||||||
|
void addErrorTrace(Error & e, const char * s, const std::string & s2) const;
|
||||||
|
[[gnu::noinline]]
|
||||||
|
void addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const;
|
||||||
|
|
||||||
|
public:
|
||||||
/* Return true iff the value `v' denotes a derivation (i.e. a
|
/* Return true iff the value `v' denotes a derivation (i.e. a
|
||||||
set with attribute `type = "derivation"'). */
|
set with attribute `type = "derivation"'). */
|
||||||
bool isDerivation(Value & v);
|
bool isDerivation(Value & v);
|
||||||
|
|
||||||
std::optional<std::string> tryAttrsToString(const Pos & pos, Value & v,
|
std::optional<std::string> tryAttrsToString(const PosIdx 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
|
||||||
string. If `coerceMore' is set, also converts nulls, integers,
|
string. If `coerceMore' is set, also converts nulls, integers,
|
||||||
booleans and lists to a string. If `copyToStore' is set,
|
booleans and lists to a string. If `copyToStore' is set,
|
||||||
referenced paths are copied to the Nix store as a side effect. */
|
referenced paths are copied to the Nix store as a side effect. */
|
||||||
BackedStringView coerceToString(const Pos & pos, Value & v, PathSet & context,
|
BackedStringView coerceToString(const PosIdx pos, Value & v, PathSet & context,
|
||||||
bool coerceMore = false, bool copyToStore = true,
|
bool coerceMore = false, bool copyToStore = true,
|
||||||
bool canonicalizePath = true);
|
bool canonicalizePath = true);
|
||||||
|
|
||||||
|
@ -270,10 +312,10 @@ public:
|
||||||
/* 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 PosIdx pos, Value & v, PathSet & context);
|
||||||
|
|
||||||
/* Like coerceToPath, but the result must be a store path. */
|
/* Like coerceToPath, but the result must be a store path. */
|
||||||
StorePath coerceToStorePath(const Pos & pos, Value & v, PathSet & context);
|
StorePath coerceToStorePath(const PosIdx pos, Value & v, PathSet & context);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
@ -306,7 +348,7 @@ public:
|
||||||
struct Doc
|
struct Doc
|
||||||
{
|
{
|
||||||
Pos pos;
|
Pos pos;
|
||||||
std::optional<Symbol> name;
|
std::optional<std::string> name;
|
||||||
size_t arity;
|
size_t arity;
|
||||||
std::vector<std::string> args;
|
std::vector<std::string> args;
|
||||||
const char * doc;
|
const char * doc;
|
||||||
|
@ -334,9 +376,9 @@ public:
|
||||||
bool isFunctor(Value & fun);
|
bool isFunctor(Value & fun);
|
||||||
|
|
||||||
// FIXME: use std::span
|
// FIXME: use std::span
|
||||||
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const Pos & pos);
|
void callFunction(Value & fun, size_t nrArgs, Value * * args, Value & vRes, const PosIdx pos);
|
||||||
|
|
||||||
void callFunction(Value & fun, Value & arg, Value & vRes, const Pos & pos)
|
void callFunction(Value & fun, Value & arg, Value & vRes, const PosIdx pos)
|
||||||
{
|
{
|
||||||
Value * args[] = {&arg};
|
Value * args[] = {&arg};
|
||||||
callFunction(fun, 1, args, vRes, pos);
|
callFunction(fun, 1, args, vRes, pos);
|
||||||
|
@ -347,10 +389,10 @@ 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 SymbolIdx & name);
|
||||||
Value * allocAttr(Value & vAttrs, std::string_view name);
|
Value * allocAttr(Value & vAttrs, std::string_view name);
|
||||||
|
|
||||||
Bindings * allocBindings(size_t capacity);
|
Bindings * allocBindings(size_t capacity);
|
||||||
|
@ -362,9 +404,9 @@ public:
|
||||||
|
|
||||||
void mkList(Value & v, size_t length);
|
void mkList(Value & v, size_t length);
|
||||||
void mkThunk_(Value & v, Expr * expr);
|
void mkThunk_(Value & v, Expr * expr);
|
||||||
void mkPos(Value & v, ptr<Pos> pos);
|
void mkPos(Value & v, PosIdx pos);
|
||||||
|
|
||||||
void concatLists(Value & v, size_t nrLists, Value * * lists, const Pos & pos);
|
void concatLists(Value & v, size_t nrLists, Value * * lists, const PosIdx pos);
|
||||||
|
|
||||||
/* Print statistics. */
|
/* Print statistics. */
|
||||||
void printStats();
|
void printStats();
|
||||||
|
@ -392,7 +434,7 @@ private:
|
||||||
|
|
||||||
bool countCalls;
|
bool countCalls;
|
||||||
|
|
||||||
typedef std::map<Symbol, size_t> PrimOpCalls;
|
typedef std::map<std::string, size_t> PrimOpCalls;
|
||||||
PrimOpCalls primOpCalls;
|
PrimOpCalls primOpCalls;
|
||||||
|
|
||||||
typedef std::map<ExprLambda *, size_t> FunctionCalls;
|
typedef std::map<ExprLambda *, size_t> FunctionCalls;
|
||||||
|
@ -400,7 +442,7 @@ private:
|
||||||
|
|
||||||
void incrFunctionCall(ExprLambda * fun);
|
void incrFunctionCall(ExprLambda * fun);
|
||||||
|
|
||||||
typedef std::map<Pos, size_t> AttrSelects;
|
typedef std::map<PosIdx, size_t> AttrSelects;
|
||||||
AttrSelects attrSelects;
|
AttrSelects attrSelects;
|
||||||
|
|
||||||
friend struct ExprOpUpdate;
|
friend struct ExprOpUpdate;
|
||||||
|
@ -411,9 +453,9 @@ private:
|
||||||
friend struct ExprFloat;
|
friend struct ExprFloat;
|
||||||
friend struct ExprPath;
|
friend struct ExprPath;
|
||||||
friend struct ExprSelect;
|
friend struct ExprSelect;
|
||||||
friend void prim_getAttr(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
friend void prim_getAttr(EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
||||||
friend void prim_match(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
friend void prim_match(EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
||||||
friend void prim_split(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
friend void prim_split(EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
||||||
|
|
||||||
friend struct Value;
|
friend struct Value;
|
||||||
};
|
};
|
||||||
|
@ -425,7 +467,7 @@ std::string showType(const Value & v);
|
||||||
|
|
||||||
/* Decode a context string ‘!<name>!<path>’ into a pair <path,
|
/* Decode a context string ‘!<name>!<path>’ into a pair <path,
|
||||||
name>. */
|
name>. */
|
||||||
std::pair<std::string, std::string> decodeContext(std::string_view s);
|
NixStringContextElem decodeContext(const Store & store, std::string_view s);
|
||||||
|
|
||||||
/* If `path' refers to a directory, then append "/default.nix". */
|
/* If `path' refers to a directory, then append "/default.nix". */
|
||||||
Path resolveExprPath(Path path);
|
Path resolveExprPath(Path path);
|
||||||
|
@ -509,3 +551,5 @@ extern EvalSettings evalSettings;
|
||||||
static const std::string corepkgsPrefix{"/__corepkgs__/"};
|
static const std::string corepkgsPrefix{"/__corepkgs__/"};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "eval-inline.hh"
|
||||||
|
|
|
@ -72,7 +72,7 @@ static std::tuple<fetchers::Tree, FlakeRef, FlakeRef> fetchOrSubstituteTree(
|
||||||
return {std::move(tree), resolvedRef, lockedRef};
|
return {std::move(tree), resolvedRef, lockedRef};
|
||||||
}
|
}
|
||||||
|
|
||||||
static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos)
|
static void forceTrivialValue(EvalState & state, Value & value, const PosIdx pos)
|
||||||
{
|
{
|
||||||
if (value.isThunk() && value.isTrivial())
|
if (value.isThunk() && value.isTrivial())
|
||||||
state.forceValue(value, pos);
|
state.forceValue(value, pos);
|
||||||
|
@ -80,20 +80,20 @@ static void forceTrivialValue(EvalState & state, Value & value, const Pos & pos)
|
||||||
|
|
||||||
|
|
||||||
static void expectType(EvalState & state, ValueType type,
|
static void expectType(EvalState & state, ValueType type,
|
||||||
Value & value, const Pos & pos)
|
Value & value, const PosIdx pos)
|
||||||
{
|
{
|
||||||
forceTrivialValue(state, value, pos);
|
forceTrivialValue(state, value, pos);
|
||||||
if (value.type() != type)
|
if (value.type() != type)
|
||||||
throw Error("expected %s but got %s at %s",
|
throw Error("expected %s but got %s at %s",
|
||||||
showType(type), showType(value.type()), pos);
|
showType(type), showType(value.type()), state.positions[pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||||
EvalState & state, Value * value, const Pos & pos,
|
EvalState & state, Value * value, const PosIdx pos,
|
||||||
const std::optional<Path> & baseDir, InputPath lockRootPath);
|
const std::optional<Path> & baseDir, InputPath lockRootPath);
|
||||||
|
|
||||||
static FlakeInput parseFlakeInput(EvalState & state,
|
static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
const std::string & inputName, Value * value, const Pos & pos,
|
const std::string & inputName, Value * value, const PosIdx pos,
|
||||||
const std::optional<Path> & baseDir, InputPath lockRootPath)
|
const std::optional<Path> & baseDir, InputPath lockRootPath)
|
||||||
{
|
{
|
||||||
expectType(state, nAttrs, *value, pos);
|
expectType(state, nAttrs, *value, pos);
|
||||||
|
@ -111,37 +111,39 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
for (nix::Attr attr : *(value->attrs)) {
|
for (nix::Attr attr : *(value->attrs)) {
|
||||||
try {
|
try {
|
||||||
if (attr.name == sUrl) {
|
if (attr.name == sUrl) {
|
||||||
expectType(state, nString, *attr.value, *attr.pos);
|
expectType(state, nString, *attr.value, attr.pos);
|
||||||
url = attr.value->string.s;
|
url = attr.value->string.s;
|
||||||
attrs.emplace("url", *url);
|
attrs.emplace("url", *url);
|
||||||
} else if (attr.name == sFlake) {
|
} else if (attr.name == sFlake) {
|
||||||
expectType(state, nBool, *attr.value, *attr.pos);
|
expectType(state, nBool, *attr.value, attr.pos);
|
||||||
input.isFlake = attr.value->boolean;
|
input.isFlake = attr.value->boolean;
|
||||||
} else if (attr.name == sInputs) {
|
} else if (attr.name == sInputs) {
|
||||||
input.overrides = parseFlakeInputs(state, attr.value, *attr.pos, baseDir, lockRootPath);
|
input.overrides = parseFlakeInputs(state, attr.value, attr.pos, baseDir, lockRootPath);
|
||||||
} else if (attr.name == sFollows) {
|
} else if (attr.name == sFollows) {
|
||||||
expectType(state, nString, *attr.value, *attr.pos);
|
expectType(state, nString, *attr.value, attr.pos);
|
||||||
auto follows(parseInputPath(attr.value->string.s));
|
auto follows(parseInputPath(attr.value->string.s));
|
||||||
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
|
follows.insert(follows.begin(), lockRootPath.begin(), lockRootPath.end());
|
||||||
input.follows = follows;
|
input.follows = follows;
|
||||||
} else {
|
} else {
|
||||||
switch (attr.value->type()) {
|
switch (attr.value->type()) {
|
||||||
case nString:
|
case nString:
|
||||||
attrs.emplace(attr.name, attr.value->string.s);
|
attrs.emplace(state.symbols[attr.name], attr.value->string.s);
|
||||||
break;
|
break;
|
||||||
case nBool:
|
case nBool:
|
||||||
attrs.emplace(attr.name, Explicit<bool> { attr.value->boolean });
|
attrs.emplace(state.symbols[attr.name], Explicit<bool> { attr.value->boolean });
|
||||||
break;
|
break;
|
||||||
case nInt:
|
case nInt:
|
||||||
attrs.emplace(attr.name, (long unsigned int)attr.value->integer);
|
attrs.emplace(state.symbols[attr.name], (long unsigned int)attr.value->integer);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
|
throw TypeError("flake input attribute '%s' is %s while a string, Boolean, or integer is expected",
|
||||||
attr.name, showType(*attr.value));
|
state.symbols[attr.name], showType(*attr.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(*attr.pos, hintfmt("in flake attribute '%s'", attr.name));
|
e.addTrace(
|
||||||
|
state.positions[attr.pos],
|
||||||
|
hintfmt("in flake attribute '%s'", state.symbols[attr.name]));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,13 +152,13 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
try {
|
try {
|
||||||
input.ref = FlakeRef::fromAttrs(attrs);
|
input.ref = FlakeRef::fromAttrs(attrs);
|
||||||
} catch (Error & e) {
|
} catch (Error & e) {
|
||||||
e.addTrace(pos, hintfmt("in flake input"));
|
e.addTrace(state.positions[pos], hintfmt("in flake input"));
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
attrs.erase("url");
|
attrs.erase("url");
|
||||||
if (!attrs.empty())
|
if (!attrs.empty())
|
||||||
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, pos);
|
throw Error("unexpected flake input attribute '%s', at %s", attrs.begin()->first, state.positions[pos]);
|
||||||
if (url)
|
if (url)
|
||||||
input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake);
|
input.ref = parseFlakeRef(*url, baseDir, true, input.isFlake);
|
||||||
}
|
}
|
||||||
|
@ -168,7 +170,7 @@ static FlakeInput parseFlakeInput(EvalState & state,
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||||
EvalState & state, Value * value, const Pos & pos,
|
EvalState & state, Value * value, const PosIdx pos,
|
||||||
const std::optional<Path> & baseDir, InputPath lockRootPath)
|
const std::optional<Path> & baseDir, InputPath lockRootPath)
|
||||||
{
|
{
|
||||||
std::map<FlakeId, FlakeInput> inputs;
|
std::map<FlakeId, FlakeInput> inputs;
|
||||||
|
@ -176,11 +178,11 @@ static std::map<FlakeId, FlakeInput> parseFlakeInputs(
|
||||||
expectType(state, nAttrs, *value, pos);
|
expectType(state, nAttrs, *value, pos);
|
||||||
|
|
||||||
for (nix::Attr & inputAttr : *(*value).attrs) {
|
for (nix::Attr & inputAttr : *(*value).attrs) {
|
||||||
inputs.emplace(inputAttr.name,
|
inputs.emplace(state.symbols[inputAttr.name],
|
||||||
parseFlakeInput(state,
|
parseFlakeInput(state,
|
||||||
inputAttr.name,
|
state.symbols[inputAttr.name],
|
||||||
inputAttr.value,
|
inputAttr.value,
|
||||||
*inputAttr.pos,
|
inputAttr.pos,
|
||||||
baseDir,
|
baseDir,
|
||||||
lockRootPath));
|
lockRootPath));
|
||||||
}
|
}
|
||||||
|
@ -218,28 +220,28 @@ static Flake getFlake(
|
||||||
Value vInfo;
|
Value vInfo;
|
||||||
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
|
state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack
|
||||||
|
|
||||||
expectType(state, nAttrs, vInfo, Pos(foFile, state.symbols.create(flakeFile), 0, 0));
|
expectType(state, nAttrs, vInfo, state.positions.add({flakeFile, foFile}, 0, 0));
|
||||||
|
|
||||||
if (auto description = vInfo.attrs->get(state.sDescription)) {
|
if (auto description = vInfo.attrs->get(state.sDescription)) {
|
||||||
expectType(state, nString, *description->value, *description->pos);
|
expectType(state, nString, *description->value, description->pos);
|
||||||
flake.description = description->value->string.s;
|
flake.description = description->value->string.s;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sInputs = state.symbols.create("inputs");
|
auto sInputs = state.symbols.create("inputs");
|
||||||
|
|
||||||
if (auto inputs = vInfo.attrs->get(sInputs))
|
if (auto inputs = vInfo.attrs->get(sInputs))
|
||||||
flake.inputs = parseFlakeInputs(state, inputs->value, *inputs->pos, flakeDir, lockRootPath);
|
flake.inputs = parseFlakeInputs(state, inputs->value, inputs->pos, flakeDir, lockRootPath);
|
||||||
|
|
||||||
auto sOutputs = state.symbols.create("outputs");
|
auto sOutputs = state.symbols.create("outputs");
|
||||||
|
|
||||||
if (auto outputs = vInfo.attrs->get(sOutputs)) {
|
if (auto outputs = vInfo.attrs->get(sOutputs)) {
|
||||||
expectType(state, nFunction, *outputs->value, *outputs->pos);
|
expectType(state, nFunction, *outputs->value, outputs->pos);
|
||||||
|
|
||||||
if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) {
|
if (outputs->value->isLambda() && outputs->value->lambda.fun->hasFormals()) {
|
||||||
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
|
for (auto & formal : outputs->value->lambda.fun->formals->formals) {
|
||||||
if (formal.name != state.sSelf)
|
if (formal.name != state.sSelf)
|
||||||
flake.inputs.emplace(formal.name, FlakeInput {
|
flake.inputs.emplace(state.symbols[formal.name], FlakeInput {
|
||||||
.ref = parseFlakeRef(formal.name)
|
.ref = parseFlakeRef(state.symbols[formal.name])
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -250,35 +252,41 @@ static Flake getFlake(
|
||||||
auto sNixConfig = state.symbols.create("nixConfig");
|
auto sNixConfig = state.symbols.create("nixConfig");
|
||||||
|
|
||||||
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
|
if (auto nixConfig = vInfo.attrs->get(sNixConfig)) {
|
||||||
expectType(state, nAttrs, *nixConfig->value, *nixConfig->pos);
|
expectType(state, nAttrs, *nixConfig->value, nixConfig->pos);
|
||||||
|
|
||||||
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, std::string(state.forceStringNoCtx(*setting.value, *setting.pos))});
|
flake.config.settings.emplace(
|
||||||
|
state.symbols[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(
|
||||||
setting.name,
|
state.symbols[setting.name],
|
||||||
state.coerceToString(*setting.pos, *setting.value, emptyContext, false, true, true) .toOwned());
|
state.coerceToString(setting.pos, *setting.value, emptyContext, false, true, true) .toOwned());
|
||||||
}
|
}
|
||||||
else if (setting.value->type() == nInt)
|
else if (setting.value->type() == nInt)
|
||||||
flake.config.settings.insert({setting.name, state.forceInt(*setting.value, *setting.pos)});
|
flake.config.settings.emplace(
|
||||||
|
state.symbols[setting.name],
|
||||||
|
state.forceInt(*setting.value, setting.pos));
|
||||||
else if (setting.value->type() == nBool)
|
else if (setting.value->type() == nBool)
|
||||||
flake.config.settings.insert({setting.name, Explicit<bool> { state.forceBool(*setting.value, *setting.pos) }});
|
flake.config.settings.emplace(
|
||||||
|
state.symbols[setting.name],
|
||||||
|
Explicit<bool> { state.forceBool(*setting.value, setting.pos) });
|
||||||
else if (setting.value->type() == nList) {
|
else if (setting.value->type() == nList) {
|
||||||
std::vector<std::string> ss;
|
std::vector<std::string> ss;
|
||||||
for (auto elem : setting.value->listItems()) {
|
for (auto elem : setting.value->listItems()) {
|
||||||
if (elem->type() != nString)
|
if (elem->type() != nString)
|
||||||
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
|
throw TypeError("list element in flake configuration setting '%s' is %s while a string is expected",
|
||||||
setting.name, showType(*setting.value));
|
state.symbols[setting.name], showType(*setting.value));
|
||||||
ss.emplace_back(state.forceStringNoCtx(*elem, *setting.pos));
|
ss.emplace_back(state.forceStringNoCtx(*elem, setting.pos));
|
||||||
}
|
}
|
||||||
flake.config.settings.insert({setting.name, ss});
|
flake.config.settings.emplace(state.symbols[setting.name], ss);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
throw TypeError("flake configuration setting '%s' is %s",
|
throw TypeError("flake configuration setting '%s' is %s",
|
||||||
setting.name, showType(*setting.value));
|
state.symbols[setting.name], showType(*setting.value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -288,7 +296,7 @@ static Flake getFlake(
|
||||||
attr.name != sOutputs &&
|
attr.name != sOutputs &&
|
||||||
attr.name != sNixConfig)
|
attr.name != sNixConfig)
|
||||||
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
|
throw Error("flake '%s' has an unsupported attribute '%s', at %s",
|
||||||
lockedRef, attr.name, *attr.pos);
|
lockedRef, state.symbols[attr.name], state.positions[attr.pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return flake;
|
return flake;
|
||||||
|
@ -704,14 +712,12 @@ void callFlake(EvalState & state,
|
||||||
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
|
state.callFunction(*vTmp2, *vRootSubdir, vRes, noPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
state.requireExperimentalFeatureOnEvaluation(Xp::Flakes, "builtins.getFlake", pos);
|
|
||||||
|
|
||||||
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
|
std::string flakeRefS(state.forceStringNoCtx(*args[0], pos));
|
||||||
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
auto flakeRef = parseFlakeRef(flakeRefS, {}, true);
|
||||||
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
if (evalSettings.pureEval && !flakeRef.input.isLocked())
|
||||||
throw Error("cannot call 'getFlake' on unlocked 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, state.positions[pos]);
|
||||||
|
|
||||||
callFlake(state,
|
callFlake(state,
|
||||||
lockFlake(state, flakeRef,
|
lockFlake(state, flakeRef,
|
||||||
|
@ -723,7 +729,30 @@ static void prim_getFlake(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
v);
|
v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static RegisterPrimOp r2("__getFlake", 1, prim_getFlake);
|
static RegisterPrimOp r2({
|
||||||
|
.name = "__getFlake",
|
||||||
|
.args = {"args"},
|
||||||
|
.doc = R"(
|
||||||
|
Fetch a flake from a flake reference, and return its output attributes and some metadata. For example:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(builtins.getFlake "nix/55bc52401966fbffa525c574c14f67b00bc4fb3a").packages.x86_64-linux.nix
|
||||||
|
```
|
||||||
|
|
||||||
|
Unless impure evaluation is allowed (`--impure`), the flake reference
|
||||||
|
must be "locked", e.g. contain a Git revision or content hash. An
|
||||||
|
example of an unlocked usage is:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
(builtins.getFlake "github:edolstra/dwarffs").rev
|
||||||
|
```
|
||||||
|
|
||||||
|
This function is only available if you enable the experimental feature
|
||||||
|
`flakes`.
|
||||||
|
)",
|
||||||
|
.fun = prim_getFlake,
|
||||||
|
.experimentalFeature = Xp::Flakes,
|
||||||
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ namespace nix {
|
||||||
|
|
||||||
struct FunctionCallTrace
|
struct FunctionCallTrace
|
||||||
{
|
{
|
||||||
const Pos & pos;
|
const Pos pos;
|
||||||
FunctionCallTrace(const Pos & pos);
|
FunctionCallTrace(const Pos & pos);
|
||||||
~FunctionCallTrace();
|
~FunctionCallTrace();
|
||||||
};
|
};
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
@ -60,7 +61,7 @@ std::string DrvInfo::querySystem() const
|
||||||
{
|
{
|
||||||
if (system == "" && attrs) {
|
if (system == "" && attrs) {
|
||||||
auto i = attrs->find(state->sSystem);
|
auto i = attrs->find(state->sSystem);
|
||||||
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, *i->pos);
|
system = i == attrs->end() ? "unknown" : state->forceStringNoCtx(*i->value, i->pos);
|
||||||
}
|
}
|
||||||
return system;
|
return system;
|
||||||
}
|
}
|
||||||
|
@ -74,7 +75,7 @@ std::optional<StorePath> DrvInfo::queryDrvPath() const
|
||||||
if (i == attrs->end())
|
if (i == attrs->end())
|
||||||
drvPath = {std::nullopt};
|
drvPath = {std::nullopt};
|
||||||
else
|
else
|
||||||
drvPath = {state->coerceToStorePath(*i->pos, *i->value, context)};
|
drvPath = {state->coerceToStorePath(i->pos, *i->value, context)};
|
||||||
}
|
}
|
||||||
return drvPath.value_or(std::nullopt);
|
return drvPath.value_or(std::nullopt);
|
||||||
}
|
}
|
||||||
|
@ -94,7 +95,7 @@ StorePath DrvInfo::queryOutPath() const
|
||||||
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->coerceToStorePath(*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");
|
||||||
|
@ -102,30 +103,34 @@ StorePath 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. */
|
||||||
Bindings::iterator i;
|
Bindings::iterator i;
|
||||||
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
|
if (attrs && (i = attrs->find(state->sOutputs)) != attrs->end()) {
|
||||||
state->forceList(*i->value, *i->pos);
|
state->forceList(*i->value, i->pos);
|
||||||
|
|
||||||
/* 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));
|
||||||
std::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.emplace(name, state->coerceToStorePath(*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.emplace("out", queryOutPath());
|
outputs.emplace("out", withPaths ? std::optional{queryOutPath()} : std::nullopt);
|
||||||
}
|
}
|
||||||
if (!onlyOutputsToInstall || !attrs)
|
if (!onlyOutputsToInstall || !attrs)
|
||||||
return outputs;
|
return outputs;
|
||||||
|
@ -163,7 +168,7 @@ Bindings * DrvInfo::getMeta()
|
||||||
if (!attrs) return 0;
|
if (!attrs) return 0;
|
||||||
Bindings::iterator a = attrs->find(state->sMeta);
|
Bindings::iterator a = attrs->find(state->sMeta);
|
||||||
if (a == attrs->end()) return 0;
|
if (a == attrs->end()) return 0;
|
||||||
state->forceAttrs(*a->value, *a->pos);
|
state->forceAttrs(*a->value, a->pos);
|
||||||
meta = a->value->attrs;
|
meta = a->value->attrs;
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
@ -174,7 +179,7 @@ StringSet DrvInfo::queryMetaNames()
|
||||||
StringSet res;
|
StringSet res;
|
||||||
if (!getMeta()) return res;
|
if (!getMeta()) return res;
|
||||||
for (auto & i : *meta)
|
for (auto & i : *meta)
|
||||||
res.insert(i.name);
|
res.emplace(state->symbols[i.name]);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,7 +269,7 @@ 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));
|
||||||
Symbol sym = state->symbols.create(name);
|
auto sym = state->symbols.create(name);
|
||||||
if (meta)
|
if (meta)
|
||||||
for (auto i : *meta)
|
for (auto i : *meta)
|
||||||
if (i.name != sym)
|
if (i.name != sym)
|
||||||
|
@ -351,11 +356,11 @@ static void getDerivations(EvalState & state, Value & vIn,
|
||||||
there are names clashes between derivations, the derivation
|
there are names clashes between derivations, the derivation
|
||||||
bound to the attribute with the "lower" name should take
|
bound to the attribute with the "lower" name should take
|
||||||
precedence). */
|
precedence). */
|
||||||
for (auto & i : v.attrs->lexicographicOrder()) {
|
for (auto & i : v.attrs->lexicographicOrder(state.symbols)) {
|
||||||
debug("evaluating attribute '%1%'", i->name);
|
debug("evaluating attribute '%1%'", state.symbols[i->name]);
|
||||||
if (!std::regex_match(std::string(i->name), attrRegex))
|
if (!std::regex_match(std::string(state.symbols[i->name]), attrRegex))
|
||||||
continue;
|
continue;
|
||||||
std::string pathPrefix2 = addToPath(pathPrefix, i->name);
|
std::string pathPrefix2 = addToPath(pathPrefix, state.symbols[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 +369,7 @@ static void getDerivations(EvalState & state, Value & vIn,
|
||||||
`recurseForDerivations = true' attribute. */
|
`recurseForDerivations = true' attribute. */
|
||||||
if (i->value->type() == nAttrs) {
|
if (i->value->type() == nAttrs) {
|
||||||
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
|
Bindings::iterator j = i->value->attrs->find(state.sRecurseForDerivations);
|
||||||
if (j != i->value->attrs->end() && state.forceBool(*j->value, *j->pos))
|
if (j != i->value->attrs->end() && state.forceBool(*j->value, j->pos))
|
||||||
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
getDerivations(state, *i->value, pathPrefix2, autoArgs, drvs, done, ignoreAssertionFailures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ namespace nix {
|
||||||
struct DrvInfo
|
struct DrvInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef std::map<std::string, StorePath> Outputs;
|
typedef std::map<std::string, std::optional<StorePath>> Outputs;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EvalState * state;
|
EvalState * state;
|
||||||
|
@ -46,8 +46,9 @@ public:
|
||||||
StorePath requireDrvPath() const;
|
StorePath requireDrvPath() const;
|
||||||
StorePath queryOutPath() const;
|
StorePath queryOutPath() const;
|
||||||
std::string queryOutputName() const;
|
std::string queryOutputName() const;
|
||||||
/** Return the list of outputs. The "outputs to install" are determined by `meta.outputsToInstall`. */
|
/** Return the unordered map of output names to (optional) output paths.
|
||||||
Outputs queryOutputs(bool onlyOutputsToInstall = false);
|
* The "outputs to install" are determined by `meta.outputsToInstall`. */
|
||||||
|
Outputs queryOutputs(bool withPaths = true, bool onlyOutputsToInstall = false);
|
||||||
|
|
||||||
StringSet queryMetaNames();
|
StringSet queryMetaNames();
|
||||||
Value * queryMeta(const std::string & name);
|
Value * queryMeta(const std::string & name);
|
||||||
|
|
|
@ -28,6 +28,13 @@ using namespace nix;
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data)
|
||||||
|
{
|
||||||
|
return data->state.positions.add(data->origin, 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 = data->state.positions[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 = data->state.positions[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 = data->state.positions[CUR_POS],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
|
{SPATH} { yylval->path = {yytext, (size_t) yyleng}; return SPATH; }
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "nixexpr.hh"
|
#include "nixexpr.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
|
#include "eval.hh"
|
||||||
|
#include "symbol-table.hh"
|
||||||
#include "util.hh"
|
#include "util.hh"
|
||||||
|
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
|
@ -10,12 +12,6 @@ namespace nix {
|
||||||
|
|
||||||
/* Displaying abstract syntax trees. */
|
/* Displaying abstract syntax trees. */
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const Expr & e)
|
|
||||||
{
|
|
||||||
e.show(str);
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void showString(std::ostream & str, std::string_view s)
|
static void showString(std::ostream & str, std::string_view s)
|
||||||
{
|
{
|
||||||
str << '"';
|
str << '"';
|
||||||
|
@ -54,81 +50,101 @@ static void showId(std::ostream & str, std::string_view s)
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const Symbol & sym)
|
std::ostream & operator << (std::ostream & str, const Symbol & sym)
|
||||||
{
|
{
|
||||||
showId(str, *sym.s);
|
showId(str, sym.s);
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Expr::show(std::ostream & str) const
|
void Expr::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprInt::show(std::ostream & str) const
|
void ExprInt::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << n;
|
str << n;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprFloat::show(std::ostream & str) const
|
void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << nf;
|
str << nf;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprString::show(std::ostream & str) const
|
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
showString(str, s);
|
showString(str, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprPath::show(std::ostream & str) const
|
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << s;
|
str << s;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprVar::show(std::ostream & str) const
|
void ExprVar::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << name;
|
str << symbols[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprSelect::show(std::ostream & str) const
|
void ExprSelect::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "(" << *e << ")." << showAttrPath(attrPath);
|
str << "(";
|
||||||
if (def) str << " or (" << *def << ")";
|
e->show(symbols, str);
|
||||||
|
str << ")." << showAttrPath(symbols, attrPath);
|
||||||
|
if (def) {
|
||||||
|
str << " or (";
|
||||||
|
def->show(symbols, str);
|
||||||
|
str << ")";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprOpHasAttr::show(std::ostream & str) const
|
void ExprOpHasAttr::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "((" << *e << ") ? " << showAttrPath(attrPath) << ")";
|
str << "((";
|
||||||
|
e->show(symbols, str);
|
||||||
|
str << ") ? " << showAttrPath(symbols, attrPath) << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprAttrs::show(std::ostream & str) const
|
void ExprAttrs::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
if (recursive) str << "rec ";
|
if (recursive) str << "rec ";
|
||||||
str << "{ ";
|
str << "{ ";
|
||||||
typedef const decltype(attrs)::value_type * Attr;
|
typedef const decltype(attrs)::value_type * Attr;
|
||||||
std::vector<Attr> sorted;
|
std::vector<Attr> sorted;
|
||||||
for (auto & i : attrs) sorted.push_back(&i);
|
for (auto & i : attrs) sorted.push_back(&i);
|
||||||
std::sort(sorted.begin(), sorted.end(), [](Attr a, Attr b) {
|
std::sort(sorted.begin(), sorted.end(), [&](Attr a, Attr b) {
|
||||||
return (const std::string &) a->first < (const std::string &) b->first;
|
std::string_view sa = symbols[a->first], sb = symbols[b->first];
|
||||||
});
|
return sa < sb;
|
||||||
|
});
|
||||||
for (auto & i : sorted) {
|
for (auto & i : sorted) {
|
||||||
if (i->second.inherited)
|
if (i->second.inherited)
|
||||||
str << "inherit " << i->first << " " << "; ";
|
str << "inherit " << symbols[i->first] << " " << "; ";
|
||||||
else
|
else {
|
||||||
str << i->first << " = " << *i->second.e << "; ";
|
str << symbols[i->first] << " = ";
|
||||||
|
i->second.e->show(symbols, str);
|
||||||
|
str << "; ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto & i : dynamicAttrs) {
|
||||||
|
str << "\"${";
|
||||||
|
i.nameExpr->show(symbols, str);
|
||||||
|
str << "}\" = ";
|
||||||
|
i.valueExpr->show(symbols, str);
|
||||||
|
str << "; ";
|
||||||
}
|
}
|
||||||
for (auto & i : dynamicAttrs)
|
|
||||||
str << "\"${" << *i.nameExpr << "}\" = " << *i.valueExpr << "; ";
|
|
||||||
str << "}";
|
str << "}";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprList::show(std::ostream & str) const
|
void ExprList::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "[ ";
|
str << "[ ";
|
||||||
for (auto & i : elems)
|
for (auto & i : elems) {
|
||||||
str << "(" << *i << ") ";
|
str << "(";
|
||||||
|
i->show(symbols, str);
|
||||||
|
str << ") ";
|
||||||
|
}
|
||||||
str << "]";
|
str << "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprLambda::show(std::ostream & str) const
|
void ExprLambda::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "(";
|
str << "(";
|
||||||
if (hasFormals()) {
|
if (hasFormals()) {
|
||||||
|
@ -136,74 +152,100 @@ void ExprLambda::show(std::ostream & str) const
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (auto & i : formals->formals) {
|
for (auto & i : formals->formals) {
|
||||||
if (first) first = false; else str << ", ";
|
if (first) first = false; else str << ", ";
|
||||||
str << i.name;
|
str << symbols[i.name];
|
||||||
if (i.def) str << " ? " << *i.def;
|
if (i.def) {
|
||||||
|
str << " ? ";
|
||||||
|
i.def->show(symbols, str);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (formals->ellipsis) {
|
if (formals->ellipsis) {
|
||||||
if (!first) str << ", ";
|
if (!first) str << ", ";
|
||||||
str << "...";
|
str << "...";
|
||||||
}
|
}
|
||||||
str << " }";
|
str << " }";
|
||||||
if (!arg.empty()) str << " @ ";
|
if (arg) str << " @ ";
|
||||||
}
|
}
|
||||||
if (!arg.empty()) str << arg;
|
if (arg) str << symbols[arg];
|
||||||
str << ": " << *body << ")";
|
str << ": ";
|
||||||
|
body->show(symbols, str);
|
||||||
|
str << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprCall::show(std::ostream & str) const
|
void ExprCall::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << '(' << *fun;
|
str << '(';
|
||||||
|
fun->show(symbols, str);
|
||||||
for (auto e : args) {
|
for (auto e : args) {
|
||||||
str << ' ';
|
str << ' ';
|
||||||
str << *e;
|
e->show(symbols, str);
|
||||||
}
|
}
|
||||||
str << ')';
|
str << ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprLet::show(std::ostream & str) const
|
void ExprLet::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "(let ";
|
str << "(let ";
|
||||||
for (auto & i : attrs->attrs)
|
for (auto & i : attrs->attrs)
|
||||||
if (i.second.inherited) {
|
if (i.second.inherited) {
|
||||||
str << "inherit " << i.first << "; ";
|
str << "inherit " << symbols[i.first] << "; ";
|
||||||
}
|
}
|
||||||
else
|
else {
|
||||||
str << i.first << " = " << *i.second.e << "; ";
|
str << symbols[i.first] << " = ";
|
||||||
str << "in " << *body << ")";
|
i.second.e->show(symbols, str);
|
||||||
|
str << "; ";
|
||||||
|
}
|
||||||
|
str << "in ";
|
||||||
|
body->show(symbols, str);
|
||||||
|
str << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprWith::show(std::ostream & str) const
|
void ExprWith::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "(with " << *attrs << "; " << *body << ")";
|
str << "(with ";
|
||||||
|
attrs->show(symbols, str);
|
||||||
|
str << "; ";
|
||||||
|
body->show(symbols, str);
|
||||||
|
str << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprIf::show(std::ostream & str) const
|
void ExprIf::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "(if " << *cond << " then " << *then << " else " << *else_ << ")";
|
str << "(if ";
|
||||||
|
cond->show(symbols, str);
|
||||||
|
str << " then ";
|
||||||
|
then->show(symbols, str);
|
||||||
|
str << " else ";
|
||||||
|
else_->show(symbols, str);
|
||||||
|
str << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprAssert::show(std::ostream & str) const
|
void ExprAssert::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "assert " << *cond << "; " << *body;
|
str << "assert ";
|
||||||
|
cond->show(symbols, str);
|
||||||
|
str << "; ";
|
||||||
|
body->show(symbols, str);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprOpNot::show(std::ostream & str) const
|
void ExprOpNot::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "(! " << *e << ")";
|
str << "(! ";
|
||||||
|
e->show(symbols, str);
|
||||||
|
str << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprConcatStrings::show(std::ostream & str) const
|
void ExprConcatStrings::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
bool first = true;
|
bool first = true;
|
||||||
str << "(";
|
str << "(";
|
||||||
for (auto & i : *es) {
|
for (auto & i : *es) {
|
||||||
if (first) first = false; else str << " + ";
|
if (first) first = false; else str << " + ";
|
||||||
str << *i.second;
|
i.second->show(symbols, str);
|
||||||
}
|
}
|
||||||
str << ")";
|
str << ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprPos::show(std::ostream & str) const
|
void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
str << "__curPos";
|
str << "__curPos";
|
||||||
}
|
}
|
||||||
|
@ -234,48 +276,49 @@ std::ostream & operator << (std::ostream & str, const Pos & pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string showAttrPath(const AttrPath & attrPath)
|
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath)
|
||||||
{
|
{
|
||||||
std::ostringstream out;
|
std::ostringstream out;
|
||||||
bool first = true;
|
bool first = true;
|
||||||
for (auto & i : attrPath) {
|
for (auto & i : attrPath) {
|
||||||
if (!first) out << '.'; else first = false;
|
if (!first) out << '.'; else first = false;
|
||||||
if (i.symbol.set())
|
if (i.symbol)
|
||||||
out << i.symbol;
|
out << symbols[i.symbol];
|
||||||
else
|
else {
|
||||||
out << "\"${" << *i.expr << "}\"";
|
out << "\"${";
|
||||||
|
i.expr->show(symbols, out);
|
||||||
|
out << "}\"";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return out.str();
|
return out.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Pos noPos;
|
|
||||||
|
|
||||||
|
|
||||||
/* Computing levels/displacements for variables. */
|
/* Computing levels/displacements for variables. */
|
||||||
|
|
||||||
void Expr::bindVars(const StaticEnv & env)
|
void Expr::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprInt::bindVars(const StaticEnv & env)
|
void ExprInt::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprFloat::bindVars(const StaticEnv & env)
|
void ExprFloat::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprString::bindVars(const StaticEnv & env)
|
void ExprString::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprPath::bindVars(const StaticEnv & env)
|
void ExprPath::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprVar::bindVars(const StaticEnv & env)
|
void ExprVar::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
/* Check whether the variable appears in the environment. If so,
|
/* Check whether the variable appears in the environment. If so,
|
||||||
set its level and displacement. */
|
set its level and displacement. */
|
||||||
|
@ -301,31 +344,31 @@ void ExprVar::bindVars(const StaticEnv & env)
|
||||||
"undefined variable" error now. */
|
"undefined variable" error now. */
|
||||||
if (withLevel == -1)
|
if (withLevel == -1)
|
||||||
throw UndefinedVarError({
|
throw UndefinedVarError({
|
||||||
.msg = hintfmt("undefined variable '%1%'", name),
|
.msg = hintfmt("undefined variable '%1%'", es.symbols[name]),
|
||||||
.errPos = pos
|
.errPos = es.positions[pos]
|
||||||
});
|
});
|
||||||
fromWith = true;
|
fromWith = true;
|
||||||
this->level = withLevel;
|
this->level = withLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprSelect::bindVars(const StaticEnv & env)
|
void ExprSelect::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
e->bindVars(env);
|
e->bindVars(es, env);
|
||||||
if (def) def->bindVars(env);
|
if (def) def->bindVars(es, env);
|
||||||
for (auto & i : attrPath)
|
for (auto & i : attrPath)
|
||||||
if (!i.symbol.set())
|
if (!i.symbol)
|
||||||
i.expr->bindVars(env);
|
i.expr->bindVars(es, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprOpHasAttr::bindVars(const StaticEnv & env)
|
void ExprOpHasAttr::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
e->bindVars(env);
|
e->bindVars(es, env);
|
||||||
for (auto & i : attrPath)
|
for (auto & i : attrPath)
|
||||||
if (!i.symbol.set())
|
if (!i.symbol)
|
||||||
i.expr->bindVars(env);
|
i.expr->bindVars(es, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprAttrs::bindVars(const StaticEnv & env)
|
void ExprAttrs::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
const StaticEnv * dynamicEnv = &env;
|
const StaticEnv * dynamicEnv = &env;
|
||||||
StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0);
|
StaticEnv newEnv(false, &env, recursive ? attrs.size() : 0);
|
||||||
|
@ -340,35 +383,35 @@ void ExprAttrs::bindVars(const StaticEnv & env)
|
||||||
// No need to sort newEnv since attrs is in sorted order.
|
// No need to sort newEnv since attrs is in sorted order.
|
||||||
|
|
||||||
for (auto & i : attrs)
|
for (auto & i : attrs)
|
||||||
i.second.e->bindVars(i.second.inherited ? env : newEnv);
|
i.second.e->bindVars(es, i.second.inherited ? env : newEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
else
|
else
|
||||||
for (auto & i : attrs)
|
for (auto & i : attrs)
|
||||||
i.second.e->bindVars(env);
|
i.second.e->bindVars(es, env);
|
||||||
|
|
||||||
for (auto & i : dynamicAttrs) {
|
for (auto & i : dynamicAttrs) {
|
||||||
i.nameExpr->bindVars(*dynamicEnv);
|
i.nameExpr->bindVars(es, *dynamicEnv);
|
||||||
i.valueExpr->bindVars(*dynamicEnv);
|
i.valueExpr->bindVars(es, *dynamicEnv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprList::bindVars(const StaticEnv & env)
|
void ExprList::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
for (auto & i : elems)
|
for (auto & i : elems)
|
||||||
i->bindVars(env);
|
i->bindVars(es, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprLambda::bindVars(const StaticEnv & env)
|
void ExprLambda::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
StaticEnv newEnv(
|
StaticEnv newEnv(
|
||||||
false, &env,
|
false, &env,
|
||||||
(hasFormals() ? formals->formals.size() : 0) +
|
(hasFormals() ? formals->formals.size() : 0) +
|
||||||
(arg.empty() ? 0 : 1));
|
(!arg ? 0 : 1));
|
||||||
|
|
||||||
Displacement displ = 0;
|
Displacement displ = 0;
|
||||||
|
|
||||||
if (!arg.empty()) newEnv.vars.emplace_back(arg, displ++);
|
if (arg) newEnv.vars.emplace_back(arg, displ++);
|
||||||
|
|
||||||
if (hasFormals()) {
|
if (hasFormals()) {
|
||||||
for (auto & i : formals->formals)
|
for (auto & i : formals->formals)
|
||||||
|
@ -377,20 +420,20 @@ void ExprLambda::bindVars(const StaticEnv & env)
|
||||||
newEnv.sort();
|
newEnv.sort();
|
||||||
|
|
||||||
for (auto & i : formals->formals)
|
for (auto & i : formals->formals)
|
||||||
if (i.def) i.def->bindVars(newEnv);
|
if (i.def) i.def->bindVars(es, newEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
body->bindVars(newEnv);
|
body->bindVars(es, newEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprCall::bindVars(const StaticEnv & env)
|
void ExprCall::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
fun->bindVars(env);
|
fun->bindVars(es, env);
|
||||||
for (auto e : args)
|
for (auto e : args)
|
||||||
e->bindVars(env);
|
e->bindVars(es, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprLet::bindVars(const StaticEnv & env)
|
void ExprLet::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
StaticEnv newEnv(false, &env, attrs->attrs.size());
|
StaticEnv newEnv(false, &env, attrs->attrs.size());
|
||||||
|
|
||||||
|
@ -401,12 +444,12 @@ void ExprLet::bindVars(const StaticEnv & env)
|
||||||
// No need to sort newEnv since attrs->attrs is in sorted order.
|
// No need to sort newEnv since attrs->attrs is in sorted order.
|
||||||
|
|
||||||
for (auto & i : attrs->attrs)
|
for (auto & i : attrs->attrs)
|
||||||
i.second.e->bindVars(i.second.inherited ? env : newEnv);
|
i.second.e->bindVars(es, i.second.inherited ? env : newEnv);
|
||||||
|
|
||||||
body->bindVars(newEnv);
|
body->bindVars(es, newEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprWith::bindVars(const StaticEnv & env)
|
void ExprWith::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
/* Does this `with' have an enclosing `with'? If so, record its
|
/* Does this `with' have an enclosing `with'? If so, record its
|
||||||
level so that `lookupVar' can look up variables in the previous
|
level so that `lookupVar' can look up variables in the previous
|
||||||
|
@ -420,57 +463,60 @@ void ExprWith::bindVars(const StaticEnv & env)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
attrs->bindVars(env);
|
attrs->bindVars(es, env);
|
||||||
StaticEnv newEnv(true, &env);
|
StaticEnv newEnv(true, &env);
|
||||||
body->bindVars(newEnv);
|
body->bindVars(es, newEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprIf::bindVars(const StaticEnv & env)
|
void ExprIf::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
cond->bindVars(env);
|
cond->bindVars(es, env);
|
||||||
then->bindVars(env);
|
then->bindVars(es, env);
|
||||||
else_->bindVars(env);
|
else_->bindVars(es, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprAssert::bindVars(const StaticEnv & env)
|
void ExprAssert::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
cond->bindVars(env);
|
cond->bindVars(es, env);
|
||||||
body->bindVars(env);
|
body->bindVars(es, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprOpNot::bindVars(const StaticEnv & env)
|
void ExprOpNot::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
e->bindVars(env);
|
e->bindVars(es, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprConcatStrings::bindVars(const StaticEnv & env)
|
void ExprConcatStrings::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
for (auto & i : *es)
|
for (auto & i : *this->es)
|
||||||
i.second->bindVars(env);
|
i.second->bindVars(es, env);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprPos::bindVars(const StaticEnv & env)
|
void ExprPos::bindVars(const EvalState & es, const StaticEnv & env)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Storing function names. */
|
/* Storing function names. */
|
||||||
|
|
||||||
void Expr::setName(Symbol & name)
|
void Expr::setName(SymbolIdx name)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void ExprLambda::setName(Symbol & name)
|
void ExprLambda::setName(SymbolIdx name)
|
||||||
{
|
{
|
||||||
this->name = name;
|
this->name = name;
|
||||||
body->setName(name);
|
body->setName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::string ExprLambda::showNamePos() const
|
std::string ExprLambda::showNamePos(const EvalState & state) const
|
||||||
{
|
{
|
||||||
return fmt("%1% at %2%", name.set() ? "'" + (std::string) name + "'" : "anonymous function", pos);
|
std::string id(name
|
||||||
|
? concatStrings("'", state.symbols[name], "'")
|
||||||
|
: "anonymous function");
|
||||||
|
return fmt("%1% at %2%", id, state.positions[pos]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -480,8 +526,7 @@ std::string ExprLambda::showNamePos() const
|
||||||
size_t SymbolTable::totalSize() const
|
size_t SymbolTable::totalSize() const
|
||||||
{
|
{
|
||||||
size_t n = 0;
|
size_t n = 0;
|
||||||
for (auto & i : store)
|
dump([&] (const Symbol & s) { n += std::string_view(s).size(); });
|
||||||
n += i.size();
|
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#include "value.hh"
|
#include "value.hh"
|
||||||
#include "symbol-table.hh"
|
#include "symbol-table.hh"
|
||||||
#include "error.hh"
|
#include "error.hh"
|
||||||
|
#include "chunked-vector.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -23,33 +27,92 @@ MakeError(RestrictedPathError, Error);
|
||||||
|
|
||||||
struct Pos
|
struct Pos
|
||||||
{
|
{
|
||||||
|
std::string file;
|
||||||
FileOrigin origin;
|
FileOrigin origin;
|
||||||
Symbol file;
|
uint32_t line;
|
||||||
unsigned int line, column;
|
uint32_t column;
|
||||||
|
|
||||||
Pos() : origin(foString), line(0), column(0) { }
|
explicit operator bool() const { return line > 0; }
|
||||||
Pos(FileOrigin origin, const Symbol & file, unsigned int line, unsigned int column)
|
};
|
||||||
: origin(origin), file(file), line(line), column(column) { }
|
|
||||||
|
|
||||||
operator bool() const
|
class PosIdx {
|
||||||
|
friend class PosTable;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t id;
|
||||||
|
|
||||||
|
explicit PosIdx(uint32_t id): id(id) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
PosIdx() : id(0) {}
|
||||||
|
|
||||||
|
explicit operator bool() const { return id > 0; }
|
||||||
|
|
||||||
|
bool operator<(const PosIdx other) const { return id < other.id; }
|
||||||
|
};
|
||||||
|
|
||||||
|
class PosTable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
class Origin {
|
||||||
|
friend PosTable;
|
||||||
|
private:
|
||||||
|
// must always be invalid by default, add() replaces this with the actual value.
|
||||||
|
// subsequent add() calls use this index as a token to quickly check whether the
|
||||||
|
// current origins.back() can be reused or not.
|
||||||
|
mutable uint32_t idx = std::numeric_limits<uint32_t>::max();
|
||||||
|
|
||||||
|
explicit Origin(uint32_t idx): idx(idx), file{}, origin{} {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
const std::string file;
|
||||||
|
const FileOrigin origin;
|
||||||
|
|
||||||
|
Origin(std::string file, FileOrigin origin): file(std::move(file)), origin(origin) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Offset {
|
||||||
|
uint32_t line, column;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<Origin> origins;
|
||||||
|
ChunkedVector<Offset, 8192> offsets;
|
||||||
|
|
||||||
|
public:
|
||||||
|
PosTable(): offsets(1024)
|
||||||
{
|
{
|
||||||
return line != 0;
|
origins.reserve(1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator < (const Pos & p2) const
|
PosIdx add(const Origin & origin, uint32_t line, uint32_t column)
|
||||||
{
|
{
|
||||||
if (!line) return p2.line;
|
const auto idx = offsets.add({line, column}).second;
|
||||||
if (!p2.line) return false;
|
if (origins.empty() || origins.back().idx != origin.idx) {
|
||||||
int d = ((const std::string &) file).compare((const std::string &) p2.file);
|
origin.idx = idx;
|
||||||
if (d < 0) return true;
|
origins.push_back(origin);
|
||||||
if (d > 0) return false;
|
}
|
||||||
if (line < p2.line) return true;
|
return PosIdx(idx + 1);
|
||||||
if (line > p2.line) return false;
|
}
|
||||||
return column < p2.column;
|
|
||||||
|
Pos operator[](PosIdx p) const
|
||||||
|
{
|
||||||
|
if (p.id == 0 || p.id > offsets.size())
|
||||||
|
return {};
|
||||||
|
const auto idx = p.id - 1;
|
||||||
|
/* we want the last key <= idx, so we'll take prev(first key > idx).
|
||||||
|
this is guaranteed to never rewind origin.begin because the first
|
||||||
|
key is always 0. */
|
||||||
|
const auto pastOrigin = std::upper_bound(
|
||||||
|
origins.begin(), origins.end(), Origin(idx),
|
||||||
|
[] (const auto & a, const auto & b) { return a.idx < b.idx; });
|
||||||
|
const auto origin = *std::prev(pastOrigin);
|
||||||
|
const auto offset = offsets[idx];
|
||||||
|
return {origin.file, origin.origin, offset.line, offset.column};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
extern Pos noPos;
|
inline PosIdx noPos = {};
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const Pos & pos);
|
std::ostream & operator << (std::ostream & str, const Pos & pos);
|
||||||
|
|
||||||
|
@ -63,15 +126,15 @@ struct StaticEnv;
|
||||||
/* An attribute path is a sequence of attribute names. */
|
/* An attribute path is a sequence of attribute names. */
|
||||||
struct AttrName
|
struct AttrName
|
||||||
{
|
{
|
||||||
Symbol symbol;
|
SymbolIdx symbol;
|
||||||
Expr * expr;
|
Expr * expr;
|
||||||
AttrName(const Symbol & s) : symbol(s) {};
|
AttrName(const SymbolIdx & s) : symbol(s) {};
|
||||||
AttrName(Expr * e) : expr(e) {};
|
AttrName(Expr * e) : expr(e) {};
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::vector<AttrName> AttrPath;
|
typedef std::vector<AttrName> AttrPath;
|
||||||
|
|
||||||
std::string showAttrPath(const AttrPath & attrPath);
|
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath);
|
||||||
|
|
||||||
|
|
||||||
/* Abstract syntax of Nix expressions. */
|
/* Abstract syntax of Nix expressions. */
|
||||||
|
@ -79,19 +142,17 @@ std::string showAttrPath(const AttrPath & attrPath);
|
||||||
struct Expr
|
struct Expr
|
||||||
{
|
{
|
||||||
virtual ~Expr() { };
|
virtual ~Expr() { };
|
||||||
virtual void show(std::ostream & str) const;
|
virtual void show(const SymbolTable & symbols, std::ostream & str) const;
|
||||||
virtual void bindVars(const StaticEnv & env);
|
virtual void bindVars(const EvalState & es, const StaticEnv & env);
|
||||||
virtual void eval(EvalState & state, Env & env, Value & v);
|
virtual void eval(EvalState & state, Env & env, Value & v);
|
||||||
virtual Value * maybeThunk(EvalState & state, Env & env);
|
virtual Value * maybeThunk(EvalState & state, Env & env);
|
||||||
virtual void setName(Symbol & name);
|
virtual void setName(SymbolIdx name);
|
||||||
};
|
};
|
||||||
|
|
||||||
std::ostream & operator << (std::ostream & str, const Expr & e);
|
|
||||||
|
|
||||||
#define COMMON_METHODS \
|
#define COMMON_METHODS \
|
||||||
void show(std::ostream & str) const; \
|
void show(const SymbolTable & symbols, std::ostream & str) const; \
|
||||||
void eval(EvalState & state, Env & env, Value & v); \
|
void eval(EvalState & state, Env & env, Value & v); \
|
||||||
void bindVars(const StaticEnv & env);
|
void bindVars(const EvalState & es, const StaticEnv & env);
|
||||||
|
|
||||||
struct ExprInt : Expr
|
struct ExprInt : Expr
|
||||||
{
|
{
|
||||||
|
@ -134,8 +195,8 @@ typedef uint32_t Displacement;
|
||||||
|
|
||||||
struct ExprVar : Expr
|
struct ExprVar : Expr
|
||||||
{
|
{
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
Symbol name;
|
SymbolIdx name;
|
||||||
|
|
||||||
/* Whether the variable comes from an environment (e.g. a rec, let
|
/* Whether the variable comes from an environment (e.g. a rec, let
|
||||||
or function argument) or from a "with". */
|
or function argument) or from a "with". */
|
||||||
|
@ -150,19 +211,19 @@ struct ExprVar : Expr
|
||||||
Level level;
|
Level level;
|
||||||
Displacement displ;
|
Displacement displ;
|
||||||
|
|
||||||
ExprVar(const Symbol & name) : name(name) { };
|
ExprVar(const SymbolIdx & name) : name(name) { };
|
||||||
ExprVar(const Pos & pos, const Symbol & name) : pos(pos), name(name) { };
|
ExprVar(const PosIdx & pos, const SymbolIdx & name) : pos(pos), name(name) { };
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
Value * maybeThunk(EvalState & state, Env & env);
|
Value * maybeThunk(EvalState & state, Env & env);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ExprSelect : Expr
|
struct ExprSelect : Expr
|
||||||
{
|
{
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
Expr * e, * def;
|
Expr * e, * def;
|
||||||
AttrPath attrPath;
|
AttrPath attrPath;
|
||||||
ExprSelect(const Pos & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { };
|
ExprSelect(const PosIdx & pos, Expr * e, const AttrPath & attrPath, Expr * def) : pos(pos), e(e), def(def), attrPath(attrPath) { };
|
||||||
ExprSelect(const Pos & pos, Expr * e, const Symbol & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
|
ExprSelect(const PosIdx & pos, Expr * e, const SymbolIdx & name) : pos(pos), e(e), def(0) { attrPath.push_back(AttrName(name)); };
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -177,28 +238,28 @@ struct ExprOpHasAttr : Expr
|
||||||
struct ExprAttrs : Expr
|
struct ExprAttrs : Expr
|
||||||
{
|
{
|
||||||
bool recursive;
|
bool recursive;
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
struct AttrDef {
|
struct AttrDef {
|
||||||
bool inherited;
|
bool inherited;
|
||||||
Expr * e;
|
Expr * e;
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
Displacement displ; // displacement
|
Displacement displ; // displacement
|
||||||
AttrDef(Expr * e, const Pos & pos, bool inherited=false)
|
AttrDef(Expr * e, const PosIdx & pos, bool inherited=false)
|
||||||
: inherited(inherited), e(e), pos(pos) { };
|
: inherited(inherited), e(e), pos(pos) { };
|
||||||
AttrDef() { };
|
AttrDef() { };
|
||||||
};
|
};
|
||||||
typedef std::map<Symbol, AttrDef> AttrDefs;
|
typedef std::map<SymbolIdx, AttrDef> AttrDefs;
|
||||||
AttrDefs attrs;
|
AttrDefs attrs;
|
||||||
struct DynamicAttrDef {
|
struct DynamicAttrDef {
|
||||||
Expr * nameExpr, * valueExpr;
|
Expr * nameExpr, * valueExpr;
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const Pos & pos)
|
DynamicAttrDef(Expr * nameExpr, Expr * valueExpr, const PosIdx & pos)
|
||||||
: nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { };
|
: nameExpr(nameExpr), valueExpr(valueExpr), pos(pos) { };
|
||||||
};
|
};
|
||||||
typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
|
typedef std::vector<DynamicAttrDef> DynamicAttrDefs;
|
||||||
DynamicAttrDefs dynamicAttrs;
|
DynamicAttrDefs dynamicAttrs;
|
||||||
ExprAttrs(const Pos &pos) : recursive(false), pos(pos) { };
|
ExprAttrs(const PosIdx &pos) : recursive(false), pos(pos) { };
|
||||||
ExprAttrs() : recursive(false), pos(noPos) { };
|
ExprAttrs() : recursive(false) { };
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -211,10 +272,9 @@ struct ExprList : Expr
|
||||||
|
|
||||||
struct Formal
|
struct Formal
|
||||||
{
|
{
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
Symbol name;
|
SymbolIdx name;
|
||||||
Expr * def;
|
Expr * def;
|
||||||
Formal(const Pos & pos, const Symbol & name, Expr * def) : pos(pos), name(name), def(def) { };
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Formals
|
struct Formals
|
||||||
|
@ -223,18 +283,19 @@ struct Formals
|
||||||
Formals_ formals;
|
Formals_ formals;
|
||||||
bool ellipsis;
|
bool ellipsis;
|
||||||
|
|
||||||
bool has(Symbol arg) const {
|
bool has(SymbolIdx arg) const {
|
||||||
auto it = std::lower_bound(formals.begin(), formals.end(), arg,
|
auto it = std::lower_bound(formals.begin(), formals.end(), arg,
|
||||||
[] (const Formal & f, const Symbol & sym) { return f.name < sym; });
|
[] (const Formal & f, const SymbolIdx & sym) { return f.name < sym; });
|
||||||
return it != formals.end() && it->name == arg;
|
return it != formals.end() && it->name == arg;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Formal> lexicographicOrder() const
|
std::vector<Formal> lexicographicOrder(const SymbolTable & symbols) const
|
||||||
{
|
{
|
||||||
std::vector<Formal> result(formals.begin(), formals.end());
|
std::vector<Formal> result(formals.begin(), formals.end());
|
||||||
std::sort(result.begin(), result.end(),
|
std::sort(result.begin(), result.end(),
|
||||||
[] (const Formal & a, const Formal & b) {
|
[&] (const Formal & a, const Formal & b) {
|
||||||
return std::string_view(a.name) < std::string_view(b.name);
|
std::string_view sa = symbols[a.name], sb = symbols[b.name];
|
||||||
|
return sa < sb;
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -242,17 +303,21 @@ struct Formals
|
||||||
|
|
||||||
struct ExprLambda : Expr
|
struct ExprLambda : Expr
|
||||||
{
|
{
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
Symbol name;
|
SymbolIdx name;
|
||||||
Symbol arg;
|
SymbolIdx arg;
|
||||||
Formals * formals;
|
Formals * formals;
|
||||||
Expr * body;
|
Expr * body;
|
||||||
ExprLambda(const Pos & pos, const Symbol & arg, Formals * formals, Expr * body)
|
ExprLambda(PosIdx pos, SymbolIdx arg, Formals * formals, Expr * body)
|
||||||
: pos(pos), arg(arg), formals(formals), body(body)
|
: pos(pos), arg(arg), formals(formals), body(body)
|
||||||
{
|
{
|
||||||
};
|
};
|
||||||
void setName(Symbol & name);
|
ExprLambda(PosIdx pos, Formals * formals, Expr * body)
|
||||||
std::string showNamePos() const;
|
: pos(pos), formals(formals), body(body)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
void setName(SymbolIdx name);
|
||||||
|
std::string showNamePos(const EvalState & state) const;
|
||||||
inline bool hasFormals() const { return formals != nullptr; }
|
inline bool hasFormals() const { return formals != nullptr; }
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
@ -261,8 +326,8 @@ struct ExprCall : Expr
|
||||||
{
|
{
|
||||||
Expr * fun;
|
Expr * fun;
|
||||||
std::vector<Expr *> args;
|
std::vector<Expr *> args;
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
ExprCall(const Pos & pos, Expr * fun, std::vector<Expr *> && args)
|
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args)
|
||||||
: fun(fun), args(args), pos(pos)
|
: fun(fun), args(args), pos(pos)
|
||||||
{ }
|
{ }
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
|
@ -278,26 +343,26 @@ struct ExprLet : Expr
|
||||||
|
|
||||||
struct ExprWith : Expr
|
struct ExprWith : Expr
|
||||||
{
|
{
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
Expr * attrs, * body;
|
Expr * attrs, * body;
|
||||||
size_t prevWith;
|
size_t prevWith;
|
||||||
ExprWith(const Pos & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
|
ExprWith(const PosIdx & pos, Expr * attrs, Expr * body) : pos(pos), attrs(attrs), body(body) { };
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ExprIf : Expr
|
struct ExprIf : Expr
|
||||||
{
|
{
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
Expr * cond, * then, * else_;
|
Expr * cond, * then, * else_;
|
||||||
ExprIf(const Pos & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { };
|
ExprIf(const PosIdx & pos, Expr * cond, Expr * then, Expr * else_) : pos(pos), cond(cond), then(then), else_(else_) { };
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ExprAssert : Expr
|
struct ExprAssert : Expr
|
||||||
{
|
{
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
Expr * cond, * body;
|
Expr * cond, * body;
|
||||||
ExprAssert(const Pos & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { };
|
ExprAssert(const PosIdx & pos, Expr * cond, Expr * body) : pos(pos), cond(cond), body(body) { };
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -311,17 +376,17 @@ struct ExprOpNot : Expr
|
||||||
#define MakeBinOp(name, s) \
|
#define MakeBinOp(name, s) \
|
||||||
struct name : Expr \
|
struct name : Expr \
|
||||||
{ \
|
{ \
|
||||||
Pos pos; \
|
PosIdx pos; \
|
||||||
Expr * e1, * e2; \
|
Expr * e1, * e2; \
|
||||||
name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \
|
name(Expr * e1, Expr * e2) : e1(e1), e2(e2) { }; \
|
||||||
name(const Pos & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \
|
name(const PosIdx & pos, Expr * e1, Expr * e2) : pos(pos), e1(e1), e2(e2) { }; \
|
||||||
void show(std::ostream & str) const \
|
void show(const SymbolTable & symbols, std::ostream & str) const \
|
||||||
{ \
|
{ \
|
||||||
str << "(" << *e1 << " " s " " << *e2 << ")"; \
|
str << "("; e1->show(symbols, str); str << " " s " "; e2->show(symbols, str); str << ")"; \
|
||||||
} \
|
} \
|
||||||
void bindVars(const StaticEnv & env) \
|
void bindVars(const EvalState & es, const StaticEnv & env) \
|
||||||
{ \
|
{ \
|
||||||
e1->bindVars(env); e2->bindVars(env); \
|
e1->bindVars(es, env); e2->bindVars(es, env); \
|
||||||
} \
|
} \
|
||||||
void eval(EvalState & state, Env & env, Value & v); \
|
void eval(EvalState & state, Env & env, Value & v); \
|
||||||
};
|
};
|
||||||
|
@ -336,18 +401,18 @@ MakeBinOp(ExprOpConcatLists, "++")
|
||||||
|
|
||||||
struct ExprConcatStrings : Expr
|
struct ExprConcatStrings : Expr
|
||||||
{
|
{
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
bool forceString;
|
bool forceString;
|
||||||
std::vector<std::pair<Pos, Expr *> > * es;
|
std::vector<std::pair<PosIdx, Expr *> > * es;
|
||||||
ExprConcatStrings(const Pos & pos, bool forceString, std::vector<std::pair<Pos, Expr *> > * es)
|
ExprConcatStrings(const PosIdx & pos, bool forceString, std::vector<std::pair<PosIdx, Expr *> > * es)
|
||||||
: pos(pos), forceString(forceString), es(es) { };
|
: pos(pos), forceString(forceString), es(es) { };
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ExprPos : Expr
|
struct ExprPos : Expr
|
||||||
{
|
{
|
||||||
Pos pos;
|
PosIdx pos;
|
||||||
ExprPos(const Pos & pos) : pos(pos) { };
|
ExprPos(const PosIdx & pos) : pos(pos) { };
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -361,7 +426,7 @@ struct StaticEnv
|
||||||
const StaticEnv * up;
|
const StaticEnv * up;
|
||||||
|
|
||||||
// Note: these must be in sorted order.
|
// Note: these must be in sorted order.
|
||||||
typedef std::vector<std::pair<Symbol, Displacement>> Vars;
|
typedef std::vector<std::pair<SymbolIdx, Displacement>> Vars;
|
||||||
Vars vars;
|
Vars vars;
|
||||||
|
|
||||||
StaticEnv(bool isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) {
|
StaticEnv(bool isWith, const StaticEnv * up, size_t expectedSize = 0) : isWith(isWith), up(up) {
|
||||||
|
@ -385,7 +450,7 @@ struct StaticEnv
|
||||||
vars.erase(it, end);
|
vars.erase(it, end);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vars::const_iterator find(const Symbol & name) const
|
Vars::const_iterator find(const SymbolIdx & name) const
|
||||||
{
|
{
|
||||||
Vars::value_type key(name, 0);
|
Vars::value_type key(name, 0);
|
||||||
auto i = std::lower_bound(vars.begin(), vars.end(), key);
|
auto i = std::lower_bound(vars.begin(), vars.end(), key);
|
||||||
|
|
|
@ -32,12 +32,12 @@ namespace nix {
|
||||||
SymbolTable & symbols;
|
SymbolTable & symbols;
|
||||||
Expr * result;
|
Expr * result;
|
||||||
Path basePath;
|
Path basePath;
|
||||||
Symbol file;
|
PosTable::Origin origin;
|
||||||
FileOrigin origin;
|
|
||||||
std::optional<ErrorInfo> error;
|
std::optional<ErrorInfo> error;
|
||||||
ParseData(EvalState & state)
|
ParseData(EvalState & state, PosTable::Origin origin)
|
||||||
: state(state)
|
: state(state)
|
||||||
, symbols(state.symbols)
|
, symbols(state.symbols)
|
||||||
|
, origin(std::move(origin))
|
||||||
{ };
|
{ };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -77,26 +77,26 @@ using namespace nix;
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
static void dupAttr(const AttrPath & attrPath, const Pos & pos, const Pos & prevPos)
|
static void dupAttr(const EvalState & state, const AttrPath & attrPath, const PosIdx pos, const PosIdx prevPos)
|
||||||
{
|
{
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("attribute '%1%' already defined at %2%",
|
.msg = hintfmt("attribute '%1%' already defined at %2%",
|
||||||
showAttrPath(attrPath), prevPos),
|
showAttrPath(state.symbols, attrPath), state.positions[prevPos]),
|
||||||
.errPos = pos
|
.errPos = state.positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static void dupAttr(Symbol attr, const Pos & pos, const Pos & prevPos)
|
static void dupAttr(const EvalState & state, SymbolIdx attr, const PosIdx pos, const PosIdx prevPos)
|
||||||
{
|
{
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("attribute '%1%' already defined at %2%", attr, prevPos),
|
.msg = hintfmt("attribute '%1%' already defined at %2%", state.symbols[attr], state.positions[prevPos]),
|
||||||
.errPos = pos
|
.errPos = state.positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
|
static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
|
||||||
Expr * e, const Pos & pos)
|
Expr * e, const PosIdx pos, const nix::EvalState & state)
|
||||||
{
|
{
|
||||||
AttrPath::iterator i;
|
AttrPath::iterator i;
|
||||||
// All attrpaths have at least one attr
|
// All attrpaths have at least one attr
|
||||||
|
@ -104,15 +104,15 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
|
||||||
// Checking attrPath validity.
|
// Checking attrPath validity.
|
||||||
// ===========================
|
// ===========================
|
||||||
for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
|
for (i = attrPath.begin(); i + 1 < attrPath.end(); i++) {
|
||||||
if (i->symbol.set()) {
|
if (i->symbol) {
|
||||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
|
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
|
||||||
if (j != attrs->attrs.end()) {
|
if (j != attrs->attrs.end()) {
|
||||||
if (!j->second.inherited) {
|
if (!j->second.inherited) {
|
||||||
ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e);
|
ExprAttrs * attrs2 = dynamic_cast<ExprAttrs *>(j->second.e);
|
||||||
if (!attrs2) dupAttr(attrPath, pos, j->second.pos);
|
if (!attrs2) dupAttr(state, attrPath, pos, j->second.pos);
|
||||||
attrs = attrs2;
|
attrs = attrs2;
|
||||||
} else
|
} else
|
||||||
dupAttr(attrPath, pos, j->second.pos);
|
dupAttr(state, attrPath, pos, j->second.pos);
|
||||||
} else {
|
} else {
|
||||||
ExprAttrs * nested = new ExprAttrs;
|
ExprAttrs * nested = new ExprAttrs;
|
||||||
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos);
|
attrs->attrs[i->symbol] = ExprAttrs::AttrDef(nested, pos);
|
||||||
|
@ -126,7 +126,7 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
|
||||||
}
|
}
|
||||||
// Expr insertion.
|
// Expr insertion.
|
||||||
// ==========================
|
// ==========================
|
||||||
if (i->symbol.set()) {
|
if (i->symbol) {
|
||||||
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
|
ExprAttrs::AttrDefs::iterator j = attrs->attrs.find(i->symbol);
|
||||||
if (j != attrs->attrs.end()) {
|
if (j != attrs->attrs.end()) {
|
||||||
// This attr path is already defined. However, if both
|
// This attr path is already defined. However, if both
|
||||||
|
@ -139,11 +139,11 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
|
||||||
for (auto & ad : ae->attrs) {
|
for (auto & ad : ae->attrs) {
|
||||||
auto j2 = jAttrs->attrs.find(ad.first);
|
auto j2 = jAttrs->attrs.find(ad.first);
|
||||||
if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error.
|
if (j2 != jAttrs->attrs.end()) // Attr already defined in iAttrs, error.
|
||||||
dupAttr(ad.first, j2->second.pos, ad.second.pos);
|
dupAttr(state, ad.first, j2->second.pos, ad.second.pos);
|
||||||
jAttrs->attrs.emplace(ad.first, ad.second);
|
jAttrs->attrs.emplace(ad.first, ad.second);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dupAttr(attrPath, pos, j->second.pos);
|
dupAttr(state, attrPath, pos, j->second.pos);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// This attr path is not defined. Let's create it.
|
// This attr path is not defined. Let's create it.
|
||||||
|
@ -157,14 +157,14 @@ static void addAttr(ExprAttrs * attrs, AttrPath & attrPath,
|
||||||
|
|
||||||
|
|
||||||
static Formals * toFormals(ParseData & data, ParserFormals * formals,
|
static Formals * toFormals(ParseData & data, ParserFormals * formals,
|
||||||
Pos pos = noPos, Symbol arg = {})
|
PosIdx pos = noPos, SymbolIdx arg = {})
|
||||||
{
|
{
|
||||||
std::sort(formals->formals.begin(), formals->formals.end(),
|
std::sort(formals->formals.begin(), formals->formals.end(),
|
||||||
[] (const auto & a, const auto & b) {
|
[] (const auto & a, const auto & b) {
|
||||||
return std::tie(a.name, a.pos) < std::tie(b.name, b.pos);
|
return std::tie(a.name, a.pos) < std::tie(b.name, b.pos);
|
||||||
});
|
});
|
||||||
|
|
||||||
std::optional<std::pair<Symbol, Pos>> duplicate;
|
std::optional<std::pair<SymbolIdx, PosIdx>> duplicate;
|
||||||
for (size_t i = 0; i + 1 < formals->formals.size(); i++) {
|
for (size_t i = 0; i + 1 < formals->formals.size(); i++) {
|
||||||
if (formals->formals[i].name != formals->formals[i + 1].name)
|
if (formals->formals[i].name != formals->formals[i + 1].name)
|
||||||
continue;
|
continue;
|
||||||
|
@ -173,18 +173,18 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
|
||||||
}
|
}
|
||||||
if (duplicate)
|
if (duplicate)
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("duplicate formal function argument '%1%'", duplicate->first),
|
.msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[duplicate->first]),
|
||||||
.errPos = duplicate->second
|
.errPos = data.state.positions[duplicate->second]
|
||||||
});
|
});
|
||||||
|
|
||||||
Formals result;
|
Formals result;
|
||||||
result.ellipsis = formals->ellipsis;
|
result.ellipsis = formals->ellipsis;
|
||||||
result.formals = std::move(formals->formals);
|
result.formals = std::move(formals->formals);
|
||||||
|
|
||||||
if (arg.set() && result.has(arg))
|
if (arg && result.has(arg))
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("duplicate formal function argument '%1%'", arg),
|
.msg = hintfmt("duplicate formal function argument '%1%'", data.symbols[arg]),
|
||||||
.errPos = pos
|
.errPos = data.state.positions[pos]
|
||||||
});
|
});
|
||||||
|
|
||||||
delete formals;
|
delete formals;
|
||||||
|
@ -192,8 +192,8 @@ static Formals * toFormals(ParseData & data, ParserFormals * formals,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
|
static Expr * stripIndentation(const PosIdx pos, SymbolTable & symbols,
|
||||||
std::vector<std::pair<Pos, std::variant<Expr *, StringToken> > > & es)
|
std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken> > > & es)
|
||||||
{
|
{
|
||||||
if (es.empty()) return new ExprString("");
|
if (es.empty()) return new ExprString("");
|
||||||
|
|
||||||
|
@ -233,7 +233,7 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Strip spaces from each line. */
|
/* Strip spaces from each line. */
|
||||||
std::vector<std::pair<Pos, Expr *> > * es2 = new std::vector<std::pair<Pos, Expr *> >;
|
auto * es2 = new std::vector<std::pair<PosIdx, Expr *> >;
|
||||||
atStartOfLine = true;
|
atStartOfLine = true;
|
||||||
size_t curDropped = 0;
|
size_t curDropped = 0;
|
||||||
size_t n = es.size();
|
size_t n = es.size();
|
||||||
|
@ -284,9 +284,9 @@ static Expr * stripIndentation(const Pos & pos, SymbolTable & symbols,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static inline Pos makeCurPos(const YYLTYPE & loc, ParseData * data)
|
static inline PosIdx makeCurPos(const YYLTYPE & loc, ParseData * data)
|
||||||
{
|
{
|
||||||
return Pos(data->origin, data->file, loc.first_line, loc.first_column);
|
return data->state.positions.add(data->origin, loc.first_line, loc.first_column);
|
||||||
}
|
}
|
||||||
|
|
||||||
#define CUR_POS makeCurPos(*yylocp, data)
|
#define CUR_POS makeCurPos(*yylocp, data)
|
||||||
|
@ -299,7 +299,7 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
|
||||||
{
|
{
|
||||||
data->error = {
|
data->error = {
|
||||||
.msg = hintfmt(error),
|
.msg = hintfmt(error),
|
||||||
.errPos = makeCurPos(*loc, data)
|
.errPos = data->state.positions[makeCurPos(*loc, data)]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -320,8 +320,8 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
|
||||||
StringToken uri;
|
StringToken uri;
|
||||||
StringToken str;
|
StringToken str;
|
||||||
std::vector<nix::AttrName> * attrNames;
|
std::vector<nix::AttrName> * attrNames;
|
||||||
std::vector<std::pair<nix::Pos, nix::Expr *> > * string_parts;
|
std::vector<std::pair<nix::PosIdx, nix::Expr *> > * string_parts;
|
||||||
std::vector<std::pair<nix::Pos, std::variant<nix::Expr *, StringToken> > > * ind_string_parts;
|
std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, StringToken> > > * ind_string_parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
%type <e> start expr expr_function expr_if expr_op
|
%type <e> start expr expr_function expr_if expr_op
|
||||||
|
@ -369,15 +369,15 @@ expr_function
|
||||||
: ID ':' expr_function
|
: ID ':' expr_function
|
||||||
{ $$ = new ExprLambda(CUR_POS, data->symbols.create($1), 0, $3); }
|
{ $$ = new ExprLambda(CUR_POS, data->symbols.create($1), 0, $3); }
|
||||||
| '{' formals '}' ':' expr_function
|
| '{' formals '}' ':' expr_function
|
||||||
{ $$ = new ExprLambda(CUR_POS, data->symbols.create(""), toFormals(*data, $2), $5); }
|
{ $$ = new ExprLambda(CUR_POS, toFormals(*data, $2), $5); }
|
||||||
| '{' formals '}' '@' ID ':' expr_function
|
| '{' formals '}' '@' ID ':' expr_function
|
||||||
{
|
{
|
||||||
Symbol arg = data->symbols.create($5);
|
auto arg = data->symbols.create($5);
|
||||||
$$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $2, CUR_POS, arg), $7);
|
$$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $2, CUR_POS, arg), $7);
|
||||||
}
|
}
|
||||||
| ID '@' '{' formals '}' ':' expr_function
|
| ID '@' '{' formals '}' ':' expr_function
|
||||||
{
|
{
|
||||||
Symbol arg = data->symbols.create($1);
|
auto arg = data->symbols.create($1);
|
||||||
$$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $4, CUR_POS, arg), $7);
|
$$ = new ExprLambda(CUR_POS, arg, toFormals(*data, $4, CUR_POS, arg), $7);
|
||||||
}
|
}
|
||||||
| ASSERT expr ';' expr_function
|
| ASSERT expr ';' expr_function
|
||||||
|
@ -388,7 +388,7 @@ expr_function
|
||||||
{ if (!$2->dynamicAttrs.empty())
|
{ if (!$2->dynamicAttrs.empty())
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("dynamic attributes not allowed in let"),
|
.msg = hintfmt("dynamic attributes not allowed in let"),
|
||||||
.errPos = CUR_POS
|
.errPos = data->state.positions[CUR_POS]
|
||||||
});
|
});
|
||||||
$$ = new ExprLet($2, $4);
|
$$ = new ExprLet($2, $4);
|
||||||
}
|
}
|
||||||
|
@ -415,7 +415,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 std::vector<std::pair<Pos, Expr *> >({{makeCurPos(@1, data), $1}, {makeCurPos(@3, data), $3}})); }
|
{ $$ = new ExprConcatStrings(CUR_POS, false, new std::vector<std::pair<PosIdx, 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}); }
|
||||||
|
@ -477,7 +477,7 @@ expr_simple
|
||||||
if (noURLLiterals)
|
if (noURLLiterals)
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("URL literals are disabled"),
|
.msg = hintfmt("URL literals are disabled"),
|
||||||
.errPos = CUR_POS
|
.errPos = data->state.positions[CUR_POS]
|
||||||
});
|
});
|
||||||
$$ = new ExprString(std::string($1));
|
$$ = new ExprString(std::string($1));
|
||||||
}
|
}
|
||||||
|
@ -503,9 +503,9 @@ string_parts_interpolated
|
||||||
: string_parts_interpolated STR
|
: string_parts_interpolated STR
|
||||||
{ $$ = $1; $1->emplace_back(makeCurPos(@2, data), new ExprString(std::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 std::vector<std::pair<Pos, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); }
|
| DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *> >; $$->emplace_back(makeCurPos(@1, data), $2); }
|
||||||
| STR DOLLAR_CURLY expr '}' {
|
| STR DOLLAR_CURLY expr '}' {
|
||||||
$$ = new std::vector<std::pair<Pos, Expr *> >;
|
$$ = new std::vector<std::pair<PosIdx, Expr *> >;
|
||||||
$$->emplace_back(makeCurPos(@1, data), new ExprString(std::string($1)));
|
$$->emplace_back(makeCurPos(@1, data), new ExprString(std::string($1)));
|
||||||
$$->emplace_back(makeCurPos(@2, data), $3);
|
$$->emplace_back(makeCurPos(@2, data), $3);
|
||||||
}
|
}
|
||||||
|
@ -528,17 +528,17 @@ 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 std::vector<std::pair<Pos, std::variant<Expr *, StringToken> > >; }
|
| { $$ = new std::vector<std::pair<PosIdx, std::variant<Expr *, StringToken> > >; }
|
||||||
;
|
;
|
||||||
|
|
||||||
binds
|
binds
|
||||||
: binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data)); }
|
: binds attrpath '=' expr ';' { $$ = $1; addAttr($$, *$2, $4, makeCurPos(@2, data), data->state); }
|
||||||
| binds INHERIT attrs ';'
|
| binds INHERIT attrs ';'
|
||||||
{ $$ = $1;
|
{ $$ = $1;
|
||||||
for (auto & i : *$3) {
|
for (auto & i : *$3) {
|
||||||
if ($$->attrs.find(i.symbol) != $$->attrs.end())
|
if ($$->attrs.find(i.symbol) != $$->attrs.end())
|
||||||
dupAttr(i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos);
|
dupAttr(data->state, i.symbol, makeCurPos(@3, data), $$->attrs[i.symbol].pos);
|
||||||
Pos pos = makeCurPos(@3, data);
|
auto pos = makeCurPos(@3, data);
|
||||||
$$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true));
|
$$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprVar(CUR_POS, i.symbol), pos, true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -547,7 +547,7 @@ binds
|
||||||
/* !!! Should ensure sharing of the expression in $4. */
|
/* !!! Should ensure sharing of the expression in $4. */
|
||||||
for (auto & i : *$6) {
|
for (auto & i : *$6) {
|
||||||
if ($$->attrs.find(i.symbol) != $$->attrs.end())
|
if ($$->attrs.find(i.symbol) != $$->attrs.end())
|
||||||
dupAttr(i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos);
|
dupAttr(data->state, i.symbol, makeCurPos(@6, data), $$->attrs[i.symbol].pos);
|
||||||
$$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data)));
|
$$->attrs.emplace(i.symbol, ExprAttrs::AttrDef(new ExprSelect(CUR_POS, $4, i.symbol), makeCurPos(@6, data)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -565,7 +565,7 @@ attrs
|
||||||
} else
|
} else
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = hintfmt("dynamic attributes not allowed in inherit"),
|
.msg = hintfmt("dynamic attributes not allowed in inherit"),
|
||||||
.errPos = makeCurPos(@2, data)
|
.errPos = data->state.positions[makeCurPos(@2, data)]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
| { $$ = new AttrPath; }
|
| { $$ = new AttrPath; }
|
||||||
|
@ -621,8 +621,8 @@ formals
|
||||||
;
|
;
|
||||||
|
|
||||||
formal
|
formal
|
||||||
: ID { $$ = new Formal(CUR_POS, data->symbols.create($1), 0); }
|
: ID { $$ = new Formal{CUR_POS, data->symbols.create($1), 0}; }
|
||||||
| ID '?' expr { $$ = new Formal(CUR_POS, data->symbols.create($1), $3); }
|
| ID '?' expr { $$ = new Formal{CUR_POS, data->symbols.create($1), $3}; }
|
||||||
;
|
;
|
||||||
|
|
||||||
%%
|
%%
|
||||||
|
@ -646,19 +646,19 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
|
||||||
const PathView path, const PathView basePath, StaticEnv & staticEnv)
|
const PathView path, const PathView basePath, StaticEnv & staticEnv)
|
||||||
{
|
{
|
||||||
yyscan_t scanner;
|
yyscan_t scanner;
|
||||||
ParseData data(*this);
|
std::string file;
|
||||||
data.origin = origin;
|
|
||||||
switch (origin) {
|
switch (origin) {
|
||||||
case foFile:
|
case foFile:
|
||||||
data.file = data.symbols.create(path);
|
file = path;
|
||||||
break;
|
break;
|
||||||
case foStdin:
|
case foStdin:
|
||||||
case foString:
|
case foString:
|
||||||
data.file = data.symbols.create(text);
|
file = text;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert(false);
|
assert(false);
|
||||||
}
|
}
|
||||||
|
ParseData data(*this, {file, origin});
|
||||||
data.basePath = basePath;
|
data.basePath = basePath;
|
||||||
|
|
||||||
yylex_init(&scanner);
|
yylex_init(&scanner);
|
||||||
|
@ -668,7 +668,7 @@ Expr * EvalState::parse(char * text, size_t length, FileOrigin origin,
|
||||||
|
|
||||||
if (res) throw ParseError(data.error.value());
|
if (res) throw ParseError(data.error.value());
|
||||||
|
|
||||||
data.result->bindVars(staticEnv);
|
data.result->bindVars(*this, staticEnv);
|
||||||
|
|
||||||
return data.result;
|
return data.result;
|
||||||
}
|
}
|
||||||
|
@ -760,7 +760,7 @@ Path EvalState::findFile(const std::string_view path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const Pos & pos)
|
Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, const PosIdx pos)
|
||||||
{
|
{
|
||||||
for (auto & i : searchPath) {
|
for (auto & i : searchPath) {
|
||||||
std::string suffix;
|
std::string suffix;
|
||||||
|
@ -787,7 +787,7 @@ Path EvalState::findFile(SearchPath & searchPath, const std::string_view path, c
|
||||||
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
|
? "cannot look up '<%s>' in pure evaluation mode (use '--impure' to override)"
|
||||||
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
|
: "file '%s' was not found in the Nix search path (add it using $NIX_PATH or -I)",
|
||||||
path),
|
path),
|
||||||
.errPos = pos
|
.errPos = positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -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,10 +36,11 @@ 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 PosIdx pos, Value * * args, Value & v);
|
||||||
|
|
||||||
/* Execute a program and parse its output */
|
/* Execute a program and parse its output */
|
||||||
void prim_exec(EvalState & state, const Pos & pos, Value * * args, Value & v);
|
void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
#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 {
|
||||||
|
|
||||||
static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context);
|
auto s = state.coerceToString(pos, *args[0], context);
|
||||||
|
@ -14,7 +15,7 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const Pos & pos,
|
||||||
static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
|
static RegisterPrimOp primop_unsafeDiscardStringContext("__unsafeDiscardStringContext", 1, prim_unsafeDiscardStringContext);
|
||||||
|
|
||||||
|
|
||||||
static void prim_hasContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_hasContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
state.forceString(*args[0], context, pos);
|
state.forceString(*args[0], context, pos);
|
||||||
|
@ -30,7 +31,7 @@ static RegisterPrimOp primop_hasContext("__hasContext", 1, prim_hasContext);
|
||||||
source-only deployment). This primop marks the string context so
|
source-only deployment). This primop marks the string context so
|
||||||
that builtins.derivation adds the path to drv.inputSrcs rather than
|
that builtins.derivation adds the path to drv.inputSrcs rather than
|
||||||
drv.inputDrvs. */
|
drv.inputDrvs. */
|
||||||
static void prim_unsafeDiscardOutputDependency(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_unsafeDiscardOutputDependency(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto s = state.coerceToString(pos, *args[0], context);
|
auto s = state.coerceToString(pos, *args[0], context);
|
||||||
|
@ -64,7 +65,7 @@ static RegisterPrimOp primop_unsafeDiscardOutputDependency("__unsafeDiscardOutpu
|
||||||
Note that for a given path any combination of the above attributes
|
Note that for a given path any combination of the above attributes
|
||||||
may be present.
|
may be present.
|
||||||
*/
|
*/
|
||||||
static void prim_getContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_getContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
struct ContextInfo {
|
struct ContextInfo {
|
||||||
bool path = false;
|
bool path = false;
|
||||||
|
@ -82,8 +83,8 @@ static void prim_getContext(EvalState & state, const Pos & pos, Value * * args,
|
||||||
drv = std::string(p, 1);
|
drv = std::string(p, 1);
|
||||||
path = &drv;
|
path = &drv;
|
||||||
} else if (p.at(0) == '!') {
|
} else if (p.at(0) == '!') {
|
||||||
std::pair<std::string, std::string> ctx = decodeContext(p);
|
NixStringContextElem ctx = decodeContext(*state.store, p);
|
||||||
drv = ctx.first;
|
drv = state.store->printStorePath(ctx.first);
|
||||||
output = ctx.second;
|
output = ctx.second;
|
||||||
path = &drv;
|
path = &drv;
|
||||||
}
|
}
|
||||||
|
@ -133,7 +134,7 @@ static RegisterPrimOp primop_getContext("__getContext", 1, prim_getContext);
|
||||||
See the commentary above unsafeGetContext for details of the
|
See the commentary above unsafeGetContext for details of the
|
||||||
context representation.
|
context representation.
|
||||||
*/
|
*/
|
||||||
static void prim_appendContext(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_appendContext(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
PathSet context;
|
PathSet context;
|
||||||
auto orig = state.forceString(*args[0], context, pos);
|
auto orig = state.forceString(*args[0], context, pos);
|
||||||
|
@ -143,45 +144,46 @@ static void prim_appendContext(EvalState & state, const Pos & pos, Value * * arg
|
||||||
auto sPath = state.symbols.create("path");
|
auto sPath = state.symbols.create("path");
|
||||||
auto sAllOutputs = state.symbols.create("allOutputs");
|
auto sAllOutputs = state.symbols.create("allOutputs");
|
||||||
for (auto & i : *args[1]->attrs) {
|
for (auto & i : *args[1]->attrs) {
|
||||||
if (!state.store->isStorePath(i.name))
|
const auto & name = state.symbols[i.name];
|
||||||
|
if (!state.store->isStorePath(name))
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("Context key '%s' is not a store path", i.name),
|
.msg = hintfmt("Context key '%s' is not a store path", name),
|
||||||
.errPos = *i.pos
|
.errPos = state.positions[i.pos]
|
||||||
});
|
});
|
||||||
if (!settings.readOnlyMode)
|
if (!settings.readOnlyMode)
|
||||||
state.store->ensurePath(state.store->parseStorePath(i.name));
|
state.store->ensurePath(state.store->parseStorePath(name));
|
||||||
state.forceAttrs(*i.value, *i.pos);
|
state.forceAttrs(*i.value, i.pos);
|
||||||
auto iter = i.value->attrs->find(sPath);
|
auto iter = i.value->attrs->find(sPath);
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
if (state.forceBool(*iter->value, *iter->pos))
|
if (state.forceBool(*iter->value, iter->pos))
|
||||||
context.insert(i.name);
|
context.emplace(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
iter = i.value->attrs->find(sAllOutputs);
|
iter = i.value->attrs->find(sAllOutputs);
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
if (state.forceBool(*iter->value, *iter->pos)) {
|
if (state.forceBool(*iter->value, iter->pos)) {
|
||||||
if (!isDerivation(i.name)) {
|
if (!isDerivation(name)) {
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", i.name),
|
.msg = hintfmt("Tried to add all-outputs context of %s, which is not a derivation, to a string", name),
|
||||||
.errPos = *i.pos
|
.errPos = state.positions[i.pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
context.insert("=" + std::string(i.name));
|
context.insert(concatStrings("=", name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
iter = i.value->attrs->find(state.sOutputs);
|
iter = i.value->attrs->find(state.sOutputs);
|
||||||
if (iter != i.value->attrs->end()) {
|
if (iter != i.value->attrs->end()) {
|
||||||
state.forceList(*iter->value, *iter->pos);
|
state.forceList(*iter->value, iter->pos);
|
||||||
if (iter->value->listSize() && !isDerivation(i.name)) {
|
if (iter->value->listSize() && !isDerivation(name)) {
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", i.name),
|
.msg = hintfmt("Tried to add derivation output context of %s, which is not a derivation, to a string", name),
|
||||||
.errPos = *i.pos
|
.errPos = state.positions[i.pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (auto elem : iter->value->listItems()) {
|
for (auto elem : iter->value->listItems()) {
|
||||||
auto name = state.forceStringNoCtx(*elem, *iter->pos);
|
auto outputName = state.forceStringNoCtx(*elem, iter->pos);
|
||||||
context.insert(concatStrings("!", name, "!", i.name));
|
context.insert(concatStrings("!", outputName, "!", name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
163
src/libexpr/primops/fetchClosure.cc
Normal file
163
src/libexpr/primops/fetchClosure.cc
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
#include "primops.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
#include "make-content-addressed.hh"
|
||||||
|
#include "url.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
static void prim_fetchClosure(EvalState & state, const PosIdx 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) {
|
||||||
|
const auto & attrName = state.symbols[attr.name];
|
||||||
|
|
||||||
|
if (attrName == "fromPath") {
|
||||||
|
PathSet context;
|
||||||
|
fromPath = state.coerceToStorePath(attr.pos, *attr.value, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (attrName == "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 (attrName == "fromStore")
|
||||||
|
fromStoreUrl = state.forceStringNoCtx(*attr.value, attr.pos);
|
||||||
|
|
||||||
|
else
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("attribute '%s' isn't supported in call to 'fetchClosure'", attrName),
|
||||||
|
.errPos = state.positions[pos]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fromPath)
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromPath"),
|
||||||
|
.errPos = state.positions[pos]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!fromStoreUrl)
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("attribute '%s' is missing in call to 'fetchClosure'", "fromStore"),
|
||||||
|
.errPos = state.positions[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 = state.positions[pos]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!parsedURL.query.empty())
|
||||||
|
throw Error({
|
||||||
|
.msg = hintfmt("'fetchClosure' does not support URL query parameters (in '%s')", *fromStoreUrl),
|
||||||
|
.errPos = state.positions[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 = state.positions[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 = state.positions[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 = state.positions[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,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
|
@ -7,7 +7,7 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
std::string url;
|
std::string url;
|
||||||
std::optional<Hash> rev;
|
std::optional<Hash> rev;
|
||||||
|
@ -22,31 +22,31 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
|
||||||
state.forceAttrs(*args[0], pos);
|
state.forceAttrs(*args[0], pos);
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
for (auto & attr : *args[0]->attrs) {
|
||||||
std::string_view n(attr.name);
|
std::string_view n(state.symbols[attr.name]);
|
||||||
if (n == "url")
|
if (n == "url")
|
||||||
url = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned();
|
url = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned();
|
||||||
else if (n == "rev") {
|
else if (n == "rev") {
|
||||||
// Ugly: unlike fetchGit, here the "rev" attribute can
|
// Ugly: unlike fetchGit, here the "rev" attribute can
|
||||||
// be both a revision or a branch/tag name.
|
// be both a revision or a branch/tag name.
|
||||||
auto value = state.forceStringNoCtx(*attr.value, *attr.pos);
|
auto value = state.forceStringNoCtx(*attr.value, attr.pos);
|
||||||
if (std::regex_match(value.begin(), value.end(), revRegex))
|
if (std::regex_match(value.begin(), value.end(), revRegex))
|
||||||
rev = Hash::parseAny(value, htSHA1);
|
rev = Hash::parseAny(value, htSHA1);
|
||||||
else
|
else
|
||||||
ref = value;
|
ref = value;
|
||||||
}
|
}
|
||||||
else if (n == "name")
|
else if (n == "name")
|
||||||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
name = state.forceStringNoCtx(*attr.value, attr.pos);
|
||||||
else
|
else
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", attr.name),
|
.msg = hintfmt("unsupported argument '%s' to 'fetchMercurial'", state.symbols[attr.name]),
|
||||||
.errPos = *attr.pos
|
.errPos = state.positions[attr.pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (url.empty())
|
if (url.empty())
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("'url' argument required"),
|
.msg = hintfmt("'url' argument required"),
|
||||||
.errPos = pos
|
.errPos = state.positions[pos]
|
||||||
});
|
});
|
||||||
|
|
||||||
} else
|
} else
|
||||||
|
|
|
@ -90,7 +90,7 @@ struct FetchTreeParams {
|
||||||
|
|
||||||
static void fetchTree(
|
static void fetchTree(
|
||||||
EvalState & state,
|
EvalState & state,
|
||||||
const Pos & pos,
|
const PosIdx pos,
|
||||||
Value * * args,
|
Value * * args,
|
||||||
Value & v,
|
Value & v,
|
||||||
std::optional<std::string> type,
|
std::optional<std::string> type,
|
||||||
|
@ -110,43 +110,43 @@ static void fetchTree(
|
||||||
if (type)
|
if (type)
|
||||||
throw Error({
|
throw Error({
|
||||||
.msg = hintfmt("unexpected attribute 'type'"),
|
.msg = hintfmt("unexpected attribute 'type'"),
|
||||||
.errPos = pos
|
.errPos = state.positions[pos]
|
||||||
});
|
});
|
||||||
type = state.forceStringNoCtx(*aType->value, *aType->pos);
|
type = state.forceStringNoCtx(*aType->value, aType->pos);
|
||||||
} else if (!type)
|
} else if (!type)
|
||||||
throw Error({
|
throw Error({
|
||||||
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
.msg = hintfmt("attribute 'type' is missing in call to 'fetchTree'"),
|
||||||
.errPos = pos
|
.errPos = state.positions[pos]
|
||||||
});
|
});
|
||||||
|
|
||||||
attrs.emplace("type", type.value());
|
attrs.emplace("type", type.value());
|
||||||
|
|
||||||
for (auto & attr : *args[0]->attrs) {
|
for (auto & attr : *args[0]->attrs) {
|
||||||
if (attr.name == state.sType) continue;
|
if (attr.name == state.sType) continue;
|
||||||
state.forceValue(*attr.value, *attr.pos);
|
state.forceValue(*attr.value, attr.pos);
|
||||||
if (attr.value->type() == nPath || attr.value->type() == nString) {
|
if (attr.value->type() == nPath || attr.value->type() == nString) {
|
||||||
auto s = state.coerceToString(*attr.pos, *attr.value, context, false, false).toOwned();
|
auto s = state.coerceToString(attr.pos, *attr.value, context, false, false).toOwned();
|
||||||
attrs.emplace(attr.name,
|
attrs.emplace(state.symbols[attr.name],
|
||||||
attr.name == "url"
|
state.symbols[attr.name] == "url"
|
||||||
? type == "git"
|
? type == "git"
|
||||||
? fixURIForGit(s, state)
|
? fixURIForGit(s, state)
|
||||||
: fixURI(s, state)
|
: fixURI(s, state)
|
||||||
: s);
|
: s);
|
||||||
}
|
}
|
||||||
else if (attr.value->type() == nBool)
|
else if (attr.value->type() == nBool)
|
||||||
attrs.emplace(attr.name, Explicit<bool>{attr.value->boolean});
|
attrs.emplace(state.symbols[attr.name], Explicit<bool>{attr.value->boolean});
|
||||||
else if (attr.value->type() == nInt)
|
else if (attr.value->type() == nInt)
|
||||||
attrs.emplace(attr.name, uint64_t(attr.value->integer));
|
attrs.emplace(state.symbols[attr.name], uint64_t(attr.value->integer));
|
||||||
else
|
else
|
||||||
throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
|
throw TypeError("fetchTree argument '%s' is %s while a string, Boolean or integer is expected",
|
||||||
attr.name, showType(*attr.value));
|
state.symbols[attr.name], showType(*attr.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.allowNameArgument)
|
if (!params.allowNameArgument)
|
||||||
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
|
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
|
||||||
throw Error({
|
throw Error({
|
||||||
.msg = hintfmt("attribute 'name' isn’t supported in call to 'fetchTree'"),
|
.msg = hintfmt("attribute 'name' isn't supported in call to 'fetchTree'"),
|
||||||
.errPos = pos
|
.errPos = state.positions[pos]
|
||||||
});
|
});
|
||||||
|
|
||||||
input = fetchers::Input::fromAttrs(std::move(attrs));
|
input = fetchers::Input::fromAttrs(std::move(attrs));
|
||||||
|
@ -167,7 +167,7 @@ static void fetchTree(
|
||||||
input = lookupInRegistries(state.store, input).first;
|
input = lookupInRegistries(state.store, input).first;
|
||||||
|
|
||||||
if (evalSettings.pureEval && !input.isLocked())
|
if (evalSettings.pureEval && !input.isLocked())
|
||||||
throw Error("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", pos);
|
throw Error("in pure evaluation mode, 'fetchTree' requires a locked input, at %s", state.positions[pos]);
|
||||||
|
|
||||||
auto [tree, input2] = input.fetch(state.store);
|
auto [tree, input2] = input.fetch(state.store);
|
||||||
|
|
||||||
|
@ -176,7 +176,7 @@ static void fetchTree(
|
||||||
emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false);
|
emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_fetchTree(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
settings.requireExperimentalFeature(Xp::Flakes);
|
settings.requireExperimentalFeature(Xp::Flakes);
|
||||||
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
|
fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
|
||||||
|
@ -185,7 +185,7 @@ static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, V
|
||||||
// FIXME: document
|
// FIXME: document
|
||||||
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 PosIdx pos, Value * * args, Value & v,
|
||||||
const std::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;
|
||||||
|
@ -198,24 +198,24 @@ 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) {
|
||||||
std::string n(attr.name);
|
std::string_view n(state.symbols[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")
|
||||||
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, *attr.pos), htSHA256);
|
expectedHash = newHashAllowEmpty(state.forceStringNoCtx(*attr.value, attr.pos), htSHA256);
|
||||||
else if (n == "name")
|
else if (n == "name")
|
||||||
name = state.forceStringNoCtx(*attr.value, *attr.pos);
|
name = state.forceStringNoCtx(*attr.value, attr.pos);
|
||||||
else
|
else
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("unsupported argument '%s' to '%s'", attr.name, who),
|
.msg = hintfmt("unsupported argument '%s' to '%s'", n, who),
|
||||||
.errPos = *attr.pos
|
.errPos = state.positions[attr.pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!url)
|
if (!url)
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("'url' argument required"),
|
.msg = hintfmt("'url' argument required"),
|
||||||
.errPos = pos
|
.errPos = state.positions[pos]
|
||||||
});
|
});
|
||||||
} else
|
} else
|
||||||
url = state.forceStringNoCtx(*args[0], pos);
|
url = state.forceStringNoCtx(*args[0], pos);
|
||||||
|
@ -262,7 +262,7 @@ static void fetch(EvalState & state, const Pos & pos, Value * * args, Value & v,
|
||||||
state.allowAndSetStorePathString(storePath, v);
|
state.allowAndSetStorePathString(storePath, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void prim_fetchurl(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_fetchurl(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
fetch(state, pos, args, v, "fetchurl", false, "");
|
fetch(state, pos, args, v, "fetchurl", false, "");
|
||||||
}
|
}
|
||||||
|
@ -278,7 +278,7 @@ static RegisterPrimOp primop_fetchurl({
|
||||||
.fun = prim_fetchurl,
|
.fun = prim_fetchurl,
|
||||||
});
|
});
|
||||||
|
|
||||||
static void prim_fetchTarball(EvalState & state, const Pos & pos, Value * * args, Value & v)
|
static void prim_fetchTarball(EvalState & state, const PosIdx pos, Value * * args, Value & v)
|
||||||
{
|
{
|
||||||
fetch(state, pos, args, v, "fetchTarball", true, "source");
|
fetch(state, pos, args, v, "fetchTarball", true, "source");
|
||||||
}
|
}
|
||||||
|
@ -329,7 +329,7 @@ static RegisterPrimOp primop_fetchTarball({
|
||||||
.fun = prim_fetchTarball,
|
.fun = prim_fetchTarball,
|
||||||
});
|
});
|
||||||
|
|
||||||
static void prim_fetchGit(EvalState &state, const Pos &pos, Value **args, Value &v)
|
static void prim_fetchGit(EvalState & state, const PosIdx 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 });
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Value & val)
|
static void prim_fromTOML(EvalState & state, const PosIdx pos, Value * * args, Value & val)
|
||||||
{
|
{
|
||||||
auto toml = state.forceStringNoCtx(*args[0], pos);
|
auto toml = state.forceStringNoCtx(*args[0], pos);
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ static void prim_fromTOML(EvalState & state, const Pos & pos, Value * * args, Va
|
||||||
} catch (std::exception & e) { // TODO: toml::syntax_error
|
} catch (std::exception & e) { // TODO: toml::syntax_error
|
||||||
throw EvalError({
|
throw EvalError({
|
||||||
.msg = hintfmt("while parsing a TOML string: %s", e.what()),
|
.msg = hintfmt("while parsing a TOML string: %s", e.what()),
|
||||||
.errPos = pos
|
.errPos = state.positions[pos]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
|
#include "chunked-vector.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -16,90 +17,90 @@ namespace nix {
|
||||||
|
|
||||||
class Symbol
|
class Symbol
|
||||||
{
|
{
|
||||||
private:
|
|
||||||
const std::string * s; // pointer into SymbolTable
|
|
||||||
Symbol(const std::string * s) : s(s) { };
|
|
||||||
friend class SymbolTable;
|
friend class SymbolTable;
|
||||||
|
private:
|
||||||
|
std::string s;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Symbol() : s(0) { };
|
Symbol(std::string_view s) : s(s) { }
|
||||||
|
|
||||||
bool operator == (const Symbol & s2) const
|
|
||||||
{
|
|
||||||
return s == s2.s;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: remove
|
// FIXME: remove
|
||||||
bool operator == (std::string_view s2) const
|
bool operator == (std::string_view s2) const
|
||||||
{
|
{
|
||||||
return s->compare(s2) == 0;
|
return s == s2;
|
||||||
}
|
|
||||||
|
|
||||||
bool operator != (const Symbol & s2) const
|
|
||||||
{
|
|
||||||
return s != s2.s;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator < (const Symbol & s2) const
|
|
||||||
{
|
|
||||||
return s < s2.s;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
operator const std::string & () const
|
operator const std::string & () const
|
||||||
{
|
{
|
||||||
return *s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
operator const std::string_view () const
|
operator const std::string_view () const
|
||||||
{
|
|
||||||
return *s;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool set() const
|
|
||||||
{
|
{
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool empty() const
|
|
||||||
{
|
|
||||||
return s->empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
friend std::ostream & operator << (std::ostream & str, const Symbol & sym);
|
friend std::ostream & operator << (std::ostream & str, const Symbol & sym);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class SymbolIdx
|
||||||
|
{
|
||||||
|
friend class SymbolTable;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint32_t id;
|
||||||
|
|
||||||
|
explicit SymbolIdx(uint32_t id): id(id) {}
|
||||||
|
|
||||||
|
public:
|
||||||
|
SymbolIdx() : id(0) {}
|
||||||
|
|
||||||
|
explicit operator bool() const { return id > 0; }
|
||||||
|
|
||||||
|
bool operator<(const SymbolIdx other) const { return id < other.id; }
|
||||||
|
bool operator==(const SymbolIdx other) const { return id == other.id; }
|
||||||
|
bool operator!=(const SymbolIdx other) const { return id != other.id; }
|
||||||
|
};
|
||||||
|
|
||||||
class SymbolTable
|
class SymbolTable
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string_view, Symbol> symbols;
|
std::unordered_map<std::string_view, std::pair<const Symbol *, uint32_t>> symbols;
|
||||||
std::list<std::string> store;
|
ChunkedVector<Symbol, 8192> store{16};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Symbol create(std::string_view s)
|
SymbolIdx create(std::string_view s)
|
||||||
{
|
{
|
||||||
// Most symbols are looked up more than once, so we trade off insertion performance
|
// Most symbols are looked up more than once, so we trade off insertion performance
|
||||||
// for lookup performance.
|
// for lookup performance.
|
||||||
// TODO: could probably be done more efficiently with transparent Hash and Equals
|
// TODO: could probably be done more efficiently with transparent Hash and Equals
|
||||||
// on the original implementation using unordered_set
|
// on the original implementation using unordered_set
|
||||||
auto it = symbols.find(s);
|
auto it = symbols.find(s);
|
||||||
if (it != symbols.end()) return it->second;
|
if (it != symbols.end()) return SymbolIdx(it->second.second + 1);
|
||||||
|
|
||||||
auto & rawSym = store.emplace_back(s);
|
const auto & [rawSym, idx] = store.add(s);
|
||||||
return symbols.emplace(rawSym, Symbol(&rawSym)).first->second;
|
symbols.emplace(rawSym, std::make_pair(&rawSym, idx));
|
||||||
|
return SymbolIdx(idx + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Symbol & operator[](SymbolIdx s) const
|
||||||
|
{
|
||||||
|
if (s.id == 0 || s.id > store.size())
|
||||||
|
abort();
|
||||||
|
return store[s.id - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t size() const
|
size_t size() const
|
||||||
{
|
{
|
||||||
return symbols.size();
|
return store.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t totalSize() const;
|
size_t totalSize() const;
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void dump(T callback)
|
void dump(T callback) const
|
||||||
{
|
{
|
||||||
for (auto & s : store)
|
store.forEach(callback);
|
||||||
callback(s);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
void printValueAsJSON(EvalState & state, bool strict,
|
void printValueAsJSON(EvalState & state, bool strict,
|
||||||
Value & v, const Pos & pos, JSONPlaceholder & out, PathSet & context)
|
Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context)
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
|
@ -50,14 +50,14 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||||
auto obj(out.object());
|
auto obj(out.object());
|
||||||
StringSet names;
|
StringSet names;
|
||||||
for (auto & j : *v.attrs)
|
for (auto & j : *v.attrs)
|
||||||
names.insert(j.name);
|
names.emplace(state.symbols[j.name]);
|
||||||
for (auto & j : names) {
|
for (auto & j : names) {
|
||||||
Attr & a(*v.attrs->find(state.symbols.create(j)));
|
Attr & a(*v.attrs->find(state.symbols.create(j)));
|
||||||
auto placeholder(obj.placeholder(j));
|
auto placeholder(obj.placeholder(j));
|
||||||
printValueAsJSON(state, strict, *a.value, *a.pos, placeholder, context);
|
printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context);
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
printValueAsJSON(state, strict, *i->value, *i->pos, out, context);
|
printValueAsJSON(state, strict, *i->value, i->pos, out, context);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,14 +82,15 @@ void printValueAsJSON(EvalState & state, bool strict,
|
||||||
case nFunction:
|
case nFunction:
|
||||||
auto e = TypeError({
|
auto e = TypeError({
|
||||||
.msg = hintfmt("cannot convert %1% to JSON", showType(v)),
|
.msg = hintfmt("cannot convert %1% to JSON", showType(v)),
|
||||||
.errPos = v.determinePos(pos)
|
.errPos = state.positions[v.determinePos(pos)]
|
||||||
});
|
});
|
||||||
throw e.addTrace(pos, hintfmt("message for the trace"));
|
e.addTrace(state.positions[pos], hintfmt("message for the trace"));
|
||||||
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void printValueAsJSON(EvalState & state, bool strict,
|
void printValueAsJSON(EvalState & state, bool strict,
|
||||||
Value & v, const Pos & pos, std::ostream & str, PathSet & context)
|
Value & v, const PosIdx pos, std::ostream & str, PathSet & context)
|
||||||
{
|
{
|
||||||
JSONPlaceholder out(str);
|
JSONPlaceholder out(str);
|
||||||
printValueAsJSON(state, strict, v, pos, out, context);
|
printValueAsJSON(state, strict, v, pos, out, context);
|
||||||
|
|
|
@ -11,9 +11,9 @@ namespace nix {
|
||||||
class JSONPlaceholder;
|
class JSONPlaceholder;
|
||||||
|
|
||||||
void printValueAsJSON(EvalState & state, bool strict,
|
void printValueAsJSON(EvalState & state, bool strict,
|
||||||
Value & v, const Pos & pos, JSONPlaceholder & out, PathSet & context);
|
Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context);
|
||||||
|
|
||||||
void printValueAsJSON(EvalState & state, bool strict,
|
void printValueAsJSON(EvalState & state, bool strict,
|
||||||
Value & v, const Pos & pos, std::ostream & str, PathSet & context);
|
Value & v, const PosIdx pos, std::ostream & str, PathSet & context);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,10 +19,10 @@ static XMLAttrs singletonAttrs(const std::string & name, const std::string & val
|
||||||
|
|
||||||
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
||||||
const Pos & pos);
|
const PosIdx pos);
|
||||||
|
|
||||||
|
|
||||||
static void posToXML(XMLAttrs & xmlAttrs, const Pos & pos)
|
static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos)
|
||||||
{
|
{
|
||||||
xmlAttrs["path"] = pos.file;
|
xmlAttrs["path"] = pos.file;
|
||||||
xmlAttrs["line"] = (format("%1%") % pos.line).str();
|
xmlAttrs["line"] = (format("%1%") % pos.line).str();
|
||||||
|
@ -36,25 +36,25 @@ static void showAttrs(EvalState & state, bool strict, bool location,
|
||||||
StringSet names;
|
StringSet names;
|
||||||
|
|
||||||
for (auto & i : attrs)
|
for (auto & i : attrs)
|
||||||
names.insert(i.name);
|
names.emplace(state.symbols[i.name]);
|
||||||
|
|
||||||
for (auto & i : names) {
|
for (auto & i : names) {
|
||||||
Attr & a(*attrs.find(state.symbols.create(i)));
|
Attr & a(*attrs.find(state.symbols.create(i)));
|
||||||
|
|
||||||
XMLAttrs xmlAttrs;
|
XMLAttrs xmlAttrs;
|
||||||
xmlAttrs["name"] = i;
|
xmlAttrs["name"] = i;
|
||||||
if (location && a.pos != ptr(&noPos)) posToXML(xmlAttrs, *a.pos);
|
if (location && a.pos) posToXML(state, xmlAttrs, state.positions[a.pos]);
|
||||||
|
|
||||||
XMLOpenElement _(doc, "attr", xmlAttrs);
|
XMLOpenElement _(doc, "attr", xmlAttrs);
|
||||||
printValueAsXML(state, strict, location,
|
printValueAsXML(state, strict, location,
|
||||||
*a.value, doc, context, drvsSeen, *a.pos);
|
*a.value, doc, context, drvsSeen, a.pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
Value & v, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
||||||
const Pos & pos)
|
const PosIdx pos)
|
||||||
{
|
{
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
|
|
||||||
|
@ -93,14 +93,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
Path drvPath;
|
Path drvPath;
|
||||||
a = v.attrs->find(state.sDrvPath);
|
a = v.attrs->find(state.sDrvPath);
|
||||||
if (a != v.attrs->end()) {
|
if (a != v.attrs->end()) {
|
||||||
if (strict) state.forceValue(*a->value, *a->pos);
|
if (strict) state.forceValue(*a->value, a->pos);
|
||||||
if (a->value->type() == nString)
|
if (a->value->type() == nString)
|
||||||
xmlAttrs["drvPath"] = drvPath = a->value->string.s;
|
xmlAttrs["drvPath"] = drvPath = a->value->string.s;
|
||||||
}
|
}
|
||||||
|
|
||||||
a = v.attrs->find(state.sOutPath);
|
a = v.attrs->find(state.sOutPath);
|
||||||
if (a != v.attrs->end()) {
|
if (a != v.attrs->end()) {
|
||||||
if (strict) state.forceValue(*a->value, *a->pos);
|
if (strict) state.forceValue(*a->value, a->pos);
|
||||||
if (a->value->type() == nString)
|
if (a->value->type() == nString)
|
||||||
xmlAttrs["outPath"] = a->value->string.s;
|
xmlAttrs["outPath"] = a->value->string.s;
|
||||||
}
|
}
|
||||||
|
@ -134,18 +134,18 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
XMLAttrs xmlAttrs;
|
XMLAttrs xmlAttrs;
|
||||||
if (location) posToXML(xmlAttrs, v.lambda.fun->pos);
|
if (location) posToXML(state, xmlAttrs, state.positions[v.lambda.fun->pos]);
|
||||||
XMLOpenElement _(doc, "function", xmlAttrs);
|
XMLOpenElement _(doc, "function", xmlAttrs);
|
||||||
|
|
||||||
if (v.lambda.fun->hasFormals()) {
|
if (v.lambda.fun->hasFormals()) {
|
||||||
XMLAttrs attrs;
|
XMLAttrs attrs;
|
||||||
if (!v.lambda.fun->arg.empty()) attrs["name"] = v.lambda.fun->arg;
|
if (v.lambda.fun->arg) attrs["name"] = state.symbols[v.lambda.fun->arg];
|
||||||
if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
|
if (v.lambda.fun->formals->ellipsis) attrs["ellipsis"] = "1";
|
||||||
XMLOpenElement _(doc, "attrspat", attrs);
|
XMLOpenElement _(doc, "attrspat", attrs);
|
||||||
for (auto & i : v.lambda.fun->formals->lexicographicOrder())
|
for (auto & i : v.lambda.fun->formals->lexicographicOrder(state.symbols))
|
||||||
doc.writeEmptyElement("attr", singletonAttrs("name", i.name));
|
doc.writeEmptyElement("attr", singletonAttrs("name", state.symbols[i.name]));
|
||||||
} else
|
} else
|
||||||
doc.writeEmptyElement("varpat", singletonAttrs("name", v.lambda.fun->arg));
|
doc.writeEmptyElement("varpat", singletonAttrs("name", state.symbols[v.lambda.fun->arg]));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -166,14 +166,14 @@ static void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
|
|
||||||
void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
|
void ExternalValueBase::printValueAsXML(EvalState & state, bool strict,
|
||||||
bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
bool location, XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
||||||
const Pos & pos) const
|
const PosIdx pos) const
|
||||||
{
|
{
|
||||||
doc.writeEmptyElement("unevaluated");
|
doc.writeEmptyElement("unevaluated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void printValueAsXML(EvalState & state, bool strict, bool location,
|
void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
Value & v, std::ostream & out, PathSet & context, const Pos & pos)
|
Value & v, std::ostream & out, PathSet & context, const PosIdx pos)
|
||||||
{
|
{
|
||||||
XMLWriter doc(true, out);
|
XMLWriter doc(true, out);
|
||||||
XMLOpenElement root(doc, "expr");
|
XMLOpenElement root(doc, "expr");
|
||||||
|
|
|
@ -9,6 +9,6 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
void printValueAsXML(EvalState & state, bool strict, bool location,
|
void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
Value & v, std::ostream & out, PathSet & context, const Pos & pos);
|
Value & v, std::ostream & out, PathSet & context, const PosIdx pos);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,8 +55,11 @@ struct Env;
|
||||||
struct Expr;
|
struct Expr;
|
||||||
struct ExprLambda;
|
struct ExprLambda;
|
||||||
struct PrimOp;
|
struct PrimOp;
|
||||||
class Symbol;
|
class SymbolIdx;
|
||||||
|
class PosIdx;
|
||||||
struct Pos;
|
struct Pos;
|
||||||
|
class StorePath;
|
||||||
|
class Store;
|
||||||
class EvalState;
|
class EvalState;
|
||||||
class XMLWriter;
|
class XMLWriter;
|
||||||
class JSONPlaceholder;
|
class JSONPlaceholder;
|
||||||
|
@ -64,6 +67,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
|
||||||
|
@ -99,7 +104,7 @@ class ExternalValueBase
|
||||||
/* Print the value as XML. Defaults to unevaluated */
|
/* Print the value as XML. Defaults to unevaluated */
|
||||||
virtual void printValueAsXML(EvalState & state, bool strict, bool location,
|
virtual void printValueAsXML(EvalState & state, bool strict, bool location,
|
||||||
XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
XMLWriter & doc, PathSet & context, PathSet & drvsSeen,
|
||||||
const Pos & pos) const;
|
const PosIdx pos) const;
|
||||||
|
|
||||||
virtual ~ExternalValueBase()
|
virtual ~ExternalValueBase()
|
||||||
{
|
{
|
||||||
|
@ -115,10 +120,13 @@ private:
|
||||||
InternalType internalType;
|
InternalType internalType;
|
||||||
|
|
||||||
friend std::string showType(const Value & v);
|
friend std::string showType(const Value & v);
|
||||||
friend void printValue(std::ostream & str, std::set<const void *> & seen, const Value & v);
|
|
||||||
|
void print(const SymbolTable & symbols, std::ostream & str, std::set<const void *> * seen) const;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
void print(const SymbolTable & symbols, 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
|
||||||
|
@ -245,7 +253,7 @@ public:
|
||||||
|
|
||||||
inline void mkString(const Symbol & s)
|
inline void mkString(const Symbol & s)
|
||||||
{
|
{
|
||||||
mkString(((const std::string &) s).c_str());
|
mkString(std::string_view(s).data());
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void mkPath(const char * s)
|
inline void mkPath(const char * s)
|
||||||
|
@ -361,14 +369,14 @@ public:
|
||||||
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size;
|
return internalType == tList1 ? 1 : internalType == tList2 ? 2 : bigList.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
Pos determinePos(const Pos & pos) const;
|
PosIdx determinePos(const PosIdx pos) const;
|
||||||
|
|
||||||
/* Check whether forcing this value requires a trivial amount of
|
/* Check whether forcing this value requires a trivial amount of
|
||||||
computation. In particular, function applications are
|
computation. In particular, function applications are
|
||||||
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()
|
||||||
{
|
{
|
||||||
|
@ -402,12 +410,12 @@ public:
|
||||||
|
|
||||||
#if HAVE_BOEHMGC
|
#if HAVE_BOEHMGC
|
||||||
typedef std::vector<Value *, traceable_allocator<Value *> > ValueVector;
|
typedef std::vector<Value *, traceable_allocator<Value *> > ValueVector;
|
||||||
typedef std::map<Symbol, Value *, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, Value *> > > ValueMap;
|
typedef std::map<SymbolIdx, Value *, std::less<SymbolIdx>, traceable_allocator<std::pair<const SymbolIdx, Value *> > > ValueMap;
|
||||||
typedef std::map<Symbol, ValueVector, std::less<Symbol>, traceable_allocator<std::pair<const Symbol, ValueVector> > > ValueVectorMap;
|
typedef std::map<SymbolIdx, ValueVector, std::less<SymbolIdx>, traceable_allocator<std::pair<const SymbolIdx, ValueVector> > > ValueVectorMap;
|
||||||
#else
|
#else
|
||||||
typedef std::vector<Value *> ValueVector;
|
typedef std::vector<Value *> ValueVector;
|
||||||
typedef std::map<Symbol, Value *> ValueMap;
|
typedef std::map<SymbolIdx, Value *> ValueMap;
|
||||||
typedef std::map<Symbol, ValueVector> ValueVectorMap;
|
typedef std::map<SymbolIdx, ValueVector> ValueVectorMap;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -238,9 +238,18 @@ std::optional<std::string> Input::getRef() const
|
||||||
|
|
||||||
std::optional<Hash> Input::getRev() const
|
std::optional<Hash> Input::getRev() const
|
||||||
{
|
{
|
||||||
if (auto s = maybeGetStrAttr(attrs, "rev"))
|
std::optional<Hash> hash = {};
|
||||||
return Hash::parseAny(*s, htSHA1);
|
|
||||||
return {};
|
if (auto s = maybeGetStrAttr(attrs, "rev")) {
|
||||||
|
try {
|
||||||
|
hash = Hash::parseAnyPrefixed(*s);
|
||||||
|
} catch (BadHash &e) {
|
||||||
|
// Default to sha1 for backwards compatibility with existing flakes
|
||||||
|
hash = Hash::parseAny(*s, htSHA1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<uint64_t> Input::getRevCount() const
|
std::optional<uint64_t> Input::getRevCount() const
|
||||||
|
|
|
@ -28,9 +28,7 @@ static std::string readHead(const Path & path)
|
||||||
|
|
||||||
static bool isNotDotGitDirectory(const Path & path)
|
static bool isNotDotGitDirectory(const Path & path)
|
||||||
{
|
{
|
||||||
static const std::regex gitDirRegex("^(?:.*/)?\\.git$");
|
return baseNameOf(path) != ".git";
|
||||||
|
|
||||||
return not std::regex_match(path, gitDirRegex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct GitInputScheme : InputScheme
|
struct GitInputScheme : InputScheme
|
||||||
|
@ -189,8 +187,16 @@ struct GitInputScheme : InputScheme
|
||||||
if (submodules) cacheType += "-submodules";
|
if (submodules) cacheType += "-submodules";
|
||||||
if (allRefs) cacheType += "-all-refs";
|
if (allRefs) cacheType += "-all-refs";
|
||||||
|
|
||||||
|
auto checkHashType = [&](const std::optional<Hash> & hash)
|
||||||
|
{
|
||||||
|
if (hash.has_value() && !(hash->type == htSHA1 || hash->type == htSHA256))
|
||||||
|
throw Error("Hash '%s' is not supported by Git. Supported types are sha1 and sha256.", hash->to_string(Base16, true));
|
||||||
|
};
|
||||||
|
|
||||||
auto getLockedAttrs = [&]()
|
auto getLockedAttrs = [&]()
|
||||||
{
|
{
|
||||||
|
checkHashType(input.getRev());
|
||||||
|
|
||||||
return Attrs({
|
return Attrs({
|
||||||
{"type", cacheType},
|
{"type", cacheType},
|
||||||
{"name", name},
|
{"name", name},
|
||||||
|
@ -222,22 +228,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) {
|
||||||
|
@ -261,9 +291,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);
|
||||||
|
|
||||||
|
@ -276,13 +308,13 @@ struct GitInputScheme : InputScheme
|
||||||
return files.count(file);
|
return files.count(file);
|
||||||
};
|
};
|
||||||
|
|
||||||
auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
|
auto storePath = store->addToStore(input.getName(), actualPath, FileIngestionMethod::Recursive, htSHA256, filter);
|
||||||
|
|
||||||
// FIXME: maybe we should use the timestamp of the last
|
// FIXME: maybe we should use the timestamp of the last
|
||||||
// modified dirty file?
|
// modified dirty file?
|
||||||
input.attrs.insert_or_assign(
|
input.attrs.insert_or_assign(
|
||||||
"lastModified",
|
"lastModified",
|
||||||
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 {std::move(storePath), input};
|
return {std::move(storePath), input};
|
||||||
}
|
}
|
||||||
|
|
|
@ -390,7 +390,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||||
|
|
||||||
ref_uri = line.substr(ref_index+5, line.length()-1);
|
ref_uri = line.substr(ref_index+5, line.length()-1);
|
||||||
} else
|
} else
|
||||||
ref_uri = fmt("refs/heads/%s", ref);
|
ref_uri = fmt("refs/(heads|tags)/%s", ref);
|
||||||
|
|
||||||
auto file = store->toRealPath(
|
auto file = store->toRealPath(
|
||||||
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
|
downloadFile(store, fmt("%s/info/refs", base_url), "source", false, headers).storePath);
|
||||||
|
@ -399,9 +399,11 @@ struct SourceHutInputScheme : GitArchiveInputScheme
|
||||||
std::string line;
|
std::string line;
|
||||||
std::string id;
|
std::string id;
|
||||||
while(getline(is, line)) {
|
while(getline(is, line)) {
|
||||||
auto index = line.find(ref_uri);
|
// Append $ to avoid partial name matches
|
||||||
if (index != std::string::npos) {
|
std::regex pattern(fmt("%s$", ref_uri));
|
||||||
id = line.substr(0, index-1);
|
|
||||||
|
if (std::regex_search(line, pattern)) {
|
||||||
|
id = line.substr(0, line.find('\t'));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ static std::string runHg(const Strings & args, const std::optional<std::string>
|
||||||
auto res = runProgram(std::move(opts));
|
auto res = runProgram(std::move(opts));
|
||||||
|
|
||||||
if (!statusOk(res.first))
|
if (!statusOk(res.first))
|
||||||
throw ExecError(res.first, fmt("hg %1%", statusToString(res.first)));
|
throw ExecError(res.first, "hg %1%", statusToString(res.first));
|
||||||
|
|
||||||
return res.second;
|
return res.second;
|
||||||
}
|
}
|
||||||
|
@ -178,9 +178,11 @@ struct MercurialInputScheme : InputScheme
|
||||||
auto files = tokenizeString<std::set<std::string>>(
|
auto files = tokenizeString<std::set<std::string>>(
|
||||||
runHg({ "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s);
|
runHg({ "status", "-R", actualUrl, "--clean", "--modified", "--added", "--no-status", "--print0" }), "\0"s);
|
||||||
|
|
||||||
|
Path actualPath(absPath(actualUrl));
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
@ -193,7 +195,7 @@ struct MercurialInputScheme : 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);
|
||||||
|
|
||||||
return {std::move(storePath), input};
|
return {std::move(storePath), input};
|
||||||
}
|
}
|
||||||
|
@ -201,8 +203,17 @@ struct MercurialInputScheme : InputScheme
|
||||||
|
|
||||||
if (!input.getRef()) input.attrs.insert_or_assign("ref", "default");
|
if (!input.getRef()) input.attrs.insert_or_assign("ref", "default");
|
||||||
|
|
||||||
|
auto checkHashType = [&](const std::optional<Hash> & hash)
|
||||||
|
{
|
||||||
|
if (hash.has_value() && hash->type != htSHA1)
|
||||||
|
throw Error("Hash '%s' is not supported by Mercurial. Only sha1 is supported.", hash->to_string(Base16, true));
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
auto getLockedAttrs = [&]()
|
auto getLockedAttrs = [&]()
|
||||||
{
|
{
|
||||||
|
checkHashType(input.getRev());
|
||||||
|
|
||||||
return Attrs({
|
return Attrs({
|
||||||
{"type", "hg"},
|
{"type", "hg"},
|
||||||
{"name", name},
|
{"name", name},
|
||||||
|
@ -262,7 +273,7 @@ struct MercurialInputScheme : InputScheme
|
||||||
runHg({ "recover", "-R", cacheDir });
|
runHg({ "recover", "-R", cacheDir });
|
||||||
runHg({ "pull", "-R", cacheDir, "--", actualUrl });
|
runHg({ "pull", "-R", cacheDir, "--", actualUrl });
|
||||||
} else {
|
} else {
|
||||||
throw ExecError(e.status, fmt("'hg pull' %s", statusToString(e.status)));
|
throw ExecError(e.status, "'hg pull' %s", statusToString(e.status));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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<StorePath, 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,9 +113,15 @@ 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 {std::move(*storePath), input};
|
return {std::move(*storePath), input};
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,37 +60,37 @@ void printMissing(ref<Store> store, const StorePathSet & willBuild,
|
||||||
{
|
{
|
||||||
if (!willBuild.empty()) {
|
if (!willBuild.empty()) {
|
||||||
if (willBuild.size() == 1)
|
if (willBuild.size() == 1)
|
||||||
printMsg(lvl, fmt("this derivation will be built:"));
|
printMsg(lvl, "this derivation will be built:");
|
||||||
else
|
else
|
||||||
printMsg(lvl, fmt("these %d derivations will be built:", willBuild.size()));
|
printMsg(lvl, "these %d derivations will be built:", willBuild.size());
|
||||||
auto sorted = store->topoSortPaths(willBuild);
|
auto sorted = store->topoSortPaths(willBuild);
|
||||||
reverse(sorted.begin(), sorted.end());
|
reverse(sorted.begin(), sorted.end());
|
||||||
for (auto & i : sorted)
|
for (auto & i : sorted)
|
||||||
printMsg(lvl, fmt(" %s", store->printStorePath(i)));
|
printMsg(lvl, " %s", store->printStorePath(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!willSubstitute.empty()) {
|
if (!willSubstitute.empty()) {
|
||||||
const float downloadSizeMiB = downloadSize / (1024.f * 1024.f);
|
const float downloadSizeMiB = downloadSize / (1024.f * 1024.f);
|
||||||
const float narSizeMiB = narSize / (1024.f * 1024.f);
|
const float narSizeMiB = narSize / (1024.f * 1024.f);
|
||||||
if (willSubstitute.size() == 1) {
|
if (willSubstitute.size() == 1) {
|
||||||
printMsg(lvl, fmt("this path will be fetched (%.2f MiB download, %.2f MiB unpacked):",
|
printMsg(lvl, "this path will be fetched (%.2f MiB download, %.2f MiB unpacked):",
|
||||||
downloadSizeMiB,
|
downloadSizeMiB,
|
||||||
narSizeMiB));
|
narSizeMiB);
|
||||||
} else {
|
} else {
|
||||||
printMsg(lvl, fmt("these %d paths will be fetched (%.2f MiB download, %.2f MiB unpacked):",
|
printMsg(lvl, "these %d paths will be fetched (%.2f MiB download, %.2f MiB unpacked):",
|
||||||
willSubstitute.size(),
|
willSubstitute.size(),
|
||||||
downloadSizeMiB,
|
downloadSizeMiB,
|
||||||
narSizeMiB));
|
narSizeMiB);
|
||||||
}
|
}
|
||||||
for (auto & i : willSubstitute)
|
for (auto & i : willSubstitute)
|
||||||
printMsg(lvl, fmt(" %s", store->printStorePath(i)));
|
printMsg(lvl, " %s", store->printStorePath(i));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!unknown.empty()) {
|
if (!unknown.empty()) {
|
||||||
printMsg(lvl, fmt("don't know how to build these paths%s:",
|
printMsg(lvl, "don't know how to build these paths%s:",
|
||||||
(settings.readOnlyMode ? " (may be caused by read-only store access)" : "")));
|
(settings.readOnlyMode ? " (may be caused by read-only store access)" : ""));
|
||||||
for (auto & i : unknown)
|
for (auto & i : unknown)
|
||||||
printMsg(lvl, fmt(" %s", store->printStorePath(i)));
|
printMsg(lvl, " %s", store->printStorePath(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "realisation.hh"
|
#include "realisation.hh"
|
||||||
|
#include "derived-path.hh"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
@ -30,6 +31,8 @@ struct BuildResult
|
||||||
ResolvesToAlreadyValid,
|
ResolvesToAlreadyValid,
|
||||||
NoSubstituters,
|
NoSubstituters,
|
||||||
} status = MiscFailure;
|
} status = MiscFailure;
|
||||||
|
|
||||||
|
// FIXME: include entire ErrorInfo object.
|
||||||
std::string errorMsg;
|
std::string errorMsg;
|
||||||
|
|
||||||
std::string toString() const {
|
std::string toString() const {
|
||||||
|
|
|
@ -204,10 +204,33 @@ void DerivationGoal::haveDerivation()
|
||||||
{
|
{
|
||||||
trace("have derivation");
|
trace("have derivation");
|
||||||
|
|
||||||
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)
|
||||||
|
@ -232,9 +255,6 @@ void DerivationGoal::haveDerivation()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedDrv = std::make_unique<ParsedDerivation>(drvPath, *drv);
|
|
||||||
|
|
||||||
|
|
||||||
/* We are first going to try to create the invalid output paths
|
/* We are first going to try to create the invalid output paths
|
||||||
through substitutes. If that doesn't work, we'll build
|
through substitutes. If that doesn't work, we'll build
|
||||||
them. */
|
them. */
|
||||||
|
@ -268,6 +288,8 @@ void DerivationGoal::outputsSubstitutionTried()
|
||||||
{
|
{
|
||||||
trace("all outputs substituted (maybe)");
|
trace("all outputs substituted (maybe)");
|
||||||
|
|
||||||
|
assert(drv->type().isPure());
|
||||||
|
|
||||||
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) {
|
if (nrFailed > 0 && nrFailed > nrNoSubstituters + nrIncompleteClosure && !settings.tryFallback) {
|
||||||
done(BuildResult::TransientFailure, {},
|
done(BuildResult::TransientFailure, {},
|
||||||
Error("some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
|
Error("some substitutes for the outputs of derivation '%s' failed (usually happens due to networking issues); try '--fallback' to build derivation from source ",
|
||||||
|
@ -311,18 +333,27 @@ void DerivationGoal::outputsSubstitutionTried()
|
||||||
gaveUpOnSubstitution();
|
gaveUpOnSubstitution();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* At least one of the output paths could not be
|
/* At least one of the output paths could not be
|
||||||
produced using a substitute. So we have to build instead. */
|
produced using a substitute. So we have to build instead. */
|
||||||
void DerivationGoal::gaveUpOnSubstitution()
|
void DerivationGoal::gaveUpOnSubstitution()
|
||||||
{
|
{
|
||||||
/* Make sure checkPathValidity() from now on checks all
|
|
||||||
outputs. */
|
|
||||||
wantedOutputs.clear();
|
|
||||||
|
|
||||||
/* The inputs must be built before we can build this goal. */
|
/* The inputs must be built before we can build this goal. */
|
||||||
|
inputDrvOutputs.clear();
|
||||||
if (useDerivation)
|
if (useDerivation)
|
||||||
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs)
|
for (auto & i : dynamic_cast<Derivation *>(drv.get())->inputDrvs) {
|
||||||
|
/* Ensure that pure, non-fixed-output derivations don't
|
||||||
|
depend on impure derivations. */
|
||||||
|
if (drv->type().isPure() && !drv->type().isFixed()) {
|
||||||
|
auto inputDrv = worker.evalStore.readDerivation(i.first);
|
||||||
|
if (!inputDrv.type().isPure())
|
||||||
|
throw Error("pure derivation '%s' depends on impure derivation '%s'",
|
||||||
|
worker.store.printStorePath(drvPath),
|
||||||
|
worker.store.printStorePath(i.first));
|
||||||
|
}
|
||||||
|
|
||||||
addWaitee(worker.makeDerivationGoal(i.first, i.second, buildMode == bmRepair ? bmRepair : bmNormal));
|
addWaitee(worker.makeDerivationGoal(i.first, i.second, buildMode == bmRepair ? bmRepair : bmNormal));
|
||||||
|
}
|
||||||
|
|
||||||
/* Copy the input sources from the eval store to the build
|
/* Copy the input sources from the eval store to the build
|
||||||
store. */
|
store. */
|
||||||
|
@ -350,6 +381,8 @@ void DerivationGoal::gaveUpOnSubstitution()
|
||||||
|
|
||||||
void DerivationGoal::repairClosure()
|
void DerivationGoal::repairClosure()
|
||||||
{
|
{
|
||||||
|
assert(drv->type().isPure());
|
||||||
|
|
||||||
/* If we're repairing, we now know that our own outputs are valid.
|
/* If we're repairing, we now know that our own outputs are valid.
|
||||||
Now check whether the other paths in the outputs closure are
|
Now check whether the other paths in the outputs closure are
|
||||||
good. If not, then start derivation goals for the derivations
|
good. If not, then start derivation goals for the derivations
|
||||||
|
@ -426,7 +459,8 @@ void DerivationGoal::inputsRealised()
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retrySubstitution) {
|
if (retrySubstitution && !retriedSubstitution) {
|
||||||
|
retriedSubstitution = true;
|
||||||
haveDerivation();
|
haveDerivation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -440,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,
|
||||||
|
@ -473,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));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -501,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
|
||||||
|
@ -908,7 +955,7 @@ void DerivationGoal::buildDone()
|
||||||
st =
|
st =
|
||||||
dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
|
dynamic_cast<NotDeterministic*>(&e) ? BuildResult::NotDeterministic :
|
||||||
statusOk(status) ? BuildResult::OutputRejected :
|
statusOk(status) ? BuildResult::OutputRejected :
|
||||||
derivationIsImpure(derivationType) || diskFull ? BuildResult::TransientFailure :
|
!derivationType.isSandboxed() || diskFull ? BuildResult::TransientFailure :
|
||||||
BuildResult::PermanentFailure;
|
BuildResult::PermanentFailure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -919,60 +966,53 @@ void DerivationGoal::buildDone()
|
||||||
|
|
||||||
void DerivationGoal::resolvedFinished()
|
void DerivationGoal::resolvedFinished()
|
||||||
{
|
{
|
||||||
|
trace("resolved derivation finished");
|
||||||
|
|
||||||
assert(resolvedDrvGoal);
|
assert(resolvedDrvGoal);
|
||||||
auto resolvedDrv = *resolvedDrvGoal->drv;
|
auto resolvedDrv = *resolvedDrvGoal->drv;
|
||||||
|
auto & resolvedResult = resolvedDrvGoal->buildResult;
|
||||||
auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv);
|
|
||||||
|
|
||||||
StorePathSet outputPaths;
|
|
||||||
|
|
||||||
// `wantedOutputs` might be empty, which means “all the outputs”
|
|
||||||
auto realWantedOutputs = wantedOutputs;
|
|
||||||
if (realWantedOutputs.empty())
|
|
||||||
realWantedOutputs = resolvedDrv.outputNames();
|
|
||||||
|
|
||||||
DrvOutputs builtOutputs;
|
DrvOutputs builtOutputs;
|
||||||
|
|
||||||
for (auto & wantedOutput : realWantedOutputs) {
|
if (resolvedResult.success()) {
|
||||||
assert(initialOutputs.count(wantedOutput) != 0);
|
auto resolvedHashes = staticOutputHashes(worker.store, resolvedDrv);
|
||||||
assert(resolvedHashes.count(wantedOutput) != 0);
|
|
||||||
auto realisation = worker.store.queryRealisation(
|
StorePathSet outputPaths;
|
||||||
DrvOutput{resolvedHashes.at(wantedOutput), wantedOutput}
|
|
||||||
);
|
// `wantedOutputs` might be empty, which means “all the outputs”
|
||||||
// We've just built it, but maybe the build failed, in which case the
|
auto realWantedOutputs = wantedOutputs;
|
||||||
// realisation won't be there
|
if (realWantedOutputs.empty())
|
||||||
if (realisation) {
|
realWantedOutputs = resolvedDrv.outputNames();
|
||||||
auto newRealisation = *realisation;
|
|
||||||
newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput};
|
for (auto & wantedOutput : realWantedOutputs) {
|
||||||
newRealisation.signatures.clear();
|
assert(initialOutputs.count(wantedOutput) != 0);
|
||||||
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath);
|
assert(resolvedHashes.count(wantedOutput) != 0);
|
||||||
signRealisation(newRealisation);
|
auto realisation = resolvedResult.builtOutputs.at(
|
||||||
worker.store.registerDrvOutput(newRealisation);
|
DrvOutput { resolvedHashes.at(wantedOutput), wantedOutput });
|
||||||
outputPaths.insert(realisation->outPath);
|
if (drv->type().isPure()) {
|
||||||
builtOutputs.emplace(realisation->id, *realisation);
|
auto newRealisation = realisation;
|
||||||
} else {
|
newRealisation.id = DrvOutput { initialOutputs.at(wantedOutput).outputHash, wantedOutput };
|
||||||
// If we don't have a realisation, then it must mean that something
|
newRealisation.signatures.clear();
|
||||||
// failed when building the resolved drv
|
if (!drv->type().isFixed())
|
||||||
assert(!buildResult.success());
|
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath);
|
||||||
|
signRealisation(newRealisation);
|
||||||
|
worker.store.registerDrvOutput(newRealisation);
|
||||||
|
}
|
||||||
|
outputPaths.insert(realisation.outPath);
|
||||||
|
builtOutputs.emplace(realisation.id, realisation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
runPostBuildHook(
|
||||||
|
worker.store,
|
||||||
|
*logger,
|
||||||
|
drvPath,
|
||||||
|
outputPaths
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
runPostBuildHook(
|
auto status = resolvedResult.status;
|
||||||
worker.store,
|
if (status == BuildResult::AlreadyValid)
|
||||||
*logger,
|
status = BuildResult::ResolvesToAlreadyValid;
|
||||||
drvPath,
|
|
||||||
outputPaths
|
|
||||||
);
|
|
||||||
|
|
||||||
auto status = [&]() {
|
|
||||||
auto & resolvedResult = resolvedDrvGoal->buildResult;
|
|
||||||
switch (resolvedResult.status) {
|
|
||||||
case BuildResult::AlreadyValid:
|
|
||||||
return BuildResult::ResolvesToAlreadyValid;
|
|
||||||
default:
|
|
||||||
return resolvedResult.status;
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
||||||
done(status, std::move(builtOutputs));
|
done(status, std::move(builtOutputs));
|
||||||
}
|
}
|
||||||
|
@ -1221,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));
|
||||||
|
@ -1233,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);
|
||||||
|
@ -1246,6 +1288,8 @@ OutputPathMap DerivationGoal::queryDerivationOutputMap()
|
||||||
|
|
||||||
std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
||||||
{
|
{
|
||||||
|
if (!drv->type().isPure()) return { false, {} };
|
||||||
|
|
||||||
bool checkHash = buildMode == bmRepair;
|
bool checkHash = buildMode == bmRepair;
|
||||||
auto wantedOutputsLeft = wantedOutputs;
|
auto wantedOutputsLeft = wantedOutputs;
|
||||||
DrvOutputs validOutputs;
|
DrvOutputs validOutputs;
|
||||||
|
@ -1289,6 +1333,7 @@ std::pair<bool, DrvOutputs> DerivationGoal::checkPathValidity()
|
||||||
if (info.wanted && info.known && info.known->isValid())
|
if (info.wanted && info.known && info.known->isValid())
|
||||||
validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path });
|
validOutputs.emplace(drvOutput, Realisation { drvOutput, info.known->path });
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we requested all the outputs via the empty set, we are always fine.
|
// If we requested all the outputs via the empty set, we are always fine.
|
||||||
// If we requested specific elements, the loop above removes all the valid
|
// If we requested specific elements, the loop above removes all the valid
|
||||||
// ones, so any that are left must be invalid.
|
// ones, so any that are left must be invalid.
|
||||||
|
@ -1326,9 +1371,7 @@ void DerivationGoal::done(
|
||||||
{
|
{
|
||||||
buildResult.status = status;
|
buildResult.status = status;
|
||||||
if (ex)
|
if (ex)
|
||||||
// FIXME: strip: "error: "
|
buildResult.errorMsg = fmt("%s", normaltxt(ex->info().msg));
|
||||||
buildResult.errorMsg = ex->what();
|
|
||||||
amDone(buildResult.success() ? ecSuccess : ecFailed, ex);
|
|
||||||
if (buildResult.status == BuildResult::TimedOut)
|
if (buildResult.status == BuildResult::TimedOut)
|
||||||
worker.timedOut = true;
|
worker.timedOut = true;
|
||||||
if (buildResult.status == BuildResult::PermanentFailure)
|
if (buildResult.status == BuildResult::PermanentFailure)
|
||||||
|
@ -1355,7 +1398,21 @@ void DerivationGoal::done(
|
||||||
fs.open(traceBuiltOutputsFile, std::fstream::out);
|
fs.open(traceBuiltOutputsFile, std::fstream::out);
|
||||||
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
amDone(buildResult.success() ? ecSuccess : ecFailed, ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void DerivationGoal::waiteeDone(GoalPtr waitee, ExitCode result)
|
||||||
|
{
|
||||||
|
Goal::waiteeDone(waitee, result);
|
||||||
|
|
||||||
|
if (waitee->buildResult.success())
|
||||||
|
if (auto bfd = std::get_if<DerivedPath::Built>(&waitee->buildResult.path))
|
||||||
|
for (auto & [output, realisation] : waitee->buildResult.builtOutputs)
|
||||||
|
inputDrvOutputs.insert_or_assign(
|
||||||
|
{ bfd->drvPath, output.outputName },
|
||||||
|
realisation.outPath);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,12 +57,21 @@ struct DerivationGoal : public Goal
|
||||||
them. */
|
them. */
|
||||||
StringSet wantedOutputs;
|
StringSet wantedOutputs;
|
||||||
|
|
||||||
|
/* Mapping from input derivations + output names to actual store
|
||||||
|
paths. This is filled in by waiteeDone() as each dependency
|
||||||
|
finishes, before inputsRealised() is reached, */
|
||||||
|
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
|
||||||
|
|
||||||
/* Whether additional wanted outputs have been added. */
|
/* Whether additional wanted outputs have been added. */
|
||||||
bool needRestart = false;
|
bool needRestart = false;
|
||||||
|
|
||||||
/* Whether to retry substituting the outputs after building the
|
/* Whether to retry substituting the outputs after building the
|
||||||
inputs. */
|
inputs. This is done in case of an incomplete closure. */
|
||||||
bool retrySubstitution;
|
bool retrySubstitution = false;
|
||||||
|
|
||||||
|
/* Whether we've retried substitution, in which case we won't try
|
||||||
|
again. */
|
||||||
|
bool retriedSubstitution = false;
|
||||||
|
|
||||||
/* The derivation stored at drvPath. */
|
/* The derivation stored at drvPath. */
|
||||||
std::unique_ptr<Derivation> drv;
|
std::unique_ptr<Derivation> drv;
|
||||||
|
@ -220,6 +229,8 @@ struct DerivationGoal : public Goal
|
||||||
DrvOutputs builtOutputs = {},
|
DrvOutputs builtOutputs = {},
|
||||||
std::optional<Error> ex = {});
|
std::optional<Error> ex = {});
|
||||||
|
|
||||||
|
void waiteeDone(GoalPtr waitee, ExitCode result) override;
|
||||||
|
|
||||||
StorePathSet exportReferences(const StorePathSet & storePaths);
|
StorePathSet exportReferences(const StorePathSet & storePaths);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -41,7 +41,7 @@ void DrvOutputSubstitutionGoal::tryNext()
|
||||||
if (subs.size() == 0) {
|
if (subs.size() == 0) {
|
||||||
/* None left. Terminate this goal and let someone else deal
|
/* None left. Terminate this goal and let someone else deal
|
||||||
with it. */
|
with it. */
|
||||||
debug("drv output '%s' is required, but there is no substituter that can provide it", id.to_string());
|
debug("derivation output '%s' is required, but there is no substituter that can provide it", id.to_string());
|
||||||
|
|
||||||
/* Hack: don't indicate failure if there were no substituters.
|
/* Hack: don't indicate failure if there were no substituters.
|
||||||
In that case the calling derivation should just do a
|
In that case the calling derivation should just do a
|
||||||
|
|
|
@ -28,7 +28,7 @@ void Goal::addWaitee(GoalPtr waitee)
|
||||||
|
|
||||||
void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
|
void Goal::waiteeDone(GoalPtr waitee, ExitCode result)
|
||||||
{
|
{
|
||||||
assert(waitees.find(waitee) != waitees.end());
|
assert(waitees.count(waitee));
|
||||||
waitees.erase(waitee);
|
waitees.erase(waitee);
|
||||||
|
|
||||||
trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size()));
|
trace(fmt("waitee '%s' done; %d left", waitee->name, waitees.size()));
|
||||||
|
|
|
@ -40,21 +40,21 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
||||||
WeakGoals waiters;
|
WeakGoals waiters;
|
||||||
|
|
||||||
/* Number of goals we are/were waiting for that have failed. */
|
/* Number of goals we are/were waiting for that have failed. */
|
||||||
unsigned int nrFailed;
|
size_t nrFailed = 0;
|
||||||
|
|
||||||
/* Number of substitution goals we are/were waiting for that
|
/* Number of substitution goals we are/were waiting for that
|
||||||
failed because there are no substituters. */
|
failed because there are no substituters. */
|
||||||
unsigned int nrNoSubstituters;
|
size_t nrNoSubstituters = 0;
|
||||||
|
|
||||||
/* Number of substitution goals we are/were waiting for that
|
/* Number of substitution goals we are/were waiting for that
|
||||||
failed because they had unsubstitutable references. */
|
failed because they had unsubstitutable references. */
|
||||||
unsigned int nrIncompleteClosure;
|
size_t nrIncompleteClosure = 0;
|
||||||
|
|
||||||
/* Name of this goal for debugging purposes. */
|
/* Name of this goal for debugging purposes. */
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
/* Whether the goal is finished. */
|
/* Whether the goal is finished. */
|
||||||
ExitCode exitCode;
|
ExitCode exitCode = ecBusy;
|
||||||
|
|
||||||
/* Build result. */
|
/* Build result. */
|
||||||
BuildResult buildResult;
|
BuildResult buildResult;
|
||||||
|
@ -65,10 +65,7 @@ struct Goal : public std::enable_shared_from_this<Goal>
|
||||||
Goal(Worker & worker, DerivedPath path)
|
Goal(Worker & worker, DerivedPath path)
|
||||||
: worker(worker)
|
: worker(worker)
|
||||||
, buildResult { .path = std::move(path) }
|
, buildResult { .path = std::move(path) }
|
||||||
{
|
{ }
|
||||||
nrFailed = nrNoSubstituters = nrIncompleteClosure = 0;
|
|
||||||
exitCode = ecBusy;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~Goal()
|
virtual ~Goal()
|
||||||
{
|
{
|
||||||
|
|
|
@ -395,7 +395,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
else if (settings.sandboxMode == smDisabled)
|
else if (settings.sandboxMode == smDisabled)
|
||||||
useChroot = false;
|
useChroot = false;
|
||||||
else if (settings.sandboxMode == smRelaxed)
|
else if (settings.sandboxMode == smRelaxed)
|
||||||
useChroot = !(derivationIsImpure(derivationType)) && !noChroot;
|
useChroot = derivationType.isSandboxed() && !noChroot;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto & localStore = getLocalStore();
|
auto & localStore = getLocalStore();
|
||||||
|
@ -608,7 +608,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
"nogroup:x:65534:\n", sandboxGid()));
|
"nogroup:x:65534:\n", sandboxGid()));
|
||||||
|
|
||||||
/* Create /etc/hosts with localhost entry. */
|
/* Create /etc/hosts with localhost entry. */
|
||||||
if (!(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,
|
||||||
|
@ -704,6 +704,9 @@ void LocalDerivationGoal::startBuilder()
|
||||||
|
|
||||||
/* Run the builder. */
|
/* Run the builder. */
|
||||||
printMsg(lvlChatty, "executing builder '%1%'", drv->builder);
|
printMsg(lvlChatty, "executing builder '%1%'", drv->builder);
|
||||||
|
printMsg(lvlChatty, "using builder args '%1%'", concatStringsSep(" ", drv->args));
|
||||||
|
for (auto & i : drv->env)
|
||||||
|
printMsg(lvlVomit, "setting builder env variable '%1%'='%2%'", i.first, i.second);
|
||||||
|
|
||||||
/* Create the log file. */
|
/* Create the log file. */
|
||||||
Path logFile = openLogFile();
|
Path logFile = openLogFile();
|
||||||
|
@ -796,7 +799,7 @@ void LocalDerivationGoal::startBuilder()
|
||||||
us.
|
us.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (!(derivationIsImpure(derivationType)))
|
if (derivationType.isSandboxed())
|
||||||
privateNetwork = true;
|
privateNetwork = true;
|
||||||
|
|
||||||
userNamespaceSync.create();
|
userNamespaceSync.create();
|
||||||
|
@ -1049,7 +1052,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
|
||||||
|
@ -1060,7 +1063,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("");
|
||||||
}
|
}
|
||||||
|
@ -1340,6 +1343,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"); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -1668,7 +1677,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
|
||||||
|
@ -1912,7 +1921,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 */
|
||||||
|
@ -2273,7 +2282,7 @@ DrvOutputs 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. */
|
||||||
|
@ -2340,7 +2349,7 @@ DrvOutputs 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
|
||||||
|
@ -2360,8 +2369,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
return newInfo0;
|
return newInfo0;
|
||||||
},
|
},
|
||||||
|
|
||||||
[&](const DerivationOutputCAFixed & dof) {
|
[&](const DerivationOutput::CAFixed & dof) {
|
||||||
auto newInfo0 = newInfoFromCA(DerivationOutputCAFloating {
|
auto newInfo0 = newInfoFromCA(DerivationOutput::CAFloating {
|
||||||
.method = dof.hash.method,
|
.method = dof.hash.method,
|
||||||
.hashType = dof.hash.hash.type,
|
.hashType = dof.hash.hash.type,
|
||||||
});
|
});
|
||||||
|
@ -2383,17 +2392,24 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
return newInfo0;
|
return newInfo0;
|
||||||
},
|
},
|
||||||
|
|
||||||
[&](DerivationOutputCAFloating & dof) {
|
[&](const DerivationOutput::CAFloating & dof) {
|
||||||
return newInfoFromCA(dof);
|
return newInfoFromCA(dof);
|
||||||
},
|
},
|
||||||
|
|
||||||
[&](DerivationOutputDeferred) -> ValidPathInfo {
|
[&](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);
|
||||||
},
|
},
|
||||||
|
|
||||||
}, 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. */
|
||||||
|
@ -2603,11 +2619,14 @@ DrvOutputs LocalDerivationGoal::registerOutputs()
|
||||||
},
|
},
|
||||||
.outPath = newInfo.path
|
.outPath = newInfo.path
|
||||||
};
|
};
|
||||||
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) {
|
if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)
|
||||||
|
&& drv->type().isPure())
|
||||||
|
{
|
||||||
signRealisation(thisRealisation);
|
signRealisation(thisRealisation);
|
||||||
worker.store.registerDrvOutput(thisRealisation);
|
worker.store.registerDrvOutput(thisRealisation);
|
||||||
}
|
}
|
||||||
builtOutputs.emplace(thisRealisation.id, thisRealisation);
|
if (wantOutput(outputName, wantedOutputs))
|
||||||
|
builtOutputs.emplace(thisRealisation.id, thisRealisation);
|
||||||
}
|
}
|
||||||
|
|
||||||
return builtOutputs;
|
return builtOutputs;
|
||||||
|
|
|
@ -24,9 +24,16 @@ PathSubstitutionGoal::~PathSubstitutionGoal()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void PathSubstitutionGoal::done(ExitCode result, BuildResult::Status status)
|
void PathSubstitutionGoal::done(
|
||||||
|
ExitCode result,
|
||||||
|
BuildResult::Status status,
|
||||||
|
std::optional<std::string> errorMsg)
|
||||||
{
|
{
|
||||||
buildResult.status = status;
|
buildResult.status = status;
|
||||||
|
if (errorMsg) {
|
||||||
|
debug(*errorMsg);
|
||||||
|
buildResult.errorMsg = *errorMsg;
|
||||||
|
}
|
||||||
amDone(result);
|
amDone(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,12 +74,14 @@ void PathSubstitutionGoal::tryNext()
|
||||||
if (subs.size() == 0) {
|
if (subs.size() == 0) {
|
||||||
/* None left. Terminate this goal and let someone else deal
|
/* None left. Terminate this goal and let someone else deal
|
||||||
with it. */
|
with it. */
|
||||||
debug("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath));
|
|
||||||
|
|
||||||
/* Hack: don't indicate failure if there were no substituters.
|
/* Hack: don't indicate failure if there were no substituters.
|
||||||
In that case the calling derivation should just do a
|
In that case the calling derivation should just do a
|
||||||
build. */
|
build. */
|
||||||
done(substituterFailed ? ecFailed : ecNoSubstituters, BuildResult::NoSubstituters);
|
done(
|
||||||
|
substituterFailed ? ecFailed : ecNoSubstituters,
|
||||||
|
BuildResult::NoSubstituters,
|
||||||
|
fmt("path '%s' is required, but there is no substituter that can build it", worker.store.printStorePath(storePath)));
|
||||||
|
|
||||||
if (substituterFailed) {
|
if (substituterFailed) {
|
||||||
worker.failedSubstitutions++;
|
worker.failedSubstitutions++;
|
||||||
|
@ -169,10 +178,10 @@ void PathSubstitutionGoal::referencesValid()
|
||||||
trace("all references realised");
|
trace("all references realised");
|
||||||
|
|
||||||
if (nrFailed > 0) {
|
if (nrFailed > 0) {
|
||||||
debug("some references of path '%s' could not be realised", worker.store.printStorePath(storePath));
|
|
||||||
done(
|
done(
|
||||||
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
nrNoSubstituters > 0 || nrIncompleteClosure > 0 ? ecIncompleteClosure : ecFailed,
|
||||||
BuildResult::DependencyFailed);
|
BuildResult::DependencyFailed,
|
||||||
|
fmt("some references of path '%s' could not be realised", worker.store.printStorePath(storePath)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,10 @@ 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);
|
void done(
|
||||||
|
ExitCode result,
|
||||||
|
BuildResult::Status status,
|
||||||
|
std::optional<std::string> errorMsg = {});
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
PathSubstitutionGoal(const StorePath & storePath, Worker & worker, RepairFlag repair = NoRepair, std::optional<ContentAddress> ca = std::nullopt);
|
||||||
|
|
|
@ -47,9 +47,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The files below are special-cased to that they don't show up
|
/* The files below are special-cased to that they don't show
|
||||||
* in user profiles, either because they are useless, or
|
* up in user profiles, either because they are useless, or
|
||||||
* because they would cauase pointless collisions (e.g., each
|
* because they would cause pointless collisions (e.g., each
|
||||||
* Python package brings its own
|
* Python package brings its own
|
||||||
* `$out/lib/pythonX.Y/site-packages/easy-install.pth'.)
|
* `$out/lib/pythonX.Y/site-packages/easy-install.pth'.)
|
||||||
*/
|
*/
|
||||||
|
@ -57,7 +57,9 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir,
|
||||||
hasSuffix(srcFile, "/nix-support") ||
|
hasSuffix(srcFile, "/nix-support") ||
|
||||||
hasSuffix(srcFile, "/perllocal.pod") ||
|
hasSuffix(srcFile, "/perllocal.pod") ||
|
||||||
hasSuffix(srcFile, "/info/dir") ||
|
hasSuffix(srcFile, "/info/dir") ||
|
||||||
hasSuffix(srcFile, "/log"))
|
hasSuffix(srcFile, "/log") ||
|
||||||
|
hasSuffix(srcFile, "/manifest.nix") ||
|
||||||
|
hasSuffix(srcFile, "/manifest.json"))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
else if (S_ISDIR(srcSt.st_mode)) {
|
else if (S_ISDIR(srcSt.st_mode)) {
|
||||||
|
|
|
@ -13,12 +13,27 @@ create table if not exists Realisations (
|
||||||
|
|
||||||
create index if not exists IndexRealisations on Realisations(drvPath, outputName);
|
create index if not exists IndexRealisations on Realisations(drvPath, outputName);
|
||||||
|
|
||||||
|
-- We can end-up in a weird edge-case where a path depends on itself because
|
||||||
|
-- it’s an output of a CA derivation, that happens to be the same as one of its
|
||||||
|
-- dependencies.
|
||||||
|
-- In that case we have a dependency loop (path -> realisation1 -> realisation2
|
||||||
|
-- -> path) that we need to break by removing the dependencies between the
|
||||||
|
-- realisations
|
||||||
|
create trigger if not exists DeleteSelfRefsViaRealisations before delete on ValidPaths
|
||||||
|
begin
|
||||||
|
delete from RealisationsRefs where realisationReference in (
|
||||||
|
select id from Realisations where outputPath = old.id
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
|
||||||
create table if not exists RealisationsRefs (
|
create table if not exists RealisationsRefs (
|
||||||
referrer integer not null,
|
referrer integer not null,
|
||||||
realisationReference integer,
|
realisationReference integer,
|
||||||
foreign key (referrer) references Realisations(id) on delete cascade,
|
foreign key (referrer) references Realisations(id) on delete cascade,
|
||||||
foreign key (realisationReference) references Realisations(id) on delete restrict
|
foreign key (realisationReference) references Realisations(id) on delete restrict
|
||||||
);
|
);
|
||||||
|
-- used by deletion trigger
|
||||||
|
create index if not exists IndexRealisationsRefsRealisationReference on RealisationsRefs(realisationReference);
|
||||||
|
|
||||||
-- used by QueryRealisationReferences
|
-- used by QueryRealisationReferences
|
||||||
create index if not exists IndexRealisationsRefs on RealisationsRefs(referrer);
|
create index if not exists IndexRealisationsRefs on RealisationsRefs(referrer);
|
||||||
|
|
|
@ -3,7 +3,9 @@
|
||||||
#include "worker-protocol.hh"
|
#include "worker-protocol.hh"
|
||||||
#include "build-result.hh"
|
#include "build-result.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
#include "store-cast.hh"
|
||||||
#include "gc-store.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"
|
||||||
|
@ -558,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.
|
||||||
|
@ -590,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) {
|
||||||
|
@ -604,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;
|
||||||
|
@ -645,7 +649,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
Path path = absPath(readString(from));
|
Path path = absPath(readString(from));
|
||||||
|
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
auto & gcStore = requireGcStore(*store);
|
auto & gcStore = require<GcStore>(*store);
|
||||||
gcStore.addIndirectRoot(path);
|
gcStore.addIndirectRoot(path);
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
|
|
||||||
|
@ -663,7 +667,7 @@ static void performOp(TunnelLogger * logger, ref<Store> store,
|
||||||
|
|
||||||
case wopFindRoots: {
|
case wopFindRoots: {
|
||||||
logger->startWork();
|
logger->startWork();
|
||||||
auto & gcStore = requireGcStore(*store);
|
auto & gcStore = require<GcStore>(*store);
|
||||||
Roots roots = gcStore.findRoots(!trusted);
|
Roots roots = gcStore.findRoots(!trusted);
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
|
|
||||||
|
@ -695,7 +699,7 @@ 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");
|
||||||
auto & gcStore = requireGcStore(*store);
|
auto & gcStore = require<GcStore>(*store);
|
||||||
gcStore.collectGarbage(options, results);
|
gcStore.collectGarbage(options, results);
|
||||||
logger->stopWork();
|
logger->stopWork();
|
||||||
|
|
||||||
|
@ -953,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;
|
||||||
|
|
|
@ -11,72 +11,114 @@ namespace nix {
|
||||||
std::optional<StorePath> DerivationOutput::path(const Store & store, std::string_view drvName, std::string_view outputName) const
|
std::optional<StorePath> DerivationOutput::path(const Store & store, std::string_view drvName, std::string_view outputName) const
|
||||||
{
|
{
|
||||||
return std::visit(overloaded {
|
return std::visit(overloaded {
|
||||||
[](const DerivationOutputInputAddressed & doi) -> std::optional<StorePath> {
|
[](const DerivationOutput::InputAddressed & doi) -> std::optional<StorePath> {
|
||||||
return { doi.path };
|
return { doi.path };
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFixed & dof) -> std::optional<StorePath> {
|
[&](const DerivationOutput::CAFixed & dof) -> std::optional<StorePath> {
|
||||||
return {
|
return {
|
||||||
dof.path(store, drvName, outputName)
|
dof.path(store, drvName, outputName)
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
[](const DerivationOutputCAFloating & dof) -> std::optional<StorePath> {
|
[](const DerivationOutput::CAFloating & dof) -> std::optional<StorePath> {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
},
|
},
|
||||||
[](const DerivationOutputDeferred &) -> std::optional<StorePath> {
|
[](const DerivationOutput::Deferred &) -> std::optional<StorePath> {
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
},
|
},
|
||||||
}, output);
|
[](const DerivationOutput::Impure &) -> std::optional<StorePath> {
|
||||||
|
return std::nullopt;
|
||||||
|
},
|
||||||
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
StorePath DerivationOutputCAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const {
|
StorePath DerivationOutput::CAFixed::path(const Store & store, std::string_view drvName, std::string_view outputName) const
|
||||||
|
{
|
||||||
return store.makeFixedOutputPath(
|
return store.makeFixedOutputPath(
|
||||||
hash.method, hash.hash,
|
hash.method, hash.hash,
|
||||||
outputPathName(drvName, outputName));
|
outputPathName(drvName, outputName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool derivationIsCA(DerivationType dt) {
|
bool DerivationType::isCA() const
|
||||||
switch (dt) {
|
{
|
||||||
case DerivationType::InputAddressed: return false;
|
/* Normally we do the full `std::visit` to make sure we have
|
||||||
case DerivationType::CAFixed: return true;
|
exhaustively handled all variants, but so long as there is a
|
||||||
case DerivationType::CAFloating: return true;
|
variant called `ContentAddressed`, it must be the only one for
|
||||||
case DerivationType::DeferredInputAddressed: return false;
|
which `isCA` is true for this to make sense!. */
|
||||||
};
|
return std::visit(overloaded {
|
||||||
// Since enums can have non-variant values, but making a `default:` would
|
[](const InputAddressed & ia) {
|
||||||
// disable exhaustiveness warnings.
|
return false;
|
||||||
assert(false);
|
},
|
||||||
|
[](const ContentAddressed & ca) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool derivationIsFixed(DerivationType dt) {
|
bool DerivationType::isFixed() const
|
||||||
switch (dt) {
|
{
|
||||||
case DerivationType::InputAddressed: return false;
|
return std::visit(overloaded {
|
||||||
case DerivationType::CAFixed: return true;
|
[](const InputAddressed & ia) {
|
||||||
case DerivationType::CAFloating: return false;
|
return false;
|
||||||
case DerivationType::DeferredInputAddressed: return false;
|
},
|
||||||
};
|
[](const ContentAddressed & ca) {
|
||||||
assert(false);
|
return ca.fixed;
|
||||||
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool derivationHasKnownOutputPaths(DerivationType dt) {
|
bool DerivationType::hasKnownOutputPaths() const
|
||||||
switch (dt) {
|
{
|
||||||
case DerivationType::InputAddressed: return true;
|
return std::visit(overloaded {
|
||||||
case DerivationType::CAFixed: return true;
|
[](const InputAddressed & ia) {
|
||||||
case DerivationType::CAFloating: return false;
|
return !ia.deferred;
|
||||||
case DerivationType::DeferredInputAddressed: return false;
|
},
|
||||||
};
|
[](const ContentAddressed & ca) {
|
||||||
assert(false);
|
return ca.fixed;
|
||||||
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
bool derivationIsImpure(DerivationType dt) {
|
bool DerivationType::isSandboxed() const
|
||||||
switch (dt) {
|
{
|
||||||
case DerivationType::InputAddressed: return false;
|
return std::visit(overloaded {
|
||||||
case DerivationType::CAFixed: return true;
|
[](const InputAddressed & ia) {
|
||||||
case DerivationType::CAFloating: return false;
|
return true;
|
||||||
case DerivationType::DeferredInputAddressed: return false;
|
},
|
||||||
};
|
[](const ContentAddressed & ca) {
|
||||||
assert(false);
|
return ca.sandboxed;
|
||||||
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}, raw());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool DerivationType::isPure() const
|
||||||
|
{
|
||||||
|
return std::visit(overloaded {
|
||||||
|
[](const InputAddressed & ia) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[](const ContentAddressed & ca) {
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
[](const Impure &) {
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
}, raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -177,37 +219,36 @@ static DerivationOutput parseDerivationOutput(const Store & store,
|
||||||
hashAlgo = hashAlgo.substr(2);
|
hashAlgo = hashAlgo.substr(2);
|
||||||
}
|
}
|
||||||
const auto hashType = parseHashType(hashAlgo);
|
const auto hashType = parseHashType(hashAlgo);
|
||||||
if (hash != "") {
|
if (hash == "impure") {
|
||||||
|
settings.requireExperimentalFeature(Xp::ImpureDerivations);
|
||||||
|
assert(pathS == "");
|
||||||
|
return DerivationOutput::Impure {
|
||||||
|
.method = std::move(method),
|
||||||
|
.hashType = std::move(hashType),
|
||||||
|
};
|
||||||
|
} else if (hash != "") {
|
||||||
validatePath(pathS);
|
validatePath(pathS);
|
||||||
return DerivationOutput {
|
return DerivationOutput::CAFixed {
|
||||||
.output = DerivationOutputCAFixed {
|
.hash = FixedOutputHash {
|
||||||
.hash = FixedOutputHash {
|
.method = std::move(method),
|
||||||
.method = std::move(method),
|
.hash = Hash::parseNonSRIUnprefixed(hash, hashType),
|
||||||
.hash = Hash::parseNonSRIUnprefixed(hash, hashType),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
settings.requireExperimentalFeature(Xp::CaDerivations);
|
settings.requireExperimentalFeature(Xp::CaDerivations);
|
||||||
assert(pathS == "");
|
assert(pathS == "");
|
||||||
return DerivationOutput {
|
return DerivationOutput::CAFloating {
|
||||||
.output = DerivationOutputCAFloating {
|
.method = std::move(method),
|
||||||
.method = std::move(method),
|
.hashType = std::move(hashType),
|
||||||
.hashType = std::move(hashType),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (pathS == "") {
|
if (pathS == "") {
|
||||||
return DerivationOutput {
|
return DerivationOutput::Deferred { };
|
||||||
.output = DerivationOutputDeferred { }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
validatePath(pathS);
|
validatePath(pathS);
|
||||||
return DerivationOutput {
|
return DerivationOutput::InputAddressed {
|
||||||
.output = DerivationOutputInputAddressed {
|
.path = store.parseStorePath(pathS),
|
||||||
.path = store.parseStorePath(pathS),
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,27 +376,33 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs,
|
||||||
if (first) first = false; else s += ',';
|
if (first) first = false; else s += ',';
|
||||||
s += '('; printUnquotedString(s, i.first);
|
s += '('; printUnquotedString(s, i.first);
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const DerivationOutputInputAddressed & doi) {
|
[&](const DerivationOutput::InputAddressed & doi) {
|
||||||
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(doi.path));
|
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(doi.path));
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFixed & dof) {
|
[&](const DerivationOutput::CAFixed & dof) {
|
||||||
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first)));
|
s += ','; printUnquotedString(s, maskOutputs ? "" : store.printStorePath(dof.path(store, name, i.first)));
|
||||||
s += ','; printUnquotedString(s, dof.hash.printMethodAlgo());
|
s += ','; printUnquotedString(s, dof.hash.printMethodAlgo());
|
||||||
s += ','; printUnquotedString(s, dof.hash.hash.to_string(Base16, false));
|
s += ','; printUnquotedString(s, dof.hash.hash.to_string(Base16, false));
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFloating & dof) {
|
[&](const DerivationOutput::CAFloating & dof) {
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
s += ','; printUnquotedString(s, makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
|
s += ','; printUnquotedString(s, makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType));
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputDeferred &) {
|
[&](const DerivationOutput::Deferred &) {
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
s += ','; printUnquotedString(s, "");
|
s += ','; printUnquotedString(s, "");
|
||||||
|
},
|
||||||
|
[&](const DerivationOutputImpure & doi) {
|
||||||
|
// FIXME
|
||||||
|
s += ','; printUnquotedString(s, "");
|
||||||
|
s += ','; printUnquotedString(s, makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType));
|
||||||
|
s += ','; printUnquotedString(s, "impure");
|
||||||
}
|
}
|
||||||
}, i.second.output);
|
}, i.second.raw());
|
||||||
s += ')';
|
s += ')';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -419,49 +466,100 @@ std::string outputPathName(std::string_view drvName, std::string_view outputName
|
||||||
|
|
||||||
DerivationType BasicDerivation::type() const
|
DerivationType BasicDerivation::type() const
|
||||||
{
|
{
|
||||||
std::set<std::string_view> inputAddressedOutputs, fixedCAOutputs, floatingCAOutputs, deferredIAOutputs;
|
std::set<std::string_view>
|
||||||
|
inputAddressedOutputs,
|
||||||
|
fixedCAOutputs,
|
||||||
|
floatingCAOutputs,
|
||||||
|
deferredIAOutputs,
|
||||||
|
impureOutputs;
|
||||||
std::optional<HashType> floatingHashType;
|
std::optional<HashType> floatingHashType;
|
||||||
|
|
||||||
for (auto & i : outputs) {
|
for (auto & i : outputs) {
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const DerivationOutputInputAddressed &) {
|
[&](const DerivationOutput::InputAddressed &) {
|
||||||
inputAddressedOutputs.insert(i.first);
|
inputAddressedOutputs.insert(i.first);
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFixed &) {
|
[&](const DerivationOutput::CAFixed &) {
|
||||||
fixedCAOutputs.insert(i.first);
|
fixedCAOutputs.insert(i.first);
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFloating & dof) {
|
[&](const DerivationOutput::CAFloating & dof) {
|
||||||
floatingCAOutputs.insert(i.first);
|
floatingCAOutputs.insert(i.first);
|
||||||
if (!floatingHashType) {
|
if (!floatingHashType) {
|
||||||
floatingHashType = dof.hashType;
|
floatingHashType = dof.hashType;
|
||||||
} else {
|
} else {
|
||||||
if (*floatingHashType != dof.hashType)
|
if (*floatingHashType != dof.hashType)
|
||||||
throw Error("All floating outputs must use the same hash type");
|
throw Error("all floating outputs must use the same hash type");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputDeferred &) {
|
[&](const DerivationOutput::Deferred &) {
|
||||||
deferredIAOutputs.insert(i.first);
|
deferredIAOutputs.insert(i.first);
|
||||||
},
|
},
|
||||||
}, i.second.output);
|
[&](const DerivationOutput::Impure &) {
|
||||||
|
impureOutputs.insert(i.first);
|
||||||
|
},
|
||||||
|
}, i.second.raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
if (inputAddressedOutputs.empty()
|
||||||
throw Error("Must have at least one output");
|
&& fixedCAOutputs.empty()
|
||||||
} else if (! inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
&& floatingCAOutputs.empty()
|
||||||
return DerivationType::InputAddressed;
|
&& deferredIAOutputs.empty()
|
||||||
} else if (inputAddressedOutputs.empty() && ! fixedCAOutputs.empty() && floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
&& impureOutputs.empty())
|
||||||
|
throw Error("must have at least one output");
|
||||||
|
|
||||||
|
if (!inputAddressedOutputs.empty()
|
||||||
|
&& fixedCAOutputs.empty()
|
||||||
|
&& floatingCAOutputs.empty()
|
||||||
|
&& deferredIAOutputs.empty()
|
||||||
|
&& impureOutputs.empty())
|
||||||
|
return DerivationType::InputAddressed {
|
||||||
|
.deferred = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (inputAddressedOutputs.empty()
|
||||||
|
&& !fixedCAOutputs.empty()
|
||||||
|
&& floatingCAOutputs.empty()
|
||||||
|
&& deferredIAOutputs.empty()
|
||||||
|
&& impureOutputs.empty())
|
||||||
|
{
|
||||||
if (fixedCAOutputs.size() > 1)
|
if (fixedCAOutputs.size() > 1)
|
||||||
// FIXME: Experimental feature?
|
// FIXME: Experimental feature?
|
||||||
throw Error("Only one fixed output is allowed for now");
|
throw Error("only one fixed output is allowed for now");
|
||||||
if (*fixedCAOutputs.begin() != "out")
|
if (*fixedCAOutputs.begin() != "out")
|
||||||
throw Error("Single fixed output must be named \"out\"");
|
throw Error("single fixed output must be named \"out\"");
|
||||||
return DerivationType::CAFixed;
|
return DerivationType::ContentAddressed {
|
||||||
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && ! floatingCAOutputs.empty() && deferredIAOutputs.empty()) {
|
.sandboxed = false,
|
||||||
return DerivationType::CAFloating;
|
.fixed = true,
|
||||||
} else if (inputAddressedOutputs.empty() && fixedCAOutputs.empty() && floatingCAOutputs.empty() && !deferredIAOutputs.empty()) {
|
};
|
||||||
return DerivationType::DeferredInputAddressed;
|
|
||||||
} else {
|
|
||||||
throw Error("Can't mix derivation output types");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (inputAddressedOutputs.empty()
|
||||||
|
&& fixedCAOutputs.empty()
|
||||||
|
&& !floatingCAOutputs.empty()
|
||||||
|
&& deferredIAOutputs.empty()
|
||||||
|
&& impureOutputs.empty())
|
||||||
|
return DerivationType::ContentAddressed {
|
||||||
|
.sandboxed = true,
|
||||||
|
.fixed = false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (inputAddressedOutputs.empty()
|
||||||
|
&& fixedCAOutputs.empty()
|
||||||
|
&& floatingCAOutputs.empty()
|
||||||
|
&& !deferredIAOutputs.empty()
|
||||||
|
&& impureOutputs.empty())
|
||||||
|
return DerivationType::InputAddressed {
|
||||||
|
.deferred = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (inputAddressedOutputs.empty()
|
||||||
|
&& fixedCAOutputs.empty()
|
||||||
|
&& floatingCAOutputs.empty()
|
||||||
|
&& deferredIAOutputs.empty()
|
||||||
|
&& !impureOutputs.empty())
|
||||||
|
return DerivationType::Impure { };
|
||||||
|
|
||||||
|
throw Error("can't mix derivation output types");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -473,7 +571,7 @@ Sync<DrvHashes> drvHashes;
|
||||||
/* Look up the derivation by value and memoize the
|
/* Look up the derivation by value and memoize the
|
||||||
`hashDerivationModulo` call.
|
`hashDerivationModulo` call.
|
||||||
*/
|
*/
|
||||||
static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath & drvPath)
|
static const DrvHash pathDerivationModulo(Store & store, const StorePath & drvPath)
|
||||||
{
|
{
|
||||||
{
|
{
|
||||||
auto hashes = drvHashes.lock();
|
auto hashes = drvHashes.lock();
|
||||||
|
@ -508,88 +606,83 @@ static const DrvHashModulo pathDerivationModulo(Store & store, const StorePath &
|
||||||
don't leak the provenance of fixed outputs, reducing pointless cache
|
don't leak the provenance of fixed outputs, reducing pointless cache
|
||||||
misses as the build itself won't know this.
|
misses as the build itself won't know this.
|
||||||
*/
|
*/
|
||||||
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
|
DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs)
|
||||||
{
|
{
|
||||||
bool isDeferred = false;
|
auto type = drv.type();
|
||||||
|
|
||||||
/* Return a fixed hash for fixed-output derivations. */
|
/* Return a fixed hash for fixed-output derivations. */
|
||||||
switch (drv.type()) {
|
if (type.isFixed()) {
|
||||||
case DerivationType::CAFixed: {
|
|
||||||
std::map<std::string, Hash> outputHashes;
|
std::map<std::string, Hash> outputHashes;
|
||||||
for (const auto & i : drv.outputs) {
|
for (const auto & i : drv.outputs) {
|
||||||
auto & dof = std::get<DerivationOutputCAFixed>(i.second.output);
|
auto & dof = std::get<DerivationOutput::CAFixed>(i.second.raw());
|
||||||
auto hash = hashString(htSHA256, "fixed:out:"
|
auto hash = hashString(htSHA256, "fixed:out:"
|
||||||
+ dof.hash.printMethodAlgo() + ":"
|
+ dof.hash.printMethodAlgo() + ":"
|
||||||
+ dof.hash.hash.to_string(Base16, false) + ":"
|
+ dof.hash.hash.to_string(Base16, false) + ":"
|
||||||
+ store.printStorePath(dof.path(store, drv.name, i.first)));
|
+ store.printStorePath(dof.path(store, drv.name, i.first)));
|
||||||
outputHashes.insert_or_assign(i.first, std::move(hash));
|
outputHashes.insert_or_assign(i.first, std::move(hash));
|
||||||
}
|
}
|
||||||
return outputHashes;
|
return DrvHash {
|
||||||
}
|
.hashes = outputHashes,
|
||||||
case DerivationType::CAFloating:
|
.kind = DrvHash::Kind::Regular,
|
||||||
isDeferred = true;
|
};
|
||||||
break;
|
|
||||||
case DerivationType::InputAddressed:
|
|
||||||
break;
|
|
||||||
case DerivationType::DeferredInputAddressed:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* For other derivations, replace the inputs paths with recursive
|
if (!type.isPure()) {
|
||||||
calls to this function. */
|
std::map<std::string, Hash> outputHashes;
|
||||||
|
for (const auto & [outputName, _] : drv.outputs)
|
||||||
|
outputHashes.insert_or_assign(outputName, impureOutputHash);
|
||||||
|
return DrvHash {
|
||||||
|
.hashes = outputHashes,
|
||||||
|
.kind = DrvHash::Kind::Deferred,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto kind = std::visit(overloaded {
|
||||||
|
[](const DerivationType::InputAddressed & ia) {
|
||||||
|
/* This might be a "pesimistically" deferred output, so we don't
|
||||||
|
"taint" the kind yet. */
|
||||||
|
return DrvHash::Kind::Regular;
|
||||||
|
},
|
||||||
|
[](const DerivationType::ContentAddressed & ca) {
|
||||||
|
return ca.fixed
|
||||||
|
? DrvHash::Kind::Regular
|
||||||
|
: DrvHash::Kind::Deferred;
|
||||||
|
},
|
||||||
|
[](const DerivationType::Impure &) -> DrvHash::Kind {
|
||||||
|
assert(false);
|
||||||
|
}
|
||||||
|
}, drv.type().raw());
|
||||||
|
|
||||||
std::map<std::string, StringSet> inputs2;
|
std::map<std::string, StringSet> inputs2;
|
||||||
for (auto & i : drv.inputDrvs) {
|
for (auto & [drvPath, inputOutputs0] : drv.inputDrvs) {
|
||||||
const auto & res = pathDerivationModulo(store, i.first);
|
// Avoid lambda capture restriction with standard / Clang
|
||||||
std::visit(overloaded {
|
auto & inputOutputs = inputOutputs0;
|
||||||
// Regular non-CA derivation, replace derivation
|
const auto & res = pathDerivationModulo(store, drvPath);
|
||||||
[&](const Hash & drvHash) {
|
if (res.kind == DrvHash::Kind::Deferred)
|
||||||
inputs2.insert_or_assign(drvHash.to_string(Base16, false), i.second);
|
kind = DrvHash::Kind::Deferred;
|
||||||
},
|
for (auto & outputName : inputOutputs) {
|
||||||
[&](const DeferredHash & deferredHash) {
|
const auto h = res.hashes.at(outputName);
|
||||||
isDeferred = true;
|
inputs2[h.to_string(Base16, false)].insert(outputName);
|
||||||
inputs2.insert_or_assign(deferredHash.hash.to_string(Base16, false), i.second);
|
}
|
||||||
},
|
|
||||||
// CA derivation's output hashes
|
|
||||||
[&](const CaOutputHashes & outputHashes) {
|
|
||||||
std::set<std::string> justOut = { "out" };
|
|
||||||
for (auto & output : i.second) {
|
|
||||||
/* Put each one in with a single "out" output.. */
|
|
||||||
const auto h = outputHashes.at(output);
|
|
||||||
inputs2.insert_or_assign(
|
|
||||||
h.to_string(Base16, false),
|
|
||||||
justOut);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
|
auto hash = hashString(htSHA256, drv.unparse(store, maskOutputs, &inputs2));
|
||||||
|
|
||||||
if (isDeferred)
|
std::map<std::string, Hash> outputHashes;
|
||||||
return DeferredHash { hash };
|
for (const auto & [outputName, _] : drv.outputs) {
|
||||||
else
|
outputHashes.insert_or_assign(outputName, hash);
|
||||||
return hash;
|
}
|
||||||
|
|
||||||
|
return DrvHash {
|
||||||
|
.hashes = outputHashes,
|
||||||
|
.kind = kind,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation & drv)
|
std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation & drv)
|
||||||
{
|
{
|
||||||
std::map<std::string, Hash> res;
|
return hashDerivationModulo(store, drv, true).hashes;
|
||||||
std::visit(overloaded {
|
|
||||||
[&](const Hash & drvHash) {
|
|
||||||
for (auto & outputName : drv.outputNames()) {
|
|
||||||
res.insert({outputName, drvHash});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[&](const DeferredHash & deferredHash) {
|
|
||||||
for (auto & outputName : drv.outputNames()) {
|
|
||||||
res.insert({outputName, deferredHash.hash});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[&](const CaOutputHashes & outputHashes) {
|
|
||||||
res = outputHashes;
|
|
||||||
},
|
|
||||||
}, hashDerivationModulo(store, drv, true));
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -616,7 +709,8 @@ StringSet BasicDerivation::outputNames() const
|
||||||
return names;
|
return names;
|
||||||
}
|
}
|
||||||
|
|
||||||
DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & store) const {
|
DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & store) const
|
||||||
|
{
|
||||||
DerivationOutputsAndOptPaths outsAndOptPaths;
|
DerivationOutputsAndOptPaths outsAndOptPaths;
|
||||||
for (auto output : outputs)
|
for (auto output : outputs)
|
||||||
outsAndOptPaths.insert(std::make_pair(
|
outsAndOptPaths.insert(std::make_pair(
|
||||||
|
@ -627,7 +721,8 @@ DerivationOutputsAndOptPaths BasicDerivation::outputsAndOptPaths(const Store & s
|
||||||
return outsAndOptPaths;
|
return outsAndOptPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string_view BasicDerivation::nameFromPath(const StorePath & drvPath) {
|
std::string_view BasicDerivation::nameFromPath(const StorePath & drvPath)
|
||||||
|
{
|
||||||
auto nameWithSuffix = drvPath.name();
|
auto nameWithSuffix = drvPath.name();
|
||||||
constexpr std::string_view extension = ".drv";
|
constexpr std::string_view extension = ".drv";
|
||||||
assert(hasSuffix(nameWithSuffix, extension));
|
assert(hasSuffix(nameWithSuffix, extension));
|
||||||
|
@ -669,27 +764,32 @@ void writeDerivation(Sink & out, const Store & store, const BasicDerivation & dr
|
||||||
for (auto & i : drv.outputs) {
|
for (auto & i : drv.outputs) {
|
||||||
out << i.first;
|
out << i.first;
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const DerivationOutputInputAddressed & doi) {
|
[&](const DerivationOutput::InputAddressed & doi) {
|
||||||
out << store.printStorePath(doi.path)
|
out << store.printStorePath(doi.path)
|
||||||
<< ""
|
<< ""
|
||||||
<< "";
|
<< "";
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFixed & dof) {
|
[&](const DerivationOutput::CAFixed & dof) {
|
||||||
out << store.printStorePath(dof.path(store, drv.name, i.first))
|
out << store.printStorePath(dof.path(store, drv.name, i.first))
|
||||||
<< dof.hash.printMethodAlgo()
|
<< dof.hash.printMethodAlgo()
|
||||||
<< dof.hash.hash.to_string(Base16, false);
|
<< dof.hash.hash.to_string(Base16, false);
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFloating & dof) {
|
[&](const DerivationOutput::CAFloating & dof) {
|
||||||
out << ""
|
out << ""
|
||||||
<< (makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType))
|
<< (makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType))
|
||||||
<< "";
|
<< "";
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputDeferred &) {
|
[&](const DerivationOutput::Deferred &) {
|
||||||
out << ""
|
out << ""
|
||||||
<< ""
|
<< ""
|
||||||
<< "";
|
<< "";
|
||||||
},
|
},
|
||||||
}, i.second.output);
|
[&](const DerivationOutput::Impure & doi) {
|
||||||
|
out << ""
|
||||||
|
<< (makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType))
|
||||||
|
<< "impure";
|
||||||
|
},
|
||||||
|
}, i.second.raw());
|
||||||
}
|
}
|
||||||
worker_proto::write(store, out, drv.inputSrcs);
|
worker_proto::write(store, out, drv.inputSrcs);
|
||||||
out << drv.platform << drv.builder << drv.args;
|
out << drv.platform << drv.builder << drv.args;
|
||||||
|
@ -714,21 +814,19 @@ std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites) {
|
static void rewriteDerivation(Store & store, BasicDerivation & drv, const StringMap & rewrites)
|
||||||
|
{
|
||||||
debug("Rewriting the derivation");
|
for (auto & rewrite : rewrites) {
|
||||||
|
|
||||||
for (auto &rewrite: rewrites) {
|
|
||||||
debug("rewriting %s as %s", rewrite.first, rewrite.second);
|
debug("rewriting %s as %s", rewrite.first, rewrite.second);
|
||||||
}
|
}
|
||||||
|
|
||||||
drv.builder = rewriteStrings(drv.builder, rewrites);
|
drv.builder = rewriteStrings(drv.builder, rewrites);
|
||||||
for (auto & arg: drv.args) {
|
for (auto & arg : drv.args) {
|
||||||
arg = rewriteStrings(arg, rewrites);
|
arg = rewriteStrings(arg, rewrites);
|
||||||
}
|
}
|
||||||
|
|
||||||
StringPairs newEnv;
|
StringPairs newEnv;
|
||||||
for (auto & envVar: drv.env) {
|
for (auto & envVar : drv.env) {
|
||||||
auto envName = rewriteStrings(envVar.first, rewrites);
|
auto envName = rewriteStrings(envVar.first, rewrites);
|
||||||
auto envValue = rewriteStrings(envVar.second, rewrites);
|
auto envValue = rewriteStrings(envVar.second, rewrites);
|
||||||
newEnv.emplace(envName, envValue);
|
newEnv.emplace(envName, envValue);
|
||||||
|
@ -737,43 +835,52 @@ static void rewriteDerivation(Store & store, BasicDerivation & drv, const String
|
||||||
|
|
||||||
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
|
auto hashModulo = hashDerivationModulo(store, Derivation(drv), true);
|
||||||
for (auto & [outputName, output] : drv.outputs) {
|
for (auto & [outputName, output] : drv.outputs) {
|
||||||
if (std::holds_alternative<DerivationOutputDeferred>(output.output)) {
|
if (std::holds_alternative<DerivationOutput::Deferred>(output.raw())) {
|
||||||
Hash h = std::get<Hash>(hashModulo);
|
auto & h = hashModulo.hashes.at(outputName);
|
||||||
auto outPath = store.makeOutputPath(outputName, h, drv.name);
|
auto outPath = store.makeOutputPath(outputName, h, drv.name);
|
||||||
drv.env[outputName] = store.printStorePath(outPath);
|
drv.env[outputName] = store.printStorePath(outPath);
|
||||||
output = DerivationOutput {
|
output = DerivationOutput::InputAddressed {
|
||||||
.output = DerivationOutputInputAddressed {
|
.path = std::move(outPath),
|
||||||
.path = std::move(outPath),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
|
std::optional<BasicDerivation> Derivation::tryResolve(Store & store) const
|
||||||
|
{
|
||||||
|
std::map<std::pair<StorePath, std::string>, StorePath> inputDrvOutputs;
|
||||||
|
|
||||||
|
for (auto & input : inputDrvs)
|
||||||
|
for (auto & [outputName, outputPath] : store.queryPartialDerivationOutputMap(input.first))
|
||||||
|
if (outputPath)
|
||||||
|
inputDrvOutputs.insert_or_assign({input.first, outputName}, *outputPath);
|
||||||
|
|
||||||
|
return tryResolve(store, inputDrvOutputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<BasicDerivation> Derivation::tryResolve(
|
||||||
|
Store & store,
|
||||||
|
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const
|
||||||
|
{
|
||||||
BasicDerivation resolved { *this };
|
BasicDerivation resolved { *this };
|
||||||
|
|
||||||
// Input paths that we'll want to rewrite in the derivation
|
// Input paths that we'll want to rewrite in the derivation
|
||||||
StringMap inputRewrites;
|
StringMap inputRewrites;
|
||||||
|
|
||||||
for (auto & input : inputDrvs) {
|
for (auto & [inputDrv, inputOutputs] : inputDrvs) {
|
||||||
auto inputDrvOutputs = store.queryPartialDerivationOutputMap(input.first);
|
for (auto & outputName : inputOutputs) {
|
||||||
StringSet newOutputNames;
|
if (auto actualPath = get(inputDrvOutputs, { inputDrv, outputName })) {
|
||||||
for (auto & outputName : input.second) {
|
inputRewrites.emplace(
|
||||||
auto actualPathOpt = inputDrvOutputs.at(outputName);
|
downstreamPlaceholder(store, inputDrv, outputName),
|
||||||
if (!actualPathOpt) {
|
store.printStorePath(*actualPath));
|
||||||
warn("output %s of input %s missing, aborting the resolving",
|
resolved.inputSrcs.insert(*actualPath);
|
||||||
|
} else {
|
||||||
|
warn("output '%s' of input '%s' missing, aborting the resolving",
|
||||||
outputName,
|
outputName,
|
||||||
store.printStorePath(input.first)
|
store.printStorePath(inputDrv));
|
||||||
);
|
return {};
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
auto actualPath = *actualPathOpt;
|
|
||||||
inputRewrites.emplace(
|
|
||||||
downstreamPlaceholder(store, input.first, outputName),
|
|
||||||
store.printStorePath(actualPath));
|
|
||||||
resolved.inputSrcs.insert(std::move(actualPath));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,4 +889,6 @@ std::optional<BasicDerivation> Derivation::tryResolve(Store & store) {
|
||||||
return resolved;
|
return resolved;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const Hash impureOutputHash = hashString(htSHA256, "impure");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include "types.hh"
|
#include "types.hh"
|
||||||
#include "hash.hh"
|
#include "hash.hh"
|
||||||
#include "content-address.hh"
|
#include "content-address.hh"
|
||||||
|
#include "repair-flag.hh"
|
||||||
#include "sync.hh"
|
#include "sync.hh"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
@ -40,23 +41,47 @@ struct DerivationOutputCAFloating
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Input-addressed output which depends on a (CA) derivation whose hash isn't
|
/* Input-addressed output which depends on a (CA) derivation whose hash isn't
|
||||||
* known atm
|
* known yet.
|
||||||
*/
|
*/
|
||||||
struct DerivationOutputDeferred {};
|
struct DerivationOutputDeferred {};
|
||||||
|
|
||||||
struct DerivationOutput
|
/* Impure output which is moved to a content-addressed location (like
|
||||||
|
CAFloating) but isn't registered as a realization.
|
||||||
|
*/
|
||||||
|
struct DerivationOutputImpure
|
||||||
{
|
{
|
||||||
std::variant<
|
/* information used for expected hash computation */
|
||||||
DerivationOutputInputAddressed,
|
FileIngestionMethod method;
|
||||||
DerivationOutputCAFixed,
|
HashType hashType;
|
||||||
DerivationOutputCAFloating,
|
};
|
||||||
DerivationOutputDeferred
|
|
||||||
> output;
|
typedef std::variant<
|
||||||
|
DerivationOutputInputAddressed,
|
||||||
|
DerivationOutputCAFixed,
|
||||||
|
DerivationOutputCAFloating,
|
||||||
|
DerivationOutputDeferred,
|
||||||
|
DerivationOutputImpure
|
||||||
|
> _DerivationOutputRaw;
|
||||||
|
|
||||||
|
struct DerivationOutput : _DerivationOutputRaw
|
||||||
|
{
|
||||||
|
using Raw = _DerivationOutputRaw;
|
||||||
|
using Raw::Raw;
|
||||||
|
|
||||||
|
using InputAddressed = DerivationOutputInputAddressed;
|
||||||
|
using CAFixed = DerivationOutputCAFixed;
|
||||||
|
using CAFloating = DerivationOutputCAFloating;
|
||||||
|
using Deferred = DerivationOutputDeferred;
|
||||||
|
using Impure = DerivationOutputImpure;
|
||||||
|
|
||||||
/* Note, when you use this function you should make sure that you're passing
|
/* Note, when you use this function you should make sure that you're passing
|
||||||
the right derivation name. When in doubt, you should use the safer
|
the right derivation name. When in doubt, you should use the safer
|
||||||
interface provided by BasicDerivation::outputsAndOptPaths */
|
interface provided by BasicDerivation::outputsAndOptPaths */
|
||||||
std::optional<StorePath> path(const Store & store, std::string_view drvName, std::string_view outputName) const;
|
std::optional<StorePath> path(const Store & store, std::string_view drvName, std::string_view outputName) const;
|
||||||
|
|
||||||
|
inline const Raw & raw() const {
|
||||||
|
return static_cast<const Raw &>(*this);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::map<std::string, DerivationOutput> DerivationOutputs;
|
typedef std::map<std::string, DerivationOutput> DerivationOutputs;
|
||||||
|
@ -72,30 +97,62 @@ typedef std::map<std::string, std::pair<DerivationOutput, std::optional<StorePat
|
||||||
output IDs we are interested in. */
|
output IDs we are interested in. */
|
||||||
typedef std::map<StorePath, StringSet> DerivationInputs;
|
typedef std::map<StorePath, StringSet> DerivationInputs;
|
||||||
|
|
||||||
enum struct DerivationType : uint8_t {
|
struct DerivationType_InputAddressed {
|
||||||
InputAddressed,
|
bool deferred;
|
||||||
DeferredInputAddressed,
|
|
||||||
CAFixed,
|
|
||||||
CAFloating,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Do the outputs of the derivation have paths calculated from their content,
|
struct DerivationType_ContentAddressed {
|
||||||
or from the derivation itself? */
|
bool sandboxed;
|
||||||
bool derivationIsCA(DerivationType);
|
bool fixed;
|
||||||
|
};
|
||||||
|
|
||||||
/* Is the content of the outputs fixed a-priori via a hash? Never true for
|
struct DerivationType_Impure {
|
||||||
non-CA derivations. */
|
};
|
||||||
bool derivationIsFixed(DerivationType);
|
|
||||||
|
|
||||||
/* Is the derivation impure and needs to access non-deterministic resources, or
|
typedef std::variant<
|
||||||
pure and can be sandboxed? Note that whether or not we actually sandbox the
|
DerivationType_InputAddressed,
|
||||||
derivation is controlled separately. Never true for non-CA derivations. */
|
DerivationType_ContentAddressed,
|
||||||
bool derivationIsImpure(DerivationType);
|
DerivationType_Impure
|
||||||
|
> _DerivationTypeRaw;
|
||||||
|
|
||||||
/* Does the derivation knows its own output paths?
|
struct DerivationType : _DerivationTypeRaw {
|
||||||
* Only true when there's no floating-ca derivation involved in the closure.
|
using Raw = _DerivationTypeRaw;
|
||||||
*/
|
using Raw::Raw;
|
||||||
bool derivationHasKnownOutputPaths(DerivationType);
|
using InputAddressed = DerivationType_InputAddressed;
|
||||||
|
using ContentAddressed = DerivationType_ContentAddressed;
|
||||||
|
using Impure = DerivationType_Impure;
|
||||||
|
|
||||||
|
/* Do the outputs of the derivation have paths calculated from their content,
|
||||||
|
or from the derivation itself? */
|
||||||
|
bool isCA() const;
|
||||||
|
|
||||||
|
/* Is the content of the outputs fixed a-priori via a hash? Never true for
|
||||||
|
non-CA derivations. */
|
||||||
|
bool isFixed() const;
|
||||||
|
|
||||||
|
/* Whether the derivation is fully sandboxed. If false, the
|
||||||
|
sandbox is opened up, e.g. the derivation has access to the
|
||||||
|
network. Note that whether or not we actually sandbox the
|
||||||
|
derivation is controlled separately. Always true for non-CA
|
||||||
|
derivations. */
|
||||||
|
bool isSandboxed() const;
|
||||||
|
|
||||||
|
/* Whether the derivation is expected to produce the same result
|
||||||
|
every time, and therefore it only needs to be built once. This
|
||||||
|
is only false for derivations that have the attribute '__impure
|
||||||
|
= true'. */
|
||||||
|
bool isPure() const;
|
||||||
|
|
||||||
|
/* Does the derivation knows its own output paths?
|
||||||
|
Only true when there's no floating-ca derivation involved in the
|
||||||
|
closure, or if fixed output.
|
||||||
|
*/
|
||||||
|
bool hasKnownOutputPaths() const;
|
||||||
|
|
||||||
|
inline const Raw & raw() const {
|
||||||
|
return static_cast<const Raw &>(*this);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct BasicDerivation
|
struct BasicDerivation
|
||||||
{
|
{
|
||||||
|
@ -140,7 +197,14 @@ struct Derivation : BasicDerivation
|
||||||
added directly to input sources.
|
added directly to input sources.
|
||||||
|
|
||||||
2. Input placeholders are replaced with realized input store paths. */
|
2. Input placeholders are replaced with realized input store paths. */
|
||||||
std::optional<BasicDerivation> tryResolve(Store & store);
|
std::optional<BasicDerivation> tryResolve(Store & store) const;
|
||||||
|
|
||||||
|
/* Like the above, but instead of querying the Nix database for
|
||||||
|
realisations, uses a given mapping from input derivation paths
|
||||||
|
+ output names to actual output store paths. */
|
||||||
|
std::optional<BasicDerivation> tryResolve(
|
||||||
|
Store & store,
|
||||||
|
const std::map<std::pair<StorePath, std::string>, StorePath> & inputDrvOutputs) const;
|
||||||
|
|
||||||
Derivation() = default;
|
Derivation() = default;
|
||||||
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
|
Derivation(const BasicDerivation & bd) : BasicDerivation(bd) { }
|
||||||
|
@ -150,8 +214,6 @@ struct Derivation : BasicDerivation
|
||||||
|
|
||||||
class Store;
|
class Store;
|
||||||
|
|
||||||
enum RepairFlag : bool { NoRepair = false, Repair = true };
|
|
||||||
|
|
||||||
/* Write a derivation to the Nix store, and return its path. */
|
/* Write a derivation to the Nix store, and return its path. */
|
||||||
StorePath writeDerivation(Store & store,
|
StorePath writeDerivation(Store & store,
|
||||||
const Derivation & drv,
|
const Derivation & drv,
|
||||||
|
@ -171,17 +233,27 @@ bool isDerivation(const std::string & fileName);
|
||||||
the output name is "out". */
|
the output name is "out". */
|
||||||
std::string outputPathName(std::string_view drvName, std::string_view outputName);
|
std::string outputPathName(std::string_view drvName, std::string_view outputName);
|
||||||
|
|
||||||
// known CA drv's output hashes, current just for fixed-output derivations
|
|
||||||
// whose output hashes are always known since they are fixed up-front.
|
|
||||||
typedef std::map<std::string, Hash> CaOutputHashes;
|
|
||||||
|
|
||||||
struct DeferredHash { Hash hash; };
|
// The hashes modulo of a derivation.
|
||||||
|
//
|
||||||
|
// Each output is given a hash, although in practice only the content-addressed
|
||||||
|
// derivations (fixed-output or not) will have a different hash for each
|
||||||
|
// output.
|
||||||
|
struct DrvHash {
|
||||||
|
std::map<std::string, Hash> hashes;
|
||||||
|
|
||||||
typedef std::variant<
|
enum struct Kind : bool {
|
||||||
Hash, // regular DRV normalized hash
|
// Statically determined derivations.
|
||||||
CaOutputHashes, // Fixed-output derivation hashes
|
// This hash will be directly used to compute the output paths
|
||||||
DeferredHash // Deferred hashes for floating outputs drvs and their dependencies
|
Regular,
|
||||||
> DrvHashModulo;
|
// Floating-output derivations (and their reverse dependencies).
|
||||||
|
Deferred,
|
||||||
|
};
|
||||||
|
|
||||||
|
Kind kind;
|
||||||
|
};
|
||||||
|
|
||||||
|
void operator |= (DrvHash::Kind & self, const DrvHash::Kind & other) noexcept;
|
||||||
|
|
||||||
/* Returns hashes with the details of fixed-output subderivations
|
/* Returns hashes with the details of fixed-output subderivations
|
||||||
expunged.
|
expunged.
|
||||||
|
@ -206,16 +278,18 @@ typedef std::variant<
|
||||||
ATerm, after subderivations have been likewise expunged from that
|
ATerm, after subderivations have been likewise expunged from that
|
||||||
derivation.
|
derivation.
|
||||||
*/
|
*/
|
||||||
DrvHashModulo hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs);
|
DrvHash hashDerivationModulo(Store & store, const Derivation & drv, bool maskOutputs);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Return a map associating each output to a hash that uniquely identifies its
|
Return a map associating each output to a hash that uniquely identifies its
|
||||||
derivation (modulo the self-references).
|
derivation (modulo the self-references).
|
||||||
|
|
||||||
|
FIXME: what is the Hash in this map?
|
||||||
*/
|
*/
|
||||||
std::map<std::string, Hash> staticOutputHashes(Store& store, const Derivation& drv);
|
std::map<std::string, Hash> staticOutputHashes(Store & store, const Derivation & drv);
|
||||||
|
|
||||||
/* Memoisation of hashDerivationModulo(). */
|
/* Memoisation of hashDerivationModulo(). */
|
||||||
typedef std::map<StorePath, DrvHashModulo> DrvHashes;
|
typedef std::map<StorePath, DrvHash> DrvHashes;
|
||||||
|
|
||||||
// FIXME: global, though at least thread-safe.
|
// FIXME: global, though at least thread-safe.
|
||||||
extern Sync<DrvHashes> drvHashes;
|
extern Sync<DrvHashes> drvHashes;
|
||||||
|
@ -245,4 +319,6 @@ std::string hashPlaceholder(const std::string_view outputName);
|
||||||
dependency which is a CA derivation. */
|
dependency which is a CA derivation. */
|
||||||
std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath, std::string_view outputName);
|
std::string downstreamPlaceholder(const Store & store, const StorePath & drvPath, std::string_view outputName);
|
||||||
|
|
||||||
|
extern const Hash impureOutputHash;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "derived-path.hh"
|
#include "derived-path.hh"
|
||||||
|
#include "derivations.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
@ -11,6 +12,21 @@ nlohmann::json DerivedPath::Opaque::toJSON(ref<Store> store) const {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nlohmann::json DerivedPath::Built::toJSON(ref<Store> store) const {
|
||||||
|
nlohmann::json res;
|
||||||
|
res["drvPath"] = store->printStorePath(drvPath);
|
||||||
|
// Fallback for the input-addressed derivation case: We expect to always be
|
||||||
|
// able to print the output paths, so let’s do it
|
||||||
|
auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath);
|
||||||
|
for (const auto& output : outputs) {
|
||||||
|
if (knownOutputs.at(output))
|
||||||
|
res["outputs"][output] = store->printStorePath(knownOutputs.at(output).value());
|
||||||
|
else
|
||||||
|
res["outputs"][output] = nullptr;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
nlohmann::json BuiltPath::Built::toJSON(ref<Store> store) const {
|
nlohmann::json BuiltPath::Built::toJSON(ref<Store> store) const {
|
||||||
nlohmann::json res;
|
nlohmann::json res;
|
||||||
res["drvPath"] = store->printStorePath(drvPath);
|
res["drvPath"] = store->printStorePath(drvPath);
|
||||||
|
@ -35,16 +51,22 @@ StorePathSet BuiltPath::outPaths() const
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store) {
|
template<typename T>
|
||||||
|
nlohmann::json stuffToJSON(const std::vector<T> & ts, ref<Store> store) {
|
||||||
auto res = nlohmann::json::array();
|
auto res = nlohmann::json::array();
|
||||||
for (const BuiltPath & buildable : buildables) {
|
for (const T & t : ts) {
|
||||||
std::visit([&res, store](const auto & buildable) {
|
std::visit([&res, store](const auto & t) {
|
||||||
res.push_back(buildable.toJSON(store));
|
res.push_back(t.toJSON(store));
|
||||||
}, buildable.raw());
|
}, t.raw());
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store)
|
||||||
|
{ return stuffToJSON<BuiltPath>(buildables, store); }
|
||||||
|
nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref<Store> store)
|
||||||
|
{ return stuffToJSON<DerivedPath>(paths, store); }
|
||||||
|
|
||||||
|
|
||||||
std::string DerivedPath::Opaque::to_string(const Store & store) const {
|
std::string DerivedPath::Opaque::to_string(const Store & store) const {
|
||||||
return store.printStorePath(path);
|
return store.printStorePath(path);
|
||||||
|
|
|
@ -25,6 +25,9 @@ struct DerivedPathOpaque {
|
||||||
nlohmann::json toJSON(ref<Store> store) const;
|
nlohmann::json toJSON(ref<Store> store) const;
|
||||||
std::string to_string(const Store & store) const;
|
std::string to_string(const Store & store) const;
|
||||||
static DerivedPathOpaque parse(const Store & store, std::string_view);
|
static DerivedPathOpaque parse(const Store & store, std::string_view);
|
||||||
|
|
||||||
|
bool operator < (const DerivedPathOpaque & b) const
|
||||||
|
{ return path < b.path; }
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -45,6 +48,10 @@ struct DerivedPathBuilt {
|
||||||
|
|
||||||
std::string to_string(const Store & store) const;
|
std::string to_string(const Store & store) const;
|
||||||
static DerivedPathBuilt parse(const Store & store, std::string_view);
|
static DerivedPathBuilt parse(const Store & store, std::string_view);
|
||||||
|
nlohmann::json toJSON(ref<Store> store) const;
|
||||||
|
|
||||||
|
bool operator < (const DerivedPathBuilt & b) const
|
||||||
|
{ return std::make_pair(drvPath, outputs) < std::make_pair(b.drvPath, b.outputs); }
|
||||||
};
|
};
|
||||||
|
|
||||||
using _DerivedPathRaw = std::variant<
|
using _DerivedPathRaw = std::variant<
|
||||||
|
@ -119,5 +126,6 @@ typedef std::vector<DerivedPath> DerivedPaths;
|
||||||
typedef std::vector<BuiltPath> BuiltPaths;
|
typedef std::vector<BuiltPath> BuiltPaths;
|
||||||
|
|
||||||
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store);
|
nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref<Store> store);
|
||||||
|
nlohmann::json derivedPathsToJSON(const DerivedPaths & , ref<Store> store);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -443,14 +443,13 @@ struct curlFileTransfer : public FileTransfer
|
||||||
: httpStatus != 0
|
: httpStatus != 0
|
||||||
? FileTransferError(err,
|
? FileTransferError(err,
|
||||||
std::move(response),
|
std::move(response),
|
||||||
fmt("unable to %s '%s': HTTP error %d ('%s')",
|
"unable to %s '%s': HTTP error %d%s",
|
||||||
request.verb(), request.uri, httpStatus, statusMsg)
|
request.verb(), request.uri, httpStatus,
|
||||||
+ (code == CURLE_OK ? "" : fmt(" (curl error: %s)", curl_easy_strerror(code)))
|
code == CURLE_OK ? "" : fmt(" (curl error: %s)", curl_easy_strerror(code)))
|
||||||
)
|
|
||||||
: FileTransferError(err,
|
: FileTransferError(err,
|
||||||
std::move(response),
|
std::move(response),
|
||||||
fmt("unable to %s '%s': %s (%d)",
|
"unable to %s '%s': %s (%d)",
|
||||||
request.verb(), request.uri, curl_easy_strerror(code), code));
|
request.verb(), request.uri, curl_easy_strerror(code), code);
|
||||||
|
|
||||||
/* If this is a transient error, then maybe retry the
|
/* If this is a transient error, then maybe retry the
|
||||||
download after a while. If we're writing to a
|
download after a while. If we're writing to a
|
||||||
|
@ -704,7 +703,7 @@ struct curlFileTransfer : public FileTransfer
|
||||||
auto s3Res = s3Helper.getObject(bucketName, key);
|
auto s3Res = s3Helper.getObject(bucketName, key);
|
||||||
FileTransferResult res;
|
FileTransferResult res;
|
||||||
if (!s3Res.data)
|
if (!s3Res.data)
|
||||||
throw FileTransferError(NotFound, {}, "S3 object '%s' does not exist", request.uri);
|
throw FileTransferError(NotFound, "S3 object '%s' does not exist", request.uri);
|
||||||
res.data = std::move(*s3Res.data);
|
res.data = std::move(*s3Res.data);
|
||||||
callback(std::move(res));
|
callback(std::move(res));
|
||||||
#else
|
#else
|
||||||
|
|
|
@ -31,7 +31,7 @@ struct FileTransferSettings : Config
|
||||||
R"(
|
R"(
|
||||||
The timeout (in seconds) for establishing connections in the
|
The timeout (in seconds) for establishing connections in the
|
||||||
binary cache substituter. It corresponds to `curl`’s
|
binary cache substituter. It corresponds to `curl`’s
|
||||||
`--connect-timeout` option.
|
`--connect-timeout` option. A value of 0 means no limit.
|
||||||
)"};
|
)"};
|
||||||
|
|
||||||
Setting<unsigned long> stalledDownloadTimeout{
|
Setting<unsigned long> stalledDownloadTimeout{
|
||||||
|
@ -123,8 +123,6 @@ public:
|
||||||
|
|
||||||
template<typename... Args>
|
template<typename... Args>
|
||||||
FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args);
|
FileTransferError(FileTransfer::Error error, std::optional<std::string> response, const Args & ... args);
|
||||||
|
|
||||||
virtual const char* sname() const override { return "FileTransferError"; }
|
|
||||||
};
|
};
|
||||||
|
|
||||||
bool isUri(std::string_view s);
|
bool isUri(std::string_view s);
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
#include "gc-store.hh"
|
|
||||||
|
|
||||||
namespace nix {
|
|
||||||
|
|
||||||
GcStore & requireGcStore(Store & store)
|
|
||||||
{
|
|
||||||
auto * gcStore = dynamic_cast<GcStore *>(&store);
|
|
||||||
if (!gcStore)
|
|
||||||
throw UsageError("Garbage collection not supported by this store");
|
|
||||||
return *gcStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -61,6 +61,8 @@ struct GCResults
|
||||||
|
|
||||||
struct GcStore : public virtual Store
|
struct GcStore : public virtual Store
|
||||||
{
|
{
|
||||||
|
inline static std::string operationName = "Garbage collection";
|
||||||
|
|
||||||
/* Add an indirect root, which is merely a symlink to `path' from
|
/* Add an indirect root, which is merely a symlink to `path' from
|
||||||
/nix/var/nix/gcroots/auto/<hash of `path'>. `path' is supposed
|
/nix/var/nix/gcroots/auto/<hash of `path'>. `path' is supposed
|
||||||
to be a symlink to a store path. The garbage collector will
|
to be a symlink to a store path. The garbage collector will
|
||||||
|
@ -79,6 +81,4 @@ struct GcStore : public virtual Store
|
||||||
virtual void collectGarbage(const GCOptions & options, GCResults & results) = 0;
|
virtual void collectGarbage(const GCOptions & options, GCResults & results) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
GcStore & requireGcStore(Store & store);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -678,7 +678,8 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
alive.insert(start);
|
alive.insert(start);
|
||||||
try {
|
try {
|
||||||
StorePathSet closure;
|
StorePathSet closure;
|
||||||
computeFSClosure(*path, closure);
|
computeFSClosure(*path, closure,
|
||||||
|
/* flipDirection */ false, gcKeepOutputs, gcKeepDerivations);
|
||||||
for (auto & p : closure)
|
for (auto & p : closure)
|
||||||
alive.insert(p);
|
alive.insert(p);
|
||||||
} catch (InvalidPath &) { }
|
} catch (InvalidPath &) { }
|
||||||
|
@ -841,7 +842,8 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results)
|
||||||
if (unlink(path.c_str()) == -1)
|
if (unlink(path.c_str()) == -1)
|
||||||
throw SysError("deleting '%1%'", path);
|
throw SysError("deleting '%1%'", path);
|
||||||
|
|
||||||
results.bytesFreed += st.st_size;
|
/* Do not accound for deleted file here. Rely on deletePath()
|
||||||
|
accounting. */
|
||||||
}
|
}
|
||||||
|
|
||||||
struct stat st;
|
struct stat st;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "gc-store.hh"
|
#include "gc-store.hh"
|
||||||
|
#include "log-store.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
@ -24,7 +25,10 @@ struct LocalFSStoreConfig : virtual StoreConfig
|
||||||
"physical path to the Nix store"};
|
"physical path to the Nix store"};
|
||||||
};
|
};
|
||||||
|
|
||||||
class LocalFSStore : public virtual LocalFSStoreConfig, public virtual Store, virtual GcStore
|
class LocalFSStore : public virtual LocalFSStoreConfig,
|
||||||
|
public virtual Store,
|
||||||
|
public virtual GcStore,
|
||||||
|
public virtual LogStore
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ int getSchema(Path schemaPath)
|
||||||
|
|
||||||
void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
|
void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
|
||||||
{
|
{
|
||||||
const int nixCASchemaVersion = 3;
|
const int nixCASchemaVersion = 4;
|
||||||
int curCASchema = getSchema(schemaPath);
|
int curCASchema = getSchema(schemaPath);
|
||||||
if (curCASchema != nixCASchemaVersion) {
|
if (curCASchema != nixCASchemaVersion) {
|
||||||
if (curCASchema > nixCASchemaVersion) {
|
if (curCASchema > nixCASchemaVersion) {
|
||||||
|
@ -143,6 +143,21 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
|
||||||
)");
|
)");
|
||||||
txn.commit();
|
txn.commit();
|
||||||
}
|
}
|
||||||
|
if (curCASchema < 4) {
|
||||||
|
SQLiteTxn txn(db);
|
||||||
|
db.exec(R"(
|
||||||
|
create trigger if not exists DeleteSelfRefsViaRealisations before delete on ValidPaths
|
||||||
|
begin
|
||||||
|
delete from RealisationsRefs where realisationReference in (
|
||||||
|
select id from Realisations where outputPath = old.id
|
||||||
|
);
|
||||||
|
end;
|
||||||
|
-- used by deletion trigger
|
||||||
|
create index if not exists IndexRealisationsRefsRealisationReference on RealisationsRefs(realisationReference);
|
||||||
|
)");
|
||||||
|
txn.commit();
|
||||||
|
}
|
||||||
|
|
||||||
writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
|
writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
|
||||||
lockFile(lockFd.get(), ltRead, true);
|
lockFile(lockFd.get(), ltRead, true);
|
||||||
}
|
}
|
||||||
|
@ -482,18 +497,18 @@ void LocalStore::openDB(State & state, bool create)
|
||||||
SQLiteStmt stmt;
|
SQLiteStmt stmt;
|
||||||
stmt.create(db, "pragma main.journal_mode;");
|
stmt.create(db, "pragma main.journal_mode;");
|
||||||
if (sqlite3_step(stmt) != SQLITE_ROW)
|
if (sqlite3_step(stmt) != SQLITE_ROW)
|
||||||
throwSQLiteError(db, "querying journal mode");
|
SQLiteError::throw_(db, "querying journal mode");
|
||||||
prevMode = std::string((const char *) sqlite3_column_text(stmt, 0));
|
prevMode = std::string((const char *) sqlite3_column_text(stmt, 0));
|
||||||
}
|
}
|
||||||
if (prevMode != mode &&
|
if (prevMode != mode &&
|
||||||
sqlite3_exec(db, ("pragma main.journal_mode = " + mode + ";").c_str(), 0, 0, 0) != SQLITE_OK)
|
sqlite3_exec(db, ("pragma main.journal_mode = " + mode + ";").c_str(), 0, 0, 0) != SQLITE_OK)
|
||||||
throwSQLiteError(db, "setting journal mode");
|
SQLiteError::throw_(db, "setting journal mode");
|
||||||
|
|
||||||
/* Increase the auto-checkpoint interval to 40000 pages. This
|
/* Increase the auto-checkpoint interval to 40000 pages. This
|
||||||
seems enough to ensure that instantiating the NixOS system
|
seems enough to ensure that instantiating the NixOS system
|
||||||
derivation is done in a single fsync(). */
|
derivation is done in a single fsync(). */
|
||||||
if (mode == "wal" && sqlite3_exec(db, "pragma wal_autocheckpoint = 40000;", 0, 0, 0) != SQLITE_OK)
|
if (mode == "wal" && sqlite3_exec(db, "pragma wal_autocheckpoint = 40000;", 0, 0, 0) != SQLITE_OK)
|
||||||
throwSQLiteError(db, "setting autocheckpoint interval");
|
SQLiteError::throw_(db, "setting autocheckpoint interval");
|
||||||
|
|
||||||
/* Initialise the database schema, if necessary. */
|
/* Initialise the database schema, if necessary. */
|
||||||
if (create) {
|
if (create) {
|
||||||
|
@ -695,31 +710,34 @@ void LocalStore::checkDerivationOutputs(const StorePath & drvPath, const Derivat
|
||||||
// combinations that are currently prohibited.
|
// combinations that are currently prohibited.
|
||||||
drv.type();
|
drv.type();
|
||||||
|
|
||||||
std::optional<Hash> h;
|
std::optional<DrvHash> hashesModulo;
|
||||||
for (auto & i : drv.outputs) {
|
for (auto & i : drv.outputs) {
|
||||||
std::visit(overloaded {
|
std::visit(overloaded {
|
||||||
[&](const DerivationOutputInputAddressed & doia) {
|
[&](const DerivationOutput::InputAddressed & doia) {
|
||||||
if (!h) {
|
if (!hashesModulo) {
|
||||||
// somewhat expensive so we do lazily
|
// somewhat expensive so we do lazily
|
||||||
auto temp = hashDerivationModulo(*this, drv, true);
|
hashesModulo = hashDerivationModulo(*this, drv, true);
|
||||||
h = std::get<Hash>(temp);
|
|
||||||
}
|
}
|
||||||
StorePath recomputed = makeOutputPath(i.first, *h, drvName);
|
StorePath recomputed = makeOutputPath(i.first, hashesModulo->hashes.at(i.first), drvName);
|
||||||
if (doia.path != recomputed)
|
if (doia.path != recomputed)
|
||||||
throw Error("derivation '%s' has incorrect output '%s', should be '%s'",
|
throw Error("derivation '%s' has incorrect output '%s', should be '%s'",
|
||||||
printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed));
|
printStorePath(drvPath), printStorePath(doia.path), printStorePath(recomputed));
|
||||||
envHasRightPath(doia.path, i.first);
|
envHasRightPath(doia.path, i.first);
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFixed & dof) {
|
[&](const DerivationOutput::CAFixed & dof) {
|
||||||
StorePath path = makeFixedOutputPath(dof.hash.method, dof.hash.hash, drvName);
|
StorePath path = makeFixedOutputPath(dof.hash.method, dof.hash.hash, drvName);
|
||||||
envHasRightPath(path, i.first);
|
envHasRightPath(path, i.first);
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputCAFloating &) {
|
[&](const DerivationOutput::CAFloating &) {
|
||||||
/* Nothing to check */
|
/* Nothing to check */
|
||||||
},
|
},
|
||||||
[&](const DerivationOutputDeferred &) {
|
[&](const DerivationOutput::Deferred &) {
|
||||||
|
/* Nothing to check */
|
||||||
},
|
},
|
||||||
}, i.second.output);
|
[&](const DerivationOutput::Impure &) {
|
||||||
|
/* Nothing to check */
|
||||||
|
},
|
||||||
|
}, i.second.raw());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
21
src/libstore/log-store.hh
Normal file
21
src/libstore/log-store.hh
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
struct LogStore : public virtual Store
|
||||||
|
{
|
||||||
|
inline static std::string operationName = "Build log storage and retrieval";
|
||||||
|
|
||||||
|
/* Return the build log of the specified store path, if available,
|
||||||
|
or null otherwise. */
|
||||||
|
virtual std::optional<std::string> getBuildLog(const StorePath & path) = 0;
|
||||||
|
|
||||||
|
virtual void addBuildLog(const StorePath & path, std::string_view log) = 0;
|
||||||
|
|
||||||
|
static LogStore & require(Store & store);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
80
src/libstore/make-content-addressed.cc
Normal file
80
src/libstore/make-content-addressed.cc
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
#include "make-content-addressed.hh"
|
||||||
|
#include "references.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
std::map<StorePath, StorePath> makeContentAddressed(
|
||||||
|
Store & srcStore,
|
||||||
|
Store & dstStore,
|
||||||
|
const StorePathSet & storePaths)
|
||||||
|
{
|
||||||
|
StorePathSet closure;
|
||||||
|
srcStore.computeFSClosure(storePaths, closure);
|
||||||
|
|
||||||
|
auto paths = srcStore.topoSortPaths(closure);
|
||||||
|
|
||||||
|
std::reverse(paths.begin(), paths.end());
|
||||||
|
|
||||||
|
std::map<StorePath, StorePath> remappings;
|
||||||
|
|
||||||
|
for (auto & path : paths) {
|
||||||
|
auto pathS = srcStore.printStorePath(path);
|
||||||
|
auto oldInfo = srcStore.queryPathInfo(path);
|
||||||
|
std::string oldHashPart(path.hashPart());
|
||||||
|
|
||||||
|
StringSink sink;
|
||||||
|
srcStore.narFromPath(path, sink);
|
||||||
|
|
||||||
|
StringMap rewrites;
|
||||||
|
|
||||||
|
StorePathSet references;
|
||||||
|
bool hasSelfReference = false;
|
||||||
|
for (auto & ref : oldInfo->references) {
|
||||||
|
if (ref == path)
|
||||||
|
hasSelfReference = true;
|
||||||
|
else {
|
||||||
|
auto i = remappings.find(ref);
|
||||||
|
auto replacement = i != remappings.end() ? i->second : ref;
|
||||||
|
// FIXME: warn about unremapped paths?
|
||||||
|
if (replacement != ref)
|
||||||
|
rewrites.insert_or_assign(srcStore.printStorePath(ref), srcStore.printStorePath(replacement));
|
||||||
|
references.insert(std::move(replacement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sink.s = rewriteStrings(sink.s, rewrites);
|
||||||
|
|
||||||
|
HashModuloSink hashModuloSink(htSHA256, oldHashPart);
|
||||||
|
hashModuloSink(sink.s);
|
||||||
|
|
||||||
|
auto narModuloHash = hashModuloSink.finish().first;
|
||||||
|
|
||||||
|
auto dstPath = dstStore.makeFixedOutputPath(
|
||||||
|
FileIngestionMethod::Recursive, narModuloHash, path.name(), references, hasSelfReference);
|
||||||
|
|
||||||
|
printInfo("rewriting '%s' to '%s'", pathS, srcStore.printStorePath(dstPath));
|
||||||
|
|
||||||
|
StringSink sink2;
|
||||||
|
RewritingSink rsink2(oldHashPart, std::string(dstPath.hashPart()), sink2);
|
||||||
|
rsink2(sink.s);
|
||||||
|
rsink2.flush();
|
||||||
|
|
||||||
|
ValidPathInfo info { dstPath, hashString(htSHA256, sink2.s) };
|
||||||
|
info.references = std::move(references);
|
||||||
|
if (hasSelfReference) info.references.insert(info.path);
|
||||||
|
info.narSize = sink.s.size();
|
||||||
|
info.ca = FixedOutputHash {
|
||||||
|
.method = FileIngestionMethod::Recursive,
|
||||||
|
.hash = narModuloHash,
|
||||||
|
};
|
||||||
|
|
||||||
|
StringSource source(sink2.s);
|
||||||
|
dstStore.addToStore(info, source);
|
||||||
|
|
||||||
|
remappings.insert_or_assign(std::move(path), std::move(info.path));
|
||||||
|
}
|
||||||
|
|
||||||
|
return remappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
12
src/libstore/make-content-addressed.hh
Normal file
12
src/libstore/make-content-addressed.hh
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
std::map<StorePath, StorePath> makeContentAddressed(
|
||||||
|
Store & srcStore,
|
||||||
|
Store & dstStore,
|
||||||
|
const StorePathSet & storePaths);
|
||||||
|
|
||||||
|
}
|
|
@ -87,7 +87,7 @@ std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv)
|
||||||
{
|
{
|
||||||
auto out = drv.outputs.find("out");
|
auto out = drv.outputs.find("out");
|
||||||
if (out != drv.outputs.end()) {
|
if (out != drv.outputs.end()) {
|
||||||
if (auto v = std::get_if<DerivationOutputCAFixed>(&out->second.output))
|
if (const auto * v = std::get_if<DerivationOutput::CAFixed>(&out->second.raw()))
|
||||||
return v->hash;
|
return v->hash;
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
|
@ -277,15 +277,15 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
|
||||||
{
|
{
|
||||||
std::set<Realisation> inputRealisations;
|
std::set<Realisation> inputRealisations;
|
||||||
|
|
||||||
for (const auto& [inputDrv, outputNames] : drv.inputDrvs) {
|
for (const auto & [inputDrv, outputNames] : drv.inputDrvs) {
|
||||||
auto outputHashes =
|
auto outputHashes =
|
||||||
staticOutputHashes(store, store.readDerivation(inputDrv));
|
staticOutputHashes(store, store.readDerivation(inputDrv));
|
||||||
for (const auto& outputName : outputNames) {
|
for (const auto & outputName : outputNames) {
|
||||||
auto thisRealisation = store.queryRealisation(
|
auto thisRealisation = store.queryRealisation(
|
||||||
DrvOutput{outputHashes.at(outputName), outputName});
|
DrvOutput{outputHashes.at(outputName), outputName});
|
||||||
if (!thisRealisation)
|
if (!thisRealisation)
|
||||||
throw Error(
|
throw Error(
|
||||||
"output '%s' of derivation '%s' isn’t built", outputName,
|
"output '%s' of derivation '%s' isn't built", outputName,
|
||||||
store.printStorePath(inputDrv));
|
store.printStorePath(inputDrv));
|
||||||
inputRealisations.insert(*thisRealisation);
|
inputRealisations.insert(*thisRealisation);
|
||||||
}
|
}
|
||||||
|
@ -295,4 +295,5 @@ std::map<DrvOutput, StorePath> drvOutputReferences(
|
||||||
|
|
||||||
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references);
|
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ StringSet ParsedDerivation::getRequiredSystemFeatures() const
|
||||||
StringSet res;
|
StringSet res;
|
||||||
for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings()))
|
for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings()))
|
||||||
res.insert(i);
|
res.insert(i);
|
||||||
if (!derivationHasKnownOutputPaths(drv.type()))
|
if (!drv.type().hasKnownOutputPaths())
|
||||||
res.insert("ca-derivations");
|
res.insert("ca-derivations");
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "derivations.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
|
||||||
#include <nlohmann/json_fwd.hpp>
|
#include <nlohmann/json_fwd.hpp>
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
|
||||||
|
#include <sodium.h>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
static void checkName(std::string_view path, std::string_view name)
|
static void checkName(std::string_view path, std::string_view name)
|
||||||
|
@ -41,6 +43,13 @@ bool StorePath::isDerivation() const
|
||||||
|
|
||||||
StorePath StorePath::dummy("ffffffffffffffffffffffffffffffff-x");
|
StorePath StorePath::dummy("ffffffffffffffffffffffffffffffff-x");
|
||||||
|
|
||||||
|
StorePath StorePath::random(std::string_view name)
|
||||||
|
{
|
||||||
|
Hash hash(htSHA1);
|
||||||
|
randombytes_buf(hash.hash, hash.hashSize);
|
||||||
|
return StorePath(hash, name);
|
||||||
|
}
|
||||||
|
|
||||||
StorePath Store::parseStorePath(std::string_view path) const
|
StorePath Store::parseStorePath(std::string_view path) const
|
||||||
{
|
{
|
||||||
auto p = canonPath(std::string(path));
|
auto p = canonPath(std::string(path));
|
||||||
|
|
|
@ -58,6 +58,8 @@ public:
|
||||||
}
|
}
|
||||||
|
|
||||||
static StorePath dummy;
|
static StorePath dummy;
|
||||||
|
|
||||||
|
static StorePath random(std::string_view name);
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef std::set<StorePath> StorePathSet;
|
typedef std::set<StorePath> StorePathSet;
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "gc-store.hh"
|
#include "gc-store.hh"
|
||||||
|
#include "log-store.hh"
|
||||||
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
@ -30,7 +31,10 @@ struct RemoteStoreConfig : virtual StoreConfig
|
||||||
|
|
||||||
/* FIXME: RemoteStore is a misnomer - should be something like
|
/* FIXME: RemoteStore is a misnomer - should be something like
|
||||||
DaemonStore. */
|
DaemonStore. */
|
||||||
class RemoteStore : public virtual RemoteStoreConfig, public virtual Store, public virtual GcStore
|
class RemoteStore : public virtual RemoteStoreConfig,
|
||||||
|
public virtual Store,
|
||||||
|
public virtual GcStore,
|
||||||
|
public virtual LogStore
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
|
7
src/libstore/repair-flag.hh
Normal file
7
src/libstore/repair-flag.hh
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
enum RepairFlag : bool { NoRepair = false, Repair = true };
|
||||||
|
|
||||||
|
}
|
|
@ -8,22 +8,32 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
[[noreturn]] void throwSQLiteError(sqlite3 * db, const FormatOrString & fs)
|
SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf)
|
||||||
|
: Error(""), path(path), errNo(errNo), extendedErrNo(extendedErrNo)
|
||||||
|
{
|
||||||
|
err.msg = hintfmt("%s: %s (in '%s')",
|
||||||
|
normaltxt(hf.str()),
|
||||||
|
sqlite3_errstr(extendedErrNo),
|
||||||
|
path ? path : "(in-memory)");
|
||||||
|
}
|
||||||
|
|
||||||
|
[[noreturn]] void SQLiteError::throw_(sqlite3 * db, hintformat && hf)
|
||||||
{
|
{
|
||||||
int err = sqlite3_errcode(db);
|
int err = sqlite3_errcode(db);
|
||||||
int exterr = sqlite3_extended_errcode(db);
|
int exterr = sqlite3_extended_errcode(db);
|
||||||
|
|
||||||
auto path = sqlite3_db_filename(db, nullptr);
|
auto path = sqlite3_db_filename(db, nullptr);
|
||||||
if (!path) path = "(in-memory)";
|
|
||||||
|
|
||||||
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
|
if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) {
|
||||||
throw SQLiteBusy(
|
auto exp = SQLiteBusy(path, err, exterr, std::move(hf));
|
||||||
|
exp.err.msg = hintfmt(
|
||||||
err == SQLITE_PROTOCOL
|
err == SQLITE_PROTOCOL
|
||||||
? fmt("SQLite database '%s' is busy (SQLITE_PROTOCOL)", path)
|
? "SQLite database '%s' is busy (SQLITE_PROTOCOL)"
|
||||||
: fmt("SQLite database '%s' is busy", path));
|
: "SQLite database '%s' is busy",
|
||||||
}
|
path ? path : "(in-memory)");
|
||||||
else
|
throw exp;
|
||||||
throw SQLiteError("%s: %s (in '%s')", fs.s, sqlite3_errstr(exterr), path);
|
} else
|
||||||
|
throw SQLiteError(path, err, exterr, std::move(hf));
|
||||||
}
|
}
|
||||||
|
|
||||||
SQLite::SQLite(const Path & path, bool create)
|
SQLite::SQLite(const Path & path, bool create)
|
||||||
|
@ -37,7 +47,7 @@ SQLite::SQLite(const Path & path, bool create)
|
||||||
throw Error("cannot open SQLite database '%s'", path);
|
throw Error("cannot open SQLite database '%s'", path);
|
||||||
|
|
||||||
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
|
if (sqlite3_busy_timeout(db, 60 * 60 * 1000) != SQLITE_OK)
|
||||||
throwSQLiteError(db, "setting timeout");
|
SQLiteError::throw_(db, "setting timeout");
|
||||||
|
|
||||||
exec("pragma foreign_keys = 1");
|
exec("pragma foreign_keys = 1");
|
||||||
}
|
}
|
||||||
|
@ -46,7 +56,7 @@ SQLite::~SQLite()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (db && sqlite3_close(db) != SQLITE_OK)
|
if (db && sqlite3_close(db) != SQLITE_OK)
|
||||||
throwSQLiteError(db, "closing database");
|
SQLiteError::throw_(db, "closing database");
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreException();
|
||||||
}
|
}
|
||||||
|
@ -62,7 +72,7 @@ void SQLite::exec(const std::string & stmt)
|
||||||
{
|
{
|
||||||
retrySQLite<void>([&]() {
|
retrySQLite<void>([&]() {
|
||||||
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK)
|
if (sqlite3_exec(db, stmt.c_str(), 0, 0, 0) != SQLITE_OK)
|
||||||
throwSQLiteError(db, format("executing SQLite statement '%s'") % stmt);
|
SQLiteError::throw_(db, "executing SQLite statement '%s'", stmt);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +86,7 @@ void SQLiteStmt::create(sqlite3 * db, const std::string & sql)
|
||||||
checkInterrupt();
|
checkInterrupt();
|
||||||
assert(!stmt);
|
assert(!stmt);
|
||||||
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK)
|
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, 0) != SQLITE_OK)
|
||||||
throwSQLiteError(db, fmt("creating statement '%s'", sql));
|
SQLiteError::throw_(db, "creating statement '%s'", sql);
|
||||||
this->db = db;
|
this->db = db;
|
||||||
this->sql = sql;
|
this->sql = sql;
|
||||||
}
|
}
|
||||||
|
@ -85,7 +95,7 @@ SQLiteStmt::~SQLiteStmt()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
|
if (stmt && sqlite3_finalize(stmt) != SQLITE_OK)
|
||||||
throwSQLiteError(db, fmt("finalizing statement '%s'", sql));
|
SQLiteError::throw_(db, "finalizing statement '%s'", sql);
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreException();
|
||||||
}
|
}
|
||||||
|
@ -109,7 +119,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (std::string_view value, bool not
|
||||||
{
|
{
|
||||||
if (notNull) {
|
if (notNull) {
|
||||||
if (sqlite3_bind_text(stmt, curArg++, value.data(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
|
if (sqlite3_bind_text(stmt, curArg++, value.data(), -1, SQLITE_TRANSIENT) != SQLITE_OK)
|
||||||
throwSQLiteError(stmt.db, "binding argument");
|
SQLiteError::throw_(stmt.db, "binding argument");
|
||||||
} else
|
} else
|
||||||
bind();
|
bind();
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -119,7 +129,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (const unsigned char * data, size
|
||||||
{
|
{
|
||||||
if (notNull) {
|
if (notNull) {
|
||||||
if (sqlite3_bind_blob(stmt, curArg++, data, len, SQLITE_TRANSIENT) != SQLITE_OK)
|
if (sqlite3_bind_blob(stmt, curArg++, data, len, SQLITE_TRANSIENT) != SQLITE_OK)
|
||||||
throwSQLiteError(stmt.db, "binding argument");
|
SQLiteError::throw_(stmt.db, "binding argument");
|
||||||
} else
|
} else
|
||||||
bind();
|
bind();
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -129,7 +139,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull)
|
||||||
{
|
{
|
||||||
if (notNull) {
|
if (notNull) {
|
||||||
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
|
if (sqlite3_bind_int64(stmt, curArg++, value) != SQLITE_OK)
|
||||||
throwSQLiteError(stmt.db, "binding argument");
|
SQLiteError::throw_(stmt.db, "binding argument");
|
||||||
} else
|
} else
|
||||||
bind();
|
bind();
|
||||||
return *this;
|
return *this;
|
||||||
|
@ -138,7 +148,7 @@ SQLiteStmt::Use & SQLiteStmt::Use::operator () (int64_t value, bool notNull)
|
||||||
SQLiteStmt::Use & SQLiteStmt::Use::bind()
|
SQLiteStmt::Use & SQLiteStmt::Use::bind()
|
||||||
{
|
{
|
||||||
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
|
if (sqlite3_bind_null(stmt, curArg++) != SQLITE_OK)
|
||||||
throwSQLiteError(stmt.db, "binding argument");
|
SQLiteError::throw_(stmt.db, "binding argument");
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,14 +162,14 @@ void SQLiteStmt::Use::exec()
|
||||||
int r = step();
|
int r = step();
|
||||||
assert(r != SQLITE_ROW);
|
assert(r != SQLITE_ROW);
|
||||||
if (r != SQLITE_DONE)
|
if (r != SQLITE_DONE)
|
||||||
throwSQLiteError(stmt.db, fmt("executing SQLite statement '%s'", sqlite3_expanded_sql(stmt.stmt)));
|
SQLiteError::throw_(stmt.db, fmt("executing SQLite statement '%s'", sqlite3_expanded_sql(stmt.stmt)));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool SQLiteStmt::Use::next()
|
bool SQLiteStmt::Use::next()
|
||||||
{
|
{
|
||||||
int r = step();
|
int r = step();
|
||||||
if (r != SQLITE_DONE && r != SQLITE_ROW)
|
if (r != SQLITE_DONE && r != SQLITE_ROW)
|
||||||
throwSQLiteError(stmt.db, fmt("executing SQLite query '%s'", sqlite3_expanded_sql(stmt.stmt)));
|
SQLiteError::throw_(stmt.db, fmt("executing SQLite query '%s'", sqlite3_expanded_sql(stmt.stmt)));
|
||||||
return r == SQLITE_ROW;
|
return r == SQLITE_ROW;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -185,14 +195,14 @@ SQLiteTxn::SQLiteTxn(sqlite3 * db)
|
||||||
{
|
{
|
||||||
this->db = db;
|
this->db = db;
|
||||||
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
|
if (sqlite3_exec(db, "begin;", 0, 0, 0) != SQLITE_OK)
|
||||||
throwSQLiteError(db, "starting transaction");
|
SQLiteError::throw_(db, "starting transaction");
|
||||||
active = true;
|
active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SQLiteTxn::commit()
|
void SQLiteTxn::commit()
|
||||||
{
|
{
|
||||||
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
|
if (sqlite3_exec(db, "commit;", 0, 0, 0) != SQLITE_OK)
|
||||||
throwSQLiteError(db, "committing transaction");
|
SQLiteError::throw_(db, "committing transaction");
|
||||||
active = false;
|
active = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +210,7 @@ SQLiteTxn::~SQLiteTxn()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
|
if (active && sqlite3_exec(db, "rollback;", 0, 0, 0) != SQLITE_OK)
|
||||||
throwSQLiteError(db, "aborting transaction");
|
SQLiteError::throw_(db, "aborting transaction");
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
ignoreException();
|
ignoreException();
|
||||||
}
|
}
|
||||||
|
@ -215,7 +225,6 @@ void handleSQLiteBusy(const SQLiteBusy & e)
|
||||||
if (now > lastWarned + 10) {
|
if (now > lastWarned + 10) {
|
||||||
lastWarned = now;
|
lastWarned = now;
|
||||||
logWarning({
|
logWarning({
|
||||||
.name = "Sqlite busy",
|
|
||||||
.msg = hintfmt(e.what())
|
.msg = hintfmt(e.what())
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue