mirror of
https://github.com/privatevoid-net/nix-super.git
synced 2024-11-27 00:06:16 +02:00
Merge branch 'read-only-local-store' into overlayfs-store
This commit is contained in:
commit
b852bdb3f8
37 changed files with 360 additions and 154 deletions
2
.github/workflows/backport.yml
vendored
2
.github/workflows/backport.yml
vendored
|
@ -21,7 +21,7 @@ jobs:
|
|||
fetch-depth: 0
|
||||
- name: Create backport PRs
|
||||
# should be kept in sync with `version`
|
||||
uses: zeebe-io/backport-action@v1.2.0
|
||||
uses: zeebe-io/backport-action@v1.3.0
|
||||
with:
|
||||
# Config README: https://github.com/zeebe-io/backport-action#backport-action
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
11
.github/workflows/ci.yml
vendored
11
.github/workflows/ci.yml
vendored
|
@ -19,7 +19,10 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: cachix/install-nix-action@v20
|
||||
- uses: cachix/install-nix-action@v21
|
||||
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@v12
|
||||
if: needs.check_secrets.outputs.cachix == 'true'
|
||||
|
@ -58,7 +61,7 @@ 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@v20
|
||||
- uses: cachix/install-nix-action@v21
|
||||
with:
|
||||
install_url: https://releases.nixos.org/nix/nix-2.13.3/install
|
||||
- uses: cachix/cachix-action@v12
|
||||
|
@ -79,7 +82,7 @@ jobs:
|
|||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV
|
||||
- uses: cachix/install-nix-action@v20
|
||||
- uses: cachix/install-nix-action@v21
|
||||
with:
|
||||
install_url: '${{needs.installer.outputs.installerURL}}'
|
||||
install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve"
|
||||
|
@ -106,7 +109,7 @@ jobs:
|
|||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: cachix/install-nix-action@v20
|
||||
- uses: cachix/install-nix-action@v21
|
||||
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
|
||||
|
|
2
.version
2
.version
|
@ -1 +1 @@
|
|||
2.16.0
|
||||
2.17.0
|
||||
|
|
|
@ -105,6 +105,7 @@
|
|||
- [CLI guideline](contributing/cli-guideline.md)
|
||||
- [Release Notes](release-notes/release-notes.md)
|
||||
- [Release X.Y (202?-??-??)](release-notes/rl-next.md)
|
||||
- [Release 2.16 (2023-05-31)](release-notes/rl-2.16.md)
|
||||
- [Release 2.15 (2023-04-11)](release-notes/rl-2.15.md)
|
||||
- [Release 2.14 (2023-02-28)](release-notes/rl-2.14.md)
|
||||
- [Release 2.13 (2023-01-17)](release-notes/rl-2.13.md)
|
||||
|
|
|
@ -38,11 +38,9 @@ contains Nix.
|
|||
|
||||
> **Warning**
|
||||
>
|
||||
> If you are building via the Nix daemon, it is the Nix daemon user
|
||||
> account (that is, `root`) that should have SSH access to the remote
|
||||
> machine. If you can’t or don’t want to configure `root` to be able to
|
||||
> access to remote machine, you can use a private Nix store instead by
|
||||
> passing e.g. `--store ~/my-nix`.
|
||||
> If you are building via the Nix daemon, it is the Nix daemon user account (that is, `root`) that should have SSH access to a user (not necessarily `root`) on the remote machine.
|
||||
>
|
||||
> If you can’t or don’t want to configure `root` to be able to access the remote machine, you can use a private Nix store instead by passing e.g. `--store ~/my-nix` when running a Nix command from the local machine.
|
||||
|
||||
The list of remote machines can be specified on the command line or in
|
||||
the Nix configuration file. The former is convenient for testing. For
|
||||
|
|
|
@ -17,3 +17,27 @@ These constants are built into the Nix language evaluator:
|
|||
The built-in value `currentSystem` evaluates to the Nix platform
|
||||
identifier for the Nix installation on which the expression is being
|
||||
evaluated, such as `"i686-linux"` or `"x86_64-darwin"`.
|
||||
|
||||
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
|
||||
- [`builtins.currentTime`]{#builtins-currentTime} (integer)
|
||||
|
||||
Return the [Unix time](https://en.wikipedia.org/wiki/Unix_time) at first evaluation.
|
||||
Repeated references to that name will re-use the initially obtained value.
|
||||
|
||||
Example:
|
||||
|
||||
```console
|
||||
$ nix repl
|
||||
Welcome to Nix 2.15.1 Type :? for help.
|
||||
|
||||
nix-repl> builtins.currentTime
|
||||
1683705525
|
||||
|
||||
nix-repl> builtins.currentTime
|
||||
1683705525
|
||||
```
|
||||
|
||||
The [store path](@docroot@/glossary.md#gloss-store-path) of a derivation depending on `currentTime` will differ for each evaluation.
|
||||
|
||||
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
|
||||
## Recursive sets
|
||||
|
||||
Recursive sets are just normal sets, but the attributes can refer to
|
||||
each other. For example,
|
||||
Recursive sets are like normal [attribute sets](./values.md#attribute-set), but the attributes can refer to each other.
|
||||
|
||||
> *rec-attrset* = `rec {` [ *name* `=` *expr* `;` `]`... `}`
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
rec {
|
||||
|
@ -12,7 +15,9 @@ rec {
|
|||
}.x
|
||||
```
|
||||
|
||||
evaluates to `123`. Note that without `rec` the binding `x = y;` would
|
||||
This evaluates to `123`.
|
||||
|
||||
Note that without `rec` the binding `x = y;` would
|
||||
refer to the variable `y` in the surrounding scope, if one exists, and
|
||||
would be invalid if no such variable exists. That is, in a normal
|
||||
(non-recursive) set, attributes are not added to the lexical scope; in a
|
||||
|
@ -33,7 +38,10 @@ will crash with an `infinite recursion encountered` error message.
|
|||
## Let-expressions
|
||||
|
||||
A let-expression allows you to define local variables for an expression.
|
||||
For instance,
|
||||
|
||||
> *let-in* = `let` [ *identifier* = *expr* ]... `in` *expr*
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
let
|
||||
|
@ -42,18 +50,19 @@ let
|
|||
in x + y
|
||||
```
|
||||
|
||||
evaluates to `"foobar"`.
|
||||
This evaluates to `"foobar"`.
|
||||
|
||||
## Inheriting attributes
|
||||
|
||||
When defining a set or in a let-expression it is often convenient to
|
||||
copy variables from the surrounding lexical scope (e.g., when you want
|
||||
to propagate attributes). This can be shortened using the `inherit`
|
||||
keyword. For instance,
|
||||
When defining an [attribute set](./values.md#attribute-set) or in a [let-expression](#let-expressions) it is often convenient to copy variables from the surrounding lexical scope (e.g., when you want to propagate attributes).
|
||||
This can be shortened using the `inherit` keyword.
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
let x = 123; in
|
||||
{ inherit x;
|
||||
{
|
||||
inherit x;
|
||||
y = 456;
|
||||
}
|
||||
```
|
||||
|
@ -62,15 +71,23 @@ is equivalent to
|
|||
|
||||
```nix
|
||||
let x = 123; in
|
||||
{ x = x;
|
||||
{
|
||||
x = x;
|
||||
y = 456;
|
||||
}
|
||||
```
|
||||
|
||||
and both evaluate to `{ x = 123; y = 456; }`. (Note that this works
|
||||
because `x` is added to the lexical scope by the `let` construct.) It is
|
||||
also possible to inherit attributes from another set. For instance, in
|
||||
this fragment from `all-packages.nix`,
|
||||
and both evaluate to `{ x = 123; y = 456; }`.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> This works because `x` is added to the lexical scope by the `let` construct.
|
||||
|
||||
It is also possible to inherit attributes from another attribute set.
|
||||
|
||||
Example:
|
||||
|
||||
In this fragment from `all-packages.nix`,
|
||||
|
||||
```nix
|
||||
graphviz = (import ../tools/graphics/graphviz) {
|
||||
|
|
|
@ -35,17 +35,14 @@
|
|||
|
||||
## Attribute selection
|
||||
|
||||
> *attrset* `.` *attrpath* \[ `or` *expr* \]
|
||||
|
||||
Select the attribute denoted by attribute path *attrpath* from [attribute set] *attrset*.
|
||||
If the attribute doesn’t exist, return the *expr* after `or` if provided, otherwise abort evaluation.
|
||||
|
||||
<!-- FIXME: the following should to into its own language syntax section, but that needs more work to fit in well -->
|
||||
An attribute path is a dot-separated list of [attribute names](./values.md#attribute-set).
|
||||
|
||||
An attribute path is a dot-separated list of attribute names.
|
||||
An attribute name can be an identifier or a string.
|
||||
|
||||
> *attrpath* = *name* [ `.` *name* ]... \
|
||||
> *name* = *identifier* | *string* \
|
||||
> *identifier* ~ `[a-zA-Z_][a-zA-Z0-9_'-]*`
|
||||
> *attrpath* = *name* [ `.` *name* ]...
|
||||
|
||||
[Attribute selection]: #attribute-selection
|
||||
|
||||
|
|
|
@ -164,9 +164,17 @@ Note that lists are only lazy in values, and they are strict in length.
|
|||
|
||||
An attribute set is a collection of name-value-pairs (called *attributes*) enclosed in curly brackets (`{ }`).
|
||||
|
||||
An attribute name can be an identifier or a [string](#string).
|
||||
An identifier must start with a letter (`a-z`, `A-Z`) or underscore (`_`), and can otherwise contain letters (`a-z`, `A-Z`), numbers (`0-9`), underscores (`_`), apostrophes (`'`), or dashes (`-`).
|
||||
|
||||
> *name* = *identifier* | *string* \
|
||||
> *identifier* ~ `[a-zA-Z_][a-zA-Z0-9_'-]*`
|
||||
|
||||
Names and values are separated by an equal sign (`=`).
|
||||
Each value is an arbitrary expression terminated by a semicolon (`;`).
|
||||
|
||||
> *attrset* = `{` [ *name* `=` *expr* `;` `]`... `}`
|
||||
|
||||
Attributes can appear in any order.
|
||||
An attribute name may only occur once.
|
||||
|
||||
|
@ -182,15 +190,19 @@ Example:
|
|||
|
||||
This defines a set with attributes named `x`, `text`, `y`.
|
||||
|
||||
Attributes can be selected from a set using the `.` operator. For
|
||||
instance,
|
||||
Attributes can be accessed with the [`.` operator](./operators.md#attribute-selection).
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
{ a = "Foo"; b = "Bar"; }.a
|
||||
```
|
||||
|
||||
evaluates to `"Foo"`. It is possible to provide a default value in an
|
||||
attribute selection using the `or` keyword:
|
||||
This evaluates to `"Foo"`.
|
||||
|
||||
It is possible to provide a default value in an attribute selection using the `or` keyword.
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
{ a = "Foo"; b = "Bar"; }.c or "Xyzzy"
|
||||
|
|
8
doc/manual/src/release-notes/rl-2.16.md
Normal file
8
doc/manual/src/release-notes/rl-2.16.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Release 2.16 (2023-05-31)
|
||||
|
||||
* Speed-up of downloads from binary caches.
|
||||
The number of parallel downloads (also known as substitutions) has been separated from the [`--max-jobs` setting](../command-ref/conf-file.md#conf-max-jobs).
|
||||
The new setting is called [`max-substitution-jobs`](../command-ref/conf-file.md#conf-max-substitution-jobs).
|
||||
The number of parallel downloads is now set to 16 by default (previously, the default was 1 due to the coupling to build jobs).
|
||||
|
||||
* The function [`builtins.replaceStrings`](@docroot@/language/builtins.md#builtins-replaceStrings) is now lazy in the value of its second argument `to`. That is, `to` is only evaluated when its corresponding pattern in `from` is matched in the string `s`.
|
|
@ -1,6 +1,2 @@
|
|||
# Release X.Y (202?-??-??)
|
||||
|
||||
- Speed-up of downloads from binary caches.
|
||||
The number of parallel downloads (also known as substitutions) has been separated from the [`--max-jobs` setting](../command-ref/conf-file.md#conf-max-jobs).
|
||||
The new setting is called [`max-substitution-jobs`](../command-ref/conf-file.md#conf-max-substitution-jobs).
|
||||
The number of parallel downloads is now set to 16 by default (previously, the default was 1 due to the coupling to build jobs).
|
||||
|
|
|
@ -119,8 +119,7 @@ release:
|
|||
TODO: This script requires the right AWS credentials. Document.
|
||||
|
||||
TODO: This script currently requires a
|
||||
`/home/eelco/Dev/nix-pristine` and
|
||||
`/home/eelco/Dev/nixpkgs-pristine`.
|
||||
`/home/eelco/Dev/nix-pristine`.
|
||||
|
||||
TODO: trigger nixos.org netlify: https://docs.netlify.com/configure-builds/build-hooks/
|
||||
|
||||
|
@ -141,7 +140,7 @@ release:
|
|||
$ git checkout master
|
||||
$ git pull
|
||||
$ NEW_VERSION=2.13.0
|
||||
$ echo -n $NEW_VERSION > .version
|
||||
$ echo $NEW_VERSION > .version
|
||||
$ git checkout -b bump-$NEW_VERSION
|
||||
$ git commit -a -m 'Bump version'
|
||||
$ git push --set-upstream origin bump-$NEW_VERSION
|
||||
|
|
|
@ -15,7 +15,6 @@ my $evalId = $ARGV[0] or die "Usage: $0 EVAL-ID\n";
|
|||
|
||||
my $releasesBucketName = "nix-releases";
|
||||
my $channelsBucketName = "nix-channels";
|
||||
my $nixpkgsDir = "/home/eelco/Dev/nixpkgs-pristine";
|
||||
|
||||
my $TMPDIR = $ENV{'TMPDIR'} // "/tmp";
|
||||
|
||||
|
@ -200,26 +199,22 @@ for my $fn (glob "$tmpDir/*") {
|
|||
}
|
||||
}
|
||||
|
||||
# Update nix-fallback-paths.nix.
|
||||
# Print new nix-fallback-paths.nix.
|
||||
if ($isLatest) {
|
||||
system("cd $nixpkgsDir && git pull") == 0 or die;
|
||||
|
||||
sub getStorePath {
|
||||
my ($jobName) = @_;
|
||||
my $buildInfo = decode_json(fetch("$evalUrl/job/$jobName", 'application/json'));
|
||||
return $buildInfo->{buildoutputs}->{out}->{path} or die "cannot get store path for '$jobName'";
|
||||
}
|
||||
|
||||
write_file("$nixpkgsDir/nixos/modules/installer/tools/nix-fallback-paths.nix",
|
||||
print STDERR "nixos/modules/installer/tools/nix-fallback-paths.nix:\n" .
|
||||
"{\n" .
|
||||
" x86_64-linux = \"" . getStorePath("build.x86_64-linux") . "\";\n" .
|
||||
" i686-linux = \"" . getStorePath("build.i686-linux") . "\";\n" .
|
||||
" aarch64-linux = \"" . getStorePath("build.aarch64-linux") . "\";\n" .
|
||||
" x86_64-darwin = \"" . getStorePath("build.x86_64-darwin") . "\";\n" .
|
||||
" aarch64-darwin = \"" . getStorePath("build.aarch64-darwin") . "\";\n" .
|
||||
"}\n");
|
||||
|
||||
system("cd $nixpkgsDir && git commit -a -m 'nix-fallback-paths.nix: Update to $version'") == 0 or die;
|
||||
"}\n";
|
||||
}
|
||||
|
||||
# Update the "latest" symlink.
|
||||
|
|
|
@ -246,8 +246,15 @@ printf -v _OLD_LINE_FMT "%b" $'\033[1;7;31m-'"$ESC ${RED}%L${ESC}"
|
|||
printf -v _NEW_LINE_FMT "%b" $'\033[1;7;32m+'"$ESC ${GREEN}%L${ESC}"
|
||||
|
||||
_diff() {
|
||||
# macOS Ventura doesn't ship with GNU diff. Print similar output except
|
||||
# without +/- markers or dimming
|
||||
if diff --version | grep -q "Apple diff"; then
|
||||
printf -v CHANGED_GROUP_FORMAT "%b" "${GREEN}%>${RED}%<${ESC}"
|
||||
diff --changed-group-format="$CHANGED_GROUP_FORMAT" "$@"
|
||||
else
|
||||
# simple colorized diff comatible w/ pre `--color` versions
|
||||
diff --unchanged-group-format="$_UNCHANGED_GRP_FMT" --old-line-format="$_OLD_LINE_FMT" --new-line-format="$_NEW_LINE_FMT" --unchanged-line-format=" %L" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
confirm_rm() {
|
||||
|
|
|
@ -2620,7 +2620,7 @@ Strings EvalSettings::getDefaultNixPath()
|
|||
{
|
||||
Strings res;
|
||||
auto add = [&](const Path & p, const std::string & s = std::string()) {
|
||||
if (pathExists(p)) {
|
||||
if (pathAccessible(p)) {
|
||||
if (s.empty()) {
|
||||
res.push_back(p);
|
||||
} else {
|
||||
|
|
|
@ -745,7 +745,13 @@ struct EvalSettings : Config
|
|||
)"};
|
||||
|
||||
Setting<bool> pureEval{this, false, "pure-eval",
|
||||
"Whether to restrict file system and network access to files specified by cryptographic hash."};
|
||||
R"(
|
||||
Pure evaluation mode ensures that the result of Nix expressions is fully determined by explicitly declared inputs, and not influenced by external state:
|
||||
|
||||
- Restrict file system and network access to files specified by cryptographic hash
|
||||
- Disable [`bultins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem) and [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime)
|
||||
)"
|
||||
};
|
||||
|
||||
Setting<bool> enableImportFromDerivation{
|
||||
this, true, "allow-import-from-derivation",
|
||||
|
|
|
@ -1152,15 +1152,13 @@ drvName, Bindings * attrs, Value & v)
|
|||
if (i->value->type() == nNull) continue;
|
||||
}
|
||||
|
||||
if (i->name == state.sContentAddressed) {
|
||||
contentAddressed = state.forceBool(*i->value, noPos, context_below);
|
||||
if (contentAddressed)
|
||||
if (i->name == state.sContentAddressed && state.forceBool(*i->value, noPos, context_below)) {
|
||||
contentAddressed = true;
|
||||
experimentalFeatureSettings.require(Xp::CaDerivations);
|
||||
}
|
||||
|
||||
else if (i->name == state.sImpure) {
|
||||
isImpure = state.forceBool(*i->value, noPos, context_below);
|
||||
if (isImpure)
|
||||
else if (i->name == state.sImpure && state.forceBool(*i->value, noPos, context_below)) {
|
||||
isImpure = true;
|
||||
experimentalFeatureSettings.require(Xp::ImpureDerivations);
|
||||
}
|
||||
|
||||
|
@ -1503,7 +1501,7 @@ static RegisterPrimOp primop_storePath({
|
|||
causes the path to be *copied* again to the Nix store, resulting
|
||||
in a new path (e.g. `/nix/store/ld01dnzc…-source-source`).
|
||||
|
||||
This function is not available in pure evaluation mode.
|
||||
Not available in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
)",
|
||||
.fun = prim_storePath,
|
||||
});
|
||||
|
@ -3910,13 +3908,8 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
|
|||
for (auto elem : args[0]->listItems())
|
||||
from.emplace_back(state.forceString(*elem, pos, "while evaluating one of the strings to replace passed to builtins.replaceStrings"));
|
||||
|
||||
std::vector<std::pair<std::string, NixStringContext>> to;
|
||||
to.reserve(args[1]->listSize());
|
||||
for (auto elem : args[1]->listItems()) {
|
||||
NixStringContext ctx;
|
||||
auto s = state.forceString(*elem, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
|
||||
to.emplace_back(s, std::move(ctx));
|
||||
}
|
||||
std::unordered_map<size_t, std::string> cache;
|
||||
auto to = args[1]->listItems();
|
||||
|
||||
NixStringContext context;
|
||||
auto s = state.forceString(*args[2], context, pos, "while evaluating the third argument passed to builtins.replaceStrings");
|
||||
|
@ -3927,10 +3920,19 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
|
|||
bool found = false;
|
||||
auto i = from.begin();
|
||||
auto j = to.begin();
|
||||
for (; i != from.end(); ++i, ++j)
|
||||
size_t j_index = 0;
|
||||
for (; i != from.end(); ++i, ++j, ++j_index)
|
||||
if (s.compare(p, i->size(), *i) == 0) {
|
||||
found = true;
|
||||
res += j->first;
|
||||
auto v = cache.find(j_index);
|
||||
if (v == cache.end()) {
|
||||
NixStringContext ctx;
|
||||
auto ts = state.forceString(**j, ctx, pos, "while evaluating one of the replacement strings passed to builtins.replaceStrings");
|
||||
v = (cache.emplace(j_index, ts)).first;
|
||||
for (auto& path : ctx)
|
||||
context.insert(path);
|
||||
}
|
||||
res += v->second;
|
||||
if (i->empty()) {
|
||||
if (p < s.size())
|
||||
res += s[p];
|
||||
|
@ -3938,9 +3940,6 @@ static void prim_replaceStrings(EvalState & state, const PosIdx pos, Value * * a
|
|||
} else {
|
||||
p += i->size();
|
||||
}
|
||||
for (auto& path : j->second)
|
||||
context.insert(path);
|
||||
j->second.clear();
|
||||
break;
|
||||
}
|
||||
if (!found) {
|
||||
|
@ -3958,7 +3957,11 @@ static RegisterPrimOp primop_replaceStrings({
|
|||
.args = {"from", "to", "s"},
|
||||
.doc = R"(
|
||||
Given string *s*, replace every occurrence of the strings in *from*
|
||||
with the corresponding string in *to*. For example,
|
||||
with the corresponding string in *to*.
|
||||
|
||||
The argument *to* is lazy, that is, it is only evaluated when its corresponding pattern in *from* is matched in the string *s*
|
||||
|
||||
Example:
|
||||
|
||||
```nix
|
||||
builtins.replaceStrings ["oo" "a"] ["a" "i"] "foobar"
|
||||
|
|
|
@ -286,9 +286,9 @@ static RegisterPrimOp primop_fetchurl({
|
|||
.name = "__fetchurl",
|
||||
.args = {"url"},
|
||||
.doc = R"(
|
||||
Download the specified URL and return the path of the downloaded
|
||||
file. This function is not available if [restricted evaluation
|
||||
mode](../command-ref/conf-file.md) is enabled.
|
||||
Download the specified URL and return the path of the downloaded file.
|
||||
|
||||
Not available in [restricted evaluation mode](@docroot@/command-ref/conf-file.md#conf-restrict-eval).
|
||||
)",
|
||||
.fun = prim_fetchurl,
|
||||
});
|
||||
|
@ -338,8 +338,7 @@ static RegisterPrimOp primop_fetchTarball({
|
|||
stdenv.mkDerivation { … }
|
||||
```
|
||||
|
||||
This function is not available if [restricted evaluation
|
||||
mode](../command-ref/conf-file.md) is enabled.
|
||||
Not available in [restricted evaluation mode](@docroot@/command-ref/conf-file.md#conf-restrict-eval).
|
||||
)",
|
||||
.fun = prim_fetchTarball,
|
||||
});
|
||||
|
@ -470,14 +469,9 @@ static RegisterPrimOp primop_fetchGit({
|
|||
}
|
||||
```
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> Nix will refetch the branch in accordance with
|
||||
> the option `tarball-ttl`.
|
||||
Nix will refetch the branch according to the [`tarball-ttl`](@docroot@/command-ref/conf-file.md#conf-tarball-ttl) setting.
|
||||
|
||||
> **Note**
|
||||
>
|
||||
> This behavior is disabled in *Pure evaluation mode*.
|
||||
This behavior is disabled in [pure evaluation mode](@docroot@/command-ref/conf-file.md#conf-pure-eval).
|
||||
|
||||
- To fetch the content of a checked-out work directory:
|
||||
|
||||
|
|
|
@ -171,7 +171,7 @@ namespace nix {
|
|||
hintfmt("value is %s while a string was expected", "an integer"),
|
||||
hintfmt("while evaluating one of the strings to replace passed to builtins.replaceStrings"));
|
||||
|
||||
ASSERT_TRACE2("replaceStrings [ \"old\" ] [ true ] {}",
|
||||
ASSERT_TRACE2("replaceStrings [ \"oo\" ] [ true ] \"foo\"",
|
||||
TypeError,
|
||||
hintfmt("value is %s while a string was expected", "a Boolean"),
|
||||
hintfmt("while evaluating one of the replacement strings passed to builtins.replaceStrings"));
|
||||
|
|
|
@ -75,22 +75,28 @@ SourcePath SourcePath::resolveSymlinks() const
|
|||
|
||||
int linksAllowed = 1024;
|
||||
|
||||
for (auto & component : path) {
|
||||
res.path.push(component);
|
||||
while (true) {
|
||||
if (auto st = res.maybeLstat()) {
|
||||
std::list<std::string> todo;
|
||||
for (auto & c : path)
|
||||
todo.push_back(std::string(c));
|
||||
|
||||
while (!todo.empty()) {
|
||||
auto c = *todo.begin();
|
||||
todo.pop_front();
|
||||
if (c == "" || c == ".")
|
||||
;
|
||||
else if (c == "..")
|
||||
res.path.pop();
|
||||
else {
|
||||
res.path.push(c);
|
||||
if (auto st = res.maybeLstat(); st && st->type == InputAccessor::tSymlink) {
|
||||
if (!linksAllowed--)
|
||||
throw Error("infinite symlink recursion in path '%s'", path);
|
||||
if (st->type != InputAccessor::tSymlink) break;
|
||||
auto target = res.readLink();
|
||||
if (hasPrefix(target, "/"))
|
||||
res = CanonPath(target);
|
||||
else {
|
||||
res.path.pop();
|
||||
res.path.extend(CanonPath(target));
|
||||
if (hasPrefix(target, "/"))
|
||||
res.path = CanonPath::root;
|
||||
todo.splice(todo.begin(), tokenizeString<std::list<std::string>>(target, "/"));
|
||||
}
|
||||
} else
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -357,7 +357,7 @@ bool LocalDerivationGoal::cleanupDecideWhetherDiskFull()
|
|||
for (auto & [_, status] : initialOutputs) {
|
||||
if (!status.known) continue;
|
||||
if (buildMode != bmCheck && status.known->isValid()) continue;
|
||||
auto p = worker.store.printStorePath(status.known->path);
|
||||
auto p = worker.store.toRealPath(status.known->path);
|
||||
if (pathExists(chrootRootDir + p))
|
||||
renameFile((chrootRootDir + p), p);
|
||||
}
|
||||
|
@ -1772,7 +1772,8 @@ void LocalDerivationGoal::runChild()
|
|||
if (pathExists(path))
|
||||
ss.push_back(path);
|
||||
|
||||
dirsInChroot.emplace(settings.caFile, "/etc/ssl/certs/ca-certificates.crt");
|
||||
if (settings.caFile != "")
|
||||
dirsInChroot.try_emplace("/etc/ssl/certs/ca-certificates.crt", settings.caFile, true);
|
||||
}
|
||||
|
||||
for (auto & i : ss) dirsInChroot.emplace(i, i);
|
||||
|
|
|
@ -183,7 +183,7 @@ bool Settings::isWSL1()
|
|||
Path Settings::getDefaultSSLCertFile()
|
||||
{
|
||||
for (auto & fn : {"/etc/ssl/certs/ca-certificates.crt", "/nix/var/nix/profiles/default/etc/ssl/certs/ca-bundle.crt"})
|
||||
if (pathExists(fn)) return fn;
|
||||
if (pathAccessible(fn)) return fn;
|
||||
return "";
|
||||
}
|
||||
|
||||
|
|
|
@ -1014,6 +1014,18 @@ public:
|
|||
| `~/.nix-profile` | `$XDG_STATE_HOME/nix/profile` |
|
||||
| `~/.nix-defexpr` | `$XDG_STATE_HOME/nix/defexpr` |
|
||||
| `~/.nix-channels` | `$XDG_STATE_HOME/nix/channels` |
|
||||
|
||||
If you already have Nix installed and are using [profiles](@docroot@/package-management/profiles.md) or [channels](@docroot@/package-management/channels.md), you should migrate manually when you enable this option.
|
||||
If `$XDG_STATE_HOME` is not set, use `$HOME/.local/state/nix` instead of `$XDG_STATE_HOME/nix`.
|
||||
This can be achieved with the following shell commands:
|
||||
|
||||
```sh
|
||||
nix_state_home=${XDG_STATE_HOME-$HOME/.local/state}/nix
|
||||
mkdir -p $nix_state_home
|
||||
mv $HOME/.nix-profile $nix_state_home/profile
|
||||
mv $HOME/.nix-defexpr $nix_state_home/defexpr
|
||||
mv $HOME/.nix-channels $nix_state_home/channels
|
||||
```
|
||||
)"
|
||||
};
|
||||
};
|
||||
|
|
|
@ -190,7 +190,11 @@ LocalStore::LocalStore(const Params & params)
|
|||
|
||||
/* Create missing state directories if they don't already exist. */
|
||||
createDirs(realStoreDir);
|
||||
if (readOnly) {
|
||||
experimentalFeatureSettings.require(Xp::ReadOnlyLocalStore);
|
||||
} else {
|
||||
makeStoreWritable();
|
||||
}
|
||||
createDirs(linksDir);
|
||||
Path profilesDir = stateDir + "/profiles";
|
||||
createDirs(profilesDir);
|
||||
|
@ -202,10 +206,6 @@ LocalStore::LocalStore(const Params & params)
|
|||
createSymlink(profilesDir, gcRootsDir + "/profiles");
|
||||
}
|
||||
|
||||
if (readOnly) {
|
||||
experimentalFeatureSettings.require(Xp::ReadOnlyLocalStore);
|
||||
}
|
||||
|
||||
for (auto & perUserDir : {profilesDir + "/per-user", gcRootsDir + "/per-user"}) {
|
||||
createDirs(perUserDir);
|
||||
if (!readOnly) {
|
||||
|
|
|
@ -9,8 +9,8 @@ static void checkName(std::string_view path, std::string_view name)
|
|||
if (name.empty())
|
||||
throw BadStorePath("store path '%s' has an empty name", path);
|
||||
if (name.size() > StorePath::MaxPathLen)
|
||||
throw BadStorePath("store path '%s' has a name longer than '%d characters",
|
||||
StorePath::MaxPathLen, path);
|
||||
throw BadStorePath("store path '%s' has a name longer than %d characters",
|
||||
path, StorePath::MaxPathLen);
|
||||
// See nameRegexStr for the definition
|
||||
for (auto c : name)
|
||||
if (!((c >= '0' && c <= '9')
|
||||
|
|
|
@ -50,6 +50,8 @@ constexpr std::array<ExperimentalFeatureDetails, 14> xpFeatureDetails = {{
|
|||
or other impure derivations can rely on impure derivations. Finally,
|
||||
an impure derivation cannot also be
|
||||
[content-addressed](#xp-feature-ca-derivations).
|
||||
|
||||
This is a more explicit alternative to using [`builtins.currentTime`](@docroot@/language/builtin-constants.md#builtins-currentTime).
|
||||
)",
|
||||
},
|
||||
{
|
||||
|
|
|
@ -202,7 +202,7 @@ namespace nix {
|
|||
}
|
||||
|
||||
TEST(pathExists, bogusPathDoesNotExist) {
|
||||
ASSERT_FALSE(pathExists("/home/schnitzel/darmstadt/pommes"));
|
||||
ASSERT_FALSE(pathExists("/schnitzel/darmstadt/pommes"));
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
|
|
|
@ -266,6 +266,17 @@ bool pathExists(const Path & path)
|
|||
return false;
|
||||
}
|
||||
|
||||
bool pathAccessible(const Path & path)
|
||||
{
|
||||
try {
|
||||
return pathExists(path);
|
||||
} catch (SysError & e) {
|
||||
// swallow EPERM
|
||||
if (e.errNo == EPERM) return false;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Path readLink(const Path & path)
|
||||
{
|
||||
|
|
|
@ -120,6 +120,14 @@ struct stat lstat(const Path & path);
|
|||
*/
|
||||
bool pathExists(const Path & path);
|
||||
|
||||
/**
|
||||
* A version of pathExists that returns false on a permission error.
|
||||
* Useful for inferring default paths across directories that might not
|
||||
* be readable.
|
||||
* @return true iff the given path can be accessed and exists
|
||||
*/
|
||||
bool pathAccessible(const Path & path);
|
||||
|
||||
/**
|
||||
* Read the contents (target) of a symbolic link. The result is not
|
||||
* in any way canonicalised.
|
||||
|
|
|
@ -259,6 +259,7 @@ struct CmdFlakeInfo : CmdFlakeMetadata
|
|||
struct CmdFlakeCheck : FlakeCommand
|
||||
{
|
||||
bool build = true;
|
||||
bool checkAllSystems = false;
|
||||
|
||||
CmdFlakeCheck()
|
||||
{
|
||||
|
@ -267,6 +268,11 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
.description = "Do not build checks.",
|
||||
.handler = {&build, false}
|
||||
});
|
||||
addFlag({
|
||||
.longName = "all-systems",
|
||||
.description = "Check the outputs for all systems.",
|
||||
.handler = {&checkAllSystems, true}
|
||||
});
|
||||
}
|
||||
|
||||
std::string description() override
|
||||
|
@ -292,6 +298,7 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
|
||||
lockFlags.applyNixConfig = true;
|
||||
auto flake = lockFlake();
|
||||
auto localSystem = std::string(settings.thisSystem.get());
|
||||
|
||||
bool hasErrors = false;
|
||||
auto reportError = [&](const Error & e) {
|
||||
|
@ -307,6 +314,8 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
}
|
||||
};
|
||||
|
||||
std::set<std::string> omittedSystems;
|
||||
|
||||
// FIXME: rewrite to use EvalCache.
|
||||
|
||||
auto resolve = [&] (PosIdx p) {
|
||||
|
@ -327,6 +336,15 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
reportError(Error("'%s' is not a valid system type, at %s", system, resolve(pos)));
|
||||
};
|
||||
|
||||
auto checkSystemType = [&](const std::string & system, const PosIdx pos) {
|
||||
if (!checkAllSystems && system != localSystem) {
|
||||
omittedSystems.insert(system);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
auto checkDerivation = [&](const std::string & attrPath, Value & v, const PosIdx pos) -> std::optional<StorePath> {
|
||||
try {
|
||||
auto drvInfo = getDerivation(*state, v, false);
|
||||
|
@ -509,6 +527,7 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
for (auto & attr : *vOutput.attrs) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
state->forceAttrs(*attr.value, attr.pos, "");
|
||||
for (auto & attr2 : *attr.value->attrs) {
|
||||
auto drvPath = checkDerivation(
|
||||
|
@ -523,15 +542,18 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else if (name == "formatter") {
|
||||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
checkApp(
|
||||
fmt("%s.%s", name, attr_name),
|
||||
*attr.value, attr.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,11 +562,13 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
for (auto & attr : *vOutput.attrs) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
state->forceAttrs(*attr.value, attr.pos, "");
|
||||
for (auto & attr2 : *attr.value->attrs)
|
||||
checkDerivation(
|
||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||
*attr2.value, attr2.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -553,11 +577,13 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
for (auto & attr : *vOutput.attrs) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
state->forceAttrs(*attr.value, attr.pos, "");
|
||||
for (auto & attr2 : *attr.value->attrs)
|
||||
checkApp(
|
||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||
*attr2.value, attr2.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -566,9 +592,11 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
for (auto & attr : *vOutput.attrs) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
checkDerivation(
|
||||
fmt("%s.%s", name, attr_name),
|
||||
*attr.value, attr.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -577,9 +605,11 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
for (auto & attr : *vOutput.attrs) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos) ) {
|
||||
checkApp(
|
||||
fmt("%s.%s", name, attr_name),
|
||||
*attr.value, attr.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -587,6 +617,7 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
state->forceAttrs(vOutput, pos, "");
|
||||
for (auto & attr : *vOutput.attrs) {
|
||||
checkSystemName(state->symbols[attr.name], attr.pos);
|
||||
checkSystemType(state->symbols[attr.name], attr.pos);
|
||||
// FIXME: do getDerivations?
|
||||
}
|
||||
}
|
||||
|
@ -636,9 +667,11 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
for (auto & attr : *vOutput.attrs) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
checkBundler(
|
||||
fmt("%s.%s", name, attr_name),
|
||||
*attr.value, attr.pos);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -647,12 +680,14 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
for (auto & attr : *vOutput.attrs) {
|
||||
const auto & attr_name = state->symbols[attr.name];
|
||||
checkSystemName(attr_name, attr.pos);
|
||||
if (checkSystemType(attr_name, attr.pos)) {
|
||||
state->forceAttrs(*attr.value, attr.pos, "");
|
||||
for (auto & attr2 : *attr.value->attrs) {
|
||||
checkBundler(
|
||||
fmt("%s.%s.%s", name, attr_name, state->symbols[attr2.name]),
|
||||
*attr2.value, attr2.pos);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -685,7 +720,15 @@ struct CmdFlakeCheck : FlakeCommand
|
|||
}
|
||||
if (hasErrors)
|
||||
throw Error("some errors were encountered during the evaluation");
|
||||
}
|
||||
|
||||
if (!omittedSystems.empty()) {
|
||||
warn(
|
||||
"The check omitted these incompatible systems: %s\n"
|
||||
"Use '--all-systems' to check all.",
|
||||
concatStringsSep(", ", omittedSystems)
|
||||
);
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
static Strings defaultTemplateAttrPathsPrefixes{"templates."};
|
||||
|
|
|
@ -102,6 +102,7 @@ way:
|
|||
available in the flake. If this is undesirable, specify `path:<directory>` explicitly;
|
||||
|
||||
For example, if `/foo/bar` is a git repository with the following structure:
|
||||
|
||||
```
|
||||
.
|
||||
└── baz
|
||||
|
|
|
@ -35,3 +35,9 @@ nix-instantiate --eval -E 'assert 1 + 2 == 3; true'
|
|||
# Check that symlink cycles don't cause a hang.
|
||||
ln -sfn cycle.nix $TEST_ROOT/cycle.nix
|
||||
(! nix eval --file $TEST_ROOT/cycle.nix)
|
||||
|
||||
# Check that relative symlinks are resolved correctly.
|
||||
mkdir -p $TEST_ROOT/xyzzy $TEST_ROOT/foo
|
||||
ln -sfn ../xyzzy $TEST_ROOT/foo/bar
|
||||
printf 123 > $TEST_ROOT/xyzzy/default.nix
|
||||
[[ $(nix eval --impure --expr "import $TEST_ROOT/foo/bar") = 123 ]]
|
||||
|
|
|
@ -72,6 +72,8 @@ cat > $flakeDir/flake.nix <<EOF
|
|||
}
|
||||
EOF
|
||||
|
||||
checkRes=$(nix flake check --keep-going $flakeDir 2>&1 && fail "nix flake check should have failed" || true)
|
||||
nix flake check $flakeDir
|
||||
|
||||
checkRes=$(nix flake check --all-systems --keep-going $flakeDir 2>&1 && fail "nix flake check --all-systems should have failed" || true)
|
||||
echo "$checkRes" | grepQuiet "packages.system-1.default"
|
||||
echo "$checkRes" | grepQuiet "packages.system-2.default"
|
||||
|
|
|
@ -1 +1 @@
|
|||
[ "faabar" "fbar" "fubar" "faboor" "fubar" "XaXbXcX" "X" "a_b" ]
|
||||
[ "faabar" "fbar" "fubar" "faboor" "fubar" "XaXbXcX" "X" "a_b" "fubar" ]
|
||||
|
|
|
@ -8,4 +8,5 @@ with builtins;
|
|||
(replaceStrings [""] ["X"] "abc")
|
||||
(replaceStrings [""] ["X"] "")
|
||||
(replaceStrings ["-"] ["_"] "a-b")
|
||||
(replaceStrings ["oo" "XX"] ["u" (throw "unreachable")] "foobar")
|
||||
]
|
||||
|
|
29
tests/linux-sandbox-cert-test.nix
Normal file
29
tests/linux-sandbox-cert-test.nix
Normal file
|
@ -0,0 +1,29 @@
|
|||
{ fixed-output }:
|
||||
|
||||
with import ./config.nix;
|
||||
|
||||
mkDerivation ({
|
||||
name = "ssl-export";
|
||||
buildCommand = ''
|
||||
# Add some indirection, otherwise grepping into the debug output finds the string.
|
||||
report () { echo CERT_$1_IN_SANDBOX; }
|
||||
|
||||
if [ -f /etc/ssl/certs/ca-certificates.crt ]; then
|
||||
content=$(</etc/ssl/certs/ca-certificates.crt)
|
||||
if [ "$content" == CERT_CONTENT ]; then
|
||||
report present
|
||||
fi
|
||||
else
|
||||
report missing
|
||||
fi
|
||||
|
||||
# Always fail, because we do not want to bother with fixed-output
|
||||
# derivations being cached, and do not want to compute the right hash.
|
||||
false;
|
||||
'';
|
||||
} // (
|
||||
if fixed-output == "fixed-output"
|
||||
then { outputHash = "sha256:0000000000000000000000000000000000000000000000000000000000000000"; }
|
||||
else { }
|
||||
))
|
||||
|
|
@ -40,3 +40,27 @@ grepQuiet 'may not be deterministic' $TEST_ROOT/log
|
|||
|
||||
# Test that sandboxed builds cannot write to /etc easily
|
||||
(! nix-build -E 'with import ./config.nix; mkDerivation { name = "etc-write"; buildCommand = "echo > /etc/test"; }' --no-out-link --sandbox-paths /nix/store)
|
||||
|
||||
|
||||
## Test mounting of SSL certificates into the sandbox
|
||||
testCert () {
|
||||
(! nix-build linux-sandbox-cert-test.nix --argstr fixed-output "$2" --no-out-link --sandbox-paths /nix/store --option ssl-cert-file "$3" 2> $TEST_ROOT/log)
|
||||
cat $TEST_ROOT/log
|
||||
grepQuiet "CERT_${1}_IN_SANDBOX" $TEST_ROOT/log
|
||||
}
|
||||
|
||||
nocert=$TEST_ROOT/no-cert-file.pem
|
||||
cert=$TEST_ROOT/some-cert-file.pem
|
||||
echo -n "CERT_CONTENT" > $cert
|
||||
|
||||
# No cert in sandbox when not a fixed-output derivation
|
||||
testCert missing normal "$cert"
|
||||
|
||||
# No cert in sandbox when ssl-cert-file is empty
|
||||
testCert missing fixed-output ""
|
||||
|
||||
# No cert in sandbox when ssl-cert-file is a nonexistent file
|
||||
testCert missing fixed-output "$nocert"
|
||||
|
||||
# Cert in sandbox when ssl-cert-file is set to an existing file
|
||||
testCert present fixed-output "$cert"
|
||||
|
|
Loading…
Reference in a new issue