1
1
Fork 0
mirror of https://github.com/NixOS/nix.git synced 2025-12-02 15:11:00 +01:00

Merge remote-tracking branch 'detsys/detsys-main' into fix-deep-overrides

This commit is contained in:
Eelco Dolstra 2025-06-25 21:53:23 +02:00
commit 0e352a6cec
51 changed files with 722 additions and 461 deletions

View file

@ -45,6 +45,7 @@ linkify_gh() {
| trim_trailing_newlines \ | trim_trailing_newlines \
| sed -e 's/^\* /\n* /' \ | sed -e 's/^\* /\n* /' \
| linkify_gh | linkify_gh
echo "" # final newline
) > "$scratch/changes.md" ) > "$scratch/changes.md"
( (

View file

@ -1,10 +1,16 @@
on: on:
workflow_call: workflow_call:
inputs: inputs:
os: system:
required: true required: true
type: string type: string
system: runner:
required: true
type: string
runner_for_virt:
required: true
type: string
runner_small:
required: true required: true
type: string type: string
if: if:
@ -15,13 +21,21 @@ on:
required: false required: false
default: true default: true
type: boolean type: boolean
run_vm_tests:
required: false
default: false
type: boolean
run_regression_tests:
required: false
default: false
type: boolean
jobs: jobs:
build: build:
if: ${{ inputs.if }} if: ${{ inputs.if }}
strategy: strategy:
fail-fast: false fail-fast: false
runs-on: ${{ inputs.os }} runs-on: ${{ inputs.runner }}
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
@ -33,15 +47,172 @@ jobs:
with: with:
name: ${{ inputs.system }} name: ${{ inputs.system }}
path: ./tarball/*.xz path: ./tarball/*.xz
test: test:
if: ${{ inputs.if && inputs.run_tests}} if: ${{ inputs.if && inputs.run_tests}}
needs: build needs: build
strategy: strategy:
fail-fast: false fail-fast: false
runs-on: ${{ inputs.os }} runs-on: ${{ inputs.runner }}
timeout-minutes: 60 timeout-minutes: 60
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main - uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main - uses: DeterminateSystems/flakehub-cache-action@main
- run: nix flake check -L --system ${{ inputs.system }} - run: nix flake check -L --system ${{ inputs.system }}
vm_tests_smoke:
if: inputs.run_vm_tests && github.event_name != 'merge_group'
needs: build
runs-on: ${{ inputs.runner_for_virt }}
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- run: |
nix build -L \
.#hydraJobs.tests.functional_user \
.#hydraJobs.tests.githubFlakes \
.#hydraJobs.tests.nix-docker \
.#hydraJobs.tests.tarballFlakes \
;
vm_tests_all:
if: inputs.run_vm_tests && github.event_name == 'merge_group'
needs: build
runs-on: ${{ inputs.runner_for_virt }}
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- run: |
cmd() {
nix build -L --keep-going --timeout 600 \
$(nix flake show --json \
| jq -r '
.hydraJobs.tests
| with_entries(select(.value.type == "derivation"))
| keys[]
| ".#hydraJobs.tests." + .')
}
if ! cmd; then
echo "failed, retrying once ..."
printf "\n\n\n\n\n\n\n\n"
cmd
fi
flake_regressions:
if: |
(inputs.run_regression_tests && github.event_name == 'merge_group')
|| (
inputs.run_regression_tests
&& github.event.pull_request.head.repo.full_name == 'DeterminateSystems/nix-src'
&& (
(github.event.action == 'labeled' && github.event.label.name == 'flake-regression-test')
|| (github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'flake-regression-test'))
)
)
needs: build
runs-on: ${{ inputs.runner }}
strategy:
matrix:
nix_config:
- "lazy-trees = true"
- "lazy-trees = false"
glob:
- "[0-d]*"
- "[e-l]*"
- "[m]*"
- "[n-r]*"
- "[s-z]*"
steps:
- name: Checkout nix
uses: actions/checkout@v4
- name: Checkout flake-regressions
uses: actions/checkout@v4
with:
repository: DeterminateSystems/flake-regressions
path: flake-regressions
- name: Checkout flake-regressions-data
uses: actions/checkout@v4
with:
repository: DeterminateSystems/flake-regressions-data
path: flake-regressions/tests
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- env:
PARALLEL: "-P 50%"
FLAKE_REGRESSION_GLOB: ${{ matrix.glob }}
NIX_CONFIG: ${{ matrix.nix_config }}
run: |
set -x
if [ ! -z "${NSC_CACHE_PATH:-}" ]; then
mkdir -p "${NSC_CACHE_PATH}/nix/xdg-cache"
export XDG_CACHE_HOME="${NSC_CACHE_PATH}/nix/xdg-cache"
fi
nix build -L --out-link ./new-nix
export PATH=$(pwd)/new-nix/bin:$PATH
if ! flake-regressions/eval-all.sh; then
echo "Some failed, trying again"
printf "\n\n\n\n\n\n\n\n"
flake-regressions/eval-all.sh
fi
manual:
if: github.event_name != 'merge_group'
needs: build
runs-on: ${{ inputs.runner_small }}
permissions:
id-token: "write"
contents: "read"
pull-requests: "write"
statuses: "write"
deployments: "write"
steps:
- name: Checkout nix
uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- name: Build manual
run: nix build .#hydraJobs.manual
- uses: nwtgck/actions-netlify@v3.0
with:
publish-dir: "./result/share/doc/nix/manual"
production-branch: detsys-main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deploy from GitHub Actions"
# NOTE(cole-h): We have a perpetual PR displaying our changes against upstream open, but
# its conversation is locked, so this PR comment can never be posted.
# https://github.com/DeterminateSystems/nix-src/pull/4
enable-pull-request-comment: ${{ github.event.pull_request.number != 4 }}
enable-commit-comment: true
enable-commit-status: true
overwrites-pull-request-comment: true
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
success:
needs:
- build
- test
- vm_tests_smoke
- vm_tests_all
- flake_regressions
- manual
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- run: "true"
- run: |
echo "A dependent in the build matrix failed:"
echo "$needs"
exit 1
env:
needs: ${{ toJSON(needs) }}
if: |
contains(needs.*.result, 'failure') ||
contains(needs.*.result, 'cancelled')

View file

@ -4,18 +4,26 @@ on:
pull_request: pull_request:
push: push:
branches: branches:
# NOTE: make sure any branches here are also valid directory names,
# otherwise creating the directory and uploading to s3 will fail
- detsys-main - detsys-main
- main - main
- master - master
merge_group: merge_group:
release:
types:
- published
permissions: permissions:
id-token: "write" id-token: "write"
contents: "read" contents: "read"
pull-requests: "write"
statuses: "write"
deployments: "write"
jobs: jobs:
eval: eval:
runs-on: blacksmith-32vcpu-ubuntu-2204 runs-on: UbuntuLatest32Cores128G
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
@ -26,150 +34,108 @@ jobs:
build_x86_64-linux: build_x86_64-linux:
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
with: with:
os: blacksmith-32vcpu-ubuntu-2204
system: x86_64-linux system: x86_64-linux
runner: namespace-profile-linuxamd32c64g-cache
runner_for_virt: UbuntuLatest32Cores128G
runner_small: ubuntu-latest
run_tests: true
run_vm_tests: true
run_regression_tests: true
build_aarch64-linux: build_aarch64-linux:
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
with: with:
if: ${{ github.event_name == 'merge_group' }} if: ${{ github.event_name != 'pull_request' }}
os: blacksmith-32vcpu-ubuntu-2204-arm
system: aarch64-linux system: aarch64-linux
runner: UbuntuLatest32Cores128GArm
runner_for_virt: UbuntuLatest32Cores128GArm
runner_small: UbuntuLatest32Cores128GArm
build_x86_64-darwin: build_x86_64-darwin:
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
with: with:
if: ${{ github.event_name == 'merge_group' }} if: ${{ github.event_name != 'pull_request' }}
os: macos-latest-large
system: x86_64-darwin system: x86_64-darwin
runner: macos-latest-large
runner_for_virt: macos-latest-large
runner_small: macos-latest-large
build_aarch64-darwin: build_aarch64-darwin:
uses: ./.github/workflows/build.yml uses: ./.github/workflows/build.yml
with: with:
os: namespace-profile-mac-m2-12c28g
system: aarch64-darwin system: aarch64-darwin
runner: namespace-profile-mac-m2-12c28g
runner_for_virt: namespace-profile-mac-m2-12c28g
runner_small: macos-latest-xlarge
vm_tests_smoke: success:
if: github.event_name != 'merge_group' runs-on: ubuntu-latest
needs: build_x86_64-linux needs:
runs-on: blacksmith-32vcpu-ubuntu-2204 - eval
- build_x86_64-linux
- build_aarch64-linux
- build_x86_64-darwin
- build_aarch64-darwin
if: ${{ always() }}
steps: steps:
- uses: actions/checkout@v4 - run: "true"
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- run: | - run: |
nix build -L \ echo "A dependent in the build matrix failed:"
.#hydraJobs.tests.functional_user \ echo "$needs"
.#hydraJobs.tests.githubFlakes \ exit 1
.#hydraJobs.tests.nix-docker \
.#hydraJobs.tests.tarballFlakes \
;
vm_tests_all:
if: github.event_name == 'merge_group'
needs: build_x86_64-linux
runs-on: blacksmith-32vcpu-ubuntu-2204
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- run: |
nix build -L --keep-going \
$(nix flake show --json \
| jq -r '
.hydraJobs.tests
| with_entries(select(.value.type == "derivation"))
| keys[]
| ".#hydraJobs.tests." + .')
flake_regressions:
if: |
github.event_name == 'merge_group'
|| (
github.event.pull_request.head.repo.full_name == 'DeterminateSystems/nix-src'
&& (
(github.event.action == 'labeled' && github.event.label.name == 'flake-regression-test')
|| (github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'flake-regression-test'))
)
)
needs: build_x86_64-linux
runs-on: namespace-profile-x86-32cpu-64gb
steps:
- name: Checkout nix
uses: actions/checkout@v4
- name: Checkout flake-regressions
uses: actions/checkout@v4
with:
repository: DeterminateSystems/flake-regressions
path: flake-regressions
- name: Checkout flake-regressions-data
uses: actions/checkout@v4
with:
repository: DeterminateSystems/flake-regressions-data
path: flake-regressions/tests
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- run: nix build -L --out-link ./new-nix && PATH=$(pwd)/new-nix/bin:$PATH PARALLEL="-P 50%" flake-regressions/eval-all.sh
flake_regressions_lazy:
if: |
github.event_name == 'merge_group'
|| (
github.event.pull_request.head.repo.full_name == 'DeterminateSystems/nix-src'
&& (
(github.event.action == 'labeled' && github.event.label.name == 'flake-regression-test')
|| (github.event.action != 'labeled' && contains(github.event.pull_request.labels.*.name, 'flake-regression-test'))
)
)
needs: build_x86_64-linux
runs-on: namespace-profile-x86-32cpu-64gb
steps:
- name: Checkout nix
uses: actions/checkout@v4
- name: Checkout flake-regressions
uses: actions/checkout@v4
with:
repository: DeterminateSystems/flake-regressions
path: flake-regressions
- name: Checkout flake-regressions-data
uses: actions/checkout@v4
with:
repository: DeterminateSystems/flake-regressions-data
path: flake-regressions/tests
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- run: nix build -L --out-link ./new-nix && PATH=$(pwd)/new-nix/bin:$PATH PARALLEL="-P 50%" NIX_CONFIG="lazy-trees = true" flake-regressions/eval-all.sh
manual:
if: github.event_name != 'merge_group'
needs: build_x86_64-linux
runs-on: blacksmith
permissions:
id-token: "write"
contents: "read"
pull-requests: "write"
statuses: "write"
deployments: "write"
steps:
- name: Checkout nix
uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-cache-action@main
- name: Build manual
run: nix build .#hydraJobs.manual
- uses: nwtgck/actions-netlify@v3.0
with:
publish-dir: "./result/share/doc/nix/manual"
production-branch: detsys-main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: "Deploy from GitHub Actions"
# NOTE(cole-h): We have a perpetual PR displaying our changes against upstream open, but
# its conversation is locked, so this PR comment can never be posted.
# https://github.com/DeterminateSystems/nix-src/pull/4
enable-pull-request-comment: ${{ github.event.pull_request.number != 4 }}
enable-commit-comment: true
enable-commit-status: true
overwrites-pull-request-comment: true
env: env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }} needs: ${{ toJSON(needs) }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }} if: |
contains(needs.*.result, 'failure') ||
contains(needs.*.result, 'cancelled')
- uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main
- name: Create artifacts directory
run: mkdir -p ./artifacts
- name: Fetch artifacts
uses: actions/download-artifact@v4
with:
path: downloaded
- name: Move downloaded artifacts to artifacts directory
run: |
for dir in ./downloaded/*; do
arch="$(basename "$dir")"
mv "$dir"/*.xz ./artifacts/"${arch}"
done
- name: Build fallback-paths.nix
if: ${{ github.event_name != 'pull_request' }}
run: |
nix build .#fallbackPathsNix --out-link fallback
cat fallback > ./artifacts/fallback-paths.nix
- uses: DeterminateSystems/push-artifact-ids@main
with:
s3_upload_role: ${{ secrets.AWS_S3_UPLOAD_ROLE_ARN }}
bucket: ${{ secrets.AWS_S3_UPLOAD_BUCKET_NAME }}
directory: ./artifacts
ids_project_name: determinate-nix
ids_binary_prefix: determinate-nix
skip_acl: true
allowed_branches: '["detsys-main"]'
publish:
needs:
- success
if: (!github.repository.fork && (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) || startsWith(github.ref, 'refs/tags/')))
environment: ${{ github.event_name == 'release' && 'production' || '' }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-push@main
with:
rolling: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
visibility: "public"
tag: "${{ github.ref_name }}"

View file

@ -29,4 +29,4 @@ jobs:
git commit -m "Set .version-determinate to ${{ inputs.version }}" || true git commit -m "Set .version-determinate to ${{ inputs.version }}" || true
./.github/release-notes.sh ./.github/release-notes.sh
git add doc git add doc
git commit -m "Generare release notes for ${{ inputs.version }}" || true git commit -m "Generate release notes for ${{ inputs.version }}" || true

View file

@ -1,113 +0,0 @@
name: Upload release
concurrency:
group: upload-release
on:
workflow_call:
push:
branches:
# NOTE: make sure any branches here are also valid directory names,
# otherwise creating the directory and uploading to s3 will fail
- "detsys-main"
pull_request:
types:
- opened
- reopened
- synchronize
- labeled
release:
types:
- published
permissions:
id-token: "write"
contents: "read"
jobs:
build-x86_64-linux:
uses: ./.github/workflows/build.yml
with:
os: blacksmith-32vcpu-ubuntu-2204
system: x86_64-linux
run_tests: false
build-aarch64-linux:
uses: ./.github/workflows/build.yml
with:
os: blacksmith-32vcpu-ubuntu-2204-arm
system: aarch64-linux
run_tests: false
build-x86_64-darwin:
uses: ./.github/workflows/build.yml
with:
os: macos-latest-large
system: x86_64-darwin
run_tests: false
build-aarch64-darwin:
uses: ./.github/workflows/build.yml
with:
os: macos-latest-xlarge
system: aarch64-darwin
run_tests: false
release:
runs-on: ubuntu-latest
needs:
- build-x86_64-linux
- build-aarch64-linux
- build-x86_64-darwin
- build-aarch64-darwin
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main
- name: Create artifacts directory
run: mkdir -p ./artifacts
- name: Fetch artifacts
uses: actions/download-artifact@v4
with:
path: downloaded
- name: Move downloaded artifacts to artifacts directory
run: |
for dir in ./downloaded/*; do
arch="$(basename "$dir")"
mv "$dir"/*.xz ./artifacts/"${arch}"
done
- name: Build fallback-paths.nix
run: |
nix build .#fallbackPathsNix --out-link fallback
cat fallback > ./artifacts/fallback-paths.nix
- uses: DeterminateSystems/push-artifact-ids@main
with:
s3_upload_role: ${{ secrets.AWS_S3_UPLOAD_ROLE_ARN }}
bucket: ${{ secrets.AWS_S3_UPLOAD_BUCKET_NAME }}
directory: ./artifacts
ids_project_name: determinate-nix
ids_binary_prefix: determinate-nix
skip_acl: true
allowed_branches: '["detsys-main"]'
publish:
needs:
- release
if: (!github.repository.fork && (github.ref == format('refs/heads/{0}', github.event.repository.default_branch) || startsWith(github.ref, 'refs/tags/')))
environment: ${{ github.event_name == 'release' && 'production' || '' }}
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: DeterminateSystems/determinate-nix-action@main
- uses: DeterminateSystems/flakehub-push@main
with:
rolling: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
visibility: "public"
tag: "${{ github.ref_name }}"

View file

@ -1 +1 @@
2.29.0 2.29.1

View file

@ -1 +1 @@
3.6.2 3.6.8

View file

@ -129,6 +129,10 @@
- [Contributing](development/contributing.md) - [Contributing](development/contributing.md)
- [Determinate Nix Release Notes](release-notes-determinate/index.md) - [Determinate Nix Release Notes](release-notes-determinate/index.md)
- [Changes between Nix and Determinate Nix](release-notes-determinate/changes.md)<!-- next --> - [Changes between Nix and Determinate Nix](release-notes-determinate/changes.md)<!-- next -->
- [Release 3.6.8 (2025-06-25)](release-notes-determinate/rl-3.6.8.md)
- [Release 3.6.7 (2025-06-24)](release-notes-determinate/rl-3.6.7.md)
- [Release 3.6.6 (2025-06-17)](release-notes-determinate/rl-3.6.6.md)
- [Release 3.6.5 (2025-06-16)](release-notes-determinate/rl-3.6.5.md)
- [Release 3.6.2 (2025-06-02)](release-notes-determinate/rl-3.6.2.md) - [Release 3.6.2 (2025-06-02)](release-notes-determinate/rl-3.6.2.md)
- [Release 3.6.1 (2025-05-24)](release-notes-determinate/rl-3.6.1.md) - [Release 3.6.1 (2025-05-24)](release-notes-determinate/rl-3.6.1.md)
- [Release 3.6.0 (2025-05-22)](release-notes-determinate/rl-3.6.0.md) - [Release 3.6.0 (2025-05-22)](release-notes-determinate/rl-3.6.0.md)

View file

@ -1,6 +1,6 @@
# Changes between Nix and Determinate Nix # Changes between Nix and Determinate Nix
This section lists the differences between upstream Nix 2.29 and Determinate Nix 3.6.2.<!-- differences --> This section lists the differences between upstream Nix 2.29 and Determinate Nix 3.6.8.<!-- differences -->
* In Determinate Nix, flakes are stable. You no longer need to enable the `flakes` experimental feature. * In Determinate Nix, flakes are stable. You no longer need to enable the `flakes` experimental feature.
@ -44,4 +44,45 @@ This section lists the differences between upstream Nix 2.29 and Determinate Nix
* nix profile: Replace ε and ∅ with descriptive English words by @grahamc in [DeterminateSystems/nix-src#81](https://github.com/DeterminateSystems/nix-src/pull/81) * nix profile: Replace ε and ∅ with descriptive English words by @grahamc in [DeterminateSystems/nix-src#81](https://github.com/DeterminateSystems/nix-src/pull/81)
* Call out that `--keep-failed` with remote builders will keep the failed build directory on that builder by @cole-h in [DeterminateSystems/nix-src#85](https://github.com/DeterminateSystems/nix-src/pull/85) * Call out that `--keep-failed` with remote builders will keep the failed build directory on that builder by @cole-h in [DeterminateSystems/nix-src#85](https://github.com/DeterminateSystems/nix-src/pull/85)
<!-- Determinate Nix version 3.6.3 revoked -->
<!-- Determinate Nix version 3.6.4 revoked -->
<!-- Determinate Nix version 3.6.5 -->
* When remote building with --keep-failed, only show "you can rerun" message if the derivation's platform is supported on this machine by @cole-h in [DeterminateSystems/nix-src#87](https://github.com/DeterminateSystems/nix-src/pull/87)
* Indicate that sandbox-paths specifies a missing file in the corresponding error message. by @cole-h in [DeterminateSystems/nix-src#88](https://github.com/DeterminateSystems/nix-src/pull/88)
* Use 'published' release type to avoid double uploads by @gustavderdrache in [DeterminateSystems/nix-src#90](https://github.com/DeterminateSystems/nix-src/pull/90)
* Render lazy tree paths in messages withouth the/nix/store/hash... prefix in substituted source trees by @edolstra in [DeterminateSystems/nix-src#91](https://github.com/DeterminateSystems/nix-src/pull/91)
* Use FlakeHub inputs by @lucperkins in [DeterminateSystems/nix-src#89](https://github.com/DeterminateSystems/nix-src/pull/89)
* Proactively cache more flake inputs and fetches by @edolstra in [DeterminateSystems/nix-src#93](https://github.com/DeterminateSystems/nix-src/pull/93)
* Fix: register extra builtins just once by @edolstra in [DeterminateSystems/nix-src#97](https://github.com/DeterminateSystems/nix-src/pull/97)
* Fix: Make the S3 test more robust by @gustavderdrache in [DeterminateSystems/nix-src#101](https://github.com/DeterminateSystems/nix-src/pull/101)
* Fix the link to `builders-use-substitutes` documentation for `builders` by @lucperkins in [DeterminateSystems/nix-src#102](https://github.com/DeterminateSystems/nix-src/pull/102)
* Improve error messages that use the hypothetical future tense of "will" by @lucperkins in [DeterminateSystems/nix-src#92](https://github.com/DeterminateSystems/nix-src/pull/92)
* Improve caching of inputs in dry-run mode by @edolstra in [DeterminateSystems/nix-src#98](https://github.com/DeterminateSystems/nix-src/pull/98)
<!-- Determinate Nix version 3.6.6 -->
<!-- Determinate Nix version 3.6.7 -->
<!-- Determinate Nix version 3.6.8 -->
* Fix fetchToStore() caching with --impure, improve testing by @edolstra in [DeterminateSystems/nix-src#117](https://github.com/DeterminateSystems/nix-src/pull/117)
* Add lazy-locks setting by @edolstra in [DeterminateSystems/nix-src#113](https://github.com/DeterminateSystems/nix-src/pull/113)
* Sync 2.29.1 by @edolstra in [DeterminateSystems/nix-src#124](https://github.com/DeterminateSystems/nix-src/pull/124)
* Release v3.6.7 by @github-actions in [DeterminateSystems/nix-src#126](https://github.com/DeterminateSystems/nix-src/pull/126)

View file

@ -0,0 +1,19 @@
# Release 3.6.5 (2025-06-12)
* Based on [upstream Nix 2.29.0](../release-notes/rl-2.29.md).
## What's Changed
* When remote building with --keep-failed, only show "you can rerun" message if the derivation's platform is supported on this machine by @cole-h in [DeterminateSystems/nix-src#87](https://github.com/DeterminateSystems/nix-src/pull/87)
* Indicate that sandbox-paths specifies a missing file in the corresponding error message. by @cole-h in [DeterminateSystems/nix-src#88](https://github.com/DeterminateSystems/nix-src/pull/88)
* Render lazy tree paths in messages withouth the/nix/store/hash... prefix in substituted source trees by @edolstra in [DeterminateSystems/nix-src#91](https://github.com/DeterminateSystems/nix-src/pull/91)
* Use FlakeHub inputs by @lucperkins in [DeterminateSystems/nix-src#89](https://github.com/DeterminateSystems/nix-src/pull/89)
* Proactively cache more flake inputs and fetches by @edolstra in [DeterminateSystems/nix-src#93](https://github.com/DeterminateSystems/nix-src/pull/93)
* Fix: register extra builtins just once by @edolstra in [DeterminateSystems/nix-src#97](https://github.com/DeterminateSystems/nix-src/pull/97)
* Fix the link to `builders-use-substitutes` documentation for `builders` by @lucperkins in [DeterminateSystems/nix-src#102](https://github.com/DeterminateSystems/nix-src/pull/102)
* Improve error messages that use the hypothetical future tense of "will" by @lucperkins in [DeterminateSystems/nix-src#92](https://github.com/DeterminateSystems/nix-src/pull/92)
* Make the `nix repl` test more stable by @edolstra in [DeterminateSystems/nix-src#103](https://github.com/DeterminateSystems/nix-src/pull/103)
* Run nixpkgsLibTests against lazy trees by @edolstra in [DeterminateSystems/nix-src#100](https://github.com/DeterminateSystems/nix-src/pull/100)
* Run the Nix test suite against lazy trees by @edolstra in [DeterminateSystems/nix-src#105](https://github.com/DeterminateSystems/nix-src/pull/105)
* Improve caching of inputs by @edolstra in [DeterminateSystems/nix-src#98](https://github.com/DeterminateSystems/nix-src/pull/98), [DeterminateSystems/nix-src#110](https://github.com/DeterminateSystems/nix-src/pull/110), and [DeterminateSystems/nix-src#115](https://github.com/DeterminateSystems/nix-src/pull/115)
**Full Changelog**: [v3.6.2...v3.6.5](https://github.com/DeterminateSystems/nix-src/compare/v3.6.2...v3.6.4)

View file

@ -0,0 +1,7 @@
# Release 3.6.6 (2025-06-17)
* Based on [upstream Nix 2.29.0](../release-notes/rl-2.29.md).
## What's Changed
* No-op release on the nix-src side, due to a regression on nix-darwin in determinate-nixd.

View file

@ -0,0 +1,17 @@
# Release 3.6.7 (2025-06-24)
* Based on [upstream Nix 2.29.1](../release-notes/rl-2.29.md).
## What's Changed
### Security contents
* Patched against GHSA-g948-229j-48j3
### Lazy trees:
* Lazy trees now produces `flake.lock` files with NAR hashes unless `lazy-locks` is set to `true` by @edolstra in [DeterminateSystems/nix-src#113](https://github.com/DeterminateSystems/nix-src/pull/113)
* Improved caching with lazy-trees when using --impure, with enhanced testing by @edolstra in [DeterminateSystems/nix-src#117](https://github.com/DeterminateSystems/nix-src/pull/117)
**Full Changelog**: [v3.6.6...v3.6.7](https://github.com/DeterminateSystems/nix-src/compare/v3.6.6...v3.6.7)

View file

@ -0,0 +1,12 @@
# Release 3.6.8 (2025-06-25)
* Based on [upstream Nix 2.29.1](../release-notes/rl-2.29.md).
## What's Changed
* Fix fetchToStore() caching with --impure, improve testing by @edolstra in [DeterminateSystems/nix-src#117](https://github.com/DeterminateSystems/nix-src/pull/117)
* Add lazy-locks setting by @edolstra in [DeterminateSystems/nix-src#113](https://github.com/DeterminateSystems/nix-src/pull/113)
* Sync 2.29.1 by @edolstra in [DeterminateSystems/nix-src#124](https://github.com/DeterminateSystems/nix-src/pull/124)
* Release v3.6.7 by @github-actions in [DeterminateSystems/nix-src#126](https://github.com/DeterminateSystems/nix-src/pull/126)
**Full Changelog**: [v3.6.6...v3.6.8](https://github.com/DeterminateSystems/nix-src/compare/v3.6.6...v3.6.8)

View file

@ -220,6 +220,11 @@
''; '';
repl-completion = nixpkgsFor.${system}.native.callPackage ./tests/repl-completion.nix { }; repl-completion = nixpkgsFor.${system}.native.callPackage ./tests/repl-completion.nix { };
lazyTrees = nixpkgsFor.${system}.native.nixComponents2.nix-functional-tests.override {
pname = "nix-lazy-trees-tests";
lazyTrees = true;
};
/** /**
Checks for our packaging expressions. Checks for our packaging expressions.
This shouldn't build anything significant; just check that things This shouldn't build anything significant; just check that things

View file

@ -262,6 +262,19 @@ struct EvalSettings : Config
R"( R"(
If set to true, flakes and trees fetched by [`builtins.fetchTree`](@docroot@/language/builtins.md#builtins-fetchTree) are only copied to the Nix store when they're used as a dependency of a derivation. This avoids copying (potentially large) source trees unnecessarily. If set to true, flakes and trees fetched by [`builtins.fetchTree`](@docroot@/language/builtins.md#builtins-fetchTree) are only copied to the Nix store when they're used as a dependency of a derivation. This avoids copying (potentially large) source trees unnecessarily.
)"}; )"};
// FIXME: this setting should really be in libflake, but it's
// currently needed in mountInput().
Setting<bool> lazyLocks{
this,
false,
"lazy-locks",
R"(
If enabled, Nix only includes NAR hashes in lock file entries if they're necessary to lock the input (i.e. when there is no other attribute that allows the content to be verified, like a Git revision).
This is not backward compatible with older versions of Nix.
If disabled, lock file entries always contain a NAR hash.
)"
};
}; };
/** /**

View file

@ -77,25 +77,30 @@ StorePath EvalState::mountInput(
allowPath(storePath); // FIXME: should just whitelist the entire virtual store allowPath(storePath); // FIXME: should just whitelist the entire virtual store
std::optional<Hash> _narHash;
auto getNarHash = [&]() {
if (!_narHash) {
if (store->isValidPath(storePath))
_narHash = store->queryPathInfo(storePath)->narHash;
else
_narHash = fetchToStore2(*store, accessor, FetchMode::DryRun, input.getName()).second;
}
return _narHash;
};
storeFS->mount(CanonPath(store->printStorePath(storePath)), accessor); storeFS->mount(CanonPath(store->printStorePath(storePath)), accessor);
if (requireLockable && (!settings.lazyTrees || !input.isLocked()) && !input.getNarHash()) { if (requireLockable && (!settings.lazyTrees || !settings.lazyLocks || !input.isLocked()) && !input.getNarHash())
// FIXME: use fetchToStore to make it cache this input.attrs.insert_or_assign("narHash", getNarHash()->to_string(HashFormat::SRI, true));
auto narHash = accessor->hashPath(CanonPath::root);
input.attrs.insert_or_assign("narHash", narHash.to_string(HashFormat::SRI, true));
}
// FIXME: what to do with the NAR hash in lazy mode? if (originalInput.getNarHash() && *getNarHash() != *originalInput.getNarHash())
if (!settings.lazyTrees && originalInput.getNarHash()) { throw Error(
auto expected = originalInput.computeStorePath(*store); (unsigned int) 102,
if (storePath != expected) "NAR hash mismatch in input '%s', expected '%s' but got '%s'",
throw Error( originalInput.to_string(),
(unsigned int) 102, getNarHash()->to_string(HashFormat::SRI, true),
"NAR hash mismatch in input '%s', expected '%s' but got '%s'", originalInput.getNarHash()->to_string(HashFormat::SRI, true));
originalInput.to_string(),
store->printStorePath(storePath),
store->printStorePath(expected));
}
return storePath; return storePath;
} }

View file

@ -3,19 +3,16 @@
namespace nix { namespace nix {
fetchers::Cache::Key makeFetchToStoreCacheKey( fetchers::Cache::Key makeSourcePathToHashCacheKey(
const std::string &name, const std::string & fingerprint,
const std::string &fingerprint,
ContentAddressMethod method, ContentAddressMethod method,
const std::string &path) const std::string & path)
{ {
return fetchers::Cache::Key{"fetchToStore", { return fetchers::Cache::Key{"sourcePathToHash", {
{"name", name},
{"fingerprint", fingerprint}, {"fingerprint", fingerprint},
{"method", std::string{method.render()}}, {"method", std::string{method.render()}},
{"path", path} {"path", path}
}}; }};
} }
StorePath fetchToStore( StorePath fetchToStore(
@ -27,39 +24,81 @@ StorePath fetchToStore(
PathFilter * filter, PathFilter * filter,
RepairFlag repair) RepairFlag repair)
{ {
// FIXME: add an optimisation for the case where the accessor is return fetchToStore2(store, path, mode, name, method, filter, repair).first;
// a `PosixSourceAccessor` pointing to a store path. }
std::pair<StorePath, Hash> fetchToStore2(
Store & store,
const SourcePath & path,
FetchMode mode,
std::string_view name,
ContentAddressMethod method,
PathFilter * filter,
RepairFlag repair)
{
std::optional<fetchers::Cache::Key> cacheKey; std::optional<fetchers::Cache::Key> cacheKey;
std::optional<std::string> fingerprint;
if (!filter && (fingerprint = path.accessor->getFingerprint(path.path))) { auto [subpath, fingerprint] =
cacheKey = makeFetchToStoreCacheKey(std::string{name}, *fingerprint, method, path.path.abs()); filter
if (auto res = fetchers::getCache()->lookupStorePath(*cacheKey, store, mode == FetchMode::DryRun)) { ? std::pair<CanonPath, std::optional<std::string>>{path.path, std::nullopt}
debug("store path cache hit for '%s'", path); : path.accessor->getFingerprint(path.path);
return res->storePath;
if (fingerprint) {
cacheKey = makeSourcePathToHashCacheKey(*fingerprint, method, subpath.abs());
if (auto res = fetchers::getCache()->lookup(*cacheKey)) {
auto hash = Hash::parseSRI(fetchers::getStrAttr(*res, "hash"));
auto storePath = store.makeFixedOutputPathFromCA(name,
ContentAddressWithReferences::fromParts(method, hash, {}));
if (mode == FetchMode::DryRun || store.isValidPath(storePath)) {
debug("source path '%s' cache hit in '%s' (hash '%s')", path, store.printStorePath(storePath), hash.to_string(HashFormat::SRI, true));
return {storePath, hash};
}
debug("source path '%s' not in store", path);
} }
} else } else {
debug("source path '%s' is uncacheable (%d, %d)", path, filter, (bool) fingerprint); static auto barf = getEnv("_NIX_TEST_BARF_ON_UNCACHEABLE").value_or("") == "1";
if (barf)
throw Error("source path '%s' is uncacheable (filter=%d)", path, (bool) filter);
// FIXME: could still provide in-memory caching keyed on `SourcePath`.
debug("source path '%s' is uncacheable", path);
}
Activity act(*logger, lvlChatty, actUnknown, Activity act(*logger, lvlChatty, actUnknown,
fmt(mode == FetchMode::DryRun ? "hashing '%s'" : "copying '%s' to the store", path)); fmt(mode == FetchMode::DryRun ? "hashing '%s'" : "copying '%s' to the store", path));
auto filter2 = filter ? *filter : defaultPathFilter; auto filter2 = filter ? *filter : defaultPathFilter;
auto storePath = auto [storePath, hash] =
mode == FetchMode::DryRun mode == FetchMode::DryRun
? store.computeStorePath( ? ({
name, path, method, HashAlgorithm::SHA256, {}, filter2).first auto [storePath, hash] = store.computeStorePath(
: store.addToStore( name, path, method, HashAlgorithm::SHA256, {}, filter2);
name, path, method, HashAlgorithm::SHA256, {}, filter2, repair); debug("hashed '%s' to '%s' (hash '%s')", path, store.printStorePath(storePath), hash.to_string(HashFormat::SRI, true));
std::make_pair(storePath, hash);
debug(mode == FetchMode::DryRun ? "hashed '%s'" : "copied '%s' to '%s'", path, store.printStorePath(storePath)); })
: ({
// FIXME: ideally addToStore() would return the hash
// right away (like computeStorePath()).
auto storePath = store.addToStore(
name, path, method, HashAlgorithm::SHA256, {}, filter2, repair);
auto info = store.queryPathInfo(storePath);
assert(info->references.empty());
auto hash =
method == ContentAddressMethod::Raw::NixArchive
? info->narHash
: ({
if (!info->ca || info->ca->method != method)
throw Error("path '%s' lacks a CA field", store.printStorePath(storePath));
info->ca->hash;
});
debug("copied '%s' to '%s' (hash '%s')", path, store.printStorePath(storePath), hash.to_string(HashFormat::SRI, true));
std::make_pair(storePath, hash);
});
if (cacheKey) if (cacheKey)
fetchers::getCache()->upsert(*cacheKey, store, {}, storePath); fetchers::getCache()->upsert(*cacheKey, {{"hash", hash.to_string(HashFormat::SRI, true)}});
return storePath; return {storePath, hash};
} }
} }

View file

@ -338,8 +338,7 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
auto accessor = make_ref<SubstitutedSourceAccessor>(makeStorePathAccessor(store, storePath)); auto accessor = make_ref<SubstitutedSourceAccessor>(makeStorePathAccessor(store, storePath));
if (auto fingerprint = getFingerprint(store)) accessor->fingerprint = getFingerprint(store);
accessor->setFingerprint(*fingerprint);
// FIXME: ideally we would use the `showPath()` of the // FIXME: ideally we would use the `showPath()` of the
// "real" accessor for this fetcher type. // "real" accessor for this fetcher type.
@ -353,10 +352,8 @@ std::pair<ref<SourceAccessor>, Input> Input::getAccessorUnchecked(ref<Store> sto
auto [accessor, result] = scheme->getAccessor(store, *this); auto [accessor, result] = scheme->getAccessor(store, *this);
assert(!accessor->getFingerprint(CanonPath::root)); if (!accessor->fingerprint)
accessor->fingerprint = result.getFingerprint(store);
if (auto fingerprint = getFingerprint(store))
accessor->setFingerprint(*fingerprint);
return {accessor, std::move(result)}; return {accessor, std::move(result)};
} }

View file

@ -58,16 +58,13 @@ std::string FilteringSourceAccessor::showPath(const CanonPath & path)
return displayPrefix + next->showPath(prefix / path) + displaySuffix; return displayPrefix + next->showPath(prefix / path) + displaySuffix;
} }
std::optional<std::string> FilteringSourceAccessor::getFingerprint(const CanonPath & path) std::pair<CanonPath, std::optional<std::string>> FilteringSourceAccessor::getFingerprint(const CanonPath & path)
{ {
if (fingerprint)
return {path, fingerprint};
return next->getFingerprint(prefix / path); return next->getFingerprint(prefix / path);
} }
void FilteringSourceAccessor::setFingerprint(std::string fingerprint)
{
next->setFingerprint(std::move(fingerprint));
}
void FilteringSourceAccessor::checkAccess(const CanonPath & path) void FilteringSourceAccessor::checkAccess(const CanonPath & path)
{ {
if (!isAllowed(path)) if (!isAllowed(path))

View file

@ -860,7 +860,7 @@ struct GitInputScheme : InputScheme
return makeFingerprint(*rev); return makeFingerprint(*rev);
else { else {
auto repoInfo = getRepoInfo(input); auto repoInfo = getRepoInfo(input);
if (auto repoPath = repoInfo.getPath(); repoPath && repoInfo.workdirInfo.headRev && repoInfo.workdirInfo.submodules.empty()) { if (auto repoPath = repoInfo.getPath(); repoPath && repoInfo.workdirInfo.submodules.empty()) {
/* Calculate a fingerprint that takes into account the /* Calculate a fingerprint that takes into account the
deleted and modified/added files. */ deleted and modified/added files. */
HashSink hashSink{HashAlgorithm::SHA512}; HashSink hashSink{HashAlgorithm::SHA512};
@ -873,7 +873,7 @@ struct GitInputScheme : InputScheme
writeString("deleted:", hashSink); writeString("deleted:", hashSink);
writeString(file.abs(), hashSink); writeString(file.abs(), hashSink);
} }
return makeFingerprint(*repoInfo.workdirInfo.headRev) return makeFingerprint(repoInfo.workdirInfo.headRev.value_or(nullRev))
+ ";d=" + hashSink.finish().first.to_string(HashFormat::Base16, false); + ";d=" + hashSink.finish().first.to_string(HashFormat::Base16, false);
} }
return std::nullopt; return std::nullopt;
@ -882,7 +882,8 @@ struct GitInputScheme : InputScheme
bool isLocked(const Input & input) const override bool isLocked(const Input & input) const override
{ {
return (bool) input.getRev(); auto rev = input.getRev();
return rev && rev != nullRev;
} }
}; };

View file

@ -23,7 +23,16 @@ StorePath fetchToStore(
PathFilter * filter = nullptr, PathFilter * filter = nullptr,
RepairFlag repair = NoRepair); RepairFlag repair = NoRepair);
fetchers::Cache::Key makeFetchToStoreCacheKey( std::pair<StorePath, Hash> fetchToStore2(
const std::string & name, const std::string & fingerprint, ContentAddressMethod method, const std::string & path); Store & store,
const SourcePath & path,
FetchMode mode,
std::string_view name = "source",
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive,
PathFilter * filter = nullptr,
RepairFlag repair = NoRepair);
fetchers::Cache::Key
makeSourcePathToHashCacheKey(const std::string & fingerprint, ContentAddressMethod method, const std::string & path);
} }

View file

@ -50,9 +50,7 @@ struct FilteringSourceAccessor : SourceAccessor
std::string showPath(const CanonPath & path) override; std::string showPath(const CanonPath & path) override;
std::optional<std::string> getFingerprint(const CanonPath & path) override; std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path) override;
void setFingerprint(std::string fingerprint) override;
/** /**
* Call `makeNotAllowedError` to throw a `RestrictedPathError` * Call `makeNotAllowedError` to throw a `RestrictedPathError`

View file

@ -144,37 +144,22 @@ struct PathInputScheme : InputScheme
storePath = store->addToStoreFromDump(*src, "source"); storePath = store->addToStoreFromDump(*src, "source");
} }
// To avoid copying the path again to the /nix/store, we need to add a cache entry. auto accessor = makeStorePathAccessor(store, *storePath);
ContentAddressMethod method = ContentAddressMethod::Raw::NixArchive;
auto fp = getFingerprint(store, input); // To prevent `fetchToStore()` copying the path again to Nix
if (fp) { // store, pre-create an entry in the fetcher cache.
auto cacheKey = makeFetchToStoreCacheKey(input.getName(), *fp, method, "/"); auto info = store->queryPathInfo(*storePath);
fetchers::getCache()->upsert(cacheKey, *store, {}, *storePath); accessor->fingerprint = fmt("path:%s", store->queryPathInfo(*storePath)->narHash.to_string(HashFormat::SRI, true));
} fetchers::getCache()->upsert(
makeSourcePathToHashCacheKey(*accessor->fingerprint, ContentAddressMethod::Raw::NixArchive, "/"),
{{"hash", info->narHash.to_string(HashFormat::SRI, true)}});
/* Trust the lastModified value supplied by the user, if /* Trust the lastModified value supplied by the user, if
any. It's not a "secure" attribute so we don't care. */ any. It's not a "secure" attribute so we don't care. */
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 {accessor, std::move(input)};
}
std::optional<std::string> getFingerprint(ref<Store> store, const Input & input) const override
{
if (isRelative(input))
return std::nullopt;
/* If this path is in the Nix store, use the hash of the
store object and the subpath. */
auto path = getAbsPath(input);
try {
auto [storePath, subPath] = store->toStorePath(path.string());
auto info = store->queryPathInfo(storePath);
return fmt("path:%s:%s", info->narHash.to_string(HashFormat::Base16, false), subPath);
} catch (Error &) {
return std::nullopt;
}
} }
}; };

