mirror of
https://github.com/NixOS/nix.git
synced 2025-11-28 13:11:00 +01:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
e8a9a1b305
279 changed files with 4694 additions and 1687 deletions
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
|
|
@ -29,7 +29,32 @@ jobs:
|
||||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- run: nix flake show --all-systems --json
|
- run: nix flake show --all-systems --json
|
||||||
|
|
||||||
|
pre-commit-checks:
|
||||||
|
name: pre-commit checks
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v5
|
||||||
|
- uses: ./.github/actions/install-nix-action
|
||||||
|
with:
|
||||||
|
dogfood: ${{ github.event_name == 'workflow_dispatch' && inputs.dogfood || github.event_name != 'workflow_dispatch' }}
|
||||||
|
extra_nix_config: experimental-features = nix-command flakes
|
||||||
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- uses: DeterminateSystems/magic-nix-cache-action@main
|
||||||
|
- run: ./ci/gha/tests/pre-commit-checks
|
||||||
|
|
||||||
|
basic-checks:
|
||||||
|
name: aggregate basic checks
|
||||||
|
if: ${{ always() }}
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
needs: [pre-commit-checks, eval]
|
||||||
|
steps:
|
||||||
|
- name: Exit with any errors
|
||||||
|
if: ${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}
|
||||||
|
run: |
|
||||||
|
exit 1
|
||||||
|
|
||||||
tests:
|
tests:
|
||||||
|
needs: basic-checks
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
|
|
@ -214,6 +239,7 @@ jobs:
|
||||||
docker push $IMAGE_ID:master
|
docker push $IMAGE_ID:master
|
||||||
|
|
||||||
vm_tests:
|
vm_tests:
|
||||||
|
needs: basic-checks
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v5
|
- uses: actions/checkout@v5
|
||||||
|
|
|
||||||
11
COPYING
11
COPYING
|
|
@ -2,7 +2,7 @@
|
||||||
Version 2.1, February 1999
|
Version 2.1, February 1999
|
||||||
|
|
||||||
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
Copyright (C) 1991, 1999 Free Software Foundation, Inc.
|
||||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
<https://fsf.org/>
|
||||||
Everyone is permitted to copy and distribute verbatim copies
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
of this license document, but changing it is not allowed.
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
@ -484,8 +484,7 @@ convey the exclusion of warranty; and each file should have at least the
|
||||||
Lesser General Public License for more details.
|
Lesser General Public License for more details.
|
||||||
|
|
||||||
You should have received a copy of the GNU Lesser General Public
|
You should have received a copy of the GNU Lesser General Public
|
||||||
License along with this library; if not, write to the Free Software
|
License along with this library; if not, see <https://www.gnu.org/licenses/>.
|
||||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
||||||
|
|
||||||
Also add information on how to contact you by electronic and paper mail.
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
|
@ -496,9 +495,7 @@ necessary. Here is a sample; alter the names:
|
||||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
Yoyodyne, Inc., hereby disclaims all copyright interest in the
|
||||||
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
library `Frob' (a library for tweaking knobs) written by James Random Hacker.
|
||||||
|
|
||||||
<signature of Ty Coon>, 1 April 1990
|
<signature of Moe Ghoul>, 1 April 1990
|
||||||
Ty Coon, President of Vice
|
Moe Ghoul, President of Vice
|
||||||
|
|
||||||
That's all there is to it!
|
That's all there is to it!
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,16 +24,7 @@ let
|
||||||
enableSanitizersLayer = finalAttrs: prevAttrs: {
|
enableSanitizersLayer = finalAttrs: prevAttrs: {
|
||||||
mesonFlags =
|
mesonFlags =
|
||||||
(prevAttrs.mesonFlags or [ ])
|
(prevAttrs.mesonFlags or [ ])
|
||||||
++ [
|
++ [ (lib.mesonOption "b_sanitize" "address,undefined") ]
|
||||||
# Run all tests with UBSAN enabled. Running both with ubsan and
|
|
||||||
# without doesn't seem to have much immediate benefit for doubling
|
|
||||||
# the GHA CI workaround.
|
|
||||||
#
|
|
||||||
# TODO: Work toward enabling "address,undefined" if it seems feasible.
|
|
||||||
# This would maybe require dropping Boost coroutines and ignoring intentional
|
|
||||||
# memory leaks with detect_leaks=0.
|
|
||||||
(lib.mesonOption "b_sanitize" "undefined")
|
|
||||||
]
|
|
||||||
++ (lib.optionals stdenv.cc.isClang [
|
++ (lib.optionals stdenv.cc.isClang [
|
||||||
# https://www.github.com/mesonbuild/meson/issues/764
|
# https://www.github.com/mesonbuild/meson/issues/764
|
||||||
(lib.mesonBool "b_lundef" false)
|
(lib.mesonBool "b_lundef" false)
|
||||||
|
|
@ -71,8 +62,12 @@ rec {
|
||||||
nixComponentsInstrumented = nixComponents.overrideScope (
|
nixComponentsInstrumented = nixComponents.overrideScope (
|
||||||
final: prev: {
|
final: prev: {
|
||||||
nix-store-tests = prev.nix-store-tests.override { withBenchmarks = true; };
|
nix-store-tests = prev.nix-store-tests.override { withBenchmarks = true; };
|
||||||
|
# Boehm is incompatible with ASAN.
|
||||||
|
nix-expr = prev.nix-expr.override { enableGC = !withSanitizers; };
|
||||||
|
|
||||||
mesonComponentOverrides = lib.composeManyExtensions componentOverrides;
|
mesonComponentOverrides = lib.composeManyExtensions componentOverrides;
|
||||||
|
# Unclear how to make Perl bindings work with a dynamically linked ASAN.
|
||||||
|
nix-perl-bindings = if withSanitizers then null else prev.nix-perl-bindings;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
24
ci/gha/tests/pre-commit-checks
Executable file
24
ci/gha/tests/pre-commit-checks
Executable file
|
|
@ -0,0 +1,24 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
system=$(nix eval --raw --impure --expr builtins.currentSystem)
|
||||||
|
|
||||||
|
echo "::group::Running pre-commit checks"
|
||||||
|
|
||||||
|
if nix build ".#checks.$system.pre-commit" -L; then
|
||||||
|
echo "::endgroup::"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "::error ::Changes do not pass pre-commit checks"
|
||||||
|
|
||||||
|
cat <<EOF
|
||||||
|
The code isn't formatted or doesn't pass lints. You can run pre-commit locally with:
|
||||||
|
|
||||||
|
nix develop -c ./maintainers/format.sh
|
||||||
|
EOF
|
||||||
|
|
||||||
|
echo "::endgroup::"
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
|
@ -15,6 +15,7 @@ pymod = import('python')
|
||||||
python = pymod.find_installation('python3')
|
python = pymod.find_installation('python3')
|
||||||
|
|
||||||
nix_env_for_docs = {
|
nix_env_for_docs = {
|
||||||
|
'ASAN_OPTIONS' : 'abort_on_error=1:print_summary=1:detect_leaks=0',
|
||||||
'HOME' : '/dummy',
|
'HOME' : '/dummy',
|
||||||
'NIX_CONF_DIR' : '/dummy',
|
'NIX_CONF_DIR' : '/dummy',
|
||||||
'NIX_SSL_CERT_FILE' : '/dummy/no-ca-bundle.crt',
|
'NIX_SSL_CERT_FILE' : '/dummy/no-ca-bundle.crt',
|
||||||
|
|
|
||||||
7
doc/manual/rl-next/c-api-byidx.md
Normal file
7
doc/manual/rl-next/c-api-byidx.md
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
---
|
||||||
|
synopsis: "C API: `nix_get_attr_name_byidx`, `nix_get_attr_byidx` take a `nix_value *` instead of `const nix_value *`"
|
||||||
|
prs: [13987]
|
||||||
|
---
|
||||||
|
|
||||||
|
In order to accommodate a more optimized internal representation of attribute set merges these functions require
|
||||||
|
a mutable `nix_value *` that might be modified on access. This does *not* break the ABI of these functions.
|
||||||
16
doc/manual/rl-next/c-api-lazy-accessors.md
Normal file
16
doc/manual/rl-next/c-api-lazy-accessors.md
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
synopsis: "C API: Add lazy attribute and list item accessors"
|
||||||
|
prs: [14030]
|
||||||
|
---
|
||||||
|
|
||||||
|
The C API now includes lazy accessor functions for retrieving values from lists and attribute sets without forcing evaluation:
|
||||||
|
|
||||||
|
- `nix_get_list_byidx_lazy()` - Get a list element without forcing its evaluation
|
||||||
|
- `nix_get_attr_byname_lazy()` - Get an attribute value by name without forcing evaluation
|
||||||
|
- `nix_get_attr_byidx_lazy()` - Get an attribute by index without forcing evaluation
|
||||||
|
|
||||||
|
These functions are useful when forwarding unevaluated sub-values to other lists, attribute sets, or function calls. They allow more efficient handling of Nix values by deferring evaluation until actually needed.
|
||||||
|
|
||||||
|
Additionally, bounds checking has been improved for all `_byidx` functions to properly validate indices before access, preventing potential out-of-bounds errors.
|
||||||
|
|
||||||
|
The documentation for `NIX_ERR_KEY` error handling has also been clarified to specify when this error code is returned.
|
||||||
10
doc/manual/rl-next/cached-substituted-inputs.md
Normal file
10
doc/manual/rl-next/cached-substituted-inputs.md
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
---
|
||||||
|
synopsis: "Substituted flake inputs are no longer re-copied to the store"
|
||||||
|
prs: [14041]
|
||||||
|
---
|
||||||
|
|
||||||
|
Since 2.25, Nix would fail to store a cache entry for substituted flake inputs,
|
||||||
|
which in turn would cause them to be re-copied to the store on initial
|
||||||
|
evaluation. Caching these inputs results in a near doubling of a performance in
|
||||||
|
some cases — especially on I/O-bound machines and when using commands that
|
||||||
|
fetch many inputs, like `nix flake archive/prefetch-inputs`
|
||||||
15
doc/manual/rl-next/derivation-json.md
Normal file
15
doc/manual/rl-next/derivation-json.md
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
---
|
||||||
|
synopsis: Derivation JSON format now uses store path basenames (no store dir) only
|
||||||
|
prs: [13980]
|
||||||
|
issues: [13570]
|
||||||
|
---
|
||||||
|
|
||||||
|
Experience with many JSON frameworks (e.g. nlohmann/json in C++, Serde in Rust, and Aeson in Haskell), has shown that the use of the store dir in JSON formats is an impediment to systematic JSON formats,
|
||||||
|
because it requires the serializer/deserializer to take an extra paramater (the store dir).
|
||||||
|
|
||||||
|
We ultimately want to rectify this issue with all (non-stable, able to be changed) JSON formats.
|
||||||
|
To start with, we are changing the JSON format for derivations because the `nix derivation` commands are
|
||||||
|
--- in addition to being formally unstable
|
||||||
|
--- less widely used than other unstable commands.
|
||||||
|
|
||||||
|
See the documentation on the [JSON format for derivations](@docroot@/protocols/json/derivation.md) for further details.
|
||||||
|
|
@ -2,6 +2,7 @@ xp_features_json = custom_target(
|
||||||
command : [ nix, '__dump-xp-features' ],
|
command : [ nix, '__dump-xp-features' ],
|
||||||
capture : true,
|
capture : true,
|
||||||
output : 'xp-features.json',
|
output : 'xp-features.json',
|
||||||
|
env : nix_env_for_docs,
|
||||||
)
|
)
|
||||||
|
|
||||||
experimental_features_shortlist_md = custom_target(
|
experimental_features_shortlist_md = custom_target(
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ $ nix-shell
|
||||||
To get a shell with one of the other [supported compilation environments](#compilation-environments):
|
To get a shell with one of the other [supported compilation environments](#compilation-environments):
|
||||||
|
|
||||||
```console
|
```console
|
||||||
$ nix-shell --attr devShells.x86_64-linux.native-clangStdenvPackages
|
$ nix-shell --attr devShells.x86_64-linux.native-clangStdenv
|
||||||
```
|
```
|
||||||
|
|
||||||
> **Note**
|
> **Note**
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,19 @@ It is also possible to build without debugging for faster build:
|
||||||
|
|
||||||
(The first line is needed because `fortify` hardening requires at least some optimization.)
|
(The first line is needed because `fortify` hardening requires at least some optimization.)
|
||||||
|
|
||||||
|
## Building Nix with sanitizers
|
||||||
|
|
||||||
|
Nix can be built with [Address](https://clang.llvm.org/docs/AddressSanitizer.html) and
|
||||||
|
[UB](https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html) sanitizers using LLVM
|
||||||
|
or GCC. This is useful when debugging memory corruption issues.
|
||||||
|
|
||||||
|
```console
|
||||||
|
[nix-shell]$ export mesonBuildType=debugoptimized
|
||||||
|
[nix-shell]$ appendToVar mesonFlags "-Dlibexpr:gc=disabled" # Disable Boehm
|
||||||
|
[nix-shell]$ appendToVar mesonFlags "-Dbindings=false" # Disable nix-perl
|
||||||
|
[nix-shell]$ appendToVar mesonFlags "-Db_sanitize=address,undefined"
|
||||||
|
```
|
||||||
|
|
||||||
## Debugging the Nix Binary
|
## Debugging the Nix Binary
|
||||||
|
|
||||||
Obtain your preferred debugger within the development shell:
|
Obtain your preferred debugger within the development shell:
|
||||||
|
|
|
||||||
|
|
@ -7,5 +7,6 @@ experimental_feature_descriptions_md = custom_target(
|
||||||
xp_features_json,
|
xp_features_json,
|
||||||
],
|
],
|
||||||
capture : true,
|
capture : true,
|
||||||
|
env : nix_env_for_docs,
|
||||||
output : 'experimental-feature-descriptions.md',
|
output : 'experimental-feature-descriptions.md',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,28 @@ All built-ins are available through the global [`builtins`](#builtins-builtins)
|
||||||
|
|
||||||
Some built-ins are also exposed directly in the global scope:
|
Some built-ins are also exposed directly in the global scope:
|
||||||
|
|
||||||
<!-- TODO(@rhendric, #10970): this list is incomplete -->
|
|
||||||
|
|
||||||
- [`derivation`](#builtins-derivation)
|
- [`derivation`](#builtins-derivation)
|
||||||
- [`import`](#builtins-import)
|
- `derivationStrict`
|
||||||
- [`abort`](#builtins-abort)
|
- [`abort`](#builtins-abort)
|
||||||
|
- [`baseNameOf`](#builtins-baseNameOf)
|
||||||
|
- [`break`](#builtins-break)
|
||||||
|
- [`dirOf`](#builtins-dirOf)
|
||||||
|
- [`false`](#builtins-false)
|
||||||
|
- [`fetchGit`](#builtins-fetchGit)
|
||||||
|
- `fetchMercurial`
|
||||||
|
- [`fetchTarball`](#builtins-fetchTarball)
|
||||||
|
- [`fetchTree`](#builtins-fetchTree)
|
||||||
|
- [`fromTOML`](#builtins-fromTOML)
|
||||||
|
- [`import`](#builtins-import)
|
||||||
|
- [`isNull`](#builtins-isNull)
|
||||||
|
- [`map`](#builtins-map)
|
||||||
|
- [`null`](#builtins-null)
|
||||||
|
- [`placeholder`](#builtins-placeholder)
|
||||||
|
- [`removeAttrs`](#builtins-removeAttrs)
|
||||||
|
- `scopedImport`
|
||||||
- [`throw`](#builtins-throw)
|
- [`throw`](#builtins-throw)
|
||||||
|
- [`toString`](#builtins-toString)
|
||||||
|
- [`true`](#builtins-true)
|
||||||
|
|
||||||
<dl>
|
<dl>
|
||||||
<dt id="builtins-derivation"><a href="#builtins-derivation"><code>derivation <var>attrs</var></code></a></dt>
|
<dt id="builtins-derivation"><a href="#builtins-derivation"><code>derivation <var>attrs</var></code></a></dt>
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,21 @@ is a JSON object with the following fields:
|
||||||
The name of the derivation.
|
The name of the derivation.
|
||||||
This is used when calculating the store paths of the derivation's outputs.
|
This is used when calculating the store paths of the derivation's outputs.
|
||||||
|
|
||||||
|
* `version`:
|
||||||
|
Must be `3`.
|
||||||
|
This is a guard that allows us to continue evolving this format.
|
||||||
|
The choice of `3` is fairly arbitrary, but corresponds to this informal version:
|
||||||
|
|
||||||
|
- Version 0: A-Term format
|
||||||
|
|
||||||
|
- Version 1: Original JSON format, with ugly `"r:sha256"` inherited from A-Term format.
|
||||||
|
|
||||||
|
- Version 2: Separate `method` and `hashAlgo` fields in output specs
|
||||||
|
|
||||||
|
- Verison 3: Drop store dir from store paths, just include base name.
|
||||||
|
|
||||||
|
Note that while this format is experimental, the maintenance of versions is best-effort, and not promised to identify every change.
|
||||||
|
|
||||||
* `outputs`:
|
* `outputs`:
|
||||||
Information about the output paths of the derivation.
|
Information about the output paths of the derivation.
|
||||||
This is a JSON object with one member per output, where the key is the output name and the value is a JSON object with these fields:
|
This is a JSON object with one member per output, where the key is the output name and the value is a JSON object with these fields:
|
||||||
|
|
@ -52,7 +67,6 @@ is a JSON object with the following fields:
|
||||||
> ```json
|
> ```json
|
||||||
> "outputs": {
|
> "outputs": {
|
||||||
> "out": {
|
> "out": {
|
||||||
> "path": "/nix/store/2543j7c6jn75blc3drf4g5vhb1rhdq29-source",
|
|
||||||
> "method": "nar",
|
> "method": "nar",
|
||||||
> "hashAlgo": "sha256",
|
> "hashAlgo": "sha256",
|
||||||
> "hash": "6fc80dcc62179dbc12fc0b5881275898f93444833d21b89dfe5f7fbcbb1d0d62"
|
> "hash": "6fc80dcc62179dbc12fc0b5881275898f93444833d21b89dfe5f7fbcbb1d0d62"
|
||||||
|
|
@ -63,6 +77,15 @@ is a JSON object with the following fields:
|
||||||
* `inputSrcs`:
|
* `inputSrcs`:
|
||||||
A list of store paths on which this derivation depends.
|
A list of store paths on which this derivation depends.
|
||||||
|
|
||||||
|
> **Example**
|
||||||
|
>
|
||||||
|
> ```json
|
||||||
|
> "inputSrcs": [
|
||||||
|
> "47y241wqdhac3jm5l7nv0x4975mb1975-separate-debug-info.sh",
|
||||||
|
> "56d0w71pjj9bdr363ym3wj1zkwyqq97j-fix-pop-var-context-error.patch"
|
||||||
|
> ]
|
||||||
|
> ```
|
||||||
|
|
||||||
* `inputDrvs`:
|
* `inputDrvs`:
|
||||||
A JSON object specifying the derivations on which this derivation depends, and what outputs of those derivations.
|
A JSON object specifying the derivations on which this derivation depends, and what outputs of those derivations.
|
||||||
|
|
||||||
|
|
@ -70,8 +93,8 @@ is a JSON object with the following fields:
|
||||||
>
|
>
|
||||||
> ```json
|
> ```json
|
||||||
> "inputDrvs": {
|
> "inputDrvs": {
|
||||||
> "/nix/store/6lkh5yi7nlb7l6dr8fljlli5zfd9hq58-curl-7.73.0.drv": ["dev"],
|
> "6lkh5yi7nlb7l6dr8fljlli5zfd9hq58-curl-7.73.0.drv": ["dev"],
|
||||||
> "/nix/store/fn3kgnfzl5dzym26j8g907gq3kbm8bfh-unzip-6.0.drv": ["out"]
|
> "fn3kgnfzl5dzym26j8g907gq3kbm8bfh-unzip-6.0.drv": ["out"]
|
||||||
> }
|
> }
|
||||||
> ```
|
> ```
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -106,63 +106,6 @@
|
||||||
enable = true;
|
enable = true;
|
||||||
excludes = [
|
excludes = [
|
||||||
# We haven't linted these files yet
|
# We haven't linted these files yet
|
||||||
''^config/install-sh$''
|
|
||||||
''^misc/bash/completion\.sh$''
|
|
||||||
''^misc/fish/completion\.fish$''
|
|
||||||
''^misc/zsh/completion\.zsh$''
|
|
||||||
''^scripts/create-darwin-volume\.sh$''
|
|
||||||
''^scripts/install-darwin-multi-user\.sh$''
|
|
||||||
''^scripts/install-multi-user\.sh$''
|
|
||||||
''^scripts/install-systemd-multi-user\.sh$''
|
|
||||||
''^src/nix/get-env\.sh$''
|
|
||||||
''^tests/functional/ca/build-dry\.sh$''
|
|
||||||
''^tests/functional/ca/build-with-garbage-path\.sh$''
|
|
||||||
''^tests/functional/ca/common\.sh$''
|
|
||||||
''^tests/functional/ca/concurrent-builds\.sh$''
|
|
||||||
''^tests/functional/ca/eval-store\.sh$''
|
|
||||||
''^tests/functional/ca/gc\.sh$''
|
|
||||||
''^tests/functional/ca/import-from-derivation\.sh$''
|
|
||||||
''^tests/functional/ca/new-build-cmd\.sh$''
|
|
||||||
''^tests/functional/ca/nix-shell\.sh$''
|
|
||||||
''^tests/functional/ca/post-hook\.sh$''
|
|
||||||
''^tests/functional/ca/recursive\.sh$''
|
|
||||||
''^tests/functional/ca/repl\.sh$''
|
|
||||||
''^tests/functional/ca/selfref-gc\.sh$''
|
|
||||||
''^tests/functional/ca/why-depends\.sh$''
|
|
||||||
''^tests/functional/characterisation-test-infra\.sh$''
|
|
||||||
''^tests/functional/common/vars-and-functions\.sh$''
|
|
||||||
''^tests/functional/completions\.sh$''
|
|
||||||
''^tests/functional/compute-levels\.sh$''
|
|
||||||
''^tests/functional/config\.sh$''
|
|
||||||
''^tests/functional/db-migration\.sh$''
|
|
||||||
''^tests/functional/debugger\.sh$''
|
|
||||||
''^tests/functional/dependencies\.builder0\.sh$''
|
|
||||||
''^tests/functional/dependencies\.sh$''
|
|
||||||
''^tests/functional/dump-db\.sh$''
|
|
||||||
''^tests/functional/dyn-drv/build-built-drv\.sh$''
|
|
||||||
''^tests/functional/dyn-drv/common\.sh$''
|
|
||||||
''^tests/functional/dyn-drv/dep-built-drv\.sh$''
|
|
||||||
''^tests/functional/dyn-drv/eval-outputOf\.sh$''
|
|
||||||
''^tests/functional/dyn-drv/old-daemon-error-hack\.sh$''
|
|
||||||
''^tests/functional/dyn-drv/recursive-mod-json\.sh$''
|
|
||||||
''^tests/functional/eval-store\.sh$''
|
|
||||||
''^tests/functional/export-graph\.sh$''
|
|
||||||
''^tests/functional/export\.sh$''
|
|
||||||
''^tests/functional/extra-sandbox-profile\.sh$''
|
|
||||||
''^tests/functional/fetchClosure\.sh$''
|
|
||||||
''^tests/functional/fetchGit\.sh$''
|
|
||||||
''^tests/functional/fetchGitRefs\.sh$''
|
|
||||||
''^tests/functional/fetchGitSubmodules\.sh$''
|
|
||||||
''^tests/functional/fetchGitVerification\.sh$''
|
|
||||||
''^tests/functional/fetchMercurial\.sh$''
|
|
||||||
''^tests/functional/fixed\.builder1\.sh$''
|
|
||||||
''^tests/functional/fixed\.builder2\.sh$''
|
|
||||||
''^tests/functional/fixed\.sh$''
|
|
||||||
''^tests/functional/flakes/absolute-paths\.sh$''
|
|
||||||
''^tests/functional/flakes/check\.sh$''
|
|
||||||
''^tests/functional/flakes/config\.sh$''
|
|
||||||
''^tests/functional/flakes/flakes\.sh$''
|
|
||||||
''^tests/functional/flakes/follow-paths\.sh$''
|
|
||||||
''^tests/functional/flakes/prefetch\.sh$''
|
''^tests/functional/flakes/prefetch\.sh$''
|
||||||
''^tests/functional/flakes/run\.sh$''
|
''^tests/functional/flakes/run\.sh$''
|
||||||
''^tests/functional/flakes/show\.sh$''
|
''^tests/functional/flakes/show\.sh$''
|
||||||
|
|
@ -179,29 +122,6 @@
|
||||||
''^tests/functional/install-darwin\.sh$''
|
''^tests/functional/install-darwin\.sh$''
|
||||||
''^tests/functional/legacy-ssh-store\.sh$''
|
''^tests/functional/legacy-ssh-store\.sh$''
|
||||||
''^tests/functional/linux-sandbox\.sh$''
|
''^tests/functional/linux-sandbox\.sh$''
|
||||||
''^tests/functional/local-overlay-store/add-lower-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/add-lower\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/bad-uris\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/build-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/build\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/check-post-init-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/check-post-init\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/common\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/delete-duplicate-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/delete-duplicate\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/delete-refs-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/delete-refs\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/gc-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/gc\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/optimise-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/optimise\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/redundant-add-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/redundant-add\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/remount\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/stale-file-handle-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/stale-file-handle\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/verify-inner\.sh$''
|
|
||||||
''^tests/functional/local-overlay-store/verify\.sh$''
|
|
||||||
''^tests/functional/logging\.sh$''
|
''^tests/functional/logging\.sh$''
|
||||||
''^tests/functional/misc\.sh$''
|
''^tests/functional/misc\.sh$''
|
||||||
''^tests/functional/multiple-outputs\.sh$''
|
''^tests/functional/multiple-outputs\.sh$''
|
||||||
|
|
@ -248,6 +168,23 @@
|
||||||
''^tests/functional/user-envs\.builder\.sh$''
|
''^tests/functional/user-envs\.builder\.sh$''
|
||||||
''^tests/functional/user-envs\.sh$''
|
''^tests/functional/user-envs\.sh$''
|
||||||
''^tests/functional/why-depends\.sh$''
|
''^tests/functional/why-depends\.sh$''
|
||||||
|
|
||||||
|
# Content-addressed test files that use recursive-*looking* sourcing
|
||||||
|
# (cd .. && source <self>), causing shellcheck to loop
|
||||||
|
# They're small wrapper scripts with not a lot going on
|
||||||
|
''^tests/functional/ca/build-delete\.sh$''
|
||||||
|
''^tests/functional/ca/build-dry\.sh$''
|
||||||
|
''^tests/functional/ca/eval-store\.sh$''
|
||||||
|
''^tests/functional/ca/gc\.sh$''
|
||||||
|
''^tests/functional/ca/import-from-derivation\.sh$''
|
||||||
|
''^tests/functional/ca/multiple-outputs\.sh$''
|
||||||
|
''^tests/functional/ca/new-build-cmd\.sh$''
|
||||||
|
''^tests/functional/ca/nix-shell\.sh$''
|
||||||
|
''^tests/functional/ca/post-hook\.sh$''
|
||||||
|
''^tests/functional/ca/recursive\.sh$''
|
||||||
|
''^tests/functional/ca/repl\.sh$''
|
||||||
|
''^tests/functional/ca/selfref-gc\.sh$''
|
||||||
|
''^tests/functional/ca/why-depends\.sh$''
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -41,8 +41,10 @@ subproject('libexpr-c')
|
||||||
subproject('libflake-c')
|
subproject('libflake-c')
|
||||||
subproject('libmain-c')
|
subproject('libmain-c')
|
||||||
|
|
||||||
|
asan_enabled = 'address' in get_option('b_sanitize')
|
||||||
|
|
||||||
# Language Bindings
|
# Language Bindings
|
||||||
if get_option('bindings') and not meson.is_cross_build()
|
if get_option('bindings') and not meson.is_cross_build() and not asan_enabled
|
||||||
subproject('perl')
|
subproject('perl')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
function _complete_nix {
|
function _complete_nix {
|
||||||
local -a words
|
local -a words
|
||||||
local cword cur
|
local cword cur
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck disable=all
|
||||||
function _nix_complete
|
function _nix_complete
|
||||||
# Get the current command up to a cursor.
|
# Get the current command up to a cursor.
|
||||||
# - Behaves correctly even with pipes and nested in commands like env.
|
# - Behaves correctly even with pipes and nested in commands like env.
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
# shellcheck disable=all
|
||||||
#compdef nix
|
#compdef nix
|
||||||
|
|
||||||
function _nix() {
|
function _nix() {
|
||||||
|
|
|
||||||
12
nix-meson-build-support/asan-options/meson.build
Normal file
12
nix-meson-build-support/asan-options/meson.build
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
asan_test_options_env = {
|
||||||
|
'ASAN_OPTIONS' : 'abort_on_error=1:print_summary=1:detect_leaks=0',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clang gets grumpy about missing libasan symbols if -shared-libasan is not
|
||||||
|
# passed when building shared libs, at least on Linux
|
||||||
|
if cxx.get_id() == 'clang' and ('address' in get_option('b_sanitize') or 'undefined' in get_option(
|
||||||
|
'b_sanitize',
|
||||||
|
))
|
||||||
|
add_project_link_arguments('-shared-libasan', language : 'cpp')
|
||||||
|
endif
|
||||||
|
|
||||||
|
|
@ -5,6 +5,15 @@ if not (host_machine.system() == 'windows' and cxx.get_id() == 'gcc')
|
||||||
deps_private += dependency('threads')
|
deps_private += dependency('threads')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
|
if host_machine.system() == 'cygwin'
|
||||||
|
# -std=gnu on cygwin defines 'unix', which conflicts with the namespace
|
||||||
|
add_project_arguments(
|
||||||
|
'-D_POSIX_C_SOURCE=200809L',
|
||||||
|
'-D_GNU_SOURCE',
|
||||||
|
language : 'cpp',
|
||||||
|
)
|
||||||
|
endif
|
||||||
|
|
||||||
add_project_arguments(
|
add_project_arguments(
|
||||||
'-Wdeprecated-copy',
|
'-Wdeprecated-copy',
|
||||||
'-Werror=suggest-override',
|
'-Werror=suggest-override',
|
||||||
|
|
@ -33,13 +42,5 @@ if cxx.get_id() == 'clang'
|
||||||
add_project_arguments('-fpch-instantiate-templates', language : 'cpp')
|
add_project_arguments('-fpch-instantiate-templates', language : 'cpp')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Clang gets grumpy about missing libasan symbols if -shared-libasan is not
|
|
||||||
# passed when building shared libs, at least on Linux
|
|
||||||
if cxx.get_id() == 'clang' and ('address' in get_option('b_sanitize') or 'undefined' in get_option(
|
|
||||||
'b_sanitize',
|
|
||||||
))
|
|
||||||
add_project_link_arguments('-shared-libasan', language : 'cpp')
|
|
||||||
endif
|
|
||||||
|
|
||||||
# Darwin ld doesn't like "X.Y.Zpre"
|
# Darwin ld doesn't like "X.Y.Zpre"
|
||||||
nix_soversion = meson.project_version().strip('pre')
|
nix_soversion = meson.project_version().split('pre')[0]
|
||||||
|
|
|
||||||
|
|
@ -164,6 +164,24 @@ let
|
||||||
};
|
};
|
||||||
|
|
||||||
mesonLibraryLayer = finalAttrs: prevAttrs: {
|
mesonLibraryLayer = finalAttrs: prevAttrs: {
|
||||||
|
preConfigure =
|
||||||
|
let
|
||||||
|
interpositionFlags = [
|
||||||
|
"-fno-semantic-interposition"
|
||||||
|
"-Wl,-Bsymbolic-functions"
|
||||||
|
];
|
||||||
|
in
|
||||||
|
# NOTE: By default GCC disables interprocedular optimizations (in particular inlining) for
|
||||||
|
# position-independent code and thus shared libraries.
|
||||||
|
# Since LD_PRELOAD tricks aren't worth losing out on optimizations, we disable it for good.
|
||||||
|
# This is not the case for Clang, where inlining is done by default even without -fno-semantic-interposition.
|
||||||
|
# https://reviews.llvm.org/D102453
|
||||||
|
# https://fedoraproject.org/wiki/Changes/PythonNoSemanticInterpositionSpeedup
|
||||||
|
prevAttrs.preConfigure or ""
|
||||||
|
+ lib.optionalString stdenv.cc.isGNU ''
|
||||||
|
export CFLAGS="''${CFLAGS:-} ${toString interpositionFlags}"
|
||||||
|
export CXXFLAGS="''${CXXFLAGS:-} ${toString interpositionFlags}"
|
||||||
|
'';
|
||||||
outputs = prevAttrs.outputs or [ "out" ] ++ [ "dev" ];
|
outputs = prevAttrs.outputs or [ "out" ] ++ [ "dev" ];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,7 @@ pkgs.nixComponents2.nix-util.overrideAttrs (
|
||||||
modular.pre-commit.settings.package
|
modular.pre-commit.settings.package
|
||||||
(pkgs.writeScriptBin "pre-commit-hooks-install" modular.pre-commit.settings.installationScript)
|
(pkgs.writeScriptBin "pre-commit-hooks-install" modular.pre-commit.settings.installationScript)
|
||||||
pkgs.buildPackages.nixfmt-rfc-style
|
pkgs.buildPackages.nixfmt-rfc-style
|
||||||
|
pkgs.buildPackages.shellcheck
|
||||||
pkgs.buildPackages.gdb
|
pkgs.buildPackages.gdb
|
||||||
]
|
]
|
||||||
++ lib.optional (stdenv.cc.isClang && stdenv.hostPlatform == stdenv.buildPlatform) (
|
++ lib.optional (stdenv.cc.isClang && stdenv.hostPlatform == stdenv.buildPlatform) (
|
||||||
|
|
|
||||||
|
|
@ -55,18 +55,22 @@ readonly NIX_INSTALLED_NIX="@nix@"
|
||||||
readonly NIX_INSTALLED_CACERT="@cacert@"
|
readonly NIX_INSTALLED_CACERT="@cacert@"
|
||||||
#readonly NIX_INSTALLED_NIX="/nix/store/j8dbv5w6jl34caywh2ygdy88knx1mdf7-nix-2.3.6"
|
#readonly NIX_INSTALLED_NIX="/nix/store/j8dbv5w6jl34caywh2ygdy88knx1mdf7-nix-2.3.6"
|
||||||
#readonly NIX_INSTALLED_CACERT="/nix/store/7dxhzymvy330i28ii676fl1pqwcahv2f-nss-cacert-3.49.2"
|
#readonly NIX_INSTALLED_CACERT="/nix/store/7dxhzymvy330i28ii676fl1pqwcahv2f-nss-cacert-3.49.2"
|
||||||
readonly EXTRACTED_NIX_PATH="$(dirname "$0")"
|
EXTRACTED_NIX_PATH="$(dirname "$0")"
|
||||||
|
readonly EXTRACTED_NIX_PATH
|
||||||
|
|
||||||
# allow to override identity change command
|
# allow to override identity change command
|
||||||
readonly NIX_BECOME=${NIX_BECOME:-sudo}
|
NIX_BECOME=${NIX_BECOME:-sudo}
|
||||||
|
readonly NIX_BECOME
|
||||||
|
|
||||||
readonly ROOT_HOME=~root
|
ROOT_HOME=~root
|
||||||
|
readonly ROOT_HOME
|
||||||
|
|
||||||
if [ -t 0 ] && [ -z "${NIX_INSTALLER_YES:-}" ]; then
|
if [ -t 0 ] && [ -z "${NIX_INSTALLER_YES:-}" ]; then
|
||||||
readonly IS_HEADLESS='no'
|
IS_HEADLESS='no'
|
||||||
else
|
else
|
||||||
readonly IS_HEADLESS='yes'
|
IS_HEADLESS='yes'
|
||||||
fi
|
fi
|
||||||
|
readonly IS_HEADLESS
|
||||||
|
|
||||||
headless() {
|
headless() {
|
||||||
if [ "$IS_HEADLESS" = "yes" ]; then
|
if [ "$IS_HEADLESS" = "yes" ]; then
|
||||||
|
|
@ -156,6 +160,7 @@ EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
nix_user_for_core() {
|
nix_user_for_core() {
|
||||||
|
# shellcheck disable=SC2059
|
||||||
printf "$NIX_BUILD_USER_NAME_TEMPLATE" "$1"
|
printf "$NIX_BUILD_USER_NAME_TEMPLATE" "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -381,10 +386,12 @@ _sudo() {
|
||||||
|
|
||||||
# Ensure that $TMPDIR exists if defined.
|
# Ensure that $TMPDIR exists if defined.
|
||||||
if [[ -n "${TMPDIR:-}" ]] && [[ ! -d "${TMPDIR:-}" ]]; then
|
if [[ -n "${TMPDIR:-}" ]] && [[ ! -d "${TMPDIR:-}" ]]; then
|
||||||
|
# shellcheck disable=SC2174
|
||||||
mkdir -m 0700 -p "${TMPDIR:-}"
|
mkdir -m 0700 -p "${TMPDIR:-}"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
readonly SCRATCH=$(mktemp -d)
|
SCRATCH=$(mktemp -d)
|
||||||
|
readonly SCRATCH
|
||||||
finish_cleanup() {
|
finish_cleanup() {
|
||||||
rm -rf "$SCRATCH"
|
rm -rf "$SCRATCH"
|
||||||
}
|
}
|
||||||
|
|
@ -677,7 +684,8 @@ create_directories() {
|
||||||
# hiding behind || true, and the general state
|
# hiding behind || true, and the general state
|
||||||
# should be one the user can repair once they
|
# should be one the user can repair once they
|
||||||
# figure out where chown is...
|
# figure out where chown is...
|
||||||
local get_chr_own="$(PATH="$(getconf PATH 2>/dev/null)" command -vp chown)"
|
local get_chr_own
|
||||||
|
get_chr_own="$(PATH="$(getconf PATH 2>/dev/null)" command -vp chown)"
|
||||||
if [[ -z "$get_chr_own" ]]; then
|
if [[ -z "$get_chr_own" ]]; then
|
||||||
get_chr_own="$(command -v chown)"
|
get_chr_own="$(command -v chown)"
|
||||||
fi
|
fi
|
||||||
|
|
@ -915,9 +923,11 @@ configure_shell_profile() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -e "$profile_target" ]; then
|
if [ -e "$profile_target" ]; then
|
||||||
shell_source_lines \
|
{
|
||||||
| _sudo "extend your $profile_target with nix-daemon settings" \
|
shell_source_lines
|
||||||
tee -a "$profile_target"
|
cat "$profile_target"
|
||||||
|
} | _sudo "extend your $profile_target with nix-daemon settings" \
|
||||||
|
tee "$profile_target"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
@ -1013,6 +1023,7 @@ main() {
|
||||||
|
|
||||||
# Set profile targets after OS-specific scripts are loaded
|
# Set profile targets after OS-specific scripts are loaded
|
||||||
if command -v poly_configure_default_profile_targets > /dev/null 2>&1; then
|
if command -v poly_configure_default_profile_targets > /dev/null 2>&1; then
|
||||||
|
# shellcheck disable=SC2207
|
||||||
PROFILE_TARGETS=($(poly_configure_default_profile_targets))
|
PROFILE_TARGETS=($(poly_configure_default_profile_targets))
|
||||||
else
|
else
|
||||||
PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/zshrc" "/etc/bash.bashrc" "/etc/zsh/zshrc")
|
PROFILE_TARGETS=("/etc/bashrc" "/etc/profile.d/nix.sh" "/etc/zshrc" "/etc/bash.bashrc" "/etc/zsh/zshrc")
|
||||||
|
|
|
||||||
|
|
@ -39,7 +39,7 @@ create_systemd_proxy_env() {
|
||||||
vars="http_proxy https_proxy ftp_proxy all_proxy no_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY ALL_PROXY NO_PROXY"
|
vars="http_proxy https_proxy ftp_proxy all_proxy no_proxy HTTP_PROXY HTTPS_PROXY FTP_PROXY ALL_PROXY NO_PROXY"
|
||||||
for v in $vars; do
|
for v in $vars; do
|
||||||
if [ "x${!v:-}" != "x" ]; then
|
if [ "x${!v:-}" != "x" ]; then
|
||||||
echo "Environment=${v}=$(escape_systemd_env ${!v})"
|
echo "Environment=${v}=$(escape_systemd_env "${!v}")"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -83,12 +83,22 @@ nlohmann::json SingleBuiltPath::Built::toJSON(const StoreDirConfig & store) cons
|
||||||
|
|
||||||
nlohmann::json SingleBuiltPath::toJSON(const StoreDirConfig & store) const
|
nlohmann::json SingleBuiltPath::toJSON(const StoreDirConfig & store) const
|
||||||
{
|
{
|
||||||
return std::visit([&](const auto & buildable) { return buildable.toJSON(store); }, raw());
|
return std::visit(
|
||||||
|
overloaded{
|
||||||
|
[&](const SingleBuiltPath::Opaque & o) -> nlohmann::json { return store.printStorePath(o.path); },
|
||||||
|
[&](const SingleBuiltPath::Built & b) { return b.toJSON(store); },
|
||||||
|
},
|
||||||
|
raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json BuiltPath::toJSON(const StoreDirConfig & store) const
|
nlohmann::json BuiltPath::toJSON(const StoreDirConfig & store) const
|
||||||
{
|
{
|
||||||
return std::visit([&](const auto & buildable) { return buildable.toJSON(store); }, raw());
|
return std::visit(
|
||||||
|
overloaded{
|
||||||
|
[&](const BuiltPath::Opaque & o) -> nlohmann::json { return store.printStorePath(o.path); },
|
||||||
|
[&](const BuiltPath::Built & b) { return b.toJSON(store); },
|
||||||
|
},
|
||||||
|
raw());
|
||||||
}
|
}
|
||||||
|
|
||||||
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
|
RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ config_priv_h = configure_file(
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'built-path.cc',
|
'built-path.cc',
|
||||||
|
|
|
||||||
|
|
@ -760,7 +760,7 @@ void NixRepl::loadFlake(const std::string & flakeRefS)
|
||||||
|
|
||||||
void NixRepl::initEnv()
|
void NixRepl::initEnv()
|
||||||
{
|
{
|
||||||
env = &state->allocEnv(envSize);
|
env = &state->mem.allocEnv(envSize);
|
||||||
env->up = &state->baseEnv;
|
env->up = &state->baseEnv;
|
||||||
displ = 0;
|
displ = 0;
|
||||||
staticEnv->vars.clear();
|
staticEnv->vars.clear();
|
||||||
|
|
@ -869,14 +869,8 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
|
||||||
|
|
||||||
Expr * NixRepl::parseString(std::string s)
|
Expr * NixRepl::parseString(std::string s)
|
||||||
{
|
{
|
||||||
return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NixRepl::evalString(std::string s, Value & v)
|
|
||||||
{
|
|
||||||
Expr * e;
|
|
||||||
try {
|
try {
|
||||||
e = parseString(s);
|
return state->parseExprFromString(std::move(s), state->rootPath("."), staticEnv);
|
||||||
} catch (ParseError & e) {
|
} catch (ParseError & e) {
|
||||||
if (e.msg().find("unexpected end of file") != std::string::npos)
|
if (e.msg().find("unexpected end of file") != std::string::npos)
|
||||||
// For parse errors on incomplete input, we continue waiting for the next line of
|
// For parse errors on incomplete input, we continue waiting for the next line of
|
||||||
|
|
@ -885,6 +879,11 @@ void NixRepl::evalString(std::string s, Value & v)
|
||||||
else
|
else
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NixRepl::evalString(std::string s, Value & v)
|
||||||
|
{
|
||||||
|
Expr * e = parseString(s);
|
||||||
e->eval(*state, *env, v);
|
e->eval(*state, *env, v);
|
||||||
state->forceValue(v, v.determinePos(noPos));
|
state->forceValue(v, v.determinePos(noPos));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ deps_public_maybe_subproject = [
|
||||||
subdir('nix-meson-build-support/subprojects')
|
subdir('nix-meson-build-support/subprojects')
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'nix_api_expr.cc',
|
'nix_api_expr.cc',
|
||||||
|
|
|
||||||
|
|
@ -137,7 +137,7 @@ nix_eval_state_builder * nix_eval_state_builder_new(nix_c_context * context, Sto
|
||||||
|
|
||||||
void nix_eval_state_builder_free(nix_eval_state_builder * builder)
|
void nix_eval_state_builder_free(nix_eval_state_builder * builder)
|
||||||
{
|
{
|
||||||
delete builder;
|
operator delete(builder, static_cast<std::align_val_t>(alignof(nix_eval_state_builder)));
|
||||||
}
|
}
|
||||||
|
|
||||||
nix_err nix_eval_state_builder_load(nix_c_context * context, nix_eval_state_builder * builder)
|
nix_err nix_eval_state_builder_load(nix_c_context * context, nix_eval_state_builder * builder)
|
||||||
|
|
@ -203,7 +203,7 @@ EvalState * nix_state_create(nix_c_context * context, const char ** lookupPath_c
|
||||||
|
|
||||||
void nix_state_free(EvalState * state)
|
void nix_state_free(EvalState * state)
|
||||||
{
|
{
|
||||||
delete state;
|
operator delete(state, static_cast<std::align_val_t>(alignof(EvalState)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#if NIX_USE_BOEHMGC
|
#if NIX_USE_BOEHMGC
|
||||||
|
|
|
||||||
|
|
@ -326,6 +326,10 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
|
||||||
try {
|
try {
|
||||||
auto & v = check_value_in(value);
|
auto & v = check_value_in(value);
|
||||||
assert(v.type() == nix::nList);
|
assert(v.type() == nix::nList);
|
||||||
|
if (ix >= v.listSize()) {
|
||||||
|
nix_set_err_msg(context, NIX_ERR_KEY, "list index out of bounds");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
auto * p = v.listView()[ix];
|
auto * p = v.listView()[ix];
|
||||||
nix_gc_incref(nullptr, p);
|
nix_gc_incref(nullptr, p);
|
||||||
if (p != nullptr)
|
if (p != nullptr)
|
||||||
|
|
@ -335,6 +339,26 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
|
||||||
NIXC_CATCH_ERRS_NULL
|
NIXC_CATCH_ERRS_NULL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nix_value *
|
||||||
|
nix_get_list_byidx_lazy(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix)
|
||||||
|
{
|
||||||
|
if (context)
|
||||||
|
context->last_err_code = NIX_OK;
|
||||||
|
try {
|
||||||
|
auto & v = check_value_in(value);
|
||||||
|
assert(v.type() == nix::nList);
|
||||||
|
if (ix >= v.listSize()) {
|
||||||
|
nix_set_err_msg(context, NIX_ERR_KEY, "list index out of bounds");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
auto * p = v.listView()[ix];
|
||||||
|
nix_gc_incref(nullptr, p);
|
||||||
|
// Note: intentionally NOT calling forceValue() to keep the element lazy
|
||||||
|
return as_nix_value_ptr(p);
|
||||||
|
}
|
||||||
|
NIXC_CATCH_ERRS_NULL
|
||||||
|
}
|
||||||
|
|
||||||
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
|
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
|
||||||
{
|
{
|
||||||
if (context)
|
if (context)
|
||||||
|
|
@ -355,6 +379,27 @@ nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value
|
||||||
NIXC_CATCH_ERRS_NULL
|
NIXC_CATCH_ERRS_NULL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nix_value *
|
||||||
|
nix_get_attr_byname_lazy(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
|
||||||
|
{
|
||||||
|
if (context)
|
||||||
|
context->last_err_code = NIX_OK;
|
||||||
|
try {
|
||||||
|
auto & v = check_value_in(value);
|
||||||
|
assert(v.type() == nix::nAttrs);
|
||||||
|
nix::Symbol s = state->state.symbols.create(name);
|
||||||
|
auto attr = v.attrs()->get(s);
|
||||||
|
if (attr) {
|
||||||
|
nix_gc_incref(nullptr, attr->value);
|
||||||
|
// Note: intentionally NOT calling forceValue() to keep the attribute lazy
|
||||||
|
return as_nix_value_ptr(attr->value);
|
||||||
|
}
|
||||||
|
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
NIXC_CATCH_ERRS_NULL
|
||||||
|
}
|
||||||
|
|
||||||
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
|
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name)
|
||||||
{
|
{
|
||||||
if (context)
|
if (context)
|
||||||
|
|
@ -371,13 +416,28 @@ bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalS
|
||||||
NIXC_CATCH_ERRS_RES(false);
|
NIXC_CATCH_ERRS_RES(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
nix_value * nix_get_attr_byidx(
|
static void collapse_attrset_layer_chain_if_needed(nix::Value & v, EvalState * state)
|
||||||
nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name)
|
{
|
||||||
|
auto & attrs = *v.attrs();
|
||||||
|
if (attrs.isLayered()) {
|
||||||
|
auto bindings = state->state.buildBindings(attrs.size());
|
||||||
|
std::ranges::copy(attrs, std::back_inserter(bindings));
|
||||||
|
v.mkAttrs(bindings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nix_value *
|
||||||
|
nix_get_attr_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name)
|
||||||
{
|
{
|
||||||
if (context)
|
if (context)
|
||||||
context->last_err_code = NIX_OK;
|
context->last_err_code = NIX_OK;
|
||||||
try {
|
try {
|
||||||
auto & v = check_value_in(value);
|
auto & v = check_value_in(value);
|
||||||
|
collapse_attrset_layer_chain_if_needed(v, state);
|
||||||
|
if (i >= v.attrs()->size()) {
|
||||||
|
nix_set_err_msg(context, NIX_ERR_KEY, "attribute index out of bounds");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
const nix::Attr & a = (*v.attrs())[i];
|
const nix::Attr & a = (*v.attrs())[i];
|
||||||
*name = state->state.symbols[a.name].c_str();
|
*name = state->state.symbols[a.name].c_str();
|
||||||
nix_gc_incref(nullptr, a.value);
|
nix_gc_incref(nullptr, a.value);
|
||||||
|
|
@ -387,13 +447,38 @@ nix_value * nix_get_attr_byidx(
|
||||||
NIXC_CATCH_ERRS_NULL
|
NIXC_CATCH_ERRS_NULL
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *
|
nix_value * nix_get_attr_byidx_lazy(
|
||||||
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i)
|
nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name)
|
||||||
{
|
{
|
||||||
if (context)
|
if (context)
|
||||||
context->last_err_code = NIX_OK;
|
context->last_err_code = NIX_OK;
|
||||||
try {
|
try {
|
||||||
auto & v = check_value_in(value);
|
auto & v = check_value_in(value);
|
||||||
|
collapse_attrset_layer_chain_if_needed(v, state);
|
||||||
|
if (i >= v.attrs()->size()) {
|
||||||
|
nix_set_err_msg(context, NIX_ERR_KEY, "attribute index out of bounds (Nix C API contract violation)");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
const nix::Attr & a = (*v.attrs())[i];
|
||||||
|
*name = state->state.symbols[a.name].c_str();
|
||||||
|
nix_gc_incref(nullptr, a.value);
|
||||||
|
// Note: intentionally NOT calling forceValue() to keep the attribute lazy
|
||||||
|
return as_nix_value_ptr(a.value);
|
||||||
|
}
|
||||||
|
NIXC_CATCH_ERRS_NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * nix_get_attr_name_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i)
|
||||||
|
{
|
||||||
|
if (context)
|
||||||
|
context->last_err_code = NIX_OK;
|
||||||
|
try {
|
||||||
|
auto & v = check_value_in(value);
|
||||||
|
collapse_attrset_layer_chain_if_needed(v, state);
|
||||||
|
if (i >= v.attrs()->size()) {
|
||||||
|
nix_set_err_msg(context, NIX_ERR_KEY, "attribute index out of bounds (Nix C API contract violation)");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
const nix::Attr & a = (*v.attrs())[i];
|
const nix::Attr & a = (*v.attrs())[i];
|
||||||
return state->state.symbols[a.name].c_str();
|
return state->state.symbols[a.name].c_str();
|
||||||
}
|
}
|
||||||
|
|
@ -594,7 +679,7 @@ nix_err nix_bindings_builder_insert(nix_c_context * context, BindingsBuilder * b
|
||||||
context->last_err_code = NIX_OK;
|
context->last_err_code = NIX_OK;
|
||||||
try {
|
try {
|
||||||
auto & v = check_value_not_null(value);
|
auto & v = check_value_not_null(value);
|
||||||
nix::Symbol s = bb->builder.state.get().symbols.create(name);
|
nix::Symbol s = bb->builder.symbols.get().create(name);
|
||||||
bb->builder.insert(s, &v);
|
bb->builder.insert(s, &v);
|
||||||
}
|
}
|
||||||
NIXC_CATCH_ERRS
|
NIXC_CATCH_ERRS
|
||||||
|
|
|
||||||
|
|
@ -265,10 +265,25 @@ ExternalValue * nix_get_external(nix_c_context * context, nix_value * value);
|
||||||
*/
|
*/
|
||||||
nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix);
|
nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix);
|
||||||
|
|
||||||
/** @brief Get an attr by name
|
/** @brief Get the ix'th element of a list without forcing evaluation of the element
|
||||||
|
*
|
||||||
|
* Returns the list element without forcing its evaluation, allowing access to lazy values.
|
||||||
|
* The list value itself must already be evaluated.
|
||||||
*
|
*
|
||||||
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
|
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
|
||||||
* @param[out] context Optional, stores error information
|
* @param[out] context Optional, stores error information
|
||||||
|
* @param[in] value Nix value to inspect (must be an evaluated list)
|
||||||
|
* @param[in] state nix evaluator state
|
||||||
|
* @param[in] ix list element to get
|
||||||
|
* @return value, NULL in case of errors
|
||||||
|
*/
|
||||||
|
nix_value *
|
||||||
|
nix_get_list_byidx_lazy(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int ix);
|
||||||
|
|
||||||
|
/** @brief Get an attr by name
|
||||||
|
*
|
||||||
|
* Use nix_gc_decref when you're done with the pointer
|
||||||
|
* @param[out] context Optional, stores error information
|
||||||
* @param[in] value Nix value to inspect
|
* @param[in] value Nix value to inspect
|
||||||
* @param[in] state nix evaluator state
|
* @param[in] state nix evaluator state
|
||||||
* @param[in] name attribute name
|
* @param[in] name attribute name
|
||||||
|
|
@ -276,6 +291,21 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
|
||||||
*/
|
*/
|
||||||
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
|
nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
|
||||||
|
|
||||||
|
/** @brief Get an attribute value by attribute name, without forcing evaluation of the attribute's value
|
||||||
|
*
|
||||||
|
* Returns the attribute value without forcing its evaluation, allowing access to lazy values.
|
||||||
|
* The attribute set value itself must already be evaluated.
|
||||||
|
*
|
||||||
|
* Use nix_gc_decref when you're done with the pointer
|
||||||
|
* @param[out] context Optional, stores error information
|
||||||
|
* @param[in] value Nix value to inspect (must be an evaluated attribute set)
|
||||||
|
* @param[in] state nix evaluator state
|
||||||
|
* @param[in] name attribute name
|
||||||
|
* @return value, NULL in case of errors
|
||||||
|
*/
|
||||||
|
nix_value *
|
||||||
|
nix_get_attr_byname_lazy(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
|
||||||
|
|
||||||
/** @brief Check if an attribute name exists on a value
|
/** @brief Check if an attribute name exists on a value
|
||||||
* @param[out] context Optional, stores error information
|
* @param[out] context Optional, stores error information
|
||||||
* @param[in] value Nix value to inspect
|
* @param[in] value Nix value to inspect
|
||||||
|
|
@ -285,11 +315,21 @@ nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value
|
||||||
*/
|
*/
|
||||||
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
|
bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalState * state, const char * name);
|
||||||
|
|
||||||
/** @brief Get an attribute by index in the sorted bindings
|
/** @brief Get an attribute by index
|
||||||
*
|
*
|
||||||
* Also gives you the name.
|
* Also gives you the name.
|
||||||
*
|
*
|
||||||
* Owned by the GC. Use nix_gc_decref when you're done with the pointer
|
* Attributes are returned in an unspecified order which is NOT suitable for
|
||||||
|
* reproducible operations. In Nix's domain, reproducibility is paramount. The caller
|
||||||
|
* is responsible for sorting the attributes or storing them in an ordered map to
|
||||||
|
* ensure deterministic behavior in your application.
|
||||||
|
*
|
||||||
|
* @note When Nix does sort attributes, which it does for virtually all intermediate
|
||||||
|
* operations and outputs, it uses byte-wise lexicographic order (equivalent to
|
||||||
|
* lexicographic order by Unicode scalar value for valid UTF-8). We recommend
|
||||||
|
* applying this same ordering for consistency.
|
||||||
|
*
|
||||||
|
* Use nix_gc_decref when you're done with the pointer
|
||||||
* @param[out] context Optional, stores error information
|
* @param[out] context Optional, stores error information
|
||||||
* @param[in] value Nix value to inspect
|
* @param[in] value Nix value to inspect
|
||||||
* @param[in] state nix evaluator state
|
* @param[in] state nix evaluator state
|
||||||
|
|
@ -297,12 +337,50 @@ bool nix_has_attr_byname(nix_c_context * context, const nix_value * value, EvalS
|
||||||
* @param[out] name will store a pointer to the attribute name
|
* @param[out] name will store a pointer to the attribute name
|
||||||
* @return value, NULL in case of errors
|
* @return value, NULL in case of errors
|
||||||
*/
|
*/
|
||||||
nix_value * nix_get_attr_byidx(
|
nix_value *
|
||||||
nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i, const char ** name);
|
nix_get_attr_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name);
|
||||||
|
|
||||||
/** @brief Get an attribute name by index in the sorted bindings
|
/** @brief Get an attribute by index, without forcing evaluation of the attribute's value
|
||||||
*
|
*
|
||||||
* Useful when you want the name but want to avoid evaluation.
|
* Also gives you the name.
|
||||||
|
*
|
||||||
|
* Returns the attribute value without forcing its evaluation, allowing access to lazy values.
|
||||||
|
* The attribute set value itself must already have been evaluated.
|
||||||
|
*
|
||||||
|
* Attributes are returned in an unspecified order which is NOT suitable for
|
||||||
|
* reproducible operations. In Nix's domain, reproducibility is paramount. The caller
|
||||||
|
* is responsible for sorting the attributes or storing them in an ordered map to
|
||||||
|
* ensure deterministic behavior in your application.
|
||||||
|
*
|
||||||
|
* @note When Nix does sort attributes, which it does for virtually all intermediate
|
||||||
|
* operations and outputs, it uses byte-wise lexicographic order (equivalent to
|
||||||
|
* lexicographic order by Unicode scalar value for valid UTF-8). We recommend
|
||||||
|
* applying this same ordering for consistency.
|
||||||
|
*
|
||||||
|
* Use nix_gc_decref when you're done with the pointer
|
||||||
|
* @param[out] context Optional, stores error information
|
||||||
|
* @param[in] value Nix value to inspect (must be an evaluated attribute set)
|
||||||
|
* @param[in] state nix evaluator state
|
||||||
|
* @param[in] i attribute index
|
||||||
|
* @param[out] name will store a pointer to the attribute name
|
||||||
|
* @return value, NULL in case of errors
|
||||||
|
*/
|
||||||
|
nix_value * nix_get_attr_byidx_lazy(
|
||||||
|
nix_c_context * context, nix_value * value, EvalState * state, unsigned int i, const char ** name);
|
||||||
|
|
||||||
|
/** @brief Get an attribute name by index
|
||||||
|
*
|
||||||
|
* Returns the attribute name without forcing evaluation of the attribute's value.
|
||||||
|
*
|
||||||
|
* Attributes are returned in an unspecified order which is NOT suitable for
|
||||||
|
* reproducible operations. In Nix's domain, reproducibility is paramount. The caller
|
||||||
|
* is responsible for sorting the attributes or storing them in an ordered map to
|
||||||
|
* ensure deterministic behavior in your application.
|
||||||
|
*
|
||||||
|
* @note When Nix does sort attributes, which it does for virtually all intermediate
|
||||||
|
* operations and outputs, it uses byte-wise lexicographic order (equivalent to
|
||||||
|
* lexicographic order by Unicode scalar value for valid UTF-8). We recommend
|
||||||
|
* applying this same ordering for consistency.
|
||||||
*
|
*
|
||||||
* Owned by the nix EvalState
|
* Owned by the nix EvalState
|
||||||
* @param[out] context Optional, stores error information
|
* @param[out] context Optional, stores error information
|
||||||
|
|
@ -311,8 +389,7 @@ nix_value * nix_get_attr_byidx(
|
||||||
* @param[in] i attribute index
|
* @param[in] i attribute index
|
||||||
* @return name, NULL in case of errors
|
* @return name, NULL in case of errors
|
||||||
*/
|
*/
|
||||||
const char *
|
const char * nix_get_attr_name_byidx(nix_c_context * context, nix_value * value, EvalState * state, unsigned int i);
|
||||||
nix_get_attr_name_byidx(nix_c_context * context, const nix_value * value, EvalState * state, unsigned int i);
|
|
||||||
|
|
||||||
/**@}*/
|
/**@}*/
|
||||||
/** @name Initializers
|
/** @name Initializers
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ rapidcheck = dependency('rapidcheck')
|
||||||
deps_public += rapidcheck
|
deps_public += rapidcheck
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'tests/value/context.cc',
|
'tests/value/context.cc',
|
||||||
|
|
|
||||||
|
|
@ -1,40 +1,15 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include <cstdlib>
|
|
||||||
#include "nix/store/globals.hh"
|
#include "nix/store/tests/test-main.hh"
|
||||||
#include "nix/util/logging.hh"
|
#include "nix/util/config-global.hh"
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
int main(int argc, char ** argv)
|
int main(int argc, char ** argv)
|
||||||
{
|
{
|
||||||
if (argc > 1 && std::string_view(argv[1]) == "__build-remote") {
|
auto res = testMainForBuidingPre(argc, argv);
|
||||||
printError("test-build-remote: not supported in libexpr unit tests");
|
if (res)
|
||||||
return 1;
|
return res;
|
||||||
}
|
|
||||||
|
|
||||||
// Disable build hook. We won't be testing remote builds in these unit tests. If we do, fix the above build hook.
|
|
||||||
settings.buildHook = {};
|
|
||||||
|
|
||||||
#ifdef __linux__ // should match the conditional around sandboxBuildDir declaration.
|
|
||||||
|
|
||||||
// When building and testing nix within the host's Nix sandbox, our store dir will be located in the host's
|
|
||||||
// sandboxBuildDir, e.g.: Host
|
|
||||||
// storeDir = /nix/store
|
|
||||||
// sandboxBuildDir = /build
|
|
||||||
// This process
|
|
||||||
// storeDir = /build/foo/bar/store
|
|
||||||
// sandboxBuildDir = /build
|
|
||||||
// However, we have a rule that the store dir must not be inside the storeDir, so we need to pick a different
|
|
||||||
// sandboxBuildDir.
|
|
||||||
settings.sandboxBuildDir = "/test-build-dir-instead-of-usual-build-dir";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
|
||||||
// Avoid this error, when already running in a sandbox:
|
|
||||||
// sandbox-exec: sandbox_apply: Operation not permitted
|
|
||||||
settings.sandboxMode = smDisabled;
|
|
||||||
setEnv("_NIX_TEST_NO_SANDBOX", "1");
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// For pipe operator tests in trivial.cc
|
// For pipe operator tests in trivial.cc
|
||||||
experimentalFeatureSettings.set("experimental-features", "pipe-operators");
|
experimentalFeatureSettings.set("experimental-features", "pipe-operators");
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ config_priv_h = configure_file(
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'derived-path.cc',
|
'derived-path.cc',
|
||||||
|
|
@ -82,7 +83,7 @@ this_exe = executable(
|
||||||
test(
|
test(
|
||||||
meson.project_name(),
|
meson.project_name(),
|
||||||
this_exe,
|
this_exe,
|
||||||
env : {
|
env : asan_test_options_env + {
|
||||||
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
|
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
|
||||||
},
|
},
|
||||||
protocol : 'gtest',
|
protocol : 'gtest',
|
||||||
|
|
|
||||||
|
|
@ -423,6 +423,55 @@ TEST_F(nix_api_expr_test, nix_expr_primop_bad_return_thunk)
|
||||||
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("badReturnThunk"));
|
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("badReturnThunk"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void primop_with_nix_err_key(
|
||||||
|
void * user_data, nix_c_context * context, EvalState * state, nix_value ** args, nix_value * ret)
|
||||||
|
{
|
||||||
|
nix_set_err_msg(context, NIX_ERR_KEY, "Test error from primop");
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(nix_api_expr_test, nix_expr_primop_nix_err_key_conversion)
|
||||||
|
{
|
||||||
|
// Test that NIX_ERR_KEY from a custom primop gets converted to a generic EvalError
|
||||||
|
//
|
||||||
|
// RATIONALE: NIX_ERR_KEY must not be propagated from custom primops because it would
|
||||||
|
// create semantic confusion. NIX_ERR_KEY indicates missing keys/indices in C API functions
|
||||||
|
// (like nix_get_attr_byname, nix_get_list_byidx). If custom primops could return NIX_ERR_KEY,
|
||||||
|
// an evaluation error would be indistinguishable from an actual missing attribute.
|
||||||
|
//
|
||||||
|
// For example, if nix_get_attr_byname returned NIX_ERR_KEY when the attribute is present
|
||||||
|
// but the value evaluation fails, callers expecting NIX_ERR_KEY to mean "missing attribute"
|
||||||
|
// would incorrectly handle evaluation failures as missing attributes. In places where
|
||||||
|
// missing attributes are tolerated (like optional attributes), this would cause the
|
||||||
|
// program to continue after swallowing the error, leading to silent failures.
|
||||||
|
PrimOp * primop = nix_alloc_primop(
|
||||||
|
ctx, primop_with_nix_err_key, 1, "testErrorPrimop", nullptr, "a test primop that sets NIX_ERR_KEY", nullptr);
|
||||||
|
assert_ctx_ok();
|
||||||
|
nix_value * primopValue = nix_alloc_value(ctx, state);
|
||||||
|
assert_ctx_ok();
|
||||||
|
nix_init_primop(ctx, primopValue, primop);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
nix_value * arg = nix_alloc_value(ctx, state);
|
||||||
|
assert_ctx_ok();
|
||||||
|
nix_init_int(ctx, arg, 42);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
nix_value * result = nix_alloc_value(ctx, state);
|
||||||
|
assert_ctx_ok();
|
||||||
|
nix_value_call(ctx, state, primopValue, arg, result);
|
||||||
|
|
||||||
|
// Verify that NIX_ERR_KEY gets converted to NIX_ERR_NIX_ERROR (generic evaluation error)
|
||||||
|
ASSERT_EQ(nix_err_code(ctx), NIX_ERR_NIX_ERROR);
|
||||||
|
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("Error from custom function"));
|
||||||
|
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("Test error from primop"));
|
||||||
|
ASSERT_THAT(nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("testErrorPrimop"));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
nix_gc_decref(ctx, primopValue);
|
||||||
|
nix_gc_decref(ctx, arg);
|
||||||
|
nix_gc_decref(ctx, result);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
|
TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
|
||||||
{
|
{
|
||||||
nix_value * n = nix_alloc_value(ctx, state);
|
nix_value * n = nix_alloc_value(ctx, state);
|
||||||
|
|
@ -437,4 +486,31 @@ TEST_F(nix_api_expr_test, nix_value_call_multi_no_args)
|
||||||
assert_ctx_ok();
|
assert_ctx_ok();
|
||||||
ASSERT_EQ(3, rInt);
|
ASSERT_EQ(3, rInt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(nix_api_expr_test, nix_expr_attrset_update)
|
||||||
|
{
|
||||||
|
nix_expr_eval_from_string(ctx, state, "{ a = 0; b = 2; } // { a = 1; b = 3; } // { a = 2; }", ".", value);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
ASSERT_EQ(nix_get_attrs_size(ctx, value), 2);
|
||||||
|
assert_ctx_ok();
|
||||||
|
std::array<std::pair<std::string_view, nix_value *>, 2> values;
|
||||||
|
for (unsigned int i = 0; i < 2; ++i) {
|
||||||
|
const char * name;
|
||||||
|
values[i].second = nix_get_attr_byidx(ctx, value, state, i, &name);
|
||||||
|
assert_ctx_ok();
|
||||||
|
values[i].first = name;
|
||||||
|
}
|
||||||
|
std::sort(values.begin(), values.end(), [](const auto & lhs, const auto & rhs) { return lhs.first < rhs.first; });
|
||||||
|
|
||||||
|
nix_value * a = values[0].second;
|
||||||
|
ASSERT_EQ("a", values[0].first);
|
||||||
|
ASSERT_EQ(nix_get_int(ctx, a), 2);
|
||||||
|
assert_ctx_ok();
|
||||||
|
nix_value * b = values[1].second;
|
||||||
|
ASSERT_EQ("b", values[1].first);
|
||||||
|
ASSERT_EQ(nix_get_int(ctx, b), 3);
|
||||||
|
assert_ctx_ok();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace nixC
|
} // namespace nixC
|
||||||
|
|
|
||||||
|
|
@ -162,6 +162,114 @@ TEST_F(nix_api_expr_test, nix_build_and_init_list)
|
||||||
nix_gc_decref(ctx, intValue);
|
nix_gc_decref(ctx, intValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(nix_api_expr_test, nix_get_list_byidx_large_indices)
|
||||||
|
{
|
||||||
|
// Create a small list to test extremely large out-of-bounds access
|
||||||
|
ListBuilder * builder = nix_make_list_builder(ctx, state, 2);
|
||||||
|
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||||
|
nix_init_int(ctx, intValue, 42);
|
||||||
|
nix_list_builder_insert(ctx, builder, 0, intValue);
|
||||||
|
nix_list_builder_insert(ctx, builder, 1, intValue);
|
||||||
|
nix_make_list(ctx, builder, value);
|
||||||
|
nix_list_builder_free(builder);
|
||||||
|
|
||||||
|
// Test extremely large indices that would definitely crash without bounds checking
|
||||||
|
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, 1000000));
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, UINT_MAX / 2));
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
ASSERT_EQ(nullptr, nix_get_list_byidx(ctx, value, state, UINT_MAX / 2 + 1000000));
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
nix_gc_decref(ctx, intValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(nix_api_expr_test, nix_get_list_byidx_lazy)
|
||||||
|
{
|
||||||
|
// Create a list with a throwing lazy element, an already-evaluated int, and a lazy function call
|
||||||
|
|
||||||
|
// 1. Throwing lazy element - create a function application thunk that will throw when forced
|
||||||
|
nix_value * throwingFn = nix_alloc_value(ctx, state);
|
||||||
|
nix_value * throwingValue = nix_alloc_value(ctx, state);
|
||||||
|
|
||||||
|
nix_expr_eval_from_string(
|
||||||
|
ctx,
|
||||||
|
state,
|
||||||
|
R"(
|
||||||
|
_: throw "This should not be evaluated by the lazy accessor"
|
||||||
|
)",
|
||||||
|
"<test>",
|
||||||
|
throwingFn);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
nix_init_apply(ctx, throwingValue, throwingFn, throwingFn);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
// 2. Already evaluated int (not lazy)
|
||||||
|
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||||
|
nix_init_int(ctx, intValue, 42);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
// 3. Lazy function application that would compute increment 5 = 6
|
||||||
|
nix_value * lazyApply = nix_alloc_value(ctx, state);
|
||||||
|
nix_value * incrementFn = nix_alloc_value(ctx, state);
|
||||||
|
nix_value * argFive = nix_alloc_value(ctx, state);
|
||||||
|
|
||||||
|
nix_expr_eval_from_string(ctx, state, "x: x + 1", "<test>", incrementFn);
|
||||||
|
assert_ctx_ok();
|
||||||
|
nix_init_int(ctx, argFive, 5);
|
||||||
|
|
||||||
|
// Create a lazy application: (x: x + 1) 5
|
||||||
|
nix_init_apply(ctx, lazyApply, incrementFn, argFive);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
ListBuilder * builder = nix_make_list_builder(ctx, state, 3);
|
||||||
|
nix_list_builder_insert(ctx, builder, 0, throwingValue);
|
||||||
|
nix_list_builder_insert(ctx, builder, 1, intValue);
|
||||||
|
nix_list_builder_insert(ctx, builder, 2, lazyApply);
|
||||||
|
nix_make_list(ctx, builder, value);
|
||||||
|
nix_list_builder_free(builder);
|
||||||
|
|
||||||
|
// Test 1: Lazy accessor should return the throwing element without forcing evaluation
|
||||||
|
nix_value * lazyThrowingElement = nix_get_list_byidx_lazy(ctx, value, state, 0);
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_NE(nullptr, lazyThrowingElement);
|
||||||
|
|
||||||
|
// Verify the element is still lazy by checking that forcing it throws
|
||||||
|
nix_value_force(ctx, state, lazyThrowingElement);
|
||||||
|
assert_ctx_err();
|
||||||
|
ASSERT_THAT(
|
||||||
|
nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("This should not be evaluated by the lazy accessor"));
|
||||||
|
|
||||||
|
// Test 2: Lazy accessor should return the already-evaluated int
|
||||||
|
nix_value * intElement = nix_get_list_byidx_lazy(ctx, value, state, 1);
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_NE(nullptr, intElement);
|
||||||
|
ASSERT_EQ(42, nix_get_int(ctx, intElement));
|
||||||
|
|
||||||
|
// Test 3: Lazy accessor should return the lazy function application without forcing
|
||||||
|
nix_value * lazyFunctionElement = nix_get_list_byidx_lazy(ctx, value, state, 2);
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_NE(nullptr, lazyFunctionElement);
|
||||||
|
|
||||||
|
// Force the lazy function application - should compute 5 + 1 = 6
|
||||||
|
nix_value_force(ctx, state, lazyFunctionElement);
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_EQ(6, nix_get_int(ctx, lazyFunctionElement));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
nix_gc_decref(ctx, throwingFn);
|
||||||
|
nix_gc_decref(ctx, throwingValue);
|
||||||
|
nix_gc_decref(ctx, intValue);
|
||||||
|
nix_gc_decref(ctx, lazyApply);
|
||||||
|
nix_gc_decref(ctx, incrementFn);
|
||||||
|
nix_gc_decref(ctx, argFive);
|
||||||
|
nix_gc_decref(ctx, lazyThrowingElement);
|
||||||
|
nix_gc_decref(ctx, intElement);
|
||||||
|
nix_gc_decref(ctx, lazyFunctionElement);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(nix_api_expr_test, nix_build_and_init_attr_invalid)
|
TEST_F(nix_api_expr_test, nix_build_and_init_attr_invalid)
|
||||||
{
|
{
|
||||||
ASSERT_EQ(nullptr, nix_get_attr_byname(ctx, nullptr, state, 0));
|
ASSERT_EQ(nullptr, nix_get_attr_byname(ctx, nullptr, state, 0));
|
||||||
|
|
@ -244,6 +352,225 @@ TEST_F(nix_api_expr_test, nix_build_and_init_attr)
|
||||||
free(out_name);
|
free(out_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(nix_api_expr_test, nix_get_attr_byidx_large_indices)
|
||||||
|
{
|
||||||
|
// Create a small attribute set to test extremely large out-of-bounds access
|
||||||
|
const char ** out_name = (const char **) malloc(sizeof(char *));
|
||||||
|
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, 2);
|
||||||
|
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||||
|
nix_init_int(ctx, intValue, 42);
|
||||||
|
nix_bindings_builder_insert(ctx, builder, "test", intValue);
|
||||||
|
nix_make_attrs(ctx, value, builder);
|
||||||
|
nix_bindings_builder_free(builder);
|
||||||
|
|
||||||
|
// Test extremely large indices that would definitely crash without bounds checking
|
||||||
|
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, 1000000, out_name));
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, UINT_MAX / 2, out_name));
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
ASSERT_EQ(nullptr, nix_get_attr_byidx(ctx, value, state, UINT_MAX / 2 + 1000000, out_name));
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
|
||||||
|
// Test nix_get_attr_name_byidx with large indices too
|
||||||
|
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, 1000000));
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, UINT_MAX / 2));
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
ASSERT_EQ(nullptr, nix_get_attr_name_byidx(ctx, value, state, UINT_MAX / 2 + 1000000));
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
nix_gc_decref(ctx, intValue);
|
||||||
|
free(out_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(nix_api_expr_test, nix_get_attr_byname_lazy)
|
||||||
|
{
|
||||||
|
// Create an attribute set with a throwing lazy attribute, an already-evaluated int, and a lazy function call
|
||||||
|
|
||||||
|
// 1. Throwing lazy element - create a function application thunk that will throw when forced
|
||||||
|
nix_value * throwingFn = nix_alloc_value(ctx, state);
|
||||||
|
nix_value * throwingValue = nix_alloc_value(ctx, state);
|
||||||
|
|
||||||
|
nix_expr_eval_from_string(
|
||||||
|
ctx,
|
||||||
|
state,
|
||||||
|
R"(
|
||||||
|
_: throw "This should not be evaluated by the lazy accessor"
|
||||||
|
)",
|
||||||
|
"<test>",
|
||||||
|
throwingFn);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
nix_init_apply(ctx, throwingValue, throwingFn, throwingFn);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
// 2. Already evaluated int (not lazy)
|
||||||
|
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||||
|
nix_init_int(ctx, intValue, 42);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
// 3. Lazy function application that would compute increment 7 = 8
|
||||||
|
nix_value * lazyApply = nix_alloc_value(ctx, state);
|
||||||
|
nix_value * incrementFn = nix_alloc_value(ctx, state);
|
||||||
|
nix_value * argSeven = nix_alloc_value(ctx, state);
|
||||||
|
|
||||||
|
nix_expr_eval_from_string(ctx, state, "x: x + 1", "<test>", incrementFn);
|
||||||
|
assert_ctx_ok();
|
||||||
|
nix_init_int(ctx, argSeven, 7);
|
||||||
|
|
||||||
|
// Create a lazy application: (x: x + 1) 7
|
||||||
|
nix_init_apply(ctx, lazyApply, incrementFn, argSeven);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, 3);
|
||||||
|
nix_bindings_builder_insert(ctx, builder, "throwing", throwingValue);
|
||||||
|
nix_bindings_builder_insert(ctx, builder, "normal", intValue);
|
||||||
|
nix_bindings_builder_insert(ctx, builder, "lazy", lazyApply);
|
||||||
|
nix_make_attrs(ctx, value, builder);
|
||||||
|
nix_bindings_builder_free(builder);
|
||||||
|
|
||||||
|
// Test 1: Lazy accessor should return the throwing attribute without forcing evaluation
|
||||||
|
nix_value * lazyThrowingAttr = nix_get_attr_byname_lazy(ctx, value, state, "throwing");
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_NE(nullptr, lazyThrowingAttr);
|
||||||
|
|
||||||
|
// Verify the attribute is still lazy by checking that forcing it throws
|
||||||
|
nix_value_force(ctx, state, lazyThrowingAttr);
|
||||||
|
assert_ctx_err();
|
||||||
|
ASSERT_THAT(
|
||||||
|
nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("This should not be evaluated by the lazy accessor"));
|
||||||
|
|
||||||
|
// Test 2: Lazy accessor should return the already-evaluated int
|
||||||
|
nix_value * intAttr = nix_get_attr_byname_lazy(ctx, value, state, "normal");
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_NE(nullptr, intAttr);
|
||||||
|
ASSERT_EQ(42, nix_get_int(ctx, intAttr));
|
||||||
|
|
||||||
|
// Test 3: Lazy accessor should return the lazy function application without forcing
|
||||||
|
nix_value * lazyFunctionAttr = nix_get_attr_byname_lazy(ctx, value, state, "lazy");
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_NE(nullptr, lazyFunctionAttr);
|
||||||
|
|
||||||
|
// Force the lazy function application - should compute 7 + 1 = 8
|
||||||
|
nix_value_force(ctx, state, lazyFunctionAttr);
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_EQ(8, nix_get_int(ctx, lazyFunctionAttr));
|
||||||
|
|
||||||
|
// Test 4: Missing attribute should return NULL with NIX_ERR_KEY
|
||||||
|
nix_value * missingAttr = nix_get_attr_byname_lazy(ctx, value, state, "nonexistent");
|
||||||
|
ASSERT_EQ(nullptr, missingAttr);
|
||||||
|
ASSERT_EQ(NIX_ERR_KEY, nix_err_code(ctx));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
nix_gc_decref(ctx, throwingFn);
|
||||||
|
nix_gc_decref(ctx, throwingValue);
|
||||||
|
nix_gc_decref(ctx, intValue);
|
||||||
|
nix_gc_decref(ctx, lazyApply);
|
||||||
|
nix_gc_decref(ctx, incrementFn);
|
||||||
|
nix_gc_decref(ctx, argSeven);
|
||||||
|
nix_gc_decref(ctx, lazyThrowingAttr);
|
||||||
|
nix_gc_decref(ctx, intAttr);
|
||||||
|
nix_gc_decref(ctx, lazyFunctionAttr);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(nix_api_expr_test, nix_get_attr_byidx_lazy)
|
||||||
|
{
|
||||||
|
// Create an attribute set with a throwing lazy attribute, an already-evaluated int, and a lazy function call
|
||||||
|
|
||||||
|
// 1. Throwing lazy element - create a function application thunk that will throw when forced
|
||||||
|
nix_value * throwingFn = nix_alloc_value(ctx, state);
|
||||||
|
nix_value * throwingValue = nix_alloc_value(ctx, state);
|
||||||
|
|
||||||
|
nix_expr_eval_from_string(
|
||||||
|
ctx,
|
||||||
|
state,
|
||||||
|
R"(
|
||||||
|
_: throw "This should not be evaluated by the lazy accessor"
|
||||||
|
)",
|
||||||
|
"<test>",
|
||||||
|
throwingFn);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
nix_init_apply(ctx, throwingValue, throwingFn, throwingFn);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
// 2. Already evaluated int (not lazy)
|
||||||
|
nix_value * intValue = nix_alloc_value(ctx, state);
|
||||||
|
nix_init_int(ctx, intValue, 99);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
// 3. Lazy function application that would compute increment 10 = 11
|
||||||
|
nix_value * lazyApply = nix_alloc_value(ctx, state);
|
||||||
|
nix_value * incrementFn = nix_alloc_value(ctx, state);
|
||||||
|
nix_value * argTen = nix_alloc_value(ctx, state);
|
||||||
|
|
||||||
|
nix_expr_eval_from_string(ctx, state, "x: x + 1", "<test>", incrementFn);
|
||||||
|
assert_ctx_ok();
|
||||||
|
nix_init_int(ctx, argTen, 10);
|
||||||
|
|
||||||
|
// Create a lazy application: (x: x + 1) 10
|
||||||
|
nix_init_apply(ctx, lazyApply, incrementFn, argTen);
|
||||||
|
assert_ctx_ok();
|
||||||
|
|
||||||
|
BindingsBuilder * builder = nix_make_bindings_builder(ctx, state, 3);
|
||||||
|
nix_bindings_builder_insert(ctx, builder, "a_throwing", throwingValue);
|
||||||
|
nix_bindings_builder_insert(ctx, builder, "b_normal", intValue);
|
||||||
|
nix_bindings_builder_insert(ctx, builder, "c_lazy", lazyApply);
|
||||||
|
nix_make_attrs(ctx, value, builder);
|
||||||
|
nix_bindings_builder_free(builder);
|
||||||
|
|
||||||
|
// Proper usage: first get the size and gather all attributes into a map
|
||||||
|
unsigned int attrCount = nix_get_attrs_size(ctx, value);
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_EQ(3u, attrCount);
|
||||||
|
|
||||||
|
// Gather all attributes into a map (proper contract usage)
|
||||||
|
std::map<std::string, nix_value *> attrMap;
|
||||||
|
const char * name;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < attrCount; i++) {
|
||||||
|
nix_value * attr = nix_get_attr_byidx_lazy(ctx, value, state, i, &name);
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_NE(nullptr, attr);
|
||||||
|
attrMap[std::string(name)] = attr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now test the gathered attributes
|
||||||
|
ASSERT_EQ(3u, attrMap.size());
|
||||||
|
ASSERT_TRUE(attrMap.count("a_throwing"));
|
||||||
|
ASSERT_TRUE(attrMap.count("b_normal"));
|
||||||
|
ASSERT_TRUE(attrMap.count("c_lazy"));
|
||||||
|
|
||||||
|
// Test 1: Throwing attribute should be lazy
|
||||||
|
nix_value * throwingAttr = attrMap["a_throwing"];
|
||||||
|
nix_value_force(ctx, state, throwingAttr);
|
||||||
|
assert_ctx_err();
|
||||||
|
ASSERT_THAT(
|
||||||
|
nix_err_msg(nullptr, ctx, nullptr), testing::HasSubstr("This should not be evaluated by the lazy accessor"));
|
||||||
|
|
||||||
|
// Test 2: Normal attribute should be already evaluated
|
||||||
|
nix_value * normalAttr = attrMap["b_normal"];
|
||||||
|
ASSERT_EQ(99, nix_get_int(ctx, normalAttr));
|
||||||
|
|
||||||
|
// Test 3: Lazy function should compute when forced
|
||||||
|
nix_value * lazyAttr = attrMap["c_lazy"];
|
||||||
|
nix_value_force(ctx, state, lazyAttr);
|
||||||
|
assert_ctx_ok();
|
||||||
|
ASSERT_EQ(11, nix_get_int(ctx, lazyAttr));
|
||||||
|
|
||||||
|
// Clean up
|
||||||
|
nix_gc_decref(ctx, throwingFn);
|
||||||
|
nix_gc_decref(ctx, throwingValue);
|
||||||
|
nix_gc_decref(ctx, intValue);
|
||||||
|
nix_gc_decref(ctx, lazyApply);
|
||||||
|
nix_gc_decref(ctx, incrementFn);
|
||||||
|
nix_gc_decref(ctx, argTen);
|
||||||
|
for (auto & pair : attrMap) {
|
||||||
|
nix_gc_decref(ctx, pair.second);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(nix_api_expr_test, nix_value_init)
|
TEST_F(nix_api_expr_test, nix_value_init)
|
||||||
{
|
{
|
||||||
// Setup
|
// Setup
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ mkMesonExecutable (finalAttrs: {
|
||||||
mkdir -p "$HOME"
|
mkdir -p "$HOME"
|
||||||
''
|
''
|
||||||
+ ''
|
+ ''
|
||||||
|
export ASAN_OPTIONS=abort_on_error=1:print_summary=1:detect_leaks=0
|
||||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||||
touch $out
|
touch $out
|
||||||
|
|
|
||||||
|
|
@ -642,7 +642,7 @@ class ToStringPrimOpTest : public PrimOpTest,
|
||||||
|
|
||||||
TEST_P(ToStringPrimOpTest, toString)
|
TEST_P(ToStringPrimOpTest, toString)
|
||||||
{
|
{
|
||||||
const auto [input, output] = GetParam();
|
const auto & [input, output] = GetParam();
|
||||||
auto v = eval(input);
|
auto v = eval(input);
|
||||||
ASSERT_THAT(v, IsStringEq(output));
|
ASSERT_THAT(v, IsStringEq(output));
|
||||||
}
|
}
|
||||||
|
|
@ -798,7 +798,7 @@ class CompareVersionsPrimOpTest : public PrimOpTest,
|
||||||
|
|
||||||
TEST_P(CompareVersionsPrimOpTest, compareVersions)
|
TEST_P(CompareVersionsPrimOpTest, compareVersions)
|
||||||
{
|
{
|
||||||
auto [expression, expectation] = GetParam();
|
const auto & [expression, expectation] = GetParam();
|
||||||
auto v = eval(expression);
|
auto v = eval(expression);
|
||||||
ASSERT_THAT(v, IsIntEq(expectation));
|
ASSERT_THAT(v, IsIntEq(expectation));
|
||||||
}
|
}
|
||||||
|
|
@ -834,7 +834,7 @@ class ParseDrvNamePrimOpTest
|
||||||
|
|
||||||
TEST_P(ParseDrvNamePrimOpTest, parseDrvName)
|
TEST_P(ParseDrvNamePrimOpTest, parseDrvName)
|
||||||
{
|
{
|
||||||
auto [input, expectedName, expectedVersion] = GetParam();
|
const auto & [input, expectedName, expectedVersion] = GetParam();
|
||||||
const auto expr = fmt("builtins.parseDrvName \"%1%\"", input);
|
const auto expr = fmt("builtins.parseDrvName \"%1%\"", input);
|
||||||
auto v = eval(expr);
|
auto v = eval(expr);
|
||||||
ASSERT_THAT(v, IsAttrsOfSize(2));
|
ASSERT_THAT(v, IsAttrsOfSize(2));
|
||||||
|
|
|
||||||
|
|
@ -10,32 +10,32 @@ Bindings Bindings::emptyBindings;
|
||||||
/* Allocate a new array of attributes for an attribute set with a specific
|
/* Allocate a new array of attributes for an attribute set with a specific
|
||||||
capacity. The space is implicitly reserved after the Bindings
|
capacity. The space is implicitly reserved after the Bindings
|
||||||
structure. */
|
structure. */
|
||||||
Bindings * EvalState::allocBindings(size_t capacity)
|
Bindings * EvalMemory::allocBindings(size_t capacity)
|
||||||
{
|
{
|
||||||
if (capacity == 0)
|
if (capacity == 0)
|
||||||
return &Bindings::emptyBindings;
|
return &Bindings::emptyBindings;
|
||||||
if (capacity > std::numeric_limits<Bindings::size_t>::max())
|
if (capacity > std::numeric_limits<Bindings::size_type>::max())
|
||||||
throw Error("attribute set of size %d is too big", capacity);
|
throw Error("attribute set of size %d is too big", capacity);
|
||||||
nrAttrsets++;
|
stats.nrAttrsets++;
|
||||||
nrAttrsInAttrsets += capacity;
|
stats.nrAttrsInAttrsets += capacity;
|
||||||
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
|
return new (allocBytes(sizeof(Bindings) + sizeof(Attr) * capacity)) Bindings();
|
||||||
}
|
}
|
||||||
|
|
||||||
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
|
Value & BindingsBuilder::alloc(Symbol name, PosIdx pos)
|
||||||
{
|
{
|
||||||
auto value = state.get().allocValue();
|
auto value = mem.get().allocValue();
|
||||||
bindings->push_back(Attr(name, value, pos));
|
bindings->push_back(Attr(name, value, pos));
|
||||||
return *value;
|
return *value;
|
||||||
}
|
}
|
||||||
|
|
||||||
Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos)
|
Value & BindingsBuilder::alloc(std::string_view name, PosIdx pos)
|
||||||
{
|
{
|
||||||
return alloc(state.get().symbols.create(name), pos);
|
return alloc(symbols.get().create(name), pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Bindings::sort()
|
void Bindings::sort()
|
||||||
{
|
{
|
||||||
std::sort(attrs, attrs + size_);
|
std::sort(attrs, attrs + numAttrs);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value & Value::mkAttrs(BindingsBuilder & bindings)
|
Value & Value::mkAttrs(BindingsBuilder & bindings)
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include "nix/expr/print.hh"
|
#include "nix/expr/print.hh"
|
||||||
#include "nix/fetchers/filtering-source-accessor.hh"
|
#include "nix/fetchers/filtering-source-accessor.hh"
|
||||||
#include "nix/util/memory-source-accessor.hh"
|
#include "nix/util/memory-source-accessor.hh"
|
||||||
|
#include "nix/util/mounted-source-accessor.hh"
|
||||||
#include "nix/expr/gc-small-vector.hh"
|
#include "nix/expr/gc-small-vector.hh"
|
||||||
#include "nix/util/url.hh"
|
#include "nix/util/url.hh"
|
||||||
#include "nix/fetchers/fetch-to-store.hh"
|
#include "nix/fetchers/fetch-to-store.hh"
|
||||||
|
|
@ -193,6 +194,15 @@ static Symbol getName(const AttrName & name, EvalState & state, Env & env)
|
||||||
|
|
||||||
static constexpr size_t BASE_ENV_SIZE = 128;
|
static constexpr size_t BASE_ENV_SIZE = 128;
|
||||||
|
|
||||||
|
EvalMemory::EvalMemory()
|
||||||
|
#if NIX_USE_BOEHMGC
|
||||||
|
: valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||||
|
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
assertGCInitialized();
|
||||||
|
}
|
||||||
|
|
||||||
EvalState::EvalState(
|
EvalState::EvalState(
|
||||||
const LookupPath & lookupPathFromArguments,
|
const LookupPath & lookupPathFromArguments,
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
|
|
@ -225,22 +235,25 @@ EvalState::EvalState(
|
||||||
*/
|
*/
|
||||||
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
|
{CanonPath(store->storeDir), store->getFSAccessor(settings.pureEval)},
|
||||||
}))
|
}))
|
||||||
, rootFS(({
|
, rootFS([&] {
|
||||||
|
auto accessor = [&]() -> decltype(rootFS) {
|
||||||
/* In pure eval mode, we provide a filesystem that only
|
/* In pure eval mode, we provide a filesystem that only
|
||||||
contains the Nix store.
|
contains the Nix store. */
|
||||||
|
if (settings.pureEval)
|
||||||
|
return storeFS;
|
||||||
|
|
||||||
If we have a chroot store and pure eval is not enabled,
|
/* If we have a chroot store and pure eval is not enabled,
|
||||||
use a union accessor to make the chroot store available
|
use a union accessor to make the chroot store available
|
||||||
at its logical location while still having the
|
at its logical location while still having the underlying
|
||||||
underlying directory available. This is necessary for
|
directory available. This is necessary for instance if
|
||||||
instance if we're evaluating a file from the physical
|
we're evaluating a file from the physical /nix/store
|
||||||
/nix/store while using a chroot store. */
|
while using a chroot store. */
|
||||||
auto accessor = getFSSourceAccessor();
|
|
||||||
|
|
||||||
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
|
auto realStoreDir = dirOf(store->toRealPath(StorePath::dummy));
|
||||||
if (settings.pureEval || store->storeDir != realStoreDir) {
|
if (store->storeDir != realStoreDir)
|
||||||
accessor = settings.pureEval ? storeFS : makeUnionSourceAccessor({accessor, storeFS});
|
return makeUnionSourceAccessor({getFSSourceAccessor(), storeFS});
|
||||||
}
|
|
||||||
|
return getFSSourceAccessor();
|
||||||
|
}();
|
||||||
|
|
||||||
/* Apply access control if needed. */
|
/* Apply access control if needed. */
|
||||||
if (settings.restrictEval || settings.pureEval)
|
if (settings.restrictEval || settings.pureEval)
|
||||||
|
|
@ -251,8 +264,8 @@ EvalState::EvalState(
|
||||||
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
|
throw RestrictedPathError("access to absolute path '%1%' is forbidden %2%", path, modeInformation);
|
||||||
});
|
});
|
||||||
|
|
||||||
accessor;
|
return accessor;
|
||||||
}))
|
}())
|
||||||
, corepkgsFS(make_ref<MemorySourceAccessor>())
|
, corepkgsFS(make_ref<MemorySourceAccessor>())
|
||||||
, internalFS(make_ref<MemorySourceAccessor>())
|
, internalFS(make_ref<MemorySourceAccessor>())
|
||||||
, derivationInternal{corepkgsFS->addFile(
|
, derivationInternal{corepkgsFS->addFile(
|
||||||
|
|
@ -270,12 +283,10 @@ EvalState::EvalState(
|
||||||
, fileEvalCache(make_ref<decltype(fileEvalCache)::element_type>())
|
, fileEvalCache(make_ref<decltype(fileEvalCache)::element_type>())
|
||||||
, regexCache(makeRegexCache())
|
, regexCache(makeRegexCache())
|
||||||
#if NIX_USE_BOEHMGC
|
#if NIX_USE_BOEHMGC
|
||||||
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
, baseEnvP(std::allocate_shared<Env *>(traceable_allocator<Env *>(), &mem.allocEnv(BASE_ENV_SIZE)))
|
||||||
, env1AllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
|
|
||||||
, baseEnvP(std::allocate_shared<Env *>(traceable_allocator<Env *>(), &allocEnv(BASE_ENV_SIZE)))
|
|
||||||
, baseEnv(**baseEnvP)
|
, baseEnv(**baseEnvP)
|
||||||
#else
|
#else
|
||||||
, baseEnv(allocEnv(BASE_ENV_SIZE))
|
, baseEnv(mem.allocEnv(BASE_ENV_SIZE))
|
||||||
#endif
|
#endif
|
||||||
, staticBaseEnv{std::make_shared<StaticEnv>(nullptr, nullptr)}
|
, staticBaseEnv{std::make_shared<StaticEnv>(nullptr, nullptr)}
|
||||||
{
|
{
|
||||||
|
|
@ -284,9 +295,8 @@ EvalState::EvalState(
|
||||||
|
|
||||||
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
|
countCalls = getEnv("NIX_COUNT_CALLS").value_or("0") != "0";
|
||||||
|
|
||||||
assertGCInitialized();
|
|
||||||
|
|
||||||
static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes");
|
static_assert(sizeof(Env) <= 16, "environment must be <= 16 bytes");
|
||||||
|
static_assert(sizeof(Counter) == 64, "counters must be 64 bytes");
|
||||||
|
|
||||||
/* Construct the Nix expression search path. */
|
/* Construct the Nix expression search path. */
|
||||||
assert(lookupPath.elements.empty());
|
assert(lookupPath.elements.empty());
|
||||||
|
|
@ -333,7 +343,7 @@ EvalState::EvalState(
|
||||||
|
|
||||||
EvalState::~EvalState() {}
|
EvalState::~EvalState() {}
|
||||||
|
|
||||||
void EvalState::allowPath(const Path & path)
|
void EvalState::allowPathLegacy(const Path & path)
|
||||||
{
|
{
|
||||||
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListSourceAccessor>())
|
if (auto rootFS2 = rootFS.dynamic_pointer_cast<AllowListSourceAccessor>())
|
||||||
rootFS2->allowPrefix(CanonPath(path));
|
rootFS2->allowPrefix(CanonPath(path));
|
||||||
|
|
@ -880,11 +890,10 @@ inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ListBuilder::ListBuilder(EvalState & state, size_t size)
|
ListBuilder::ListBuilder(size_t size)
|
||||||
: size(size)
|
: size(size)
|
||||||
, elems(size <= 2 ? inlineElems : (Value **) allocBytes(size * sizeof(Value *)))
|
, elems(size <= 2 ? inlineElems : (Value **) allocBytes(size * sizeof(Value *)))
|
||||||
{
|
{
|
||||||
state.nrListElems += size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value * EvalState::getBool(bool b)
|
Value * EvalState::getBool(bool b)
|
||||||
|
|
@ -892,7 +901,7 @@ Value * EvalState::getBool(bool b)
|
||||||
return b ? &Value::vTrue : &Value::vFalse;
|
return b ? &Value::vTrue : &Value::vFalse;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsigned long nrThunks = 0;
|
static Counter nrThunks;
|
||||||
|
|
||||||
static inline void mkThunk(Value & v, Env & env, Expr * expr)
|
static inline void mkThunk(Value & v, Env & env, Expr * expr)
|
||||||
{
|
{
|
||||||
|
|
@ -983,10 +992,6 @@ void EvalState::mkSingleDerivedPathString(const SingleDerivedPath & p, Value & v
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Create a thunk for the delayed computation of the given expression
|
|
||||||
in the given environment. But if the expression is a variable,
|
|
||||||
then look it up right away. This significantly reduces the number
|
|
||||||
of thunks allocated. */
|
|
||||||
Value * Expr::maybeThunk(EvalState & state, Env & env)
|
Value * Expr::maybeThunk(EvalState & state, Env & env)
|
||||||
{
|
{
|
||||||
Value * v = state.allocValue();
|
Value * v = state.allocValue();
|
||||||
|
|
@ -1035,9 +1040,10 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
|
||||||
* from a thunk, ensuring that every file is parsed/evaluated only
|
* from a thunk, ensuring that every file is parsed/evaluated only
|
||||||
* once (via the thunk stored in `EvalState::fileEvalCache`).
|
* once (via the thunk stored in `EvalState::fileEvalCache`).
|
||||||
*/
|
*/
|
||||||
struct ExprParseFile : Expr
|
struct ExprParseFile : Expr, gc
|
||||||
{
|
{
|
||||||
SourcePath & path;
|
// FIXME: make this a reference (see below).
|
||||||
|
SourcePath path;
|
||||||
bool mustBeTrivial;
|
bool mustBeTrivial;
|
||||||
|
|
||||||
ExprParseFile(SourcePath & path, bool mustBeTrivial)
|
ExprParseFile(SourcePath & path, bool mustBeTrivial)
|
||||||
|
|
@ -1088,14 +1094,18 @@ void EvalState::evalFile(const SourcePath & path, Value & v, bool mustBeTrivial)
|
||||||
}
|
}
|
||||||
|
|
||||||
Value * vExpr;
|
Value * vExpr;
|
||||||
ExprParseFile expr{*resolvedPath, mustBeTrivial};
|
// FIXME: put ExprParseFile on the stack instead of the heap once
|
||||||
|
// https://github.com/NixOS/nix/pull/13930 is merged. That will ensure
|
||||||
|
// the post-condition that `expr` is unreachable after
|
||||||
|
// `forceValue()` returns.
|
||||||
|
auto expr = new ExprParseFile{*resolvedPath, mustBeTrivial};
|
||||||
|
|
||||||
fileEvalCache->try_emplace_and_cvisit(
|
fileEvalCache->try_emplace_and_cvisit(
|
||||||
*resolvedPath,
|
*resolvedPath,
|
||||||
nullptr,
|
nullptr,
|
||||||
[&](auto & i) {
|
[&](auto & i) {
|
||||||
vExpr = allocValue();
|
vExpr = allocValue();
|
||||||
vExpr->mkThunk(&baseEnv, &expr);
|
vExpr->mkThunk(&baseEnv, expr);
|
||||||
i.second = vExpr;
|
i.second = vExpr;
|
||||||
},
|
},
|
||||||
[&](auto & i) { vExpr = i.second; });
|
[&](auto & i) { vExpr = i.second; });
|
||||||
|
|
@ -1177,7 +1187,7 @@ void ExprPath::eval(EvalState & state, Env & env, Value & v)
|
||||||
|
|
||||||
Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
|
Env * ExprAttrs::buildInheritFromEnv(EvalState & state, Env & up)
|
||||||
{
|
{
|
||||||
Env & inheritEnv = state.allocEnv(inheritFromExprs->size());
|
Env & inheritEnv = state.mem.allocEnv(inheritFromExprs->size());
|
||||||
inheritEnv.up = &up;
|
inheritEnv.up = &up;
|
||||||
|
|
||||||
Displacement displ = 0;
|
Displacement displ = 0;
|
||||||
|
|
@ -1196,7 +1206,7 @@ void ExprAttrs::eval(EvalState & state, Env & env, Value & v)
|
||||||
if (recursive) {
|
if (recursive) {
|
||||||
/* Create a new environment that contains the attributes in
|
/* Create a new environment that contains the attributes in
|
||||||
this `rec'. */
|
this `rec'. */
|
||||||
Env & env2(state.allocEnv(attrs.size()));
|
Env & env2(state.mem.allocEnv(attrs.size()));
|
||||||
env2.up = &env;
|
env2.up = &env;
|
||||||
dynamicEnv = &env2;
|
dynamicEnv = &env2;
|
||||||
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
|
Env * inheritEnv = inheritFromExprs ? buildInheritFromEnv(state, env2) : nullptr;
|
||||||
|
|
@ -1288,7 +1298,7 @@ void ExprLet::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
/* Create a new environment that contains the attributes in this
|
/* Create a new environment that contains the attributes in this
|
||||||
`let'. */
|
`let'. */
|
||||||
Env & env2(state.allocEnv(attrs->attrs.size()));
|
Env & env2(state.mem.allocEnv(attrs->attrs.size()));
|
||||||
env2.up = &env;
|
env2.up = &env;
|
||||||
|
|
||||||
Env * inheritEnv = attrs->inheritFromExprs ? attrs->buildInheritFromEnv(state, env2) : nullptr;
|
Env * inheritEnv = attrs->inheritFromExprs ? attrs->buildInheritFromEnv(state, env2) : nullptr;
|
||||||
|
|
@ -1494,7 +1504,7 @@ void EvalState::callFunction(Value & fun, std::span<Value *> args, Value & vRes,
|
||||||
ExprLambda & lambda(*vCur.lambda().fun);
|
ExprLambda & lambda(*vCur.lambda().fun);
|
||||||
|
|
||||||
auto size = (!lambda.arg ? 0 : 1) + (lambda.hasFormals() ? lambda.formals->formals.size() : 0);
|
auto size = (!lambda.arg ? 0 : 1) + (lambda.hasFormals() ? lambda.formals->formals.size() : 0);
|
||||||
Env & env2(allocEnv(size));
|
Env & env2(mem.allocEnv(size));
|
||||||
env2.up = vCur.lambda().env;
|
env2.up = vCur.lambda().env;
|
||||||
|
|
||||||
Displacement displ = 0;
|
Displacement displ = 0;
|
||||||
|
|
@ -1783,7 +1793,7 @@ https://nix.dev/manual/nix/stable/language/syntax.html#functions.)",
|
||||||
|
|
||||||
void ExprWith::eval(EvalState & state, Env & env, Value & v)
|
void ExprWith::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
Env & env2(state.allocEnv(1));
|
Env & env2(state.mem.allocEnv(1));
|
||||||
env2.up = &env;
|
env2.up = &env;
|
||||||
env2.values[0] = attrs->maybeThunk(state, env);
|
env2.values[0] = attrs->maybeThunk(state, env);
|
||||||
|
|
||||||
|
|
@ -1865,51 +1875,113 @@ void ExprOpImpl::eval(EvalState & state, Env & env, Value & v)
|
||||||
|| state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
|
|| state.evalBool(env, e2, pos, "in the right operand of the IMPL (->) operator"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
|
void ExprOpUpdate::eval(EvalState & state, Value & v, Value & v1, Value & v2)
|
||||||
{
|
{
|
||||||
Value v1, v2;
|
|
||||||
state.evalAttrs(env, e1, v1, pos, "in the left operand of the update (//) operator");
|
|
||||||
state.evalAttrs(env, e2, v2, pos, "in the right operand of the update (//) operator");
|
|
||||||
|
|
||||||
state.nrOpUpdates++;
|
state.nrOpUpdates++;
|
||||||
|
|
||||||
if (v1.attrs()->size() == 0) {
|
const Bindings & bindings1 = *v1.attrs();
|
||||||
|
if (bindings1.empty()) {
|
||||||
v = v2;
|
v = v2;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (v2.attrs()->size() == 0) {
|
|
||||||
|
const Bindings & bindings2 = *v2.attrs();
|
||||||
|
if (bindings2.empty()) {
|
||||||
v = v1;
|
v = v1;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto attrs = state.buildBindings(v1.attrs()->size() + v2.attrs()->size());
|
/* Simple heuristic for determining whether attrs2 should be "layered" on top of
|
||||||
|
attrs1 instead of copying to a new Bindings. */
|
||||||
|
bool shouldLayer = [&]() -> bool {
|
||||||
|
if (bindings1.isLayerListFull())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (bindings2.size() > state.settings.bindingsUpdateLayerRhsSizeThreshold)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}();
|
||||||
|
|
||||||
|
if (shouldLayer) {
|
||||||
|
auto attrs = state.buildBindings(bindings2.size());
|
||||||
|
attrs.layerOnTopOf(bindings1);
|
||||||
|
|
||||||
|
std::ranges::copy(bindings2, std::back_inserter(attrs));
|
||||||
|
v.mkAttrs(attrs.alreadySorted());
|
||||||
|
|
||||||
|
state.nrOpUpdateValuesCopied += bindings2.size();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto attrs = state.buildBindings(bindings1.size() + bindings2.size());
|
||||||
|
|
||||||
/* Merge the sets, preferring values from the second set. Make
|
/* Merge the sets, preferring values from the second set. Make
|
||||||
sure to keep the resulting vector in sorted order. */
|
sure to keep the resulting vector in sorted order. */
|
||||||
auto i = v1.attrs()->begin();
|
auto i = bindings1.begin();
|
||||||
auto j = v2.attrs()->begin();
|
auto j = bindings2.begin();
|
||||||
|
|
||||||
while (i != v1.attrs()->end() && j != v2.attrs()->end()) {
|
while (i != bindings1.end() && j != bindings2.end()) {
|
||||||
if (i->name == j->name) {
|
if (i->name == j->name) {
|
||||||
attrs.insert(*j);
|
attrs.insert(*j);
|
||||||
++i;
|
++i;
|
||||||
++j;
|
++j;
|
||||||
} else if (i->name < j->name)
|
} else if (i->name < j->name) {
|
||||||
attrs.insert(*i++);
|
attrs.insert(*i);
|
||||||
else
|
++i;
|
||||||
attrs.insert(*j++);
|
} else {
|
||||||
|
attrs.insert(*j);
|
||||||
|
++j;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (i != v1.attrs()->end())
|
while (i != bindings1.end()) {
|
||||||
attrs.insert(*i++);
|
attrs.insert(*i);
|
||||||
while (j != v2.attrs()->end())
|
++i;
|
||||||
attrs.insert(*j++);
|
}
|
||||||
|
|
||||||
|
while (j != bindings2.end()) {
|
||||||
|
attrs.insert(*j);
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
|
||||||
v.mkAttrs(attrs.alreadySorted());
|
v.mkAttrs(attrs.alreadySorted());
|
||||||
|
|
||||||
state.nrOpUpdateValuesCopied += v.attrs()->size();
|
state.nrOpUpdateValuesCopied += v.attrs()->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ExprOpUpdate::eval(EvalState & state, Env & env, Value & v)
|
||||||
|
{
|
||||||
|
UpdateQueue q;
|
||||||
|
evalForUpdate(state, env, q);
|
||||||
|
|
||||||
|
v.mkAttrs(&Bindings::emptyBindings);
|
||||||
|
for (auto & rhs : std::views::reverse(q)) {
|
||||||
|
/* Remember that queue is sorted rightmost attrset first. */
|
||||||
|
eval(state, /*v=*/v, /*v1=*/v, /*v2=*/rhs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Expr::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx)
|
||||||
|
{
|
||||||
|
Value v;
|
||||||
|
state.evalAttrs(env, this, v, getPos(), errorCtx);
|
||||||
|
q.push_back(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExprOpUpdate::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q)
|
||||||
|
{
|
||||||
|
/* Output rightmost attrset first to the merge queue as the one
|
||||||
|
with the most priority. */
|
||||||
|
e2->evalForUpdate(state, env, q, "in the right operand of the update (//) operator");
|
||||||
|
e1->evalForUpdate(state, env, q, "in the left operand of the update (//) operator");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExprOpUpdate::evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx)
|
||||||
|
{
|
||||||
|
evalForUpdate(state, env, q);
|
||||||
|
}
|
||||||
|
|
||||||
void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
|
void ExprOpConcatLists::eval(EvalState & state, Env & env, Value & v)
|
||||||
{
|
{
|
||||||
Value v1;
|
Value v1;
|
||||||
|
|
@ -2828,11 +2900,11 @@ bool EvalState::fullGC()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Counter::enabled = getEnv("NIX_SHOW_STATS").value_or("0") != "0";
|
||||||
|
|
||||||
void EvalState::maybePrintStats()
|
void EvalState::maybePrintStats()
|
||||||
{
|
{
|
||||||
bool showStats = getEnv("NIX_SHOW_STATS").value_or("0") != "0";
|
if (Counter::enabled) {
|
||||||
|
|
||||||
if (showStats) {
|
|
||||||
// Make the final heap size more deterministic.
|
// Make the final heap size more deterministic.
|
||||||
#if NIX_USE_BOEHMGC
|
#if NIX_USE_BOEHMGC
|
||||||
if (!fullGC()) {
|
if (!fullGC()) {
|
||||||
|
|
@ -2848,10 +2920,12 @@ void EvalState::printStatistics()
|
||||||
std::chrono::microseconds cpuTimeDuration = getCpuUserTime();
|
std::chrono::microseconds cpuTimeDuration = getCpuUserTime();
|
||||||
float cpuTime = std::chrono::duration_cast<std::chrono::duration<float>>(cpuTimeDuration).count();
|
float cpuTime = std::chrono::duration_cast<std::chrono::duration<float>>(cpuTimeDuration).count();
|
||||||
|
|
||||||
uint64_t bEnvs = nrEnvs * sizeof(Env) + nrValuesInEnvs * sizeof(Value *);
|
auto & memstats = mem.getStats();
|
||||||
uint64_t bLists = nrListElems * sizeof(Value *);
|
|
||||||
uint64_t bValues = nrValues * sizeof(Value);
|
uint64_t bEnvs = memstats.nrEnvs * sizeof(Env) + memstats.nrValuesInEnvs * sizeof(Value *);
|
||||||
uint64_t bAttrsets = nrAttrsets * sizeof(Bindings) + nrAttrsInAttrsets * sizeof(Attr);
|
uint64_t bLists = memstats.nrListElems * sizeof(Value *);
|
||||||
|
uint64_t bValues = memstats.nrValues * sizeof(Value);
|
||||||
|
uint64_t bAttrsets = memstats.nrAttrsets * sizeof(Bindings) + memstats.nrAttrsInAttrsets * sizeof(Attr);
|
||||||
|
|
||||||
#if NIX_USE_BOEHMGC
|
#if NIX_USE_BOEHMGC
|
||||||
GC_word heapSize, totalBytes;
|
GC_word heapSize, totalBytes;
|
||||||
|
|
@ -2877,18 +2951,18 @@ void EvalState::printStatistics()
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
topObj["envs"] = {
|
topObj["envs"] = {
|
||||||
{"number", nrEnvs},
|
{"number", memstats.nrEnvs.load()},
|
||||||
{"elements", nrValuesInEnvs},
|
{"elements", memstats.nrValuesInEnvs.load()},
|
||||||
{"bytes", bEnvs},
|
{"bytes", bEnvs},
|
||||||
};
|
};
|
||||||
topObj["nrExprs"] = Expr::nrExprs;
|
topObj["nrExprs"] = Expr::nrExprs.load();
|
||||||
topObj["list"] = {
|
topObj["list"] = {
|
||||||
{"elements", nrListElems},
|
{"elements", memstats.nrListElems.load()},
|
||||||
{"bytes", bLists},
|
{"bytes", bLists},
|
||||||
{"concats", nrListConcats},
|
{"concats", nrListConcats.load()},
|
||||||
};
|
};
|
||||||
topObj["values"] = {
|
topObj["values"] = {
|
||||||
{"number", nrValues},
|
{"number", memstats.nrValues.load()},
|
||||||
{"bytes", bValues},
|
{"bytes", bValues},
|
||||||
};
|
};
|
||||||
topObj["symbols"] = {
|
topObj["symbols"] = {
|
||||||
|
|
@ -2896,9 +2970,9 @@ void EvalState::printStatistics()
|
||||||
{"bytes", symbols.totalSize()},
|
{"bytes", symbols.totalSize()},
|
||||||
};
|
};
|
||||||
topObj["sets"] = {
|
topObj["sets"] = {
|
||||||
{"number", nrAttrsets},
|
{"number", memstats.nrAttrsets.load()},
|
||||||
{"bytes", bAttrsets},
|
{"bytes", bAttrsets},
|
||||||
{"elements", nrAttrsInAttrsets},
|
{"elements", memstats.nrAttrsInAttrsets.load()},
|
||||||
};
|
};
|
||||||
topObj["sizes"] = {
|
topObj["sizes"] = {
|
||||||
{"Env", sizeof(Env)},
|
{"Env", sizeof(Env)},
|
||||||
|
|
@ -2906,13 +2980,13 @@ void EvalState::printStatistics()
|
||||||
{"Bindings", sizeof(Bindings)},
|
{"Bindings", sizeof(Bindings)},
|
||||||
{"Attr", sizeof(Attr)},
|
{"Attr", sizeof(Attr)},
|
||||||
};
|
};
|
||||||
topObj["nrOpUpdates"] = nrOpUpdates;
|
topObj["nrOpUpdates"] = nrOpUpdates.load();
|
||||||
topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied;
|
topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied.load();
|
||||||
topObj["nrThunks"] = nrThunks;
|
topObj["nrThunks"] = nrThunks.load();
|
||||||
topObj["nrAvoided"] = nrAvoided;
|
topObj["nrAvoided"] = nrAvoided.load();
|
||||||
topObj["nrLookups"] = nrLookups;
|
topObj["nrLookups"] = nrLookups.load();
|
||||||
topObj["nrPrimOpCalls"] = nrPrimOpCalls;
|
topObj["nrPrimOpCalls"] = nrPrimOpCalls.load();
|
||||||
topObj["nrFunctionCalls"] = nrFunctionCalls;
|
topObj["nrFunctionCalls"] = nrFunctionCalls.load();
|
||||||
#if NIX_USE_BOEHMGC
|
#if NIX_USE_BOEHMGC
|
||||||
topObj["gc"] = {
|
topObj["gc"] = {
|
||||||
{"heapSize", heapSize},
|
{"heapSize", heapSize},
|
||||||
|
|
@ -3113,7 +3187,7 @@ std::optional<SourcePath> EvalState::resolveLookupPathPath(const LookupPath::Pat
|
||||||
|
|
||||||
/* Allow access to paths in the search path. */
|
/* Allow access to paths in the search path. */
|
||||||
if (initAccessControl) {
|
if (initAccessControl) {
|
||||||
allowPath(path.path.abs());
|
allowPathLegacy(path.path.abs());
|
||||||
if (store->isInStore(path.path.abs())) {
|
if (store->isInStore(path.path.abs())) {
|
||||||
try {
|
try {
|
||||||
allowClosure(store->toStorePath(path.path.abs()).first);
|
allowClosure(store->toStorePath(path.path.abs()).first);
|
||||||
|
|
@ -3143,7 +3217,8 @@ Expr * EvalState::parse(
|
||||||
docComments = &it->second;
|
docComments = &it->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto result = parseExprFromBuf(text, length, origin, basePath, symbols, settings, positions, *docComments, rootFS);
|
auto result = parseExprFromBuf(
|
||||||
|
text, length, origin, basePath, mem.exprs.alloc, symbols, settings, positions, *docComments, rootFS);
|
||||||
|
|
||||||
result->bindVars(*this, staticEnv);
|
result->bindVars(*this, staticEnv);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,16 @@
|
||||||
#include "nix/expr/nixexpr.hh"
|
#include "nix/expr/nixexpr.hh"
|
||||||
#include "nix/expr/symbol-table.hh"
|
#include "nix/expr/symbol-table.hh"
|
||||||
|
|
||||||
|
#include <boost/container/static_vector.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <concepts>
|
#include <ranges>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
class EvalState;
|
class EvalMemory;
|
||||||
struct Value;
|
struct Value;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,11 +51,18 @@ static_assert(
|
||||||
* 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
|
||||||
* the number of elements already inserted in this structure.
|
* the number of elements already inserted in this structure.
|
||||||
|
*
|
||||||
|
* Bindings can be efficiently `//`-composed into an intrusive linked list of "layers"
|
||||||
|
* that saves on copies and allocations. Each lookup (@see Bindings::get) traverses
|
||||||
|
* this linked list until a matching attribute is found (thus overlays earlier in
|
||||||
|
* the list take precedence). For iteration over the whole Bindings, an on-the-fly
|
||||||
|
* k-way merge is performed by Bindings::iterator class.
|
||||||
*/
|
*/
|
||||||
class Bindings
|
class Bindings
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef uint32_t size_t;
|
using size_type = uint32_t;
|
||||||
|
|
||||||
PosIdx pos;
|
PosIdx pos;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -62,7 +72,32 @@ public:
|
||||||
static Bindings emptyBindings;
|
static Bindings emptyBindings;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
size_t size_ = 0;
|
/**
|
||||||
|
* Number of attributes in the attrs FAM (Flexible Array Member).
|
||||||
|
*/
|
||||||
|
size_type numAttrs = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Number of attributes with unique names in the layer chain.
|
||||||
|
*
|
||||||
|
* This is the *real* user-facing size of bindings, whereas @ref numAttrs is
|
||||||
|
* an implementation detail of the data structure.
|
||||||
|
*/
|
||||||
|
size_type numAttrsInChain = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Length of the layers list.
|
||||||
|
*/
|
||||||
|
uint32_t numLayers = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bindings that this attrset is "layered" on top of.
|
||||||
|
*/
|
||||||
|
const Bindings * baseLayer = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flexible array member of attributes.
|
||||||
|
*/
|
||||||
Attr attrs[0];
|
Attr attrs[0];
|
||||||
|
|
||||||
Bindings() = default;
|
Bindings() = default;
|
||||||
|
|
@ -71,15 +106,22 @@ private:
|
||||||
Bindings & operator=(const Bindings &) = delete;
|
Bindings & operator=(const Bindings &) = delete;
|
||||||
Bindings & operator=(Bindings &&) = delete;
|
Bindings & operator=(Bindings &&) = delete;
|
||||||
|
|
||||||
|
friend class BindingsBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maximum length of the Bindings layer chains.
|
||||||
|
*/
|
||||||
|
static constexpr unsigned maxLayers = 8;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
size_t size() const
|
size_type size() const
|
||||||
{
|
{
|
||||||
return size_;
|
return numAttrsInChain;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool empty() const
|
bool empty() const
|
||||||
{
|
{
|
||||||
return !size_;
|
return size() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
class iterator
|
class iterator
|
||||||
|
|
@ -94,77 +136,276 @@ public:
|
||||||
friend class Bindings;
|
friend class Bindings;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
pointer ptr = nullptr;
|
struct BindingsCursor
|
||||||
|
|
||||||
explicit iterator(pointer ptr)
|
|
||||||
: ptr(ptr)
|
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Attr that the cursor currently points to.
|
||||||
|
*/
|
||||||
|
pointer current;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* One past the end pointer to the contiguous buffer of Attrs.
|
||||||
|
*/
|
||||||
|
pointer end;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority of the value. Lesser values have more priority (i.e. they override
|
||||||
|
* attributes that appear later in the linked list of Bindings).
|
||||||
|
*/
|
||||||
|
uint32_t priority;
|
||||||
|
|
||||||
|
pointer operator->() const noexcept
|
||||||
|
{
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
reference get() const noexcept
|
||||||
|
{
|
||||||
|
return *current;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool empty() const noexcept
|
||||||
|
{
|
||||||
|
return current == end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void increment() noexcept
|
||||||
|
{
|
||||||
|
++current;
|
||||||
|
}
|
||||||
|
|
||||||
|
void consume(Symbol name) noexcept
|
||||||
|
{
|
||||||
|
while (!empty() && current->name <= name)
|
||||||
|
++current;
|
||||||
|
}
|
||||||
|
|
||||||
|
GENERATE_CMP(BindingsCursor, me->current->name, me->priority)
|
||||||
|
};
|
||||||
|
|
||||||
|
using QueueStorageType = boost::container::static_vector<BindingsCursor, maxLayers>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comparator implementing the override priority / name ordering
|
||||||
|
* for BindingsCursor.
|
||||||
|
*/
|
||||||
|
static constexpr auto comp = std::greater<BindingsCursor>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A priority queue used to implement an on-the-fly k-way merge.
|
||||||
|
*/
|
||||||
|
QueueStorageType cursorHeap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The attribute the iterator currently points to.
|
||||||
|
*/
|
||||||
|
pointer current = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether iterating over a single attribute and not a merge chain.
|
||||||
|
*/
|
||||||
|
bool doMerge = true;
|
||||||
|
|
||||||
|
void push(BindingsCursor cursor) noexcept
|
||||||
|
{
|
||||||
|
cursorHeap.push_back(cursor);
|
||||||
|
std::ranges::make_heap(cursorHeap, comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] BindingsCursor pop() noexcept
|
||||||
|
{
|
||||||
|
std::ranges::pop_heap(cursorHeap, comp);
|
||||||
|
auto cursor = cursorHeap.back();
|
||||||
|
cursorHeap.pop_back();
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator & finished() noexcept
|
||||||
|
{
|
||||||
|
current = nullptr;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void next(BindingsCursor cursor) noexcept
|
||||||
|
{
|
||||||
|
current = &cursor.get();
|
||||||
|
cursor.increment();
|
||||||
|
|
||||||
|
if (!cursor.empty())
|
||||||
|
push(cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<BindingsCursor> consumeAllUntilCurrentName() noexcept
|
||||||
|
{
|
||||||
|
auto cursor = pop();
|
||||||
|
Symbol lastHandledName = current->name;
|
||||||
|
|
||||||
|
while (cursor->name <= lastHandledName) {
|
||||||
|
cursor.consume(lastHandledName);
|
||||||
|
if (!cursor.empty())
|
||||||
|
push(cursor);
|
||||||
|
|
||||||
|
if (cursorHeap.empty())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
cursor = pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
explicit iterator(const Bindings & attrs) noexcept
|
||||||
|
: doMerge(attrs.baseLayer)
|
||||||
|
{
|
||||||
|
auto pushBindings = [this, priority = unsigned{0}](const Bindings & layer) mutable {
|
||||||
|
auto first = layer.attrs;
|
||||||
|
push(
|
||||||
|
BindingsCursor{
|
||||||
|
.current = first,
|
||||||
|
.end = first + layer.numAttrs,
|
||||||
|
.priority = priority++,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!doMerge) {
|
||||||
|
if (attrs.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
current = attrs.attrs;
|
||||||
|
pushBindings(attrs);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Bindings * layer = &attrs;
|
||||||
|
while (layer) {
|
||||||
|
if (layer->numAttrs != 0)
|
||||||
|
pushBindings(*layer);
|
||||||
|
layer = layer->baseLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cursorHeap.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
next(pop());
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
iterator() = default;
|
iterator() = default;
|
||||||
|
|
||||||
reference operator*() const
|
reference operator*() const noexcept
|
||||||
{
|
{
|
||||||
return *ptr;
|
return *current;
|
||||||
}
|
}
|
||||||
|
|
||||||
const value_type * operator->() const
|
pointer operator->() const noexcept
|
||||||
{
|
{
|
||||||
return ptr;
|
return current;
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator & operator++()
|
iterator & operator++() noexcept
|
||||||
{
|
{
|
||||||
++ptr;
|
if (!doMerge) {
|
||||||
|
++current;
|
||||||
|
if (current == cursorHeap.front().end)
|
||||||
|
return finished();
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator operator++(int)
|
if (cursorHeap.empty())
|
||||||
{
|
return finished();
|
||||||
pointer tmp = ptr;
|
|
||||||
++*this;
|
auto cursor = consumeAllUntilCurrentName();
|
||||||
return iterator(tmp);
|
if (!cursor)
|
||||||
|
return finished();
|
||||||
|
|
||||||
|
next(*cursor);
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const iterator & rhs) const = default;
|
iterator operator++(int) noexcept
|
||||||
|
{
|
||||||
|
iterator tmp = *this;
|
||||||
|
++*this;
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const iterator & rhs) const noexcept
|
||||||
|
{
|
||||||
|
return current == rhs.current;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
using const_iterator = iterator;
|
using const_iterator = iterator;
|
||||||
|
|
||||||
void push_back(const Attr & attr)
|
void push_back(const Attr & attr)
|
||||||
{
|
{
|
||||||
attrs[size_++] = attr;
|
attrs[numAttrs++] = attr;
|
||||||
|
numAttrsInChain = numAttrs;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Attr * get(Symbol name) const
|
/**
|
||||||
|
* Get attribute by name or nullptr if no such attribute exists.
|
||||||
|
*/
|
||||||
|
const Attr * get(Symbol name) const noexcept
|
||||||
{
|
{
|
||||||
Attr key(name, 0);
|
auto getInChunk = [key = Attr{name, nullptr}](const Bindings & chunk) -> const Attr * {
|
||||||
auto first = attrs;
|
auto first = chunk.attrs;
|
||||||
auto last = attrs + size_;
|
auto last = first + chunk.numAttrs;
|
||||||
const Attr * i = std::lower_bound(first, last, key);
|
const Attr * i = std::lower_bound(first, last, key);
|
||||||
if (i != last && i->name == name)
|
if (i != last && i->name == key.name)
|
||||||
return i;
|
return i;
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Bindings * currentChunk = this;
|
||||||
|
while (currentChunk) {
|
||||||
|
const Attr * maybeAttr = getInChunk(*currentChunk);
|
||||||
|
if (maybeAttr)
|
||||||
|
return maybeAttr;
|
||||||
|
currentChunk = currentChunk->baseLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the layer chain is full.
|
||||||
|
*/
|
||||||
|
bool isLayerListFull() const noexcept
|
||||||
|
{
|
||||||
|
return numLayers == Bindings::maxLayers;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if the length of the linked list of layers is greater than 1.
|
||||||
|
*/
|
||||||
|
bool isLayered() const noexcept
|
||||||
|
{
|
||||||
|
return numLayers > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
const_iterator begin() const
|
const_iterator begin() const
|
||||||
{
|
{
|
||||||
return const_iterator(attrs);
|
return const_iterator(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
const_iterator end() const
|
const_iterator end() const
|
||||||
{
|
{
|
||||||
return const_iterator(attrs + size_);
|
return const_iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
Attr & operator[](size_t pos)
|
Attr & operator[](size_type pos)
|
||||||
{
|
{
|
||||||
|
if (isLayered()) [[unlikely]]
|
||||||
|
unreachable();
|
||||||
return attrs[pos];
|
return attrs[pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Attr & operator[](size_t pos) const
|
const Attr & operator[](size_type pos) const
|
||||||
{
|
{
|
||||||
|
if (isLayered()) [[unlikely]]
|
||||||
|
unreachable();
|
||||||
return attrs[pos];
|
return attrs[pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -176,17 +417,16 @@ public:
|
||||||
std::vector<const Attr *> lexicographicOrder(const SymbolTable & symbols) 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++)
|
std::ranges::transform(*this, std::back_inserter(res), [](const Attr & a) { return &a; });
|
||||||
res.emplace_back(&attrs[n]);
|
std::ranges::sort(res, [&](const Attr * a, const Attr * b) {
|
||||||
std::sort(res.begin(), res.end(), [&](const Attr * a, const Attr * b) {
|
|
||||||
std::string_view sa = symbols[a->name], sb = symbols[b->name];
|
std::string_view sa = symbols[a->name], sb = symbols[b->name];
|
||||||
return sa < sb;
|
return sa < sb;
|
||||||
});
|
});
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
friend class EvalState;
|
friend class EvalMemory;
|
||||||
};
|
};
|
||||||
|
|
||||||
static_assert(std::forward_iterator<Bindings::iterator>);
|
static_assert(std::forward_iterator<Bindings::iterator>);
|
||||||
|
|
@ -202,23 +442,38 @@ class BindingsBuilder final
|
||||||
public:
|
public:
|
||||||
// needed by std::back_inserter
|
// needed by std::back_inserter
|
||||||
using value_type = Attr;
|
using value_type = Attr;
|
||||||
using size_type = Bindings::size_t;
|
using size_type = Bindings::size_type;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Bindings * bindings;
|
Bindings * bindings;
|
||||||
Bindings::size_t capacity_;
|
Bindings::size_type capacity_;
|
||||||
|
|
||||||
friend class EvalState;
|
friend class EvalMemory;
|
||||||
|
|
||||||
BindingsBuilder(EvalState & state, Bindings * bindings, size_type capacity)
|
BindingsBuilder(EvalMemory & mem, SymbolTable & symbols, Bindings * bindings, size_type capacity)
|
||||||
: bindings(bindings)
|
: bindings(bindings)
|
||||||
, capacity_(capacity)
|
, capacity_(capacity)
|
||||||
, state(state)
|
, mem(mem)
|
||||||
|
, symbols(symbols)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool hasBaseLayer() const noexcept
|
||||||
|
{
|
||||||
|
return bindings->baseLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void finishSizeIfNecessary()
|
||||||
|
{
|
||||||
|
if (hasBaseLayer())
|
||||||
|
/* NOTE: Do not use std::ranges::distance, since Bindings is a sized
|
||||||
|
range, but we are calculating this size here. */
|
||||||
|
bindings->numAttrsInChain = std::distance(bindings->begin(), bindings->end());
|
||||||
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
std::reference_wrapper<EvalState> state;
|
std::reference_wrapper<EvalMemory> mem;
|
||||||
|
std::reference_wrapper<SymbolTable> symbols;
|
||||||
|
|
||||||
void insert(Symbol name, Value * value, PosIdx pos = noPos)
|
void insert(Symbol name, Value * value, PosIdx pos = noPos)
|
||||||
{
|
{
|
||||||
|
|
@ -232,10 +487,26 @@ public:
|
||||||
|
|
||||||
void push_back(const Attr & attr)
|
void push_back(const Attr & attr)
|
||||||
{
|
{
|
||||||
assert(bindings->size() < capacity_);
|
assert(bindings->numAttrs < capacity_);
|
||||||
bindings->push_back(attr);
|
bindings->push_back(attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* "Layer" the newly constructured Bindings on top of another attribute set.
|
||||||
|
*
|
||||||
|
* This effectively performs an attribute set merge, while giving preference
|
||||||
|
* to attributes from the newly constructed Bindings in case of duplicate attribute
|
||||||
|
* names.
|
||||||
|
*
|
||||||
|
* This operation amortizes the need to copy over all attributes and allows
|
||||||
|
* for efficient implementation of attribute set merges (ExprOpUpdate::eval).
|
||||||
|
*/
|
||||||
|
void layerOnTopOf(const Bindings & base) noexcept
|
||||||
|
{
|
||||||
|
bindings->baseLayer = &base;
|
||||||
|
bindings->numLayers = base.numLayers + 1;
|
||||||
|
}
|
||||||
|
|
||||||
Value & alloc(Symbol name, PosIdx pos = noPos);
|
Value & alloc(Symbol name, PosIdx pos = noPos);
|
||||||
|
|
||||||
Value & alloc(std::string_view name, PosIdx pos = noPos);
|
Value & alloc(std::string_view name, PosIdx pos = noPos);
|
||||||
|
|
@ -243,11 +514,13 @@ public:
|
||||||
Bindings * finish()
|
Bindings * finish()
|
||||||
{
|
{
|
||||||
bindings->sort();
|
bindings->sort();
|
||||||
|
finishSizeIfNecessary();
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bindings * alreadySorted()
|
Bindings * alreadySorted()
|
||||||
{
|
{
|
||||||
|
finishSizeIfNecessary();
|
||||||
return bindings;
|
return bindings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
70
src/libexpr/include/nix/expr/counter.hh
Normal file
70
src/libexpr/include/nix/expr/counter.hh
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An atomic counter aligned on a cache line to prevent false sharing.
|
||||||
|
* The counter is only enabled when the `NIX_SHOW_STATS` environment
|
||||||
|
* variable is set. This is to prevent contention on these counters
|
||||||
|
* when multi-threaded evaluation is enabled.
|
||||||
|
*/
|
||||||
|
struct alignas(64) Counter
|
||||||
|
{
|
||||||
|
using value_type = uint64_t;
|
||||||
|
|
||||||
|
std::atomic<value_type> inner{0};
|
||||||
|
|
||||||
|
static bool enabled;
|
||||||
|
|
||||||
|
Counter() {}
|
||||||
|
|
||||||
|
operator value_type() const noexcept
|
||||||
|
{
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
void operator=(value_type n) noexcept
|
||||||
|
{
|
||||||
|
inner = n;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type load() const noexcept
|
||||||
|
{
|
||||||
|
return inner;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type operator++() noexcept
|
||||||
|
{
|
||||||
|
return enabled ? ++inner : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type operator++(int) noexcept
|
||||||
|
{
|
||||||
|
return enabled ? inner++ : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type operator--() noexcept
|
||||||
|
{
|
||||||
|
return enabled ? --inner : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type operator--(int) noexcept
|
||||||
|
{
|
||||||
|
return enabled ? inner-- : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type operator+=(value_type n) noexcept
|
||||||
|
{
|
||||||
|
return enabled ? inner += n : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_type operator-=(value_type n) noexcept
|
||||||
|
{
|
||||||
|
return enabled ? inner -= n : 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
|
|
@ -26,7 +26,7 @@ inline void * allocBytes(size_t n)
|
||||||
}
|
}
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
[[gnu::always_inline]]
|
||||||
Value * EvalState::allocValue()
|
Value * EvalMemory::allocValue()
|
||||||
{
|
{
|
||||||
#if NIX_USE_BOEHMGC
|
#if NIX_USE_BOEHMGC
|
||||||
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
|
/* We use the boehm batch allocator to speed up allocations of Values (of which there are many).
|
||||||
|
|
@ -48,15 +48,15 @@ Value * EvalState::allocValue()
|
||||||
void * p = allocBytes(sizeof(Value));
|
void * p = allocBytes(sizeof(Value));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
nrValues++;
|
stats.nrValues++;
|
||||||
return (Value *) p;
|
return (Value *) p;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[gnu::always_inline]]
|
[[gnu::always_inline]]
|
||||||
Env & EvalState::allocEnv(size_t size)
|
Env & EvalMemory::allocEnv(size_t size)
|
||||||
{
|
{
|
||||||
nrEnvs++;
|
stats.nrEnvs++;
|
||||||
nrValuesInEnvs += size;
|
stats.nrValuesInEnvs += size;
|
||||||
|
|
||||||
Env * env;
|
Env * env;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -342,6 +342,25 @@ struct EvalSettings : Config
|
||||||
This is useful for improving code readability and making path literals
|
This is useful for improving code readability and making path literals
|
||||||
more explicit.
|
more explicit.
|
||||||
)"};
|
)"};
|
||||||
|
|
||||||
|
Setting<unsigned> bindingsUpdateLayerRhsSizeThreshold{
|
||||||
|
this,
|
||||||
|
sizeof(void *) == 4 ? 8192 : 16,
|
||||||
|
"eval-attrset-update-layer-rhs-threshold",
|
||||||
|
R"(
|
||||||
|
Tunes the maximum size of an attribute set that, when used
|
||||||
|
as a right operand in an [attribute set update expression](@docroot@/language/operators.md#update),
|
||||||
|
uses a more space-efficient linked-list representation of attribute sets.
|
||||||
|
|
||||||
|
Setting this to larger values generally leads to less memory allocations,
|
||||||
|
but may lead to worse evaluation performance.
|
||||||
|
|
||||||
|
A value of `0` disables this optimization completely.
|
||||||
|
|
||||||
|
This is an advanced performance tuning option and typically should not be changed.
|
||||||
|
The default value is chosen to balance performance and memory usage. On 32 bit systems
|
||||||
|
where memory is scarce, the default is a large value to reduce the amount of allocations.
|
||||||
|
)"};
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include "nix/expr/search-path.hh"
|
#include "nix/expr/search-path.hh"
|
||||||
#include "nix/expr/repl-exit-status.hh"
|
#include "nix/expr/repl-exit-status.hh"
|
||||||
#include "nix/util/ref.hh"
|
#include "nix/util/ref.hh"
|
||||||
|
#include "nix/expr/counter.hh"
|
||||||
|
|
||||||
// For `NIX_USE_BOEHMGC`, and if that's set, `GC_THREADS`
|
// For `NIX_USE_BOEHMGC`, and if that's set, `GC_THREADS`
|
||||||
#include "nix/expr/config.hh"
|
#include "nix/expr/config.hh"
|
||||||
|
|
@ -48,6 +49,7 @@ class StorePath;
|
||||||
struct SingleDerivedPath;
|
struct SingleDerivedPath;
|
||||||
enum RepairFlag : bool;
|
enum RepairFlag : bool;
|
||||||
struct MemorySourceAccessor;
|
struct MemorySourceAccessor;
|
||||||
|
struct MountedSourceAccessor;
|
||||||
|
|
||||||
namespace eval_cache {
|
namespace eval_cache {
|
||||||
class EvalCache;
|
class EvalCache;
|
||||||
|
|
@ -300,6 +302,68 @@ struct StaticEvalSymbols
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class EvalMemory
|
||||||
|
{
|
||||||
|
#if NIX_USE_BOEHMGC
|
||||||
|
/**
|
||||||
|
* Allocation cache for GC'd Value objects.
|
||||||
|
*/
|
||||||
|
std::shared_ptr<void *> valueAllocCache;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocation cache for size-1 Env objects.
|
||||||
|
*/
|
||||||
|
std::shared_ptr<void *> env1AllocCache;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct Statistics
|
||||||
|
{
|
||||||
|
Counter nrEnvs;
|
||||||
|
Counter nrValuesInEnvs;
|
||||||
|
Counter nrValues;
|
||||||
|
Counter nrAttrsets;
|
||||||
|
Counter nrAttrsInAttrsets;
|
||||||
|
Counter nrListElems;
|
||||||
|
};
|
||||||
|
|
||||||
|
EvalMemory();
|
||||||
|
|
||||||
|
EvalMemory(const EvalMemory &) = delete;
|
||||||
|
EvalMemory(EvalMemory &&) = delete;
|
||||||
|
EvalMemory & operator=(const EvalMemory &) = delete;
|
||||||
|
EvalMemory & operator=(EvalMemory &&) = delete;
|
||||||
|
|
||||||
|
inline Value * allocValue();
|
||||||
|
inline Env & allocEnv(size_t size);
|
||||||
|
|
||||||
|
Bindings * allocBindings(size_t capacity);
|
||||||
|
|
||||||
|
BindingsBuilder buildBindings(SymbolTable & symbols, size_t capacity)
|
||||||
|
{
|
||||||
|
return BindingsBuilder(*this, symbols, allocBindings(capacity), capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
ListBuilder buildList(size_t size)
|
||||||
|
{
|
||||||
|
stats.nrListElems += size;
|
||||||
|
return ListBuilder(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Statistics & getStats() const &
|
||||||
|
{
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage for the AST nodes
|
||||||
|
*/
|
||||||
|
Exprs exprs;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Statistics stats;
|
||||||
|
};
|
||||||
|
|
||||||
class EvalState : public std::enable_shared_from_this<EvalState>
|
class EvalState : public std::enable_shared_from_this<EvalState>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
@ -310,6 +374,8 @@ public:
|
||||||
SymbolTable symbols;
|
SymbolTable symbols;
|
||||||
PosTable positions;
|
PosTable positions;
|
||||||
|
|
||||||
|
EvalMemory mem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
|
|
@ -319,7 +385,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* The accessor corresponding to `store`.
|
* The accessor corresponding to `store`.
|
||||||
*/
|
*/
|
||||||
const ref<SourceAccessor> storeFS;
|
const ref<MountedSourceAccessor> storeFS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The accessor for the root filesystem.
|
* The accessor for the root filesystem.
|
||||||
|
|
@ -439,18 +505,6 @@ private:
|
||||||
*/
|
*/
|
||||||
std::shared_ptr<RegexCache> regexCache;
|
std::shared_ptr<RegexCache> regexCache;
|
||||||
|
|
||||||
#if NIX_USE_BOEHMGC
|
|
||||||
/**
|
|
||||||
* Allocation cache for GC'd Value objects.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<void *> valueAllocCache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocation cache for size-1 Env objects.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<void *> env1AllocCache;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
EvalState(
|
EvalState(
|
||||||
|
|
@ -461,6 +515,15 @@ public:
|
||||||
std::shared_ptr<Store> buildStore = nullptr);
|
std::shared_ptr<Store> buildStore = nullptr);
|
||||||
~EvalState();
|
~EvalState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A wrapper around EvalMemory::allocValue() to avoid code churn when it
|
||||||
|
* was introduced.
|
||||||
|
*/
|
||||||
|
inline Value * allocValue()
|
||||||
|
{
|
||||||
|
return mem.allocValue();
|
||||||
|
}
|
||||||
|
|
||||||
LookupPath getLookupPath()
|
LookupPath getLookupPath()
|
||||||
{
|
{
|
||||||
return lookupPath;
|
return lookupPath;
|
||||||
|
|
@ -488,8 +551,11 @@ public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow access to a path.
|
* Allow access to a path.
|
||||||
|
*
|
||||||
|
* Only for restrict eval: pure eval just whitelist store paths,
|
||||||
|
* never arbitrary paths.
|
||||||
*/
|
*/
|
||||||
void allowPath(const Path & path);
|
void allowPathLegacy(const Path & path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allow access to a store path. Note that this gets remapped to
|
* Allow access to a store path. Note that this gets remapped to
|
||||||
|
|
@ -829,22 +895,14 @@ public:
|
||||||
*/
|
*/
|
||||||
void autoCallFunction(const Bindings & args, Value & fun, Value & res);
|
void autoCallFunction(const Bindings & args, Value & fun, Value & res);
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocation primitives.
|
|
||||||
*/
|
|
||||||
inline Value * allocValue();
|
|
||||||
inline Env & allocEnv(size_t size);
|
|
||||||
|
|
||||||
Bindings * allocBindings(size_t capacity);
|
|
||||||
|
|
||||||
BindingsBuilder buildBindings(size_t capacity)
|
BindingsBuilder buildBindings(size_t capacity)
|
||||||
{
|
{
|
||||||
return BindingsBuilder(*this, allocBindings(capacity), capacity);
|
return mem.buildBindings(symbols, capacity);
|
||||||
}
|
}
|
||||||
|
|
||||||
ListBuilder buildList(size_t size)
|
ListBuilder buildList(size_t size)
|
||||||
{
|
{
|
||||||
return ListBuilder(*this, size);
|
return mem.buildList(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -961,19 +1019,13 @@ private:
|
||||||
*/
|
*/
|
||||||
std::string mkSingleDerivedPathStringRaw(const SingleDerivedPath & p);
|
std::string mkSingleDerivedPathStringRaw(const SingleDerivedPath & p);
|
||||||
|
|
||||||
unsigned long nrEnvs = 0;
|
Counter nrLookups;
|
||||||
unsigned long nrValuesInEnvs = 0;
|
Counter nrAvoided;
|
||||||
unsigned long nrValues = 0;
|
Counter nrOpUpdates;
|
||||||
unsigned long nrListElems = 0;
|
Counter nrOpUpdateValuesCopied;
|
||||||
unsigned long nrLookups = 0;
|
Counter nrListConcats;
|
||||||
unsigned long nrAttrsets = 0;
|
Counter nrPrimOpCalls;
|
||||||
unsigned long nrAttrsInAttrsets = 0;
|
Counter nrFunctionCalls;
|
||||||
unsigned long nrAvoided = 0;
|
|
||||||
unsigned long nrOpUpdates = 0;
|
|
||||||
unsigned long nrOpUpdateValuesCopied = 0;
|
|
||||||
unsigned long nrListConcats = 0;
|
|
||||||
unsigned long nrPrimOpCalls = 0;
|
|
||||||
unsigned long nrFunctionCalls = 0;
|
|
||||||
|
|
||||||
bool countCalls;
|
bool countCalls;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,20 @@ using SmallValueVector = SmallVector<Value *, nItems>;
|
||||||
template<size_t nItems>
|
template<size_t nItems>
|
||||||
using SmallTemporaryValueVector = SmallVector<Value, nItems>;
|
using SmallTemporaryValueVector = SmallVector<Value, nItems>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For functions where we do not expect deep recursion, we can use a sizable
|
||||||
|
* part of the stack a free allocation space.
|
||||||
|
*
|
||||||
|
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
|
||||||
|
*/
|
||||||
|
constexpr size_t nonRecursiveStackReservation = 128;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions that maybe applied to self-similar inputs, such as concatMap on a
|
||||||
|
* tree, should reserve a smaller part of the stack for allocation.
|
||||||
|
*
|
||||||
|
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
|
||||||
|
*/
|
||||||
|
constexpr size_t conservativeStackReservation = 16;
|
||||||
|
|
||||||
} // namespace nix
|
} // namespace nix
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ config_pub_h = configure_file(
|
||||||
headers = [ config_pub_h ] + files(
|
headers = [ config_pub_h ] + files(
|
||||||
'attr-path.hh',
|
'attr-path.hh',
|
||||||
'attr-set.hh',
|
'attr-set.hh',
|
||||||
|
'counter.hh',
|
||||||
'eval-cache.hh',
|
'eval-cache.hh',
|
||||||
'eval-error.hh',
|
'eval-error.hh',
|
||||||
'eval-gc.hh',
|
'eval-gc.hh',
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,14 @@
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <memory_resource>
|
||||||
|
|
||||||
|
#include "nix/expr/gc-small-vector.hh"
|
||||||
#include "nix/expr/value.hh"
|
#include "nix/expr/value.hh"
|
||||||
#include "nix/expr/symbol-table.hh"
|
#include "nix/expr/symbol-table.hh"
|
||||||
#include "nix/expr/eval-error.hh"
|
#include "nix/expr/eval-error.hh"
|
||||||
#include "nix/util/pos-idx.hh"
|
#include "nix/util/pos-idx.hh"
|
||||||
|
#include "nix/expr/counter.hh"
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
@ -80,6 +83,15 @@ typedef std::vector<AttrName> AttrPath;
|
||||||
|
|
||||||
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath);
|
std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath);
|
||||||
|
|
||||||
|
using UpdateQueue = SmallTemporaryValueVector<conservativeStackReservation>;
|
||||||
|
|
||||||
|
class Exprs
|
||||||
|
{
|
||||||
|
std::pmr::monotonic_buffer_resource buffer;
|
||||||
|
public:
|
||||||
|
std::pmr::polymorphic_allocator<char> alloc{&buffer};
|
||||||
|
};
|
||||||
|
|
||||||
/* Abstract syntax of Nix expressions. */
|
/* Abstract syntax of Nix expressions. */
|
||||||
|
|
||||||
struct Expr
|
struct Expr
|
||||||
|
|
@ -89,7 +101,7 @@ struct Expr
|
||||||
Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body;
|
Symbol sub, lessThan, mul, div, or_, findFile, nixPath, body;
|
||||||
};
|
};
|
||||||
|
|
||||||
static unsigned long nrExprs;
|
static Counter nrExprs;
|
||||||
|
|
||||||
Expr()
|
Expr()
|
||||||
{
|
{
|
||||||
|
|
@ -99,8 +111,25 @@ struct Expr
|
||||||
virtual ~Expr() {};
|
virtual ~Expr() {};
|
||||||
virtual void show(const SymbolTable & symbols, std::ostream & str) const;
|
virtual void show(const SymbolTable & symbols, std::ostream & str) const;
|
||||||
virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
|
virtual void bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env);
|
||||||
|
|
||||||
|
/** Normal evaluation, implemented directly by all subclasses. */
|
||||||
virtual void eval(EvalState & state, Env & env, Value & v);
|
virtual void eval(EvalState & state, Env & env, Value & v);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a thunk for the delayed computation of the given expression
|
||||||
|
* in the given environment. But if the expression is a variable,
|
||||||
|
* then look it up right away. This significantly reduces the number
|
||||||
|
* of thunks allocated.
|
||||||
|
*/
|
||||||
virtual Value * maybeThunk(EvalState & state, Env & env);
|
virtual Value * maybeThunk(EvalState & state, Env & env);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only called when performing an attrset update: `//` or similar.
|
||||||
|
* Instead of writing to a Value &, this function writes to an UpdateQueue.
|
||||||
|
* This allows the expression to perform multiple updates in a delayed manner, gathering up all the updates before
|
||||||
|
* applying them.
|
||||||
|
*/
|
||||||
|
virtual void evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx);
|
||||||
virtual void setName(Symbol name);
|
virtual void setName(Symbol name);
|
||||||
virtual void setDocComment(DocComment docComment) {};
|
virtual void setDocComment(DocComment docComment) {};
|
||||||
|
|
||||||
|
|
@ -152,13 +181,28 @@ struct ExprFloat : Expr
|
||||||
|
|
||||||
struct ExprString : Expr
|
struct ExprString : Expr
|
||||||
{
|
{
|
||||||
std::string s;
|
|
||||||
Value v;
|
Value v;
|
||||||
|
|
||||||
ExprString(std::string && s)
|
/**
|
||||||
: s(std::move(s))
|
* This is only for strings already allocated in our polymorphic allocator,
|
||||||
|
* or that live at least that long (e.g. c++ string literals)
|
||||||
|
*/
|
||||||
|
ExprString(const char * s)
|
||||||
{
|
{
|
||||||
v.mkStringNoCopy(this->s.data());
|
v.mkStringNoCopy(s);
|
||||||
|
};
|
||||||
|
|
||||||
|
ExprString(std::pmr::polymorphic_allocator<char> & alloc, std::string_view sv)
|
||||||
|
{
|
||||||
|
auto len = sv.length();
|
||||||
|
if (len == 0) {
|
||||||
|
v.mkStringNoCopy("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
char * s = alloc.allocate(len + 1);
|
||||||
|
sv.copy(s, len);
|
||||||
|
s[len] = '\0';
|
||||||
|
v.mkStringNoCopy(s);
|
||||||
};
|
};
|
||||||
|
|
||||||
Value * maybeThunk(EvalState & state, Env & env) override;
|
Value * maybeThunk(EvalState & state, Env & env) override;
|
||||||
|
|
@ -565,18 +609,16 @@ struct ExprOpNot : Expr
|
||||||
COMMON_METHODS
|
COMMON_METHODS
|
||||||
};
|
};
|
||||||
|
|
||||||
#define MakeBinOp(name, s) \
|
#define MakeBinOpMembers(name, s) \
|
||||||
struct name : Expr \
|
|
||||||
{ \
|
|
||||||
PosIdx pos; \
|
PosIdx pos; \
|
||||||
Expr *e1, *e2; \
|
Expr *e1, *e2; \
|
||||||
name(Expr * e1, Expr * e2) \
|
name(Expr * e1, Expr * e2) \
|
||||||
: e1(e1) \
|
: e1(e1) \
|
||||||
, e2(e2) {}; \
|
, e2(e2){}; \
|
||||||
name(const PosIdx & pos, Expr * e1, Expr * e2) \
|
name(const PosIdx & pos, Expr * e1, Expr * e2) \
|
||||||
: pos(pos) \
|
: pos(pos) \
|
||||||
, e1(e1) \
|
, e1(e1) \
|
||||||
, e2(e2) {}; \
|
, e2(e2){}; \
|
||||||
void show(const SymbolTable & symbols, std::ostream & str) const override \
|
void show(const SymbolTable & symbols, std::ostream & str) const override \
|
||||||
{ \
|
{ \
|
||||||
str << "("; \
|
str << "("; \
|
||||||
|
|
@ -594,7 +636,12 @@ struct ExprOpNot : Expr
|
||||||
PosIdx getPos() const override \
|
PosIdx getPos() const override \
|
||||||
{ \
|
{ \
|
||||||
return pos; \
|
return pos; \
|
||||||
} \
|
}
|
||||||
|
|
||||||
|
#define MakeBinOp(name, s) \
|
||||||
|
struct name : Expr \
|
||||||
|
{ \
|
||||||
|
MakeBinOpMembers(name, s) \
|
||||||
}
|
}
|
||||||
|
|
||||||
MakeBinOp(ExprOpEq, "==");
|
MakeBinOp(ExprOpEq, "==");
|
||||||
|
|
@ -602,9 +649,20 @@ MakeBinOp(ExprOpNEq, "!=");
|
||||||
MakeBinOp(ExprOpAnd, "&&");
|
MakeBinOp(ExprOpAnd, "&&");
|
||||||
MakeBinOp(ExprOpOr, "||");
|
MakeBinOp(ExprOpOr, "||");
|
||||||
MakeBinOp(ExprOpImpl, "->");
|
MakeBinOp(ExprOpImpl, "->");
|
||||||
MakeBinOp(ExprOpUpdate, "//");
|
|
||||||
MakeBinOp(ExprOpConcatLists, "++");
|
MakeBinOp(ExprOpConcatLists, "++");
|
||||||
|
|
||||||
|
struct ExprOpUpdate : Expr
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
/** Special case for merging of two attrsets. */
|
||||||
|
void eval(EvalState & state, Value & v, Value & v1, Value & v2);
|
||||||
|
void evalForUpdate(EvalState & state, Env & env, UpdateQueue & q);
|
||||||
|
|
||||||
|
public:
|
||||||
|
MakeBinOpMembers(ExprOpUpdate, "//");
|
||||||
|
virtual void evalForUpdate(EvalState & state, Env & env, UpdateQueue & q, std::string_view errorCtx) override;
|
||||||
|
};
|
||||||
|
|
||||||
struct ExprConcatStrings : Expr
|
struct ExprConcatStrings : Expr
|
||||||
{
|
{
|
||||||
PosIdx pos;
|
PosIdx pos;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ struct StringToken
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// This type must be trivially copyable; see YYLTYPE_IS_TRIVIAL in parser.y.
|
|
||||||
struct ParserLocation
|
struct ParserLocation
|
||||||
{
|
{
|
||||||
int beginOffset;
|
int beginOffset;
|
||||||
|
|
@ -44,9 +43,6 @@ struct ParserLocation
|
||||||
beginOffset = stashedBeginOffset;
|
beginOffset = stashedBeginOffset;
|
||||||
endOffset = stashedEndOffset;
|
endOffset = stashedEndOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Latest doc comment position, or 0. */
|
|
||||||
int doc_comment_first_column, doc_comment_last_column;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct LexerState
|
struct LexerState
|
||||||
|
|
@ -82,6 +78,7 @@ struct LexerState
|
||||||
struct ParserState
|
struct ParserState
|
||||||
{
|
{
|
||||||
const LexerState & lexerState;
|
const LexerState & lexerState;
|
||||||
|
std::pmr::polymorphic_allocator<char> & alloc;
|
||||||
SymbolTable & symbols;
|
SymbolTable & symbols;
|
||||||
PosTable & positions;
|
PosTable & positions;
|
||||||
Expr * result;
|
Expr * result;
|
||||||
|
|
@ -327,7 +324,7 @@ ParserState::stripIndentation(const PosIdx pos, std::vector<std::pair<PosIdx, st
|
||||||
|
|
||||||
// Ignore empty strings for a minor optimisation and AST simplification
|
// Ignore empty strings for a minor optimisation and AST simplification
|
||||||
if (s2 != "") {
|
if (s2 != "") {
|
||||||
es2->emplace_back(i->first, new ExprString(std::move(s2)));
|
es2->emplace_back(i->first, new ExprString(alloc, s2));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
for (; i != es.end(); ++i, --n) {
|
for (; i != es.end(); ++i, --n) {
|
||||||
|
|
|
||||||
|
|
@ -8,22 +8,6 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
/**
|
|
||||||
* For functions where we do not expect deep recursion, we can use a sizable
|
|
||||||
* part of the stack a free allocation space.
|
|
||||||
*
|
|
||||||
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
|
|
||||||
*/
|
|
||||||
constexpr size_t nonRecursiveStackReservation = 128;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Functions that maybe applied to self-similar inputs, such as concatMap on a
|
|
||||||
* tree, should reserve a smaller part of the stack for allocation.
|
|
||||||
*
|
|
||||||
* Note: this is expected to be multiplied by sizeof(Value), or about 24 bytes.
|
|
||||||
*/
|
|
||||||
constexpr size_t conservativeStackReservation = 16;
|
|
||||||
|
|
||||||
struct RegisterPrimOp
|
struct RegisterPrimOp
|
||||||
{
|
{
|
||||||
typedef std::vector<PrimOp> PrimOps;
|
typedef std::vector<PrimOp> PrimOps;
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,7 @@ class ListBuilder
|
||||||
Value * inlineElems[2] = {nullptr, nullptr};
|
Value * inlineElems[2] = {nullptr, nullptr};
|
||||||
public:
|
public:
|
||||||
Value ** elems;
|
Value ** elems;
|
||||||
ListBuilder(EvalState & state, size_t size);
|
ListBuilder(size_t size);
|
||||||
|
|
||||||
// NOTE: Can be noexcept because we are just copying integral values and
|
// NOTE: Can be noexcept because we are just copying integral values and
|
||||||
// raw pointers.
|
// raw pointers.
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
#include "lexer-helpers.hh"
|
#include "lexer-helpers.hh"
|
||||||
|
|
||||||
void nix::lexer::internal::initLoc(YYLTYPE * loc)
|
void nix::lexer::internal::initLoc(Parser::location_type * loc)
|
||||||
{
|
{
|
||||||
loc->beginOffset = loc->endOffset = 0;
|
loc->beginOffset = loc->endOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void nix::lexer::internal::adjustLoc(yyscan_t yyscanner, YYLTYPE * loc, const char * s, size_t len)
|
void nix::lexer::internal::adjustLoc(yyscan_t yyscanner, Parser::location_type * loc, const char * s, size_t len)
|
||||||
{
|
{
|
||||||
loc->stash();
|
loc->stash();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,12 @@
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
|
|
||||||
// including the generated headers twice leads to errors
|
#include "parser-scanner-decls.hh"
|
||||||
#ifndef BISON_HEADER
|
|
||||||
# include "lexer-tab.hh"
|
|
||||||
# include "parser-tab.hh"
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace nix::lexer::internal {
|
namespace nix::lexer::internal {
|
||||||
|
|
||||||
void initLoc(YYLTYPE * loc);
|
void initLoc(Parser::location_type * loc);
|
||||||
|
|
||||||
void adjustLoc(yyscan_t yyscanner, YYLTYPE * loc, const char * s, size_t len);
|
void adjustLoc(yyscan_t yyscanner, Parser::location_type * loc, const char * s, size_t len);
|
||||||
|
|
||||||
} // namespace nix::lexer::internal
|
} // namespace nix::lexer::internal
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,10 @@ static void requireExperimentalFeature(const ExperimentalFeature & feature, cons
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using enum nix::Parser::token::token_kind_type;
|
||||||
|
using YYSTYPE = nix::Parser::value_type;
|
||||||
|
using YYLTYPE = nix::Parser::location_type;
|
||||||
|
|
||||||
// yacc generates code that uses unannotated fallthrough.
|
// yacc generates code that uses unannotated fallthrough.
|
||||||
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
#pragma GCC diagnostic ignored "-Wimplicit-fallthrough"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,12 @@ deps_other += boost
|
||||||
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||||
deps_public += nlohmann_json
|
deps_public += nlohmann_json
|
||||||
|
|
||||||
bdw_gc = dependency('bdw-gc', required : get_option('gc'))
|
bdw_gc_required = get_option('gc').disable_if(
|
||||||
|
'address' in get_option('b_sanitize'),
|
||||||
|
error_message : 'Building with Boehm GC and ASAN is not supported',
|
||||||
|
)
|
||||||
|
|
||||||
|
bdw_gc = dependency('bdw-gc', required : bdw_gc_required)
|
||||||
if bdw_gc.found()
|
if bdw_gc.found()
|
||||||
deps_public += bdw_gc
|
deps_public += bdw_gc
|
||||||
foreach funcspec : [
|
foreach funcspec : [
|
||||||
|
|
@ -64,6 +69,10 @@ if bdw_gc.found()
|
||||||
define_value = cxx.has_function(funcspec).to_int()
|
define_value = cxx.has_function(funcspec).to_int()
|
||||||
configdata_priv.set(define_name, define_value)
|
configdata_priv.set(define_name, define_value)
|
||||||
endforeach
|
endforeach
|
||||||
|
if host_machine.system() == 'cygwin'
|
||||||
|
# undefined reference to `__wrap__Znwm'
|
||||||
|
configdata_pub.set('GC_NO_INLINE_STD_NEW', 1)
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
# Used in public header. Affects ABI!
|
# Used in public header. Affects ABI!
|
||||||
configdata_pub.set('NIX_USE_BOEHMGC', bdw_gc.found().to_int())
|
configdata_pub.set('NIX_USE_BOEHMGC', bdw_gc.found().to_int())
|
||||||
|
|
@ -88,6 +97,7 @@ config_priv_h = configure_file(
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
parser_tab = custom_target(
|
parser_tab = custom_target(
|
||||||
input : 'parser.y',
|
input : 'parser.y',
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
unsigned long Expr::nrExprs = 0;
|
Counter Expr::nrExprs;
|
||||||
|
|
||||||
ExprBlackHole eBlackHole;
|
ExprBlackHole eBlackHole;
|
||||||
|
|
||||||
|
|
@ -40,7 +40,7 @@ void ExprFloat::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
|
|
||||||
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
|
void ExprString::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
{
|
{
|
||||||
printLiteralString(str, s);
|
printLiteralString(str, v.string_view());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
|
void ExprPath::show(const SymbolTable & symbols, std::ostream & str) const
|
||||||
|
|
|
||||||
17
src/libexpr/parser-scanner-decls.hh
Normal file
17
src/libexpr/parser-scanner-decls.hh
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef BISON_HEADER
|
||||||
|
# include "parser-tab.hh"
|
||||||
|
using YYSTYPE = nix::parser::BisonParser::value_type;
|
||||||
|
using YYLTYPE = nix::parser::BisonParser::location_type;
|
||||||
|
# include "lexer-tab.hh" // IWYU pragma: export
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
class Parser : public parser::BisonParser
|
||||||
|
{
|
||||||
|
using BisonParser::BisonParser;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
|
%skeleton "lalr1.cc"
|
||||||
%define api.location.type { ::nix::ParserLocation }
|
%define api.location.type { ::nix::ParserLocation }
|
||||||
%define api.pure
|
%define api.namespace { ::nix::parser }
|
||||||
|
%define api.parser.class { BisonParser }
|
||||||
%locations
|
%locations
|
||||||
%define parse.error verbose
|
%define parse.error verbose
|
||||||
%defines
|
%defines
|
||||||
|
|
@ -26,19 +28,12 @@
|
||||||
#include "nix/expr/eval-settings.hh"
|
#include "nix/expr/eval-settings.hh"
|
||||||
#include "nix/expr/parser-state.hh"
|
#include "nix/expr/parser-state.hh"
|
||||||
|
|
||||||
// Bison seems to have difficulty growing the parser stack when using C++ with
|
#define YY_DECL \
|
||||||
// a custom location type. This undocumented macro tells Bison that our
|
int yylex( \
|
||||||
// location type is "trivially copyable" in C++-ese, so it is safe to use the
|
nix::Parser::value_type * yylval_param, \
|
||||||
// same memcpy macro it uses to grow the stack that it uses with its own
|
nix::Parser::location_type * yylloc_param, \
|
||||||
// default location type. Without this, we get "error: memory exhausted" when
|
yyscan_t yyscanner, \
|
||||||
// parsing some large Nix files. Our other options are to increase the initial
|
nix::ParserState * state)
|
||||||
// stack size (200 by default) to be as large as we ever want to support (so
|
|
||||||
// that growing the stack is unnecessary), or redefine the stack-relocation
|
|
||||||
// macro ourselves (which is also undocumented).
|
|
||||||
#define YYLTYPE_IS_TRIVIAL 1
|
|
||||||
|
|
||||||
#define YY_DECL int yylex \
|
|
||||||
(YYSTYPE * yylval_param, YYLTYPE * yylloc_param, yyscan_t yyscanner, nix::ParserState * state)
|
|
||||||
|
|
||||||
// For efficiency, we only track offsets; not line,column coordinates
|
// For efficiency, we only track offsets; not line,column coordinates
|
||||||
# define YYLLOC_DEFAULT(Current, Rhs, N) \
|
# define YYLLOC_DEFAULT(Current, Rhs, N) \
|
||||||
|
|
@ -64,6 +59,7 @@ Expr * parseExprFromBuf(
|
||||||
size_t length,
|
size_t length,
|
||||||
Pos::Origin origin,
|
Pos::Origin origin,
|
||||||
const SourcePath & basePath,
|
const SourcePath & basePath,
|
||||||
|
std::pmr::polymorphic_allocator<char> & alloc,
|
||||||
SymbolTable & symbols,
|
SymbolTable & symbols,
|
||||||
const EvalSettings & settings,
|
const EvalSettings & settings,
|
||||||
PosTable & positions,
|
PosTable & positions,
|
||||||
|
|
@ -78,24 +74,30 @@ Expr * parseExprFromBuf(
|
||||||
|
|
||||||
%{
|
%{
|
||||||
|
|
||||||
#include "parser-tab.hh"
|
/* The parser is very performance sensitive and loses out on a lot
|
||||||
#include "lexer-tab.hh"
|
of performance even with basic stdlib assertions. Since those don't
|
||||||
|
affect ABI we can disable those just for this file. */
|
||||||
|
#if defined(_GLIBCXX_ASSERTIONS) && !defined(_GLIBCXX_DEBUG)
|
||||||
|
#undef _GLIBCXX_ASSERTIONS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "parser-scanner-decls.hh"
|
||||||
|
|
||||||
YY_DECL;
|
YY_DECL;
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
#define CUR_POS state->at(yyloc)
|
#define CUR_POS state->at(yylhs.location)
|
||||||
|
|
||||||
|
void parser::BisonParser::error(const location_type &loc_, const std::string &error)
|
||||||
void yyerror(YYLTYPE * loc, yyscan_t scanner, ParserState * state, const char * error)
|
|
||||||
{
|
{
|
||||||
|
auto loc = loc_;
|
||||||
if (std::string_view(error).starts_with("syntax error, unexpected end of file")) {
|
if (std::string_view(error).starts_with("syntax error, unexpected end of file")) {
|
||||||
loc->beginOffset = loc->endOffset;
|
loc.beginOffset = loc.endOffset;
|
||||||
}
|
}
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = HintFmt(error),
|
.msg = HintFmt(error),
|
||||||
.pos = state->positions[state->at(*loc)]
|
.pos = state->positions[state->at(loc)]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,6 +136,7 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
|
||||||
std::vector<nix::AttrName> * attrNames;
|
std::vector<nix::AttrName> * attrNames;
|
||||||
std::vector<std::pair<nix::AttrName, nix::PosIdx>> * inheritAttrs;
|
std::vector<std::pair<nix::AttrName, nix::PosIdx>> * inheritAttrs;
|
||||||
std::vector<std::pair<nix::PosIdx, nix::Expr *>> * string_parts;
|
std::vector<std::pair<nix::PosIdx, nix::Expr *>> * string_parts;
|
||||||
|
std::variant<nix::Expr *, std::string_view> * to_be_string;
|
||||||
std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, nix::StringToken>>> * ind_string_parts;
|
std::vector<std::pair<nix::PosIdx, std::variant<nix::Expr *, nix::StringToken>>> * ind_string_parts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -148,7 +151,8 @@ static Expr * makeCall(PosIdx pos, Expr * fn, Expr * arg) {
|
||||||
%type <inheritAttrs> attrs
|
%type <inheritAttrs> attrs
|
||||||
%type <string_parts> string_parts_interpolated
|
%type <string_parts> string_parts_interpolated
|
||||||
%type <ind_string_parts> ind_string_parts
|
%type <ind_string_parts> ind_string_parts
|
||||||
%type <e> path_start string_parts string_attr
|
%type <e> path_start
|
||||||
|
%type <to_be_string> string_parts string_attr
|
||||||
%type <id> attr
|
%type <id> attr
|
||||||
%token <id> ID
|
%token <id> ID
|
||||||
%token <str> STR IND_STR
|
%token <str> STR IND_STR
|
||||||
|
|
@ -182,7 +186,7 @@ start: expr {
|
||||||
state->result = $1;
|
state->result = $1;
|
||||||
|
|
||||||
// This parser does not use yynerrs; suppress the warning.
|
// This parser does not use yynerrs; suppress the warning.
|
||||||
(void) yynerrs;
|
(void) yynerrs_;
|
||||||
};
|
};
|
||||||
|
|
||||||
expr: expr_function;
|
expr: expr_function;
|
||||||
|
|
@ -303,7 +307,13 @@ expr_simple
|
||||||
}
|
}
|
||||||
| INT_LIT { $$ = new ExprInt($1); }
|
| INT_LIT { $$ = new ExprInt($1); }
|
||||||
| FLOAT_LIT { $$ = new ExprFloat($1); }
|
| FLOAT_LIT { $$ = new ExprFloat($1); }
|
||||||
| '"' string_parts '"' { $$ = $2; }
|
| '"' string_parts '"' {
|
||||||
|
std::visit(overloaded{
|
||||||
|
[&](std::string_view str) { $$ = new ExprString(state->alloc, str); },
|
||||||
|
[&](Expr * expr) { $$ = expr; }},
|
||||||
|
*$2);
|
||||||
|
delete $2;
|
||||||
|
}
|
||||||
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
|
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
|
||||||
$$ = state->stripIndentation(CUR_POS, std::move(*$2));
|
$$ = state->stripIndentation(CUR_POS, std::move(*$2));
|
||||||
delete $2;
|
delete $2;
|
||||||
|
|
@ -314,11 +324,11 @@ expr_simple
|
||||||
$$ = new ExprConcatStrings(CUR_POS, false, $2);
|
$$ = new ExprConcatStrings(CUR_POS, false, $2);
|
||||||
}
|
}
|
||||||
| SPATH {
|
| SPATH {
|
||||||
std::string path($1.p + 1, $1.l - 2);
|
std::string_view path($1.p + 1, $1.l - 2);
|
||||||
$$ = new ExprCall(CUR_POS,
|
$$ = new ExprCall(CUR_POS,
|
||||||
new ExprVar(state->s.findFile),
|
new ExprVar(state->s.findFile),
|
||||||
{new ExprVar(state->s.nixPath),
|
{new ExprVar(state->s.nixPath),
|
||||||
new ExprString(std::move(path))});
|
new ExprString(state->alloc, path)});
|
||||||
}
|
}
|
||||||
| URI {
|
| URI {
|
||||||
static bool noURLLiterals = experimentalFeatureSettings.isEnabled(Xp::NoUrlLiterals);
|
static bool noURLLiterals = experimentalFeatureSettings.isEnabled(Xp::NoUrlLiterals);
|
||||||
|
|
@ -327,7 +337,7 @@ expr_simple
|
||||||
.msg = HintFmt("URL literals are disabled"),
|
.msg = HintFmt("URL literals are disabled"),
|
||||||
.pos = state->positions[CUR_POS]
|
.pos = state->positions[CUR_POS]
|
||||||
});
|
});
|
||||||
$$ = new ExprString(std::string($1));
|
$$ = new ExprString(state->alloc, $1);
|
||||||
}
|
}
|
||||||
| '(' expr ')' { $$ = $2; }
|
| '(' expr ')' { $$ = $2; }
|
||||||
/* Let expressions `let {..., body = ...}' are just desugared
|
/* Let expressions `let {..., body = ...}' are just desugared
|
||||||
|
|
@ -344,19 +354,19 @@ expr_simple
|
||||||
;
|
;
|
||||||
|
|
||||||
string_parts
|
string_parts
|
||||||
: STR { $$ = new ExprString(std::string($1)); }
|
: STR { $$ = new std::variant<Expr *, std::string_view>($1); }
|
||||||
| string_parts_interpolated { $$ = new ExprConcatStrings(CUR_POS, true, $1); }
|
| string_parts_interpolated { $$ = new std::variant<Expr *, std::string_view>(new ExprConcatStrings(CUR_POS, true, $1)); }
|
||||||
| { $$ = new ExprString(""); }
|
| { $$ = new std::variant<Expr *, std::string_view>(std::string_view()); }
|
||||||
;
|
;
|
||||||
|
|
||||||
string_parts_interpolated
|
string_parts_interpolated
|
||||||
: string_parts_interpolated STR
|
: string_parts_interpolated STR
|
||||||
{ $$ = $1; $1->emplace_back(state->at(@2), new ExprString(std::string($2))); }
|
{ $$ = $1; $1->emplace_back(state->at(@2), new ExprString(state->alloc, $2)); }
|
||||||
| string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(state->at(@2), $3); }
|
| string_parts_interpolated DOLLAR_CURLY expr '}' { $$ = $1; $1->emplace_back(state->at(@2), $3); }
|
||||||
| DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *>>; $$->emplace_back(state->at(@1), $2); }
|
| DOLLAR_CURLY expr '}' { $$ = new std::vector<std::pair<PosIdx, Expr *>>; $$->emplace_back(state->at(@1), $2); }
|
||||||
| STR DOLLAR_CURLY expr '}' {
|
| STR DOLLAR_CURLY expr '}' {
|
||||||
$$ = new std::vector<std::pair<PosIdx, Expr *>>;
|
$$ = new std::vector<std::pair<PosIdx, Expr *>>;
|
||||||
$$->emplace_back(state->at(@1), new ExprString(std::string($1)));
|
$$->emplace_back(state->at(@1), new ExprString(state->alloc, $1));
|
||||||
$$->emplace_back(state->at(@2), $3);
|
$$->emplace_back(state->at(@2), $3);
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
@ -454,16 +464,17 @@ attrs
|
||||||
: attrs attr { $$ = $1; $1->emplace_back(AttrName(state->symbols.create($2)), state->at(@2)); }
|
: attrs attr { $$ = $1; $1->emplace_back(AttrName(state->symbols.create($2)), state->at(@2)); }
|
||||||
| attrs string_attr
|
| attrs string_attr
|
||||||
{ $$ = $1;
|
{ $$ = $1;
|
||||||
ExprString * str = dynamic_cast<ExprString *>($2);
|
std::visit(overloaded {
|
||||||
if (str) {
|
[&](std::string_view str) { $$->emplace_back(AttrName(state->symbols.create(str)), state->at(@2)); },
|
||||||
$$->emplace_back(AttrName(state->symbols.create(str->s)), state->at(@2));
|
[&](Expr * expr) {
|
||||||
delete str;
|
|
||||||
} else
|
|
||||||
throw ParseError({
|
throw ParseError({
|
||||||
.msg = HintFmt("dynamic attributes not allowed in inherit"),
|
.msg = HintFmt("dynamic attributes not allowed in inherit"),
|
||||||
.pos = state->positions[state->at(@2)]
|
.pos = state->positions[state->at(@2)]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}, *$2);
|
||||||
|
delete $2;
|
||||||
|
}
|
||||||
| { $$ = new std::vector<std::pair<AttrName, PosIdx>>; }
|
| { $$ = new std::vector<std::pair<AttrName, PosIdx>>; }
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -471,22 +482,20 @@ attrpath
|
||||||
: attrpath '.' attr { $$ = $1; $1->push_back(AttrName(state->symbols.create($3))); }
|
: attrpath '.' attr { $$ = $1; $1->push_back(AttrName(state->symbols.create($3))); }
|
||||||
| attrpath '.' string_attr
|
| attrpath '.' string_attr
|
||||||
{ $$ = $1;
|
{ $$ = $1;
|
||||||
ExprString * str = dynamic_cast<ExprString *>($3);
|
std::visit(overloaded {
|
||||||
if (str) {
|
[&](std::string_view str) { $$->push_back(AttrName(state->symbols.create(str))); },
|
||||||
$$->push_back(AttrName(state->symbols.create(str->s)));
|
[&](Expr * expr) { $$->push_back(AttrName(expr)); }
|
||||||
delete str;
|
}, *$3);
|
||||||
} else
|
delete $3;
|
||||||
$$->push_back(AttrName($3));
|
|
||||||
}
|
}
|
||||||
| attr { $$ = new std::vector<AttrName>; $$->push_back(AttrName(state->symbols.create($1))); }
|
| attr { $$ = new std::vector<AttrName>; $$->push_back(AttrName(state->symbols.create($1))); }
|
||||||
| string_attr
|
| string_attr
|
||||||
{ $$ = new std::vector<AttrName>;
|
{ $$ = new std::vector<AttrName>;
|
||||||
ExprString *str = dynamic_cast<ExprString *>($1);
|
std::visit(overloaded {
|
||||||
if (str) {
|
[&](std::string_view str) { $$->push_back(AttrName(state->symbols.create(str))); },
|
||||||
$$->push_back(AttrName(state->symbols.create(str->s)));
|
[&](Expr * expr) { $$->push_back(AttrName(expr)); }
|
||||||
delete str;
|
}, *$1);
|
||||||
} else
|
delete $1;
|
||||||
$$->push_back(AttrName($1));
|
|
||||||
}
|
}
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -497,7 +506,7 @@ attr
|
||||||
|
|
||||||
string_attr
|
string_attr
|
||||||
: '"' string_parts '"' { $$ = $2; }
|
: '"' string_parts '"' { $$ = $2; }
|
||||||
| DOLLAR_CURLY expr '}' { $$ = $2; }
|
| DOLLAR_CURLY expr '}' { $$ = new std::variant<Expr *, std::string_view>($2); }
|
||||||
;
|
;
|
||||||
|
|
||||||
expr_list
|
expr_list
|
||||||
|
|
@ -537,6 +546,7 @@ Expr * parseExprFromBuf(
|
||||||
size_t length,
|
size_t length,
|
||||||
Pos::Origin origin,
|
Pos::Origin origin,
|
||||||
const SourcePath & basePath,
|
const SourcePath & basePath,
|
||||||
|
std::pmr::polymorphic_allocator<char> & alloc,
|
||||||
SymbolTable & symbols,
|
SymbolTable & symbols,
|
||||||
const EvalSettings & settings,
|
const EvalSettings & settings,
|
||||||
PosTable & positions,
|
PosTable & positions,
|
||||||
|
|
@ -551,6 +561,7 @@ Expr * parseExprFromBuf(
|
||||||
};
|
};
|
||||||
ParserState state {
|
ParserState state {
|
||||||
.lexerState = lexerState,
|
.lexerState = lexerState,
|
||||||
|
.alloc = alloc,
|
||||||
.symbols = symbols,
|
.symbols = symbols,
|
||||||
.positions = positions,
|
.positions = positions,
|
||||||
.basePath = basePath,
|
.basePath = basePath,
|
||||||
|
|
@ -563,7 +574,8 @@ Expr * parseExprFromBuf(
|
||||||
Finally _destroy([&] { yylex_destroy(scanner); });
|
Finally _destroy([&] { yylex_destroy(scanner); });
|
||||||
|
|
||||||
yy_scan_buffer(text, length, scanner);
|
yy_scan_buffer(text, length, scanner);
|
||||||
yyparse(scanner, &state);
|
Parser parser(scanner, &state);
|
||||||
|
parser.parse();
|
||||||
|
|
||||||
return state.result;
|
return state.result;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -262,7 +262,7 @@ static void scopedImport(EvalState & state, const PosIdx pos, SourcePath & path,
|
||||||
{
|
{
|
||||||
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
|
state.forceAttrs(*vScope, pos, "while evaluating the first argument passed to builtins.scopedImport");
|
||||||
|
|
||||||
Env * env = &state.allocEnv(vScope->attrs()->size());
|
Env * env = &state.mem.allocEnv(vScope->attrs()->size());
|
||||||
env->up = &state.baseEnv;
|
env->up = &state.baseEnv;
|
||||||
|
|
||||||
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv, vScope->attrs()->size());
|
auto staticEnv = std::make_shared<StaticEnv>(nullptr, state.staticBaseEnv, vScope->attrs()->size());
|
||||||
|
|
@ -3161,7 +3161,7 @@ static void prim_listToAttrs(EvalState & state, const PosIdx pos, Value ** args,
|
||||||
// Step 1. Sort the name-value attrsets in place using the memory we allocate for the result
|
// Step 1. Sort the name-value attrsets in place using the memory we allocate for the result
|
||||||
auto listView = args[0]->listView();
|
auto listView = args[0]->listView();
|
||||||
size_t listSize = listView.size();
|
size_t listSize = listView.size();
|
||||||
auto & bindings = *state.allocBindings(listSize);
|
auto & bindings = *state.mem.allocBindings(listSize);
|
||||||
using ElemPtr = decltype(&bindings[0].value);
|
using ElemPtr = decltype(&bindings[0].value);
|
||||||
|
|
||||||
for (const auto & [n, v2] : enumerate(listView)) {
|
for (const auto & [n, v2] : enumerate(listView)) {
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ add_project_arguments(
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'nix_api_fetchers.cc',
|
'nix_api_fetchers.cc',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include "nix/store/store-open.hh"
|
#include "nix/store/store-open.hh"
|
||||||
#include "nix/store/globals.hh"
|
#include "nix/store/globals.hh"
|
||||||
|
#include "nix/store/dummy-store.hh"
|
||||||
#include "nix/fetchers/fetch-settings.hh"
|
#include "nix/fetchers/fetch-settings.hh"
|
||||||
#include "nix/fetchers/fetchers.hh"
|
#include "nix/fetchers/fetchers.hh"
|
||||||
#include "nix/fetchers/git-utils.hh"
|
#include "nix/fetchers/git-utils.hh"
|
||||||
|
|
@ -179,10 +180,11 @@ TEST_F(GitTest, submodulePeriodSupport)
|
||||||
// 6) Commit the addition in super
|
// 6) Commit the addition in super
|
||||||
commitAll(super.get(), "Add submodule with branch='.'");
|
commitAll(super.get(), "Add submodule with branch='.'");
|
||||||
|
|
||||||
// TODO: Use dummy:// store with MemorySourceAccessor.
|
auto store = [] {
|
||||||
Path storeTmpDir = createTempDir();
|
auto cfg = make_ref<DummyStoreConfig>(StoreReference::Params{});
|
||||||
auto storeTmpDirAutoDelete = AutoDelete(storeTmpDir, true);
|
cfg->readOnly = false;
|
||||||
ref<Store> store = openStore(storeTmpDir);
|
return cfg->openStore();
|
||||||
|
}();
|
||||||
|
|
||||||
auto settings = fetchers::Settings{};
|
auto settings = fetchers::Settings{};
|
||||||
auto input = fetchers::Input::fromAttrs(
|
auto input = fetchers::Input::fromAttrs(
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ libgit2 = dependency('libgit2')
|
||||||
deps_private += libgit2
|
deps_private += libgit2
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'access-tokens.cc',
|
'access-tokens.cc',
|
||||||
|
|
@ -63,7 +64,7 @@ this_exe = executable(
|
||||||
test(
|
test(
|
||||||
meson.project_name(),
|
meson.project_name(),
|
||||||
this_exe,
|
this_exe,
|
||||||
env : {
|
env : asan_test_options_env + {
|
||||||
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
|
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
|
||||||
},
|
},
|
||||||
protocol : 'gtest',
|
protocol : 'gtest',
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ mkMesonExecutable (finalAttrs: {
|
||||||
buildInputs = [ writableTmpDirAsHomeHook ];
|
buildInputs = [ writableTmpDirAsHomeHook ];
|
||||||
}
|
}
|
||||||
''
|
''
|
||||||
|
export ASAN_OPTIONS=abort_on_error=1:print_summary=1:detect_leaks=0
|
||||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||||
touch $out
|
touch $out
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
#include "nix/fetchers/fetchers.hh"
|
#include "nix/fetchers/fetchers.hh"
|
||||||
#include "nix/util/json-utils.hh"
|
#include "nix/util/json-utils.hh"
|
||||||
#include <nlohmann/json.hpp>
|
#include "nix/util/tests/json-characterization.hh"
|
||||||
#include "nix/util/tests/characterization.hh"
|
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
using nlohmann::json;
|
using nlohmann::json;
|
||||||
|
|
||||||
class PublicKeyTest : public CharacterizationTest
|
class PublicKeyTest : public JsonCharacterizationTest<fetchers::PublicKey>,
|
||||||
|
public ::testing::WithParamInterface<std::pair<std::string_view, fetchers::PublicKey>>
|
||||||
{
|
{
|
||||||
std::filesystem::path unitTestData = getUnitTestData() / "public-key";
|
std::filesystem::path unitTestData = getUnitTestData() / "public-key";
|
||||||
|
|
||||||
|
|
@ -19,30 +19,35 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
#define TEST_JSON(FIXTURE, NAME, VAL) \
|
TEST_P(PublicKeyTest, from_json)
|
||||||
TEST_F(FIXTURE, PublicKey_##NAME##_from_json) \
|
{
|
||||||
{ \
|
const auto & [name, expected] = GetParam();
|
||||||
readTest(#NAME ".json", [&](const auto & encoded_) { \
|
readJsonTest(name, expected);
|
||||||
fetchers::PublicKey expected{VAL}; \
|
}
|
||||||
fetchers::PublicKey got = nlohmann::json::parse(encoded_); \
|
|
||||||
ASSERT_EQ(got, expected); \
|
|
||||||
}); \
|
|
||||||
} \
|
|
||||||
\
|
|
||||||
TEST_F(FIXTURE, PublicKey_##NAME##_to_json) \
|
|
||||||
{ \
|
|
||||||
writeTest( \
|
|
||||||
#NAME ".json", \
|
|
||||||
[&]() -> json { return nlohmann::json(fetchers::PublicKey{VAL}); }, \
|
|
||||||
[](const auto & file) { return json::parse(readFile(file)); }, \
|
|
||||||
[](const auto & file, const auto & got) { return writeFile(file, got.dump(2) + "\n"); }); \
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_JSON(PublicKeyTest, simple, (fetchers::PublicKey{.type = "ssh-rsa", .key = "ABCDE"}))
|
TEST_P(PublicKeyTest, to_json)
|
||||||
|
{
|
||||||
|
const auto & [name, value] = GetParam();
|
||||||
|
writeJsonTest(name, value);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_JSON(PublicKeyTest, defaultType, fetchers::PublicKey{.key = "ABCDE"})
|
INSTANTIATE_TEST_SUITE_P(
|
||||||
|
PublicKeyJSON,
|
||||||
#undef TEST_JSON
|
PublicKeyTest,
|
||||||
|
::testing::Values(
|
||||||
|
std::pair{
|
||||||
|
"simple",
|
||||||
|
fetchers::PublicKey{
|
||||||
|
.type = "ssh-rsa",
|
||||||
|
.key = "ABCDE",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::pair{
|
||||||
|
"defaultType",
|
||||||
|
fetchers::PublicKey{
|
||||||
|
.key = "ABCDE",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
TEST_F(PublicKeyTest, PublicKey_noRoundTrip_from_json)
|
TEST_F(PublicKeyTest, PublicKey_noRoundTrip_from_json)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
#include "nix/util/source-path.hh"
|
#include "nix/util/source-path.hh"
|
||||||
#include "nix/fetchers/fetch-to-store.hh"
|
#include "nix/fetchers/fetch-to-store.hh"
|
||||||
#include "nix/util/json-utils.hh"
|
#include "nix/util/json-utils.hh"
|
||||||
#include "nix/fetchers/store-path-accessor.hh"
|
|
||||||
#include "nix/fetchers/fetch-settings.hh"
|
#include "nix/fetchers/fetch-settings.hh"
|
||||||
|
#include "nix/fetchers/fetch-to-store.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
|
@ -332,10 +332,20 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
|
||||||
|
|
||||||
debug("using substituted/cached input '%s' in '%s'", to_string(), store->printStorePath(storePath));
|
debug("using substituted/cached input '%s' in '%s'", to_string(), store->printStorePath(storePath));
|
||||||
|
|
||||||
auto accessor = makeStorePathAccessor(store, storePath);
|
// We just ensured the store object was there
|
||||||
|
auto accessor = ref{store->getFSAccessor(storePath)};
|
||||||
|
|
||||||
accessor->fingerprint = getFingerprint(store);
|
accessor->fingerprint = getFingerprint(store);
|
||||||
|
|
||||||
|
// Store a cache entry for the substituted tree so later fetches
|
||||||
|
// can reuse the existing nar instead of copying the unpacked
|
||||||
|
// input back into the store on every evaluation.
|
||||||
|
if (accessor->fingerprint) {
|
||||||
|
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive;
|
||||||
|
auto cacheKey = makeFetchToStoreCacheKey(getName(), *accessor->fingerprint, method, "/");
|
||||||
|
settings->getCache()->upsert(cacheKey, *store, {}, storePath);
|
||||||
|
}
|
||||||
|
|
||||||
accessor->setPathDisplay("«" + to_string() + "»");
|
accessor->setPathDisplay("«" + to_string() + "»");
|
||||||
|
|
||||||
return {accessor, *this};
|
return {accessor, *this};
|
||||||
|
|
@ -509,7 +519,7 @@ fetchers::PublicKey adl_serializer<fetchers::PublicKey>::from_json(const json &
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
void adl_serializer<fetchers::PublicKey>::to_json(json & json, fetchers::PublicKey p)
|
void adl_serializer<fetchers::PublicKey>::to_json(json & json, const fetchers::PublicKey & p)
|
||||||
{
|
{
|
||||||
json["type"] = p.type;
|
json["type"] = p.type;
|
||||||
json["key"] = p.key;
|
json["key"] = p.key;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include "nix/fetchers/fetch-settings.hh"
|
#include "nix/fetchers/fetch-settings.hh"
|
||||||
#include "nix/util/json-utils.hh"
|
#include "nix/util/json-utils.hh"
|
||||||
#include "nix/util/archive.hh"
|
#include "nix/util/archive.hh"
|
||||||
|
#include "nix/util/mounted-source-accessor.hh"
|
||||||
|
|
||||||
#include <regex>
|
#include <regex>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
|
||||||
|
|
@ -398,8 +398,8 @@ struct GitHubInputScheme : GitArchiveInputScheme
|
||||||
|
|
||||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||||
|
|
||||||
auto json = nlohmann::json::parse(
|
auto downloadResult = downloadFile(store, *input.settings, url, "source", headers);
|
||||||
readFile(store->toRealPath(downloadFile(store, *input.settings, url, "source", headers).storePath)));
|
auto json = nlohmann::json::parse(store->getFSAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||||
|
|
||||||
return RefInfo{
|
return RefInfo{
|
||||||
.rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1),
|
.rev = Hash::parseAny(std::string{json["sha"]}, HashAlgorithm::SHA1),
|
||||||
|
|
@ -472,8 +472,8 @@ struct GitLabInputScheme : GitArchiveInputScheme
|
||||||
|
|
||||||
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
Headers headers = makeHeadersWithAuthTokens(*input.settings, host, input);
|
||||||
|
|
||||||
auto json = nlohmann::json::parse(
|
auto downloadResult = downloadFile(store, *input.settings, url, "source", headers);
|
||||||
readFile(store->toRealPath(downloadFile(store, *input.settings, url, "source", headers).storePath)));
|
auto json = nlohmann::json::parse(store->getFSAccessor(downloadResult.storePath)->readFile(CanonPath::root));
|
||||||
|
|
||||||
if (json.is_array() && json.size() >= 1 && json[0]["id"] != nullptr) {
|
if (json.is_array() && json.size() >= 1 && json[0]["id"] != nullptr) {
|
||||||
return RefInfo{.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)};
|
return RefInfo{.rev = Hash::parseAny(std::string(json[0]["id"]), HashAlgorithm::SHA1)};
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,5 @@ headers = files(
|
||||||
'git-utils.hh',
|
'git-utils.hh',
|
||||||
'input-cache.hh',
|
'input-cache.hh',
|
||||||
'registry.hh',
|
'registry.hh',
|
||||||
'store-path-accessor.hh',
|
|
||||||
'tarball.hh',
|
'tarball.hh',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "nix/util/source-path.hh"
|
|
||||||
|
|
||||||
namespace nix {
|
|
||||||
|
|
||||||
class StorePath;
|
|
||||||
class Store;
|
|
||||||
|
|
||||||
ref<SourceAccessor> makeStorePathAccessor(ref<Store> store, const StorePath & storePath);
|
|
||||||
|
|
||||||
SourcePath getUnfilteredRootPath(CanonPath path);
|
|
||||||
|
|
||||||
} // namespace nix
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
#include "nix/util/tarfile.hh"
|
#include "nix/util/tarfile.hh"
|
||||||
#include "nix/store/store-api.hh"
|
#include "nix/store/store-api.hh"
|
||||||
#include "nix/util/url-parts.hh"
|
#include "nix/util/url-parts.hh"
|
||||||
#include "nix/fetchers/store-path-accessor.hh"
|
|
||||||
#include "nix/fetchers/fetch-settings.hh"
|
#include "nix/fetchers/fetch-settings.hh"
|
||||||
|
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
@ -331,7 +330,8 @@ struct MercurialInputScheme : InputScheme
|
||||||
|
|
||||||
auto storePath = fetchToStore(store, input);
|
auto storePath = fetchToStore(store, input);
|
||||||
|
|
||||||
auto accessor = makeStorePathAccessor(store, storePath);
|
// We just added it, it should be there.
|
||||||
|
auto accessor = ref{store->getFSAccessor(storePath)};
|
||||||
|
|
||||||
accessor->setPathDisplay("«" + input.to_string() + "»");
|
accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ libgit2 = dependency('libgit2', version : '>= 1.9')
|
||||||
deps_private += libgit2
|
deps_private += libgit2
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'attrs.cc',
|
'attrs.cc',
|
||||||
|
|
@ -49,7 +50,6 @@ sources = files(
|
||||||
'mercurial.cc',
|
'mercurial.cc',
|
||||||
'path.cc',
|
'path.cc',
|
||||||
'registry.cc',
|
'registry.cc',
|
||||||
'store-path-accessor.cc',
|
|
||||||
'tarball.cc',
|
'tarball.cc',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
#include "nix/fetchers/fetchers.hh"
|
#include "nix/fetchers/fetchers.hh"
|
||||||
#include "nix/store/store-api.hh"
|
#include "nix/store/store-api.hh"
|
||||||
#include "nix/util/archive.hh"
|
#include "nix/util/archive.hh"
|
||||||
#include "nix/fetchers/store-path-accessor.hh"
|
|
||||||
#include "nix/fetchers/cache.hh"
|
#include "nix/fetchers/cache.hh"
|
||||||
#include "nix/fetchers/fetch-to-store.hh"
|
#include "nix/fetchers/fetch-to-store.hh"
|
||||||
#include "nix/fetchers/fetch-settings.hh"
|
#include "nix/fetchers/fetch-settings.hh"
|
||||||
|
|
@ -153,7 +152,7 @@ struct PathInputScheme : InputScheme
|
||||||
if (!input.getLastModified())
|
if (!input.getLastModified())
|
||||||
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
|
input.attrs.insert_or_assign("lastModified", uint64_t(mtime));
|
||||||
|
|
||||||
return {makeStorePathAccessor(store, *storePath), std::move(input)};
|
return {ref{store->getFSAccessor(*storePath)}, std::move(input)};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
|
||||||
|
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
#include "nix/fetchers/store-path-accessor.hh"
|
|
||||||
#include "nix/store/store-api.hh"
|
|
||||||
|
|
||||||
namespace nix {
|
|
||||||
|
|
||||||
ref<SourceAccessor> makeStorePathAccessor(ref<Store> store, const StorePath & storePath)
|
|
||||||
{
|
|
||||||
return projectSubdirSourceAccessor(store->getFSAccessor(), storePath.to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace nix
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
#include "nix/util/archive.hh"
|
#include "nix/util/archive.hh"
|
||||||
#include "nix/util/tarfile.hh"
|
#include "nix/util/tarfile.hh"
|
||||||
#include "nix/util/types.hh"
|
#include "nix/util/types.hh"
|
||||||
#include "nix/fetchers/store-path-accessor.hh"
|
|
||||||
#include "nix/store/store-api.hh"
|
#include "nix/store/store-api.hh"
|
||||||
#include "nix/fetchers/git-utils.hh"
|
#include "nix/fetchers/git-utils.hh"
|
||||||
#include "nix/fetchers/fetch-settings.hh"
|
#include "nix/fetchers/fetch-settings.hh"
|
||||||
|
|
@ -354,7 +353,7 @@ struct FileInputScheme : CurlInputScheme
|
||||||
auto narHash = store->queryPathInfo(file.storePath)->narHash;
|
auto narHash = store->queryPathInfo(file.storePath)->narHash;
|
||||||
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
|
||||||
|
|
||||||
auto accessor = makeStorePathAccessor(store, file.storePath);
|
auto accessor = ref{store->getFSAccessor(file.storePath)};
|
||||||
|
|
||||||
accessor->setPathDisplay("«" + input.to_string() + "»");
|
accessor->setPathDisplay("«" + input.to_string() + "»");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ deps_public_maybe_subproject = [
|
||||||
subdir('nix-meson-build-support/subprojects')
|
subdir('nix-meson-build-support/subprojects')
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'nix_api_flake.cc',
|
'nix_api_flake.cc',
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ gtest = dependency('gtest', main : true)
|
||||||
deps_private += gtest
|
deps_private += gtest
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'flakeref.cc',
|
'flakeref.cc',
|
||||||
|
|
@ -58,7 +59,7 @@ this_exe = executable(
|
||||||
test(
|
test(
|
||||||
meson.project_name(),
|
meson.project_name(),
|
||||||
this_exe,
|
this_exe,
|
||||||
env : {
|
env : asan_test_options_env + {
|
||||||
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
|
'_NIX_TEST_UNIT_DATA' : meson.current_source_dir() / 'data',
|
||||||
'NIX_CONFIG' : 'extra-experimental-features = flakes',
|
'NIX_CONFIG' : 'extra-experimental-features = flakes',
|
||||||
'HOME' : meson.current_build_dir() / 'test-home',
|
'HOME' : meson.current_build_dir() / 'test-home',
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,7 @@ mkMesonExecutable (finalAttrs: {
|
||||||
buildInputs = [ writableTmpDirAsHomeHook ];
|
buildInputs = [ writableTmpDirAsHomeHook ];
|
||||||
}
|
}
|
||||||
(''
|
(''
|
||||||
|
export ASAN_OPTIONS=abort_on_error=1:print_summary=1:detect_leaks=0
|
||||||
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
|
||||||
export NIX_CONFIG="extra-experimental-features = flakes"
|
export NIX_CONFIG="extra-experimental-features = flakes"
|
||||||
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ nlohmann_json = dependency('nlohmann_json', version : '>= 3.9')
|
||||||
deps_public += nlohmann_json
|
deps_public += nlohmann_json
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
subdir('nix-meson-build-support/generate-header')
|
subdir('nix-meson-build-support/generate-header')
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ deps_public_maybe_subproject = [
|
||||||
subdir('nix-meson-build-support/subprojects')
|
subdir('nix-meson-build-support/subprojects')
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'nix_api_main.cc',
|
'nix_api_main.cc',
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include "nix_api_util_internal.h"
|
#include "nix_api_util_internal.h"
|
||||||
|
|
||||||
#include "nix/main/plugin.hh"
|
#include "nix/main/plugin.hh"
|
||||||
|
#include "nix/main/loggers.hh"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
|
|
@ -17,4 +18,16 @@ nix_err nix_init_plugins(nix_c_context * context)
|
||||||
NIXC_CATCH_ERRS
|
NIXC_CATCH_ERRS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nix_err nix_set_log_format(nix_c_context * context, const char * format)
|
||||||
|
{
|
||||||
|
if (context)
|
||||||
|
context->last_err_code = NIX_OK;
|
||||||
|
if (format == nullptr)
|
||||||
|
return nix_set_err_msg(context, NIX_ERR_UNKNOWN, "Log format is null");
|
||||||
|
try {
|
||||||
|
nix::setLogFormat(format);
|
||||||
|
}
|
||||||
|
NIXC_CATCH_ERRS
|
||||||
|
}
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,14 @@ extern "C" {
|
||||||
*/
|
*/
|
||||||
nix_err nix_init_plugins(nix_c_context * context);
|
nix_err nix_init_plugins(nix_c_context * context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the log format
|
||||||
|
*
|
||||||
|
* @param[out] context Optional, stores error information
|
||||||
|
* @param[in] format The string name of the format.
|
||||||
|
*/
|
||||||
|
nix_err nix_set_log_format(nix_c_context * context, const char * format);
|
||||||
|
|
||||||
// cffi end
|
// cffi end
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,7 @@ config_priv_h = configure_file(
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'common-args.cc',
|
'common-args.cc',
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ deps_public_maybe_subproject = [
|
||||||
subdir('nix-meson-build-support/subprojects')
|
subdir('nix-meson-build-support/subprojects')
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'nix_api_store.cc',
|
'nix_api_store.cc',
|
||||||
|
|
|
||||||
|
|
@ -166,11 +166,44 @@ void nix_store_path_free(StorePath * sp)
|
||||||
delete sp;
|
delete sp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void nix_derivation_free(nix_derivation * drv)
|
||||||
|
{
|
||||||
|
delete drv;
|
||||||
|
}
|
||||||
|
|
||||||
StorePath * nix_store_path_clone(const StorePath * p)
|
StorePath * nix_store_path_clone(const StorePath * p)
|
||||||
{
|
{
|
||||||
return new StorePath{p->path};
|
return new StorePath{p->path};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store, const char * json)
|
||||||
|
{
|
||||||
|
if (context)
|
||||||
|
context->last_err_code = NIX_OK;
|
||||||
|
try {
|
||||||
|
auto drv = static_cast<nix::Derivation>(nlohmann::json::parse(json));
|
||||||
|
|
||||||
|
auto drvPath = nix::writeDerivation(*store->ptr, drv, nix::NoRepair, /* read only */ true);
|
||||||
|
|
||||||
|
drv.checkInvariants(*store->ptr, drvPath);
|
||||||
|
|
||||||
|
return new nix_derivation{drv};
|
||||||
|
}
|
||||||
|
NIXC_CATCH_ERRS_NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
StorePath * nix_add_derivation(nix_c_context * context, Store * store, nix_derivation * derivation)
|
||||||
|
{
|
||||||
|
if (context)
|
||||||
|
context->last_err_code = NIX_OK;
|
||||||
|
try {
|
||||||
|
auto ret = nix::writeDerivation(*store->ptr, derivation->drv, nix::NoRepair);
|
||||||
|
|
||||||
|
return new StorePath{ret};
|
||||||
|
}
|
||||||
|
NIXC_CATCH_ERRS_NULL
|
||||||
|
}
|
||||||
|
|
||||||
nix_err nix_store_copy_closure(nix_c_context * context, Store * srcStore, Store * dstStore, StorePath * path)
|
nix_err nix_store_copy_closure(nix_c_context * context, Store * srcStore, Store * dstStore, StorePath * path)
|
||||||
{
|
{
|
||||||
if (context)
|
if (context)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,8 @@ extern "C" {
|
||||||
typedef struct Store Store;
|
typedef struct Store Store;
|
||||||
/** @brief Nix store path */
|
/** @brief Nix store path */
|
||||||
typedef struct StorePath StorePath;
|
typedef struct StorePath StorePath;
|
||||||
|
/** @brief Nix Derivation */
|
||||||
|
typedef struct nix_derivation nix_derivation;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Initializes the Nix store library
|
* @brief Initializes the Nix store library
|
||||||
|
|
@ -207,6 +209,32 @@ nix_err nix_store_realise(
|
||||||
nix_err
|
nix_err
|
||||||
nix_store_get_version(nix_c_context * context, Store * store, nix_get_string_callback callback, void * user_data);
|
nix_store_get_version(nix_c_context * context, Store * store, nix_get_string_callback callback, void * user_data);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a `nix_derivation` from a JSON representation of that derivation.
|
||||||
|
*
|
||||||
|
* @param[out] context Optional, stores error information.
|
||||||
|
* @param[in] store nix store reference.
|
||||||
|
* @param[in] json JSON of the derivation as a string.
|
||||||
|
*/
|
||||||
|
nix_derivation * nix_derivation_from_json(nix_c_context * context, Store * store, const char * json);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Add the given `nix_derivation` to the given store
|
||||||
|
*
|
||||||
|
* @param[out] context Optional, stores error information.
|
||||||
|
* @param[in] store nix store reference. The derivation will be inserted here.
|
||||||
|
* @param[in] derivation nix_derivation to insert into the given store.
|
||||||
|
*/
|
||||||
|
StorePath * nix_add_derivation(nix_c_context * context, Store * store, nix_derivation * derivation);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Deallocate a `nix_derivation`
|
||||||
|
*
|
||||||
|
* Does not fail.
|
||||||
|
* @param[in] drv the derivation to free
|
||||||
|
*/
|
||||||
|
void nix_derivation_free(nix_derivation * drv);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Copy the closure of `path` from `srcStore` to `dstStore`.
|
* @brief Copy the closure of `path` from `srcStore` to `dstStore`.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#ifndef NIX_API_STORE_INTERNAL_H
|
#ifndef NIX_API_STORE_INTERNAL_H
|
||||||
#define NIX_API_STORE_INTERNAL_H
|
#define NIX_API_STORE_INTERNAL_H
|
||||||
#include "nix/store/store-api.hh"
|
#include "nix/store/store-api.hh"
|
||||||
|
#include "nix/store/derivations.hh"
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
|
|
@ -14,6 +15,11 @@ struct StorePath
|
||||||
nix::StorePath path;
|
nix::StorePath path;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct nix_derivation
|
||||||
|
{
|
||||||
|
nix::Derivation drv;
|
||||||
|
};
|
||||||
|
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,5 @@ headers = files(
|
||||||
'outputs-spec.hh',
|
'outputs-spec.hh',
|
||||||
'path.hh',
|
'path.hh',
|
||||||
'protocol.hh',
|
'protocol.hh',
|
||||||
|
'test-main.hh',
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,33 +12,32 @@
|
||||||
#include <gtest/gtest.h>
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
namespace nixC {
|
namespace nixC {
|
||||||
class nix_api_store_test : public nix_api_util_context
|
|
||||||
|
class nix_api_store_test_base : public nix_api_util_context
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
nix_api_store_test()
|
nix_api_store_test_base()
|
||||||
{
|
{
|
||||||
nix_libstore_init(ctx);
|
nix_libstore_init(ctx);
|
||||||
init_local_store();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
~nix_api_store_test() override
|
~nix_api_store_test_base() override
|
||||||
{
|
{
|
||||||
nix_store_free(store);
|
if (exists(std::filesystem::path{nixDir})) {
|
||||||
|
|
||||||
for (auto & path : std::filesystem::recursive_directory_iterator(nixDir)) {
|
for (auto & path : std::filesystem::recursive_directory_iterator(nixDir)) {
|
||||||
std::filesystem::permissions(path, std::filesystem::perms::owner_all);
|
std::filesystem::permissions(path, std::filesystem::perms::owner_all);
|
||||||
}
|
}
|
||||||
std::filesystem::remove_all(nixDir);
|
std::filesystem::remove_all(nixDir);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Store * store;
|
|
||||||
std::string nixDir;
|
std::string nixDir;
|
||||||
std::string nixStoreDir;
|
std::string nixStoreDir;
|
||||||
std::string nixStateDir;
|
std::string nixStateDir;
|
||||||
std::string nixLogDir;
|
std::string nixLogDir;
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void init_local_store()
|
Store * open_local_store()
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// no `mkdtemp` with MinGW
|
// no `mkdtemp` with MinGW
|
||||||
|
|
@ -66,11 +65,37 @@ protected:
|
||||||
|
|
||||||
const char ** params[] = {p1, p2, p3, nullptr};
|
const char ** params[] = {p1, p2, p3, nullptr};
|
||||||
|
|
||||||
store = nix_store_open(ctx, "local", params);
|
auto * store = nix_store_open(ctx, "local", params);
|
||||||
if (!store) {
|
if (!store) {
|
||||||
std::string errMsg = nix_err_msg(nullptr, ctx, nullptr);
|
std::string errMsg = nix_err_msg(nullptr, ctx, nullptr);
|
||||||
ASSERT_NE(store, nullptr) << "Could not open store: " << errMsg;
|
EXPECT_NE(store, nullptr) << "Could not open store: " << errMsg;
|
||||||
|
assert(store);
|
||||||
};
|
};
|
||||||
|
return store;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class nix_api_store_test : public nix_api_store_test_base
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
nix_api_store_test()
|
||||||
|
: nix_api_store_test_base{}
|
||||||
|
{
|
||||||
|
init_local_store();
|
||||||
|
};
|
||||||
|
|
||||||
|
~nix_api_store_test() override
|
||||||
|
{
|
||||||
|
nix_store_free(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
Store * store;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void init_local_store()
|
||||||
|
{
|
||||||
|
store = open_local_store();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace nixC
|
} // namespace nixC
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
///@file
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this for a GTest test suite that will including performing Nix
|
||||||
|
* builds, before running tests.
|
||||||
|
*/
|
||||||
|
int testMainForBuidingPre(int argc, char ** argv);
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
|
|
@ -29,11 +29,13 @@ rapidcheck = dependency('rapidcheck')
|
||||||
deps_public += rapidcheck
|
deps_public += rapidcheck
|
||||||
|
|
||||||
subdir('nix-meson-build-support/common')
|
subdir('nix-meson-build-support/common')
|
||||||
|
subdir('nix-meson-build-support/asan-options')
|
||||||
|
|
||||||
sources = files(
|
sources = files(
|
||||||
'derived-path.cc',
|
'derived-path.cc',
|
||||||
'outputs-spec.cc',
|
'outputs-spec.cc',
|
||||||
'path.cc',
|
'path.cc',
|
||||||
|
'test-main.cc',
|
||||||
)
|
)
|
||||||
|
|
||||||
subdir('include/nix/store/tests')
|
subdir('include/nix/store/tests')
|
||||||
|
|
|
||||||
47
src/libstore-test-support/test-main.cc
Normal file
47
src/libstore-test-support/test-main.cc
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include "nix/store/globals.hh"
|
||||||
|
#include "nix/util/logging.hh"
|
||||||
|
|
||||||
|
#include "nix/store/tests/test-main.hh"
|
||||||
|
|
||||||
|
namespace nix {
|
||||||
|
|
||||||
|
int testMainForBuidingPre(int argc, char ** argv)
|
||||||
|
{
|
||||||
|
if (argc > 1 && std::string_view(argv[1]) == "__build-remote") {
|
||||||
|
printError("test-build-remote: not supported in libexpr unit tests");
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable build hook. We won't be testing remote builds in these unit tests. If we do, fix the above build hook.
|
||||||
|
settings.buildHook = {};
|
||||||
|
|
||||||
|
// No substituters, unless a test specifically requests.
|
||||||
|
settings.substituters = {};
|
||||||
|
|
||||||
|
#ifdef __linux__ // should match the conditional around sandboxBuildDir declaration.
|
||||||
|
|
||||||
|
// When building and testing nix within the host's Nix sandbox, our store dir will be located in the host's
|
||||||
|
// sandboxBuildDir, e.g.: Host
|
||||||
|
// storeDir = /nix/store
|
||||||
|
// sandboxBuildDir = /build
|
||||||
|
// This process
|
||||||
|
// storeDir = /build/foo/bar/store
|
||||||
|
// sandboxBuildDir = /build
|
||||||
|
// However, we have a rule that the store dir must not be inside the storeDir, so we need to pick a different
|
||||||
|
// sandboxBuildDir.
|
||||||
|
settings.sandboxBuildDir = "/test-build-dir-instead-of-usual-build-dir";
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// Avoid this error, when already running in a sandbox:
|
||||||
|
// sandbox-exec: sandbox_apply: Operation not permitted
|
||||||
|
settings.sandboxMode = smDisabled;
|
||||||
|
setEnv("_NIX_TEST_NO_SANDBOX", "1");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace nix
|
||||||
|
|
@ -21,5 +21,6 @@
|
||||||
"method": "nar"
|
"method": "nar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": "my-system"
|
"system": "my-system",
|
||||||
|
"version": 3
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,5 +32,6 @@
|
||||||
],
|
],
|
||||||
"system": "my-system"
|
"system": "my-system"
|
||||||
},
|
},
|
||||||
"system": "my-system"
|
"system": "my-system",
|
||||||
|
"version": 3
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,14 +10,14 @@
|
||||||
"out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"
|
"out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9"
|
||||||
},
|
},
|
||||||
"inputDrvs": {
|
"inputDrvs": {
|
||||||
"/nix/store/j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv": {
|
"j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv": {
|
||||||
"dynamicOutputs": {},
|
"dynamicOutputs": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
"dev",
|
"dev",
|
||||||
"out"
|
"out"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv": {
|
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv": {
|
||||||
"dynamicOutputs": {},
|
"dynamicOutputs": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
"dev",
|
"dev",
|
||||||
|
|
@ -26,7 +26,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inputSrcs": [
|
"inputSrcs": [
|
||||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||||
],
|
],
|
||||||
"name": "advanced-attributes-structured-attrs",
|
"name": "advanced-attributes-structured-attrs",
|
||||||
"outputs": {
|
"outputs": {
|
||||||
|
|
@ -100,5 +100,6 @@
|
||||||
],
|
],
|
||||||
"system": "my-system"
|
"system": "my-system"
|
||||||
},
|
},
|
||||||
"system": "my-system"
|
"system": "my-system",
|
||||||
|
"version": 3
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,14 +26,14 @@
|
||||||
"system": "my-system"
|
"system": "my-system"
|
||||||
},
|
},
|
||||||
"inputDrvs": {
|
"inputDrvs": {
|
||||||
"/nix/store/j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv": {
|
"j56sf12rxpcv5swr14vsjn5cwm6bj03h-foo.drv": {
|
||||||
"dynamicOutputs": {},
|
"dynamicOutputs": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
"dev",
|
"dev",
|
||||||
"out"
|
"out"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv": {
|
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv": {
|
||||||
"dynamicOutputs": {},
|
"dynamicOutputs": {},
|
||||||
"outputs": [
|
"outputs": [
|
||||||
"dev",
|
"dev",
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inputSrcs": [
|
"inputSrcs": [
|
||||||
"/nix/store/qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
"qnml92yh97a6fbrs2m5qg5cqlc8vni58-bar.drv"
|
||||||
],
|
],
|
||||||
"name": "advanced-attributes",
|
"name": "advanced-attributes",
|
||||||
"outputs": {
|
"outputs": {
|
||||||
|
|
@ -51,5 +51,6 @@
|
||||||
"method": "nar"
|
"method": "nar"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"system": "my-system"
|
"system": "my-system",
|
||||||
|
"version": 3
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
src/libstore-tests/data/derivation/ca/self-contained.json
Normal file
24
src/libstore-tests/data/derivation/ca/self-contained.json
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"args": [
|
||||||
|
"-c",
|
||||||
|
"echo $name foo > $out"
|
||||||
|
],
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"env": {
|
||||||
|
"builder": "/bin/sh",
|
||||||
|
"name": "myname",
|
||||||
|
"out": "/1rz4g4znpzjwh1xymhjpm42vipw92pr73vdgl6xs1hycac8kf2n9",
|
||||||
|
"system": "x86_64-linux"
|
||||||
|
},
|
||||||
|
"inputDrvs": {},
|
||||||
|
"inputSrcs": [],
|
||||||
|
"name": "myname",
|
||||||
|
"outputs": {
|
||||||
|
"out": {
|
||||||
|
"hashAlgo": "sha256",
|
||||||
|
"method": "nar"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"system": "x86_64-linux",
|
||||||
|
"version": 3
|
||||||
|
}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
"BIG_BAD": "WOLF"
|
"BIG_BAD": "WOLF"
|
||||||
},
|
},
|
||||||
"inputDrvs": {
|
"inputDrvs": {
|
||||||
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
|
"c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep2.drv": {
|
||||||
"dynamicOutputs": {
|
"dynamicOutputs": {
|
||||||
"cat": {
|
"cat": {
|
||||||
"dynamicOutputs": {},
|
"dynamicOutputs": {},
|
||||||
|
|
@ -30,9 +30,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inputSrcs": [
|
"inputSrcs": [
|
||||||
"/nix/store/c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
|
"c015dhfh5l0lp6wxyvdn7bmwhbbr6hr9-dep1"
|
||||||
],
|
],
|
||||||
"name": "dyn-dep-derivation",
|
"name": "dyn-dep-derivation",
|
||||||
"outputs": {},
|
"outputs": {},
|
||||||
"system": "wasm-sel4"
|
"system": "wasm-sel4",
|
||||||
|
"version": 3
|
||||||
}
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue