Compare commits

...
Author SHA1 Message Date
Eelco Dolstra ffc6252e36 Expose builtins.fetchFinalTree to the user
This may be more efficient than fetchTree since it allows substitution
from binary caches if `narHash` is specified in the input
attributes. This is what call-flake.nix already used internally.
2025-11-24 19:56:05 +01:00
Eelco Dolstra 0f1b0bb439 Fix test 2025-11-24 17:43:51 +01:00
Eelco Dolstra 8cd0b35985 Add test 2025-11-24 17:43:38 +01:00
Eelco Dolstra 5b7badd008 Use non-shallow cache repo if it contains the requested commit
This fixes the issue where updating a Git input does a non-shallow
fetch, and then a subsequent eval does a shallow refetch because
the revCount is already known. Now the subsequent eval will use the
repo used in the first fetch.
2025-11-24 17:42:44 +01:00
Eelco Dolstra 50b013f612 Remove fetchTree 'shallow' hack
builtins.fetchTree was setting `shallow = true` when fetching from git.
That's bad because it makes it behave inconsistently from non-fetchTree
fetches, e.g. when updating an input.

Instead, the Git fetcher now will do a shallow fetch automatically if
`revCount` is already set (e.g. when fetching a lock).

Fixes https://github.com/NixOS/nix/issues/14588.
2025-11-24 17:42:36 +01:00
Eelco Dolstra 4ecc09c43f Make content-encoding test more reliable 2025-11-24 17:42:15 +01:00
Eelco Dolstra 8f32f28ebd Git fetcher: Don't compute lastModified if it's already specified
Same as revCount.
2025-11-24 17:42:15 +01:00
Eelco Dolstra 87baf29d6a Git fetcher: Don't compute revCount if it's already specified
We don't care if the user (or more likely the lock file) specifies
an incorrect value for revCount, since it doesn't matter for
security (unlikely content hashes like narHash).
2025-11-24 17:42:15 +01:00
John Ericson 487c6b6c46 Merge pull request #14630 from NixOS/prefetch-fixes
nix/prefetch: Be honest about when path name is derived from URL
2025-11-23 22:24:17 +00:00
Sergei Zimmerman 28fac9fe4d nix/prefetch: Be honest about when path name is derived from URL
Only add the message to trace when name is really derived from URL.
2025-11-24 00:25:48 +03:00
Sergei Zimmerman 2594e417b5 Merge pull request #14627 from jonhermansen/libstore-curl-version-maximum
libstore: fix curl version check to allow 8.17.0
2025-11-23 09:57:09 +00:00
Jon Hermansen 76ed967f79 libstore: fix curl version check to allow 8.17.0
The single-string syntax '>=8.16.0 <8.17.0' only applied the lower
bound, causing curl 8.17.0 to be incorrectly rejected. Split into two
separate version_compare() calls for compatibility with Meson 1.1,
since multi-argument syntax requires Meson 1.8+.
2025-11-23 12:13:05 +03:00
John Ericson 327e8babf7 Merge pull request #14584 from Radvendii/allocbytes-stringdata
libexpr: use allocBytes() to allocate StringData
2025-11-23 00:38:50 +00:00
John Ericson d5d4bafc2a Merge pull request #14620 from NixOS/revert-shared-tarball-cache
libfetchers: Don't have a single shared tarball cache
2025-11-23 00:33:51 +00:00
John Ericson bd11043c67 Merge pull request #14623 from Radvendii/exprcall-alloc-shvach
libexpr: plug ExprCall memory leak
2025-11-23 00:08:10 +00:00
Taeer Bar-Yam dbfe6318b3 libexpr: move ExprCall storage to the arena 2025-11-23 00:06:10 +01:00
Taeer Bar-Yam 484f40fc64 libexpr: make ExprCall::args an std::optional 2025-11-23 00:06:10 +01:00
Taeer Bar-Yam 43fc6c314d libexpr: ExprCall use std::pmr::vector 2025-11-23 00:06:10 +01:00
Sergei Zimmerman 2bbec7d573 Merge pull request #14622 from roberth/meson-commandlet-deps
src/nix: Make meson compile <cmdlet> valid
2025-11-22 19:55:02 +00:00
Sergei Zimmerman 385d7e77bd libfetchers: Don't have a single shared tarball cache
This partially reverts commit bc6b9ce.

This transformation is unsound and thread unsafe. Internal libgit2
structures must *never* be shared between threads. This causes
internal odb corruption with e.g.:

nix flake prefetch-inputs:

error:
       … while fetching the input 'github:nixos/nixpkgs/89c2b2330e733d6cdb5eae7b899326930c2c0648?narHash=sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw%3D'

       error: adding a file to a tree builder: failed to insert entry: invalid object specified - upload-image.sh
error:
       … while fetching the input 'github:NixOS/nixpkgs/a8d610af3f1a5fb71e23e08434d8d61a466fc942?narHash=sha256-v5afmLjn/uyD9EQuPBn7nZuaZVV9r%2BJerayK/4wvdWA%3D'

       error: adding a file to a tree builder: failed to insert entry: invalid object specified - outline.nix
double free or corruption (!prev)

Thread 21 "nix" received signal SIGABRT, Aborted.
2025-11-22 22:48:40 +03:00
Robert Hensing 67f6a24171 src/nix: Make meson compile <cmdlet> valid
Without this dependency, e.g. `meson compile nix-instantiate`
would produce a broken symlink, or the `nix` it points to may be
stale.
With the dependency in place, `meson compile nix-instantiate`
produces a reliable outcome.
2025-11-22 20:19:34 +01:00
Sergei Zimmerman 8cdeab8f2e Merge pull request #14613 from roberth/deepSeq-stack-overflow
`deepSeq`, json: handle stack overflow, report list index
2025-11-22 17:49:32 +00:00
Sergei Zimmerman ed176cb42e Merge pull request #14618 from jonhermansen/freebsd-path-null-terminator
fix(FreeBSD): remove null terminator from executable path
2025-11-22 11:51:01 +00:00
Jon Hermansen 3ff8d0ece4 fix(FreeBSD): remove null terminator from executable path
On FreeBSD, sysctl(KERN_PROC_PATHNAME) returns a null-terminated
string with pathLen including the terminator. This causes Nix to
fail during manual generation with:

  error:
         … while calling the 'concatStringsSep' builtin
           at /nix/var/nix/builds/nix-63232-402489527/source/doc/manual/generate-settings.nix:99:1:
             98| in
             99| concatStrings (attrValues (mapAttrs (showSetting prefix) settingsInfo))
               | ^
            100|

         error: input string '/nix/store/gq89cj02b5zs67cbd85vzg5cgsgnd8mj-nix-2.31.2/bin/nix␀'
                cannot be represented as Nix string because it contains null bytes

The issue occurs because generate-settings.nix reads the nix binary
path from JSON and evaluates it as a Nix string, which cannot contain
null bytes. Normal C++ string operations don't trigger this since they
handle null-terminated strings correctly.

Strip the null terminator on FreeBSD to match other platforms (Linux
uses /proc/self/exe, macOS uses _NSGetExecutablePath).

Credit: @wahjava (FreeBSD ports and Nixpkgs contributor)
2025-11-22 03:59:29 -05:00
John Ericson c9fe290b30 Merge pull request #14616 from vinayakankugoyal/patch-1
Clarify build options in debugging documentation
2025-11-22 06:28:56 +00:00
Vinayak Goyal 48c800f7ef Clarify build options in debugging documentation
Updated documentation to clarify that building without optimization can lead to faster builds.
2025-11-22 01:00:35 -05:00
John Ericson 79dcc094b0 Merge pull request #14614 from NixOS/libcurl-pause
libstore/filetransfer: Pause transfers instead of stalling the download thread
2025-11-22 05:41:18 +00:00
Sergei Zimmerman be28ad92fd rl-next: Add docs for libcurl pausing 2025-11-22 04:25:59 +03:00
Sergei Zimmerman a2d6a69d45 libstore: Reduce the default download-buffer-size down to 1 MiB
Since the root cause (the lack of backpressure control) has
been fixed in the previous commit we can revert the change from
8ffea0a018 and make the default size much
smaller.
2025-11-22 04:23:25 +03:00
Sergei Zimmermanandeldritch horrors 4307420c44 libstore/filetransfer: Pause transfers instead of stalling the download thread
Instead of naively stalling the download thread we can instead stop the transfer.
This allows the other multiplexed connections to continue downloading (and unpacking),
if the result of the download gets piped into a GitFileSystemObjectSink.

Prior art in lix project:

- https://git.lix.systems/lix-project/lix/commit/4ae6fb5a8f0d456b8d2ba2aaca3712b4e49057fc
- https://git.lix.systems/lix-project/lix/commit/12156d3beb8a16c0e2e8cf7180e1fbf27280a669

This patch is very different from the lix one, since we are using a decompression sink
in the middle of the pipeline but the co-authored-by is there since I was motivated to
implement this by looking at the lix side of things.

Co-authored-by: eldritch horrors <pennae@lix.systems>
2025-11-22 04:23:24 +03:00
Sergei Zimmerman ec0b270c6c libstore/filetransfer: Return an opaque handle from enqueueFileTransfer
This is necessary to make pausing/unpausing possible in a follow-up commit.
2025-11-22 03:33:13 +03:00
Sergei Zimmerman 3f8474a62f libstore/filetransfer: Use ref instead of std::shared_ptr
Those can never be nullptr, so we should use the type system
to ensure this invariant.
2025-11-22 03:33:12 +03:00
Robert Hensing c7e1c612eb libexpr: fix stack overflow in toJSON on deeply nested structures
Similar to the deepSeq fix, toJSON on deeply nested structures caused
an uncontrolled OS-level stack overflow.

Fix by adding call depth tracking to printValueAsJSON.
2025-11-22 00:17:26 +01:00
Robert Hensing a812b6c6e6 libexpr: add list index to deepSeq error traces
When deepSeq encounters an error while evaluating a list element, the
error trace now includes the list index, making it easier to locate
the problematic element.
2025-11-21 23:51:07 +01:00
Robert Hensing 59a566db13 libexpr: fix stack overflow in deepSeq on deeply nested structures
builtins.deepSeq on deeply nested structures (e.g., a linked list with
100,000 elements) caused an uncontrolled OS-level stack overflow with
no Nix stack trace.

Fix by adding call depth tracking to forceValueDeep, integrating with
Nix's existing max-call-depth mechanism. Now produces a controlled
"stack overflow; max-call-depth exceeded" error with a proper stack
trace.

Closes: https://github.com/NixOS/nix/issues/7816
2025-11-21 23:50:47 +01:00
John Ericson eb654acdd1 Merge pull request #14610 from NixOS/git-accessor-options
Introduce GitAccessorOptions
2025-11-21 22:13:52 +00:00
Taeer Bar-Yam 7cd3252946 libexpr: use allocBytes() to allocate StringData 2025-11-21 21:26:23 +01:00
Taeer Bar-Yam 9b9446e860 c api: shovel EvalMemory * into nix_value
this is a painful change. we should really add EvalState or EvalMemory
as an argument to various functions as we need it, but because we want
to preserve the stablity API, we hack it in as a field of nix_value.
2025-11-21 21:26:23 +01:00
Eelco Dolstra 6c4d2a7d11 Introduce GitAccessorOptions 2025-11-21 20:29:47 +01:00
John Ericson 152e7e48c1 Merge pull request #14607 from NixOS/open-directory-cloexec
libutil/unix: Add O_CLOEXEC to openDirectory
2025-11-21 01:23:57 +00:00
Sergei Zimmerman ea4854fda1 libutil/unix: Add O_CLOEXEC to openDirectory
As a precaution. This function might get used for some long persisted
file descriptor and we need good defaults.
2025-11-21 02:43:26 +03:00
John Ericson d3ff01cb2e Merge pull request #14606 from NixOS/fix-copy-recursive
libutil: Fix copyRecursive and use for nix flake clone
2025-11-20 22:28:45 +00:00
John Ericson a835d6ad2a Merge pull request #14319 from obsidiansystems/json-schema-fso
`nlohmann::json` instance and JSON Schema for `MemorySourceAccessor`
2025-11-20 21:52:57 +00:00
John Ericson ec3c93f17f Merge pull request #14603 from NixOS/safe-cast
Turn one unsafe C cast into a safe `static_cast`
2025-11-20 21:26:00 +00:00
Sergei Zimmerman 6d0f4fa666 libutil: Fix copyRecursive and use for nix flake clone
The use of sourceToSink is an unnecessary serialization bottleneck.
While we are at it, fix the copyRecursive implementation to actually copy
the whole directory. It wasn't used for anything prior, but now it has a use
and accompanying tests for flake clone.
2025-11-21 00:21:23 +03:00
John Ericson b2ead92791 Turn one unsafe C cast into a safe static_cast 2025-11-20 15:58:31 -05:00
John Ericson 50407ab63e Merge pull request #14598 from NixOS/nar-listing-dedup
Deduplicate `listNar` and `MemorySourceAccessor::File`
2025-11-20 20:54:48 +00:00
John Ericson 7357a654de nlohmann::json instance and JSON Schema for MemorySourceAccessor
Also do a better JSON and testing for deep and shallow NAR listings.

As documented, this for file system objects themselves, since
`MemorySourceAccessor` is an implementation detail.
2025-11-20 15:19:24 -05:00
John Ericson c4906741a1 Deduplicate listNar and MemorySourceAccessor::File
`listNar` did the not-so-pretty thing of going straight to JSON. Now it
uses `MemorySourceAccessor::File`, or rather variations of it, to go to
a C++ data type first, and only JSON second.

To accomplish this we add some type parameters to the `File` data type.
Actually, we need to do two rounds of this, because shallow NAR
listings. There is `FileT` and `DirectoryT` accordingly.
2025-11-20 14:57:47 -05:00
John Ericson ac36d74b66 listNar should just take the source accessor by simple reference
A shared pointer is not needed.
2025-11-20 14:44:41 -05:00
John Ericson d17bfe3866 Move nar-accessor.{cc,hh} to libutil
File-system-object-layer functionality doesn't depend on store-layer
concets, and therefore doesn't need to live inside there.
2025-11-20 14:44:41 -05:00
John Ericson 437b9b9879 Rename MemorySourceAccessor::File::Directory::{contents -> entries}
This matches the "NAR Listing" JSON format, and also helps distinguish
from regular file contents.

Why we want to match that will become clear in the next comments, when
we will in fact use (variations of) this data type for NAR listings.
2025-11-20 14:44:41 -05:00
92 changed files with 1513 additions and 448 deletions

No files matched your search