View file

@ -59,6 +59,7 @@ test(
this_exe, this_exe,
env : { env : {
'_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data', '_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data',
'HOME': meson.current_build_dir() / 'test-home',
}, },
protocol : 'gtest', protocol : 'gtest',
) )

View file

@ -56,17 +56,13 @@ mkMesonExecutable (finalAttrs: {
{ {
meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages; meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages;
} }
( (''
lib.optionalString stdenv.hostPlatform.isWindows '' export _NIX_TEST_UNIT_DATA=${resolvePath ./data}
export HOME="$PWD/home-dir" export HOME="$TMPDIR/home"
mkdir -p "$HOME" mkdir -p "$HOME"
'' ${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
+ '' touch $out
export _NIX_TEST_UNIT_DATA=${resolvePath ./data} '');
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
touch $out
''
);
}; };
}; };

View file

@ -562,7 +562,7 @@ LockedFlake lockFlake(
/* Get the input flake, resolve 'path:./...' /* Get the input flake, resolve 'path:./...'
flakerefs relative to the parent flake. */ flakerefs relative to the parent flake. */
auto getInputFlake = [&](const FlakeRef & ref) auto getInputFlake = [&](const FlakeRef & ref, const fetchers::UseRegistries useRegistries)
{ {
if (auto resolvedPath = resolveRelativePath()) { if (auto resolvedPath = resolveRelativePath()) {
return readFlake(state, ref, ref, ref, *resolvedPath, inputAttrPath); return readFlake(state, ref, ref, ref, *resolvedPath, inputAttrPath);
@ -653,7 +653,7 @@ LockedFlake lockFlake(
} }
if (mustRefetch) { if (mustRefetch) {
auto inputFlake = getInputFlake(oldLock->lockedRef); auto inputFlake = getInputFlake(oldLock->lockedRef, useRegistriesInputs);
nodePaths.emplace(childNode, inputFlake.path.parent()); nodePaths.emplace(childNode, inputFlake.path.parent());
computeLocks(inputFlake.inputs, childNode, inputAttrPath, oldLock, followsPrefix, computeLocks(inputFlake.inputs, childNode, inputAttrPath, oldLock, followsPrefix,
inputFlake.path, false); inputFlake.path, false);
@ -678,7 +678,8 @@ LockedFlake lockFlake(
nuked the next time we update the lock nuked the next time we update the lock
file. That is, overrides are sticky unless you file. That is, overrides are sticky unless you
use --no-write-lock-file. */ use --no-write-lock-file. */
auto ref = (input2.ref && explicitCliOverrides.contains(inputAttrPath)) ? *input2.ref : *input.ref; auto inputIsOverride = explicitCliOverrides.contains(inputAttrPath);
auto ref = (input2.ref && inputIsOverride) ? *input2.ref : *input.ref;
/* Warn against the use of indirect flakerefs /* Warn against the use of indirect flakerefs
(but only at top-level since we don't want (but only at top-level since we don't want
@ -704,7 +705,7 @@ LockedFlake lockFlake(
}; };
if (input.isFlake) { if (input.isFlake) {
auto inputFlake = getInputFlake(*input.ref); auto inputFlake = getInputFlake(*input.ref, inputIsOverride ? fetchers::UseRegistries::All : useRegistriesInputs);
auto childNode = make_ref<LockedNode>( auto childNode = make_ref<LockedNode>(
inputFlake.lockedRef, inputFlake.lockedRef,

View file

@ -60,7 +60,7 @@ struct PluginSettings : Config
itself, they must be DSOs compatible with the instance of Nix itself, they must be DSOs compatible with the instance of Nix
running at the time (i.e. compiled against the same headers, not running at the time (i.e. compiled against the same headers, not
linked to any incompatible libraries). They should not be linked to linked to any incompatible libraries). They should not be linked to
any Nix libraries directly, as those are already at load any Nix libraries directly, as those are already available at load
time. time.
If an entry in the list is a directory, all files in the directory If an entry in the list is a directory, all files in the directory

View file

@ -100,6 +100,7 @@ test(
this_exe, this_exe,
env : { env : {
'_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data', '_NIX_TEST_UNIT_DATA': meson.current_source_dir() / 'data',
'HOME': meson.current_build_dir() / 'test-home',
}, },
protocol : 'gtest', protocol : 'gtest',
) )

View file

@ -28,10 +28,6 @@ TEST_F(nix_api_store_test, nix_store_get_uri)
TEST_F(nix_api_util_context, nix_store_get_storedir_default) TEST_F(nix_api_util_context, nix_store_get_storedir_default)
{ {
if (nix::getEnv("HOME").value_or("") == "/homeless-shelter") {
// skipping test in sandbox because nix_store_open tries to create /nix/var/nix/profiles
GTEST_SKIP();
}
nix_libstore_init(ctx); nix_libstore_init(ctx);
Store * store = nix_store_open(ctx, nullptr, nullptr); Store * store = nix_store_open(ctx, nullptr, nullptr);
assert_ctx_ok(); assert_ctx_ok();
@ -136,10 +132,6 @@ TEST_F(nix_api_store_test, nix_store_real_path)
TEST_F(nix_api_util_context, nix_store_real_path_relocated) TEST_F(nix_api_util_context, nix_store_real_path_relocated)
{ {
if (nix::getEnv("HOME").value_or("") == "/homeless-shelter") {
// Can't open default store from within sandbox
GTEST_SKIP();
}
auto tmp = nix::createTempDir(); auto tmp = nix::createTempDir();
std::string storeRoot = tmp + "/store"; std::string storeRoot = tmp + "/store";
std::string stateDir = tmp + "/state"; std::string stateDir = tmp + "/state";
@ -179,13 +171,7 @@ TEST_F(nix_api_util_context, nix_store_real_path_relocated)
TEST_F(nix_api_util_context, nix_store_real_path_binary_cache) TEST_F(nix_api_util_context, nix_store_real_path_binary_cache)
{ {
if (nix::getEnv("HOME").value_or("") == "/homeless-shelter") { Store * store = nix_store_open(ctx, nix::fmt("file://%s/binary-cache", nix::createTempDir()).c_str(), nullptr);
// TODO: override NIX_CACHE_HOME?
// skipping test in sandbox because narinfo cache can't be written
GTEST_SKIP();
}
Store * store = nix_store_open(ctx, "https://cache.nixos.org", nullptr);
assert_ctx_ok(); assert_ctx_ok();
ASSERT_NE(store, nullptr); ASSERT_NE(store, nullptr);

View file

@ -73,17 +73,13 @@ mkMesonExecutable (finalAttrs: {
{ {
meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages; meta.broken = !stdenv.hostPlatform.emulatorAvailable buildPackages;
} }
( (''
lib.optionalString stdenv.hostPlatform.isWindows '' export _NIX_TEST_UNIT_DATA=${data + "/src/libstore-tests/data"}
export HOME="$PWD/home-dir" export HOME="$TMPDIR/home"
mkdir -p "$HOME" mkdir -p "$HOME"
'' ${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
+ '' touch $out
export _NIX_TEST_UNIT_DATA=${data + "/src/libstore-tests/data"} '');
${stdenv.hostPlatform.emulator buildPackages} ${lib.getExe finalAttrs.finalPackage}
touch $out
''
);
}; };
}; };

View file

@ -46,7 +46,7 @@ struct FileTransferSettings : Config
)"}; )"};
Setting<unsigned int> tries{this, 5, "download-attempts", Setting<unsigned int> tries{this, 5, "download-attempts",
"The number of times Nix will attempt to download a file before giving up."}; "The number of times Nix attempts to download a file before giving up."};
Setting<size_t> downloadBufferSize{this, 64 * 1024 * 1024, "download-buffer-size", Setting<size_t> downloadBufferSize{this, 64 * 1024 * 1024, "download-buffer-size",
R"( R"(

View file

@ -247,7 +247,7 @@ LocalStore::LocalStore(ref<const Config> config)
else if (curSchema == 0) { /* new store */ else if (curSchema == 0) { /* new store */
curSchema = nixSchemaVersion; curSchema = nixSchemaVersion;
openDB(*state, true); openDB(*state, true);
writeFile(schemaPath, fmt("%1%", curSchema), 0666, true); writeFile(schemaPath, fmt("%1%", curSchema), 0666, FsSync::Yes);
} }
else if (curSchema < nixSchemaVersion) { else if (curSchema < nixSchemaVersion) {
@ -298,7 +298,7 @@ LocalStore::LocalStore(ref<const Config> config)
txn.commit(); txn.commit();
} }
writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, true); writeFile(schemaPath, fmt("%1%", nixSchemaVersion), 0666, FsSync::Yes);
lockFile(globalLock.get(), ltRead, true); lockFile(globalLock.get(), ltRead, true);
} }

View file

@ -129,6 +129,11 @@ private:
*/ */
Path topTmpDir; Path topTmpDir;
/**
* The file descriptor of the temporary directory.
*/
AutoCloseFD tmpDirFd;
/** /**
* The path of the temporary directory in the sandbox. * The path of the temporary directory in the sandbox.
*/ */
@ -325,9 +330,24 @@ private:
/** /**
* Make a file owned by the builder. * Make a file owned by the builder.
*
* SAFETY: this function is prone to TOCTOU as it receives a path and not a descriptor.
* It's only safe to call in a child of a directory only visible to the owner.
*/ */
void chownToBuilder(const Path & path); void chownToBuilder(const Path & path);
/**
* Make a file owned by the builder addressed by its file descriptor.
*/
void chownToBuilder(int fd, const Path & path);
/**
* Create a file in `tmpDir` owned by the builder.
*/
void writeBuilderFile(
const std::string & name,
std::string_view contents);
/** /**
* Run the builder's process. * Run the builder's process.
*/ */
@ -900,7 +920,14 @@ void DerivationBuilderImpl::startBuilder()
} else { } else {
tmpDir = topTmpDir; tmpDir = topTmpDir;
} }
chownToBuilder(tmpDir);
/* The TOCTOU between the previous mkdir call and this open call is unavoidable due to
POSIX semantics.*/
tmpDirFd = AutoCloseFD{open(tmpDir.c_str(), O_RDONLY | O_NOFOLLOW | O_DIRECTORY)};
if (!tmpDirFd)
throw SysError("failed to open the build temporary directory descriptor '%1%'", tmpDir);
chownToBuilder(tmpDirFd.get(), tmpDir);
for (auto & [outputName, status] : initialOutputs) { for (auto & [outputName, status] : initialOutputs) {
/* Set scratch path we'll actually use during the build. /* Set scratch path we'll actually use during the build.
@ -1485,9 +1512,7 @@ void DerivationBuilderImpl::initTmpDir()
} else { } else {
auto hash = hashString(HashAlgorithm::SHA256, i.first); auto hash = hashString(HashAlgorithm::SHA256, i.first);
std::string fn = ".attr-" + hash.to_string(HashFormat::Nix32, false); std::string fn = ".attr-" + hash.to_string(HashFormat::Nix32, false);
Path p = tmpDir + "/" + fn; writeBuilderFile(fn, rewriteStrings(i.second, inputRewrites));
writeFile(p, rewriteStrings(i.second, inputRewrites));
chownToBuilder(p);
env[i.first + "Path"] = tmpDirInSandbox + "/" + fn; env[i.first + "Path"] = tmpDirInSandbox + "/" + fn;
} }
} }
@ -1596,11 +1621,9 @@ void DerivationBuilderImpl::writeStructuredAttrs()
auto jsonSh = StructuredAttrs::writeShell(json); auto jsonSh = StructuredAttrs::writeShell(json);
writeFile(tmpDir + "/.attrs.sh", rewriteStrings(jsonSh, inputRewrites)); writeBuilderFile(".attrs.sh", rewriteStrings(jsonSh, inputRewrites));
chownToBuilder(tmpDir + "/.attrs.sh");
env["NIX_ATTRS_SH_FILE"] = tmpDirInSandbox + "/.attrs.sh"; env["NIX_ATTRS_SH_FILE"] = tmpDirInSandbox + "/.attrs.sh";
writeFile(tmpDir + "/.attrs.json", rewriteStrings(json.dump(), inputRewrites)); writeBuilderFile(".attrs.json", rewriteStrings(json.dump(), inputRewrites));
chownToBuilder(tmpDir + "/.attrs.json");
env["NIX_ATTRS_JSON_FILE"] = tmpDirInSandbox + "/.attrs.json"; env["NIX_ATTRS_JSON_FILE"] = tmpDirInSandbox + "/.attrs.json";
} }
} }
@ -1854,6 +1877,24 @@ void setupSeccomp()
#endif #endif
} }
void DerivationBuilderImpl::chownToBuilder(int fd, const Path & path)
{
if (!buildUser) return;
if (fchown(fd, buildUser->getUID(), buildUser->getGID()) == -1)
throw SysError("cannot change ownership of file '%1%'", path);
}
void DerivationBuilderImpl::writeBuilderFile(
const std::string & name,
std::string_view contents)
{
auto path = std::filesystem::path(tmpDir) / name;
AutoCloseFD fd{openat(tmpDirFd.get(), name.c_str(), O_WRONLY | O_TRUNC | O_CREAT | O_CLOEXEC | O_EXCL | O_NOFOLLOW, 0666)};
if (!fd)
throw SysError("creating file %s", path);
writeFile(fd, path, contents);
chownToBuilder(fd.get(), path);
}
void DerivationBuilderImpl::runChild() void DerivationBuilderImpl::runChild()
{ {
@ -3065,6 +3106,15 @@ void DerivationBuilderImpl::checkOutputs(const std::map<std::string, ValidPathIn
void DerivationBuilderImpl::deleteTmpDir(bool force) void DerivationBuilderImpl::deleteTmpDir(bool force)
{ {
if (topTmpDir != "") { if (topTmpDir != "") {
/* As an extra precaution, even in the event of `deletePath` failing to
* clean up, the `tmpDir` will be chowned as if we were to move
* it inside the Nix store.
*
* This hardens against an attack which smuggles a file descriptor
* to make use of the temporary directory.
*/
chmod(topTmpDir.c_str(), 0000);
/* Don't keep temporary directories for builtins because they /* Don't keep temporary directories for builtins because they
might have privileged stuff (like a copy of netrc). */ might have privileged stuff (like a copy of netrc). */
if (settings.keepFailed && !force && !drv.isBuiltin()) { if (settings.keepFailed && !force && !drv.isBuiltin()) {

View file

@ -93,7 +93,7 @@ void restorePath(
{ {
switch (method) { switch (method) {
case FileSerialisationMethod::Flat: case FileSerialisationMethod::Flat:
writeFile(path, source, 0666, startFsync); writeFile(path, source, 0666, startFsync ? FsSync::Yes : FsSync::No);
break; break;
case FileSerialisationMethod::NixArchive: case FileSerialisationMethod::NixArchive:
restorePath(path, source, startFsync); restorePath(path, source, startFsync);

View file

@ -303,7 +303,7 @@ void readFile(const Path & path, Sink & sink, bool memory_map)
} }
void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync) void writeFile(const Path & path, std::string_view s, mode_t mode, FsSync sync)
{ {
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT
// TODO // TODO
@ -313,22 +313,29 @@ void writeFile(const Path & path, std::string_view s, mode_t mode, bool sync)
, mode)); , mode));
if (!fd) if (!fd)
throw SysError("opening file '%1%'", path); throw SysError("opening file '%1%'", path);
try {
writeFull(fd.get(), s); writeFile(fd, path, s, mode, sync);
} catch (Error & e) {
e.addTrace({}, "writing file '%1%'", path); /* Close explicitly to propagate the exceptions. */
throw;
}
if (sync)
fd.fsync();
// Explicitly close to make sure exceptions are propagated.
fd.close(); fd.close();
if (sync)
syncParent(path);
} }
void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode, FsSync sync)
{
assert(fd);
try {
writeFull(fd.get(), s);
void writeFile(const Path & path, Source & source, mode_t mode, bool sync) if (sync == FsSync::Yes)
fd.fsync();
} catch (Error & e) {
e.addTrace({}, "writing file '%1%'", origPath);
throw;
}
}
void writeFile(const Path & path, Source & source, mode_t mode, FsSync sync)
{ {
AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT AutoCloseFD fd = toDescriptor(open(path.c_str(), O_WRONLY | O_TRUNC | O_CREAT
// TODO // TODO
@ -352,11 +359,11 @@ void writeFile(const Path & path, Source & source, mode_t mode, bool sync)
e.addTrace({}, "writing file '%1%'", path); e.addTrace({}, "writing file '%1%'", path);
throw; throw;
} }
if (sync) if (sync == FsSync::Yes)
fd.fsync(); fd.fsync();
// Explicitly close to make sure exceptions are propagated. // Explicitly close to make sure exceptions are propagated.
fd.close(); fd.close();
if (sync) if (sync == FsSync::Yes)
syncParent(path); syncParent(path);
} }
@ -419,7 +426,8 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path,
#ifndef _WIN32 #ifndef _WIN32
checkInterrupt(); checkInterrupt();
std::string name(baseNameOf(path.native())); std::string name(path.filename());
assert(name != "." && name != ".." && !name.empty());
struct stat st; struct stat st;
if (fstatat(parentfd, name.c_str(), &st, if (fstatat(parentfd, name.c_str(), &st,
@ -460,7 +468,7 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path,
throw SysError("chmod %1%", path); throw SysError("chmod %1%", path);
} }
int fd = openat(parentfd, path.c_str(), O_RDONLY); int fd = openat(parentfd, name.c_str(), O_RDONLY | O_DIRECTORY | O_NOFOLLOW);
if (fd == -1) if (fd == -1)
throw SysError("opening directory %1%", path); throw SysError("opening directory %1%", path);
AutoCloseDir dir(fdopendir(fd)); AutoCloseDir dir(fdopendir(fd));
@ -472,7 +480,7 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path,
checkInterrupt(); checkInterrupt();
std::string childName = dirent->d_name; std::string childName = dirent->d_name;
if (childName == "." || childName == "..") continue; if (childName == "." || childName == "..") continue;
_deletePath(dirfd(dir.get()), path + "/" + childName, bytesFreed, ex); _deletePath(dirfd(dir.get()), path / childName, bytesFreed, ex);
} }
if (errno) throw SysError("reading directory %1%", path); if (errno) throw SysError("reading directory %1%", path);
} }
@ -497,14 +505,13 @@ static void _deletePath(Descriptor parentfd, const std::filesystem::path & path,
static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed) static void _deletePath(const std::filesystem::path & path, uint64_t & bytesFreed)
{ {
Path dir = dirOf(path.string()); assert(path.is_absolute());
if (dir == "") assert(path.parent_path() != path);
dir = "/";
AutoCloseFD dirfd = toDescriptor(open(dir.c_str(), O_RDONLY)); AutoCloseFD dirfd = toDescriptor(open(path.parent_path().string().c_str(), O_RDONLY));
if (!dirfd) { if (!dirfd) {
if (errno == ENOENT) return; if (errno == ENOENT) return;
throw SysError("opening directory '%1%'", path); throw SysError("opening directory %s", path.parent_path());
} }
std::exception_ptr ex; std::exception_ptr ex;

View file

@ -175,21 +175,27 @@ std::string readFile(const Path & path);
std::string readFile(const std::filesystem::path & path); std::string readFile(const std::filesystem::path & path);
void readFile(const Path & path, Sink & sink, bool memory_map = true); void readFile(const Path & path, Sink & sink, bool memory_map = true);
enum struct FsSync { Yes, No };
/** /**
* Write a string to a file. * Write a string to a file.
*/ */
void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, bool sync = false); void writeFile(const Path & path, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
static inline void writeFile(const std::filesystem::path & path, std::string_view s, mode_t mode = 0666, bool sync = false)
static inline void writeFile(const std::filesystem::path & path, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No)
{ {
return writeFile(path.string(), s, mode, sync); return writeFile(path.string(), s, mode, sync);
} }
void writeFile(const Path & path, Source & source, mode_t mode = 0666, bool sync = false); void writeFile(const Path & path, Source & source, mode_t mode = 0666, FsSync sync = FsSync::No);
static inline void writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 0666, bool sync = false)
static inline void writeFile(const std::filesystem::path & path, Source & source, mode_t mode = 0666, FsSync sync = FsSync::No)
{ {
return writeFile(path.string(), source, mode, sync); return writeFile(path.string(), source, mode, sync);
} }
void writeFile(AutoCloseFD & fd, const Path & origPath, std::string_view s, mode_t mode = 0666, FsSync sync = FsSync::No);
/** /**
* Flush a path's parent directory to disk. * Flush a path's parent directory to disk.
*/ */

View file

@ -52,16 +52,6 @@ struct ForwardingSourceAccessor : SourceAccessor
{ {
return next->getPhysicalPath(path); return next->getPhysicalPath(path);
} }
std::optional<std::string> getFingerprint(const CanonPath & path) override
{
return next->getFingerprint(path);
}
void setFingerprint(std::string fingerprint) override
{
next->setFingerprint(std::move(fingerprint));
}
}; };
} }

