aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/generate-manpage.nix22
-rw-r--r--doc/manual/local.mk2
-rw-r--r--doc/manual/src/contributing/hacking.md73
-rw-r--r--doc/manual/src/glossary.md52
-rw-r--r--src/libcmd/command.cc3
-rw-r--r--src/libcmd/common-eval-args.cc2
-rw-r--r--src/libcmd/common-eval-args.hh2
-rw-r--r--src/libcmd/repl.cc2
-rw-r--r--src/libexpr/primops.cc4
-rw-r--r--src/libmain/common-args.cc1
-rw-r--r--src/libmain/common-args.hh1
-rw-r--r--src/libmain/progress-bar.cc2
-rw-r--r--src/libmain/shared.hh20
-rw-r--r--src/libmain/stack.cc12
-rw-r--r--src/libstore/build/local-derivation-goal.cc15
-rw-r--r--src/libstore/globals.cc10
-rw-r--r--src/libstore/globals.hh8
-rw-r--r--src/libstore/store-api.cc4
-rw-r--r--src/nix/main.cc6
-rw-r--r--tests/lang/eval-okay-versions.nix3
20 files changed, 207 insertions, 37 deletions
diff --git a/doc/manual/generate-manpage.nix b/doc/manual/generate-manpage.nix
index 18a1a8bfe..057719e34 100644
--- a/doc/manual/generate-manpage.nix
+++ b/doc/manual/generate-manpage.nix
@@ -1,11 +1,11 @@
-{ command }:
+{ toplevel }:
with builtins;
with import ./utils.nix;
let
- showCommand = { command, details, filename }:
+ showCommand = { command, details, filename, toplevel }:
let
result = ''
> **Warning** \
@@ -57,14 +57,15 @@ let
maybeOptions = if details.flags == {} then "" else ''
# Options
- ${showOptions details.flags}
+ ${showOptions details.flags toplevel.flags}
'';
- showOptions = options:
+ showOptions = options: commonOptions:
let
+ allOptions = options // commonOptions;
showCategory = cat: ''
${if cat != "" then "**${cat}:**" else ""}
- ${listOptions (filterAttrs (n: v: v.category == cat) options)}
+ ${listOptions (filterAttrs (n: v: v.category == cat) allOptions)}
'';
listOptions = opts: concatStringsSep "\n" (attrValues (mapAttrs showOption opts));
showOption = name: option:
@@ -76,30 +77,33 @@ let
${option.description}
'';
- categories = sort builtins.lessThan (unique (map (cmd: cmd.category) (attrValues options)));
+ categories = sort builtins.lessThan (unique (map (cmd: cmd.category) (attrValues allOptions)));
in concatStrings (map showCategory categories);
in squash result;
appendName = filename: name: (if filename == "nix" then "nix3" else filename) + "-" + name;
- processCommand = { command, details, filename }:
+ processCommand = { command, details, filename, toplevel }:
let
cmd = {
inherit command;
name = filename + ".md";
- value = showCommand { inherit command details filename; };
+ value = showCommand { inherit command details filename toplevel; };
};
subcommand = subCmd: processCommand {
command = command + " " + subCmd;
details = details.commands.${subCmd};
filename = appendName filename subCmd;
+ inherit toplevel;
};
in [ cmd ] ++ concatMap subcommand (attrNames details.commands or {});
+ parsedToplevel = builtins.fromJSON toplevel;
manpages = processCommand {
command = "nix";
- details = builtins.fromJSON command;
+ details = parsedToplevel;
filename = "nix";
+ toplevel = parsedToplevel;
};
tableOfContents = let
diff --git a/doc/manual/local.mk b/doc/manual/local.mk
index 364e02967..486dbd7a2 100644
--- a/doc/manual/local.mk
+++ b/doc/manual/local.mk
@@ -50,7 +50,7 @@ $(d)/src/SUMMARY.md: $(d)/src/SUMMARY.md.in $(d)/src/command-ref/new-cli
$(d)/src/command-ref/new-cli: $(d)/nix.json $(d)/generate-manpage.nix $(bindir)/nix
@rm -rf $@
- $(trace-gen) $(nix-eval) --write-to $@ --expr 'import doc/manual/generate-manpage.nix { command = builtins.readFile $<; }'
+ $(trace-gen) $(nix-eval) --write-to $@ --expr 'import doc/manual/generate-manpage.nix { toplevel = builtins.readFile $<; }'
$(d)/src/command-ref/conf-file.md: $(d)/conf-file.json $(d)/generate-options.nix $(d)/src/command-ref/conf-file-prefix.md $(bindir)/nix
@cat doc/manual/src/command-ref/conf-file-prefix.md > $@.tmp
diff --git a/doc/manual/src/contributing/hacking.md b/doc/manual/src/contributing/hacking.md
index e97b40cdd..9f7d5057b 100644
--- a/doc/manual/src/contributing/hacking.md
+++ b/doc/manual/src/contributing/hacking.md
@@ -86,9 +86,7 @@ by:
$ nix develop
```
-## Testing
-
-Nix comes with three different flavors of tests: unit, functional and integration.
+## Running tests
### Unit-tests
@@ -111,3 +109,72 @@ These tests include everything that needs to interact with external services or
Because these tests are expensive and require more than what the standard github-actions setup provides, they only run on the master branch (on <https://hydra.nixos.org/jobset/nix/master>).
You can run them manually with `nix build .#hydraJobs.tests.{testName}` or `nix-build -A hydraJobs.tests.{testName}`
+
+### Installer tests
+
+After a one-time setup, the Nix repository's GitHub Actions continuous integration (CI) workflow can test the installer each time you push to a branch.
+
+Creating a Cachix cache for your installer tests and adding its authorization token to GitHub enables [two installer-specific jobs in the CI workflow](https://github.com/NixOS/nix/blob/88a45d6149c0e304f6eb2efcc2d7a4d0d569f8af/.github/workflows/ci.yml#L50-L91):
+
+- The `installer` job generates installers for the platforms below and uploads them to your Cachix cache:
+ - `x86_64-linux`
+ - `armv6l-linux`
+ - `armv7l-linux`
+ - `x86_64-darwin`
+
+- The `installer_test` job (which runs on `ubuntu-latest` and `macos-latest`) will try to install Nix with the cached installer and run a trivial Nix command.
+
+#### One-time setup
+
+1. Have a GitHub account with a fork of the [Nix repository](https://github.com/NixOS/nix).
+2. At cachix.org:
+ - Create or log in to an account.
+ - Create a Cachix cache using the format `<github-username>-nix-install-tests`.
+ - Navigate to the new cache > Settings > Auth Tokens.
+ - Generate a new Cachix auth token and copy the generated value.
+3. At github.com:
+ - Navigate to your Nix fork > Settings > Secrets > Actions > New repository secret.
+ - Name the secret `CACHIX_AUTH_TOKEN`.
+ - Paste the copied value of the Cachix cache auth token.
+
+#### Using the CI-generated installer for manual testing
+
+After the CI run completes, you can check the output to extract the installer URL:
+1. Click into the detailed view of the CI run.
+2. Click into any `installer_test` run (the URL you're here to extract will be the same in all of them).
+3. Click into the `Run cachix/install-nix-action@v...` step and click the detail triangle next to the first log line (it will also be `Run cachix/install-nix-action@v...`)
+4. Copy the value of `install_url`
+5. To generate an install command, plug this `install_url` and your GitHub username into this template:
+
+ ```console
+ sh <(curl -L <install_url>) --tarball-url-prefix https://<github-username>-nix-install-tests.cachix.org/serve
+ ```
+
+<!-- #### Manually generating test installers
+
+There's obviously a manual way to do this, and it's still the only way for
+platforms that lack GA runners.
+
+I did do this back in Fall 2020 (before the GA approach encouraged here). I'll
+sketch what I recall in case it encourages someone to fill in detail, but: I
+didn't know what I was doing at the time and had to fumble/ask around a lot--
+so I don't want to uphold any of it as "right". It may have been dumb or
+the _hard_ way from the getgo. Fundamentals may have changed since.
+
+Here's the build command I used to do this on and for x86_64-darwin:
+nix build --out-link /tmp/foo ".#checks.x86_64-darwin.binaryTarball"
+
+I used the stable out-link to make it easier to script the next steps:
+link=$(readlink /tmp/foo)
+cp $link/*-darwin.tar.xz ~/somewheres
+
+I've lost the last steps and am just going from memory:
+
+From here, I think I had to extract and modify the `install` script to point
+it at this tarball (which I scped to my own site, but it might make more sense
+to just share them locally). I extracted this script once and then just
+search/replaced in it for each new build.
+
+The installer now supports a `--tarball-url-prefix` flag which _may_ have
+solved this need?
+-->
diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md
index aa0ac78cb..70a0eb994 100644
--- a/doc/manual/src/glossary.md
+++ b/doc/manual/src/glossary.md
@@ -7,10 +7,44 @@
translated into low-level *store derivations* (implicitly by
`nix-env` and `nix-build`, or explicitly by `nix-instantiate`).
+ - [content-addressed derivation]{#gloss-content-addressed-derivation}\
+ A derivation which has the
+ [`__contentAddressed`](language/advanced-attributes.md#adv-attr-__contentAddressed)
+ attribute set to `true`.
+
+ - [fixed-output derivation]{#gloss-fixed-output-derivation}\
+ A derivation which includes the
+ [`outputHash`](language/advanced-attributes.md#adv-attr-outputHash) attribute.
+
- [store]{#gloss-store}\
The location in the file system where store objects live. Typically
`/nix/store`.
+ From the perspective of the location where Nix is
+ invoked, the Nix store can be referred to
+ as a "_local_" or a "_remote_" one:
+
+ + A *local store* exists on the filesystem of
+ the machine where Nix is invoked. You can use other
+ local stores by passing the `--store` flag to the
+ `nix` command. Local stores can be used for building derivations.
+
+ + A *remote store* exists anywhere other than the
+ local filesystem. One example is the `/nix/store`
+ directory on another machine, accessed via `ssh` or
+ served by the `nix-serve` Perl script.
+
+ - [chroot store]{#gloss-chroot-store}\
+ A local store whose canonical path is anything other than `/nix/store`.
+
+ - [binary cache]{#gloss-binary-cache}\
+ A *binary cache* is a Nix store which uses a different format: its
+ metadata and signatures are kept in `.narinfo` files rather than in a
+ Nix database. This different format simplifies serving store objects
+ over the network, but cannot host builds. Examples of binary caches
+ include S3 buckets and the [NixOS binary
+ cache](https://cache.nixos.org).
+
- [store path]{#gloss-store-path}\
The location in the file system of a store object, i.e., an
immediate child of the Nix store directory.
@@ -22,6 +56,19 @@
derivation outputs (objects produced by running a build action), or
derivations (files describing a build action).
+ - [input-addressed store object]{#gloss-input-addressed-store-object}\
+ A store object produced by building a
+ non-[content-addressed](#gloss-content-addressed-derivation),
+ non-[fixed-output](#gloss-fixed-output-derivation)
+ derivation.
+
+ - [output-addressed store object]{#gloss-output-addressed-store-object}\
+ A store object whose store path hashes its content. This
+ includes derivations, the outputs of
+ [content-addressed derivations](#gloss-content-addressed-derivation),
+ and the outputs of
+ [fixed-output derivations](#gloss-fixed-output-derivation).
+
- [substitute]{#gloss-substitute}\
A substitute is a command invocation stored in the Nix database that
describes how to build a store object, bypassing the normal build
@@ -29,6 +76,11 @@
store object by downloading a pre-built version of the store object
from some server.
+ - [substituter]{#gloss-substituter}\
+ A *substituter* is an additional store from which Nix will
+ copy store objects it doesn't have. For details, see the
+ [`substituters` option](command-ref/conf-file.html#conf-substituters).
+
- [purity]{#gloss-purity}\
The assumption that equal Nix derivations when run always produce
the same output. This cannot be guaranteed in general (e.g., a
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
index 14bb27936..1fdd9e0bd 100644
--- a/src/libcmd/command.cc
+++ b/src/libcmd/command.cc
@@ -88,7 +88,8 @@ EvalCommand::EvalCommand()
{
addFlag({
.longName = "debugger",
- .description = "start an interactive environment if evaluation fails",
+ .description = "Start an interactive environment if evaluation fails.",
+ .category = MixEvalArgs::category,
.handler = {&startReplOnEvalErrors, true},
});
}
diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc
index 5b6e82388..140ed3b88 100644
--- a/src/libcmd/common-eval-args.cc
+++ b/src/libcmd/common-eval-args.cc
@@ -13,8 +13,6 @@ namespace nix {
MixEvalArgs::MixEvalArgs()
{
- auto category = "Common evaluation options";
-
addFlag({
.longName = "arg",
.description = "Pass the value *expr* as the argument *name* to Nix functions.",
diff --git a/src/libcmd/common-eval-args.hh b/src/libcmd/common-eval-args.hh
index 03fa226aa..1ec800613 100644
--- a/src/libcmd/common-eval-args.hh
+++ b/src/libcmd/common-eval-args.hh
@@ -10,6 +10,8 @@ class Bindings;
struct MixEvalArgs : virtual Args
{
+ static constexpr auto category = "Common evaluation options";
+
MixEvalArgs();
Bindings * getAutoArgs(EvalState & state);
diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc
index 61c05050f..df8932087 100644
--- a/src/libcmd/repl.cc
+++ b/src/libcmd/repl.cc
@@ -1050,7 +1050,7 @@ struct CmdRepl : InstallablesCommand
evalSettings.pureEval = false;
}
- void prepare()
+ void prepare() override
{
if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
warn("future versions of Nix will require using `--file` to load a file");
diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc
index 28b998474..840bfecef 100644
--- a/src/libexpr/primops.cc
+++ b/src/libexpr/primops.cc
@@ -3821,8 +3821,8 @@ static RegisterPrimOp primop_parseDrvName({
.args = {"s"},
.doc = R"(
Split the string *s* into a package name and version. The package
- name is everything up to but not including the first dash followed
- by a digit, and the version is everything following that dash. The
+ name is everything up to but not including the first dash not followed
+ by a letter, and the version is everything following that dash. The
result is returned in a set `{ name, version }`. Thus,
`builtins.parseDrvName "nix-0.12pre12876"` returns `{ name =
"nix"; version = "0.12pre12876"; }`.
diff --git a/src/libmain/common-args.cc b/src/libmain/common-args.cc
index 12f5403ea..f92920d18 100644
--- a/src/libmain/common-args.cc
+++ b/src/libmain/common-args.cc
@@ -32,6 +32,7 @@ MixCommonArgs::MixCommonArgs(const std::string & programName)
addFlag({
.longName = "option",
.description = "Set the Nix configuration setting *name* to *value* (overriding `nix.conf`).",
+ .category = miscCategory,
.labels = {"name", "value"},
.handler = {[](std::string name, std::string value) {
try {
diff --git a/src/libmain/common-args.hh b/src/libmain/common-args.hh
index 25453b8c6..f180d83ce 100644
--- a/src/libmain/common-args.hh
+++ b/src/libmain/common-args.hh
@@ -6,6 +6,7 @@ namespace nix {
//static constexpr auto commonArgsCategory = "Miscellaneous common options";
static constexpr auto loggingCategory = "Logging-related options";
+static constexpr auto miscCategory = "Miscellaneous global options";
class MixCommonArgs : public virtual Args
{
diff --git a/src/libmain/progress-bar.cc b/src/libmain/progress-bar.cc
index 0bbeaff8d..961f4e18a 100644
--- a/src/libmain/progress-bar.cc
+++ b/src/libmain/progress-bar.cc
@@ -503,7 +503,7 @@ public:
return s[0];
}
- virtual void setPrintBuildLogs(bool printBuildLogs)
+ void setPrintBuildLogs(bool printBuildLogs) override
{
this->printBuildLogs = printBuildLogs;
}
diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh
index 0cc56d47d..3c37fd627 100644
--- a/src/libmain/shared.hh
+++ b/src/libmain/shared.hh
@@ -113,5 +113,25 @@ struct PrintFreed
/* Install a SIGSEGV handler to detect stack overflows. */
void detectStackOverflow();
+/* Pluggable behavior to run in case of a stack overflow.
+
+ Default value: defaultStackOverflowHandler.
+
+ This is called by the handler installed by detectStackOverflow().
+
+ This gives Nix library consumers a limit opportunity to report the error
+ condition. The handler should exit the process.
+ See defaultStackOverflowHandler() for a reference implementation.
+
+ NOTE: Use with diligence, because this runs in the signal handler, with very
+ limited stack space and a potentially a corrupted heap, all while the failed
+ thread is blocked indefinitely. All functions called must be reentrant. */
+extern std::function<void(siginfo_t * info, void * ctx)> stackOverflowHandler;
+
+/* The default, robust implementation of stackOverflowHandler.
+
+ Prints an error message directly to stderr using a syscall instead of the
+ logger. Exits the process immediately after. */
+void defaultStackOverflowHandler(siginfo_t * info, void * ctx);
}
diff --git a/src/libmain/stack.cc b/src/libmain/stack.cc
index b0a4a4c5d..10f71c1dc 100644
--- a/src/libmain/stack.cc
+++ b/src/libmain/stack.cc
@@ -1,4 +1,5 @@
#include "error.hh"
+#include "shared.hh"
#include <cstring>
#include <cstddef>
@@ -29,9 +30,7 @@ static void sigsegvHandler(int signo, siginfo_t * info, void * ctx)
ptrdiff_t diff = (char *) info->si_addr - sp;
if (diff < 0) diff = -diff;
if (diff < 4096) {
- char msg[] = "error: stack overflow (possible infinite recursion)\n";
- [[gnu::unused]] auto res = write(2, msg, strlen(msg));
- _exit(1); // maybe abort instead?
+ nix::stackOverflowHandler(info, ctx);
}
}
@@ -67,5 +66,12 @@ void detectStackOverflow()
#endif
}
+std::function<void(siginfo_t * info, void * ctx)> stackOverflowHandler(defaultStackOverflowHandler);
+
+void defaultStackOverflowHandler(siginfo_t * info, void * ctx) {
+ char msg[] = "error: stack overflow (possible infinite recursion)\n";
+ [[gnu::unused]] auto res = write(2, msg, strlen(msg));
+ _exit(1); // maybe abort instead?
+}
}
diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc
index 18b682e13..5cea3b590 100644
--- a/src/libstore/build/local-derivation-goal.cc
+++ b/src/libstore/build/local-derivation-goal.cc
@@ -1594,6 +1594,8 @@ void LocalDerivationGoal::runChild()
/* Warning: in the child we should absolutely not make any SQLite
calls! */
+ bool sendException = true;
+
try { /* child */
commonChildInit(builderOut);
@@ -2050,6 +2052,8 @@ void LocalDerivationGoal::runChild()
/* Indicate that we managed to set up the build environment. */
writeFull(STDERR_FILENO, std::string("\2\n"));
+ sendException = false;
+
/* Execute the program. This should not return. */
if (drv->isBuiltin()) {
try {
@@ -2103,10 +2107,13 @@ void LocalDerivationGoal::runChild()
throw SysError("executing '%1%'", drv->builder);
} catch (Error & e) {
- writeFull(STDERR_FILENO, "\1\n");
- FdSink sink(STDERR_FILENO);
- sink << e;
- sink.flush();
+ if (sendException) {
+ writeFull(STDERR_FILENO, "\1\n");
+ FdSink sink(STDERR_FILENO);
+ sink << e;
+ sink.flush();
+ } else
+ std::cerr << e.msg();
_exit(1);
}
}
diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc
index d724897bb..ff658c428 100644
--- a/src/libstore/globals.cc
+++ b/src/libstore/globals.cc
@@ -154,13 +154,9 @@ StringSet Settings::getDefaultExtraPlatforms()
// machines. Note that we can’t force processes from executing
// x86_64 in aarch64 environments or vice versa since they can
// always exec with their own binary preferences.
- if (pathExists("/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist") ||
- pathExists("/System/Library/LaunchDaemons/com.apple.oahd.plist")) {
- if (std::string{SYSTEM} == "x86_64-darwin")
- extraPlatforms.insert("aarch64-darwin");
- else if (std::string{SYSTEM} == "aarch64-darwin")
- extraPlatforms.insert("x86_64-darwin");
- }
+ if (std::string{SYSTEM} == "aarch64-darwin" &&
+ runProgram(RunOptions {.program = "arch", .args = {"-arch", "x86_64", "/usr/bin/true"}, .mergeStderrToStdout = true}).first == 0)
+ extraPlatforms.insert("x86_64-darwin");
#endif
return extraPlatforms;
diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh
index 75927d395..3dcf3d479 100644
--- a/src/libstore/globals.hh
+++ b/src/libstore/globals.hh
@@ -619,6 +619,14 @@ public:
are tried based on their Priority value, which each substituter can set
independently. Lower value means higher priority.
The default is `https://cache.nixos.org`, with a Priority of 40.
+
+ Nix will copy a store path from a remote store only if one
+ of the following is true:
+
+ - the store object is signed by one of the [`trusted-public-keys`](#conf-trusted-public-keys)
+ - the substituter is in the [`trusted-substituters`](#conf-trusted-substituters) list
+ - the [`require-sigs`](#conf-require-sigs) option has been set to `false`
+ - the store object is [output-addressed](glossary.md#gloss-output-addressed-store-object)
)",
{"binary-caches"}};
diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc
index 86b12257a..06a9758fc 100644
--- a/src/libstore/store-api.cc
+++ b/src/libstore/store-api.cc
@@ -1363,9 +1363,9 @@ std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Para
} catch (Error & e) {
return std::make_shared<LocalStore>(params);
}
- warn("'/nix' does not exist, so Nix will use '%s' as a chroot store", chrootStore);
+ warn("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore);
} else
- debug("'/nix' does not exist, so Nix will use '%s' as a chroot store", chrootStore);
+ debug("'%s' does not exist, so Nix will use '%s' as a chroot store", stateDir, chrootStore);
Store::Params params2;
params2["root"] = chrootStore;
return std::make_shared<LocalStore>(params2);
diff --git a/src/nix/main.cc b/src/nix/main.cc
index e0155cd5d..d78312944 100644
--- a/src/nix/main.cc
+++ b/src/nix/main.cc
@@ -74,6 +74,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({
.longName = "help",
.description = "Show usage information.",
+ .category = miscCategory,
.handler = {[&]() { throw HelpRequested(); }},
});
@@ -88,6 +89,7 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
addFlag({
.longName = "version",
.description = "Show version information.",
+ .category = miscCategory,
.handler = {[&]() { showVersion = true; }},
});
@@ -95,12 +97,14 @@ struct NixArgs : virtual MultiCommand, virtual MixCommonArgs
.longName = "offline",
.aliases = {"no-net"}, // FIXME: remove
.description = "Disable substituters and consider all previously downloaded files up-to-date.",
+ .category = miscCategory,
.handler = {[&]() { useNet = false; }},
});
addFlag({
.longName = "refresh",
.description = "Consider all previously downloaded files out-of-date.",
+ .category = miscCategory,
.handler = {[&]() { refresh = true; }},
});
}
@@ -187,7 +191,7 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve
*vUtils);
auto attrs = state.buildBindings(16);
- attrs.alloc("command").mkString(toplevel.toJSON().dump());
+ attrs.alloc("toplevel").mkString(toplevel.toJSON().dump());
auto vRes = state.allocValue();
state.callFunction(*vGenerateManpage, state.allocValue()->mkAttrs(attrs), *vRes, noPos);
diff --git a/tests/lang/eval-okay-versions.nix b/tests/lang/eval-okay-versions.nix
index e63c36586..e9111f5f4 100644
--- a/tests/lang/eval-okay-versions.nix
+++ b/tests/lang/eval-okay-versions.nix
@@ -4,6 +4,7 @@ let
name2 = "hello";
name3 = "915resolution-0.5.2";
name4 = "xf86-video-i810-1.7.4";
+ name5 = "name-that-ends-with-dash--1.0";
eq = 0;
lt = builtins.sub 0 1;
@@ -23,6 +24,8 @@ let
((builtins.parseDrvName name3).version == "0.5.2")
((builtins.parseDrvName name4).name == "xf86-video-i810")
((builtins.parseDrvName name4).version == "1.7.4")
+ ((builtins.parseDrvName name5).name == "name-that-ends-with-dash")
+ ((builtins.parseDrvName name5).version == "-1.0")
(versionTest "1.0" "2.3" lt)
(versionTest "2.1" "2.3" lt)
(versionTest "2.3" "2.3" eq)