+1
View File
@@ -37,6 +37,7 @@ mkMesonDerivation (finalAttrs: {
(fileset.unions [ (fileset.unions [
../../.version ../../.version
# For example JSON # For example JSON
../../src/libutil-tests/data/memory-source-accessor
../../src/libutil-tests/data/hash ../../src/libutil-tests/data/hash
../../src/libstore-tests/data/content-address ../../src/libstore-tests/data/content-address
../../src/libstore-tests/data/store-path ../../src/libstore-tests/data/store-path
+12
View File
@@ -0,0 +1,12 @@
---
synopsis: Fix "download buffer is full; consider increasing the 'download-buffer-size' setting" warning
prs: [14614]
issues: [11728]
---
The underlying issue that led to [#11728](https://github.com/NixOS/nix/issues/11728) has been resolved by utilizing
[libcurl write pausing functionality](https://curl.se/libcurl/c/curl_easy_pause.html) to control backpressure when unpacking to slow destinations like the git-backed tarball cache. The default value of `download-buffer-size` is now 1 MiB and it's no longer recommended to increase it, since the root cause has been fixed.
This is expected to improve download performance on fast connections, since previously a single slow download consumer would stall the thread and prevent any other transfers from progressing.
Many thanks go out to the [Lix project](https://lix.systems/) for the [implementation](https://git.lix.systems/lix-project/lix/commit/4ae6fb5a8f0d456b8d2ba2aaca3712b4e49057fc) that served as inspiration for this change and for triaging libcurl [issues with pausing](https://github.com/curl/curl/issues/19334).
+1
View File
@@ -121,6 +121,7 @@
- [Architecture and Design](architecture/architecture.md) - [Architecture and Design](architecture/architecture.md)
- [Formats and Protocols](protocols/index.md) - [Formats and Protocols](protocols/index.md)
- [JSON Formats](protocols/json/index.md) - [JSON Formats](protocols/json/index.md)
- [File System Object](protocols/json/file-system-object.md)
- [Hash](protocols/json/hash.md) - [Hash](protocols/json/hash.md)
- [Content Address](protocols/json/content-address.md) - [Content Address](protocols/json/content-address.md)
- [Store Path](protocols/json/store-path.md) - [Store Path](protocols/json/store-path.md)
+1 -1
View File
@@ -15,7 +15,7 @@ In the development shell, set the `mesonBuildType` environment variable to `debu
Then, proceed to build Nix as described in [Building Nix](./building.md). Then, proceed to build Nix as described in [Building Nix](./building.md).
This will build Nix with debug symbols, which are essential for effective debugging. This will build Nix with debug symbols, which are essential for effective debugging.
It is also possible to build without debugging for faster build: It is also possible to build without optimization for faster build:
```console ```console
[nix-shell]$ NIX_HARDENING_ENABLE=$(printLines $NIX_HARDENING_ENABLE | grep -v fortify) [nix-shell]$ NIX_HARDENING_ENABLE=$(printLines $NIX_HARDENING_ENABLE | grep -v fortify)
@@ -0,0 +1,21 @@
{{#include file-system-object-v1-fixed.md}}
## Examples
### Simple
```json
{{#include schema/file-system-object-v1/simple.json}}
```
### Complex
```json
{{#include schema/file-system-object-v1/complex.json}}
```
<!-- need to convert YAML to JSON first
## Raw Schema
[JSON Schema for File System Object v1](schema/file-system-object-v1.json)
-->
@@ -11,6 +11,7 @@ s/\\`/`/g
# #
# As we have more such relative links, more replacements of this nature # As we have more such relative links, more replacements of this nature
# should appear below. # should appear below.
s^#/\$defs/\(regular\|symlink\|directory\)^In this schema^g
s^\(./hash-v1.yaml\)\?#/$defs/algorithm^[JSON format for `Hash`](./hash.html#algorithm)^g s^\(./hash-v1.yaml\)\?#/$defs/algorithm^[JSON format for `Hash`](./hash.html#algorithm)^g
s^\(./hash-v1.yaml\)^[JSON format for `Hash`](./hash.html)^g s^\(./hash-v1.yaml\)^[JSON format for `Hash`](./hash.html)^g
s^\(./content-address-v1.yaml\)\?#/$defs/method^[JSON format for `ContentAddress`](./content-address.html#method)^g s^\(./content-address-v1.yaml\)\?#/$defs/method^[JSON format for `ContentAddress`](./content-address.html#method)^g
@@ -9,6 +9,7 @@ json_schema_for_humans = find_program('generate-schema-doc', required : false)
json_schema_config = files('json-schema-for-humans-config.yaml') json_schema_config = files('json-schema-for-humans-config.yaml')
schemas = [ schemas = [
'file-system-object-v1',
'hash-v1', 'hash-v1',
'content-address-v1', 'content-address-v1',
'store-path-v1', 'store-path-v1',
@@ -0,0 +1 @@
../../../../../../src/libutil-tests/data/memory-source-accessor
@@ -0,0 +1,71 @@
"$schema": http://json-schema.org/draft-04/schema#
"$id": https://nix.dev/manual/nix/latest/protocols/json/schema/file-system-object-v1.json
title: File System Object
description: |
This schema describes the JSON representation of Nix's [File System Object](@docroot@/store/file-system-object.md).
The schema is recursive because file system objects contain other file system objects.
type: object
required: ["type"]
properties:
type:
type: string
enum: ["regular", "symlink", "directory"]
# Enforce conditional structure based on `type`
anyOf:
- $ref: "#/$defs/regular"
required: ["type", "contents"]
- $ref: "#/$defs/directory"
required: ["type", "entries"]
- $ref: "#/$defs/symlink"
required: ["type", "target"]
"$defs":
regular:
title: Regular File
description: |
See [Regular File](@docroot@/store/file-system-object.md#regular) in the manual for details.
required: ["contents"]
properties:
type:
const: "regular"
contents:
type: string
description: File contents
executable:
type: boolean
description: Whether the file is executable.
default: false
additionalProperties: false
directory:
title: Directory
description: |
See [Directory](@docroot@/store/file-system-object.md#directory) in the manual for details.
required: ["entries"]
properties:
type:
const: "directory"
entries:
type: object
description: |
Map of names to nested file system objects (for type=directory)
additionalProperties:
$ref: "#"
additionalProperties: false
symlink:
title: Symbolic Link
description: |
See [Symbolic Link](@docroot@/store/file-system-object.md#symlink) in the manual for details.
required: ["target"]
properties:
type:
const: "symlink"
target:
type: string
description: Target path of the symlink.
additionalProperties: false
@@ -3,19 +3,23 @@
Nix uses a simplified model of the file system, which consists of file system objects. Nix uses a simplified model of the file system, which consists of file system objects.
Every file system object is one of the following: Every file system object is one of the following:
- File - [**Regular File**]{#regular}
- A possibly empty sequence of bytes for contents - A possibly empty sequence of bytes for contents
- A single boolean representing the [executable](https://en.m.wikipedia.org/wiki/File-system_permissions#Permissions) permission - A single boolean representing the [executable](https://en.m.wikipedia.org/wiki/File-system_permissions#Permissions) permission
- Directory - [**Directory**]{#directory}
Mapping of names to child file system objects Mapping of names to child file system objects
- [Symbolic link](https://en.m.wikipedia.org/wiki/Symbolic_link) - [**Symbolic link**]{#symlink}
An arbitrary string. An arbitrary string, known as the *target* of the symlink.
Nix does not assign any semantics to symbolic links.
In general, Nix does not assign any semantics to symbolic links.
Certain operations however, may make additional assumptions and attempt to use the target to find another file system object.
> See [the Wikpedia article on symbolic links](https://en.m.wikipedia.org/wiki/Symbolic_link) for background information if you are unfamiliar with this Unix concept.
File system objects and their children form a tree. File system objects and their children form a tree.
A bare file or symlink can be a root file system object. A bare file or symlink can be a root file system object.
+1
View File
@@ -0,0 +1 @@
../../src/libutil-tests/data/memory-source-accessor
+8
View File
@@ -20,6 +20,14 @@ schema_dir = meson.current_source_dir() / 'schema'
# Get all example files # Get all example files
schemas = [ schemas = [
{
'stem' : 'file-system-object',
'schema' : schema_dir / 'file-system-object-v1.yaml',
'files' : [
'simple.json',
'complex.json',
],
},
{ {
'stem' : 'hash', 'stem' : 'hash',
'schema' : schema_dir / 'hash-v1.yaml', 'schema' : schema_dir / 'hash-v1.yaml',
+1
View File
@@ -20,6 +20,7 @@ mkMesonDerivation (finalAttrs: {
fileset = lib.fileset.unions [ fileset = lib.fileset.unions [
../../.version ../../.version
../../doc/manual/source/protocols/json/schema ../../doc/manual/source/protocols/json/schema
../../src/libutil-tests/data/memory-source-accessor
../../src/libutil-tests/data/hash ../../src/libutil-tests/data/hash
../../src/libstore-tests/data/content-address ../../src/libstore-tests/data/content-address
../../src/libstore-tests/data/store-path ../../src/libstore-tests/data/store-path
+3 -3
View File
@@ -168,9 +168,9 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
? state.rootPath(absPath(getCommandBaseDir())) ? state.rootPath(absPath(getCommandBaseDir()))
: state.rootPath("."))); : state.rootPath(".")));
}, },
[&](const AutoArgString & arg) { v->mkString(arg.s); }, [&](const AutoArgString & arg) { v->mkString(arg.s, state.mem); },
[&](const AutoArgFile & arg) { v->mkString(readFile(arg.path.string())); }, [&](const AutoArgFile & arg) { v->mkString(readFile(arg.path.string()), state.mem); },
[&](const AutoArgStdin & arg) { v->mkString(readFile(STDIN_FILENO)); }}, [&](const AutoArgStdin & arg) { v->mkString(readFile(STDIN_FILENO), state.mem); }},
arg); arg);
res.insert(state.symbols.create(name), v); res.insert(state.symbols.create(name), v);
} }
+14 -8
View File
@@ -69,8 +69,8 @@ nix_err nix_expr_eval_from_string(
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
try { try {
nix::Expr * parsedExpr = state->state.parseExprFromString(expr, state->state.rootPath(nix::CanonPath(path))); nix::Expr * parsedExpr = state->state.parseExprFromString(expr, state->state.rootPath(nix::CanonPath(path)));
state->state.eval(parsedExpr, value->value); state->state.eval(parsedExpr, *value->value);
state->state.forceValue(value->value, nix::noPos); state->state.forceValue(*value->value, nix::noPos);
} }
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }
@@ -80,8 +80,8 @@ nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, n
if (context) if (context)
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
try { try {
state->state.callFunction(fn->value, arg->value, value->value, nix::noPos); state->state.callFunction(*fn->value, *arg->value, *value->value, nix::noPos);
state->state.forceValue(value->value, nix::noPos); state->state.forceValue(*value->value, nix::noPos);
} }
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }
@@ -91,9 +91,15 @@ nix_err nix_value_call_multi(
{ {
if (context) if (context)
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
std::vector<nix::Value *> internal_args;
internal_args.reserve(nargs);
for (size_t i = 0; i < nargs; i++)
internal_args.push_back(args[i]->value);
try { try {
state->state.callFunction(fn->value, {(nix::Value **) args, nargs}, value->value, nix::noPos); state->state.callFunction(*fn->value, {internal_args.data(), nargs}, *value->value, nix::noPos);
state->state.forceValue(value->value, nix::noPos); state->state.forceValue(*value->value, nix::noPos);
} }
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }
@@ -103,7 +109,7 @@ nix_err nix_value_force(nix_c_context * context, EvalState * state, nix_value *
if (context) if (context)
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
try { try {
state->state.forceValue(value->value, nix::noPos); state->state.forceValue(*value->value, nix::noPos);
} }
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }
@@ -113,7 +119,7 @@ nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_val
if (context) if (context)
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
try { try {
state->state.forceValueDeep(value->value); state->state.forceValueDeep(*value->value);
} }
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }
+7 -1
View File
@@ -39,7 +39,13 @@ struct ListBuilder
struct nix_value struct nix_value
{ {
nix::Value value; nix::Value * value;
/**
* As we move to a managed heap, we need EvalMemory in more places. Ideally, we would take in EvalState or
* EvalMemory as an argument when we need it, but we don't want to make changes to the stable C api, so we stuff it
* into the nix_value that will get passed in to the relevant functions.
*/
nix::EvalMemory * mem;
}; };
struct nix_string_return struct nix_string_return
+37 -25
View File
@@ -20,7 +20,7 @@ static const nix::Value & check_value_not_null(const nix_value * value)
if (!value) { if (!value) {
throw std::runtime_error("nix_value is null"); throw std::runtime_error("nix_value is null");
} }
return *((const nix::Value *) value); return *value->value;
} }
static nix::Value & check_value_not_null(nix_value * value) static nix::Value & check_value_not_null(nix_value * value)
@@ -28,7 +28,7 @@ static nix::Value & check_value_not_null(nix_value * value)
if (!value) { if (!value) {
throw std::runtime_error("nix_value is null"); throw std::runtime_error("nix_value is null");
} }
return value->value; return *value->value;
} }
static const nix::Value & check_value_in(const nix_value * value) static const nix::Value & check_value_in(const nix_value * value)
@@ -58,9 +58,14 @@ static nix::Value & check_value_out(nix_value * value)
return v; return v;
} }
static inline nix_value * as_nix_value_ptr(nix::Value * v) static inline nix_value * new_nix_value(nix::Value * v, nix::EvalMemory & mem)
{ {
return reinterpret_cast<nix_value *>(v); nix_value * ret = new (mem.allocBytes(sizeof(nix_value))) nix_value{
.value = v,
.mem = &mem,
};
nix_gc_incref(nullptr, ret);
return ret;
} }
/** /**
@@ -69,7 +74,13 @@ static inline nix_value * as_nix_value_ptr(nix::Value * v)
* Deals with errors and converts arguments from C++ into C types. * Deals with errors and converts arguments from C++ into C types.
*/ */
static void nix_c_primop_wrapper( static void nix_c_primop_wrapper(
PrimOpFun f, void * userdata, nix::EvalState & state, const nix::PosIdx pos, nix::Value ** args, nix::Value & v) PrimOpFun f,
void * userdata,
int arity,
nix::EvalState & state,
const nix::PosIdx pos,
nix::Value ** args,
nix::Value & v)
{ {
nix_c_context ctx; nix_c_context ctx;
@@ -85,8 +96,15 @@ static void nix_c_primop_wrapper(
// ok because we don't see a need for this yet (e.g. inspecting thunks, // ok because we don't see a need for this yet (e.g. inspecting thunks,
// or maybe something to make blackholes work better; we don't know). // or maybe something to make blackholes work better; we don't know).
nix::Value vTmp; nix::Value vTmp;
nix_value * vTmpPtr = new_nix_value(&vTmp, state.mem);
f(userdata, &ctx, (EvalState *) &state, (nix_value **) args, (nix_value *) &vTmp); std::vector<nix_value *> external_args;
external_args.reserve(arity);
for (int i = 0; i < arity; i++) {
nix_value * external_arg = new_nix_value(args[i], state.mem);
external_args.push_back(external_arg);
}
f(userdata, &ctx, (EvalState *) &state, external_args.data(), vTmpPtr);
if (ctx.last_err_code != NIX_OK) { if (ctx.last_err_code != NIX_OK) {
/* TODO: Throw different errors depending on the error code */ /* TODO: Throw different errors depending on the error code */
@@ -135,7 +153,7 @@ PrimOp * nix_alloc_primop(
.args = {}, .args = {},
.arity = (size_t) arity, .arity = (size_t) arity,
.doc = doc, .doc = doc,
.fun = std::bind(nix_c_primop_wrapper, fun, user_data, _1, _2, _3, _4)}; .fun = std::bind(nix_c_primop_wrapper, fun, user_data, arity, _1, _2, _3, _4)};
if (args) if (args)
for (size_t i = 0; args[i]; i++) for (size_t i = 0; args[i]; i++)
p->args.emplace_back(*args); p->args.emplace_back(*args);
@@ -160,8 +178,7 @@ nix_value * nix_alloc_value(nix_c_context * context, EvalState * state)
if (context) if (context)
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
try { try {
nix_value * res = as_nix_value_ptr(state->state.allocValue()); nix_value * res = new_nix_value(state->state.allocValue(), state->state.mem);
nix_gc_incref(nullptr, res);
return res; return res;
} }
NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_NULL
@@ -331,10 +348,10 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
return nullptr; return nullptr;
} }
auto * p = v.listView()[ix]; auto * p = v.listView()[ix];
nix_gc_incref(nullptr, p); if (p == nullptr)
if (p != nullptr) return nullptr;
state->state.forceValue(*p, nix::noPos); state->state.forceValue(*p, nix::noPos);
return as_nix_value_ptr(p); return new_nix_value(p, state->state.mem);
} }
NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_NULL
} }
@@ -352,9 +369,8 @@ nix_get_list_byidx_lazy(nix_c_context * context, const nix_value * value, EvalSt
return nullptr; return nullptr;
} }
auto * p = v.listView()[ix]; auto * p = v.listView()[ix];
nix_gc_incref(nullptr, p);
// Note: intentionally NOT calling forceValue() to keep the element lazy // Note: intentionally NOT calling forceValue() to keep the element lazy
return as_nix_value_ptr(p); return new_nix_value(p, state->state.mem);
} }
NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_NULL
} }
@@ -369,9 +385,8 @@ nix_value * nix_get_attr_byname(nix_c_context * context, const nix_value * value
nix::Symbol s = state->state.symbols.create(name); nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs()->get(s); auto attr = v.attrs()->get(s);
if (attr) { if (attr) {
nix_gc_incref(nullptr, attr->value);
state->state.forceValue(*attr->value, nix::noPos); state->state.forceValue(*attr->value, nix::noPos);
return as_nix_value_ptr(attr->value); return new_nix_value(attr->value, state->state.mem);
} }
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute"); nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
return nullptr; return nullptr;
@@ -390,9 +405,8 @@ nix_get_attr_byname_lazy(nix_c_context * context, const nix_value * value, EvalS
nix::Symbol s = state->state.symbols.create(name); nix::Symbol s = state->state.symbols.create(name);
auto attr = v.attrs()->get(s); auto attr = v.attrs()->get(s);
if (attr) { if (attr) {
nix_gc_incref(nullptr, attr->value);
// Note: intentionally NOT calling forceValue() to keep the attribute lazy // Note: intentionally NOT calling forceValue() to keep the attribute lazy
return as_nix_value_ptr(attr->value); return new_nix_value(attr->value, state->state.mem);
} }
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute"); nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
return nullptr; return nullptr;
@@ -440,9 +454,8 @@ nix_get_attr_byidx(nix_c_context * context, nix_value * value, EvalState * state
} }
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);
state->state.forceValue(*a.value, nix::noPos); state->state.forceValue(*a.value, nix::noPos);
return as_nix_value_ptr(a.value); return new_nix_value(a.value, state->state.mem);
} }
NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_NULL
} }
@@ -461,9 +474,8 @@ nix_value * nix_get_attr_byidx_lazy(
} }
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);
// Note: intentionally NOT calling forceValue() to keep the attribute lazy // Note: intentionally NOT calling forceValue() to keep the attribute lazy
return as_nix_value_ptr(a.value); return new_nix_value(a.value, state->state.mem);
} }
NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_NULL
} }
@@ -503,7 +515,7 @@ nix_err nix_init_string(nix_c_context * context, nix_value * value, const char *
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
try { try {
auto & v = check_value_out(value); auto & v = check_value_out(value);
v.mkString(std::string_view(str)); v.mkString(std::string_view(str), *value->mem);
} }
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }
@@ -514,7 +526,7 @@ nix_err nix_init_path_string(nix_c_context * context, EvalState * s, nix_value *
context->last_err_code = NIX_OK; context->last_err_code = NIX_OK;
try { try {
auto & v = check_value_out(value); auto & v = check_value_out(value);
v.mkPath(s->state.rootPath(nix::CanonPath(str))); v.mkPath(s->state.rootPath(nix::CanonPath(str)), s->state.mem);
} }
NIXC_CATCH_ERRS NIXC_CATCH_ERRS
} }
+1 -1
View File
@@ -73,7 +73,7 @@ TEST_F(JSONValueTest, StringQuotes)
TEST_F(JSONValueTest, DISABLED_Path) TEST_F(JSONValueTest, DISABLED_Path)
{ {
Value v; Value v;
v.mkPath(state.rootPath(CanonPath("/test"))); v.mkPath(state.rootPath(CanonPath("/test")), state.mem);
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\""); ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
} }
} /* namespace nix */ } /* namespace nix */
+2 -2
View File
@@ -268,7 +268,7 @@ struct StringPrintingTests : LibExprTest
void test(std::string_view literal, std::string_view expected, unsigned int maxLength, A... args) void test(std::string_view literal, std::string_view expected, unsigned int maxLength, A... args)
{ {
Value v; Value v;
v.mkString(literal); v.mkString(literal, state.mem);
std::stringstream out; std::stringstream out;
printValue(state, out, v, PrintOptions{.maxStringLength = maxLength}); printValue(state, out, v, PrintOptions{.maxStringLength = maxLength});
@@ -353,7 +353,7 @@ TEST_F(ValuePrintingTests, ansiColorsStringElided)
TEST_F(ValuePrintingTests, ansiColorsPath) TEST_F(ValuePrintingTests, ansiColorsPath)
{ {
Value v; Value v;
v.mkPath(state.rootPath(CanonPath("puppy"))); v.mkPath(state.rootPath(CanonPath("puppy")), state.mem);
test(v, ANSI_GREEN "/puppy" ANSI_NORMAL, PrintOptions{.ansiColors = true}); test(v, ANSI_GREEN "/puppy" ANSI_NORMAL, PrintOptions{.ansiColors = true});
} }
+26 -17
View File
@@ -81,20 +81,20 @@ static const char * makeImmutableString(std::string_view s)
return t; return t;
} }
StringData & StringData::alloc(size_t size) StringData & StringData::alloc(EvalMemory & mem, size_t size)
{ {
void * t = GC_MALLOC_ATOMIC(sizeof(StringData) + size + 1); void * t = mem.allocBytes(sizeof(StringData) + size + 1);
if (!t) if (!t)
throw std::bad_alloc(); throw std::bad_alloc();
auto res = new (t) StringData(size); auto res = new (t) StringData(size);
return *res; return *res;
} }
const StringData & StringData::make(std::string_view s) const StringData & StringData::make(EvalMemory & mem, std::string_view s)
{ {
if (s.empty()) if (s.empty())
return ""_sds; return ""_sds;
auto & res = alloc(s.size()); auto & res = alloc(mem, s.size());
std::memcpy(&res.data_, s.data(), s.size()); std::memcpy(&res.data_, s.data(), s.size());
res.data_[s.size()] = '\0'; res.data_[s.size()] = '\0';
return res; return res;
@@ -849,9 +849,9 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
evalState.runDebugRepl(nullptr, trace.env, trace.expr); evalState.runDebugRepl(nullptr, trace.env, trace.expr);
} }
void Value::mkString(std::string_view s) void Value::mkString(std::string_view s, EvalMemory & mem)
{ {
mkStringNoCopy(StringData::make(s)); mkStringNoCopy(StringData::make(mem, s));
} }
Value::StringWithContext::Context * Value::StringWithContext::Context *
@@ -862,13 +862,13 @@ Value::StringWithContext::Context::fromBuilder(const NixStringContext & context,
auto ctx = new (mem.allocBytes(sizeof(Context) + context.size() * sizeof(value_type))) Context(context.size()); auto ctx = new (mem.allocBytes(sizeof(Context) + context.size() * sizeof(value_type))) Context(context.size());
std::ranges::transform( std::ranges::transform(
context, ctx->elems, [](const NixStringContextElem & elt) { return &StringData::make(elt.to_string()); }); context, ctx->elems, [&](const NixStringContextElem & elt) { return &StringData::make(mem, elt.to_string()); });
return ctx; return ctx;
} }
void Value::mkString(std::string_view s, const NixStringContext & context, EvalMemory & mem) void Value::mkString(std::string_view s, const NixStringContext & context, EvalMemory & mem)
{ {
mkStringNoCopy(StringData::make(s), Value::StringWithContext::Context::fromBuilder(context, mem)); mkStringNoCopy(StringData::make(mem, s), Value::StringWithContext::Context::fromBuilder(context, mem));
} }
void Value::mkStringMove(const StringData & s, const NixStringContext & context, EvalMemory & mem) void Value::mkStringMove(const StringData & s, const NixStringContext & context, EvalMemory & mem)
@@ -876,9 +876,9 @@ void Value::mkStringMove(const StringData & s, const NixStringContext & context,
mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context, mem)); mkStringNoCopy(s, Value::StringWithContext::Context::fromBuilder(context, mem));
} }
void Value::mkPath(const SourcePath & path) void Value::mkPath(const SourcePath & path, EvalMemory & mem)
{ {
mkPath(&*path.accessor, StringData::make(path.path.abs())); mkPath(&*path.accessor, StringData::make(mem, path.path.abs()));
} }
inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval) inline Value * EvalState::lookupVar(Env * env, const ExprVar & var, bool noEval)
@@ -943,7 +943,7 @@ void EvalState::mkPos(Value & v, PosIdx p)
auto origin = positions.originOf(p); auto origin = positions.originOf(p);
if (auto path = std::get_if<SourcePath>(&origin)) { if (auto path = std::get_if<SourcePath>(&origin)) {
auto attrs = buildBindings(3); auto attrs = buildBindings(3);
attrs.alloc(s.file).mkString(path->path.abs()); attrs.alloc(s.file).mkString(path->path.abs(), mem);
makePositionThunks(*this, p, attrs.alloc(s.line), attrs.alloc(s.column)); makePositionThunks(*this, p, attrs.alloc(s.line), attrs.alloc(s.column));
v.mkAttrs(attrs); v.mkAttrs(attrs);
} else } else
@@ -1751,9 +1751,9 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v)
// 4: about 60 // 4: about 60
// 5: under 10 // 5: under 10
// This excluded attrset lambdas (`{...}:`). Contributions of mixed lambdas appears insignificant at ~150 total. // This excluded attrset lambdas (`{...}:`). Contributions of mixed lambdas appears insignificant at ~150 total.
SmallValueVector<4> vArgs(args.size()); SmallValueVector<4> vArgs(args->size());
for (size_t i = 0; i < args.size(); ++i) for (size_t i = 0; i < args->size(); ++i)
vArgs[i] = args[i]->maybeThunk(state, env); vArgs[i] = (*args)[i]->maybeThunk(state, env);
state.callFunction(vFun, vArgs, v, pos); state.callFunction(vFun, vArgs, v, pos);
} }
@@ -2139,9 +2139,9 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
for (const auto & part : strings) { for (const auto & part : strings) {
resultStr += *part; resultStr += *part;
} }
v.mkPath(state.rootPath(CanonPath(resultStr))); v.mkPath(state.rootPath(CanonPath(resultStr)), state.mem);
} else { } else {
auto & resultStr = StringData::alloc(sSize); auto & resultStr = StringData::alloc(state.mem, sSize);
auto * tmp = resultStr.data(); auto * tmp = resultStr.data();
for (const auto & part : strings) { for (const auto & part : strings) {
std::memcpy(tmp, part->data(), part->size()); std::memcpy(tmp, part->data(), part->size());
@@ -2188,6 +2188,8 @@ void EvalState::forceValueDeep(Value & v)
std::set<const Value *> seen; std::set<const Value *> seen;
[&, &state(*this)](this const auto & recurse, Value & v) { [&, &state(*this)](this const auto & recurse, Value & v) {
auto _level = state.addCallDepth(v.determinePos(noPos));
if (!seen.insert(&v).second) if (!seen.insert(&v).second)
return; return;
@@ -2214,8 +2216,15 @@ void EvalState::forceValueDeep(Value & v)
} }
else if (v.isList()) { else if (v.isList()) {
size_t index = 0;
for (auto v2 : v.listView()) for (auto v2 : v.listView())
recurse(*v2); try {
recurse(*v2);
index++;
} catch (Error & e) {
state.addErrorTrace(e, "while evaluating list element at index %1%", index);
throw;
}
} }
}(v); }(v);
} }
+8 -5
View File
@@ -592,11 +592,14 @@ public:
struct ExprCall : Expr struct ExprCall : Expr
{ {
Expr * fun; Expr * fun;
std::vector<Expr *> args; /**
* args will never be null. See comment on ExprAttrs::AttrDefs below.
*/
std::optional<std::pmr::vector<Expr *>> args;
PosIdx pos; PosIdx pos;
std::optional<PosIdx> cursedOrEndPos; // used during parsing to warn about https://github.com/NixOS/nix/issues/11118 std::optional<PosIdx> cursedOrEndPos; // used during parsing to warn about https://github.com/NixOS/nix/issues/11118
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args) ExprCall(const PosIdx & pos, Expr * fun, std::pmr::vector<Expr *> && args)
: fun(fun) : fun(fun)
, args(args) , args(args)
, pos(pos) , pos(pos)
@@ -604,7 +607,7 @@ struct ExprCall : Expr
{ {
} }
ExprCall(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args, PosIdx && cursedOrEndPos) ExprCall(const PosIdx & pos, Expr * fun, std::pmr::vector<Expr *> && args, PosIdx && cursedOrEndPos)
: fun(fun) : fun(fun)
, args(args) , args(args)
, pos(pos) , pos(pos)
@@ -836,7 +839,7 @@ public:
// we define some calls to add explicitly so that the argument can be passed in as initializer lists // we define some calls to add explicitly so that the argument can be passed in as initializer lists
template<class C> template<class C>
[[gnu::always_inline]] [[gnu::always_inline]]
C * add(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args) C * add(const PosIdx & pos, Expr * fun, std::pmr::vector<Expr *> && args)
requires(std::same_as<C, ExprCall>) requires(std::same_as<C, ExprCall>)
{ {
return alloc.new_object<C>(pos, fun, std::move(args)); return alloc.new_object<C>(pos, fun, std::move(args));
@@ -844,7 +847,7 @@ public:
template<class C> template<class C>
[[gnu::always_inline]] [[gnu::always_inline]]
C * add(const PosIdx & pos, Expr * fun, std::vector<Expr *> && args, PosIdx && cursedOrEndPos) C * add(const PosIdx & pos, Expr * fun, std::pmr::vector<Expr *> && args, PosIdx && cursedOrEndPos)
requires(std::same_as<C, ExprCall>) requires(std::same_as<C, ExprCall>)
{ {
return alloc.new_object<C>(pos, fun, std::move(args), std::move(cursedOrEndPos)); return alloc.new_object<C>(pos, fun, std::move(args), std::move(cursedOrEndPos));
+4 -4
View File
@@ -232,13 +232,13 @@ public:
* Allocate StringData on the (possibly) GC-managed heap and copy * Allocate StringData on the (possibly) GC-managed heap and copy
* the contents of s to it. * the contents of s to it.
*/ */
static const StringData & make(std::string_view s); static const StringData & make(EvalMemory & mem, std::string_view s);
/** /**
* Allocate StringData on the (possibly) GC-managed heap. * Allocate StringData on the (possibly) GC-managed heap.
* @param size Length of the string (without the NUL terminator). * @param size Length of the string (without the NUL terminator).
*/ */
static StringData & alloc(size_t size); static StringData & alloc(EvalMemory & mem, size_t size);
size_t size() const size_t size() const
{ {
@@ -1147,13 +1147,13 @@ public:
setStorage(StringWithContext{.str = &s, .context = context}); setStorage(StringWithContext{.str = &s, .context = context});
} }
void mkString(std::string_view s); void mkString(std::string_view s, EvalMemory & mem);
void mkString(std::string_view s, const NixStringContext & context, EvalMemory & mem); void mkString(std::string_view s, const NixStringContext & context, EvalMemory & mem);
void mkStringMove(const StringData & s, const NixStringContext & context, EvalMemory & mem); void mkStringMove(const StringData & s, const NixStringContext & context, EvalMemory & mem);
void mkPath(const SourcePath & path); void mkPath(const SourcePath & path, EvalMemory & mem);
inline void mkPath(SourceAccessor * accessor, const StringData & path) noexcept inline void mkPath(SourceAccessor * accessor, const StringData & path) noexcept
{ {
+1 -1
View File
@@ -151,7 +151,7 @@ public:
bool string(string_t & val) override bool string(string_t & val) override
{ {
forceNoNullByte(val); forceNoNullByte(val);
rs->value(state).mkString(val); rs->value(state).mkString(val, state.mem);
rs->add(); rs->add();
return true; return true;
} }
+8 -2
View File
@@ -191,7 +191,7 @@ void ExprCall::show(const SymbolTable & symbols, std::ostream & str) const
{ {
str << '('; str << '(';
fun->show(symbols, str); fun->show(symbols, str);
for (auto e : args) { for (auto e : *args) {
str << ' '; str << ' ';
e->show(symbols, str); e->show(symbols, str);
} }
@@ -486,11 +486,17 @@ void ExprLambda::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv>
void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env) void ExprCall::bindVars(EvalState & es, const std::shared_ptr<const StaticEnv> & env)
{ {
// Move storage into the Exprs arena
{
auto arena = es.mem.exprs.alloc;
std::pmr::vector<Expr *> newArgs{std::move(*args), arena};
args.emplace(std::move(newArgs), arena);
}
if (es.debugRepl) if (es.debugRepl)
es.exprEnvs.insert(std::make_pair(this, env)); es.exprEnvs.insert(std::make_pair(this, env));
fun->bindVars(es, env); fun->bindVars(es, env);
for (auto e : args) for (auto e : *args)
e->bindVars(es, env); e->bindVars(es, env);
} }
+2 -2
View File
@@ -115,7 +115,7 @@ static void setDocPosition(const LexerState & lexerState, ExprLambda * lambda, P
static Expr * makeCall(Exprs & exprs, PosIdx pos, Expr * fn, Expr * arg) { static Expr * makeCall(Exprs & exprs, PosIdx pos, Expr * fn, Expr * arg) {
if (auto e2 = dynamic_cast<ExprCall *>(fn)) { if (auto e2 = dynamic_cast<ExprCall *>(fn)) {
e2->args.push_back(arg); e2->args->push_back(arg);
return fn; return fn;
} }
return exprs.add<ExprCall>(pos, fn, {arg}); return exprs.add<ExprCall>(pos, fn, {arg});
@@ -129,7 +129,7 @@ static Expr * makeCall(Exprs & exprs, PosIdx pos, Expr * fn, Expr * arg) {
%type <Expr *> start expr expr_function expr_if expr_op %type <Expr *> start expr expr_function expr_if expr_op
%type <Expr *> expr_select expr_simple expr_app %type <Expr *> expr_select expr_simple expr_app
%type <Expr *> expr_pipe_from expr_pipe_into %type <Expr *> expr_pipe_from expr_pipe_into
%type <std::vector<Expr *>> list %type <std::pmr::vector<Expr *>> list
%type <ExprAttrs *> binds binds1 %type <ExprAttrs *> binds binds1
%type <FormalsBuilder> formals formal_set %type <FormalsBuilder> formals formal_set
%type <Formal> formal %type <Formal> formal
+24 -22
View File
@@ -53,7 +53,7 @@ RegisterPrimOp::PrimOps & RegisterPrimOp::primOps()
static inline Value * mkString(EvalState & state, const std::csub_match & match) static inline Value * mkString(EvalState & state, const std::csub_match & match)
{ {
Value * v = state.allocValue(); Value * v = state.allocValue();
v->mkString({match.first, match.second}); v->mkString({match.first, match.second}, state.mem);
return v; return v;
} }
@@ -230,12 +230,12 @@ void derivationToValue(
NixStringContextElem::DrvDeep{.drvPath = storePath}, NixStringContextElem::DrvDeep{.drvPath = storePath},
}, },
state.mem); state.mem);
attrs.alloc(state.s.name).mkString(drv.env["name"]); attrs.alloc(state.s.name).mkString(drv.env["name"], state.mem);
auto list = state.buildList(drv.outputs.size()); auto list = state.buildList(drv.outputs.size());
for (const auto & [i, o] : enumerate(drv.outputs)) { for (const auto & [i, o] : enumerate(drv.outputs)) {
mkOutputString(state, attrs, storePath, o); mkOutputString(state, attrs, storePath, o);
(list[i] = state.allocValue())->mkString(o.first); (list[i] = state.allocValue())->mkString(o.first, state.mem);
} }
attrs.alloc(state.s.outputs).mkList(list); attrs.alloc(state.s.outputs).mkList(list);
@@ -519,7 +519,7 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
v.mkStringNoCopy("lambda"_sds); v.mkStringNoCopy("lambda"_sds);
break; break;
case nExternal: case nExternal:
v.mkString(args[0]->external()->typeOf()); v.mkString(args[0]->external()->typeOf(), state.mem);
break; break;
case nFloat: case nFloat:
v.mkStringNoCopy("float"_sds); v.mkStringNoCopy("float"_sds);
@@ -1176,7 +1176,7 @@ static void prim_getEnv(EvalState & state, const PosIdx pos, Value ** args, Valu
{ {
std::string name( std::string name(
state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.getEnv")); state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.getEnv"));
v.mkString(state.settings.restrictEval || state.settings.pureEval ? "" : getEnv(name).value_or("")); v.mkString(state.settings.restrictEval || state.settings.pureEval ? "" : getEnv(name).value_or(""), state.mem);
} }
static RegisterPrimOp primop_getEnv({ static RegisterPrimOp primop_getEnv({
@@ -1842,8 +1842,10 @@ static RegisterPrimOp primop_derivationStrict(
‘out’. */ ‘out’. */
static void prim_placeholder(EvalState & state, const PosIdx pos, Value ** args, Value & v) static void prim_placeholder(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{ {
v.mkString(hashPlaceholder( v.mkString(
state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.placeholder"))); hashPlaceholder(state.forceStringNoCtx(
*args[0], pos, "while evaluating the first argument passed to builtins.placeholder")),
state.mem);
} }
static RegisterPrimOp primop_placeholder({ static RegisterPrimOp primop_placeholder({
@@ -2027,7 +2029,7 @@ static void prim_dirOf(EvalState & state, const PosIdx pos, Value ** args, Value
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
if (args[0]->type() == nPath) { if (args[0]->type() == nPath) {
auto path = args[0]->path(); auto path = args[0]->path();
v.mkPath(path.path.isRoot() ? path : path.parent()); v.mkPath(path.path.isRoot() ? path : path.parent(), state.mem);
} else { } else {
NixStringContext context; NixStringContext context;
auto path = state.coerceToString( auto path = state.coerceToString(
@@ -2144,7 +2146,7 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value ** args, Va
auto path = auto path =
state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.findFile"); state.forceStringNoCtx(*args[1], pos, "while evaluating the second argument passed to builtins.findFile");
v.mkPath(state.findFile(lookupPath, path, pos)); v.mkPath(state.findFile(lookupPath, path, pos), state.mem);
} }
static RegisterPrimOp primop_findFile( static RegisterPrimOp primop_findFile(
@@ -2293,7 +2295,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value ** args, Va
auto path = realisePath(state, pos, *args[1]); auto path = realisePath(state, pos, *args[1]);
v.mkString(hashString(*ha, path.readFile()).to_string(HashFormat::Base16, false)); v.mkString(hashString(*ha, path.readFile()).to_string(HashFormat::Base16, false), state.mem);
} }
static RegisterPrimOp primop_hashFile({ static RegisterPrimOp primop_hashFile({
@@ -2382,7 +2384,7 @@ static void prim_readDir(EvalState & state, const PosIdx pos, Value ** args, Val
// detailed node info quickly in this case we produce a thunk to // detailed node info quickly in this case we produce a thunk to
// query the file type lazily. // query the file type lazily.
auto epath = state.allocValue(); auto epath = state.allocValue();
epath->mkPath(path / name); epath->mkPath(path / name, state.mem);
if (!readFileType) if (!readFileType)
readFileType = &state.getBuiltin("readFileType"); readFileType = &state.getBuiltin("readFileType");
attr.mkApp(readFileType, epath); attr.mkApp(readFileType, epath);
@@ -2763,7 +2765,7 @@ bool EvalState::callPathFilter(Value * filterFun, const SourcePath & path, PosId
/* Call the filter function. The first argument is the path, the /* Call the filter function. The first argument is the path, the
second is a string indicating the type of the file. */ second is a string indicating the type of the file. */
Value arg1; Value arg1;
arg1.mkString(path.path.abs()); arg1.mkString(path.path.abs(), mem);
// assert that type is not "unknown" // assert that type is not "unknown"
Value * args[]{&arg1, const_cast<Value *>(&fileTypeToString(*this, st.type))}; Value * args[]{&arg1, const_cast<Value *>(&fileTypeToString(*this, st.type))};
@@ -4541,7 +4543,7 @@ static void prim_hashString(EvalState & state, const PosIdx pos, Value ** args,
auto s = auto s =
state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString"); state.forceString(*args[1], context, pos, "while evaluating the second argument passed to builtins.hashString");
v.mkString(hashString(*ha, s).to_string(HashFormat::Base16, false)); v.mkString(hashString(*ha, s).to_string(HashFormat::Base16, false), state.mem);
} }
static RegisterPrimOp primop_hashString({ static RegisterPrimOp primop_hashString({
@@ -4574,7 +4576,7 @@ static void prim_convertHash(EvalState & state, const PosIdx pos, Value ** args,
HashFormat hf = parseHashFormat( HashFormat hf = parseHashFormat(
state.forceStringNoCtx(*iteratorToHashFormat->value, pos, "while evaluating the attribute 'toHashFormat'")); state.forceStringNoCtx(*iteratorToHashFormat->value, pos, "while evaluating the attribute 'toHashFormat'"));
v.mkString(Hash::parseAny(hash, ha).to_string(hf, hf == HashFormat::SRI)); v.mkString(Hash::parseAny(hash, ha).to_string(hf, hf == HashFormat::SRI), state.mem);
} }
static RegisterPrimOp primop_convertHash({ static RegisterPrimOp primop_convertHash({
@@ -4992,8 +4994,8 @@ static void prim_parseDrvName(EvalState & state, const PosIdx pos, Value ** args
state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.parseDrvName"); state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.parseDrvName");
DrvName parsed(name); DrvName parsed(name);
auto attrs = state.buildBindings(2); auto attrs = state.buildBindings(2);
attrs.alloc(state.s.name).mkString(parsed.name); attrs.alloc(state.s.name).mkString(parsed.name, state.mem);
attrs.alloc("version").mkString(parsed.version); attrs.alloc("version").mkString(parsed.version, state.mem);
v.mkAttrs(attrs); v.mkAttrs(attrs);
} }
@@ -5048,7 +5050,7 @@ static void prim_splitVersion(EvalState & state, const PosIdx pos, Value ** args
} }
auto list = state.buildList(components.size()); auto list = state.buildList(components.size());
for (const auto & [n, component] : enumerate(components)) for (const auto & [n, component] : enumerate(components))
(list[n] = state.allocValue())->mkString(std::move(component)); (list[n] = state.allocValue())->mkString(std::move(component), state.mem);
v.mkList(list); v.mkList(list);
} }
@@ -5192,7 +5194,7 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
}); });
if (!settings.pureEval) if (!settings.pureEval)
v.mkString(settings.getCurrentSystem()); v.mkString(settings.getCurrentSystem(), mem);
addConstant( addConstant(
"__currentSystem", "__currentSystem",
v, v,
@@ -5224,7 +5226,7 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
.impureOnly = true, .impureOnly = true,
}); });
v.mkString(nixVersion); v.mkString(nixVersion, mem);
addConstant( addConstant(
"__nixVersion", "__nixVersion",
v, v,
@@ -5249,7 +5251,7 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
)", )",
}); });
v.mkString(store->storeDir); v.mkString(store->storeDir, mem);
addConstant( addConstant(
"__storeDir", "__storeDir",
v, v,
@@ -5314,8 +5316,8 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
auto list = buildList(lookupPath.elements.size()); auto list = buildList(lookupPath.elements.size());
for (const auto & [n, i] : enumerate(lookupPath.elements)) { for (const auto & [n, i] : enumerate(lookupPath.elements)) {
auto attrs = buildBindings(2); auto attrs = buildBindings(2);
attrs.alloc("path").mkString(i.path.s); attrs.alloc("path").mkString(i.path.s, mem);
attrs.alloc("prefix").mkString(i.prefix.s); attrs.alloc("prefix").mkString(i.prefix.s, mem);
(list[n] = allocValue())->mkAttrs(attrs); (list[n] = allocValue())->mkAttrs(attrs);
} }
v.mkList(list); v.mkList(list);
+2 -2
View File
@@ -11,7 +11,7 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos,
NixStringContext context; NixStringContext context;
auto s = state.coerceToString( auto s = state.coerceToString(
pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext"); pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
v.mkString(*s); v.mkString(*s, state.mem);
} }
static RegisterPrimOp primop_unsafeDiscardStringContext({ static RegisterPrimOp primop_unsafeDiscardStringContext({
@@ -218,7 +218,7 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value ** args,
if (!info.second.outputs.empty()) { if (!info.second.outputs.empty()) {
auto list = state.buildList(info.second.outputs.size()); auto list = state.buildList(info.second.outputs.size());
for (const auto & [i, output] : enumerate(info.second.outputs)) for (const auto & [i, output] : enumerate(info.second.outputs))
(list[i] = state.allocValue())->mkString(output); (list[i] = state.allocValue())->mkString(output, state.mem);
infoAttrs.alloc(state.s.outputs).mkList(list); infoAttrs.alloc(state.s.outputs).mkList(list);
} }
attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs); attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);
+3 -3
View File
@@ -86,12 +86,12 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value ** ar
auto attrs2 = state.buildBindings(8); auto attrs2 = state.buildBindings(8);
state.mkStorePathString(storePath, attrs2.alloc(state.s.outPath)); state.mkStorePathString(storePath, attrs2.alloc(state.s.outPath));
if (input2.getRef()) if (input2.getRef())
attrs2.alloc("branch").mkString(*input2.getRef()); attrs2.alloc("branch").mkString(*input2.getRef(), state.mem);
// Backward compatibility: set 'rev' to // Backward compatibility: set 'rev' to
// 0000000000000000000000000000000000000000 for a dirty tree. // 0000000000000000000000000000000000000000 for a dirty tree.
auto rev2 = input2.getRev().value_or(Hash(HashAlgorithm::SHA1)); auto rev2 = input2.getRev().value_or(Hash(HashAlgorithm::SHA1));
attrs2.alloc("rev").mkString(rev2.gitRev()); attrs2.alloc("rev").mkString(rev2.gitRev(), state.mem);
attrs2.alloc("shortRev").mkString(rev2.gitRev().substr(0, 12)); attrs2.alloc("shortRev").mkString(rev2.gitRev().substr(0, 12), state.mem);
if (auto revCount = input2.getRevCount()) if (auto revCount = input2.getRevCount())
attrs2.alloc("revCount").mkInt(*revCount); attrs2.alloc("revCount").mkInt(*revCount);
v.mkAttrs(attrs2); v.mkAttrs(attrs2);
+14 -15
View File
@@ -35,7 +35,7 @@ void emitTreeAttrs(
// FIXME: support arbitrary input attributes. // FIXME: support arbitrary input attributes.
if (auto narHash = input.getNarHash()) if (auto narHash = input.getNarHash())
attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true)); attrs.alloc("narHash").mkString(narHash->to_string(HashFormat::SRI, true), state.mem);
if (input.getType() == "git") if (input.getType() == "git")
attrs.alloc("submodules").mkBool(fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false)); attrs.alloc("submodules").mkBool(fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false));
@@ -43,13 +43,13 @@ void emitTreeAttrs(
if (!forceDirty) { if (!forceDirty) {
if (auto rev = input.getRev()) { if (auto rev = input.getRev()) {
attrs.alloc("rev").mkString(rev->gitRev()); attrs.alloc("rev").mkString(rev->gitRev(), state.mem);
attrs.alloc("shortRev").mkString(rev->gitShortRev()); attrs.alloc("shortRev").mkString(rev->gitShortRev(), state.mem);
} else if (emptyRevFallback) { } else if (emptyRevFallback) {
// Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev // Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
auto emptyHash = Hash(HashAlgorithm::SHA1); auto emptyHash = Hash(HashAlgorithm::SHA1);
attrs.alloc("rev").mkString(emptyHash.gitRev()); attrs.alloc("rev").mkString(emptyHash.gitRev(), state.mem);
attrs.alloc("shortRev").mkString(emptyHash.gitShortRev()); attrs.alloc("shortRev").mkString(emptyHash.gitShortRev(), state.mem);
} }
if (auto revCount = input.getRevCount()) if (auto revCount = input.getRevCount())
@@ -59,13 +59,14 @@ void emitTreeAttrs(
} }
if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) { if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) {
attrs.alloc("dirtyRev").mkString(*dirtyRev); attrs.alloc("dirtyRev").mkString(*dirtyRev, state.mem);
attrs.alloc("dirtyShortRev").mkString(*fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev")); attrs.alloc("dirtyShortRev").mkString(*fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev"), state.mem);
} }
if (auto lastModified = input.getLastModified()) { if (auto lastModified = input.getLastModified()) {
attrs.alloc("lastModified").mkInt(*lastModified); attrs.alloc("lastModified").mkInt(*lastModified);
attrs.alloc("lastModifiedDate").mkString(fmt("%s", std::put_time(std::gmtime(&*lastModified), "%Y%m%d%H%M%S"))); attrs.alloc("lastModifiedDate")
.mkString(fmt("%s", std::put_time(std::gmtime(&*lastModified), "%Y%m%d%H%M%S")), state.mem);
} }
v.mkAttrs(attrs); v.mkAttrs(attrs);
@@ -150,11 +151,6 @@ static void fetchTree(
attrs.emplace("exportIgnore", Explicit<bool>{true}); attrs.emplace("exportIgnore", Explicit<bool>{true});
} }
// fetchTree should fetch git repos with shallow = true by default
if (type == "git" && !params.isFetchGit && !attrs.contains("shallow")) {
attrs.emplace("shallow", Explicit<bool>{true});
}
if (!params.allowNameArgument) if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end()) if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
state.error<EvalError>("argument 'name' isn’t supported in call to '%s'", fetcher) state.error<EvalError>("argument 'name' isn’t supported in call to '%s'", fetcher)
@@ -366,10 +362,13 @@ void prim_fetchFinalTree(EvalState & state, const PosIdx pos, Value ** args, Val
} }
static RegisterPrimOp primop_fetchFinalTree({ static RegisterPrimOp primop_fetchFinalTree({
.name = "fetchFinalTree", .name = "__fetchFinalTree",
.args = {"input"}, .args = {"input"},
.doc = R"(
Like `fetchTree`, but does not return any additional fetcher attributes (like `revCount`).
This allows inputs to be substituted if `narHash` is specified.
)",
.fun = prim_fetchFinalTree, .fun = prim_fetchFinalTree,
.internal = true,
}); });
static void fetch( static void fetch(
+2 -2
View File
@@ -126,7 +126,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
case toml::value_t::string: { case toml::value_t::string: {
auto s = toml::get<std::string_view>(t); auto s = toml::get<std::string_view>(t);
forceNoNullByte(s); forceNoNullByte(s);
v.mkString(s); v.mkString(s, state.mem);
} break; } break;
case toml::value_t::local_datetime: case toml::value_t::local_datetime:
case toml::value_t::offset_datetime: case toml::value_t::offset_datetime:
@@ -142,7 +142,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
s << t; s << t;
auto str = s.view(); auto str = s.view();
forceNoNullByte(str); forceNoNullByte(str);
attrs.alloc("value").mkString(str); attrs.alloc("value").mkString(str, state.mem);
v.mkAttrs(attrs); v.mkAttrs(attrs);
} else { } else {
throw std::runtime_error("Dates and times are not supported"); throw std::runtime_error("Dates and times are not supported");
+2
View File
@@ -16,6 +16,8 @@ json printValueAsJSON(
{ {
checkInterrupt(); checkInterrupt();
auto _level = state.addCallDepth(pos);
if (strict) if (strict)
state.forceValue(v, pos); state.forceValue(v, pos);
+1 -1
View File
@@ -92,7 +92,7 @@ TEST_F(GitUtilsTest, sink_basic)
// sink->createHardlink("foo-1.1/links/foo-2", CanonPath("foo-1.1/hello")); // sink->createHardlink("foo-1.1/links/foo-2", CanonPath("foo-1.1/hello"));
auto result = repo->dereferenceSingletonDirectory(sink->flush()); auto result = repo->dereferenceSingletonDirectory(sink->flush());
auto accessor = repo->getAccessor(result, false, getRepoName()); auto accessor = repo->getAccessor(result, {}, getRepoName());
auto entries = accessor->readDirectory(CanonPath::root); auto entries = accessor->readDirectory(CanonPath::root);
ASSERT_EQ(entries.size(), 5u); ASSERT_EQ(entries.size(), 5u);
ASSERT_EQ(accessor->readFile(CanonPath("hello")), "hello world"); ASSERT_EQ(accessor->readFile(CanonPath("hello")), "hello world");
+3 -17
View File
@@ -269,24 +269,10 @@ void Input::checkLocks(Input specified, Input & result)
} }
} }
if (auto prevLastModified = specified.getLastModified()) {
if (result.getLastModified() != prevLastModified)
throw Error(
"'lastModified' attribute mismatch in input '%s', expected %d, got %d",
result.to_string(),
*prevLastModified,
result.getLastModified().value_or(-1));
}
if (auto prevRev = specified.getRev()) { if (auto prevRev = specified.getRev()) {
if (result.getRev() != prevRev) if (result.getRev() != prevRev)
throw Error("'rev' attribute mismatch in input '%s', expected %s", result.to_string(), prevRev->gitRev()); throw Error("'rev' attribute mismatch in input '%s', expected %s", result.to_string(), prevRev->gitRev());
} }
if (auto prevRevCount = specified.getRevCount()) {
if (result.getRevCount() != prevRevCount)
throw Error("'revCount' attribute mismatch in input '%s', expected %d", result.to_string(), *prevRevCount);
}
} }
std::pair<ref<SourceAccessor>, Input> Input::getAccessor(const Settings & settings, Store & store) const std::pair<ref<SourceAccessor>, Input> Input::getAccessor(const Settings & settings, Store & store) const
@@ -495,9 +481,9 @@ void InputScheme::clone(
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s' to %s...", input2.to_string(), destDir)); Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s' to %s...", input2.to_string(), destDir));
auto source = sinkToSource([&](Sink & sink) { accessor->dumpPath(CanonPath::root, sink); }); RestoreSink sink(/*startFsync=*/false);
sink.dstPath = destDir;
restorePath(destDir, *source); copyRecursive(*accessor, CanonPath::root, sink, CanonPath::root);
} }
std::optional<ExperimentalFeature> InputScheme::experimentalFeature() const std::optional<ExperimentalFeature> InputScheme::experimentalFeature() const
+24 -24
View File
@@ -541,14 +541,15 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
} }
/** /**
* A 'GitSourceAccessor' with no regard for export-ignore or any other transformations. * A 'GitSourceAccessor' with no regard for export-ignore.
*/ */
ref<GitSourceAccessor> getRawAccessor(const Hash & rev, bool smudgeLfs = false); ref<GitSourceAccessor> getRawAccessor(const Hash & rev, const GitAccessorOptions & options);
ref<SourceAccessor> ref<SourceAccessor>
getAccessor(const Hash & rev, bool exportIgnore, std::string displayPrefix, bool smudgeLfs = false) override; getAccessor(const Hash & rev, const GitAccessorOptions & options, std::string displayPrefix) override;
ref<SourceAccessor> getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError e) override; ref<SourceAccessor>
getAccessor(const WorkdirInfo & wd, const GitAccessorOptions & options, MakeNotAllowedError e) override;
ref<GitFileSystemObjectSink> getFileSystemObjectSink() override; ref<GitFileSystemObjectSink> getFileSystemObjectSink() override;
@@ -668,7 +669,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
Hash treeHashToNarHash(const fetchers::Settings & settings, const Hash & treeHash) override Hash treeHashToNarHash(const fetchers::Settings & settings, const Hash & treeHash) override
{ {
auto accessor = getAccessor(treeHash, false, ""); auto accessor = getAccessor(treeHash, {}, "");
fetchers::Cache::Key cacheKey{"treeHashToNarHash", {{"treeHash", treeHash.gitRev()}}}; fetchers::Cache::Key cacheKey{"treeHashToNarHash", {{"treeHash", treeHash.gitRev()}}};
@@ -716,15 +717,17 @@ struct GitSourceAccessor : SourceAccessor
ref<GitRepoImpl> repo; ref<GitRepoImpl> repo;
Object root; Object root;
std::optional<lfs::Fetch> lfsFetch = std::nullopt; std::optional<lfs::Fetch> lfsFetch = std::nullopt;
GitAccessorOptions options;
}; };
Sync<State> state_; Sync<State> state_;
GitSourceAccessor(ref<GitRepoImpl> repo_, const Hash & rev, bool smudgeLfs) GitSourceAccessor(ref<GitRepoImpl> repo_, const Hash & rev, const GitAccessorOptions & options)
: state_{State{ : state_{State{
.repo = repo_, .repo = repo_,
.root = peelToTreeOrBlob(lookupObject(*repo_, hashToOID(rev)).get()), .root = peelToTreeOrBlob(lookupObject(*repo_, hashToOID(rev)).get()),
.lfsFetch = smudgeLfs ? std::make_optional(lfs::Fetch(*repo_, hashToOID(rev))) : std::nullopt, .lfsFetch = options.smudgeLfs ? std::make_optional(lfs::Fetch(*repo_, hashToOID(rev))) : std::nullopt,
.options = options,
}} }}
{ {
} }
@@ -1254,26 +1257,26 @@ struct GitFileSystemObjectSinkImpl : GitFileSystemObjectSink
} }
}; };
ref<GitSourceAccessor> GitRepoImpl::getRawAccessor(const Hash & rev, bool smudgeLfs) ref<GitSourceAccessor> GitRepoImpl::getRawAccessor(const Hash & rev, const GitAccessorOptions & options)
{ {
auto self = ref<GitRepoImpl>(shared_from_this()); auto self = ref<GitRepoImpl>(shared_from_this());
return make_ref<GitSourceAccessor>(self, rev, smudgeLfs); return make_ref<GitSourceAccessor>(self, rev, options);
} }
ref<SourceAccessor> ref<SourceAccessor>
GitRepoImpl::getAccessor(const Hash & rev, bool exportIgnore, std::string displayPrefix, bool smudgeLfs) GitRepoImpl::getAccessor(const Hash & rev, const GitAccessorOptions & options, std::string displayPrefix)
{ {
auto self = ref<GitRepoImpl>(shared_from_this()); auto self = ref<GitRepoImpl>(shared_from_this());
ref<GitSourceAccessor> rawGitAccessor = getRawAccessor(rev, smudgeLfs); ref<GitSourceAccessor> rawGitAccessor = getRawAccessor(rev, options);
rawGitAccessor->setPathDisplay(std::move(displayPrefix)); rawGitAccessor->setPathDisplay(std::move(displayPrefix));
if (exportIgnore) if (options.exportIgnore)
return make_ref<GitExportIgnoreSourceAccessor>(self, rawGitAccessor, rev); return make_ref<GitExportIgnoreSourceAccessor>(self, rawGitAccessor, rev);
else else
return rawGitAccessor; return rawGitAccessor;
} }
ref<SourceAccessor> ref<SourceAccessor> GitRepoImpl::getAccessor(
GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) const WorkdirInfo & wd, const GitAccessorOptions & options, MakeNotAllowedError makeNotAllowedError)
{ {
auto self = ref<GitRepoImpl>(shared_from_this()); auto self = ref<GitRepoImpl>(shared_from_this());
ref<SourceAccessor> fileAccessor = AllowListSourceAccessor::create( ref<SourceAccessor> fileAccessor = AllowListSourceAccessor::create(
@@ -1283,10 +1286,9 @@ GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllow
boost::unordered_flat_set<CanonPath>{CanonPath::root}, boost::unordered_flat_set<CanonPath>{CanonPath::root},
std::move(makeNotAllowedError)) std::move(makeNotAllowedError))
.cast<SourceAccessor>(); .cast<SourceAccessor>();
if (exportIgnore) if (options.exportIgnore)
return make_ref<GitExportIgnoreSourceAccessor>(self, fileAccessor, std::nullopt); fileAccessor = make_ref<GitExportIgnoreSourceAccessor>(self, fileAccessor, std::nullopt);
else return fileAccessor;
return fileAccessor;
} }
ref<GitFileSystemObjectSink> GitRepoImpl::getFileSystemObjectSink() ref<GitFileSystemObjectSink> GitRepoImpl::getFileSystemObjectSink()
@@ -1299,7 +1301,7 @@ std::vector<std::tuple<GitRepoImpl::Submodule, Hash>> GitRepoImpl::getSubmodules
/* Read the .gitmodules files from this revision. */ /* Read the .gitmodules files from this revision. */
CanonPath modulesFile(".gitmodules"); CanonPath modulesFile(".gitmodules");
auto accessor = getAccessor(rev, exportIgnore, ""); auto accessor = getAccessor(rev, {.exportIgnore = exportIgnore}, "");
if (!accessor->pathExists(modulesFile)) if (!accessor->pathExists(modulesFile))
return {}; return {};
@@ -1316,7 +1318,7 @@ std::vector<std::tuple<GitRepoImpl::Submodule, Hash>> GitRepoImpl::getSubmodules
std::vector<std::tuple<Submodule, Hash>> result; std::vector<std::tuple<Submodule, Hash>> result;
auto rawAccessor = getRawAccessor(rev); auto rawAccessor = getRawAccessor(rev, {});
for (auto & submodule : parseSubmodules(pathTemp)) { for (auto & submodule : parseSubmodules(pathTemp)) {
/* Filter out .gitmodules entries that don't exist or are not /* Filter out .gitmodules entries that don't exist or are not
@@ -1332,10 +1334,8 @@ namespace fetchers {
ref<GitRepo> Settings::getTarballCache() const ref<GitRepo> Settings::getTarballCache() const
{ {
auto tarballCache(_tarballCache.lock()); static auto repoDir = std::filesystem::path(getCacheDir()) / "tarball-cache";
if (!*tarballCache) return GitRepo::openRepo(repoDir, true, true);
*tarballCache = GitRepo::openRepo(std::filesystem::path(getCacheDir()) / "tarball-cache", true, true);
return ref<GitRepo>(*tarballCache);
} }
} // namespace fetchers } // namespace fetchers
+48 -15
View File
@@ -778,6 +778,16 @@ struct GitInputScheme : InputScheme
} }
} }
/**
* Decide whether we can do a shallow clone, which is faster. This is possible if the user explicitly specified
* `shallow = true`, or if we already have a `revCount`.
*/
bool canDoShallow(const Input & input) const
{
bool shallow = getShallowAttr(input);
return shallow || input.getRevCount().has_value();
}
std::pair<ref<SourceAccessor>, Input> std::pair<ref<SourceAccessor>, Input>
getAccessorFromCommit(const Settings & settings, Store & store, RepoInfo & repoInfo, Input && input) const getAccessorFromCommit(const Settings & settings, Store & store, RepoInfo & repoInfo, Input && input) const
{ {
@@ -786,7 +796,7 @@ struct GitInputScheme : InputScheme
auto origRev = input.getRev(); auto origRev = input.getRev();
auto originalRef = input.getRef(); auto originalRef = input.getRef();
bool shallow = getShallowAttr(input); bool shallow = canDoShallow(input);
auto ref = originalRef ? *originalRef : getDefaultRef(repoInfo, shallow); auto ref = originalRef ? *originalRef : getDefaultRef(repoInfo, shallow);
input.attrs.insert_or_assign("ref", ref); input.attrs.insert_or_assign("ref", ref);
@@ -797,11 +807,27 @@ struct GitInputScheme : InputScheme
if (!input.getRev()) if (!input.getRev())
input.attrs.insert_or_assign("rev", GitRepo::openRepo(repoDir)->resolveRef(ref).gitRev()); input.attrs.insert_or_assign("rev", GitRepo::openRepo(repoDir)->resolveRef(ref).gitRev());
} else { } else {
auto rev = input.getRev();
auto repoUrl = std::get<ParsedURL>(repoInfo.location); auto repoUrl = std::get<ParsedURL>(repoInfo.location);
std::filesystem::path cacheDir = getCachePath(repoUrl.to_string(), shallow); std::filesystem::path cacheDir = getCachePath(repoUrl.to_string(), shallow);
repoDir = cacheDir; repoDir = cacheDir;
repoInfo.gitDir = "."; repoInfo.gitDir = ".";
/* If shallow = false, but we have a non-shallow repo that already contains the desired rev, then use that
* repo instead. */
std::filesystem::path cacheDirNonShallow = getCachePath(repoUrl.to_string(), false);
if (rev && shallow && pathExists(cacheDirNonShallow)) {
auto nonShallowRepo = GitRepo::openRepo(cacheDirNonShallow, true, true);
if (nonShallowRepo->hasObject(*rev)) {
debug(
"using non-shallow cached repo for '%s' since it contains rev '%s'",
repoUrl.to_string(),
rev->gitRev());
repoDir = cacheDirNonShallow;
goto have_rev;
}
}
std::filesystem::create_directories(cacheDir.parent_path()); std::filesystem::create_directories(cacheDir.parent_path());
PathLocks cacheDirLock({cacheDir.string()}); PathLocks cacheDirLock({cacheDir.string()});
@@ -817,7 +843,7 @@ struct GitInputScheme : InputScheme
/* If a rev was specified, we need to fetch if it's not in the /* If a rev was specified, we need to fetch if it's not in the
repo. */ repo. */
if (auto rev = input.getRev()) { if (rev) {
doFetch = !repo->hasObject(*rev); doFetch = !repo->hasObject(*rev);
} else { } else {
if (getAllRefsAttr(input)) { if (getAllRefsAttr(input)) {
@@ -831,7 +857,6 @@ struct GitInputScheme : InputScheme
} }
if (doFetch) { if (doFetch) {
bool shallow = getShallowAttr(input);
try { try {
auto fetchRef = getAllRefsAttr(input) ? "refs/*:refs/*" auto fetchRef = getAllRefsAttr(input) ? "refs/*:refs/*"
: input.getRev() ? input.getRev()->gitRev() : input.getRev() ? input.getRev()->gitRev()
@@ -859,7 +884,7 @@ struct GitInputScheme : InputScheme
warn("could not update cached head '%s' for '%s'", ref, repoInfo.locationToArg()); warn("could not update cached head '%s' for '%s'", ref, repoInfo.locationToArg());
} }
if (auto rev = input.getRev()) { if (rev) {
if (!repo->hasObject(*rev)) if (!repo->hasObject(*rev))
throw Error( throw Error(
"Cannot find Git revision '%s' in ref '%s' of repository '%s'! " "Cannot find Git revision '%s' in ref '%s' of repository '%s'! "
@@ -876,23 +901,30 @@ struct GitInputScheme : InputScheme
// the remainder // the remainder
} }
have_rev:
auto repo = GitRepo::openRepo(repoDir); auto repo = GitRepo::openRepo(repoDir);
auto isShallow = repo->isShallow();
if (isShallow && !getShallowAttr(input))
throw Error(
"'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified",
repoInfo.locationToArg());
// FIXME: check whether rev is an ancestor of ref? // FIXME: check whether rev is an ancestor of ref?
auto rev = *input.getRev(); auto rev = *input.getRev();
input.attrs.insert_or_assign("lastModified", getLastModified(settings, repoInfo, repoDir, rev)); /* Skip lastModified computation if it's already supplied by the caller.
We don't care if they specify an incorrect value; it doesn't
matter for security, unlike narHash. */
if (!input.attrs.contains("lastModified"))
input.attrs.insert_or_assign("lastModified", getLastModified(settings, repoInfo, repoDir, rev));
/* Like lastModified, skip revCount if supplied by the caller. */
if (!shallow && !input.attrs.contains("revCount")) {
auto isShallow = repo->isShallow();
if (isShallow && !shallow)
throw Error(
"'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified",
repoInfo.locationToArg());
if (!getShallowAttr(input))
input.attrs.insert_or_assign("revCount", getRevCount(settings, repoInfo, repoDir, rev)); input.attrs.insert_or_assign("revCount", getRevCount(settings, repoInfo, repoDir, rev));
}
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg()); printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg());
@@ -900,7 +932,8 @@ struct GitInputScheme : InputScheme
bool exportIgnore = getExportIgnoreAttr(input); bool exportIgnore = getExportIgnoreAttr(input);
bool smudgeLfs = getLfsAttr(input); bool smudgeLfs = getLfsAttr(input);
auto accessor = repo->getAccessor(rev, exportIgnore, "«" + input.to_string() + "»", smudgeLfs); auto accessor = repo->getAccessor(
rev, {.exportIgnore = exportIgnore, .smudgeLfs = smudgeLfs}, "«" + input.to_string() + "»");
/* If the repo has submodules, fetch them and return a mounted /* If the repo has submodules, fetch them and return a mounted
input accessor consisting of the accessor for the top-level input accessor consisting of the accessor for the top-level
@@ -967,7 +1000,7 @@ struct GitInputScheme : InputScheme
auto exportIgnore = getExportIgnoreAttr(input); auto exportIgnore = getExportIgnoreAttr(input);
ref<SourceAccessor> accessor = ref<SourceAccessor> accessor =
repo->getAccessor(repoInfo.workdirInfo, exportIgnore, makeNotAllowedError(repoPath)); repo->getAccessor(repoInfo.workdirInfo, {.exportIgnore = exportIgnore}, makeNotAllowedError(repoPath));
/* If the repo has submodules, return a mounted input accessor /* If the repo has submodules, return a mounted input accessor
consisting of the accessor for the top-level repo and the consisting of the accessor for the top-level repo and the
+1 -1
View File
@@ -351,7 +351,7 @@ struct GitArchiveInputScheme : InputScheme
input.attrs.insert_or_assign("lastModified", uint64_t(tarballInfo.lastModified)); input.attrs.insert_or_assign("lastModified", uint64_t(tarballInfo.lastModified));
auto accessor = auto accessor =
settings.getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»"); settings.getTarballCache()->getAccessor(tarballInfo.treeHash, {}, "«" + input.to_string() + "»");
return {accessor, input}; return {accessor, input};
} }
@@ -135,8 +135,6 @@ struct Settings : public Config
private: private:
mutable Sync<std::shared_ptr<Cache>> _cache; mutable Sync<std::shared_ptr<Cache>> _cache;
mutable Sync<std::shared_ptr<GitRepo>> _tarballCache;
}; };
} // namespace nix::fetchers } // namespace nix::fetchers
@@ -22,6 +22,12 @@ struct GitFileSystemObjectSink : ExtendedFileSystemObjectSink
virtual Hash flush() = 0; virtual Hash flush() = 0;
}; };
struct GitAccessorOptions
{
bool exportIgnore = false;
bool smudgeLfs = false;
};
struct GitRepo struct GitRepo
{ {
virtual ~GitRepo() {} virtual ~GitRepo() {}
@@ -89,10 +95,10 @@ struct GitRepo
virtual bool hasObject(const Hash & oid) = 0; virtual bool hasObject(const Hash & oid) = 0;
virtual ref<SourceAccessor> virtual ref<SourceAccessor>
getAccessor(const Hash & rev, bool exportIgnore, std::string displayPrefix, bool smudgeLfs = false) = 0; getAccessor(const Hash & rev, const GitAccessorOptions & options, std::string displayPrefix) = 0;
virtual ref<SourceAccessor> virtual ref<SourceAccessor> getAccessor(
getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) = 0; const WorkdirInfo & wd, const GitAccessorOptions & options, MakeNotAllowedError makeNotAllowedError) = 0;
virtual ref<GitFileSystemObjectSink> getFileSystemObjectSink() = 0; virtual ref<GitFileSystemObjectSink> getFileSystemObjectSink() = 0;
+1 -1
View File
@@ -136,7 +136,7 @@ static DownloadTarballResult downloadTarball_(
.treeHash = treeHash, .treeHash = treeHash,
.lastModified = (time_t) getIntAttr(infoAttrs, "lastModified"), .lastModified = (time_t) getIntAttr(infoAttrs, "lastModified"),
.immutableUrl = maybeGetStrAttr(infoAttrs, "immutableUrl"), .immutableUrl = maybeGetStrAttr(infoAttrs, "immutableUrl"),
.accessor = settings.getTarballCache()->getAccessor(treeHash, false, displayPrefix), .accessor = settings.getTarballCache()->getAccessor(treeHash, {}, displayPrefix),
}; };
}; };
+1 -1
View File
@@ -200,7 +200,7 @@ nix_value * nix_locked_flake_get_output_attrs(
nix_clear_err(context); nix_clear_err(context);
try { try {
auto v = nix_alloc_value(context, evalState); auto v = nix_alloc_value(context, evalState);
nix::flake::callFlake(evalState->state, *lockedFlake->lockedFlake, v->value); nix::flake::callFlake(evalState->state, *lockedFlake->lockedFlake, *v->value);
return v; return v;
} }
NIXC_CATCH_ERRS_NULL NIXC_CATCH_ERRS_NULL
+1 -4
View File
@@ -10,9 +10,6 @@ lockFileStr:
# unlocked trees. # unlocked trees.
overrides: overrides:
# This is `prim_fetchFinalTree`.
fetchTreeFinal:
let let
inherit (builtins) mapAttrs; inherit (builtins) mapAttrs;
@@ -52,7 +49,7 @@ let
else else
# FIXME: remove obsolete node.info. # FIXME: remove obsolete node.info.
# Note: lock file entries are always final. # Note: lock file entries are always final.
fetchTreeFinal (node.info or { } // removeAttrs node.locked [ "dir" ]); builtins.fetchFinalTree (node.info or { } // removeAttrs node.locked [ "dir" ]);
subdir = overrides.${key}.dir or node.locked.dir or ""; subdir = overrides.${key}.dir or node.locked.dir or "";
+2 -2
View File
@@ -93,7 +93,7 @@ static void prim_parseFlakeRef(EvalState & state, const PosIdx pos, Value ** arg
auto & vv = binds.alloc(s); auto & vv = binds.alloc(s);
std::visit( std::visit(
overloaded{ overloaded{
[&vv](const std::string & value) { vv.mkString(value); }, [&vv, &state](const std::string & value) { vv.mkString(value, state.mem); },
[&vv](const uint64_t & value) { vv.mkInt(value); }, [&vv](const uint64_t & value) { vv.mkInt(value); },
[&vv](const Explicit<bool> & value) { vv.mkBool(value.t); }}, [&vv](const Explicit<bool> & value) { vv.mkBool(value.t); }},
value); value);
@@ -156,7 +156,7 @@ static void prim_flakeRefToString(EvalState & state, const PosIdx pos, Value **
} }
} }
auto flakeRef = FlakeRef::fromAttrs(state.fetchSettings, attrs); auto flakeRef = FlakeRef::fromAttrs(state.fetchSettings, attrs);
v.mkString(flakeRef.to_string()); v.mkString(flakeRef.to_string(), state.mem);
} }
nix::PrimOp flakeRefToString({ nix::PrimOp flakeRefToString({
+3 -6
View File
@@ -956,7 +956,7 @@ void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & vRes)
auto key = keyMap.find(node); auto key = keyMap.find(node);
assert(key != keyMap.end()); assert(key != keyMap.end());
override.alloc(state.symbols.create("dir")).mkString(CanonPath(subdir).rel()); override.alloc(state.symbols.create("dir")).mkString(CanonPath(subdir).rel(), state.mem);
overrides.alloc(state.symbols.create(key->second)).mkAttrs(override); overrides.alloc(state.symbols.create(key->second)).mkAttrs(override);
} }
@@ -966,12 +966,9 @@ void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & vRes)
Value * vCallFlake = requireInternalFile(state, CanonPath("call-flake.nix")); Value * vCallFlake = requireInternalFile(state, CanonPath("call-flake.nix"));
auto vLocks = state.allocValue(); auto vLocks = state.allocValue();
vLocks->mkString(lockFileStr); vLocks->mkString(lockFileStr, state.mem);
auto vFetchFinalTree = get(state.internalPrimOps, "fetchFinalTree"); Value * args[] = {vLocks, &vOverrides};
assert(vFetchFinalTree);
Value * args[] = {vLocks, &vOverrides, *vFetchFinalTree};
state.callFunction(*vCallFlake, args, vRes, noPos); state.callFunction(*vCallFlake, args, vRes, noPos);
} }
+2 -2
View File
@@ -99,7 +99,7 @@ TEST(references, scanForReferencesDeep)
// Create an in-memory file system with various reference patterns // Create an in-memory file system with various reference patterns
auto accessor = make_ref<MemorySourceAccessor>(); auto accessor = make_ref<MemorySourceAccessor>();
accessor->root = File::Directory{ accessor->root = File::Directory{
.contents{ .entries{
{ {
// file1.txt: contains hash1 // file1.txt: contains hash1
"file1.txt", "file1.txt",
@@ -125,7 +125,7 @@ TEST(references, scanForReferencesDeep)
// subdir: a subdirectory // subdir: a subdirectory
"subdir", "subdir",
File::Directory{ File::Directory{
.contents{ .entries{
{ {
// subdir/file4.txt: contains hash1 again // subdir/file4.txt: contains hash1 again
"file4.txt", "file4.txt",
+2 -2
View File
@@ -8,7 +8,7 @@
#include "nix/util/sync.hh" #include "nix/util/sync.hh"
#include "nix/store/remote-fs-accessor.hh" #include "nix/store/remote-fs-accessor.hh"
#include "nix/store/nar-info-disk-cache.hh" #include "nix/store/nar-info-disk-cache.hh"
#include "nix/store/nar-accessor.hh" #include "nix/util/nar-accessor.hh"
#include "nix/util/thread-pool.hh" #include "nix/util/thread-pool.hh"
#include "nix/util/callback.hh" #include "nix/util/callback.hh"
#include "nix/util/signals.hh" #include "nix/util/signals.hh"
@@ -208,7 +208,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
if (config.writeNARListing) { if (config.writeNARListing) {
nlohmann::json j = { nlohmann::json j = {
{"version", 1}, {"version", 1},
{"root", listNar(ref<SourceAccessor>(narAccessor), CanonPath::root, true)}, {"root", listNarDeep(*narAccessor, CanonPath::root)},
}; };
upsertFile(std::string(info.path.hashPart()) + ".ls", j.dump(), "application/json"); upsertFile(std::string(info.path.hashPart()) + ".ls", j.dump(), "application/json");
+90 -28
View File
@@ -48,7 +48,7 @@ struct curlFileTransfer : public FileTransfer
std::random_device rd; std::random_device rd;
std::mt19937 mt19937; std::mt19937 mt19937;
struct TransferItem : public std::enable_shared_from_this<TransferItem> struct TransferItem : public std::enable_shared_from_this<TransferItem>, public FileTransfer::Item
{ {
curlFileTransfer & fileTransfer; curlFileTransfer & fileTransfer;
FileTransferRequest request; FileTransferRequest request;
@@ -60,6 +60,7 @@ struct curlFileTransfer : public FileTransfer
// buffer to accompany the `req` above // buffer to accompany the `req` above
char errbuf[CURL_ERROR_SIZE]; char errbuf[CURL_ERROR_SIZE];
bool active = false; // whether the handle has been added to the multi object bool active = false; // whether the handle has been added to the multi object
bool paused = false; // whether the request has been paused previously
std::string statusMsg; std::string statusMsg;
unsigned int attempt = 0; unsigned int attempt = 0;
@@ -116,7 +117,13 @@ struct curlFileTransfer : public FileTransfer
successful response. */ successful response. */
if (successfulStatuses.count(httpStatus)) { if (successfulStatuses.count(httpStatus)) {
writtenToSink += data.size(); writtenToSink += data.size();
this->request.dataCallback(data); PauseTransfer needsPause = this->request.dataCallback(data);
if (needsPause == PauseTransfer::Yes) {
/* Smuggle the boolean flag into writeCallback. Note that
the finalSink might get called multiple times if there's
decompression going on. */
paused = true;
}
} }
} else } else
this->result.data.append(data); this->result.data.append(data);
@@ -195,6 +202,14 @@ struct curlFileTransfer : public FileTransfer
} }
(*decompressionSink)({(char *) contents, realSize}); (*decompressionSink)({(char *) contents, realSize});
if (paused) {
/* The callback has signaled that the transfer needs to be
paused. Already consumed data won't be returned twice unlike
when returning CURL_WRITEFUNC_PAUSE.
https://curl-library.cool.haxx.narkive.com/larE1cRA/curl-easy-pause-documentation-question
*/
curl_easy_pause(req, CURLPAUSE_RECV);
}
return realSize; return realSize;
} catch (...) { } catch (...) {
@@ -364,6 +379,15 @@ struct curlFileTransfer : public FileTransfer
return ((TransferItem *) clientp)->seekCallback(offset, origin); return ((TransferItem *) clientp)->seekCallback(offset, origin);
} }
void unpause()
{
/* Unpausing an already unpaused transfer is a no-op. */
if (paused) {
curl_easy_pause(req, CURLPAUSE_CONT);
paused = false;
}
}
void init() void init()
{ {
if (!req) if (!req)
@@ -624,7 +648,7 @@ struct curlFileTransfer : public FileTransfer
errorSink.reset(); errorSink.reset();
embargo = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms); embargo = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
try { try {
fileTransfer.enqueueItem(shared_from_this()); fileTransfer.enqueueItem(ref{shared_from_this()});
} catch (const nix::Error & e) { } catch (const nix::Error & e) {
// If enqueue fails (e.g., during shutdown), fail the transfer properly // If enqueue fails (e.g., during shutdown), fail the transfer properly
// instead of letting the exception propagate, which would leave done=false // instead of letting the exception propagate, which would leave done=false
@@ -641,24 +665,24 @@ struct curlFileTransfer : public FileTransfer
{ {
struct EmbargoComparator struct EmbargoComparator
{ {
bool operator()(const std::shared_ptr<TransferItem> & i1, const std::shared_ptr<TransferItem> & i2) bool operator()(const ref<TransferItem> & i1, const ref<TransferItem> & i2)
{ {
return i1->embargo > i2->embargo; return i1->embargo > i2->embargo;
} }
}; };
std:: std::priority_queue<ref<TransferItem>, std::vector<ref<TransferItem>>, EmbargoComparator> incoming;
priority_queue<std::shared_ptr<TransferItem>, std::vector<std::shared_ptr<TransferItem>>, EmbargoComparator> std::vector<ref<TransferItem>> unpause;
incoming;
private: private:
bool quitting = false; bool quitting = false;
public: public:
void quit() void quit()
{ {
quitting = true; quitting = true;
/* We wil not be processing any more incoming requests */ /* We will not be processing any more incoming requests */
while (!incoming.empty()) while (!incoming.empty())
incoming.pop(); incoming.pop();
unpause.clear();
} }
bool isQuitting() bool isQuitting()
@@ -827,6 +851,17 @@ struct curlFileTransfer : public FileTransfer
item->active = true; item->active = true;
items[item->req] = item; items[item->req] = item;
} }
/* NOTE: Unpausing may invoke callbacks to flush all buffers. */
auto unpause = [&]() {
auto state(state_.lock());
auto res = state->unpause;
state->unpause.clear();
return res;
}();
for (auto & item : unpause)
item->unpause();
} }
debug("download thread shutting down"); debug("download thread shutting down");
@@ -851,7 +886,7 @@ struct curlFileTransfer : public FileTransfer
} }
} }
void enqueueItem(std::shared_ptr<TransferItem> item) ItemHandle enqueueItem(ref<TransferItem> item)
{ {
if (item->request.data && item->request.uri.scheme() != "http" && item->request.uri.scheme() != "https" if (item->request.data && item->request.uri.scheme() != "http" && item->request.uri.scheme() != "https"
&& item->request.uri.scheme() != "s3") && item->request.uri.scheme() != "s3")
@@ -866,19 +901,34 @@ struct curlFileTransfer : public FileTransfer
#ifndef _WIN32 // TODO need graceful async exit support on Windows? #ifndef _WIN32 // TODO need graceful async exit support on Windows?
writeFull(wakeupPipe.writeSide.get(), " "); writeFull(wakeupPipe.writeSide.get(), " ");
#endif #endif
return ItemHandle(static_cast<Item &>(*item));
} }
void enqueueFileTransfer(const FileTransferRequest & request, Callback<FileTransferResult> callback) override ItemHandle enqueueFileTransfer(const FileTransferRequest & request, Callback<FileTransferResult> callback) override
{ {
/* Handle s3:// URIs by converting to HTTPS and optionally adding auth */ /* Handle s3:// URIs by converting to HTTPS and optionally adding auth */
if (request.uri.scheme() == "s3") { if (request.uri.scheme() == "s3") {
auto modifiedRequest = request; auto modifiedRequest = request;
modifiedRequest.setupForS3(); modifiedRequest.setupForS3();
enqueueItem(std::make_shared<TransferItem>(*this, std::move(modifiedRequest), std::move(callback))); return enqueueItem(make_ref<TransferItem>(*this, std::move(modifiedRequest), std::move(callback)));
return;
} }
enqueueItem(std::make_shared<TransferItem>(*this, request, std::move(callback))); return enqueueItem(make_ref<TransferItem>(*this, request, std::move(callback)));
}
void unpauseTransfer(ref<TransferItem> item)
{
auto state(state_.lock());
state->unpause.push_back(std::move(item));
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
writeFull(wakeupPipe.writeSide.get(), " ");
#endif
}
void unpauseTransfer(ItemHandle handle) override
{
unpauseTransfer(ref{static_cast<TransferItem &>(handle.item.get()).shared_from_this()});
} }
}; };
@@ -975,6 +1025,7 @@ void FileTransfer::download(
struct State struct State
{ {
bool quit = false; bool quit = false;
bool paused = false;
std::exception_ptr exc; std::exception_ptr exc;
std::string data; std::string data;
std::condition_variable avail, request; std::condition_variable avail, request;
@@ -990,31 +1041,38 @@ void FileTransfer::download(
state->request.notify_one(); state->request.notify_one();
}); });
request.dataCallback = [_state](std::string_view data) { request.dataCallback = [_state, uri = request.uri.to_string()](std::string_view data) -> PauseTransfer {
auto state(_state->lock()); auto state(_state->lock());
if (state->quit) if (state->quit)
return; return PauseTransfer::No;
/* If the buffer is full, then go to sleep until the calling
thread wakes us up (i.e. when it has removed data from the
buffer). We don't wait forever to prevent stalling the
download thread. (Hopefully sleeping will throttle the
sender.) */
if (state->data.size() > fileTransferSettings.downloadBufferSize) {
debug("download buffer is full; going to sleep");
static bool haveWarned = false;
warnOnce(haveWarned, "download buffer is full; consider increasing the 'download-buffer-size' setting");
state.wait_for(state->request, std::chrono::seconds(10));
}
/* Append data to the buffer and wake up the calling /* Append data to the buffer and wake up the calling
thread. */ thread. */
state->data.append(data); state->data.append(data);
state->avail.notify_one(); state->avail.notify_one();
if (state->data.size() <= fileTransferSettings.downloadBufferSize)
return PauseTransfer::No;
/* dataCallback gets called multiple times by an intermediate sink. Only
issue the debug message the first time around. */
if (!state->paused)
debug(
"pausing transfer for '%s': download buffer is full (%d > %d)",
uri,
state->data.size(),
fileTransferSettings.downloadBufferSize);
state->paused = true;
/* Technically the buffer might become larger than
downloadBufferSize, but with sinks there's no way to avoid
consuming data. */
return PauseTransfer::Yes;
}; };
enqueueFileTransfer( auto handle = enqueueFileTransfer(
request, {[_state, resultCallback{std::move(resultCallback)}](std::future<FileTransferResult> fut) { request, {[_state, resultCallback{std::move(resultCallback)}](std::future<FileTransferResult> fut) {
auto state(_state->lock()); auto state(_state->lock());
state->quit = true; state->quit = true;
@@ -1047,6 +1105,10 @@ void FileTransfer::download(
return; return;
} }
if (state->paused) {
unpauseTransfer(handle);
state->paused = false;
}
state.wait(state->avail); state.wait(state->avail);
if (state->data.empty()) if (state->data.empty())
+41 -4
View File
@@ -70,12 +70,12 @@ struct FileTransferSettings : Config
Setting<size_t> downloadBufferSize{ Setting<size_t> downloadBufferSize{
this, this,
64 * 1024 * 1024, 1 * 1024 * 1024,
"download-buffer-size", "download-buffer-size",
R"( R"(
The size of Nix's internal download buffer in bytes during `curl` transfers. If data is The size of Nix's internal download buffer in bytes during `curl` transfers. If data is
not processed quickly enough to exceed the size of this buffer, downloads may stall. not processed quickly enough to exceed the size of this buffer, downloads may stall.
The default is 67108864 (64 MiB). The default is 1048576 (1 MiB).
)"}; )"};
}; };
@@ -105,6 +105,11 @@ struct UsernameAuth
std::optional<std::string> password; std::optional<std::string> password;
}; };
enum class PauseTransfer : bool {
No = false,
Yes = true,
};
struct FileTransferRequest struct FileTransferRequest
{ {
VerbatimURL uri; VerbatimURL uri;
@@ -136,7 +141,14 @@ struct FileTransferRequest
std::optional<UploadData> data; std::optional<UploadData> data;
std::string mimeType; std::string mimeType;
std::function<void(std::string_view data)> dataCallback;
/**
* Callbacked invoked with a chunk of received data.
* Can pause the transfer by returning PauseTransfer::Yes. No data must be consumed
* if transfer is paused.
*/
std::function<PauseTransfer(std::string_view data)> dataCallback;
/** /**
* Optional username and password for HTTP basic authentication. * Optional username and password for HTTP basic authentication.
* When provided, these credentials will be used with curl's CURLOPT_USERNAME/PASSWORD option. * When provided, these credentials will be used with curl's CURLOPT_USERNAME/PASSWORD option.
@@ -226,6 +238,25 @@ class Store;
struct FileTransfer struct FileTransfer
{ {
protected:
class Item
{};
public:
/**
* An opaque handle to the file transfer. Can be used to reference an in-flight transfer operations.
*/
struct ItemHandle
{
std::reference_wrapper<Item> item;
friend struct FileTransfer;
ItemHandle(Item & item)
: item(item)
{
}
};
virtual ~FileTransfer() {} virtual ~FileTransfer() {}
/** /**
@@ -233,7 +264,13 @@ struct FileTransfer
* the download. The future may throw a FileTransferError * the download. The future may throw a FileTransferError
* exception. * exception.
*/ */
virtual void enqueueFileTransfer(const FileTransferRequest & request, Callback<FileTransferResult> callback) = 0; virtual ItemHandle
enqueueFileTransfer(const FileTransferRequest & request, Callback<FileTransferResult> callback) = 0;
/**
* Unpause a transfer that has been previously paused by a dataCallback.
*/
virtual void unpauseTransfer(ItemHandle handle) = 0;
std::future<FileTransferResult> enqueueFileTransfer(const FileTransferRequest & request); std::future<FileTransferResult> enqueueFileTransfer(const FileTransferRequest & request);
@@ -55,7 +55,6 @@ headers = [ config_pub_h ] + files(
'machines.hh', 'machines.hh',
'make-content-addressed.hh', 'make-content-addressed.hh',
'names.hh', 'names.hh',
'nar-accessor.hh',
'nar-info-disk-cache.hh', 'nar-info-disk-cache.hh',
'nar-info.hh', 'nar-info.hh',
'outputs-spec.hh', 'outputs-spec.hh',
@@ -1,43 +0,0 @@
#pragma once
///@file
#include "nix/util/source-accessor.hh"
#include <functional>
#include <nlohmann/json_fwd.hpp>
namespace nix {
struct Source;
/**
* Return an object that provides access to the contents of a NAR
* file.
*/
ref<SourceAccessor> makeNarAccessor(std::string && nar);
ref<SourceAccessor> makeNarAccessor(Source & source);
/**
* Create a NAR accessor from a NAR listing (in the format produced by
* listNar()). The callback getNarBytes(offset, length) is used by the
* readFile() method of the accessor to get the contents of files
* inside the NAR.
*/
using GetNarBytes = std::function<std::string(uint64_t, uint64_t)>;
/**
* The canonical GetNarBytes function for a seekable Source.
*/
GetNarBytes seekableGetNarBytes(const Path & path);
ref<SourceAccessor> makeLazyNarAccessor(const nlohmann::json & listing, GetNarBytes getNarBytes);
/**
* Write a JSON representation of the contents of a NAR (except file
* contents).
*/
nlohmann::json listNar(ref<SourceAccessor> accessor, const CanonPath & path, bool recurse);
} // namespace nix
+11 -1
View File
@@ -114,6 +114,17 @@ boost = dependency(
deps_other += boost deps_other += boost
curl = dependency('libcurl', 'curl', version : '>= 7.75.0') curl = dependency('libcurl', 'curl', version : '>= 7.75.0')
if curl.version().version_compare('>=8.16.0') and curl.version().version_compare(
'<8.17.0',
)
# Out of precaution, avoid building with libcurl version that suffer from https://github.com/curl/curl/issues/19334.
error(
'curl @0@ has issues with write pausing, please use libcurl < 8.16 or >= 8.17, see https://github.com/curl/curl/issues/19334'.format(
curl.version(),
),
)
endif
deps_private += curl deps_private += curl
# seccomp only makes sense on Linux # seccomp only makes sense on Linux
@@ -300,7 +311,6 @@ sources = files(
'make-content-addressed.cc', 'make-content-addressed.cc',
'misc.cc', 'misc.cc',
'names.cc', 'names.cc',
'nar-accessor.cc',
'nar-info-disk-cache.cc', 'nar-info-disk-cache.cc',
'nar-info.cc', 'nar-info.cc',
'optimise-store.cc', 'optimise-store.cc',
+2 -2
View File
@@ -1,6 +1,6 @@
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
#include "nix/store/remote-fs-accessor.hh" #include "nix/store/remote-fs-accessor.hh"
#include "nix/store/nar-accessor.hh" #include "nix/util/nar-accessor.hh"
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@@ -39,7 +39,7 @@ ref<SourceAccessor> RemoteFSAccessor::addToCache(std::string_view hashPart, std:
if (cacheDir != "") { if (cacheDir != "") {
try { try {
nlohmann::json j = listNar(narAccessor, CanonPath::root, true); nlohmann::json j = listNarDeep(*narAccessor, CanonPath::root);
writeFile(makeCacheFile(hashPart, "ls"), j.dump()); writeFile(makeCacheFile(hashPart, "ls"), j.dump());
} catch (...) { } catch (...) {
ignoreExceptionExceptInterrupt(); ignoreExceptionExceptInterrupt();
@@ -0,0 +1,24 @@
{
"entries": {
"bar": {
"entries": {
"baz": {
"contents": "good day,\n\u0000\n\tworld!",
"executable": true,
"type": "regular"
},
"quux": {
"target": "/over/there",
"type": "symlink"
}
},
"type": "directory"
},
"foo": {
"contents": "hello\n\u0000\n\tworld!",
"executable": false,
"type": "regular"
}
},
"type": "directory"
}
@@ -0,0 +1,5 @@
{
"contents": "asdf",
"executable": false,
"type": "regular"
}
@@ -0,0 +1,23 @@
{
"entries": {
"bar": {
"entries": {
"baz": {
"executable": true,
"size": 19,
"type": "regular"
},
"quux": {
"target": "/over/there",
"type": "symlink"
}
},
"type": "directory"
},
"foo": {
"size": 15,
"type": "regular"
}
},
"type": "directory"
}
@@ -0,0 +1,7 @@
{
"entries": {
"bar": {},
"foo": {}
},
"type": "directory"
}
+7 -34
View File
@@ -224,42 +224,15 @@ TEST_F(GitTest, tree_sha256_write)
}); });
} }
namespace memory_source_accessor {
extern ref<MemorySourceAccessor> exampleComplex();
}
TEST_F(GitTest, both_roundrip) TEST_F(GitTest, both_roundrip)
{ {
using File = MemorySourceAccessor::File; auto files = memory_source_accessor::exampleComplex();
auto files = make_ref<MemorySourceAccessor>();
files->root = File::Directory{
.contents{
{
"foo",
File::Regular{
.contents = "hello\n\0\n\tworld!",
},
},
{
"bar",
File::Directory{
.contents =
{
{
"baz",
File::Regular{
.executable = true,
.contents = "good day,\n\0\n\tworld!",
},
},
{
"quux",
File::Symlink{
.target = "/over/there",
},
},
},
},
},
},
};
for (const auto hashAlgo : {HashAlgorithm::SHA1, HashAlgorithm::SHA256}) { for (const auto hashAlgo : {HashAlgorithm::SHA1, HashAlgorithm::SHA256}) {
std::map<Hash, std::string> cas; std::map<Hash, std::string> cas;
+116
View File
@@ -0,0 +1,116 @@
#include <string_view>
#include "nix/util/memory-source-accessor.hh"
#include "nix/util/tests/json-characterization.hh"
namespace nix {
namespace memory_source_accessor {
using namespace std::literals;
using File = MemorySourceAccessor::File;
ref<MemorySourceAccessor> exampleSimple()
{
auto sc = make_ref<MemorySourceAccessor>();
sc->root = File{File::Regular{
.executable = false,
.contents = "asdf",
}};
return sc;
}
ref<MemorySourceAccessor> exampleComplex()
{
auto files = make_ref<MemorySourceAccessor>();
files->root = File::Directory{
.entries{
{
"foo",
File::Regular{
.contents = "hello\n\0\n\tworld!"s,
},
},
{
"bar",
File::Directory{
.entries =
{
{
"baz",
File::Regular{
.executable = true,
.contents = "good day,\n\0\n\tworld!"s,
},
},
{
"quux",
File::Symlink{
.target = "/over/there",
},
},
},
},
},
},
};
return files;
}
} // namespace memory_source_accessor
/* ----------------------------------------------------------------------------
* JSON
* --------------------------------------------------------------------------*/
class MemorySourceAccessorTest : public virtual CharacterizationTest
{
std::filesystem::path unitTestData = getUnitTestData() / "memory-source-accessor";
public:
std::filesystem::path goldenMaster(std::string_view testStem) const override
{
return unitTestData / testStem;
}
};
using nlohmann::json;
struct MemorySourceAccessorJsonTest : MemorySourceAccessorTest,
JsonCharacterizationTest<MemorySourceAccessor>,
::testing::WithParamInterface<std::pair<std::string_view, MemorySourceAccessor>>
{};
TEST_P(MemorySourceAccessorJsonTest, from_json)
{
auto & [name, expected] = GetParam();
/* Cannot use `readJsonTest` because need to compare `root` field of
the source accessors for equality. */
readTest(Path{name} + ".json", [&](const auto & encodedRaw) {
auto encoded = json::parse(encodedRaw);
auto decoded = static_cast<MemorySourceAccessor>(encoded);
ASSERT_EQ(decoded.root, expected.root);
});
}
TEST_P(MemorySourceAccessorJsonTest, to_json)
{
auto & [name, value] = GetParam();
writeJsonTest(name, value);
}
INSTANTIATE_TEST_SUITE_P(
MemorySourceAccessorJSON,
MemorySourceAccessorJsonTest,
::testing::Values(
std::pair{
"simple",
*memory_source_accessor::exampleSimple(),
},
std::pair{
"complex",
*memory_source_accessor::exampleComplex(),
}));
} // namespace nix
+2
View File
@@ -63,7 +63,9 @@ sources = files(
'json-utils.cc', 'json-utils.cc',
'logging.cc', 'logging.cc',
'lru-cache.cc', 'lru-cache.cc',
'memory-source-accessor.cc',
'monitorfdhup.cc', 'monitorfdhup.cc',
'nar-listing.cc',
'nix_api_util.cc', 'nix_api_util.cc',
'nix_api_util_internal.cc', 'nix_api_util_internal.cc',
'pool.cc', 'pool.cc',
+83
View File
@@ -0,0 +1,83 @@
#include <string_view>
#include "nix/util/nar-accessor.hh"
#include "nix/util/tests/json-characterization.hh"
namespace nix {
// Forward declaration from memory-source-accessor.cc
namespace memory_source_accessor {
ref<MemorySourceAccessor> exampleComplex();
}
/* ----------------------------------------------------------------------------
* JSON
* --------------------------------------------------------------------------*/
class NarListingTest : public virtual CharacterizationTest
{
std::filesystem::path unitTestData = getUnitTestData() / "nar-listing";
public:
std::filesystem::path goldenMaster(std::string_view testStem) const override
{
return unitTestData / testStem;
}
};
using nlohmann::json;
struct NarListingJsonTest : NarListingTest,
JsonCharacterizationTest<NarListing>,
::testing::WithParamInterface<std::pair<std::string_view, NarListing>>
{};
TEST_P(NarListingJsonTest, from_json)
{
auto & [name, expected] = GetParam();
readJsonTest(name, expected);
}
TEST_P(NarListingJsonTest, to_json)
{
auto & [name, value] = GetParam();
writeJsonTest(name, value);
}
INSTANTIATE_TEST_SUITE_P(
NarListingJSON,
NarListingJsonTest,
::testing::Values(
std::pair{
"deep",
listNarDeep(*memory_source_accessor::exampleComplex(), CanonPath::root),
}));
struct ShallowNarListingJsonTest : NarListingTest,
JsonCharacterizationTest<ShallowNarListing>,
::testing::WithParamInterface<std::pair<std::string_view, ShallowNarListing>>
{};
TEST_P(ShallowNarListingJsonTest, from_json)
{
auto & [name, expected] = GetParam();
readJsonTest(name, expected);
}
TEST_P(ShallowNarListingJsonTest, to_json)
{
auto & [name, value] = GetParam();
writeJsonTest(name, value);
}
INSTANTIATE_TEST_SUITE_P(
ShallowNarListingJSON,
ShallowNarListingJsonTest,
::testing::Values(
std::pair{
"shallow",
listNarShallow(*memory_source_accessor::exampleComplex(), CanonPath::root),
}));
} // namespace nix
+5
View File
@@ -134,6 +134,11 @@ std::optional<Path> getSelfExe()
return std::nullopt; return std::nullopt;
} }
// FreeBSD's sysctl(KERN_PROC_PATHNAME) includes the null terminator in
// pathLen. Strip it to prevent Nix evaluation errors when the path is
// serialized to JSON and evaluated as a Nix string.
path.pop_back();
return Path(path.begin(), path.end()); return Path(path.begin(), path.end());
#else #else
return std::nullopt; return std::nullopt;
-1
View File
@@ -37,7 +37,6 @@ void copyRecursive(SourceAccessor & accessor, const CanonPath & from, FileSystem
sink.createDirectory(to, [&](FileSystemObjectSink & dirSink, const CanonPath & relDirPath) { sink.createDirectory(to, [&](FileSystemObjectSink & dirSink, const CanonPath & relDirPath) {
for (auto & [name, _] : accessor.readDirectory(from)) { for (auto & [name, _] : accessor.readDirectory(from)) {
copyRecursive(accessor, from / name, dirSink, relDirPath / name); copyRecursive(accessor, from / name, dirSink, relDirPath / name);
break;
} }
}); });
break; break;
+8 -5
View File
@@ -6,15 +6,18 @@
#include "nix/util/experimental-features.hh" #include "nix/util/experimental-features.hh"
// Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types // Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
#define JSON_IMPL(TYPE) \ #define JSON_IMPL_INNER(TYPE) \
namespace nlohmann { \
using namespace nix; \
template<> \
struct adl_serializer<TYPE> \ struct adl_serializer<TYPE> \
{ \ { \
static TYPE from_json(const json & json); \ static TYPE from_json(const json & json); \
static void to_json(json & json, const TYPE & t); \ static void to_json(json & json, const TYPE & t); \
}; \ };
#define JSON_IMPL(TYPE) \
namespace nlohmann { \
using namespace nix; \
template<> \
JSON_IMPL_INNER(TYPE) \
} }
#define JSON_IMPL_WITH_XP_FEATURES(TYPE) \ #define JSON_IMPL_WITH_XP_FEATURES(TYPE) \
@@ -4,59 +4,111 @@
#include "nix/util/source-path.hh" #include "nix/util/source-path.hh"
#include "nix/util/fs-sink.hh" #include "nix/util/fs-sink.hh"
#include "nix/util/variant-wrapper.hh" #include "nix/util/variant-wrapper.hh"
#include "nix/util/json-impls.hh"
namespace nix { namespace nix {
/**
* File System Object definitions
*
* @see https://nix.dev/manual/nix/latest/store/file-system-object.html
*/
namespace fso {
template<typename RegularContents>
struct Regular
{
bool executable = false;
RegularContents contents;
auto operator<=>(const Regular &) const = default;
};
/**
* Child parameter because sometimes we want "shallow" directories without
* full file children.
*/
template<typename Child>
struct DirectoryT
{
using Name = std::string;
std::map<Name, Child, std::less<>> entries;
inline bool operator==(const DirectoryT &) const noexcept;
inline std::strong_ordering operator<=>(const DirectoryT &) const noexcept;
};
struct Symlink
{
std::string target;
auto operator<=>(const Symlink &) const = default;
};
/**
* For when we know there is child, but don't know anything about it.
*
* This is not part of the core File System Object data model --- this
* represents not knowing, not an additional type of file.
*/
struct Opaque
{
auto operator<=>(const Opaque &) const = default;
};
/**
* `File<std::string>` nicely defining what a "file system object"
* is in Nix.
*
* With a different type arugment, it is also can be a "skeletal"
* version is that abstract syntax for a "NAR listing".
*/
template<typename RegularContents, bool recur>
struct VariantT
{
bool operator==(const VariantT &) const noexcept;
std::strong_ordering operator<=>(const VariantT &) const noexcept;
using Regular = nix::fso::Regular<RegularContents>;
/**
* In the default case, we do want full file children for our directory.
*/
using Directory = nix::fso::DirectoryT<std::conditional_t<recur, VariantT, Opaque>>;
using Symlink = nix::fso::Symlink;
using Raw = std::variant<Regular, Directory, Symlink>;
Raw raw;
MAKE_WRAPPER_CONSTRUCTOR(VariantT);
SourceAccessor::Stat lstat() const;
};
template<typename Child>
inline bool DirectoryT<Child>::operator==(const DirectoryT &) const noexcept = default;
template<typename Child>
inline std::strong_ordering DirectoryT<Child>::operator<=>(const DirectoryT &) const noexcept = default;
template<typename RegularContents, bool recur>
inline bool
VariantT<RegularContents, recur>::operator==(const VariantT<RegularContents, recur> &) const noexcept = default;
template<typename RegularContents, bool recur>
inline std::strong_ordering
VariantT<RegularContents, recur>::operator<=>(const VariantT<RegularContents, recur> &) const noexcept = default;
} // namespace fso
/** /**
* An source accessor for an in-memory file system. * An source accessor for an in-memory file system.
*/ */
struct MemorySourceAccessor : virtual SourceAccessor struct MemorySourceAccessor : virtual SourceAccessor
{ {
/** using File = fso::VariantT<std::string, true>;
* In addition to being part of the implementation of
* `MemorySourceAccessor`, this has a side benefit of nicely
* defining what a "file system object" is in Nix.
*/
struct File
{
bool operator==(const File &) const noexcept;
std::strong_ordering operator<=>(const File &) const noexcept;
struct Regular
{
bool executable = false;
std::string contents;
bool operator==(const Regular &) const = default;
auto operator<=>(const Regular &) const = default;
};
struct Directory
{
using Name = std::string;
std::map<Name, File, std::less<>> contents;
bool operator==(const Directory &) const noexcept;
// TODO libc++ 16 (used by darwin) missing `std::map::operator <=>`, can't do yet.
bool operator<(const Directory &) const noexcept;
};
struct Symlink
{
std::string target;
bool operator==(const Symlink &) const = default;
auto operator<=>(const Symlink &) const = default;
};
using Raw = std::variant<Regular, Directory, Symlink>;
Raw raw;
MAKE_WRAPPER_CONSTRUCTOR(File);
Stat lstat() const;
};
std::optional<File> root; std::optional<File> root;
@@ -89,19 +141,6 @@ struct MemorySourceAccessor : virtual SourceAccessor
SourcePath addFile(CanonPath path, std::string && contents); SourcePath addFile(CanonPath path, std::string && contents);
}; };
inline bool MemorySourceAccessor::File::Directory::operator==(
const MemorySourceAccessor::File::Directory &) const noexcept = default;
inline bool
MemorySourceAccessor::File::Directory::operator<(const MemorySourceAccessor::File::Directory & other) const noexcept
{
return contents < other.contents;
}
inline bool MemorySourceAccessor::File::operator==(const MemorySourceAccessor::File &) const noexcept = default;
inline std::strong_ordering
MemorySourceAccessor::File::operator<=>(const MemorySourceAccessor::File &) const noexcept = default;
/** /**
* Write to a `MemorySourceAccessor` at the given path * Write to a `MemorySourceAccessor` at the given path
*/ */
@@ -121,4 +160,53 @@ struct MemorySink : FileSystemObjectSink
void createSymlink(const CanonPath & path, const std::string & target) override; void createSymlink(const CanonPath & path, const std::string & target) override;
}; };
template<>
struct json_avoids_null<MemorySourceAccessor::File::Regular> : std::true_type
{};
template<>
struct json_avoids_null<MemorySourceAccessor::File::Directory> : std::true_type
{};
template<>
struct json_avoids_null<MemorySourceAccessor::File::Symlink> : std::true_type
{};
template<>
struct json_avoids_null<MemorySourceAccessor::File> : std::true_type
{};
template<>
struct json_avoids_null<MemorySourceAccessor> : std::true_type
{};
} // namespace nix } // namespace nix
namespace nlohmann {
using namespace nix;
#define ARG fso::Regular<RegularContents>
template<typename RegularContents>
JSON_IMPL_INNER(ARG)
#undef ARG
#define ARG fso::DirectoryT<Child>
template<typename Child>
JSON_IMPL_INNER(ARG)
#undef ARG
template<>
JSON_IMPL_INNER(fso::Symlink)
template<>
JSON_IMPL_INNER(fso::Opaque)
#define ARG fso::VariantT<RegularContents, recur>
template<typename RegularContents, bool recur>
JSON_IMPL_INNER(ARG)
#undef ARG
} // namespace nlohmann
JSON_IMPL(MemorySourceAccessor)
+1
View File
@@ -50,6 +50,7 @@ headers = files(
'memory-source-accessor.hh', 'memory-source-accessor.hh',
'mounted-source-accessor.hh', 'mounted-source-accessor.hh',
'muxable-pipe.hh', 'muxable-pipe.hh',
'nar-accessor.hh',
'os-string.hh', 'os-string.hh',
'pool.hh', 'pool.hh',
'pos-idx.hh', 'pos-idx.hh',
@@ -0,0 +1,80 @@
#pragma once
///@file
#include "nix/util/memory-source-accessor.hh"
#include <functional>
#include <nlohmann/json_fwd.hpp>
namespace nix {
struct Source;
/**
* Return an object that provides access to the contents of a NAR
* file.
*/
ref<SourceAccessor> makeNarAccessor(std::string && nar);
ref<SourceAccessor> makeNarAccessor(Source & source);
/**
* Create a NAR accessor from a NAR listing (in the format produced by
* listNar()). The callback getNarBytes(offset, length) is used by the
* readFile() method of the accessor to get the contents of files
* inside the NAR.
*/
using GetNarBytes = std::function<std::string(uint64_t, uint64_t)>;
/**
* The canonical GetNarBytes function for a seekable Source.
*/
GetNarBytes seekableGetNarBytes(const Path & path);
ref<SourceAccessor> makeLazyNarAccessor(const nlohmann::json & listing, GetNarBytes getNarBytes);
struct NarListingRegularFile
{
/**
* @see `SourceAccessor::Stat::fileSize`
*/
std::optional<uint64_t> fileSize;
/**
* @see `SourceAccessor::Stat::narOffset`
*
* We only set to non-`std::nullopt` if it is also non-zero.
*/
std::optional<uint64_t> narOffset;
auto operator<=>(const NarListingRegularFile &) const = default;
};
/**
* Abstract syntax for a "NAR listing".
*/
using NarListing = fso::VariantT<NarListingRegularFile, true>;
/**
* Shallow NAR listing where directory children are not recursively expanded.
* Uses a variant that can hold Regular/Symlink fully, but Directory children
* are just unit types indicating presence without content.
*/
using ShallowNarListing = fso::VariantT<NarListingRegularFile, false>;
/**
* Return a deep structured representation of the contents of a NAR (except file
* contents), recursively listing all children.
*/
NarListing listNarDeep(SourceAccessor & accessor, const CanonPath & path);
/**
* Return a shallow structured representation of the contents of a NAR (except file
* contents), only listing immediate children without recursing.
*/
ShallowNarListing listNarShallow(SourceAccessor & accessor, const CanonPath & path);
// All json_avoids_null and JSON_IMPL covered by generic templates in memory-source-accessor.hh
} // namespace nix
+13 -11
View File
@@ -1,4 +1,5 @@
#include "nix/util/memory-source-accessor.hh" #include "nix/util/memory-source-accessor.hh"
#include "nix/util/json-utils.hh"
namespace nix { namespace nix {
@@ -29,13 +30,13 @@ MemorySourceAccessor::File * MemorySourceAccessor::open(const CanonPath & path,
return nullptr; return nullptr;
auto & curDir = *curDirP; auto & curDir = *curDirP;
auto i = curDir.contents.find(name); auto i = curDir.entries.find(name);
if (i == curDir.contents.end()) { if (i == curDir.entries.end()) {
if (!create) if (!create)
return nullptr; return nullptr;
else { else {
newF = true; newF = true;
i = curDir.contents.insert( i = curDir.entries.insert(
i, i,
{ {
std::string{name}, std::string{name},
@@ -68,25 +69,26 @@ bool MemorySourceAccessor::pathExists(const CanonPath & path)
return open(path, std::nullopt); return open(path, std::nullopt);
} }
MemorySourceAccessor::Stat MemorySourceAccessor::File::lstat() const template<>
SourceAccessor::Stat MemorySourceAccessor::File::lstat() const
{ {
return std::visit( return std::visit(
overloaded{ overloaded{
[](const Regular & r) { [](const Regular & r) {
return Stat{ return SourceAccessor::Stat{
.type = tRegular, .type = SourceAccessor::tRegular,
.fileSize = r.contents.size(), .fileSize = r.contents.size(),
.isExecutable = r.executable, .isExecutable = r.executable,
}; };
}, },
[](const Directory &) { [](const Directory &) {
return Stat{ return SourceAccessor::Stat{
.type = tDirectory, .type = SourceAccessor::tDirectory,
}; };
}, },
[](const Symlink &) { [](const Symlink &) {
return Stat{ return SourceAccessor::Stat{
.type = tSymlink, .type = SourceAccessor::tSymlink,
}; };
}, },
}, },
@@ -106,7 +108,7 @@ MemorySourceAccessor::DirEntries MemorySourceAccessor::readDirectory(const Canon
throw Error("file '%s' does not exist", path); throw Error("file '%s' does not exist", path);
if (auto * d = std::get_if<File::Directory>(&f->raw)) { if (auto * d = std::get_if<File::Directory>(&f->raw)) {
DirEntries res; DirEntries res;
for (auto & [name, file] : d->contents) for (auto & [name, file] : d->entries)
res.insert_or_assign(name, file.lstat().type); res.insert_or_assign(name, file.lstat().type);
return res; return res;
} else } else
+162
View File
@@ -0,0 +1,162 @@
#include "nix/util/memory-source-accessor.hh"
#include "nix/util/nar-accessor.hh"
#include "nix/util/json-utils.hh"
#include <nlohmann/json.hpp>
namespace nlohmann {
using namespace nix;
// fso::Regular<RegularContents>
template<>
MemorySourceAccessor::File::Regular adl_serializer<MemorySourceAccessor::File::Regular>::from_json(const json & json)
{
auto & obj = getObject(json);
return MemorySourceAccessor::File::Regular{
.executable = getBoolean(valueAt(obj, "executable")),
.contents = getString(valueAt(obj, "contents")),
};
}
template<>
void adl_serializer<MemorySourceAccessor::File::Regular>::to_json(
json & json, const MemorySourceAccessor::File::Regular & r)
{
json = {
{"executable", r.executable},
{"contents", r.contents},
};
}
template<>
NarListing::Regular adl_serializer<NarListing::Regular>::from_json(const json & json)
{
auto & obj = getObject(json);
auto * execPtr = optionalValueAt(obj, "executable");
auto * sizePtr = optionalValueAt(obj, "size");
auto * offsetPtr = optionalValueAt(obj, "narOffset");
return NarListing::Regular{
.executable = execPtr ? getBoolean(*execPtr) : false,
.contents{
.fileSize = ptrToOwned<uint64_t>(sizePtr),
.narOffset = ptrToOwned<uint64_t>(offsetPtr).and_then(
[](auto v) { return v != 0 ? std::optional{v} : std::nullopt; }),
},
};
}
template<>
void adl_serializer<NarListing::Regular>::to_json(json & j, const NarListing::Regular & r)
{
if (r.contents.fileSize)
j["size"] = *r.contents.fileSize;
if (r.executable)
j["executable"] = true;
if (r.contents.narOffset)
j["narOffset"] = *r.contents.narOffset;
}
template<typename Child>
void adl_serializer<fso::DirectoryT<Child>>::to_json(json & j, const fso::DirectoryT<Child> & d)
{
j["entries"] = d.entries;
}
template<typename Child>
fso::DirectoryT<Child> adl_serializer<fso::DirectoryT<Child>>::from_json(const json & json)
{
auto & obj = getObject(json);
return fso::DirectoryT<Child>{
.entries = valueAt(obj, "entries"),
};
}
// fso::Symlink
fso::Symlink adl_serializer<fso::Symlink>::from_json(const json & json)
{
auto & obj = getObject(json);
return fso::Symlink{
.target = getString(valueAt(obj, "target")),
};
}
void adl_serializer<fso::Symlink>::to_json(json & json, const fso::Symlink & s)
{
json = {
{"target", s.target},
};
}
// fso::Opaque
fso::Opaque adl_serializer<fso::Opaque>::from_json(const json &)
{
return fso::Opaque{};
}
void adl_serializer<fso::Opaque>::to_json(json & j, const fso::Opaque &)
{
j = nlohmann::json::object();
}
// fso::VariantT<RegularContents, recur> - generic implementation
template<typename RegularContents, bool recur>
void adl_serializer<fso::VariantT<RegularContents, recur>>::to_json(
json & j, const fso::VariantT<RegularContents, recur> & val)
{
using Variant = fso::VariantT<RegularContents, recur>;
j = nlohmann::json::object();
std::visit(
overloaded{
[&](const typename Variant::Regular & r) {
j = r;
j["type"] = "regular";
},
[&](const typename Variant::Directory & d) {
j = d;
j["type"] = "directory";
},
[&](const typename Variant::Symlink & s) {
j = s;
j["type"] = "symlink";
},
},
val.raw);
}
template<typename RegularContents, bool recur>
fso::VariantT<RegularContents, recur>
adl_serializer<fso::VariantT<RegularContents, recur>>::from_json(const json & json)
{
using Variant = fso::VariantT<RegularContents, recur>;
auto & obj = getObject(json);
auto type = getString(valueAt(obj, "type"));
if (type == "regular")
return static_cast<typename Variant::Regular>(json);
if (type == "directory")
return static_cast<typename Variant::Directory>(json);
if (type == "symlink")
return static_cast<typename Variant::Symlink>(json);
else
throw Error("unknown type of file '%s'", type);
}
// Explicit instantiations for VariantT types we use
template struct adl_serializer<MemorySourceAccessor::File>;
template struct adl_serializer<NarListing>;
template struct adl_serializer<ShallowNarListing>;
// MemorySourceAccessor
MemorySourceAccessor adl_serializer<MemorySourceAccessor>::from_json(const json & json)
{
MemorySourceAccessor res;
res.root = json;
return res;
}
void adl_serializer<MemorySourceAccessor>::to_json(json & json, const MemorySourceAccessor & val)
{
json = val.root;
}
} // namespace nlohmann
+2
View File
@@ -146,7 +146,9 @@ sources = [ config_priv_h ] + files(
'json-utils.cc', 'json-utils.cc',
'logging.cc', 'logging.cc',
'memory-source-accessor.cc', 'memory-source-accessor.cc',
'memory-source-accessor/json.cc',
'mounted-source-accessor.cc', 'mounted-source-accessor.cc',
'nar-accessor.cc',
'pos-table.cc', 'pos-table.cc',
'position.cc', 'position.cc',
'posix-source-accessor.cc', 'posix-source-accessor.cc',
@@ -1,4 +1,4 @@
#include "nix/store/nar-accessor.hh" #include "nix/util/nar-accessor.hh"
#include "nix/util/archive.hh" #include "nix/util/archive.hh"
#include <map> #include <map>
@@ -272,41 +272,39 @@ GetNarBytes seekableGetNarBytes(const Path & path)
}; };
} }
using nlohmann::json; template<bool deep>
using ListNarResult = std::conditional_t<deep, NarListing, ShallowNarListing>;
json listNar(ref<SourceAccessor> accessor, const CanonPath & path, bool recurse) template<bool deep>
static ListNarResult<deep> listNarImpl(SourceAccessor & accessor, const CanonPath & path)
{ {
auto st = accessor->lstat(path); auto st = accessor.lstat(path);
json obj = json::object();
switch (st.type) { switch (st.type) {
case SourceAccessor::Type::tRegular: case SourceAccessor::Type::tRegular:
obj["type"] = "regular"; return typename ListNarResult<deep>::Regular{
if (st.fileSize) .executable = st.isExecutable,
obj["size"] = *st.fileSize; .contents =
if (st.isExecutable) NarListingRegularFile{
obj["executable"] = true; .fileSize = st.fileSize,
if (st.narOffset && *st.narOffset) .narOffset = st.narOffset && *st.narOffset ? st.narOffset : std::nullopt,
obj["narOffset"] = *st.narOffset; },
break; };
case SourceAccessor::Type::tDirectory: case SourceAccessor::Type::tDirectory: {
obj["type"] = "directory"; typename ListNarResult<deep>::Directory dir;
{ for (const auto & [name, type] : accessor.readDirectory(path)) {
obj["entries"] = json::object(); if constexpr (deep) {
json & res2 = obj["entries"]; dir.entries.emplace(name, listNarImpl<true>(accessor, path / name));
for (const auto & [name, type] : accessor->readDirectory(path)) { } else {
if (recurse) { dir.entries.emplace(name, fso::Opaque{});
res2[name] = listNar(accessor, path / name, true);
} else
res2[name] = json::object();
} }
} }
break; return dir;
}
case SourceAccessor::Type::tSymlink: case SourceAccessor::Type::tSymlink:
obj["type"] = "symlink"; return typename ListNarResult<deep>::Symlink{
obj["target"] = accessor->readLink(path); .target = accessor.readLink(path),
break; };
case SourceAccessor::Type::tBlock: case SourceAccessor::Type::tBlock:
case SourceAccessor::Type::tChar: case SourceAccessor::Type::tChar:
case SourceAccessor::Type::tSocket: case SourceAccessor::Type::tSocket:
@@ -314,7 +312,16 @@ json listNar(ref<SourceAccessor> accessor, const CanonPath & path, bool recurse)
case SourceAccessor::Type::tUnknown: case SourceAccessor::Type::tUnknown:
assert(false); // cannot happen for NARs assert(false); // cannot happen for NARs
} }
return obj; }
NarListing listNarDeep(SourceAccessor & accessor, const CanonPath & path)
{
return listNarImpl<true>(accessor, path);
}
ShallowNarListing listNarShallow(SourceAccessor & accessor, const CanonPath & path)
{
return listNarImpl<false>(accessor, path);
} }
} // namespace nix } // namespace nix
+1 -1
View File
@@ -16,7 +16,7 @@ namespace nix {
Descriptor openDirectory(const std::filesystem::path & path) Descriptor openDirectory(const std::filesystem::path & path)
{ {
return open(path.c_str(), O_RDONLY | O_DIRECTORY); return open(path.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
} }
void setWriteTime( void setWriteTime(
+1 -1
View File
@@ -322,7 +322,7 @@ static int main_build_remote(int argc, char ** argv)
// output ids, which break CA derivations // output ids, which break CA derivations
if (!drv.inputDrvs.map.empty()) if (!drv.inputDrvs.map.empty())
drv.inputSrcs = store->parseStorePathSet(inputs); drv.inputSrcs = store->parseStorePathSet(inputs);
optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv); optResult = sshStore->buildDerivation(*drvPath, static_cast<const BasicDerivation &>(drv));
auto & result = *optResult; auto & result = *optResult;
if (auto * failureP = result.tryGetFailure()) { if (auto * failureP = result.tryGetFailure()) {
if (settings.keepFailed) { if (settings.keepFailed) {
+2 -2
View File
@@ -1,6 +1,6 @@
#include "nix/cmd/command.hh" #include "nix/cmd/command.hh"
#include "nix/store/store-api.hh" #include "nix/store/store-api.hh"
#include "nix/store/nar-accessor.hh" #include "nix/util/nar-accessor.hh"
#include "nix/util/serialise.hh" #include "nix/util/serialise.hh"
#include "nix/util/source-accessor.hh" #include "nix/util/source-accessor.hh"
@@ -80,7 +80,7 @@ struct CmdCatNar : StoreCommand, MixCat
throw SysError("opening NAR file '%s'", narPath); throw SysError("opening NAR file '%s'", narPath);
auto source = FdSource{fd.get()}; auto source = FdSource{fd.get()};
auto narAccessor = makeNarAccessor(source); auto narAccessor = makeNarAccessor(source);
auto listing = listNar(narAccessor, CanonPath::root, true); nlohmann::json listing = listNarDeep(*narAccessor, CanonPath::root);
cat(makeLazyNarAccessor(listing, seekableGetNarBytes(narPath)), CanonPath{path}); cat(makeLazyNarAccessor(listing, seekableGetNarBytes(narPath)), CanonPath{path});
} }
}; };
+8 -3
View File
@@ -1,6 +1,6 @@
#include "nix/cmd/command.hh" #include "nix/cmd/command.hh"
#include "nix/store/store-api.hh" #include "nix/store/store-api.hh"
#include "nix/store/nar-accessor.hh" #include "nix/util/nar-accessor.hh"
#include "nix/main/common-args.hh" #include "nix/main/common-args.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
@@ -85,7 +85,12 @@ struct MixLs : virtual Args, MixJSON
if (json) { if (json) {
if (showDirectory) if (showDirectory)
throw UsageError("'--directory' is useless with '--json'"); throw UsageError("'--directory' is useless with '--json'");
logger->cout("%s", listNar(accessor, path, recursive)); nlohmann::json j;
if (recursive)
j = listNarDeep(*accessor, path);
else
j = listNarShallow(*accessor, path);
logger->cout("%s", j.dump());
} else } else
listText(accessor, std::move(path)); listText(accessor, std::move(path));
} }
@@ -150,7 +155,7 @@ struct CmdLsNar : Command, MixLs
throw SysError("opening NAR file '%s'", narPath); throw SysError("opening NAR file '%s'", narPath);
auto source = FdSource{fd.get()}; auto source = FdSource{fd.get()};
auto narAccessor = makeNarAccessor(source); auto narAccessor = makeNarAccessor(source);
auto listing = listNar(narAccessor, CanonPath::root, true); nlohmann::json listing = listNarDeep(*narAccessor, CanonPath::root);
list(makeLazyNarAccessor(listing, seekableGetNarBytes(narPath)), CanonPath{path}); list(makeLazyNarAccessor(listing, seekableGetNarBytes(narPath)), CanonPath{path});
} }
}; };
+1 -1
View File
@@ -271,7 +271,7 @@ static void showHelp(std::vector<std::string> subcommand, NixArgs & toplevel)
); );
auto vDump = state.allocValue(); auto vDump = state.allocValue();
vDump->mkString(toplevel.dumpCli()); vDump->mkString(toplevel.dumpCli(), state.mem);
auto vRes = state.allocValue(); auto vRes = state.allocValue();
Value * args[]{&state.getBuiltin("false"), vDump}; Value * args[]{&state.getBuiltin("false"), vDump};
+2
View File
@@ -229,6 +229,7 @@ foreach linkname : nix_symlinks
env : {'MSYS' : 'winsymlinks:lnk'}, env : {'MSYS' : 'winsymlinks:lnk'},
# TODO(Ericson2314): Don't do this once we have the `meson.override_find_program` working) # TODO(Ericson2314): Don't do this once we have the `meson.override_find_program` working)
build_by_default : true, build_by_default : true,
depends : this_exe,
) )
# TODO(Ericson3214): Doesn't yet work # TODO(Ericson3214): Doesn't yet work
#meson.override_find_program(linkname, t) #meson.override_find_program(linkname, t)
@@ -250,6 +251,7 @@ custom_target(
env : {'MSYS' : 'winsymlinks:lnk'}, env : {'MSYS' : 'winsymlinks:lnk'},
# TODO(Ericson2314): Don't do this once we have the `meson.override_find_program` working) # TODO(Ericson2314): Don't do this once we have the `meson.override_find_program` working)
build_by_default : true, build_by_default : true,
depends : this_exe,
) )
# TODO(Ericson3214): Doesn't yet work # TODO(Ericson3214): Doesn't yet work
#meson.override_find_program(linkname, t) #meson.override_find_program(linkname, t)
+2 -2
View File
@@ -132,7 +132,7 @@ static void getAllExprs(EvalState & state, const SourcePath & path, StringSet &
} }
/* Load the expression on demand. */ /* Load the expression on demand. */
auto vArg = state.allocValue(); auto vArg = state.allocValue();
vArg->mkPath(path2); vArg->mkPath(path2, state.mem);
if (seen.size() == maxAttrs) if (seen.size() == maxAttrs)
throw Error("too many Nix expressions in directory '%1%'", path); throw Error("too many Nix expressions in directory '%1%'", path);
attrs.alloc(attrName).mkApp(&state.getBuiltin("import"), vArg); attrs.alloc(attrName).mkApp(&state.getBuiltin("import"), vArg);
@@ -483,7 +483,7 @@ static bool keep(PackageInfo & drv)
static void setMetaFlag(EvalState & state, PackageInfo & drv, const std::string & name, const std::string & value) static void setMetaFlag(EvalState & state, PackageInfo & drv, const std::string & name, const std::string & value)
{ {
auto v = state.allocValue(); auto v = state.allocValue();
v->mkString(value); v->mkString(value, state.mem);
drv.setMeta(name, v); drv.setMeta(name, v);
} }
+6 -6
View File
@@ -58,20 +58,20 @@ bool createUserEnv(
auto attrs = state.buildBindings(7 + outputs.size()); auto attrs = state.buildBindings(7 + outputs.size());
attrs.alloc(state.s.type).mkStringNoCopy("derivation"_sds); attrs.alloc(state.s.type).mkStringNoCopy("derivation"_sds);
attrs.alloc(state.s.name).mkString(i.queryName()); attrs.alloc(state.s.name).mkString(i.queryName(), state.mem);
auto system = i.querySystem(); auto system = i.querySystem();
if (!system.empty()) if (!system.empty())
attrs.alloc(state.s.system).mkString(system); attrs.alloc(state.s.system).mkString(system, state.mem);
attrs.alloc(state.s.outPath).mkString(state.store->printStorePath(i.queryOutPath())); attrs.alloc(state.s.outPath).mkString(state.store->printStorePath(i.queryOutPath()), state.mem);
if (drvPath) if (drvPath)
attrs.alloc(state.s.drvPath).mkString(state.store->printStorePath(*drvPath)); attrs.alloc(state.s.drvPath).mkString(state.store->printStorePath(*drvPath), state.mem);
// Copy each output meant for installation. // Copy each output meant for installation.
auto outputsList = state.buildList(outputs.size()); auto outputsList = state.buildList(outputs.size());
for (const auto & [m, j] : enumerate(outputs)) { for (const auto & [m, j] : enumerate(outputs)) {
(outputsList[m] = state.allocValue())->mkString(j.first); (outputsList[m] = state.allocValue())->mkString(j.first, state.mem);
auto outputAttrs = state.buildBindings(2); auto outputAttrs = state.buildBindings(2);
outputAttrs.alloc(state.s.outPath).mkString(state.store->printStorePath(*j.second)); outputAttrs.alloc(state.s.outPath).mkString(state.store->printStorePath(*j.second), state.mem);
attrs.alloc(j.first).mkAttrs(outputAttrs); attrs.alloc(j.first).mkAttrs(outputAttrs);
/* This is only necessary when installing store paths, e.g., /* This is only necessary when installing store paths, e.g.,
+16 -11
View File
@@ -59,7 +59,7 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
std::tuple<StorePath, Hash> prefetchFile( std::tuple<StorePath, Hash> prefetchFile(
ref<Store> store, ref<Store> store,
const VerbatimURL & url, const VerbatimURL & url,
std::optional<std::string> name, std::optional<std::string> maybeName,
HashAlgorithm hashAlgo, HashAlgorithm hashAlgo,
std::optional<Hash> expectedHash, std::optional<Hash> expectedHash,
bool unpack, bool unpack,
@@ -68,16 +68,21 @@ std::tuple<StorePath, Hash> prefetchFile(
ContentAddressMethod method = ContentAddressMethod method =
unpack || executable ? ContentAddressMethod::Raw::NixArchive : ContentAddressMethod::Raw::Flat; unpack || executable ? ContentAddressMethod::Raw::NixArchive : ContentAddressMethod::Raw::Flat;
/* Figure out a name in the Nix store. */ std::string name = maybeName
if (!name) { .or_else([&]() {
name = url.lastPathSegment(); /* Figure out a name in the Nix store. */
if (!name || name->empty()) auto derivedFromUrl = url.lastPathSegment();
throw Error("cannot figure out file name for '%s'", url.to_string()); if (!derivedFromUrl || derivedFromUrl->empty())
} throw Error("cannot figure out file name for '%s'", url.to_string());
return derivedFromUrl;
})
.value();
try { try {
checkName(*name); checkName(name);
} catch (BadStorePathName & e) { } catch (BadStorePathName & e) {
e.addTrace({}, "file name '%s' was extracted from URL '%s'", *name, url.to_string()); if (!maybeName)
e.addTrace({}, "file name '%s' was extracted from URL '%s'", name, url.to_string());
throw; throw;
} }
@@ -89,7 +94,7 @@ std::tuple<StorePath, Hash> prefetchFile(
if (expectedHash) { if (expectedHash) {
hashAlgo = expectedHash->algo; hashAlgo = expectedHash->algo;
storePath = storePath =
store->makeFixedOutputPathFromCA(*name, ContentAddressWithReferences::fromParts(method, *expectedHash, {})); store->makeFixedOutputPathFromCA(name, ContentAddressWithReferences::fromParts(method, *expectedHash, {}));
if (store->isValidPath(*storePath)) if (store->isValidPath(*storePath))
hash = expectedHash; hash = expectedHash;
else else
@@ -138,7 +143,7 @@ std::tuple<StorePath, Hash> prefetchFile(
Activity act(*logger, lvlChatty, actUnknown, fmt("adding '%s' to the store", url.to_string())); Activity act(*logger, lvlChatty, actUnknown, fmt("adding '%s' to the store", url.to_string()));
auto info = store->addToStoreSlow(*name, makeFSSourceAccessor(tmpFile), method, hashAlgo, {}, expectedHash); auto info = store->addToStoreSlow(name, makeFSSourceAccessor(tmpFile), method, hashAlgo, {}, expectedHash);
storePath = info.path; storePath = info.path;
assert(info.ca); assert(info.ca);
hash = info.ca->hash; hash = info.ca->hash;
+2
View File
@@ -32,6 +32,8 @@ writeSimpleFlake() {
baseName = builtins.baseNameOf ./.; baseName = builtins.baseNameOf ./.;
root = ./.; root = ./.;
number = 123;
}; };
} }
EOF EOF
+1
View File
@@ -34,6 +34,7 @@ suites += {
'source-paths.sh', 'source-paths.sh',
'old-lockfiles.sh', 'old-lockfiles.sh',
'trace-ifd.sh', 'trace-ifd.sh',
'shallow.sh',
], ],
'workdir' : meson.current_source_dir(), 'workdir' : meson.current_source_dir(),
} }
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env bash
export _NIX_FORCE_HTTP=1
source ./common.sh
requireGit
TODO_NixOS
createFlake1
repoDir="$TEST_ROOT/repo"
mkdir -p "$repoDir"
echo "# foo" >> "$flake1Dir/flake.nix"
git -C "$flake1Dir" commit -a -m bla
cat > "$repoDir"/flake.nix <<EOF
{
inputs.dep = {
type = "git";
url = "file://$flake1Dir";
};
outputs = inputs: rec {
revs = assert inputs.dep.number == 123; inputs.dep.revCount;
};
}
EOF
# This will do a non-shallow fetch.
[[ $(nix eval "path:$repoDir#revs") = 2 ]]
# This should re-use the existing non-shallow clone.
clearStore
mv "$flake1Dir" "$flake1Dir.moved"
[[ $(nix eval "path:$repoDir#revs") = 2 ]]
@@ -0,0 +1,25 @@
error:
… while calling the 'deepSeq' builtin
at /pwd/lang/eval-fail-deepseq-list-attr.nix:3:1:
2|
3| builtins.deepSeq [
| ^
4| 1
… while evaluating list element at index 1
… while evaluating the attribute 'b'
at /pwd/lang/eval-fail-deepseq-list-attr.nix:7:5:
6| a = 2;
7| b = throw "error in attr in list element";
| ^
8| }
… while calling the 'throw' builtin
at /pwd/lang/eval-fail-deepseq-list-attr.nix:7:9:
6| a = 2;
7| b = throw "error in attr in list element";
| ^
8| }
error: error in attr in list element
@@ -0,0 +1,10 @@
# Test that deepSeq reports list index and attribute name in error traces.
builtins.deepSeq [
1
{
a = 2;
b = throw "error in attr in list element";
}
3
] "unexpected success"
@@ -0,0 +1,30 @@
error:
… while calling the 'deepSeq' builtin
at /pwd/lang/eval-fail-deepseq-stack-overflow.nix:8:1:
7| in
8| builtins.deepSeq reverseLinkedList (
| ^
9| throw "unexpected success; expected a controlled stack overflow instead"
… while evaluating the attribute 'tail'
at /pwd/lang/eval-fail-deepseq-stack-overflow.nix:6:67:
5| long = builtins.genList (x: x) 100000;
6| reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
| ^
7| in
(9997 duplicate frames omitted)
… while evaluating the attribute 'head'
at /pwd/lang/eval-fail-deepseq-stack-overflow.nix:6:62:
5| long = builtins.genList (x: x) 100000;
6| reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
| ^
7| in
error: stack overflow; max-call-depth exceeded
at /pwd/lang/eval-fail-deepseq-stack-overflow.nix:5:28:
4| let
5| long = builtins.genList (x: x) 100000;
| ^
6| reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
@@ -0,0 +1,10 @@
# Test that deepSeq on a deeply nested structure produces a controlled
# stack overflow error rather than a segfault.
let
long = builtins.genList (x: x) 100000;
reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
in
builtins.deepSeq reverseLinkedList (
throw "unexpected success; expected a controlled stack overflow instead"
)
@@ -0,0 +1,54 @@
error:
… while evaluating the attribute 'outPath'
at «nix-internal»/derivation-internal.nix:50:7:
49| value = commonAttrs // {
50| outPath = builtins.getAttr outputName strict;
| ^
51| drvPath = strict.drvPath;
… while calling the 'getAttr' builtin
at «nix-internal»/derivation-internal.nix:50:17:
49| value = commonAttrs // {
50| outPath = builtins.getAttr outputName strict;
| ^
51| drvPath = strict.drvPath;
… while calling the 'derivationStrict' builtin
at «nix-internal»/derivation-internal.nix:37:12:
36|
37| strict = derivationStrict drvAttrs;
| ^
38|
… while evaluating derivation 'test'
whose name attribute is located at /pwd/lang/eval-fail-derivation-structuredAttrs-stack-overflow.nix:5:3
… while evaluating attribute 'nested' of derivation 'test'
at /pwd/lang/eval-fail-derivation-structuredAttrs-stack-overflow.nix:9:3:
8| __structuredAttrs = true;
9| nested =
| ^
10| let
… while evaluating attribute 'tail'
at /pwd/lang/eval-fail-derivation-structuredAttrs-stack-overflow.nix:12:71:
11| long = builtins.genList (x: x) 100000;
12| reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
| ^
13| in
(9994 duplicate frames omitted)
… while evaluating attribute 'head'
at /pwd/lang/eval-fail-derivation-structuredAttrs-stack-overflow.nix:12:66:
11| long = builtins.genList (x: x) 100000;
12| reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
| ^
13| in
error: stack overflow; max-call-depth exceeded
at /pwd/lang/eval-fail-derivation-structuredAttrs-stack-overflow.nix:12:66:
11| long = builtins.genList (x: x) 100000;
12| reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
| ^
13| in
@@ -0,0 +1,15 @@
# Test that derivations with __structuredAttrs and deeply nested structures
# produce a controlled stack overflow error rather than a segfault.
derivation {
name = "test";
system = "x86_64-linux";
builder = "/bin/sh";
__structuredAttrs = true;
nested =
let
long = builtins.genList (x: x) 100000;
reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
in
reverseLinkedList;
}
@@ -0,0 +1,30 @@
error:
… while calling the 'toJSON' builtin
at /pwd/lang/eval-fail-toJSON-stack-overflow.nix:8:1:
7| in
8| builtins.toJSON reverseLinkedList
| ^
9|
… while evaluating attribute 'tail'
at /pwd/lang/eval-fail-toJSON-stack-overflow.nix:6:67:
5| long = builtins.genList (x: x) 100000;
6| reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
| ^
7| in
(9997 duplicate frames omitted)
… while evaluating attribute 'head'
at /pwd/lang/eval-fail-toJSON-stack-overflow.nix:6:62:
5| long = builtins.genList (x: x) 100000;
6| reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
| ^
7| in
error: stack overflow; max-call-depth exceeded
at /pwd/lang/eval-fail-toJSON-stack-overflow.nix:6:62:
5| long = builtins.genList (x: x) 100000;
6| reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
| ^
7| in
@@ -0,0 +1,8 @@
# Test that toJSON on a deeply nested structure produces a controlled
# stack overflow error rather than a segfault.
let
long = builtins.genList (x: x) 100000;
reverseLinkedList = builtins.foldl' (tail: head: { inherit head tail; }) null long;
in
builtins.toJSON reverseLinkedList
+1
View File
@@ -131,6 +131,7 @@ in
start_all() start_all()
machine.wait_for_unit("nginx.service") machine.wait_for_unit("nginx.service")
machine.wait_for_open_port(80)
# Original test: zstd archive with gzip content-encoding # Original test: zstd archive with gzip content-encoding
# Make sure that the file is properly compressed as the test would be meaningless otherwise # Make sure that the file is properly compressed as the test would be meaningless otherwise
@@ -1,5 +1,5 @@
{ {
description = "fetchTree fetches git repos shallowly by default"; description = "fetchTree fetches git repos shallowly if possible";
script = '' script = ''
# purge nix git cache to make sure we start with a clean slate # purge nix git cache to make sure we start with a clean slate
client.succeed("rm -rf ~/.cache/nix") client.succeed("rm -rf ~/.cache/nix")
@@ -28,6 +28,7 @@
type = "git"; type = "git";
url = "{repo.remote}"; url = "{repo.remote}";
rev = "{commit2_rev}"; rev = "{commit2_rev}";
revCount = 1234;
}} }}
""" """
-1
View File
@@ -99,7 +99,6 @@ in
# Check that fetching fails if we provide incorrect attributes. # Check that fetching fails if we provide incorrect attributes.
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?rev=493300eb13ae6fb387fbd47bf54a85915acc31c0") machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?rev=493300eb13ae6fb387fbd47bf54a85915acc31c0")
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?revCount=789")
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?narHash=sha256-tbudgBSg+bHWHiHnlteNzN8TUvI80ygS9IULh4rklEw=") machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?narHash=sha256-tbudgBSg+bHWHiHnlteNzN8TUvI80ygS9IULh4rklEw=")
''; '';