View file

@ -177,28 +177,32 @@ struct SourceAccessor : std::enable_shared_from_this<SourceAccessor>
SymlinkResolution mode = SymlinkResolution::Full); SymlinkResolution mode = SymlinkResolution::Full);
/** /**
* Return a string that uniquely represents the contents of this * A string that uniquely represents the contents of this
* accessor. This is used for caching lookups (see * accessor. This is used for caching lookups (see `fetchToStore()`).
* `fetchToStore()`).
*
* Fingerprints are generally for the entire accessor, but this
* method takes a `path` argument to support accessors like
* `MountedSourceAccessor` that combine multiple underlying
* accessors. A fingerprint should only be returned if it uniquely
* represents everything under `path`.
*/ */
virtual std::optional<std::string> getFingerprint(const CanonPath & path) std::optional<std::string> fingerprint;
{
return _fingerprint;
}
virtual void setFingerprint(std::string fingerprint) /**
* Return the fingerprint for `path`. This is usually the
* fingerprint of the current accessor, but for composite
* accessors (like `MountedSourceAccessor`), we want to return the
* fingerprint of the "inner" accessor if the current one lacks a
* fingerprint.
*
* So this method is intended to return the most-outer accessor
* that has a fingerprint for `path`. It also returns the path that `path`
* corresponds to in that accessor.
*
* For example: in a `MountedSourceAccessor` that has
* `/nix/store/foo` mounted,
* `getFingerprint("/nix/store/foo/bar")` will return the path
* `/bar` and the fingerprint of the `/nix/store/foo` accessor.
*/
virtual std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path)
{ {
_fingerprint = std::move(fingerprint); return {path, fingerprint};
} }
std::optional<std::string> _fingerprint;
/** /**
* Return the maximum last-modified time of the files in this * Return the maximum last-modified time of the files in this
* tree, if available. * tree, if available.

View file

@ -187,6 +187,10 @@ void MemorySink::createSymlink(const CanonPath & path, const std::string & targe
ref<SourceAccessor> makeEmptySourceAccessor() ref<SourceAccessor> makeEmptySourceAccessor()
{ {
static auto empty = make_ref<MemorySourceAccessor>().cast<SourceAccessor>(); static auto empty = make_ref<MemorySourceAccessor>().cast<SourceAccessor>();
/* Don't forget to clear the display prefix, as the default constructed
SourceAccessor has the «unknown» prefix. Since this accessor is supposed
to mimic an empty root directory the prefix needs to be empty. */
empty->setPathDisplay("");
return empty; return empty;
} }

