Compare commits

...

51 Commits

Author SHA1 Message Date
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 Zimmerman
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:

- 4ae6fb5a8f
- 12156d3beb

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
91 changed files with 1506 additions and 438 deletions

View File

@@ -37,6 +37,7 @@ mkMesonDerivation (finalAttrs: {
(fileset.unions [
../../.version
# For example JSON
../../src/libutil-tests/data/memory-source-accessor
../../src/libutil-tests/data/hash
../../src/libstore-tests/data/content-address
../../src/libstore-tests/data/store-path

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).

View File

@@ -121,6 +121,7 @@
- [Architecture and Design](architecture/architecture.md)
- [Formats and Protocols](protocols/index.md)
- [JSON Formats](protocols/json/index.md)
- [File System Object](protocols/json/file-system-object.md)
- [Hash](protocols/json/hash.md)
- [Content Address](protocols/json/content-address.md)
- [Store Path](protocols/json/store-path.md)

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).
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
[nix-shell]$ NIX_HARDENING_ENABLE=$(printLines $NIX_HARDENING_ENABLE | grep -v fortify)

View File

@@ -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)
-->

View File

@@ -11,6 +11,7 @@ s/\\`/`/g
#
# As we have more such relative links, more replacements of this nature
# 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\)^[JSON format for `Hash`](./hash.html)^g
s^\(./content-address-v1.yaml\)\?#/$defs/method^[JSON format for `ContentAddress`](./content-address.html#method)^g

View File

@@ -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')
schemas = [
'file-system-object-v1',
'hash-v1',
'content-address-v1',
'store-path-v1',

View File

@@ -0,0 +1 @@
../../../../../../src/libutil-tests/data/memory-source-accessor

View File

@@ -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

View File

@@ -3,19 +3,23 @@
Nix uses a simplified model of the file system, which consists of file system objects.
Every file system object is one of the following:
- File
- [**Regular File**]{#regular}
- 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
- Directory
- [**Directory**]{#directory}
Mapping of names to child file system objects
- [Symbolic link](https://en.m.wikipedia.org/wiki/Symbolic_link)
- [**Symbolic link**]{#symlink}
An arbitrary string.
Nix does not assign any semantics to symbolic links.
An arbitrary string, known as the *target* of the symlink.
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.
A bare file or symlink can be a root file system object.

View File

@@ -0,0 +1 @@
../../src/libutil-tests/data/memory-source-accessor

View File

@@ -20,6 +20,14 @@ schema_dir = meson.current_source_dir() / 'schema'
# Get all example files
schemas = [
{
'stem' : 'file-system-object',
'schema' : schema_dir / 'file-system-object-v1.yaml',
'files' : [
'simple.json',
'complex.json',
],
},
{
'stem' : 'hash',
'schema' : schema_dir / 'hash-v1.yaml',

View File

@@ -20,6 +20,7 @@ mkMesonDerivation (finalAttrs: {
fileset = lib.fileset.unions [
../../.version
../../doc/manual/source/protocols/json/schema
../../src/libutil-tests/data/memory-source-accessor
../../src/libutil-tests/data/hash
../../src/libstore-tests/data/content-address
../../src/libstore-tests/data/store-path

View File

@@ -168,9 +168,9 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
? state.rootPath(absPath(getCommandBaseDir()))
: state.rootPath(".")));
},
[&](const AutoArgString & arg) { v->mkString(arg.s); },
[&](const AutoArgFile & arg) { v->mkString(readFile(arg.path.string())); },
[&](const AutoArgStdin & arg) { v->mkString(readFile(STDIN_FILENO)); }},
[&](const AutoArgString & arg) { v->mkString(arg.s, state.mem); },
[&](const AutoArgFile & arg) { v->mkString(readFile(arg.path.string()), state.mem); },
[&](const AutoArgStdin & arg) { v->mkString(readFile(STDIN_FILENO), state.mem); }},
arg);
res.insert(state.symbols.create(name), v);
}

View File

@@ -69,8 +69,8 @@ nix_err nix_expr_eval_from_string(
context->last_err_code = NIX_OK;
try {
nix::Expr * parsedExpr = state->state.parseExprFromString(expr, state->state.rootPath(nix::CanonPath(path)));
state->state.eval(parsedExpr, value->value);
state->state.forceValue(value->value, nix::noPos);
state->state.eval(parsedExpr, *value->value);
state->state.forceValue(*value->value, nix::noPos);
}
NIXC_CATCH_ERRS
}
@@ -80,8 +80,8 @@ nix_err nix_value_call(nix_c_context * context, EvalState * state, Value * fn, n
if (context)
context->last_err_code = NIX_OK;
try {
state->state.callFunction(fn->value, arg->value, value->value, nix::noPos);
state->state.forceValue(value->value, nix::noPos);
state->state.callFunction(*fn->value, *arg->value, *value->value, nix::noPos);
state->state.forceValue(*value->value, nix::noPos);
}
NIXC_CATCH_ERRS
}
@@ -91,9 +91,15 @@ nix_err nix_value_call_multi(
{
if (context)
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 {
state->state.callFunction(fn->value, {(nix::Value **) args, nargs}, value->value, nix::noPos);
state->state.forceValue(value->value, nix::noPos);
state->state.callFunction(*fn->value, {internal_args.data(), nargs}, *value->value, nix::noPos);
state->state.forceValue(*value->value, nix::noPos);
}
NIXC_CATCH_ERRS
}
@@ -103,7 +109,7 @@ nix_err nix_value_force(nix_c_context * context, EvalState * state, nix_value *
if (context)
context->last_err_code = NIX_OK;
try {
state->state.forceValue(value->value, nix::noPos);
state->state.forceValue(*value->value, nix::noPos);
}
NIXC_CATCH_ERRS
}
@@ -113,7 +119,7 @@ nix_err nix_value_force_deep(nix_c_context * context, EvalState * state, nix_val
if (context)
context->last_err_code = NIX_OK;
try {
state->state.forceValueDeep(value->value);
state->state.forceValueDeep(*value->value);
}
NIXC_CATCH_ERRS
}

View File

@@ -39,7 +39,13 @@ struct ListBuilder
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

View File

@@ -20,7 +20,7 @@ static const nix::Value & check_value_not_null(const nix_value * value)
if (!value) {
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)
@@ -28,7 +28,7 @@ static nix::Value & check_value_not_null(nix_value * value)
if (!value) {
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)
@@ -58,9 +58,14 @@ static nix::Value & check_value_out(nix_value * value)
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.
*/
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;
@@ -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,
// or maybe something to make blackholes work better; we don't know).
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) {
/* TODO: Throw different errors depending on the error code */
@@ -135,7 +153,7 @@ PrimOp * nix_alloc_primop(
.args = {},
.arity = (size_t) arity,
.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)
for (size_t i = 0; args[i]; i++)
p->args.emplace_back(*args);
@@ -160,8 +178,7 @@ nix_value * nix_alloc_value(nix_c_context * context, EvalState * state)
if (context)
context->last_err_code = NIX_OK;
try {
nix_value * res = as_nix_value_ptr(state->state.allocValue());
nix_gc_incref(nullptr, res);
nix_value * res = new_nix_value(state->state.allocValue(), state->state.mem);
return res;
}
NIXC_CATCH_ERRS_NULL
@@ -331,10 +348,10 @@ nix_value * nix_get_list_byidx(nix_c_context * context, const nix_value * value,
return nullptr;
}
auto * p = v.listView()[ix];
nix_gc_incref(nullptr, p);
if (p != nullptr)
state->state.forceValue(*p, nix::noPos);
return as_nix_value_ptr(p);
if (p == nullptr)
return nullptr;
state->state.forceValue(*p, nix::noPos);
return new_nix_value(p, state->state.mem);
}
NIXC_CATCH_ERRS_NULL
}
@@ -352,9 +369,8 @@ nix_get_list_byidx_lazy(nix_c_context * context, const nix_value * value, EvalSt
return nullptr;
}
auto * p = v.listView()[ix];
nix_gc_incref(nullptr, p);
// Note: intentionally NOT calling forceValue() to keep the element lazy
return as_nix_value_ptr(p);
return new_nix_value(p, state->state.mem);
}
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);
auto attr = v.attrs()->get(s);
if (attr) {
nix_gc_incref(nullptr, attr->value);
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");
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);
auto attr = v.attrs()->get(s);
if (attr) {
nix_gc_incref(nullptr, attr->value);
// Note: intentionally NOT calling forceValue() to keep the attribute lazy
return as_nix_value_ptr(attr->value);
return new_nix_value(attr->value, state->state.mem);
}
nix_set_err_msg(context, NIX_ERR_KEY, "missing attribute");
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];
*name = state->state.symbols[a.name].c_str();
nix_gc_incref(nullptr, a.value);
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
}
@@ -461,9 +474,8 @@ nix_value * nix_get_attr_byidx_lazy(
}
const nix::Attr & a = (*v.attrs())[i];
*name = state->state.symbols[a.name].c_str();
nix_gc_incref(nullptr, a.value);
// Note: intentionally NOT calling forceValue() to keep the attribute lazy
return as_nix_value_ptr(a.value);
return new_nix_value(a.value, state->state.mem);
}
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;
try {
auto & v = check_value_out(value);
v.mkString(std::string_view(str));
v.mkString(std::string_view(str), *value->mem);
}
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;
try {
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
}

