Merge remote-tracking branch 'origin/master' into handle-missing-gc-socket

This commit is contained in:
Eelco Dolstra 2024-01-16 13:18:58 +01:00
commit 302625e83b
68 changed files with 1721 additions and 556 deletions

View file

@ -20,12 +20,12 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: cachix/install-nix-action@v24
- uses: cachix/install-nix-action@v25
with:
# The sandbox would otherwise be disabled by default on Darwin
extra_nix_config: "sandbox = true"
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/cachix-action@v13
- uses: cachix/cachix-action@v14
if: needs.check_secrets.outputs.cachix == 'true'
with:
name: '${{ env.CACHIX_NAME }}'
@ -62,10 +62,10 @@ jobs:
with:
fetch-depth: 0
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/install-nix-action@v24
- uses: cachix/install-nix-action@v25
with:
install_url: https://releases.nixos.org/nix/nix-2.13.3/install
- uses: cachix/cachix-action@v13
- uses: cachix/cachix-action@v14
with:
name: '${{ env.CACHIX_NAME }}'
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
@ -84,7 +84,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- uses: cachix/install-nix-action@v24
- uses: cachix/install-nix-action@v25
with:
install_url: '${{needs.installer.outputs.installerURL}}'
install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve"
@ -114,12 +114,12 @@ jobs:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: cachix/install-nix-action@v24
- uses: cachix/install-nix-action@v25
with:
install_url: https://releases.nixos.org/nix/nix-2.13.3/install
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
- run: echo NIX_VERSION="$(nix --experimental-features 'nix-command flakes' eval .\#default.version | tr -d \")" >> $GITHUB_ENV
- uses: cachix/cachix-action@v13
- uses: cachix/cachix-action@v14
if: needs.check_secrets.outputs.cachix == 'true'
with:
name: '${{ env.CACHIX_NAME }}'

View file

@ -29,7 +29,6 @@ LOWDOWN_LIBS = @LOWDOWN_LIBS@
OPENSSL_LIBS = @OPENSSL_LIBS@
PACKAGE_NAME = @PACKAGE_NAME@
PACKAGE_VERSION = @PACKAGE_VERSION@
RAPIDCHECK_HEADERS = @RAPIDCHECK_HEADERS@
SHELL = @bash@
SODIUM_LIBS = @SODIUM_LIBS@
SQLITE3_LIBS = @SQLITE3_LIBS@

View file

@ -353,27 +353,8 @@ AS_IF([test "$ENABLE_UNIT_TESTS" == "yes"],[
# Look for gtest.
PKG_CHECK_MODULES([GTEST], [gtest_main])
# Look for rapidcheck.
AC_ARG_VAR([RAPIDCHECK_HEADERS], [include path of gtest headers shipped by RAPIDCHECK])
# No pkg-config yet, https://github.com/emil-e/rapidcheck/issues/302
AC_LANG_PUSH(C++)
AC_SUBST(RAPIDCHECK_HEADERS)
[CXXFLAGS="-I $RAPIDCHECK_HEADERS $CXXFLAGS"]
[LIBS="-lrapidcheck -lgtest $LIBS"]
AC_CHECK_HEADERS([rapidcheck/gtest.h], [], [], [#include <gtest/gtest.h>])
dnl AC_CHECK_LIB doesn't work for C++ libs with mangled symbols
AC_LINK_IFELSE([
AC_LANG_PROGRAM([[
#include <gtest/gtest.h>
#include <rapidcheck/gtest.h>
]], [[
return RUN_ALL_TESTS();
]])
],
[],
[AC_MSG_ERROR([librapidcheck is not found.])])
AC_LANG_POP(C++)
PKG_CHECK_MODULES([RAPIDCHECK], [rapidcheck rapidcheck_gtest])
])

View file

@ -81,7 +81,7 @@ EXPAND_ONLY_PREDEF = YES
# RECURSIVE has no effect here.
# This tag requires that the tag SEARCH_INCLUDES is set to YES.
INCLUDE_PATH = @RAPIDCHECK_HEADERS@
INCLUDE_PATH =
# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
# tag can be used to specify a list of macro names that should be expanded. The

View file

@ -3,4 +3,6 @@ synopsis: "`nix profile` now allows referring to elements by human-readable name
prs: 8678
---
[`nix profile`](@docroot@/command-ref/new-cli/nix3-profile.md) now uses names to refer to installed packages when running [`list`](@docroot@/command-ref/new-cli/nix3-profile-list.md), [`remove`](@docroot@/command-ref/new-cli/nix3-profile-remove.md) or [`upgrade`](@docroot@/command-ref/new-cli/nix3-profile-upgrade.md) as opposed to indices. Indices are deprecated and will be removed in a future version.
[`nix profile`](@docroot@/command-ref/new-cli/nix3-profile.md) now uses names to refer to installed packages when running [`list`](@docroot@/command-ref/new-cli/nix3-profile-list.md), [`remove`](@docroot@/command-ref/new-cli/nix3-profile-remove.md) or [`upgrade`](@docroot@/command-ref/new-cli/nix3-profile-upgrade.md) as opposed to indices. Profile element names are generated when a package is installed and remain the same until the package is removed.
**Warning**: The `manifest.nix` file used to record the contents of profiles has changed. Nix will automatically upgrade profiles to the new version when you modify the profile. After that, the profile can no longer be used by older versions of Nix.

View file

@ -35,13 +35,50 @@ standard input.
- `--parse`\
Just parse the input files, and print their abstract syntax trees on
standard output in ATerm format.
standard output as a Nix expression.
- `--eval`\
Just parse and evaluate the input files, and print the resulting
values on standard output. No instantiation of store derivations
takes place.
> **Warning**
>
> This option produces ambiguous output which is not suitable for machine
> consumption. For example, these two Nix expressions print the same result
> despite having different types:
>
> ```console
> $ nix-instantiate --eval --expr '{ a = {}; }'
> { a = <CODE>; }
> $ nix-instantiate --eval --expr '{ a = <CODE>; }'
> { a = <CODE>; }
> ```
>
> For human-readable output, `nix eval` (experimental) is more informative:
>
> ```console
> $ nix-instantiate --eval --expr 'a: a'
> <LAMBDA>
> $ nix eval --expr 'a: a'
> «lambda @ «string»:1:1»
> ```
>
> For machine-readable output, the `--xml` option produces unambiguous
> output:
>
> ```console
> $ nix-instantiate --eval --xml --expr '{ foo = <CODE>; }'
> <?xml version='1.0' encoding='utf-8'?>
> <expr>
> <attrs>
> <attr column="3" line="1" name="foo">
> <unevaluated />
> </attr>
> </attrs>
> </expr>
> ```
- `--find-file`\
Look up the given files in Nixs search path (as specified by the
`NIX_PATH` environment variable). If found, print the corresponding
@ -61,11 +98,11 @@ standard input.
- `--json`\
When used with `--eval`, print the resulting value as an JSON
representation of the abstract syntax tree rather than as an ATerm.
representation of the abstract syntax tree rather than as a Nix expression.
- `--xml`\
When used with `--eval`, print the resulting value as an XML
representation of the abstract syntax tree rather than as an ATerm.
representation of the abstract syntax tree rather than as a Nix expression.
The schema is the same as that used by the [`toXML`
built-in](../language/builtins.md).
@ -133,28 +170,29 @@ $ nix-instantiate --eval --xml --expr '1 + 2'
The difference between non-strict and strict evaluation:
```console
$ nix-instantiate --eval --xml --expr 'rec { x = "foo"; y = x; }'
...
<attr name="x">
<string value="foo" />
</attr>
<attr name="y">
<unevaluated />
</attr>
...
$ nix-instantiate --eval --xml --expr '{ x = {}; }'
<?xml version='1.0' encoding='utf-8'?>
<expr>
<attrs>
<attr column="3" line="1" name="x">
<unevaluated />
</attr>
</attrs>
</expr>
```
Note that `y` is left unevaluated (the XML representation doesnt
attempt to show non-normal forms).
```console
$ nix-instantiate --eval --xml --strict --expr 'rec { x = "foo"; y = x; }'
...
<attr name="x">
<string value="foo" />
</attr>
<attr name="y">
<string value="foo" />
</attr>
...
$ nix-instantiate --eval --xml --strict --expr '{ x = {}; }'
<?xml version='1.0' encoding='utf-8'?>
<expr>
<attrs>
<attr column="3" line="1" name="x">
<attrs>
</attrs>
</attr>
</attrs>
</expr>
```

View file

@ -51,11 +51,14 @@ To install it in `$(pwd)/outputs` and test it:
```console
[nix-shell]$ make install
[nix-shell]$ make installcheck -j $NIX_BUILD_CORES
[nix-shell]$ make installcheck check -j $NIX_BUILD_CORES
[nix-shell]$ nix --version
nix (Nix) 2.12
```
For more information on running and filtering tests, see
[`testing.md`](./testing.md).
To build a release version of Nix for the current operating system and CPU architecture:
```console

View file

@ -77,7 +77,7 @@ there is no risk of any build-system wildcards for the library accidentally pick
### Running tests
You can run the whole testsuite with `make check`, or the tests for a specific component with `make libfoo-tests_RUN`.
Finer-grained filtering is also possible using the [--gtest_filter](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) command-line option, or the `GTEST_FILTER` environment variable.
Finer-grained filtering is also possible using the [--gtest_filter](https://google.github.io/googletest/advanced.html#running-a-subset-of-the-tests) command-line option, or the `GTEST_FILTER` environment variable, e.g. `GTEST_FILTER='ErrorTraceTest.*' make check`.
### Characterisation testing { #characaterisation-testing-unit }

View file

@ -3,10 +3,10 @@
- [derivation]{#gloss-derivation}
A description of a build task. The result of a derivation is a
store object. Derivations are typically specified in Nix expressions
store object. Derivations declared in Nix expressions are specified
using the [`derivation` primitive](./language/derivations.md). These are
translated into low-level *store derivations* (implicitly by
`nix-env` and `nix-build`, or explicitly by `nix-instantiate`).
`nix-build`, or explicitly by `nix-instantiate`).
[derivation]: #gloss-derivation
@ -14,6 +14,7 @@
A [derivation] represented as a `.drv` file in the [store].
It has a [store path], like any [store object].
It is the [instantiated][instantiate] form of a derivation.
Example: `/nix/store/g946hcz4c8mdvq2g8vxx42z51qb71rvp-git-2.38.1.drv`
@ -23,9 +24,9 @@
- [instantiate]{#gloss-instantiate}, instantiation
Translate a [derivation] into a [store derivation].
Save an evaluated [derivation] as a [store derivation] in the Nix [store].
See [`nix-instantiate`](./command-ref/nix-instantiate.md).
See [`nix-instantiate`](./command-ref/nix-instantiate.md), which produces a store derivation from a Nix expression that evaluates to a derivation.
[instantiate]: #gloss-instantiate
@ -66,7 +67,7 @@
From the perspective of the location where Nix is invoked, the Nix store can be referred to _local_ or _remote_.
Only a [local store]{#gloss-local-store} exposes a location in the file system of the machine where Nix is invoked that allows access to store objects, typically `/nix/store`.
Local stores can be used for building [derivations](#derivation).
Local stores can be used for building [derivations](#gloss-derivation).
See [Local Store](@docroot@/command-ref/new-cli/nix3-help-stores.md#local-store) for details.
[store]: #gloss-store
@ -168,9 +169,10 @@
A high-level description of software packages and compositions
thereof. Deploying software using Nix entails writing Nix
expressions for your packages. Nix expressions are translated to
derivations that are stored in the Nix store. These derivations can
then be built.
expressions for your packages. Nix expressions specify [derivations][derivation],
which are [instantiated][instantiate] into the Nix store as [store derivations][store derivation].
These derivations can then be [realised][realise] to produce
[outputs][output].
- [reference]{#gloss-reference}
@ -222,6 +224,9 @@
The [store derivation] that produced an [output path].
The deriver for an output path can be queried with the `--deriver` option to
[`nix-store --query`](@docroot@/command-ref/nix-store/query.md).
- [validity]{#gloss-validity}
A store path is valid if all [store object]s in its [closure] can be read from the [store].

View file

@ -34,11 +34,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1704018918,
"narHash": "sha256-erjg/HrpC9liEfm7oLqb8GXCqsxaFwIIPqCsknW5aFY=",
"lastModified": 1705033721,
"narHash": "sha256-K5eJHmL1/kev6WuqyqqbS1cdNnSidIZ3jeqJ7GbrYnQ=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "2c9c58e98243930f8cb70387934daa4bc8b00373",
"rev": "a1982c92d8980a0114372973cbdfe0a307f1bdea",
"type": "github"
},
"original": {

View file

@ -236,7 +236,6 @@ in {
openssl
sqlite
xz
] ++ lib.optionals (!stdenv.hostPlatform.isWindows) [
({ inherit readline editline; }.${readlineFlavor})
] ++ lib.optionals enableMarkdown [
lowdown
@ -310,7 +309,7 @@ in {
] ++ lib.optional (doBuild && stdenv.isLinux && !(stdenv.hostPlatform.isStatic && stdenv.system == "aarch64-linux"))
"LDFLAGS=-fuse-ld=gold"
++ lib.optional (doBuild && stdenv.hostPlatform.isStatic) "--enable-embedded-sandbox-shell"
++ lib.optional buildUnitTests "RAPIDCHECK_HEADERS=${lib.getDev rapidcheck}/extras/gtest/include";
;
enableParallelBuilding = true;

View file

@ -93,9 +93,17 @@ struct NixRepl
void evalString(std::string s, Value & v);
void loadDebugTraceEnv(DebugTrace & dt);
typedef std::set<Value *> ValuesSeen;
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth);
std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen);
void printValue(std::ostream & str,
Value & v,
unsigned int maxDepth = std::numeric_limits<unsigned int>::max())
{
::nix::printValue(*state, str, v, PrintOptions {
.ansiColors = true,
.force = true,
.derivationPaths = true,
.maxDepth = maxDepth
});
}
};
std::string removeWhitespace(std::string s)
@ -246,7 +254,7 @@ void NixRepl::mainLoop()
rl_readline_name = "nix-repl";
try {
createDirs(dirOf(historyFile));
} catch (SysError & e) {
} catch (SystemError & e) {
logWarning(e.info());
}
#ifndef USE_READLINE
@ -708,7 +716,8 @@ bool NixRepl::processLine(std::string line)
else if (command == ":p" || command == ":print") {
Value v;
evalString(arg, v);
printValue(std::cout, v, 1000000000) << std::endl;
printValue(std::cout, v);
std::cout << std::endl;
}
else if (command == ":q" || command == ":quit") {
@ -770,7 +779,8 @@ bool NixRepl::processLine(std::string line)
} else {
Value v;
evalString(line, v);
printValue(std::cout, v, 1) << std::endl;
printValue(std::cout, v, 1);
std::cout << std::endl;
}
}
@ -892,144 +902,6 @@ void NixRepl::evalString(std::string s, Value & v)
}
std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth)
{
ValuesSeen seen;
return printValue(str, v, maxDepth, seen);
}
// FIXME: lot of cut&paste from Nix's eval.cc.
std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen)
{
str.flush();
checkInterrupt();
state->forceValue(v, v.determinePos(noPos));
switch (v.type()) {
case nInt:
str << ANSI_CYAN << v.integer << ANSI_NORMAL;
break;
case nBool:
str << ANSI_CYAN;
printLiteralBool(str, v.boolean);
str << ANSI_NORMAL;
break;
case nString:
str << ANSI_WARNING;
printLiteralString(str, v.string_view());
str << ANSI_NORMAL;
break;
case nPath:
str << ANSI_GREEN << v.path().to_string() << ANSI_NORMAL; // !!! escaping?
break;
case nNull:
str << ANSI_CYAN "null" ANSI_NORMAL;
break;
case nAttrs: {
seen.insert(&v);
bool isDrv = state->isDerivation(v);
if (isDrv) {
str << "«derivation ";
Bindings::iterator i = v.attrs->find(state->sDrvPath);
NixStringContext context;
if (i != v.attrs->end())
str << state->store->printStorePath(state->coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
else
str << "???";
str << "»";
}
else if (maxDepth > 0) {
str << "{ ";
typedef std::map<std::string, Value *> Sorted;
Sorted sorted;
for (auto & i : *v.attrs)
sorted.emplace(state->symbols[i.name], i.value);
for (auto & i : sorted) {
printAttributeName(str, i.first);
str << " = ";
if (seen.count(i.second))
str << "«repeated»";
else
try {
printValue(str, *i.second, maxDepth - 1, seen);
} catch (AssertionError & e) {
str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
}
str << "; ";
}
str << "}";
} else
str << "{ ... }";
break;
}
case nList:
seen.insert(&v);
str << "[ ";
if (maxDepth > 0)
for (auto elem : v.listItems()) {
if (seen.count(elem))
str << "«repeated»";
else
try {
printValue(str, *elem, maxDepth - 1, seen);
} catch (AssertionError & e) {
str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL;
}
str << " ";
}
else
str << "... ";
str << "]";
break;
case nFunction:
if (v.isLambda()) {
std::ostringstream s;
s << state->positions[v.lambda.fun->pos];
str << ANSI_BLUE "«lambda @ " << filterANSIEscapes(s.str()) << "»" ANSI_NORMAL;
} else if (v.isPrimOp()) {
str << ANSI_MAGENTA "«primop»" ANSI_NORMAL;
} else if (v.isPrimOpApp()) {
str << ANSI_BLUE "«primop-app»" ANSI_NORMAL;
} else {
abort();
}
break;
case nFloat:
str << v.fpoint;
break;
case nThunk:
case nExternal:
default:
str << ANSI_RED "«unknown»" ANSI_NORMAL;
break;
}
return str;
}
std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<AnnotatedValues()> getValues)

View file

@ -105,117 +105,23 @@ RootValue allocRootValue(Value * v)
#endif
}
void Value::print(const SymbolTable &symbols, std::ostream &str,
std::set<const void *> *seen, int depth) const
{
checkInterrupt();
if (depth <= 0) {
str << "«too deep»";
return;
}
switch (internalType) {
case tInt:
str << integer;
break;
case tBool:
printLiteralBool(str, boolean);
break;
case tString:
printLiteralString(str, string_view());
break;
case tPath:
str << path().to_string(); // !!! escaping?
break;
case tNull:
str << "null";
break;
case tAttrs: {
if (seen && !attrs->empty() && !seen->insert(attrs).second)
str << "«repeated»";
else {
str << "{ ";
for (auto & i : attrs->lexicographicOrder(symbols)) {
str << symbols[i->name] << " = ";
i->value->print(symbols, str, seen, depth - 1);
str << "; ";
}
str << "}";
}
break;
}
case tList1:
case tList2:
case tListN:
if (seen && listSize() && !seen->insert(listElems()).second)
str << "«repeated»";
else {
str << "[ ";
for (auto v2 : listItems()) {
if (v2)
v2->print(symbols, str, seen, depth - 1);
else
str << "(nullptr)";
str << " ";
}
str << "]";
}
break;
case tThunk:
case tApp:
if (!isBlackhole()) {
str << "<CODE>";
} else {
// Although we know for sure that it's going to be an infinite recursion
// when this value is accessed _in the current context_, it's likely
// that the user will misinterpret a simpler «infinite recursion» output
// as a definitive statement about the value, while in fact it may be
// a valid value after `builtins.trace` and perhaps some other steps
// have completed.
str << "«potential infinite recursion»";
}
break;
case tLambda:
str << "<LAMBDA>";
break;
case tPrimOp:
str << "<PRIMOP>";
break;
case tPrimOpApp:
str << "<PRIMOP-APP>";
break;
case tExternal:
str << *external;
break;
case tFloat:
str << fpoint;
break;
default:
printError("Nix evaluator internal error: Value::print(): invalid value type %1%", internalType);
abort();
}
}
void Value::print(const SymbolTable &symbols, std::ostream &str,
bool showRepeated, int depth) const {
std::set<const void *> seen;
print(symbols, str, showRepeated ? nullptr : &seen, depth);
}
// Pretty print types for assertion errors
std::ostream & operator << (std::ostream & os, const ValueType t) {
os << showType(t);
return os;
}
std::string printValue(const EvalState & state, const Value & v)
std::string printValue(EvalState & state, Value & v)
{
std::ostringstream out;
v.print(state.symbols, out);
v.print(state, out);
return out.str();
}
void Value::print(EvalState & state, std::ostream & str, PrintOptions options)
{
printValue(state, str, *this, options);
}
const Value * getPrimOp(const Value &v) {
const Value * primOp = &v;
@ -710,6 +616,26 @@ void PrimOp::check()
}
std::ostream & operator<<(std::ostream & output, PrimOp & primOp)
{
output << "primop " << primOp.name;
return output;
}
PrimOp * Value::primOpAppPrimOp() const
{
Value * left = primOpApp.left;
while (left && !left->isPrimOp()) {
left = left->primOpApp.left;
}
if (!left)
return nullptr;
return left->primOp;
}
void Value::mkPrimOp(PrimOp * p)
{
p->check();

View file

@ -84,6 +84,8 @@ struct PrimOp
void check();
};
std::ostream & operator<<(std::ostream & output, PrimOp & primOp);
/**
* Info about a constant
*/
@ -127,7 +129,7 @@ std::unique_ptr<ValMap> mapStaticEnvBindings(const SymbolTable & st, const Stati
void copyContext(const Value & v, NixStringContext & context);
std::string printValue(const EvalState & state, const Value & v);
std::string printValue(EvalState & state, Value & v);
std::ostream & operator << (std::ostream & os, const ValueType t);

View file

@ -130,7 +130,7 @@ else { return ELSE; }
assert { return ASSERT; }
with { return WITH; }
let { return LET; }
in { return IN; }
in { return IN_KW; }
rec { return REC; }
inherit { return INHERIT; }
or { return OR_KW; }
@ -156,7 +156,7 @@ or { return OR_KW; }
.errPos = data->state.positions[CUR_POS],
});
}
return INT;
return INT_LIT;
}
{FLOAT} { errno = 0;
yylval->nf = strtod(yytext, 0);
@ -165,7 +165,7 @@ or { return OR_KW; }
.msg = hintfmt("invalid float '%1%'", yytext),
.errPos = data->state.positions[CUR_POS],
});
return FLOAT;
return FLOAT_LIT;
}
\$\{ { PUSH_STATE(DEFAULT); return DOLLAR_CURLY; }

View file

@ -365,11 +365,11 @@ void yyerror(YYLTYPE * loc, yyscan_t scanner, ParseData * data, const char * err
%type <id> attr
%token <id> ID
%token <str> STR IND_STR
%token <n> INT
%token <nf> FLOAT
%token <n> INT_LIT
%token <nf> FLOAT_LIT
%token <path> PATH HPATH SPATH PATH_END
%token <uri> URI
%token IF THEN ELSE ASSERT WITH LET IN REC INHERIT EQ NEQ AND OR IMPL OR_KW
%token IF THEN ELSE ASSERT WITH LET IN_KW REC INHERIT EQ NEQ AND OR IMPL OR_KW
%token DOLLAR_CURLY /* == ${ */
%token IND_STRING_OPEN IND_STRING_CLOSE
%token ELLIPSIS
@ -412,7 +412,7 @@ expr_function
{ $$ = new ExprAssert(CUR_POS, $2, $4); }
| WITH expr ';' expr_function
{ $$ = new ExprWith(CUR_POS, $2, $4); }
| LET binds IN expr_function
| LET binds IN_KW expr_function
{ if (!$2->dynamicAttrs.empty())
throw ParseError({
.msg = hintfmt("dynamic attributes not allowed in let"),
@ -482,8 +482,8 @@ expr_simple
else
$$ = new ExprVar(CUR_POS, data->symbols.create($1));
}
| INT { $$ = new ExprInt($1); }
| FLOAT { $$ = new ExprFloat($1); }
| INT_LIT { $$ = new ExprInt($1); }
| FLOAT_LIT { $$ = new ExprFloat($1); }
| '"' string_parts '"' { $$ = $2; }
| IND_STRING_OPEN ind_string_parts IND_STRING_CLOSE {
$$ = stripIndentation(CUR_POS, data->symbols, std::move(*$2));

View file

@ -3712,9 +3712,6 @@ static RegisterPrimOp primop_toString({
static void prim_substring(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
int start = state.forceInt(*args[0], pos, "while evaluating the first argument (the start offset) passed to builtins.substring");
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
NixStringContext context;
auto s = state.coerceToString(pos, *args[2], context, "while evaluating the third argument (the string) passed to builtins.substring");
if (start < 0)
state.debugThrowLastTrace(EvalError({
@ -3722,6 +3719,22 @@ static void prim_substring(EvalState & state, const PosIdx pos, Value * * args,
.errPos = state.positions[pos]
}));
int len = state.forceInt(*args[1], pos, "while evaluating the second argument (the substring length) passed to builtins.substring");
// Special-case on empty substring to avoid O(n) strlen
// This allows for the use of empty substrings to efficently capture string context
if (len == 0) {
state.forceValue(*args[2], pos);
if (args[2]->type() == nString) {
v.mkString("", args[2]->context());
return;
}
}
NixStringContext context;
auto s = state.coerceToString(pos, *args[2], context, "while evaluating the third argument (the string) passed to builtins.substring");
v.mkString((unsigned int) start >= s->size() ? "" : s->substr(start, len), context);
}

View file

@ -0,0 +1,100 @@
#include "print-ambiguous.hh"
#include "print.hh"
#include "signals.hh"
namespace nix {
// See: https://github.com/NixOS/nix/issues/9730
void printAmbiguous(
Value &v,
const SymbolTable &symbols,
std::ostream &str,
std::set<const void *> *seen,
int depth)
{
checkInterrupt();
if (depth <= 0) {
str << "«too deep»";
return;
}
switch (v.type()) {
case nInt:
str << v.integer;
break;
case nBool:
printLiteralBool(str, v.boolean);
break;
case nString:
printLiteralString(str, v.string_view());
break;
case nPath:
str << v.path().to_string(); // !!! escaping?
break;
case nNull:
str << "null";
break;
case nAttrs: {
if (seen && !v.attrs->empty() && !seen->insert(v.attrs).second)
str << "«repeated»";
else {
str << "{ ";
for (auto & i : v.attrs->lexicographicOrder(symbols)) {
str << symbols[i->name] << " = ";
printAmbiguous(*i->value, symbols, str, seen, depth - 1);
str << "; ";
}
str << "}";
}
break;
}
case nList:
if (seen && v.listSize() && !seen->insert(v.listElems()).second)
str << "«repeated»";
else {
str << "[ ";
for (auto v2 : v.listItems()) {
if (v2)
printAmbiguous(*v2, symbols, str, seen, depth - 1);
else
str << "(nullptr)";
str << " ";
}
str << "]";
}
break;
case nThunk:
if (!v.isBlackhole()) {
str << "<CODE>";
} else {
// Although we know for sure that it's going to be an infinite recursion
// when this value is accessed _in the current context_, it's likely
// that the user will misinterpret a simpler «infinite recursion» output
// as a definitive statement about the value, while in fact it may be
// a valid value after `builtins.trace` and perhaps some other steps
// have completed.
str << "«potential infinite recursion»";
}
break;
case nFunction:
if (v.isLambda()) {
str << "<LAMBDA>";
} else if (v.isPrimOp()) {
str << "<PRIMOP>";
} else if (v.isPrimOpApp()) {
str << "<PRIMOP-APP>";
}
break;
case nExternal:
str << *v.external;
break;
case nFloat:
str << v.fpoint;
break;
default:
printError("Nix evaluator internal error: printAmbiguous: invalid value type");
abort();
}
}
}

View file

@ -0,0 +1,24 @@
#pragma once
#include "value.hh"
namespace nix {
/**
* Print a value in the deprecated format used by `nix-instantiate --eval` and
* `nix-env` (for manifests).
*
* This output can't be changed because it's part of the `nix-instantiate` API,
* but it produces ambiguous output; unevaluated thunks and lambdas (and a few
* other types) are printed as Nix path syntax like `<CODE>`.
*
* See: https://github.com/NixOS/nix/issues/9730
*/
void printAmbiguous(
Value &v,
const SymbolTable &symbols,
std::ostream &str,
std::set<const void *> *seen,
int depth);
}

View file

@ -0,0 +1,52 @@
#pragma once
/**
* @file
* @brief Options for printing Nix values.
*/
#include <limits>
namespace nix {
/**
* Options for printing Nix values.
*/
struct PrintOptions
{
/**
* If true, output ANSI color sequences.
*/
bool ansiColors = false;
/**
* If true, force values.
*/
bool force = false;
/**
* If true and `force` is set, print derivations as
* `«derivation /nix/store/...»` instead of as attribute sets.
*/
bool derivationPaths = false;
/**
* If true, track which values have been printed and skip them on
* subsequent encounters. Useful for self-referential values.
*/
bool trackRepeated = true;
/**
* Maximum depth to evaluate to.
*/
size_t maxDepth = std::numeric_limits<size_t>::max();
/**
* Maximum number of attributes in an attribute set to print.
*/
size_t maxAttrs = std::numeric_limits<size_t>::max();
/**
* Maximum number of list items to print.
*/
size_t maxListItems = std::numeric_limits<size_t>::max();
/**
* Maximum string length to print.
*/
size_t maxStringLength = std::numeric_limits<size_t>::max();
};
}

View file

@ -1,24 +1,66 @@
#include "print.hh"
#include <limits>
#include <unordered_set>
#include "print.hh"
#include "ansicolor.hh"
#include "signals.hh"
#include "store-api.hh"
#include "terminal.hh"
#include "english.hh"
namespace nix {
std::ostream &
printLiteralString(std::ostream & str, const std::string_view string)
void printElided(
std::ostream & output,
unsigned int value,
const std::string_view single,
const std::string_view plural,
bool ansiColors)
{
if (ansiColors)
output << ANSI_FAINT;
output << " «";
pluralize(output, value, single, plural);
output << " elided»";
if (ansiColors)
output << ANSI_NORMAL;
}
std::ostream &
printLiteralString(std::ostream & str, const std::string_view string, size_t maxLength, bool ansiColors)
{
size_t charsPrinted = 0;
if (ansiColors)
str << ANSI_MAGENTA;
str << "\"";
for (auto i = string.begin(); i != string.end(); ++i) {
if (charsPrinted >= maxLength) {
str << "\"";
printElided(str, string.length() - charsPrinted, "byte", "bytes", ansiColors);
return str;
}
if (*i == '\"' || *i == '\\') str << "\\" << *i;
else if (*i == '\n') str << "\\n";
else if (*i == '\r') str << "\\r";
else if (*i == '\t') str << "\\t";
else if (*i == '$' && *(i+1) == '{') str << "\\" << *i;
else str << *i;
charsPrinted++;
}
str << "\"";
if (ansiColors)
str << ANSI_NORMAL;
return str;
}
std::ostream &
printLiteralString(std::ostream & str, const std::string_view string)
{
return printLiteralString(str, string, std::numeric_limits<size_t>::max(), false);
}
std::ostream &
printLiteralBool(std::ostream & str, bool boolean)
{
@ -90,5 +132,373 @@ printAttributeName(std::ostream & str, std::string_view name) {
return str;
}
bool isImportantAttrName(const std::string& attrName)
{
return attrName == "type" || attrName == "_type";
}
typedef std::pair<std::string, Value *> AttrPair;
struct ImportantFirstAttrNameCmp
{
bool operator()(const AttrPair& lhs, const AttrPair& rhs) const
{
auto lhsIsImportant = isImportantAttrName(lhs.first);
auto rhsIsImportant = isImportantAttrName(rhs.first);
return std::forward_as_tuple(!lhsIsImportant, lhs.first)
< std::forward_as_tuple(!rhsIsImportant, rhs.first);
}
};
typedef std::set<Value *> ValuesSeen;
class Printer
{
private:
std::ostream & output;
EvalState & state;
PrintOptions options;
std::optional<ValuesSeen> seen;
void printRepeated()
{
if (options.ansiColors)
output << ANSI_MAGENTA;
output << "«repeated»";
if (options.ansiColors)
output << ANSI_NORMAL;
}
void printNullptr()
{
if (options.ansiColors)
output << ANSI_MAGENTA;
output << "«nullptr»";
if (options.ansiColors)
output << ANSI_NORMAL;
}
void printElided(unsigned int value, const std::string_view single, const std::string_view plural)
{
::nix::printElided(output, value, single, plural, options.ansiColors);
}
void printInt(Value & v)
{
if (options.ansiColors)
output << ANSI_CYAN;
output << v.integer;
if (options.ansiColors)
output << ANSI_NORMAL;
}
void printFloat(Value & v)
{
if (options.ansiColors)
output << ANSI_CYAN;
output << v.fpoint;
if (options.ansiColors)
output << ANSI_NORMAL;
}
void printBool(Value & v)
{
if (options.ansiColors)
output << ANSI_CYAN;
printLiteralBool(output, v.boolean);
if (options.ansiColors)
output << ANSI_NORMAL;
}
void printString(Value & v)
{
printLiteralString(output, v.string_view(), options.maxStringLength, options.ansiColors);
}
void printPath(Value & v)
{
if (options.ansiColors)
output << ANSI_GREEN;
output << v.path().to_string(); // !!! escaping?
if (options.ansiColors)
output << ANSI_NORMAL;
}
void printNull()
{
if (options.ansiColors)
output << ANSI_CYAN;
output << "null";
if (options.ansiColors)
output << ANSI_NORMAL;
}
void printDerivation(Value & v)
{
try {
Bindings::iterator i = v.attrs->find(state.sDrvPath);
NixStringContext context;
std::string storePath;
if (i != v.attrs->end())
storePath = state.store->printStorePath(state.coerceToStorePath(i->pos, *i->value, context, "while evaluating the drvPath of a derivation"));
if (options.ansiColors)
output << ANSI_GREEN;
output << "«derivation";
if (!storePath.empty()) {
output << " " << storePath;
}
output << "»";
if (options.ansiColors)
output << ANSI_NORMAL;
} catch (BaseError & e) {
printError_(e);
}
}
void printAttrs(Value & v, size_t depth)
{
if (seen && !seen->insert(&v).second) {
printRepeated();
return;
}
if (options.force && options.derivationPaths && state.isDerivation(v)) {
printDerivation(v);
} else if (depth < options.maxDepth) {
output << "{ ";
std::vector<std::pair<std::string, Value *>> sorted;
for (auto & i : *v.attrs)
sorted.emplace_back(std::pair(state.symbols[i.name], i.value));
if (options.maxAttrs == std::numeric_limits<size_t>::max())
std::sort(sorted.begin(), sorted.end());
else
std::sort(sorted.begin(), sorted.end(), ImportantFirstAttrNameCmp());
size_t attrsPrinted = 0;
for (auto & i : sorted) {
if (attrsPrinted >= options.maxAttrs) {
printElided(sorted.size() - attrsPrinted, "attribute", "attributes");
break;
}
printAttributeName(output, i.first);
output << " = ";
print(*i.second, depth + 1);
output << "; ";
attrsPrinted++;
}
output << "}";
} else
output << "{ ... }";
}
void printList(Value & v, size_t depth)
{
if (seen && v.listSize() && !seen->insert(&v).second) {
printRepeated();
return;
}
output << "[ ";
if (depth < options.maxDepth) {
size_t listItemsPrinted = 0;
for (auto elem : v.listItems()) {
if (listItemsPrinted >= options.maxListItems) {
printElided(v.listSize() - listItemsPrinted, "item", "items");
break;
}
if (elem) {
print(*elem, depth + 1);
} else {
printNullptr();
}
output << " ";
listItemsPrinted++;
}
}
else
output << "... ";
output << "]";
}
void printFunction(Value & v)
{
if (options.ansiColors)
output << ANSI_BLUE;
output << "«";
if (v.isLambda()) {
output << "lambda";
if (v.lambda.fun) {
if (v.lambda.fun->name) {
output << " " << state.symbols[v.lambda.fun->name];
}
std::ostringstream s;
s << state.positions[v.lambda.fun->pos];
output << " @ " << filterANSIEscapes(s.str());
}
} else if (v.isPrimOp()) {
if (v.primOp)
output << *v.primOp;
else
output << "primop";
} else if (v.isPrimOpApp()) {
output << "partially applied ";
auto primOp = v.primOpAppPrimOp();
if (primOp)
output << *primOp;
else
output << "primop";
} else {
abort();
}
output << "»";
if (options.ansiColors)
output << ANSI_NORMAL;
}
void printThunk(Value & v)
{
if (v.isBlackhole()) {
// Although we know for sure that it's going to be an infinite recursion
// when this value is accessed _in the current context_, it's likely
// that the user will misinterpret a simpler «infinite recursion» output
// as a definitive statement about the value, while in fact it may be
// a valid value after `builtins.trace` and perhaps some other steps
// have completed.
if (options.ansiColors)
output << ANSI_RED;
output << "«potential infinite recursion»";
if (options.ansiColors)
output << ANSI_NORMAL;
} else if (v.isThunk() || v.isApp()) {
if (options.ansiColors)
output << ANSI_MAGENTA;
output << "«thunk»";
if (options.ansiColors)
output << ANSI_NORMAL;
} else {
abort();
}
}
void printExternal(Value & v)
{
v.external->print(output);
}
void printUnknown()
{
if (options.ansiColors)
output << ANSI_RED;
output << "«unknown»";
if (options.ansiColors)
output << ANSI_NORMAL;
}
void printError_(BaseError & e)
{
if (options.ansiColors)
output << ANSI_RED;
output << "«" << e.msg() << "»";
if (options.ansiColors)
output << ANSI_NORMAL;
}
void print(Value & v, size_t depth)
{
output.flush();
checkInterrupt();
if (options.force) {
try {
state.forceValue(v, v.determinePos(noPos));
} catch (BaseError & e) {
printError_(e);
return;
}
}
switch (v.type()) {
case nInt:
printInt(v);
break;
case nFloat:
printFloat(v);
break;
case nBool:
printBool(v);
break;
case nString:
printString(v);
break;
case nPath:
printPath(v);
break;
case nNull:
printNull();
break;
case nAttrs:
printAttrs(v, depth);
break;
case nList:
printList(v, depth);
break;
case nFunction:
printFunction(v);
break;
case nThunk:
printThunk(v);
break;
case nExternal:
printExternal(v);
break;
default:
printUnknown();
break;
}
}
public:
Printer(std::ostream & output, EvalState & state, PrintOptions options)
: output(output), state(state), options(options) { }
void print(Value & v)
{
if (options.trackRepeated) {
seen.emplace();
} else {
seen.reset();
}
ValuesSeen seen;
print(v, 0);
}
};
void printValue(EvalState & state, std::ostream & output, Value & v, PrintOptions options)
{
Printer(output, state, options).print(v);
}
}

View file

@ -9,46 +9,54 @@
#include <iostream>
#include "eval.hh"
#include "print-options.hh"
namespace nix {
/**
* Print a string as a Nix string literal.
*
* Quotes and fairly minimal escaping are added.
*
* @param s The logical string
*/
std::ostream & printLiteralString(std::ostream & o, std::string_view s);
inline std::ostream & printLiteralString(std::ostream & o, const char * s) {
return printLiteralString(o, std::string_view(s));
}
inline std::ostream & printLiteralString(std::ostream & o, const std::string & s) {
return printLiteralString(o, std::string_view(s));
}
/** Print `true` or `false`. */
std::ostream & printLiteralBool(std::ostream & o, bool b);
/**
* Print a string as an attribute name in the Nix expression language syntax.
*
* Prints a quoted string if necessary.
*/
std::ostream & printAttributeName(std::ostream & o, std::string_view s);
/**
* Returns `true' is a string is a reserved keyword which requires quotation
* when printing attribute set field names.
*/
bool isReservedKeyword(const std::string_view str);
/**
* Print a string as an identifier in the Nix expression language syntax.
*
* FIXME: "identifier" is ambiguous. Identifiers do not have a single
* textual representation. They can be used in variable references,
* let bindings, left-hand sides or attribute names in a select
* expression, or something else entirely, like JSON. Use one of the
* `print*` functions instead.
*/
std::ostream & printIdentifier(std::ostream & o, std::string_view s);
/**
* Print a string as a Nix string literal.
*
* Quotes and fairly minimal escaping are added.
*
* @param o The output stream to print to
* @param s The logical string
*/
std::ostream & printLiteralString(std::ostream & o, std::string_view s);
inline std::ostream & printLiteralString(std::ostream & o, const char * s) {
return printLiteralString(o, std::string_view(s));
}
inline std::ostream & printLiteralString(std::ostream & o, const std::string & s) {
return printLiteralString(o, std::string_view(s));
}
/** Print `true` or `false`. */
std::ostream & printLiteralBool(std::ostream & o, bool b);
/**
* Print a string as an attribute name in the Nix expression language syntax.
*
* Prints a quoted string if necessary.
*/
std::ostream & printAttributeName(std::ostream & o, std::string_view s);
/**
* Returns `true' is a string is a reserved keyword which requires quotation
* when printing attribute set field names.
*/
bool isReservedKeyword(const std::string_view str);
/**
* Print a string as an identifier in the Nix expression language syntax.
*
* FIXME: "identifier" is ambiguous. Identifiers do not have a single
* textual representation. They can be used in variable references,
* let bindings, left-hand sides or attribute names in a select
* expression, or something else entirely, like JSON. Use one of the
* `print*` functions instead.
*/
std::ostream & printIdentifier(std::ostream & o, std::string_view s);
void printValue(EvalState & state, std::ostream & str, Value & v, PrintOptions options = PrintOptions {});
}

View file

@ -9,6 +9,7 @@
#include "value/context.hh"
#include "input-accessor.hh"
#include "source-path.hh"
#include "print-options.hh"
#if HAVE_BOEHMGC
#include <gc/gc_allocator.h>
@ -70,7 +71,7 @@ struct Pos;
class StorePath;
class EvalState;
class XMLWriter;
class Printer;
typedef int64_t NixInt;
typedef double NixFloat;
@ -82,6 +83,7 @@ typedef double NixFloat;
class ExternalValueBase
{
friend std::ostream & operator << (std::ostream & str, const ExternalValueBase & v);
friend class Printer;
protected:
/**
* Print out the value
@ -139,11 +141,9 @@ private:
friend std::string showType(const Value & v);
void print(const SymbolTable &symbols, std::ostream &str, std::set<const void *> *seen, int depth) const;
public:
void print(const SymbolTable &symbols, std::ostream &str, bool showRepeated = false, int depth = INT_MAX) const;
void print(EvalState &state, std::ostream &str, PrintOptions options = PrintOptions {});
// Functions needed to distinguish the type
// These should be removed eventually, by putting the functionality that's
@ -364,10 +364,15 @@ public:
inline void mkPrimOpApp(Value * l, Value * r)
{
internalType = tPrimOpApp;
app.left = l;
app.right = r;
primOpApp.left = l;
primOpApp.right = r;
}
/**
* For a `tPrimOpApp` value, get the original `PrimOp` value.
*/
PrimOp * primOpAppPrimOp() const;
inline void mkExternal(ExternalValueBase * e)
{
clearValue();

View file

@ -1495,7 +1495,7 @@ void LocalDerivationGoal::startDaemon()
daemon::processConnection(store, from, to,
NotTrusted, daemon::Recursive);
debug("terminated daemon connection");
} catch (SysError &) {
} catch (SystemError &) {
ignoreException();
}
});
@ -1707,7 +1707,7 @@ void LocalDerivationGoal::runChild()
try {
if (drv->isBuiltin() && drv->builder == "builtin:fetchurl")
netrcData = readFile(settings.netrcFile);
} catch (SysError &) { }
} catch (SystemError &) { }
#if __linux__
if (useChroot) {

View file

@ -449,7 +449,7 @@ void Worker::waitForInput()
} else {
printMsg(lvlVomit, "%1%: read %2% bytes",
goal->getName(), rd);
std::string data((char *) buffer.data(), rd);
std::string_view data((char *) buffer.data(), rd);
j->lastOutput = after;
goal->handleChildOutput(k, data);
}

View file

@ -413,7 +413,7 @@ void LocalStore::findRuntimeRoots(Roots & roots, bool censor)
auto env_end = std::sregex_iterator{};
for (auto i = std::sregex_iterator{envString.begin(), envString.end(), storePathRegex}; i != env_end; ++i)
unchecked[i->str()].emplace(envFile);
} catch (SysError & e) {
} catch (SystemError & e) {
if (errno == ENOENT || errno == EACCES || errno == ESRCH)
continue;
throw;

View file

@ -118,7 +118,7 @@ void loadConfFile()
try {
std::string contents = readFile(path);
globalConfig.applyConfig(contents, path);
} catch (SysError &) { }
} catch (SystemError &) { }
};
applyConfigFile(settings.nixConfDir + "/nix.conf");

View file

@ -19,7 +19,7 @@ PublicKeys getDefaultPublicKeys()
try {
SecretKey secretKey(readFile(secretKeyFile));
publicKeys.emplace(secretKey.name, secretKey.toPublicKey());
} catch (SysError & e) {
} catch (SystemError & e) {
/* Ignore unreadable key files. That's normal in a
multi-user installation. */
}

View file

@ -276,7 +276,7 @@ LocalStore::LocalStore(const Params & params)
[[gnu::unused]] auto res2 = ftruncate(fd.get(), settings.reservedSize);
}
}
} catch (SysError & e) { /* don't care about errors */
} catch (SystemError & e) { /* don't care about errors */
}
/* Acquire the big fat lock in shared mode to make sure that no

View file

@ -242,7 +242,7 @@ void LocalStore::optimisePath_(Activity * act, OptimiseStats & stats,
/* Atomically replace the old file with the new hard link. */
try {
renameFile(tempLink, path);
} catch (SysError & e) {
} catch (SystemError & e) {
if (unlink(tempLink.c_str()) == -1)
printError("unable to unlink '%1%'", tempLink);
if (errno == EMLINK) {

View file

@ -87,13 +87,13 @@ std::pair<ref<SourceAccessor>, CanonPath> RemoteFSAccessor::fetch(const CanonPat
nars.emplace(storePath.hashPart(), narAccessor);
return {narAccessor, restPath};
} catch (SysError &) { }
} catch (SystemError &) { }
try {
auto narAccessor = makeNarAccessor(nix::readFile(cacheFile));
nars.emplace(storePath.hashPart(), narAccessor);
return {narAccessor, restPath};
} catch (SysError &) { }
} catch (SystemError &) { }
}
StringSink sink;

View file

@ -304,7 +304,7 @@ void RootArgs::parseCmdline(const Strings & _cmdline, bool allowShebang)
for (auto pos = savedArgs.begin(); pos != savedArgs.end();pos++)
cmdline.push_back(*pos);
}
} catch (SysError &) { }
} catch (SystemError &) { }
}
for (auto pos = cmdline.begin(); pos != cmdline.end(); ) {

View file

@ -6,11 +6,11 @@ namespace nix {
CanonPath CanonPath::root = CanonPath("/");
CanonPath::CanonPath(std::string_view raw)
: path(absPath((Path) raw, "/"))
: path(absPath(raw, "/"))
{ }
CanonPath::CanonPath(std::string_view raw, const CanonPath & root)
: path(absPath((Path) raw, root.abs()))
: path(absPath(raw, root.abs()))
{ }
CanonPath::CanonPath(const std::vector<std::string> & elems)
@ -22,7 +22,7 @@ CanonPath::CanonPath(const std::vector<std::string> & elems)
CanonPath CanonPath::fromCwd(std::string_view path)
{
return CanonPath(unchecked_t(), absPath((Path) path));
return CanonPath(unchecked_t(), absPath(path));
}
std::optional<CanonPath> CanonPath::parent() const

View file

@ -95,7 +95,7 @@ static CgroupStats destroyCgroup(const Path & cgroup, bool returnStats)
using namespace std::string_literals;
warn("killing stray builder process %d (%s)...",
pid, trim(replaceStrings(cmdline, "\0"s, " ")));
} catch (SysError &) {
} catch (SystemError &) {
}
}
// FIXME: pid wraparound