View file

@ -91,12 +91,11 @@ struct MountedSourceAccessorImpl : MountedSourceAccessor
return nullptr; return nullptr;
} }
std::optional<std::string> getFingerprint(const CanonPath & path) override std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path) override
{ {
if (fingerprint)
return {path, fingerprint};
auto [accessor, subpath] = resolve(path); auto [accessor, subpath] = resolve(path);
// FIXME: check that there are no mounts underneath the mount
// point of `accessor`, since that would invalidate the
// fingerprint. (However we don't have such at the moment.)
return accessor->getFingerprint(subpath); return accessor->getFingerprint(subpath);
} }
}; };

View file

@ -72,6 +72,18 @@ struct UnionSourceAccessor : SourceAccessor
} }
return std::nullopt; return std::nullopt;
} }
std::pair<CanonPath, std::optional<std::string>> getFingerprint(const CanonPath & path) override
{
if (fingerprint)
return {path, fingerprint};
for (auto & accessor : accessors) {
auto [subpath, fingerprint] = accessor->getFingerprint(path);
if (fingerprint)
return {subpath, fingerprint};
}
return {path, std::nullopt};
}
}; };
ref<SourceAccessor> makeUnionSourceAccessor(std::vector<ref<SourceAccessor>> && accessors) ref<SourceAccessor> makeUnionSourceAccessor(std::vector<ref<SourceAccessor>> && accessors)