View File

@@ -73,7 +73,7 @@ TEST_F(JSONValueTest, StringQuotes)
TEST_F(JSONValueTest, DISABLED_Path)
{
Value v;
v.mkPath(state.rootPath(CanonPath("/test")));
v.mkPath(state.rootPath(CanonPath("/test")), state.mem);
ASSERT_EQ(getJSONValue(v), "\"/nix/store/g1w7hy3qg1w7hy3qg1w7hy3qg1w7hy3q-x\"");
}
} /* namespace nix */

View File

@@ -268,7 +268,7 @@ struct StringPrintingTests : LibExprTest
void test(std::string_view literal, std::string_view expected, unsigned int maxLength, A... args)
{
Value v;
v.mkString(literal);
v.mkString(literal, state.mem);
std::stringstream out;
printValue(state, out, v, PrintOptions{.maxStringLength = maxLength});
@@ -353,7 +353,7 @@ TEST_F(ValuePrintingTests, ansiColorsStringElided)
TEST_F(ValuePrintingTests, ansiColorsPath)
{
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});
}

View File

@@ -81,20 +81,20 @@ static const char * makeImmutableString(std::string_view s)
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)
throw std::bad_alloc();
auto res = new (t) StringData(size);
return *res;
}
const StringData & StringData::make(std::string_view s)
const StringData & StringData::make(EvalMemory & mem, std::string_view s)
{
if (s.empty())
return ""_sds;
auto & res = alloc(s.size());
auto & res = alloc(mem, s.size());
std::memcpy(&res.data_, s.data(), s.size());
res.data_[s.size()] = '\0';
return res;
@@ -849,9 +849,9 @@ DebugTraceStacker::DebugTraceStacker(EvalState & evalState, DebugTrace t)
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 *
@@ -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());
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;
}
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)
@@ -876,9 +876,9 @@ void Value::mkStringMove(const StringData & s, const NixStringContext & context,
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)
@@ -943,7 +943,7 @@ void EvalState::mkPos(Value & v, PosIdx p)
auto origin = positions.originOf(p);
if (auto path = std::get_if<SourcePath>(&origin)) {
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));
v.mkAttrs(attrs);
} else
@@ -1751,9 +1751,9 @@ void ExprCall::eval(EvalState & state, Env & env, Value & v)
// 4: about 60
// 5: under 10
// This excluded attrset lambdas (`{...}:`). Contributions of mixed lambdas appears insignificant at ~150 total.
SmallValueVector<4> vArgs(args.size());
for (size_t i = 0; i < args.size(); ++i)
vArgs[i] = args[i]->maybeThunk(state, env);
SmallValueVector<4> vArgs(args->size());
for (size_t i = 0; i < args->size(); ++i)
vArgs[i] = (*args)[i]->maybeThunk(state, env);
state.callFunction(vFun, vArgs, v, pos);
}
@@ -2139,9 +2139,9 @@ void ExprConcatStrings::eval(EvalState & state, Env & env, Value & v)
for (const auto & part : strings) {
resultStr += *part;
}
v.mkPath(state.rootPath(CanonPath(resultStr)));
v.mkPath(state.rootPath(CanonPath(resultStr)), state.mem);
} else {
auto & resultStr = StringData::alloc(sSize);
auto & resultStr = StringData::alloc(state.mem, sSize);
auto * tmp = resultStr.data();
for (const auto & part : strings) {
std::memcpy(tmp, part->data(), part->size());
@@ -2188,6 +2188,8 @@ void EvalState::forceValueDeep(Value & v)
std::set<const Value *> seen;
[&, &state(*this)](this const auto & recurse, Value & v) {
auto _level = state.addCallDepth(v.determinePos(noPos));
if (!seen.insert(&v).second)
return;
@@ -2214,8 +2216,15 @@ void EvalState::forceValueDeep(Value & v)
}
else if (v.isList()) {
size_t index = 0;
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);
}

View File

@@ -592,11 +592,14 @@ public:
struct ExprCall : Expr
{
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;
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)
, args(args)
, 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)
, args(args)
, 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
template<class C>
[[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>)
{
return alloc.new_object<C>(pos, fun, std::move(args));
@@ -844,7 +847,7 @@ public:
template<class C>
[[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>)
{
return alloc.new_object<C>(pos, fun, std::move(args), std::move(cursedOrEndPos));

View File

@@ -232,13 +232,13 @@ public:
* Allocate StringData on the (possibly) GC-managed heap and copy
* 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.
* @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
{
@@ -1147,13 +1147,13 @@ public:
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 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
{

View File

@@ -151,7 +151,7 @@ public:
bool string(string_t & val) override
{
forceNoNullByte(val);
rs->value(state).mkString(val);
rs->value(state).mkString(val, state.mem);
rs->add();
return true;
}

View File

@@ -191,7 +191,7 @@ void ExprCall::show(const SymbolTable & symbols, std::ostream & str) const
{
str << '(';
fun->show(symbols, str);
for (auto e : args) {
for (auto e : *args) {
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)
{
// 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)
es.exprEnvs.insert(std::make_pair(this, env));
fun->bindVars(es, env);
for (auto e : args)
for (auto e : *args)
e->bindVars(es, env);
}

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) {
if (auto e2 = dynamic_cast<ExprCall *>(fn)) {
e2->args.push_back(arg);
e2->args->push_back(arg);
return fn;
}
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 *> expr_select expr_simple expr_app
%type <Expr *> expr_pipe_from expr_pipe_into
%type <std::vector<Expr *>> list
%type <std::pmr::vector<Expr *>> list
%type <ExprAttrs *> binds binds1
%type <FormalsBuilder> formals formal_set
%type <Formal> formal

View File

@@ -53,7 +53,7 @@ RegisterPrimOp::PrimOps & RegisterPrimOp::primOps()
static inline Value * mkString(EvalState & state, const std::csub_match & match)
{
Value * v = state.allocValue();
v->mkString({match.first, match.second});
v->mkString({match.first, match.second}, state.mem);
return v;
}
@@ -230,12 +230,12 @@ void derivationToValue(
NixStringContextElem::DrvDeep{.drvPath = storePath},
},
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());
for (const auto & [i, o] : enumerate(drv.outputs)) {
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);
@@ -519,7 +519,7 @@ static void prim_typeOf(EvalState & state, const PosIdx pos, Value ** args, Valu
v.mkStringNoCopy("lambda"_sds);
break;
case nExternal:
v.mkString(args[0]->external()->typeOf());
v.mkString(args[0]->external()->typeOf(), state.mem);
break;
case nFloat:
v.mkStringNoCopy("float"_sds);
@@ -1176,7 +1176,7 @@ static void prim_getEnv(EvalState & state, const PosIdx pos, Value ** args, Valu
{
std::string name(
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({
@@ -1842,8 +1842,10 @@ static RegisterPrimOp primop_derivationStrict(
out. */
static void prim_placeholder(EvalState & state, const PosIdx pos, Value ** args, Value & v)
{
v.mkString(hashPlaceholder(
state.forceStringNoCtx(*args[0], pos, "while evaluating the first argument passed to builtins.placeholder")));
v.mkString(
hashPlaceholder(state.forceStringNoCtx(
*args[0], pos, "while evaluating the first argument passed to builtins.placeholder")),
state.mem);
}
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);
if (args[0]->type() == nPath) {
auto path = args[0]->path();
v.mkPath(path.path.isRoot() ? path : path.parent());
v.mkPath(path.path.isRoot() ? path : path.parent(), state.mem);
} else {
NixStringContext context;
auto path = state.coerceToString(
@@ -2144,7 +2146,7 @@ static void prim_findFile(EvalState & state, const PosIdx pos, Value ** args, Va
auto path =
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(
@@ -2293,7 +2295,7 @@ static void prim_hashFile(EvalState & state, const PosIdx pos, Value ** args, Va
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({
@@ -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
// query the file type lazily.
auto epath = state.allocValue();
epath->mkPath(path / name);
epath->mkPath(path / name, state.mem);
if (!readFileType)
readFileType = &state.getBuiltin("readFileType");
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
second is a string indicating the type of the file. */
Value arg1;
arg1.mkString(path.path.abs());
arg1.mkString(path.path.abs(), mem);
// assert that type is not "unknown"
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 =
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({
@@ -4574,7 +4576,7 @@ static void prim_convertHash(EvalState & state, const PosIdx pos, Value ** args,
HashFormat hf = parseHashFormat(
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({
@@ -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");
DrvName parsed(name);
auto attrs = state.buildBindings(2);
attrs.alloc(state.s.name).mkString(parsed.name);
attrs.alloc("version").mkString(parsed.version);
attrs.alloc(state.s.name).mkString(parsed.name, state.mem);
attrs.alloc("version").mkString(parsed.version, state.mem);
v.mkAttrs(attrs);
}
@@ -5048,7 +5050,7 @@ static void prim_splitVersion(EvalState & state, const PosIdx pos, Value ** args
}
auto list = state.buildList(components.size());
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);
}
@@ -5192,7 +5194,7 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
});
if (!settings.pureEval)
v.mkString(settings.getCurrentSystem());
v.mkString(settings.getCurrentSystem(), mem);
addConstant(
"__currentSystem",
v,
@@ -5224,7 +5226,7 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
.impureOnly = true,
});
v.mkString(nixVersion);
v.mkString(nixVersion, mem);
addConstant(
"__nixVersion",
v,
@@ -5249,7 +5251,7 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
)",
});
v.mkString(store->storeDir);
v.mkString(store->storeDir, mem);
addConstant(
"__storeDir",
v,
@@ -5314,8 +5316,8 @@ void EvalState::createBaseEnv(const EvalSettings & evalSettings)
auto list = buildList(lookupPath.elements.size());
for (const auto & [n, i] : enumerate(lookupPath.elements)) {
auto attrs = buildBindings(2);
attrs.alloc("path").mkString(i.path.s);
attrs.alloc("prefix").mkString(i.prefix.s);
attrs.alloc("path").mkString(i.path.s, mem);
attrs.alloc("prefix").mkString(i.prefix.s, mem);
(list[n] = allocValue())->mkAttrs(attrs);
}
v.mkList(list);

View File

@@ -11,7 +11,7 @@ static void prim_unsafeDiscardStringContext(EvalState & state, const PosIdx pos,
NixStringContext context;
auto s = state.coerceToString(
pos, *args[0], context, "while evaluating the argument passed to builtins.unsafeDiscardStringContext");
v.mkString(*s);
v.mkString(*s, state.mem);
}
static RegisterPrimOp primop_unsafeDiscardStringContext({
@@ -218,7 +218,7 @@ static void prim_getContext(EvalState & state, const PosIdx pos, Value ** args,
if (!info.second.outputs.empty()) {
auto list = state.buildList(info.second.outputs.size());
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);
}
attrs.alloc(state.store->printStorePath(info.first)).mkAttrs(infoAttrs);

View File

@@ -86,12 +86,12 @@ static void prim_fetchMercurial(EvalState & state, const PosIdx pos, Value ** ar
auto attrs2 = state.buildBindings(8);
state.mkStorePathString(storePath, attrs2.alloc(state.s.outPath));
if (input2.getRef())
attrs2.alloc("branch").mkString(*input2.getRef());
attrs2.alloc("branch").mkString(*input2.getRef(), state.mem);
// Backward compatibility: set 'rev' to
// 0000000000000000000000000000000000000000 for a dirty tree.
auto rev2 = input2.getRev().value_or(Hash(HashAlgorithm::SHA1));
attrs2.alloc("rev").mkString(rev2.gitRev());
attrs2.alloc("shortRev").mkString(rev2.gitRev().substr(0, 12));
attrs2.alloc("rev").mkString(rev2.gitRev(), state.mem);
attrs2.alloc("shortRev").mkString(rev2.gitRev().substr(0, 12), state.mem);
if (auto revCount = input2.getRevCount())
attrs2.alloc("revCount").mkInt(*revCount);
v.mkAttrs(attrs2);

View File

@@ -35,7 +35,7 @@ void emitTreeAttrs(
// FIXME: support arbitrary input attributes.
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")
attrs.alloc("submodules").mkBool(fetchers::maybeGetBoolAttr(input.attrs, "submodules").value_or(false));
@@ -43,13 +43,13 @@ void emitTreeAttrs(
if (!forceDirty) {
if (auto rev = input.getRev()) {
attrs.alloc("rev").mkString(rev->gitRev());
attrs.alloc("shortRev").mkString(rev->gitShortRev());
attrs.alloc("rev").mkString(rev->gitRev(), state.mem);
attrs.alloc("shortRev").mkString(rev->gitShortRev(), state.mem);
} else if (emptyRevFallback) {
// Backwards compat for `builtins.fetchGit`: dirty repos return an empty sha1 as rev
auto emptyHash = Hash(HashAlgorithm::SHA1);
attrs.alloc("rev").mkString(emptyHash.gitRev());
attrs.alloc("shortRev").mkString(emptyHash.gitShortRev());
attrs.alloc("rev").mkString(emptyHash.gitRev(), state.mem);
attrs.alloc("shortRev").mkString(emptyHash.gitShortRev(), state.mem);
}
if (auto revCount = input.getRevCount())
@@ -59,13 +59,14 @@ void emitTreeAttrs(
}
if (auto dirtyRev = fetchers::maybeGetStrAttr(input.attrs, "dirtyRev")) {
attrs.alloc("dirtyRev").mkString(*dirtyRev);
attrs.alloc("dirtyShortRev").mkString(*fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev"));
attrs.alloc("dirtyRev").mkString(*dirtyRev, state.mem);
attrs.alloc("dirtyShortRev").mkString(*fetchers::maybeGetStrAttr(input.attrs, "dirtyShortRev"), state.mem);
}
if (auto lastModified = input.getLastModified()) {
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);
@@ -150,11 +151,6 @@ static void fetchTree(
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 (auto nameIter = attrs.find("name"); nameIter != attrs.end())
state.error<EvalError>("argument 'name' isnt supported in call to '%s'", fetcher)

View File

@@ -126,7 +126,7 @@ static void prim_fromTOML(EvalState & state, const PosIdx pos, Value ** args, Va
case toml::value_t::string: {
auto s = toml::get<std::string_view>(t);
forceNoNullByte(s);
v.mkString(s);
v.mkString(s, state.mem);
} break;
case toml::value_t::local_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;
auto str = s.view();
forceNoNullByte(str);
attrs.alloc("value").mkString(str);
attrs.alloc("value").mkString(str, state.mem);
v.mkAttrs(attrs);
} else {
throw std::runtime_error("Dates and times are not supported");

View File

@@ -16,6 +16,8 @@ json printValueAsJSON(
{
checkInterrupt();
auto _level = state.addCallDepth(pos);
if (strict)
state.forceValue(v, pos);

View File

@@ -92,7 +92,7 @@ TEST_F(GitUtilsTest, sink_basic)
// sink->createHardlink("foo-1.1/links/foo-2", CanonPath("foo-1.1/hello"));
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);
ASSERT_EQ(entries.size(), 5u);
ASSERT_EQ(accessor->readFile(CanonPath("hello")), "hello world");

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 (result.getRev() != prevRev)
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
@@ -495,9 +481,9 @@ void InputScheme::clone(
Activity act(*logger, lvlTalkative, actUnknown, fmt("copying '%s' to %s...", input2.to_string(), destDir));
auto source = sinkToSource([&](Sink & sink) { accessor->dumpPath(CanonPath::root, sink); });
restorePath(destDir, *source);
RestoreSink sink(/*startFsync=*/false);
sink.dstPath = destDir;
copyRecursive(*accessor, CanonPath::root, sink, CanonPath::root);
}
std::optional<ExperimentalFeature> InputScheme::experimentalFeature() const

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>
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;
@@ -668,7 +669,7 @@ struct GitRepoImpl : GitRepo, std::enable_shared_from_this<GitRepoImpl>
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()}}};
@@ -716,15 +717,17 @@ struct GitSourceAccessor : SourceAccessor
ref<GitRepoImpl> repo;
Object root;
std::optional<lfs::Fetch> lfsFetch = std::nullopt;
GitAccessorOptions options;
};
Sync<State> state_;
GitSourceAccessor(ref<GitRepoImpl> repo_, const Hash & rev, bool smudgeLfs)
GitSourceAccessor(ref<GitRepoImpl> repo_, const Hash & rev, const GitAccessorOptions & options)
: state_{State{
.repo = repo_,
.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());
return make_ref<GitSourceAccessor>(self, rev, smudgeLfs);
return make_ref<GitSourceAccessor>(self, rev, options);
}
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());
ref<GitSourceAccessor> rawGitAccessor = getRawAccessor(rev, smudgeLfs);
ref<GitSourceAccessor> rawGitAccessor = getRawAccessor(rev, options);
rawGitAccessor->setPathDisplay(std::move(displayPrefix));
if (exportIgnore)
if (options.exportIgnore)
return make_ref<GitExportIgnoreSourceAccessor>(self, rawGitAccessor, rev);
else
return rawGitAccessor;
}
ref<SourceAccessor>
GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError)
ref<SourceAccessor> GitRepoImpl::getAccessor(
const WorkdirInfo & wd, const GitAccessorOptions & options, MakeNotAllowedError makeNotAllowedError)
{
auto self = ref<GitRepoImpl>(shared_from_this());
ref<SourceAccessor> fileAccessor = AllowListSourceAccessor::create(
@@ -1283,10 +1286,9 @@ GitRepoImpl::getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllow
boost::unordered_flat_set<CanonPath>{CanonPath::root},
std::move(makeNotAllowedError))
.cast<SourceAccessor>();
if (exportIgnore)
return make_ref<GitExportIgnoreSourceAccessor>(self, fileAccessor, std::nullopt);
else
return fileAccessor;
if (options.exportIgnore)
fileAccessor = make_ref<GitExportIgnoreSourceAccessor>(self, fileAccessor, std::nullopt);
return fileAccessor;
}
ref<GitFileSystemObjectSink> GitRepoImpl::getFileSystemObjectSink()
@@ -1299,7 +1301,7 @@ std::vector<std::tuple<GitRepoImpl::Submodule, Hash>> GitRepoImpl::getSubmodules
/* Read the .gitmodules files from this revision. */
CanonPath modulesFile(".gitmodules");
auto accessor = getAccessor(rev, exportIgnore, "");
auto accessor = getAccessor(rev, {.exportIgnore = exportIgnore}, "");
if (!accessor->pathExists(modulesFile))
return {};
@@ -1316,7 +1318,7 @@ std::vector<std::tuple<GitRepoImpl::Submodule, Hash>> GitRepoImpl::getSubmodules
std::vector<std::tuple<Submodule, Hash>> result;
auto rawAccessor = getRawAccessor(rev);
auto rawAccessor = getRawAccessor(rev, {});
for (auto & submodule : parseSubmodules(pathTemp)) {
/* Filter out .gitmodules entries that don't exist or are not
@@ -1332,10 +1334,8 @@ namespace fetchers {
ref<GitRepo> Settings::getTarballCache() const
{
auto tarballCache(_tarballCache.lock());
if (!*tarballCache)
*tarballCache = GitRepo::openRepo(std::filesystem::path(getCacheDir()) / "tarball-cache", true, true);
return ref<GitRepo>(*tarballCache);
static auto repoDir = std::filesystem::path(getCacheDir()) / "tarball-cache";
return GitRepo::openRepo(repoDir, true, true);
}
} // namespace fetchers

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>
getAccessorFromCommit(const Settings & settings, Store & store, RepoInfo & repoInfo, Input && input) const
{
@@ -786,7 +796,7 @@ struct GitInputScheme : InputScheme
auto origRev = input.getRev();
auto originalRef = input.getRef();
bool shallow = getShallowAttr(input);
bool shallow = canDoShallow(input);
auto ref = originalRef ? *originalRef : getDefaultRef(repoInfo, shallow);
input.attrs.insert_or_assign("ref", ref);
@@ -797,11 +807,27 @@ struct GitInputScheme : InputScheme
if (!input.getRev())
input.attrs.insert_or_assign("rev", GitRepo::openRepo(repoDir)->resolveRef(ref).gitRev());
} else {
auto rev = input.getRev();
auto repoUrl = std::get<ParsedURL>(repoInfo.location);
std::filesystem::path cacheDir = getCachePath(repoUrl.to_string(), shallow);
repoDir = cacheDir;
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());
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
repo. */
if (auto rev = input.getRev()) {
if (rev) {
doFetch = !repo->hasObject(*rev);
} else {
if (getAllRefsAttr(input)) {
@@ -831,7 +857,6 @@ struct GitInputScheme : InputScheme
}
if (doFetch) {
bool shallow = getShallowAttr(input);
try {
auto fetchRef = getAllRefsAttr(input) ? "refs/*:refs/*"
: input.getRev() ? input.getRev()->gitRev()
@@ -859,7 +884,7 @@ struct GitInputScheme : InputScheme
warn("could not update cached head '%s' for '%s'", ref, repoInfo.locationToArg());
}
if (auto rev = input.getRev()) {
if (rev) {
if (!repo->hasObject(*rev))
throw Error(
"Cannot find Git revision '%s' in ref '%s' of repository '%s'! "
@@ -876,23 +901,30 @@ struct GitInputScheme : InputScheme
// the remainder
}
have_rev:
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?
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));
}
printTalkative("using revision %s of repo '%s'", rev.gitRev(), repoInfo.locationToArg());
@@ -900,7 +932,8 @@ struct GitInputScheme : InputScheme
bool exportIgnore = getExportIgnoreAttr(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
input accessor consisting of the accessor for the top-level
@@ -967,7 +1000,7 @@ struct GitInputScheme : InputScheme
auto exportIgnore = getExportIgnoreAttr(input);
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
consisting of the accessor for the top-level repo and the

View File

@@ -351,7 +351,7 @@ struct GitArchiveInputScheme : InputScheme
input.attrs.insert_or_assign("lastModified", uint64_t(tarballInfo.lastModified));
auto accessor =
settings.getTarballCache()->getAccessor(tarballInfo.treeHash, false, "«" + input.to_string() + "»");
settings.getTarballCache()->getAccessor(tarballInfo.treeHash, {}, "«" + input.to_string() + "»");
return {accessor, input};
}

View File

@@ -135,8 +135,6 @@ struct Settings : public Config
private:
mutable Sync<std::shared_ptr<Cache>> _cache;
mutable Sync<std::shared_ptr<GitRepo>> _tarballCache;
};
} // namespace nix::fetchers

View File

@@ -22,6 +22,12 @@ struct GitFileSystemObjectSink : ExtendedFileSystemObjectSink
virtual Hash flush() = 0;
};
struct GitAccessorOptions
{
bool exportIgnore = false;
bool smudgeLfs = false;
};
struct GitRepo
{
virtual ~GitRepo() {}
@@ -89,10 +95,10 @@ struct GitRepo
virtual bool hasObject(const Hash & oid) = 0;
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>
getAccessor(const WorkdirInfo & wd, bool exportIgnore, MakeNotAllowedError makeNotAllowedError) = 0;
virtual ref<SourceAccessor> getAccessor(
const WorkdirInfo & wd, const GitAccessorOptions & options, MakeNotAllowedError makeNotAllowedError) = 0;
virtual ref<GitFileSystemObjectSink> getFileSystemObjectSink() = 0;

View File

@@ -136,7 +136,7 @@ static DownloadTarballResult downloadTarball_(
.treeHash = treeHash,
.lastModified = (time_t) getIntAttr(infoAttrs, "lastModified"),
.immutableUrl = maybeGetStrAttr(infoAttrs, "immutableUrl"),
.accessor = settings.getTarballCache()->getAccessor(treeHash, false, displayPrefix),
.accessor = settings.getTarballCache()->getAccessor(treeHash, {}, displayPrefix),
};
};

View File

@@ -200,7 +200,7 @@ nix_value * nix_locked_flake_get_output_attrs(
nix_clear_err(context);
try {
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;
}
NIXC_CATCH_ERRS_NULL

View File

@@ -93,7 +93,7 @@ static void prim_parseFlakeRef(EvalState & state, const PosIdx pos, Value ** arg
auto & vv = binds.alloc(s);
std::visit(
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 Explicit<bool> & value) { vv.mkBool(value.t); }},
value);
@@ -156,7 +156,7 @@ static void prim_flakeRefToString(EvalState & state, const PosIdx pos, Value **
}
}
auto flakeRef = FlakeRef::fromAttrs(state.fetchSettings, attrs);
v.mkString(flakeRef.to_string());
v.mkString(flakeRef.to_string(), state.mem);
}
nix::PrimOp flakeRefToString({

View File

@@ -956,7 +956,7 @@ void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & vRes)
auto key = keyMap.find(node);
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);
}
@@ -966,7 +966,7 @@ void callFlake(EvalState & state, const LockedFlake & lockedFlake, Value & vRes)
Value * vCallFlake = requireInternalFile(state, CanonPath("call-flake.nix"));
auto vLocks = state.allocValue();
vLocks->mkString(lockFileStr);
vLocks->mkString(lockFileStr, state.mem);
auto vFetchFinalTree = get(state.internalPrimOps, "fetchFinalTree");
assert(vFetchFinalTree);

View File

@@ -99,7 +99,7 @@ TEST(references, scanForReferencesDeep)
// Create an in-memory file system with various reference patterns
auto accessor = make_ref<MemorySourceAccessor>();
accessor->root = File::Directory{
.contents{
.entries{
{
// file1.txt: contains hash1
"file1.txt",
@@ -125,7 +125,7 @@ TEST(references, scanForReferencesDeep)
// subdir: a subdirectory
"subdir",
File::Directory{
.contents{
.entries{
{
// subdir/file4.txt: contains hash1 again
"file4.txt",

View File

@@ -8,7 +8,7 @@
#include "nix/util/sync.hh"
#include "nix/store/remote-fs-accessor.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/callback.hh"
#include "nix/util/signals.hh"
@@ -208,7 +208,7 @@ ref<const ValidPathInfo> BinaryCacheStore::addToStoreCommon(
if (config.writeNARListing) {
nlohmann::json j = {
{"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");

View File

@@ -48,7 +48,7 @@ struct curlFileTransfer : public FileTransfer
std::random_device rd;
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;
FileTransferRequest request;
@@ -60,6 +60,7 @@ struct curlFileTransfer : public FileTransfer
// buffer to accompany the `req` above
char errbuf[CURL_ERROR_SIZE];
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;
unsigned int attempt = 0;
@@ -116,7 +117,13 @@ struct curlFileTransfer : public FileTransfer
successful response. */
if (successfulStatuses.count(httpStatus)) {
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
this->result.data.append(data);
@@ -195,6 +202,14 @@ struct curlFileTransfer : public FileTransfer
}
(*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;
} catch (...) {
@@ -364,6 +379,15 @@ struct curlFileTransfer : public FileTransfer
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()
{
if (!req)
@@ -624,7 +648,7 @@ struct curlFileTransfer : public FileTransfer
errorSink.reset();
embargo = std::chrono::steady_clock::now() + std::chrono::milliseconds(ms);
try {
fileTransfer.enqueueItem(shared_from_this());
fileTransfer.enqueueItem(ref{shared_from_this()});
} catch (const nix::Error & e) {
// If enqueue fails (e.g., during shutdown), fail the transfer properly
// instead of letting the exception propagate, which would leave done=false
@@ -641,24 +665,24 @@ struct curlFileTransfer : public FileTransfer
{
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;
}
};
std::
priority_queue<std::shared_ptr<TransferItem>, std::vector<std::shared_ptr<TransferItem>>, EmbargoComparator>
incoming;
std::priority_queue<ref<TransferItem>, std::vector<ref<TransferItem>>, EmbargoComparator> incoming;
std::vector<ref<TransferItem>> unpause;
private:
bool quitting = false;
public:
void quit()
{
quitting = true;
/* We wil not be processing any more incoming requests */
/* We will not be processing any more incoming requests */
while (!incoming.empty())
incoming.pop();
unpause.clear();
}
bool isQuitting()
@@ -827,6 +851,17 @@ struct curlFileTransfer : public FileTransfer
item->active = true;
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");
@@ -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"
&& item->request.uri.scheme() != "s3")
@@ -866,19 +901,34 @@ struct curlFileTransfer : public FileTransfer
#ifndef _WIN32 // TODO need graceful async exit support on Windows?
writeFull(wakeupPipe.writeSide.get(), " ");
#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 */
if (request.uri.scheme() == "s3") {
auto modifiedRequest = request;
modifiedRequest.setupForS3();
enqueueItem(std::make_shared<TransferItem>(*this, std::move(modifiedRequest), std::move(callback)));
return;
return enqueueItem(make_ref<TransferItem>(*this, std::move(modifiedRequest), std::move(callback)));
}
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
{
bool quit = false;
bool paused = false;
std::exception_ptr exc;
std::string data;
std::condition_variable avail, request;
@@ -990,31 +1041,38 @@ void FileTransfer::download(
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());
if (state->quit)
return;
/* 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));
}
return PauseTransfer::No;
/* Append data to the buffer and wake up the calling
thread. */
state->data.append(data);
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) {
auto state(_state->lock());
state->quit = true;
@@ -1047,6 +1105,10 @@ void FileTransfer::download(
return;
}
if (state->paused) {
unpauseTransfer(handle);
state->paused = false;
}
state.wait(state->avail);
if (state->data.empty())

View File

@@ -70,12 +70,12 @@ struct FileTransferSettings : Config
Setting<size_t> downloadBufferSize{
this,
64 * 1024 * 1024,
1 * 1024 * 1024,
"download-buffer-size",
R"(
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.
The default is 67108864 (64 MiB).
The default is 1048576 (1 MiB).
)"};
};
@@ -105,6 +105,11 @@ struct UsernameAuth
std::optional<std::string> password;
};
enum class PauseTransfer : bool {
No = false,
Yes = true,
};
struct FileTransferRequest
{
VerbatimURL uri;
@@ -136,7 +141,14 @@ struct FileTransferRequest
std::optional<UploadData> data;
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.
* When provided, these credentials will be used with curl's CURLOPT_USERNAME/PASSWORD option.
@@ -226,6 +238,25 @@ class Store;
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() {}
/**
@@ -233,7 +264,13 @@ struct FileTransfer
* the download. The future may throw a FileTransferError
* 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);

View File

@@ -55,7 +55,6 @@ headers = [ config_pub_h ] + files(
'machines.hh',
'make-content-addressed.hh',
'names.hh',
'nar-accessor.hh',
'nar-info-disk-cache.hh',
'nar-info.hh',
'outputs-spec.hh',

View File

@@ -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

View File

@@ -114,6 +114,17 @@ boost = dependency(
deps_other += boost
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
# seccomp only makes sense on Linux
@@ -300,7 +311,6 @@ sources = files(
'make-content-addressed.cc',
'misc.cc',
'names.cc',
'nar-accessor.cc',
'nar-info-disk-cache.cc',
'nar-info.cc',
'optimise-store.cc',

View File

@@ -1,6 +1,6 @@
#include <nlohmann/json.hpp>
#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/stat.h>
@@ -39,7 +39,7 @@ ref<SourceAccessor> RemoteFSAccessor::addToCache(std::string_view hashPart, std:
if (cacheDir != "") {
try {
nlohmann::json j = listNar(narAccessor, CanonPath::root, true);
nlohmann::json j = listNarDeep(*narAccessor, CanonPath::root);
writeFile(makeCacheFile(hashPart, "ls"), j.dump());
} catch (...) {
ignoreExceptionExceptInterrupt();

View File

@@ -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"
}

View File

@@ -0,0 +1,5 @@
{
"contents": "asdf",
"executable": false,
"type": "regular"
}

View File

@@ -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"
}

View File

@@ -0,0 +1,7 @@
{
"entries": {
"bar": {},
"foo": {}
},
"type": "directory"
}

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)
{
using File = MemorySourceAccessor::File;
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",
},
},
},
},
},
},
};
auto files = memory_source_accessor::exampleComplex();
for (const auto hashAlgo : {HashAlgorithm::SHA1, HashAlgorithm::SHA256}) {
std::map<Hash, std::string> cas;

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

View File

@@ -63,7 +63,9 @@ sources = files(
'json-utils.cc',
'logging.cc',
'lru-cache.cc',
'memory-source-accessor.cc',
'monitorfdhup.cc',
'nar-listing.cc',
'nix_api_util.cc',
'nix_api_util_internal.cc',
'pool.cc',

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

View File

@@ -134,6 +134,11 @@ std::optional<Path> getSelfExe()
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());
#else
return std::nullopt;

View File

@@ -37,7 +37,6 @@ void copyRecursive(SourceAccessor & accessor, const CanonPath & from, FileSystem
sink.createDirectory(to, [&](FileSystemObjectSink & dirSink, const CanonPath & relDirPath) {
for (auto & [name, _] : accessor.readDirectory(from)) {
copyRecursive(accessor, from / name, dirSink, relDirPath / name);
break;
}
});
break;

View File

@@ -6,15 +6,18 @@
#include "nix/util/experimental-features.hh"
// Following https://github.com/nlohmann/json#how-can-i-use-get-for-non-default-constructiblenon-copyable-types
#define JSON_IMPL(TYPE) \
namespace nlohmann { \
using namespace nix; \
template<> \
#define JSON_IMPL_INNER(TYPE) \
struct adl_serializer<TYPE> \
{ \
static TYPE from_json(const json & json); \
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) \

View File

@@ -4,59 +4,111 @@
#include "nix/util/source-path.hh"
#include "nix/util/fs-sink.hh"
#include "nix/util/variant-wrapper.hh"
#include "nix/util/json-impls.hh"
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.
*/
struct MemorySourceAccessor : virtual SourceAccessor
{
/**
* 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;
};
using File = fso::VariantT<std::string, true>;
std::optional<File> root;
@@ -89,19 +141,6 @@ struct MemorySourceAccessor : virtual SourceAccessor
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
*/
@@ -121,4 +160,53 @@ struct MemorySink : FileSystemObjectSink
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 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)

View File

@@ -50,6 +50,7 @@ headers = files(
'memory-source-accessor.hh',
'mounted-source-accessor.hh',
'muxable-pipe.hh',
'nar-accessor.hh',
'os-string.hh',
'pool.hh',
'pos-idx.hh',

View File

@@ -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

View File

@@ -1,4 +1,5 @@
#include "nix/util/memory-source-accessor.hh"
#include "nix/util/json-utils.hh"
namespace nix {
@@ -29,13 +30,13 @@ MemorySourceAccessor::File * MemorySourceAccessor::open(const CanonPath & path,
return nullptr;
auto & curDir = *curDirP;
auto i = curDir.contents.find(name);
if (i == curDir.contents.end()) {
auto i = curDir.entries.find(name);
if (i == curDir.entries.end()) {
if (!create)
return nullptr;
else {
newF = true;
i = curDir.contents.insert(
i = curDir.entries.insert(
i,
{
std::string{name},
@@ -68,25 +69,26 @@ bool MemorySourceAccessor::pathExists(const CanonPath & path)
return open(path, std::nullopt);
}
MemorySourceAccessor::Stat MemorySourceAccessor::File::lstat() const
template<>
SourceAccessor::Stat MemorySourceAccessor::File::lstat() const
{
return std::visit(
overloaded{
[](const Regular & r) {
return Stat{
.type = tRegular,
return SourceAccessor::Stat{
.type = SourceAccessor::tRegular,
.fileSize = r.contents.size(),
.isExecutable = r.executable,
};
},
[](const Directory &) {
return Stat{
.type = tDirectory,
return SourceAccessor::Stat{
.type = SourceAccessor::tDirectory,
};
},
[](const Symlink &) {
return Stat{
.type = tSymlink,
return SourceAccessor::Stat{
.type = SourceAccessor::tSymlink,
};
},
},
@@ -106,7 +108,7 @@ MemorySourceAccessor::DirEntries MemorySourceAccessor::readDirectory(const Canon
throw Error("file '%s' does not exist", path);
if (auto * d = std::get_if<File::Directory>(&f->raw)) {
DirEntries res;
for (auto & [name, file] : d->contents)
for (auto & [name, file] : d->entries)
res.insert_or_assign(name, file.lstat().type);
return res;
} else

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

View File

@@ -146,7 +146,9 @@ sources = [ config_priv_h ] + files(
'json-utils.cc',
'logging.cc',
'memory-source-accessor.cc',
'memory-source-accessor/json.cc',
'mounted-source-accessor.cc',
'nar-accessor.cc',
'pos-table.cc',
'position.cc',
'posix-source-accessor.cc',

View File

@@ -1,4 +1,4 @@
#include "nix/store/nar-accessor.hh"
#include "nix/util/nar-accessor.hh"
#include "nix/util/archive.hh"
#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);
json obj = json::object();
auto st = accessor.lstat(path);
switch (st.type) {
case SourceAccessor::Type::tRegular:
obj["type"] = "regular";
if (st.fileSize)
obj["size"] = *st.fileSize;
if (st.isExecutable)
obj["executable"] = true;
if (st.narOffset && *st.narOffset)
obj["narOffset"] = *st.narOffset;
break;
case SourceAccessor::Type::tDirectory:
obj["type"] = "directory";
{
obj["entries"] = json::object();
json & res2 = obj["entries"];
for (const auto & [name, type] : accessor->readDirectory(path)) {
if (recurse) {
res2[name] = listNar(accessor, path / name, true);
} else
res2[name] = json::object();
return typename ListNarResult<deep>::Regular{
.executable = st.isExecutable,
.contents =
NarListingRegularFile{
.fileSize = st.fileSize,
.narOffset = st.narOffset && *st.narOffset ? st.narOffset : std::nullopt,
},
};
case SourceAccessor::Type::tDirectory: {
typename ListNarResult<deep>::Directory dir;
for (const auto & [name, type] : accessor.readDirectory(path)) {
if constexpr (deep) {
dir.entries.emplace(name, listNarImpl<true>(accessor, path / name));
} else {
dir.entries.emplace(name, fso::Opaque{});
}
}
break;
return dir;
}
case SourceAccessor::Type::tSymlink:
obj["type"] = "symlink";
obj["target"] = accessor->readLink(path);
break;
return typename ListNarResult<deep>::Symlink{
.target = accessor.readLink(path),
};
case SourceAccessor::Type::tBlock:
case SourceAccessor::Type::tChar:
case SourceAccessor::Type::tSocket:
@@ -314,7 +312,16 @@ json listNar(ref<SourceAccessor> accessor, const CanonPath & path, bool recurse)
case SourceAccessor::Type::tUnknown:
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

View File

@@ -16,7 +16,7 @@ namespace nix {
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(

View File

@@ -322,7 +322,7 @@ static int main_build_remote(int argc, char ** argv)
// output ids, which break CA derivations
if (!drv.inputDrvs.map.empty())
drv.inputSrcs = store->parseStorePathSet(inputs);
optResult = sshStore->buildDerivation(*drvPath, (const BasicDerivation &) drv);
optResult = sshStore->buildDerivation(*drvPath, static_cast<const BasicDerivation &>(drv));
auto & result = *optResult;
if (auto * failureP = result.tryGetFailure()) {
if (settings.keepFailed) {

View File

@@ -1,6 +1,6 @@
#include "nix/cmd/command.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/source-accessor.hh"
@@ -80,7 +80,7 @@ struct CmdCatNar : StoreCommand, MixCat
throw SysError("opening NAR file '%s'", narPath);
auto source = FdSource{fd.get()};
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});
}
};

View File

@@ -1,6 +1,6 @@
#include "nix/cmd/command.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 <nlohmann/json.hpp>
@@ -85,7 +85,12 @@ struct MixLs : virtual Args, MixJSON
if (json) {
if (showDirectory)
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
listText(accessor, std::move(path));
}
@@ -150,7 +155,7 @@ struct CmdLsNar : Command, MixLs
throw SysError("opening NAR file '%s'", narPath);
auto source = FdSource{fd.get()};
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});
}
};

View File

@@ -271,7 +271,7 @@ static void showHelp(std::vector<std::string> subcommand, NixArgs & toplevel)
);
auto vDump = state.allocValue();
vDump->mkString(toplevel.dumpCli());
vDump->mkString(toplevel.dumpCli(), state.mem);
auto vRes = state.allocValue();
Value * args[]{&state.getBuiltin("false"), vDump};

View File

@@ -229,6 +229,7 @@ foreach linkname : nix_symlinks
env : {'MSYS' : 'winsymlinks:lnk'},
# TODO(Ericson2314): Don't do this once we have the `meson.override_find_program` working)
build_by_default : true,
depends : this_exe,
)
# TODO(Ericson3214): Doesn't yet work
#meson.override_find_program(linkname, t)
@@ -250,6 +251,7 @@ custom_target(
env : {'MSYS' : 'winsymlinks:lnk'},
# TODO(Ericson2314): Don't do this once we have the `meson.override_find_program` working)
build_by_default : true,
depends : this_exe,
)
# TODO(Ericson3214): Doesn't yet work
#meson.override_find_program(linkname, t)

View File

@@ -132,7 +132,7 @@ static void getAllExprs(EvalState & state, const SourcePath & path, StringSet &
}
/* Load the expression on demand. */
auto vArg = state.allocValue();
vArg->mkPath(path2);
vArg->mkPath(path2, state.mem);
if (seen.size() == maxAttrs)
throw Error("too many Nix expressions in directory '%1%'", path);
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)
{
auto v = state.allocValue();
v->mkString(value);
v->mkString(value, state.mem);
drv.setMeta(name, v);
}

View File

@@ -58,20 +58,20 @@ bool createUserEnv(
auto attrs = state.buildBindings(7 + outputs.size());
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();
if (!system.empty())
attrs.alloc(state.s.system).mkString(system);
attrs.alloc(state.s.outPath).mkString(state.store->printStorePath(i.queryOutPath()));
attrs.alloc(state.s.system).mkString(system, state.mem);
attrs.alloc(state.s.outPath).mkString(state.store->printStorePath(i.queryOutPath()), state.mem);
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.
auto outputsList = state.buildList(outputs.size());
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);
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);
/* This is only necessary when installing store paths, e.g.,

View File

@@ -59,7 +59,7 @@ std::string resolveMirrorUrl(EvalState & state, const std::string & url)
std::tuple<StorePath, Hash> prefetchFile(
ref<Store> store,
const VerbatimURL & url,
std::optional<std::string> name,
std::optional<std::string> maybeName,
HashAlgorithm hashAlgo,
std::optional<Hash> expectedHash,
bool unpack,
@@ -68,16 +68,21 @@ std::tuple<StorePath, Hash> prefetchFile(
ContentAddressMethod method =
unpack || executable ? ContentAddressMethod::Raw::NixArchive : ContentAddressMethod::Raw::Flat;
/* Figure out a name in the Nix store. */
if (!name) {
name = url.lastPathSegment();
if (!name || name->empty())
throw Error("cannot figure out file name for '%s'", url.to_string());
}
std::string name = maybeName
.or_else([&]() {
/* Figure out a name in the Nix store. */
auto derivedFromUrl = url.lastPathSegment();
if (!derivedFromUrl || derivedFromUrl->empty())
throw Error("cannot figure out file name for '%s'", url.to_string());
return derivedFromUrl;
})
.value();
try {
checkName(*name);
checkName(name);
} 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;
}
@@ -89,7 +94,7 @@ std::tuple<StorePath, Hash> prefetchFile(
if (expectedHash) {
hashAlgo = expectedHash->algo;
storePath =
store->makeFixedOutputPathFromCA(*name, ContentAddressWithReferences::fromParts(method, *expectedHash, {}));
store->makeFixedOutputPathFromCA(name, ContentAddressWithReferences::fromParts(method, *expectedHash, {}));
if (store->isValidPath(*storePath))
hash = expectedHash;
else
@@ -138,7 +143,7 @@ std::tuple<StorePath, Hash> prefetchFile(
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;
assert(info.ca);
hash = info.ca->hash;

View File

@@ -32,6 +32,8 @@ writeSimpleFlake() {
baseName = builtins.baseNameOf ./.;
root = ./.;
number = 123;
};
}
EOF

View File

@@ -34,6 +34,7 @@ suites += {
'source-paths.sh',
'old-lockfiles.sh',
'trace-ifd.sh',
'shallow.sh',
],
'workdir' : meson.current_source_dir(),
}

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 ]]

View File

@@ -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

View File

@@ -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"

View File

@@ -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;

View File

@@ -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"
)

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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

View File

@@ -131,6 +131,7 @@ in
start_all()
machine.wait_for_unit("nginx.service")
machine.wait_for_open_port(80)
# Original test: zstd archive with gzip content-encoding
# Make sure that the file is properly compressed as the test would be meaningless otherwise

View File

@@ -1,5 +1,5 @@
{
description = "fetchTree fetches git repos shallowly by default";
description = "fetchTree fetches git repos shallowly if possible";
script = ''
# purge nix git cache to make sure we start with a clean slate
client.succeed("rm -rf ~/.cache/nix")
@@ -28,6 +28,7 @@
type = "git";
url = "{repo.remote}";
rev = "{commit2_rev}";
revCount = 1234;
}}
"""

View File

@@ -99,7 +99,6 @@ in
# 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?revCount=789")
machine.fail("nix flake metadata --json http://localhost/tags/latest.tar.gz?narHash=sha256-tbudgBSg+bHWHiHnlteNzN8TUvI80ygS9IULh4rklEw=")
'';