View file

@ -124,7 +124,7 @@ static void applyConfigInner(const std::string & contents, const std::string & p
try {
std::string includedContents = readFile(path);
applyConfigInner(includedContents, p, parsedContents);
} catch (SysError &) {
} catch (SystemError &) {
// TODO: Do we actually want to ignore this? Or is it better to fail?
}
} else if (!ignoreMissing) {

18
src/libutil/english.cc Normal file
View file

@ -0,0 +1,18 @@
#include "english.hh"
namespace nix {
std::ostream & pluralize(
std::ostream & output,
unsigned int count,
const std::string_view single,
const std::string_view plural)
{
if (count == 1)
output << "1 " << single;
else
output << count << " " << plural;
return output;
}
}

18
src/libutil/english.hh Normal file
View file

@ -0,0 +1,18 @@
#pragma once
#include <iostream>
namespace nix {
/**
* Pluralize a given value.
*
* If `count == 1`, prints `1 {single}` to `output`, otherwise prints `{count} {plural}`.
*/
std::ostream & pluralize(
std::ostream & output,
unsigned int count,
const std::string_view single,
const std::string_view plural);
}

View file

@ -178,20 +178,50 @@ MakeError(Error, BaseError);
MakeError(UsageError, Error);
MakeError(UnimplementedError, Error);
class SysError : public Error
/**
* To use in catch-blocks.
*/
MakeError(SystemError, Error);
/**
* POSIX system error, created using `errno`, `strerror` friends.
*
* Throw this, but prefer not to catch this, and catch `SystemError`
* instead. This allows implementations to freely switch between this
* and `WinError` without breaking catch blocks.
*
* However, it is permissible to catch this and rethrow so long as
* certain conditions are not met (e.g. to catch only if `errNo =
* EFooBar`). In that case, try to also catch the equivalent `WinError`
* code.
*
* @todo Rename this to `PosixError` or similar. At this point Windows
* support is too WIP to justify the code churn, but if it is finished
* then a better identifier becomes moe worth it.
*/
class SysError : public SystemError
{
public:
int errNo;
/**
* Construct using the explicitly-provided error number. `strerror`
* will be used to try to add additional information to the message.
*/
template<typename... Args>
SysError(int errNo_, const Args & ... args)
: Error("")
SysError(int errNo, const Args & ... args)
: SystemError(""), errNo(errNo)
{
errNo = errNo_;
auto hf = hintfmt(args...);
err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
}
/**
* Construct using the ambient `errno`.
*
* Be sure to not perform another `errno`-modifying operation before
* calling this constructor!
*/
template<typename... Args>
SysError(const Args & ... args)
: SysError(errno, args ...)
@ -199,7 +229,9 @@ public:
}
};
/** Throw an exception for the purpose of checking that exception handling works; see 'initLibUtil()'.
/**
* Throw an exception for the purpose of checking that exception
* handling works; see 'initLibUtil()'.
*/
void throwExceptionSelfCheck();

View file

@ -231,7 +231,7 @@ void closeMostFDs(const std::set<int> & exceptions)
}
}
return;
} catch (SysError &) {
} catch (SystemError &) {
}
#endif

View file