View file

@ -54,6 +54,7 @@ flake-registry = $TEST_ROOT/registry.json
show-trace = true show-trace = true
include nix.conf.extra include nix.conf.extra
trusted-users = $(whoami) trusted-users = $(whoami)
${_NIX_TEST_EXTRA_CONFIG:-}
EOF EOF
cat > "$NIX_CONF_DIR"/nix.conf.extra <<EOF cat > "$NIX_CONF_DIR"/nix.conf.extra <<EOF

View file

@ -2,6 +2,8 @@
source ../common.sh source ../common.sh
export _NIX_TEST_BARF_ON_UNCACHEABLE=1
# shellcheck disable=SC2034 # this variable is used by tests that source this file # shellcheck disable=SC2034 # this variable is used by tests that source this file
registry=$TEST_ROOT/registry.json registry=$TEST_ROOT/registry.json

View file

@ -62,8 +62,8 @@ flakeref=git+file://$rootRepo\?submodules=1\&dir=submodule
# Check that dirtying a submodule makes the entire thing dirty. # Check that dirtying a submodule makes the entire thing dirty.
[[ $(nix flake metadata --json "$flakeref" | jq -r .locked.rev) != null ]] [[ $(nix flake metadata --json "$flakeref" | jq -r .locked.rev) != null ]]
echo '"foo"' > "$rootRepo"/submodule/sub.nix echo '"foo"' > "$rootRepo"/submodule/sub.nix
[[ $(nix eval --json "$flakeref#sub" ) = '"foo"' ]] [[ $(_NIX_TEST_BARF_ON_UNCACHEABLE='' nix eval --json "$flakeref#sub" ) = '"foo"' ]]
[[ $(nix flake metadata --json "$flakeref" | jq -r .locked.rev) = null ]] [[ $(_NIX_TEST_BARF_ON_UNCACHEABLE='' nix flake metadata --json "$flakeref" | jq -r .locked.rev) = null ]]
# Test that `nix flake metadata` parses `submodule` correctly. # Test that `nix flake metadata` parses `submodule` correctly.
cat > "$rootRepo"/flake.nix <<EOF cat > "$rootRepo"/flake.nix <<EOF
@ -75,7 +75,7 @@ EOF
git -C "$rootRepo" add flake.nix git -C "$rootRepo" add flake.nix
git -C "$rootRepo" commit -m "Add flake.nix" git -C "$rootRepo" commit -m "Add flake.nix"
storePath=$(nix flake prefetch --json "$rootRepo?submodules=1" | jq -r .storePath) storePath=$(_NIX_TEST_BARF_ON_UNCACHEABLE='' nix flake prefetch --json "$rootRepo?submodules=1" | jq -r .storePath)
[[ -e "$storePath/submodule" ]] [[ -e "$storePath/submodule" ]]
# Test the use of inputs.self. # Test the use of inputs.self.

View file

@ -69,7 +69,9 @@ nix flake metadata "$flake1Dir" | grepQuiet 'URL:.*flake1.*'
# Test 'nix flake metadata --json'. # Test 'nix flake metadata --json'.
json=$(nix flake metadata flake1 --json | jq .) json=$(nix flake metadata flake1 --json | jq .)
[[ $(echo "$json" | jq -r .description) = 'Bla bla' ]] [[ $(echo "$json" | jq -r .description) = 'Bla bla' ]]
[[ -d $(echo "$json" | jq -r .path) ]] if [[ $(nix config show lazy-trees) = false ]]; then
[[ -d $(echo "$json" | jq -r .path) ]]
fi
[[ $(echo "$json" | jq -r .lastModified) = $(git -C "$flake1Dir" log -n1 --format=%ct) ]] [[ $(echo "$json" | jq -r .lastModified) = $(git -C "$flake1Dir" log -n1 --format=%ct) ]]
hash1=$(echo "$json" | jq -r .revision) hash1=$(echo "$json" | jq -r .revision)
[[ -n $(echo "$json" | jq -r .fingerprint) ]] [[ -n $(echo "$json" | jq -r .fingerprint) ]]
@ -110,6 +112,11 @@ nix build -o "$TEST_ROOT/result" "git+file://$flake1Dir#default"
nix build -o "$TEST_ROOT/result" "$flake1Dir?ref=HEAD#default" nix build -o "$TEST_ROOT/result" "$flake1Dir?ref=HEAD#default"
nix build -o "$TEST_ROOT/result" "git+file://$flake1Dir?ref=HEAD#default" nix build -o "$TEST_ROOT/result" "git+file://$flake1Dir?ref=HEAD#default"
# Check that the fetcher cache works.
if [[ $(nix config show lazy-trees) = false ]]; then
nix build -o "$TEST_ROOT/result" "git+file://$flake1Dir?ref=HEAD#default" -vvvvv 2>&1 | grepQuiet "source path.*cache hit"
fi
# Check that relative paths are allowed for git flakes. # Check that relative paths are allowed for git flakes.
# This may change in the future once git submodule support is refined. # This may change in the future once git submodule support is refined.
# See: https://discourse.nixos.org/t/57783 and #9708. # See: https://discourse.nixos.org/t/57783 and #9708.
@ -161,7 +168,12 @@ expect 1 nix build -o "$TEST_ROOT/result" "$flake2Dir#bar" --no-update-lock-file
nix build -o "$TEST_ROOT/result" "$flake2Dir#bar" --commit-lock-file nix build -o "$TEST_ROOT/result" "$flake2Dir#bar" --commit-lock-file
[[ -e "$flake2Dir/flake.lock" ]] [[ -e "$flake2Dir/flake.lock" ]]
[[ -z $(git -C "$flake2Dir" diff main || echo failed) ]] [[ -z $(git -C "$flake2Dir" diff main || echo failed) ]]
[[ $(jq --indent 0 . < "$flake2Dir/flake.lock") =~ ^'{"nodes":{"flake1":{"locked":{"lastModified":'.*',"narHash":"sha256-'.*'","ref":"refs/heads/master","rev":"'.*'","revCount":2,"type":"git","url":"file:///'.*'"},"original":{"id":"flake1","type":"indirect"}},"root":{"inputs":{"flake1":"flake1"}}},"root":"root","version":7}'$ ]] [[ $(jq --indent 0 --compact-output . < "$flake2Dir/flake.lock") =~ ^'{"nodes":{"flake1":{"locked":{"lastModified":'[0-9]*',"narHash":"sha256-'.*'","ref":"refs/heads/master","rev":"'.*'","revCount":2,"type":"git","url":"file:///'.*'"},"original":{"id":"flake1","type":"indirect"}},"root":{"inputs":{"flake1":"flake1"}}},"root":"root","version":7}'$ ]]
if [[ $(nix config show lazy-trees) = true ]]; then
# Test that `lazy-locks` causes NAR hashes to be omitted from the lock file.
nix flake update --flake "$flake2Dir" --commit-lock-file --lazy-locks
[[ $(jq --indent 0 --compact-output . < "$flake2Dir/flake.lock") =~ ^'{"nodes":{"flake1":{"locked":{"lastModified":'[0-9]*',"ref":"refs/heads/master","rev":"'.*'","revCount":2,"type":"git","url":"file:///'.*'"},"original":{"id":"flake1","type":"indirect"}},"root":{"inputs":{"flake1":"flake1"}}},"root":"root","version":7}'$ ]]
fi
# Rerunning the build should not change the lockfile. # Rerunning the build should not change the lockfile.
nix build -o "$TEST_ROOT/result" "$flake2Dir#bar" nix build -o "$TEST_ROOT/result" "$flake2Dir#bar"