@ -21,9 +21,16 @@ namespace fs = std::filesystem;
namespace nix {
Path absPath(Path path, std::optional<PathView> dir, bool resolveSymlinks)
Path absPath(PathView path, std::optional<PathView> dir, bool resolveSymlinks)
{
std::string scratch;
if (path[0] != '/') {
// In this case we need to call `canonPath` on a newly-created
// string. We set `scratch` to that string first, and then set
// `path` to `scratch`. This ensures the newly-created string
// lives long enough for the call to `canonPath`, and allows us
// to just accept a `std::string_view`.
if (!dir) {
#ifdef __GNU__
/* GNU (aka. GNU/Hurd) doesn't have any limitation on path
@ -35,12 +42,13 @@ Path absPath(Path path, std::optional<PathView> dir, bool resolveSymlinks)
if (!getcwd(buf, sizeof(buf)))
#endif
throw SysError("cannot get cwd");
path = concatStrings(buf, "/", path);
scratch = concatStrings(buf, "/", path);
#ifdef __GNU__
free(buf);
#endif
} else
path = concatStrings(*dir, "/", path);
scratch = concatStrings(*dir, "/", path);
path = scratch;
}
return canonPath(path, resolveSymlinks);
}

View file

@ -41,7 +41,7 @@ struct Source;
* specified directory, or the current directory otherwise. The path
* is also canonicalised.
*/
Path absPath(Path path,
Path absPath(PathView path,
std::optional<PathView> dir = {},
bool resolveSymlinks = false);

View file

@ -116,7 +116,7 @@ void writeToStderr(std::string_view s)
{
try {
writeFull(STDERR_FILENO, s, false);
} catch (SysError & e) {
} catch (SystemError & e) {
/* Ignore failing writes to stderr. We need to ignore write
errors to ensure that cleanup code that logs to stderr runs
to completion if the other side of stderr has been closed

View file

@ -53,7 +53,7 @@ void FdSink::writeUnbuffered(std::string_view data)
written += data.size();
try {
writeFull(fd, data);
} catch (SysError & e) {
} catch (SystemError & e) {
_good = false;
throw;
}

View file

@ -20,7 +20,7 @@ void initLibUtil() {
// When exception handling fails, the message tends to be printed by the
// C++ runtime, followed by an abort.
// For example on macOS we might see an error such as
// libc++abi: terminating with uncaught exception of type nix::SysError: error: C++ exception handling is broken. This would appear to be a problem with the way Nix was compiled and/or linked and/or loaded.
// libc++abi: terminating with uncaught exception of type nix::SystemError: error: C++ exception handling is broken. This would appear to be a problem with the way Nix was compiled and/or linked and/or loaded.
bool caught = false;
try {
throwExceptionSelfCheck();

View file

@ -148,7 +148,7 @@ static void main_nix_build(int argc, char * * argv)
args.push_back(word);
}
}
} catch (SysError &) { }
} catch (SystemError &) { }
}
struct MyArgs : LegacyArgs, MixEvalArgs

View file

@ -8,6 +8,8 @@
#include "eval.hh"
#include "eval-inline.hh"
#include "profiles.hh"
#include "print-ambiguous.hh"
#include <limits>
namespace nix {
@ -106,7 +108,7 @@ bool createUserEnv(EvalState & state, DrvInfos & elems,
environment. */
auto manifestFile = ({
std::ostringstream str;
manifest.print(state.symbols, str, true);
printAmbiguous(manifest, state.symbols, str, nullptr, std::numeric_limits<int>::max());
// TODO with C++20 we can use str.view() instead and avoid copy.
std::string str2 = str.str();
StringSource source { str2 };

View file

@ -1,9 +1,11 @@
#include "globals.hh"
#include "print-ambiguous.hh"
#include "shared.hh"
#include "eval.hh"
#include "eval-inline.hh"
#include "get-drvs.hh"
#include "attr-path.hh"
#include "signals.hh"
#include "value-to-xml.hh"
#include "value-to-json.hh"
#include "store-api.hh"
@ -24,7 +26,6 @@ static int rootNr = 0;
enum OutputKind { okPlain, okXML, okJSON };
void processExpr(EvalState & state, const Strings & attrPaths,
bool parseOnly, bool strict, Bindings & autoArgs,
bool evalOnly, OutputKind output, bool location, Expr * e)
@ -56,7 +57,8 @@ void processExpr(EvalState & state, const Strings & attrPaths,
std::cout << std::endl;
} else {
if (strict) state.forceValueDeep(vRes);
vRes.print(state.symbols, std::cout);
std::set<const void *> seen;
printAmbiguous(vRes, state.symbols, std::cout, &seen, std::numeric_limits<int>::max());
std::cout << std::endl;
}
} else {

View file

@ -107,7 +107,7 @@ struct CmdConfigCheck : StoreCommand
if (profileDir.find("/profiles/") == std::string::npos)
dirs.insert(dir);
}
} catch (SysError &) {}
} catch (SystemError &) {}
}
if (!dirs.empty()) {

View file

@ -297,7 +297,6 @@ struct Common : InstallableCommand, MixProfile
"NIX_LOG_FD",
"NIX_REMOTE",
"PPID",
"SHELL",
"SHELLOPTS",
"SSL_CERT_FILE", // FIXME: only want to ignore /no-cert-file.crt
"TEMP",
@ -604,7 +603,7 @@ struct CmdDevelop : Common, MixEnvironment
setEnviron();
// prevent garbage collection until shell exits
setenv("NIX_GCROOT", gcroot.data(), 1);
setenv("NIX_GCROOT", gcroot.c_str(), 1);
Path shell = "bash";
@ -647,6 +646,10 @@ struct CmdDevelop : Common, MixEnvironment
ignoreException();
}
// Override SHELL with the one chosen for this environment.
// This is to make sure the system shell doesn't leak into the build environment.
setenv("SHELL", shell.c_str(), 1);
// If running a phase or single command, don't want an interactive shell running after
// Ctrl-C, so don't pass --rcfile
auto args = phase || !command.empty() ? Strings{std::string(baseNameOf(shell)), rcFilePath}

View file

@ -7,14 +7,12 @@ R""(
```console
# nix profile list
Name: gdb
Index: 0
Flake attribute: legacyPackages.x86_64-linux.gdb
Original flake URL: flake:nixpkgs
Locked flake URL: github:NixOS/nixpkgs/7b38b03d76ab71bdc8dc325e3f6338d984cc35ca
Store paths: /nix/store/indzcw5wvlhx6vwk7k4iq29q15chvr3d-gdb-11.1
Name: blender-bin
Index: 1
Flake attribute: packages.x86_64-linux.default
Original flake URL: flake:blender-bin
Locked flake URL: github:edolstra/nix-warez/91f2ffee657bf834e4475865ae336e2379282d34?dir=blender

View file

@ -8,13 +8,6 @@ R""(
# nix profile remove hello
```
* Remove a package by index
*(deprecated, will be removed in a future version)*:
```console
# nix profile remove 3
```
* Remove all packages:
```console

View file

@ -15,13 +15,6 @@ R""(
# nix profile upgrade hello
```
* Upgrade a specific package by index
*(deprecated, will be removed in a future version)*:
```console
# nix profile upgrade 0
```
# Description
This command upgrades a previously installed package in a Nix profile,

View file

@ -45,7 +45,6 @@ const int defaultPriority = 5;
struct ProfileElement
{
StorePathSet storePaths;
std::string name;
std::optional<ProfileElementSource> source;
bool active = true;
int priority = defaultPriority;
@ -82,11 +81,6 @@ struct ProfileElement
return showVersions(versions);
}
bool operator < (const ProfileElement & other) const
{
return std::tuple(identifier(), storePaths) < std::tuple(other.identifier(), other.storePaths);
}
void updateStorePaths(
ref<Store> evalStore,
ref<Store> store,
@ -109,7 +103,9 @@ struct ProfileElement
struct ProfileManifest
{
std::vector<ProfileElement> elements;
using ProfileElementName = std::string;
std::map<ProfileElementName, ProfileElement> elements;
ProfileManifest() { }
@ -119,8 +115,6 @@ struct ProfileManifest
if (pathExists(manifestPath)) {
auto json = nlohmann::json::parse(readFile(manifestPath));
/* Keep track of already found names to allow preventing duplicates. */
std::set<std::string> foundNames;
auto version = json.value("version", 0);
std::string sUrl;
@ -131,6 +125,7 @@ struct ProfileManifest
sOriginalUrl = "originalUri";
break;
case 2:
case 3:
sUrl = "url";
sOriginalUrl = "originalUrl";
break;
@ -138,7 +133,9 @@ struct ProfileManifest
throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version);
}
for (auto & e : json["elements"]) {
auto elems = json["elements"];
for (auto & elem : elems.items()) {
auto & e = elem.value();
ProfileElement element;
for (auto & p : e["storePaths"])
element.storePaths.insert(state.store->parseStorePath((std::string) p));
@ -155,25 +152,14 @@ struct ProfileManifest
};
}
std::string nameCandidate = element.identifier();
if (e.contains("name")) {
nameCandidate = e["name"];
}
else if (element.source) {
auto url = parseURL(element.source->to_string());
auto name = getNameFromURL(url);
if (name)
nameCandidate = *name;
}
std::string name =
elems.is_object()
? elem.key()
: element.source
? getNameFromURL(parseURL(element.source->to_string())).value_or(element.identifier())
: element.identifier();
auto finalName = nameCandidate;
for (int i = 1; foundNames.contains(finalName); ++i) {
finalName = nameCandidate + std::to_string(i);
}
element.name = finalName;
foundNames.insert(element.name);
elements.emplace_back(std::move(element));
addElement(name, std::move(element));
}
}
@ -187,16 +173,34 @@ struct ProfileManifest
for (auto & drvInfo : drvInfos) {
ProfileElement element;
element.storePaths = {drvInfo.queryOutPath()};
element.name = element.identifier();
elements.emplace_back(std::move(element));
addElement(std::move(element));
}
}
}
void addElement(std::string_view nameCandidate, ProfileElement element)
{
std::string finalName(nameCandidate);
for (int i = 1; elements.contains(finalName); ++i)
finalName = nameCandidate + "-" + std::to_string(i);
elements.insert_or_assign(finalName, std::move(element));
}
void addElement(ProfileElement element)
{
auto name =
element.source
? getNameFromURL(parseURL(element.source->to_string()))
: std::nullopt;
auto name2 = name ? *name : element.identifier();
addElement(name2, std::move(element));
}
nlohmann::json toJSON(Store & store) const
{
auto array = nlohmann::json::array();
for (auto & element : elements) {
auto es = nlohmann::json::object();
for (auto & [name, element] : elements) {
auto paths = nlohmann::json::array();
for (auto & path : element.storePaths)
paths.push_back(store.printStorePath(path));
@ -210,11 +214,11 @@ struct ProfileManifest
obj["attrPath"] = element.source->attrPath;
obj["outputs"] = element.source->outputs;
}
array.push_back(obj);
es[name] = obj;
}
nlohmann::json json;
json["version"] = 2;
json["elements"] = array;
json["version"] = 3;
json["elements"] = es;
return json;
}
@ -225,7 +229,7 @@ struct ProfileManifest
StorePathSet references;
Packages pkgs;
for (auto & element : elements) {
for (auto & [name, element] : elements) {
for (auto & path : element.storePaths) {
if (element.active)
pkgs.emplace_back(store->printStorePath(path), true, element.priority);
@ -267,33 +271,27 @@ struct ProfileManifest
static void printDiff(const ProfileManifest & prev, const ProfileManifest & cur, std::string_view indent)
{
auto prevElems = prev.elements;
std::sort(prevElems.begin(), prevElems.end());
auto curElems = cur.elements;
std::sort(curElems.begin(), curElems.end());
auto i = prevElems.begin();
auto j = curElems.begin();
auto i = prev.elements.begin();
auto j = cur.elements.begin();
bool changes = false;
while (i != prevElems.end() || j != curElems.end()) {
if (j != curElems.end() && (i == prevElems.end() || i->identifier() > j->identifier())) {
logger->cout("%s%s: ∅ -> %s", indent, j->identifier(), j->versions());
while (i != prev.elements.end() || j != cur.elements.end()) {
if (j != cur.elements.end() && (i == prev.elements.end() || i->first > j->first)) {
logger->cout("%s%s: ∅ -> %s", indent, j->second.identifier(), j->second.versions());
changes = true;
++j;
}
else if (i != prevElems.end() && (j == curElems.end() || i->identifier() < j->identifier())) {
logger->cout("%s%s: %s -> ∅", indent, i->identifier(), i->versions());
else if (i != prev.elements.end() && (j == cur.elements.end() || i->first < j->first)) {
logger->cout("%s%s: %s -> ∅", indent, i->second.identifier(), i->second.versions());
changes = true;
++i;
}
else {
auto v1 = i->versions();
auto v2 = j->versions();
auto v1 = i->second.versions();
auto v2 = j->second.versions();
if (v1 != v2) {
logger->cout("%s%s: %s -> %s", indent, i->identifier(), v1, v2);
logger->cout("%s%s: %s -> %s", indent, i->second.identifier(), v1, v2);
changes = true;
}
++i;
@ -392,7 +390,7 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
element.updateStorePaths(getEvalStore(), store, res);
manifest.elements.push_back(std::move(element));
manifest.addElement(std::move(element));
}
try {
@ -402,7 +400,7 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
// See https://github.com/NixOS/nix/compare/3efa476c5439f8f6c1968a6ba20a31d1239c2f04..1fe5d172ece51a619e879c4b86f603d9495cc102
auto findRefByFilePath = [&]<typename Iterator>(Iterator begin, Iterator end) {
for (auto it = begin; it != end; it++) {
auto profileElement = *it;
auto & profileElement = it->second;
for (auto & storePath : profileElement.storePaths) {
if (conflictError.fileA.starts_with(store->printStorePath(storePath))) {
return std::pair(conflictError.fileA, profileElement.toInstallables(*store));
@ -470,43 +468,35 @@ public:
std::string pattern;
std::regex reg;
};
typedef std::variant<size_t, Path, RegexPattern> Matcher;
typedef std::variant<Path, RegexPattern> Matcher;
std::vector<Matcher> getMatchers(ref<Store> store)
{
std::vector<Matcher> res;
auto anyIndexMatchers = false;
for (auto & s : _matchers) {
if (auto n = string2Int<size_t>(s)) {
res.push_back(*n);
anyIndexMatchers = true;
}
if (auto n = string2Int<size_t>(s))
throw Error("'nix profile' no longer supports indices ('%d')", *n);
else if (store->isStorePath(s))
res.push_back(s);
else
res.push_back(RegexPattern{s,std::regex(s, std::regex::extended | std::regex::icase)});
}
if (anyIndexMatchers) {
warn("Indices are deprecated and will be removed in a future version!\n"
" Refer to packages by their `Name` as printed by `nix profile list`.\n"
" See https://github.com/NixOS/nix/issues/9171 for more information.");
}
return res;
}
bool matches(const Store & store, const ProfileElement & element, size_t pos, const std::vector<Matcher> & matchers)
bool matches(
const Store & store,
const std::string & name,
const ProfileElement & element,
const std::vector<Matcher> & matchers)
{
for (auto & matcher : matchers) {
if (auto n = std::get_if<size_t>(&matcher)) {
if (*n == pos) return true;
} else if (auto path = std::get_if<Path>(&matcher)) {
if (auto path = std::get_if<Path>(&matcher)) {
if (element.storePaths.count(store.parseStorePath(*path))) return true;
} else if (auto regex = std::get_if<RegexPattern>(&matcher)) {
if (std::regex_match(element.name, regex->reg))
if (std::regex_match(name, regex->reg))
return true;
}
}
@ -537,10 +527,9 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem
ProfileManifest newManifest;
for (size_t i = 0; i < oldManifest.elements.size(); ++i) {
auto & element(oldManifest.elements[i]);
if (!matches(*store, element, i, matchers)) {
newManifest.elements.push_back(std::move(element));
for (auto & [name, element] : oldManifest.elements) {
if (!matches(*store, name, element, matchers)) {
newManifest.elements.insert_or_assign(name, std::move(element));
} else {
notice("removing '%s'", element.identifier());
}
@ -553,11 +542,9 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem
if (removedCount == 0) {
for (auto matcher: matchers) {
if (const size_t * index = std::get_if<size_t>(&matcher)){
warn("'%d' is not a valid index", *index);
} else if (const Path * path = std::get_if<Path>(&matcher)){
if (const Path * path = std::get_if<Path>(&matcher)) {
warn("'%s' does not match any paths", *path);
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)){
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)) {
warn("'%s' does not match any packages", regex->pattern);
}
}
@ -588,14 +575,13 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
auto matchers = getMatchers(store);
Installables installables;
std::vector<size_t> indices;
std::vector<ProfileElement *> elems;
auto matchedCount = 0;
auto upgradedCount = 0;
for (size_t i = 0; i < manifest.elements.size(); ++i) {
auto & element(manifest.elements[i]);
if (!matches(*store, element, i, matchers)) {
for (auto & [name, element] : manifest.elements) {
if (!matches(*store, name, element, matchers)) {
continue;
}
@ -651,17 +637,15 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
};
installables.push_back(installable);
indices.push_back(i);
elems.push_back(&element);
}
if (upgradedCount == 0) {
if (matchedCount == 0) {
for (auto & matcher : matchers) {
if (const size_t * index = std::get_if<size_t>(&matcher)){
warn("'%d' is not a valid index", *index);
} else if (const Path * path = std::get_if<Path>(&matcher)){
if (const Path * path = std::get_if<Path>(&matcher)) {
warn("'%s' does not match any paths", *path);
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)){
} else if (const RegexPattern * regex = std::get_if<RegexPattern>(&matcher)) {
warn("'%s' does not match any packages", regex->pattern);
}
}
@ -677,7 +661,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf
for (size_t i = 0; i < installables.size(); ++i) {
auto & installable = installables.at(i);
auto & element = manifest.elements[indices.at(i)];
auto & element = *elems.at(i);
element.updateStorePaths(
getEvalStore(),
store,
@ -709,13 +693,12 @@ struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultPro
if (json) {
std::cout << manifest.toJSON(*store).dump() << "\n";
} else {
for (size_t i = 0; i < manifest.elements.size(); ++i) {
auto & element(manifest.elements[i]);
for (const auto & [i, e] : enumerate(manifest.elements)) {
auto & [name, element] = e;
if (i) logger->cout("");
logger->cout("Name: " ANSI_BOLD "%s" ANSI_NORMAL "%s",
element.name,
name,
element.active ? "" : " " ANSI_RED "(inactive)" ANSI_NORMAL);
logger->cout("Index: %s", i);
if (element.source) {
logger->cout("Flake attribute: %s%s", element.source->attrPath, element.source->outputs.to_string());
logger->cout("Original flake URL: %s", element.source->originalRef.to_string());

View file

@ -45,6 +45,7 @@ if [[ -n "${NIX_DAEMON_PACKAGE:-}" ]]; then
DAEMON_PATH="${NIX_DAEMON_PACKAGE}/bin:$DAEMON_PATH"
fi
coreutils=@coreutils@
lsof=@lsof@
export dot=@dot@
export SHELL="@bash@"

View file

@ -0,0 +1,67 @@
source ../common.sh
clearStore
rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local
# Create flake under test.
cp ../shell-hello.nix ../config.nix $TEST_HOME/
cat <<EOF >$TEST_HOME/flake.nix
{
inputs.nixpkgs.url = "$TEST_HOME/nixpkgs";
outputs = {self, nixpkgs}: {
packages.$system.hello = (import ./config.nix).mkDerivation {
name = "hello";
outputs = [ "out" "dev" ];
meta.outputsToInstall = [ "out" ];
buildCommand = "";
};
};
}
EOF
# Create fake nixpkgs flake.
mkdir -p $TEST_HOME/nixpkgs
cp ../config.nix ../shell.nix $TEST_HOME/nixpkgs
cat <<EOF >$TEST_HOME/nixpkgs/flake.nix
{
outputs = {self}: {
legacyPackages.$system.bashInteractive = (import ./shell.nix {}).bashInteractive;
};
}
EOF
cd $TEST_HOME
# Test whether `nix develop` passes through environment variables.
[[ "$(
ENVVAR=a nix develop --no-write-lock-file .#hello <<EOF
echo "\$ENVVAR"
EOF
)" = "a" ]]
# Test whether `nix develop --ignore-environment` does _not_ pass through environment variables.
[[ -z "$(
ENVVAR=a nix develop --ignore-environment --no-write-lock-file .#hello <<EOF
echo "\$ENVVAR"
EOF
)" ]]
# Determine the bashInteractive executable.
nix build --no-write-lock-file './nixpkgs#bashInteractive' --out-link ./bash-interactive
BASH_INTERACTIVE_EXECUTABLE="$PWD/bash-interactive/bin/bash"
# Test whether `nix develop` sets `SHELL` to nixpkgs#bashInteractive shell.
[[ "$(
SHELL=custom nix develop --no-write-lock-file .#hello <<EOF
echo "\$SHELL"
EOF
)" -ef "$BASH_INTERACTIVE_EXECUTABLE" ]]
# Test whether `nix develop` with ignore environment sets `SHELL` to nixpkgs#bashInteractive shell.
[[ "$(
SHELL=custom nix develop --ignore-environment --no-write-lock-file .#hello <<EOF
echo "\$SHELL"
EOF
)" -ef "$BASH_INTERACTIVE_EXECUTABLE" ]]
clearStore

View file

@ -1 +1 @@
trace: [ <CODE> ]
trace: [ «thunk» ]

View file

@ -0,0 +1 @@
[ { } { } ]

View file

@ -0,0 +1,2 @@
# Tests that empty attribute sets are not printed as `«repeated»`.
[ {} {} ]

View file

@ -0,0 +1 @@
[ [ ] [ ] ]

View file

@ -0,0 +1 @@
[ [] [] ]

View file

@ -0,0 +1 @@
"okay"

View file

@ -0,0 +1,11 @@
with builtins;
let
s = "${builtins.derivation { name = "test"; builder = "/bin/sh"; system = "x86_64-linux"; }}";
in
if getContext s == getContext "${substring 0 0 s + unsafeDiscardStringContext s}"
then "okay"
else throw "empty substring should preserve context"

View file

@ -1 +1 @@
"ooxfoobarybarzobaabbc"
"ooxfoobarybarzobaabbc_bad"

View file

@ -19,3 +19,5 @@ substring 1 2 s
+ substring 3 1 s
+ "c"
+ substring 5 10 "perl"
+ "_"
+ substring 3 (-1) "tebbad"

View file

@ -2,6 +2,7 @@ nix_tests = \
test-infra.sh \
init.sh \
flakes/flakes.sh \
flakes/develop.sh \
flakes/run.sh \
flakes/mercurial.sh \
flakes/circular.sh \

View file

@ -49,7 +49,7 @@ cp ./config.nix $flake1Dir/
nix-env -f ./user-envs.nix -i foo-1.0
nix profile list | grep -A2 'Name:.*foo' | grep 'Store paths:.*foo-1.0'
nix profile install $flake1Dir -L
nix profile list | grep -A4 'Index:.*1' | grep 'Locked flake URL:.*narHash'
nix profile list | grep -A4 'Name:.*flake1' | grep 'Locked flake URL:.*narHash'
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello World" ]]
[ -e $TEST_HOME/.nix-profile/share/man ]
(! [ -e $TEST_HOME/.nix-profile/include ])
@ -58,9 +58,8 @@ nix profile history | grep "packages.$system.default: ∅ -> 1.0"
nix profile diff-closures | grep 'env-manifest.nix: ε → ∅'
# Test XDG Base Directories support
export NIX_CONFIG="use-xdg-base-directories = true"
nix profile remove 1
nix profile remove flake1 2>&1 | grep 'removed 1 packages'
nix profile install $flake1Dir
[[ $($TEST_HOME/.local/state/nix/profile/bin/hello) = "Hello World" ]]
unset NIX_CONFIG
@ -68,7 +67,7 @@ unset NIX_CONFIG
# Test upgrading a package.
printf NixOS > $flake1Dir/who
printf 2.0 > $flake1Dir/version
nix profile upgrade 1
nix profile upgrade flake1
[[ $($TEST_HOME/.nix-profile/bin/hello) = "Hello NixOS" ]]
nix profile history | grep "packages.$system.default: 1.0, 1.0-man -> 2.0, 2.0-man"
@ -81,7 +80,7 @@ nix profile rollback
# Test uninstall.
[ -e $TEST_HOME/.nix-profile/bin/foo ]
nix profile remove foo
nix profile remove foo 2>&1 | grep 'removed 1 packages'
(! [ -e $TEST_HOME/.nix-profile/bin/foo ])
nix profile history | grep 'foo: 1.0 -> ∅'
nix profile diff-closures | grep 'Version 3 -> 4'
@ -89,7 +88,7 @@ nix profile diff-closures | grep 'Version 3 -> 4'
# Test installing a non-flake package.
nix profile install --file ./simple.nix ''
[[ $(cat $TEST_HOME/.nix-profile/hello) = "Hello World!" ]]
nix profile remove 1
nix profile remove simple 2>&1 | grep 'removed 1 packages'
nix profile install $(nix-build --no-out-link ./simple.nix)
[[ $(cat $TEST_HOME/.nix-profile/hello) = "Hello World!" ]]
@ -97,8 +96,9 @@ nix profile install $(nix-build --no-out-link ./simple.nix)
mkdir $TEST_ROOT/simple-too
cp ./simple.nix ./config.nix simple.builder.sh $TEST_ROOT/simple-too
nix profile install --file $TEST_ROOT/simple-too/simple.nix ''
nix profile list | grep -A4 'Name:.*simple' | grep 'Name:.*simple1'
nix profile remove simple1
nix profile list | grep -A4 'Name:.*simple' | grep 'Name:.*simple-1'
nix profile remove simple 2>&1 | grep 'removed 1 packages'
nix profile remove simple-1 2>&1 | grep 'removed 1 packages'
# Test wipe-history.
nix profile wipe-history
@ -107,11 +107,11 @@ nix profile wipe-history
# Test upgrade to CA package.
printf true > $flake1Dir/ca.nix
printf 3.0 > $flake1Dir/version
nix profile upgrade 0
nix profile upgrade flake1
nix profile history | grep "packages.$system.default: 1.0, 1.0-man -> 3.0, 3.0-man"
# Test new install of CA package.
nix profile remove flake1
nix profile remove flake1 2>&1 | grep 'removed 1 packages'
printf 4.0 > $flake1Dir/version
printf Utrecht > $flake1Dir/who
nix profile install $flake1Dir
@ -132,14 +132,14 @@ nix profile upgrade flake1
[ -e $TEST_HOME/.nix-profile/share/man ]
[ -e $TEST_HOME/.nix-profile/include ]
nix profile remove flake1
nix profile remove flake1 2>&1 | grep 'removed 1 packages'
nix profile install "$flake1Dir^man"
(! [ -e $TEST_HOME/.nix-profile/bin/hello ])
[ -e $TEST_HOME/.nix-profile/share/man ]
(! [ -e $TEST_HOME/.nix-profile/include ])
# test priority
nix profile remove flake1
nix profile remove flake1 2>&1 | grep 'removed 1 packages'
# Make another flake.
flake2Dir=$TEST_ROOT/flake2
@ -193,3 +193,12 @@ nix profile install $flake2Dir --priority 0
clearProfiles
nix profile install $(nix build $flake1Dir --no-link --print-out-paths)
expect 1 nix profile install --impure --expr "(builtins.getFlake ''$flake2Dir'').packages.$system.default"
# Test upgrading from profile version 2.
clearProfiles
mkdir -p $TEST_ROOT/import-profile
outPath=$(nix build --no-link --print-out-paths $flake1Dir/flake.nix^out)
printf '{ "version": 2, "elements": [ { "active": true, "attrPath": "legacyPackages.x86_64-linux.hello", "originalUrl": "flake:nixpkgs", "outputs": null, "priority": 5, "storePaths": [ "%s" ], "url": "github:NixOS/nixpkgs/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" } ] }' "$outPath" > $TEST_ROOT/import-profile/manifest.json
nix build --profile $TEST_HOME/.nix-profile $(nix store add-path $TEST_ROOT/import-profile) --no-link
nix profile list | grep -A4 'Name:.*hello' | grep "Store paths:.*$outPath"
nix profile remove hello 2>&1 | grep 'removed 1 packages, kept 0 packages'

View file

@ -1,6 +1,7 @@
#include "tests/libexpr.hh"
#include "value.hh"
#include "print.hh"
namespace nix {
@ -12,7 +13,7 @@ struct ValuePrintingTests : LibExprTest
void test(Value v, std::string_view expected, A... args)
{
std::stringstream out;
v.print(state.symbols, out, args...);
v.print(state, out, args...);
ASSERT_EQ(out.str(), expected);
}
};
@ -84,7 +85,7 @@ TEST_F(ValuePrintingTests, tList)
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 3;
test(vList, "[ 1 2 (nullptr) ]");
test(vList, "[ 1 2 «nullptr» ]");
}
TEST_F(ValuePrintingTests, vThunk)
@ -92,7 +93,7 @@ TEST_F(ValuePrintingTests, vThunk)
Value vThunk;
vThunk.mkThunk(nullptr, nullptr);
test(vThunk, "<CODE>");
test(vThunk, "«thunk»");
}
TEST_F(ValuePrintingTests, vApp)
@ -100,32 +101,55 @@ TEST_F(ValuePrintingTests, vApp)
Value vApp;
vApp.mkApp(nullptr, nullptr);
test(vApp, "<CODE>");
test(vApp, "«thunk»");
}
TEST_F(ValuePrintingTests, vLambda)
{
Value vLambda;
vLambda.mkLambda(nullptr, nullptr);
Env env {
.up = nullptr,
.values = { }
};
PosTable::Origin origin((std::monostate()));
auto posIdx = state.positions.add(origin, 1, 1);
auto body = ExprInt(0);
auto formals = Formals {};
test(vLambda, "<LAMBDA>");
ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
Value vLambda;
vLambda.mkLambda(&env, &eLambda);
test(vLambda, "«lambda @ «none»:1:1»");
eLambda.setName(createSymbol("puppy"));
test(vLambda, "«lambda puppy @ «none»:1:1»");
}
TEST_F(ValuePrintingTests, vPrimOp)
{
Value vPrimOp;
PrimOp primOp{};
PrimOp primOp{
.name = "puppy"
};
vPrimOp.mkPrimOp(&primOp);
test(vPrimOp, "<PRIMOP>");
test(vPrimOp, "«primop puppy»");
}
TEST_F(ValuePrintingTests, vPrimOpApp)
{
Value vPrimOpApp;
vPrimOpApp.mkPrimOpApp(nullptr, nullptr);
PrimOp primOp{
.name = "puppy"
};
Value vPrimOp;
vPrimOp.mkPrimOp(&primOp);
test(vPrimOpApp, "<PRIMOP-APP>");
Value vPrimOpApp;
vPrimOpApp.mkPrimOpApp(&vPrimOp, nullptr);
test(vPrimOpApp, "«partially applied primop puppy»");
}
TEST_F(ValuePrintingTests, vExternal)
@ -176,9 +200,14 @@ TEST_F(ValuePrintingTests, depthAttrs)
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builderEmpty(state, state.allocBindings(0));
Value vAttrsEmpty;
vAttrsEmpty.mkAttrs(builderEmpty.finish());
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
builder.insert(state.symbols.create("nested"), &vAttrsEmpty);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
@ -191,10 +220,10 @@ TEST_F(ValuePrintingTests, depthAttrs)
Value vNested;
vNested.mkAttrs(builder2.finish());
test(vNested, "{ nested = «too deep»; one = «too deep»; two = «too deep»; }", false, 1);
test(vNested, "{ nested = { one = «too deep»; two = «too deep»; }; one = 1; two = 2; }", false, 2);
test(vNested, "{ nested = { one = 1; two = 2; }; one = 1; two = 2; }", false, 3);
test(vNested, "{ nested = { one = 1; two = 2; }; one = 1; two = 2; }", false, 4);
test(vNested, "{ nested = { ... }; one = 1; two = 2; }", PrintOptions { .maxDepth = 1 });
test(vNested, "{ nested = { nested = { ... }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 2 });
test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 3 });
test(vNested, "{ nested = { nested = { }; one = 1; two = 2; }; one = 1; two = 2; }", PrintOptions { .maxDepth = 4 });
}
TEST_F(ValuePrintingTests, depthList)
@ -227,11 +256,561 @@ TEST_F(ValuePrintingTests, depthList)
vList.bigList.elems[2] = &vNested;
vList.bigList.size = 3;
test(vList, "[ «too deep» «too deep» «too deep» ]", false, 1);
test(vList, "[ 1 2 { nested = «too deep»; one = «too deep»; two = «too deep»; } ]", false, 2);
test(vList, "[ 1 2 { nested = { one = «too deep»; two = «too deep»; }; one = 1; two = 2; } ]", false, 3);
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", false, 4);
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", false, 5);
test(vList, "[ 1 2 { ... } ]", PrintOptions { .maxDepth = 1 });
test(vList, "[ 1 2 { nested = { ... }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 2 });
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 3 });
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 4 });
test(vList, "[ 1 2 { nested = { one = 1; two = 2; }; one = 1; two = 2; } ]", PrintOptions { .maxDepth = 5 });
}
struct StringPrintingTests : LibExprTest
{
template<class... A>
void test(std::string_view literal, std::string_view expected, unsigned int maxLength, A... args)
{
Value v;
v.mkString(literal);
std::stringstream out;
printValue(state, out, v, PrintOptions {
.maxStringLength = maxLength
});
ASSERT_EQ(out.str(), expected);
}
};
TEST_F(StringPrintingTests, maxLengthTruncation)
{
test("abcdefghi", "\"abcdefghi\"", 10);
test("abcdefghij", "\"abcdefghij\"", 10);
test("abcdefghijk", "\"abcdefghij\" «1 byte elided»", 10);
test("abcdefghijkl", "\"abcdefghij\" «2 bytes elided»", 10);
test("abcdefghijklm", "\"abcdefghij\" «3 bytes elided»", 10);
}
// Check that printing an attrset shows 'important' attributes like `type`
// first, but only reorder the attrs when we have a maxAttrs budget.
TEST_F(ValuePrintingTests, attrsTypeFirst)
{
Value vType;
vType.mkString("puppy");
Value vApple;
vApple.mkString("apple");
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("type"), &vType);
builder.insert(state.symbols.create("apple"), &vApple);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ type = \"puppy\"; apple = \"apple\"; }",
PrintOptions {
.maxAttrs = 100
});
test(vAttrs,
"{ apple = \"apple\"; type = \"puppy\"; }",
PrintOptions { });
}
TEST_F(ValuePrintingTests, ansiColorsInt)
{
Value v;
v.mkInt(10);
test(v,
ANSI_CYAN "10" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsFloat)
{
Value v;
v.mkFloat(1.6);
test(v,
ANSI_CYAN "1.6" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsBool)
{
Value v;
v.mkBool(true);
test(v,
ANSI_CYAN "true" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsString)
{
Value v;
v.mkString("puppy");
test(v,
ANSI_MAGENTA "\"puppy\"" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsStringElided)
{
Value v;
v.mkString("puppy");
test(v,
ANSI_MAGENTA "\"pup\"" ANSI_FAINT " «2 bytes elided»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.maxStringLength = 3
});
}
TEST_F(ValuePrintingTests, ansiColorsPath)
{
Value v;
v.mkPath(state.rootPath(CanonPath("puppy")));
test(v,
ANSI_GREEN "/puppy" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsNull)
{
Value v;
v.mkNull();
test(v,
ANSI_CYAN "null" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsAttrs)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; two = " ANSI_CYAN "2" ANSI_NORMAL "; }",
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsDerivation)
{
Value vDerivation;
vDerivation.mkString("derivation");
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.sType, &vDerivation);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
ANSI_GREEN "«derivation»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true,
.derivationPaths = true
});
test(vAttrs,
"{ type = " ANSI_MAGENTA "\"derivation\"" ANSI_NORMAL "; }",
PrintOptions {
.ansiColors = true,
.force = true
});
}
TEST_F(ValuePrintingTests, ansiColorsError)
{
Value throw_ = state.getBuiltin("throw");
Value message;
message.mkString("uh oh!");
Value vError;
vError.mkApp(&throw_, &message);
test(vError,
ANSI_RED
"«"
ANSI_RED
"error:"
ANSI_NORMAL
"\n … while calling the '"
ANSI_MAGENTA
"throw"
ANSI_NORMAL
"' builtin\n\n "
ANSI_RED
"error:"
ANSI_NORMAL
" uh oh!»"
ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true,
});
}
TEST_F(ValuePrintingTests, ansiColorsDerivationError)
{
Value throw_ = state.getBuiltin("throw");
Value message;
message.mkString("uh oh!");
Value vError;
vError.mkApp(&throw_, &message);
Value vDerivation;
vDerivation.mkString("derivation");
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.sType, &vDerivation);
builder.insert(state.sDrvPath, &vError);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ drvPath = "
ANSI_RED
"«"
ANSI_RED
"error:"
ANSI_NORMAL
"\n … while calling the '"
ANSI_MAGENTA
"throw"
ANSI_NORMAL
"' builtin\n\n "
ANSI_RED
"error:"
ANSI_NORMAL
" uh oh!»"
ANSI_NORMAL
"; type = "
ANSI_MAGENTA
"\"derivation\""
ANSI_NORMAL
"; }",
PrintOptions {
.ansiColors = true,
.force = true
});
test(vAttrs,
ANSI_RED
"«"
ANSI_RED
"error:"
ANSI_NORMAL
"\n … while calling the '"
ANSI_MAGENTA
"throw"
ANSI_NORMAL
"' builtin\n\n "
ANSI_RED
"error:"
ANSI_NORMAL
" uh oh!»"
ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true,
.derivationPaths = true,
});
}
TEST_F(ValuePrintingTests, ansiColorsAssert)
{
ExprVar eFalse(state.symbols.create("false"));
eFalse.bindVars(state, state.staticBaseEnv);
ExprInt eInt(1);
ExprAssert expr(noPos, &eFalse, &eInt);
Value v;
state.mkThunk_(v, &expr);
test(v,
ANSI_RED "«" ANSI_RED "error:" ANSI_NORMAL " assertion '" ANSI_MAGENTA "false" ANSI_NORMAL "' failed»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
}
TEST_F(ValuePrintingTests, ansiColorsList)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
Value vList;
state.mkList(vList, 5);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 3;
test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_CYAN "2" ANSI_NORMAL " " ANSI_MAGENTA "«nullptr»" ANSI_NORMAL " ]",
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsLambda)
{
Env env {
.up = nullptr,
.values = { }
};
PosTable::Origin origin((std::monostate()));
auto posIdx = state.positions.add(origin, 1, 1);
auto body = ExprInt(0);
auto formals = Formals {};
ExprLambda eLambda(posIdx, createSymbol("a"), &formals, &body);
Value vLambda;
vLambda.mkLambda(&env, &eLambda);
test(vLambda,
ANSI_BLUE "«lambda @ «none»:1:1»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
eLambda.setName(createSymbol("puppy"));
test(vLambda,
ANSI_BLUE "«lambda puppy @ «none»:1:1»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true,
.force = true
});
}
TEST_F(ValuePrintingTests, ansiColorsPrimOp)
{
PrimOp primOp{
.name = "puppy"
};
Value v;
v.mkPrimOp(&primOp);
test(v,
ANSI_BLUE "«primop puppy»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsPrimOpApp)
{
PrimOp primOp{
.name = "puppy"
};
Value vPrimOp;
vPrimOp.mkPrimOp(&primOp);
Value v;
v.mkPrimOpApp(&vPrimOp, nullptr);
test(v,
ANSI_BLUE "«partially applied primop puppy»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsThunk)
{
Value v;
v.mkThunk(nullptr, nullptr);
test(v,
ANSI_MAGENTA "«thunk»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsBlackhole)
{
Value v;
v.mkBlackhole();
test(v,
ANSI_RED "«potential infinite recursion»" ANSI_NORMAL,
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsAttrsRepeated)
{
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
Value vEmpty;
vEmpty.mkAttrs(emptyBuilder.finish());
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("a"), &vEmpty);
builder.insert(state.symbols.create("b"), &vEmpty);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ a = { }; b = " ANSI_MAGENTA "«repeated»" ANSI_NORMAL "; }",
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, ansiColorsListRepeated)
{
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
Value vEmpty;
vEmpty.mkAttrs(emptyBuilder.finish());
Value vList;
state.mkList(vList, 3);
vList.bigList.elems[0] = &vEmpty;
vList.bigList.elems[1] = &vEmpty;
vList.bigList.size = 2;
test(vList,
"[ { } " ANSI_MAGENTA "«repeated»" ANSI_NORMAL " ]",
PrintOptions {
.ansiColors = true
});
}
TEST_F(ValuePrintingTests, listRepeated)
{
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
Value vEmpty;
vEmpty.mkAttrs(emptyBuilder.finish());
Value vList;
state.mkList(vList, 3);
vList.bigList.elems[0] = &vEmpty;
vList.bigList.elems[1] = &vEmpty;
vList.bigList.size = 2;
test(vList, "[ { } «repeated» ]", PrintOptions { });
test(vList,
"[ { } { } ]",
PrintOptions {
.trackRepeated = false
});
}
TEST_F(ValuePrintingTests, ansiColorsAttrsElided)
{
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
BindingsBuilder builder(state, state.allocBindings(10));
builder.insert(state.symbols.create("one"), &vOne);
builder.insert(state.symbols.create("two"), &vTwo);
Value vAttrs;
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT " «1 attribute elided»" ANSI_NORMAL "}",
PrintOptions {
.ansiColors = true,
.maxAttrs = 1
});
Value vThree;
vThree.mkInt(3);
builder.insert(state.symbols.create("three"), &vThree);
vAttrs.mkAttrs(builder.finish());
test(vAttrs,
"{ one = " ANSI_CYAN "1" ANSI_NORMAL "; " ANSI_FAINT " «2 attributes elided»" ANSI_NORMAL "}",
PrintOptions {
.ansiColors = true,
.maxAttrs = 1
});
}
TEST_F(ValuePrintingTests, ansiColorsListElided)
{
BindingsBuilder emptyBuilder(state, state.allocBindings(1));
Value vOne;
vOne.mkInt(1);
Value vTwo;
vTwo.mkInt(2);
Value vList;
state.mkList(vList, 4);
vList.bigList.elems[0] = &vOne;
vList.bigList.elems[1] = &vTwo;
vList.bigList.size = 2;
test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT " «1 item elided»" ANSI_NORMAL "]",
PrintOptions {
.ansiColors = true,
.maxListItems = 1
});
Value vThree;
vThree.mkInt(3);
vList.bigList.elems[2] = &vThree;
vList.bigList.size = 3;
test(vList,
"[ " ANSI_CYAN "1" ANSI_NORMAL " " ANSI_FAINT " «2 items elided»" ANSI_NORMAL "]",
PrintOptions {
.ansiColors = true,
.maxListItems = 1
});
}
} // namespace nix

View file

@ -73,7 +73,7 @@ namespace nix {
}
TEST(logEI, picksUpSysErrorExitCode) {
TEST(logEI, picksUpSystemErrorExitCode) {
MakeError(TestError, Error);
ErrorInfo::programName = std::optional("error-unit-test");
@ -81,12 +81,12 @@ namespace nix {
try {
auto x = readFile(-1);
}
catch (SysError &e) {
catch (SystemError &e) {
testing::internal::CaptureStderr();
logError(e.info());
auto str = testing::internal::GetCapturedStderr();
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SysError --- error-unit-test\x1B[0m\nstatting file: \x1B[33;1mBad file descriptor\x1B[0m\n");
ASSERT_STREQ(str.c_str(), "\x1B[31;1merror:\x1B[0m\x1B[34;1m --- SystemError --- error-unit-test\x1B[0m\nstatting file: \x1B[33;1mBad file descriptor\x1B[0m\n");
}
}