View file

@ -27,9 +27,9 @@ nix build -o "$TEST_ROOT/result" "hg+file://$flake2Dir"
(! nix flake metadata --json "hg+file://$flake2Dir" | jq -e -r .revision) (! nix flake metadata --json "hg+file://$flake2Dir" | jq -e -r .revision)
nix eval "hg+file://$flake2Dir"#expr _NIX_TEST_BARF_ON_UNCACHEABLE='' nix eval "hg+file://$flake2Dir"#expr
nix eval "hg+file://$flake2Dir"#expr _NIX_TEST_BARF_ON_UNCACHEABLE='' nix eval "hg+file://$flake2Dir"#expr
(! nix eval "hg+file://$flake2Dir"#expr --no-allow-dirty) (! nix eval "hg+file://$flake2Dir"#expr --no-allow-dirty)

View file

@ -72,7 +72,7 @@ nix build -o "$TEST_ROOT/result" "$flake3Dir#sth" --commit-lock-file
nix registry add --registry "$registry" flake3 "git+file://$flake3Dir" nix registry add --registry "$registry" flake3 "git+file://$flake3Dir"
nix build -o "$TEST_ROOT/result" flake3#fnord _NIX_TEST_BARF_ON_UNCACHEABLE='' nix build -o "$TEST_ROOT/result" flake3#fnord
[[ $(cat "$TEST_ROOT/result") = FNORD ]] [[ $(cat "$TEST_ROOT/result") = FNORD ]]
# Check whether flake input fetching is lazy: flake3#sth does not # Check whether flake input fetching is lazy: flake3#sth does not
@ -82,11 +82,11 @@ clearStore
mv "$flake2Dir" "$flake2Dir.tmp" mv "$flake2Dir" "$flake2Dir.tmp"
mv "$nonFlakeDir" "$nonFlakeDir.tmp" mv "$nonFlakeDir" "$nonFlakeDir.tmp"
nix build -o "$TEST_ROOT/result" flake3#sth nix build -o "$TEST_ROOT/result" flake3#sth
(! nix build -o "$TEST_ROOT/result" flake3#xyzzy) (! _NIX_TEST_BARF_ON_UNCACHEABLE='' nix build -o "$TEST_ROOT/result" flake3#xyzzy)
(! nix build -o "$TEST_ROOT/result" flake3#fnord) (! _NIX_TEST_BARF_ON_UNCACHEABLE='' nix build -o "$TEST_ROOT/result" flake3#fnord)
mv "$flake2Dir.tmp" "$flake2Dir" mv "$flake2Dir.tmp" "$flake2Dir"
mv "$nonFlakeDir.tmp" "$nonFlakeDir" mv "$nonFlakeDir.tmp" "$nonFlakeDir"
nix build -o "$TEST_ROOT/result" flake3#xyzzy flake3#fnord _NIX_TEST_BARF_ON_UNCACHEABLE='' nix build -o "$TEST_ROOT/result" flake3#xyzzy flake3#fnord
# Make branch "removeXyzzy" where flake3 doesn't have xyzzy anymore # Make branch "removeXyzzy" where flake3 doesn't have xyzzy anymore
git -C "$flake3Dir" checkout -b removeXyzzy git -C "$flake3Dir" checkout -b removeXyzzy

View file

@ -4,6 +4,8 @@ source ./common.sh
requireGit requireGit
unset _NIX_TEST_BARF_ON_UNCACHEABLE
# Test a "vendored" subflake dependency. This is a relative path flake # Test a "vendored" subflake dependency. This is a relative path flake
# which doesn't reference the root flake and has its own lock file. # which doesn't reference the root flake and has its own lock file.
# #

View file

@ -69,7 +69,7 @@ git -C "$rootFlake" add flake.nix sub2/flake.nix
git -C "$rootFlake" add sub2/flake.lock git -C "$rootFlake" add sub2/flake.lock
[[ $(nix eval "$subflake2#y") = 15 ]] [[ $(nix eval "$subflake2#y") = 15 ]]
[[ $(jq --indent 0 . < "$subflake2/flake.lock") =~ ^'{"nodes":{"root":{"inputs":{"root":"root_2","sub1":"sub1"}},"root_2":{"inputs":{"sub0":"sub0"},"locked":{"path":"..","type":"path"},"original":{"path":"..","type":"path"},"parent":[]},"root_3":{"inputs":{"sub0":"sub0_2"},"locked":{"path":"../","type":"path"},"original":{"path":"../","type":"path"},"parent":["sub1"]},"sub0":{"locked":{"path":"sub0","type":"path"},"original":{"path":"sub0","type":"path"},"parent":["root"]},"sub0_2":{"locked":{"path":"sub0","type":"path"},"original":{"path":"sub0","type":"path"},"parent":["sub1","root"]},"sub1":{"inputs":{"root":"root_3"},"locked":{"path":"../sub1","type":"path"},"original":{"path":"../sub1","type":"path"},"parent":[]}},"root":"root","version":7}'$ ]] [[ $(jq --indent 0 --compact-output . < "$subflake2/flake.lock") =~ ^'{"nodes":{"root":{"inputs":{"root":"root_2","sub1":"sub1"}},"root_2":{"inputs":{"sub0":"sub0"},"locked":{"path":"..","type":"path"},"original":{"path":"..","type":"path"},"parent":[]},"root_3":{"inputs":{"sub0":"sub0_2"},"locked":{"path":"../","type":"path"},"original":{"path":"../","type":"path"},"parent":["sub1"]},"sub0":{"locked":{"path":"sub0","type":"path"},"original":{"path":"sub0","type":"path"},"parent":["root"]},"sub0_2":{"locked":{"path":"sub0","type":"path"},"original":{"path":"sub0","type":"path"},"parent":["sub1","root"]},"sub1":{"inputs":{"root":"root_3"},"locked":{"path":"../sub1","type":"path"},"original":{"path":"../sub1","type":"path"},"parent":[]}},"root":"root","version":7}'$ ]]
# Make sure there are no content locks for relative path flakes. # Make sure there are no content locks for relative path flakes.
(! grep "$TEST_ROOT" "$subflake2/flake.lock") (! grep "$TEST_ROOT" "$subflake2/flake.lock")

View file

@ -26,6 +26,9 @@
# For running the functional tests against a different pre-built Nix. # For running the functional tests against a different pre-built Nix.
test-daemon ? null, test-daemon ? null,
# Whether to run tests with lazy trees enabled.
lazyTrees ? false,
}: }:
let let
@ -95,6 +98,8 @@ mkMesonDerivation (
mkdir $out mkdir $out
''; '';
_NIX_TEST_EXTRA_CONFIG = lib.optionalString lazyTrees "lazy-trees = true";
meta = { meta = {
platforms = lib.platforms.unix; platforms = lib.platforms.unix;
}; };

View file

@ -34,3 +34,15 @@ rm -rf $TEST_ROOT/eval-out
(! nix eval --store dummy:// --write-to $TEST_ROOT/eval-out --expr '{ "." = "bla"; }') (! nix eval --store dummy:// --write-to $TEST_ROOT/eval-out --expr '{ "." = "bla"; }')
(! nix eval --expr '~/foo') (! nix eval --expr '~/foo')
expectStderr 0 nix eval --expr "/some/absolute/path" \
| grepQuiet "/some/absolute/path"
expectStderr 0 nix eval --expr "/some/absolute/path" --impure \
| grepQuiet "/some/absolute/path"
expectStderr 0 nix eval --expr "some/relative/path" \
| grepQuiet "$PWD/some/relative/path"
expectStderr 0 nix eval --expr "some/relative/path" --impure \
| grepQuiet "$PWD/some/relative/path"