Merge remote-tracking branch 'nixos/master'

This commit is contained in:
Max Headroom 2022-07-31 21:49:19 +02:00
commit 9baab7cd1c
80 changed files with 1403 additions and 621 deletions

9
.github/stale.yml vendored
View file

@ -1,10 +1,9 @@
# Configuration for probot-stale - https://github.com/probot/stale # Configuration for probot-stale - https://github.com/probot/stale
daysUntilStale: 180 daysUntilStale: 180
daysUntilClose: 365 daysUntilClose: false
exemptLabels: exemptLabels:
- "critical" - "critical"
- "never-stale"
staleLabel: "stale" staleLabel: "stale"
markComment: | markComment: false
I marked this as stale due to inactivity. → [More info](https://github.com/NixOS/nix/blob/master/.github/STALE-BOT.md) closeComment: false
closeComment: |
I closed this issue due to inactivity. → [More info](https://github.com/NixOS/nix/blob/master/.github/STALE-BOT.md)

View file

@ -4,6 +4,8 @@ on:
pull_request: pull_request:
push: push:
permissions: read-all
jobs: jobs:
tests: tests:
@ -28,6 +30,8 @@ jobs:
- run: nix --experimental-features 'nix-command flakes' flake check -L - run: nix --experimental-features 'nix-command flakes' flake check -L
check_cachix: check_cachix:
permissions:
contents: none
name: Cachix secret present for installer tests name: Cachix secret present for installer tests
runs-on: ubuntu-latest runs-on: ubuntu-latest
outputs: outputs:

View file

@ -1,8 +1,12 @@
name: Hydra status name: Hydra status
permissions: read-all
on: on:
schedule: schedule:
- cron: "12,42 * * * *" - cron: "12,42 * * * *"
workflow_dispatch: workflow_dispatch:
jobs: jobs:
check_hydra_status: check_hydra_status:
name: Check Hydra status name: Check Hydra status

View file

@ -1,4 +1,3 @@
HOST_OS = @host_os@
AR = @AR@ AR = @AR@
BDW_GC_LIBS = @BDW_GC_LIBS@ BDW_GC_LIBS = @BDW_GC_LIBS@
BOOST_LDFLAGS = @BOOST_LDFLAGS@ BOOST_LDFLAGS = @BOOST_LDFLAGS@
@ -13,13 +12,14 @@ ENABLE_S3 = @ENABLE_S3@
GTEST_LIBS = @GTEST_LIBS@ GTEST_LIBS = @GTEST_LIBS@
HAVE_LIBCPUID = @HAVE_LIBCPUID@ HAVE_LIBCPUID = @HAVE_LIBCPUID@
HAVE_SECCOMP = @HAVE_SECCOMP@ HAVE_SECCOMP = @HAVE_SECCOMP@
HOST_OS = @host_os@
LDFLAGS = @LDFLAGS@ LDFLAGS = @LDFLAGS@
LIBARCHIVE_LIBS = @LIBARCHIVE_LIBS@ LIBARCHIVE_LIBS = @LIBARCHIVE_LIBS@
LIBBROTLI_LIBS = @LIBBROTLI_LIBS@ LIBBROTLI_LIBS = @LIBBROTLI_LIBS@
LIBCURL_LIBS = @LIBCURL_LIBS@ LIBCURL_LIBS = @LIBCURL_LIBS@
LIBSECCOMP_LIBS = @LIBSECCOMP_LIBS@
LOWDOWN_LIBS = @LOWDOWN_LIBS@ LOWDOWN_LIBS = @LOWDOWN_LIBS@
OPENSSL_LIBS = @OPENSSL_LIBS@ OPENSSL_LIBS = @OPENSSL_LIBS@
LIBSECCOMP_LIBS = @LIBSECCOMP_LIBS@
PACKAGE_NAME = @PACKAGE_NAME@ PACKAGE_NAME = @PACKAGE_NAME@
PACKAGE_VERSION = @PACKAGE_VERSION@ PACKAGE_VERSION = @PACKAGE_VERSION@
SHELL = @bash@ SHELL = @bash@
@ -31,6 +31,7 @@ datadir = @datadir@
datarootdir = @datarootdir@ datarootdir = @datarootdir@
doc_generate = @doc_generate@ doc_generate = @doc_generate@
docdir = @docdir@ docdir = @docdir@
embedded_sandbox_shell = @embedded_sandbox_shell@
exec_prefix = @exec_prefix@ exec_prefix = @exec_prefix@
includedir = @includedir@ includedir = @includedir@
libdir = @libdir@ libdir = @libdir@

View file

@ -320,6 +320,14 @@ if test ${cross_compiling:-no} = no && ! test -z ${sandbox_shell+x}; then
fi fi
fi fi
AC_ARG_ENABLE(embedded-sandbox-shell, AS_HELP_STRING([--enable-embedded-sandbox-shell],[include the sandbox shell in the Nix binary [default=no]]),
embedded_sandbox_shell=$enableval, embedded_sandbox_shell=no)
AC_SUBST(embedded_sandbox_shell)
if test "$embedded_sandbox_shell" = yes; then
AC_DEFINE(HAVE_EMBEDDED_SANDBOX_SHELL, 1, [Include the sandbox shell in the Nix binary.])
fi
# Expand all variables in config.status. # Expand all variables in config.status.
test "$prefix" = NONE && prefix=$ac_default_prefix test "$prefix" = NONE && prefix=$ac_default_prefix
test "$exec_prefix" = NONE && exec_prefix='${prefix}' test "$exec_prefix" = NONE && exec_prefix='${prefix}'

View file

@ -33,7 +33,7 @@
- [Arguments and Variables](expressions/arguments-variables.md) - [Arguments and Variables](expressions/arguments-variables.md)
- [Building and Testing](expressions/simple-building-testing.md) - [Building and Testing](expressions/simple-building-testing.md)
- [Generic Builder Syntax](expressions/generic-builder.md) - [Generic Builder Syntax](expressions/generic-builder.md)
- [Writing Nix Expressions](expressions/expression-language.md) - [Nix Expression Language](expressions/expression-language.md)
- [Values](expressions/language-values.md) - [Values](expressions/language-values.md)
- [Language Constructs](expressions/language-constructs.md) - [Language Constructs](expressions/language-constructs.md)
- [Operators](expressions/language-operators.md) - [Operators](expressions/language-operators.md)
@ -72,6 +72,7 @@
- [CLI guideline](contributing/cli-guideline.md) - [CLI guideline](contributing/cli-guideline.md)
- [Release Notes](release-notes/release-notes.md) - [Release Notes](release-notes/release-notes.md)
- [Release X.Y (202?-??-??)](release-notes/rl-next.md) - [Release X.Y (202?-??-??)](release-notes/rl-next.md)
- [Release 2.10 (2022-07-11)](release-notes/rl-2.10.md)
- [Release 2.9 (2022-05-30)](release-notes/rl-2.9.md) - [Release 2.9 (2022-05-30)](release-notes/rl-2.9.md)
- [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md) - [Release 2.8 (2022-04-19)](release-notes/rl-2.8.md)
- [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md) - [Release 2.7 (2022-03-07)](release-notes/rl-2.7.md)

View file

@ -31,7 +31,7 @@ subcommand to be performed. These are documented below.
Several commands, such as `nix-env -q` and `nix-env -i`, take a list of Several commands, such as `nix-env -q` and `nix-env -i`, take a list of
arguments that specify the packages on which to operate. These are arguments that specify the packages on which to operate. These are
extended regular expressions that must match the entire name of the extended regular expressions that must match the entire name of the
package. (For details on regular expressions, see regex7.) The match is package. (For details on regular expressions, see **regex**(7).) The match is
case-sensitive. The regular expression can optionally be followed by a case-sensitive. The regular expression can optionally be followed by a
dash and a version number; if omitted, any version of the package will dash and a version number; if omitted, any version of the package will
match. Here are some examples: match. Here are some examples:
@ -412,7 +412,7 @@ The upgrade operation determines whether a derivation `y` is an upgrade
of a derivation `x` by looking at their respective `name` attributes. of a derivation `x` by looking at their respective `name` attributes.
The names (e.g., `gcc-3.3.1` are split into two parts: the package name The names (e.g., `gcc-3.3.1` are split into two parts: the package name
(`gcc`), and the version (`3.3.1`). The version part starts after the (`gcc`), and the version (`3.3.1`). The version part starts after the
first dash not followed by a letter. `x` is considered an upgrade of `y` first dash not followed by a letter. `y` is considered an upgrade of `x`
if their package names match, and the version of `y` is higher than that if their package names match, and the version of `y` is higher than that
of `x`. of `x`.

View file

@ -172,25 +172,27 @@ function and the fifth being a set.
Note that lists are only lazy in values, and they are strict in length. Note that lists are only lazy in values, and they are strict in length.
## Sets ## Attribute Sets
Sets are really the core of the language, since ultimately the Nix Attribute sets are collections of name-value-pairs (called *attributes*) enclosed in curly brackets (`{ }`).
language is all about creating derivations, which are really just sets
of attributes to be passed to build scripts.
Sets are just a list of name/value pairs (called *attributes*) enclosed Names and values are separated by an equal sign (`=`).
in curly brackets, where each value is an arbitrary expression Each value is an arbitrary expression terminated by a semicolon (`;`).
terminated by a semicolon. For example:
Attributes can appear in any order.
An attribute name may only occur once.
Example:
```nix ```nix
{ x = 123; {
x = 123;
text = "Hello"; text = "Hello";
y = f { bla = 456; }; y = f { bla = 456; };
} }
``` ```
This defines a set with attributes named `x`, `text`, `y`. The order of This defines a set with attributes named `x`, `text`, `y`.
the attributes is irrelevant. An attribute name may only occur once.
Attributes can be selected from a set using the `.` operator. For Attributes can be selected from a set using the `.` operator. For
instance, instance,

View file

@ -0,0 +1,31 @@
# Release 2.10 (2022-07-11)
* `nix repl` now takes installables on the command line, unifying the usage
with other commands that use `--file` and `--expr`. Primary breaking change
is for the common usage of `nix repl '<nixpkgs>'` which can be recovered with
`nix repl --file '<nixpkgs>'` or `nix repl --expr 'import <nixpkgs>{}'`.
This is currently guarded by the `repl-flake` experimental feature.
* A new function `builtins.traceVerbose` is available. It is similar
to `builtins.trace` if the `trace-verbose` setting is set to true,
and it is a no-op otherwise.
* `nix search` has a new flag `--exclude` to filter out packages.
* On Linux, if `/nix` doesn't exist and cannot be created and you're
not running as root, Nix will automatically use
`~/.local/share/nix/root` as a chroot store. This enables non-root
users to download the statically linked Nix binary and have it work
out of the box, e.g.
```
# ~/nix run nixpkgs#hello
warning: '/nix' does not exists, so Nix will use '/home/ubuntu/.local/share/nix/root' as a chroot store
Hello, world!
```
* `flake-registry.json` is now fetched from `channels.nixos.org`.
* Nix can now be built with LTO by passing `--enable-lto` to `configure`.
LTO is currently only supported when building with GCC.

View file

@ -1,4 +1,2 @@
# Release X.Y (202?-??-??) # Release X.Y (202?-??-??)
* Nix can now be built with LTO by passing `--enable-lto` to `configure`.
LTO is currently only supported when building with GCC.

View file

@ -4,6 +4,9 @@
, tag ? "latest" , tag ? "latest"
, channelName ? "nixpkgs" , channelName ? "nixpkgs"
, channelURL ? "https://nixos.org/channels/nixpkgs-unstable" , channelURL ? "https://nixos.org/channels/nixpkgs-unstable"
, extraPkgs ? []
, maxLayers ? 100
, nixConf ? {}
}: }:
let let
defaultPkgs = with pkgs; [ defaultPkgs = with pkgs; [
@ -23,7 +26,7 @@ let
iana-etc iana-etc
git git
openssh openssh
]; ] ++ extraPkgs;
users = { users = {
@ -121,12 +124,17 @@ let
(lib.attrValues (lib.mapAttrs groupToGroup groups)) (lib.attrValues (lib.mapAttrs groupToGroup groups))
); );
nixConf = { defaultNixConf = {
sandbox = "false"; sandbox = "false";
build-users-group = "nixbld"; build-users-group = "nixbld";
trusted-public-keys = "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="; trusted-public-keys = [ "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" ];
}; };
nixConfContents = (lib.concatStringsSep "\n" (lib.mapAttrsFlatten (n: v: "${n} = ${v}") nixConf)) + "\n";
nixConfContents = (lib.concatStringsSep "\n" (lib.mapAttrsFlatten (n: v:
let
vStr = if builtins.isList v then lib.concatStringsSep " " v else v;
in
"${n} = ${vStr}") (defaultNixConf // nixConf))) + "\n";
baseSystem = baseSystem =
let let
@ -229,7 +237,7 @@ let
in in
pkgs.dockerTools.buildLayeredImageWithNixDb { pkgs.dockerTools.buildLayeredImageWithNixDb {
inherit name tag; inherit name tag maxLayers;
contents = [ baseSystem ]; contents = [ baseSystem ];

View file

@ -18,11 +18,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1653988320, "lastModified": 1657693803,
"narHash": "sha256-ZaqFFsSDipZ6KVqriwM34T739+KLYJvNmCWzErjAg7c=", "narHash": "sha256-G++2CJ9u0E7NNTAi9n5G8TdDmGJXcIjkJ3NF8cetQB8=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "2fa57ed190fd6c7c746319444f34b5917666e5c1", "rev": "365e1b3a859281cf11b94f87231adeabbdd878a2",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -54,7 +54,7 @@
# we want most of the time and for backwards compatibility # we want most of the time and for backwards compatibility
forAllSystems (system: stdenvsPackages.${system} // stdenvsPackages.${system}.stdenvPackages); forAllSystems (system: stdenvsPackages.${system} // stdenvsPackages.${system}.stdenvPackages);
commonDeps = pkgs: with pkgs; rec { commonDeps = { pkgs, isStatic ? false }: with pkgs; rec {
# Use "busybox-sandbox-shell" if present, # Use "busybox-sandbox-shell" if present,
# if not (legacy) fallback and hope it's sufficient. # if not (legacy) fallback and hope it's sufficient.
sh = pkgs.busybox-sandbox-shell or (busybox.override { sh = pkgs.busybox-sandbox-shell or (busybox.override {
@ -85,6 +85,8 @@
lib.optionals stdenv.isLinux [ lib.optionals stdenv.isLinux [
"--with-boost=${boost}/lib" "--with-boost=${boost}/lib"
"--with-sandbox-shell=${sh}/bin/busybox" "--with-sandbox-shell=${sh}/bin/busybox"
]
++ lib.optionals (stdenv.isLinux && !(isStatic && stdenv.system == "aarch64-linux")) [
"LDFLAGS=-fuse-ld=gold" "LDFLAGS=-fuse-ld=gold"
]; ];
@ -106,7 +108,7 @@
++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)]; ++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)];
buildDeps = buildDeps =
[ curl [ (curl.override { patchNetrcRegression = true; })
bzip2 xz brotli editline bzip2 xz brotli editline
openssl sqlite openssl sqlite
libarchive libarchive
@ -170,7 +172,7 @@
echo "file installer $out/install" >> $out/nix-support/hydra-build-products echo "file installer $out/install" >> $out/nix-support/hydra-build-products
''; '';
testNixVersions = pkgs: client: daemon: with commonDeps pkgs; with pkgs.lib; pkgs.stdenv.mkDerivation { testNixVersions = pkgs: client: daemon: with commonDeps { inherit pkgs; }; with pkgs.lib; pkgs.stdenv.mkDerivation {
NIX_DAEMON_PACKAGE = daemon; NIX_DAEMON_PACKAGE = daemon;
NIX_CLIENT_PACKAGE = client; NIX_CLIENT_PACKAGE = client;
name = name =
@ -281,7 +283,7 @@
# Forward from the previous stage as we dont want it to pick the lowdown override # Forward from the previous stage as we dont want it to pick the lowdown override
nixUnstable = prev.nixUnstable; nixUnstable = prev.nixUnstable;
nix = with final; with commonDeps pkgs; currentStdenv.mkDerivation { nix = with final; with commonDeps { inherit pkgs; }; currentStdenv.mkDerivation {
name = "nix-super-${version}"; name = "nix-super-${version}";
inherit version; inherit version;
@ -361,7 +363,7 @@
buildInputs = buildInputs =
[ nix [ nix
curl (curl.override { patchNetrcRegression = true; })
bzip2 bzip2
xz xz
pkgs.perl pkgs.perl
@ -448,7 +450,7 @@
# Line coverage analysis. # Line coverage analysis.
coverage = coverage =
with nixpkgsFor.x86_64-linux; with nixpkgsFor.x86_64-linux;
with commonDeps pkgs; with commonDeps { inherit pkgs; };
releaseTools.coverageAnalysis { releaseTools.coverageAnalysis {
name = "nix-super-coverage-${version}"; name = "nix-super-coverage-${version}";
@ -559,7 +561,7 @@
} // (nixpkgs.lib.optionalAttrs (builtins.elem system linux64BitSystems) { } // (nixpkgs.lib.optionalAttrs (builtins.elem system linux64BitSystems) {
nix-static = let nix-static = let
nixpkgs = nixpkgsFor.${system}.pkgsStatic; nixpkgs = nixpkgsFor.${system}.pkgsStatic;
in with commonDeps nixpkgs; nixpkgs.stdenv.mkDerivation { in with commonDeps { pkgs = nixpkgs; isStatic = true; }; nixpkgs.stdenv.mkDerivation {
name = "nix-super-${version}"; name = "nix-super-${version}";
src = self; src = self;
@ -571,14 +573,24 @@
nativeBuildInputs = nativeBuildDeps; nativeBuildInputs = nativeBuildDeps;
buildInputs = buildDeps ++ propagatedDeps; buildInputs = buildDeps ++ propagatedDeps;
configureFlags = [ "--sysconfdir=/etc" ]; # Work around pkgsStatic disabling all tests.
# Remove in NixOS 22.11, see https://github.com/NixOS/nixpkgs/pull/140271.
preHook =
''
doCheck=1
doInstallCheck=1
'';
configureFlags =
configureFlags ++
[ "--sysconfdir=/etc"
"--enable-embedded-sandbox-shell"
];
enableParallelBuilding = true; enableParallelBuilding = true;
makeFlags = "profiledir=$(out)/etc/profile.d"; makeFlags = "profiledir=$(out)/etc/profile.d";
doCheck = true;
installFlags = "sysconfdir=$(out)/etc"; installFlags = "sysconfdir=$(out)/etc";
postInstall = '' postInstall = ''
@ -588,7 +600,6 @@
echo "file binary-dist $out/bin/nix" >> $out/nix-support/hydra-build-products echo "file binary-dist $out/bin/nix" >> $out/nix-support/hydra-build-products
''; '';
doInstallCheck = true;
installCheckFlags = "sysconfdir=$(out)/etc"; installCheckFlags = "sysconfdir=$(out)/etc";
stripAllList = ["bin"]; stripAllList = ["bin"];
@ -597,6 +608,7 @@
hardeningDisable = [ "pie" ]; hardeningDisable = [ "pie" ];
}; };
dockerImage = dockerImage =
let let
pkgs = nixpkgsFor.${system}; pkgs = nixpkgsFor.${system};
@ -620,7 +632,7 @@
inherit system crossSystem; inherit system crossSystem;
overlays = [ self.overlays.default ]; overlays = [ self.overlays.default ];
}; };
in with commonDeps nixpkgsCross; nixpkgsCross.stdenv.mkDerivation { in with commonDeps { pkgs = nixpkgsCross; }; nixpkgsCross.stdenv.mkDerivation {
name = "nix-super-${version}"; name = "nix-super-${version}";
src = self; src = self;
@ -663,7 +675,7 @@
devShells = forAllSystems (system: devShells = forAllSystems (system:
forAllStdenvs (stdenv: forAllStdenvs (stdenv:
with nixpkgsFor.${system}; with nixpkgsFor.${system};
with commonDeps pkgs; with commonDeps { inherit pkgs; };
nixpkgsFor.${system}.${stdenv}.mkDerivation { nixpkgsFor.${system}.${stdenv}.mkDerivation {
name = "nix-super"; name = "nix-super";

View file

@ -125,7 +125,7 @@ define build-library
$(1)_PATH := $$(_d)/$$($(1)_NAME).a $(1)_PATH := $$(_d)/$$($(1)_NAME).a
$$($(1)_PATH): $$($(1)_OBJS) | $$(_d)/ $$($(1)_PATH): $$($(1)_OBJS) | $$(_d)/
+$$(trace-ld) $(LD) -Ur -o $$(_d)/$$($(1)_NAME).o $$? +$$(trace-ld) $(LD) -Ur -o $$(_d)/$$($(1)_NAME).o $$^
$$(trace-ar) $(AR) crs $$@ $$(_d)/$$($(1)_NAME).o $$(trace-ar) $(AR) crs $$@ $$(_d)/$$($(1)_NAME).o
$(1)_LDFLAGS_USE += $$($(1)_PATH) $$($(1)_LDFLAGS) $(1)_LDFLAGS_USE += $$($(1)_PATH) $$($(1)_LDFLAGS)

View file

@ -148,7 +148,9 @@ if ! [ -w "$dest" ]; then
exit 1 exit 1
fi fi
mkdir -p "$dest/store" # The auto-chroot code in openFromNonUri() checks for the
# non-existence of /nix/var/nix, so we need to create it here.
mkdir -p "$dest/store" "$dest/var/nix"
printf "copying Nix to %s..." "${dest}/store" >&2 printf "copying Nix to %s..." "${dest}/store" >&2
# Insert a newline if no progress is shown. # Insert a newline if no progress is shown.

View file

@ -120,7 +120,7 @@ ref<EvalState> EvalCommand::getEvalState()
; ;
if (startReplOnEvalErrors) { if (startReplOnEvalErrors) {
evalState->debugRepl = &runRepl; evalState->debugRepl = &runRepl;
}; };
} }
return ref<EvalState>(evalState); return ref<EvalState>(evalState);

View file

@ -58,6 +58,7 @@ struct CopyCommand : virtual StoreCommand
struct EvalCommand : virtual StoreCommand, MixEvalArgs struct EvalCommand : virtual StoreCommand, MixEvalArgs
{ {
bool startReplOnEvalErrors = false; bool startReplOnEvalErrors = false;
bool ignoreExceptionsDuringTry = false;
EvalCommand(); EvalCommand();
@ -77,10 +78,16 @@ struct MixFlakeOptions : virtual Args, EvalCommand
{ {
flake::LockFlags lockFlags; flake::LockFlags lockFlags;
std::optional<std::string> needsFlakeInputCompletion = {};
MixFlakeOptions(); MixFlakeOptions();
virtual std::optional<FlakeRef> getFlakeRefForCompletion() virtual std::vector<std::string> getFlakesForCompletion()
{ return {}; } { return {}; }
void completeFlakeInput(std::string_view prefix);
void completionHook() override;
}; };
struct SourceExprCommand : virtual Args, MixFlakeOptions struct SourceExprCommand : virtual Args, MixFlakeOptions
@ -125,12 +132,13 @@ struct InstallablesCommand : virtual Args, SourceExprCommand
InstallablesCommand(); InstallablesCommand();
void prepare() override; void prepare() override;
Installables load();
virtual bool useDefaultInstallables() { return true; } virtual bool useDefaultInstallables() { return true; }
std::optional<FlakeRef> getFlakeRefForCompletion() override; std::vector<std::string> getFlakesForCompletion() override;
private: protected:
std::vector<std::string> _installables; std::vector<std::string> _installables;
}; };
@ -144,9 +152,9 @@ struct InstallableCommand : virtual Args, SourceExprCommand
void prepare() override; void prepare() override;
std::optional<FlakeRef> getFlakeRefForCompletion() override std::vector<std::string> getFlakesForCompletion() override
{ {
return parseFlakeRefWithFragment(_installable, absPath(".")).first; return {_installable};
} }
private: private:

View file

@ -90,8 +90,7 @@ MixFlakeOptions::MixFlakeOptions()
lockFlags.inputUpdates.insert(flake::parseInputPath(s)); lockFlags.inputUpdates.insert(flake::parseInputPath(s));
}}, }},
.completer = {[&](size_t, std::string_view prefix) { .completer = {[&](size_t, std::string_view prefix) {
if (auto flakeRef = getFlakeRefForCompletion()) needsFlakeInputCompletion = {std::string(prefix)};
completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
}} }}
}); });
@ -107,12 +106,10 @@ MixFlakeOptions::MixFlakeOptions()
parseFlakeRef(flakeRef, absPath("."), true)); parseFlakeRef(flakeRef, absPath("."), true));
}}, }},
.completer = {[&](size_t n, std::string_view prefix) { .completer = {[&](size_t n, std::string_view prefix) {
if (n == 0) { if (n == 0)
if (auto flakeRef = getFlakeRefForCompletion()) needsFlakeInputCompletion = {std::string(prefix)};
completeFlakeInputPath(getEvalState(), *flakeRef, prefix); else if (n == 1)
} else if (n == 1) {
completeFlakeRef(getEvalState()->store, prefix); completeFlakeRef(getEvalState()->store, prefix);
}
}} }}
}); });
@ -143,6 +140,24 @@ MixFlakeOptions::MixFlakeOptions()
}); });
} }
void MixFlakeOptions::completeFlakeInput(std::string_view prefix)
{
auto evalState = getEvalState();
for (auto & flakeRefS : getFlakesForCompletion()) {
auto flakeRef = parseFlakeRefWithFragment(expandTilde(flakeRefS), absPath(".")).first;
auto flake = flake::getFlake(*evalState, flakeRef, true);
for (auto & input : flake.inputs)
if (hasPrefix(input.first, prefix))
completions->add(input.first);
}
}
void MixFlakeOptions::completionHook()
{
if (auto & prefix = needsFlakeInputCompletion)
completeFlakeInput(*prefix);
}
SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode) SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode)
{ {
addFlag({ addFlag({
@ -1225,21 +1240,26 @@ InstallablesCommand::InstallablesCommand()
void InstallablesCommand::prepare() void InstallablesCommand::prepare()
{ {
installables = load();
}
Installables InstallablesCommand::load() {
Installables installables;
if (_installables.empty() && useDefaultInstallables()) if (_installables.empty() && useDefaultInstallables())
// FIXME: commands like "nix profile install" should not have a // FIXME: commands like "nix profile install" should not have a
// default, probably. // default, probably.
_installables.push_back("."); _installables.push_back(".");
installables = parseInstallables(getStore(), _installables); return parseInstallables(getStore(), _installables);
} }
std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion() std::vector<std::string> InstallablesCommand::getFlakesForCompletion()
{ {
if (_installables.empty()) { if (_installables.empty()) {
if (useDefaultInstallables()) if (useDefaultInstallables())
return parseFlakeRefWithFragment(".", absPath(".")).first; return {"."};
return {}; return {};
} }
return parseFlakeRefWithFragment(_installables.front(), absPath(".")).first; return _installables;
} }
InstallableCommand::InstallableCommand(bool supportReadOnlyMode) InstallableCommand::InstallableCommand(bool supportReadOnlyMode)

View file

@ -132,6 +132,8 @@ struct Installable
const std::vector<std::shared_ptr<Installable>> & installables); const std::vector<std::shared_ptr<Installable>> & installables);
}; };
typedef std::vector<std::shared_ptr<Installable>> Installables;
struct InstallableValue : Installable struct InstallableValue : Installable
{ {
ref<EvalState> state; ref<EvalState> state;

View file

@ -22,6 +22,7 @@ extern "C" {
#include "ansicolor.hh" #include "ansicolor.hh"
#include "shared.hh" #include "shared.hh"
#include "eval.hh" #include "eval.hh"
#include "eval-cache.hh"
#include "eval-inline.hh" #include "eval-inline.hh"
#include "attr-path.hh" #include "attr-path.hh"
#include "store-api.hh" #include "store-api.hh"
@ -54,6 +55,8 @@ struct NixRepl
size_t debugTraceIndex; size_t debugTraceIndex;
Strings loadedFiles; Strings loadedFiles;
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
std::function<AnnotatedValues()> getValues;
const static int envSize = 32768; const static int envSize = 32768;
std::shared_ptr<StaticEnv> staticEnv; std::shared_ptr<StaticEnv> staticEnv;
@ -63,13 +66,15 @@ struct NixRepl
const Path historyFile; const Path historyFile;
NixRepl(ref<EvalState> state); NixRepl(const Strings & searchPath, nix::ref<Store> store,ref<EvalState> state,
std::function<AnnotatedValues()> getValues);
~NixRepl(); ~NixRepl();
void mainLoop(const std::vector<std::string> & files); void mainLoop();
StringSet completePrefix(const std::string & prefix); StringSet completePrefix(const std::string & prefix);
bool getLine(std::string & input, const std::string & prompt); bool getLine(std::string & input, const std::string & prompt);
StorePath getDerivationPath(Value & v); StorePath getDerivationPath(Value & v);
bool processLine(std::string line); bool processLine(std::string line);
void loadFile(const Path & path); void loadFile(const Path & path);
void loadFlake(const std::string & flakeRef); void loadFlake(const std::string & flakeRef);
void initEnv(); void initEnv();
@ -96,9 +101,11 @@ std::string removeWhitespace(std::string s)
} }
NixRepl::NixRepl(ref<EvalState> state) NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
std::function<NixRepl::AnnotatedValues()> getValues)
: state(state) : state(state)
, debugTraceIndex(0) , debugTraceIndex(0)
, getValues(getValues)
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get())) , staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
, historyFile(getDataDir() + "/nix/repl-history") , historyFile(getDataDir() + "/nix/repl-history")
{ {
@ -111,23 +118,20 @@ NixRepl::~NixRepl()
write_history(historyFile.c_str()); write_history(historyFile.c_str());
} }
std::string runNix(Path program, const Strings & args, void runNix(Path program, const Strings & args,
const std::optional<std::string> & input = {}) const std::optional<std::string> & input = {})
{ {
auto subprocessEnv = getEnv(); auto subprocessEnv = getEnv();
subprocessEnv["NIX_CONFIG"] = globalConfig.toKeyValue(); subprocessEnv["NIX_CONFIG"] = globalConfig.toKeyValue();
auto res = runProgram(RunOptions { runProgram2(RunOptions {
.program = settings.nixBinDir+ "/" + program, .program = settings.nixBinDir+ "/" + program,
.args = args, .args = args,
.environment = subprocessEnv, .environment = subprocessEnv,
.input = input, .input = input,
}); });
if (!statusOk(res.first)) return;
throw ExecError(res.first, "program '%1%' %2%", program, statusToString(res.first));
return res.second;
} }
static NixRepl * curRepl; // ugly static NixRepl * curRepl; // ugly
@ -228,18 +232,12 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi
return out; return out;
} }
void NixRepl::mainLoop(const std::vector<std::string> & files) void NixRepl::mainLoop()
{ {
std::string error = ANSI_RED "error:" ANSI_NORMAL " "; std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
notice("Welcome to Nix Super " + nixVersion + ". Type :? for help.\n"); notice("Welcome to Nix Super " + nixVersion + ". Type :? for help.\n");
if (!files.empty()) {
for (auto & i : files)
loadedFiles.push_back(i);
}
loadFiles(); loadFiles();
if (!loadedFiles.empty()) notice("");
// Allow nix-repl specific settings in .inputrc // Allow nix-repl specific settings in .inputrc
rl_readline_name = "nix-repl"; rl_readline_name = "nix-repl";
@ -749,7 +747,6 @@ bool NixRepl::processLine(std::string line)
return true; return true;
} }
void NixRepl::loadFile(const Path & path) void NixRepl::loadFile(const Path & path)
{ {
loadedFiles.remove(path); loadedFiles.remove(path);
@ -809,13 +806,15 @@ void NixRepl::loadFiles()
Strings old = loadedFiles; Strings old = loadedFiles;
loadedFiles.clear(); loadedFiles.clear();
bool first = true;
for (auto & i : old) { for (auto & i : old) {
if (!first) notice("");
first = false;
notice("Loading '%1%'...", i); notice("Loading '%1%'...", i);
loadFile(i); loadFile(i);
} }
for (auto & [i, what] : getValues()) {
notice("Loading installable '%1%'...", what);
addAttrsToScope(*i);
}
} }
@ -1015,7 +1014,17 @@ void runRepl(
ref<EvalState>evalState, ref<EvalState>evalState,
const ValMap & extraEnv) const ValMap & extraEnv)
{ {
auto repl = std::make_unique<NixRepl>(evalState); auto getValues = [&]()->NixRepl::AnnotatedValues{
NixRepl::AnnotatedValues values;
return values;
};
const Strings & searchPath = {};
auto repl = std::make_unique<NixRepl>(
searchPath,
openStore(),
evalState,
getValues
);
repl->initEnv(); repl->initEnv();
@ -1023,20 +1032,35 @@ void runRepl(
for (auto & [name, value] : extraEnv) for (auto & [name, value] : extraEnv)
repl->addVarToScope(repl->state->symbols.create(name), *value); repl->addVarToScope(repl->state->symbols.create(name), *value);
repl->mainLoop({}); repl->mainLoop();
} }
struct CmdRepl : StoreCommand, MixEvalArgs struct CmdRepl : InstallablesCommand
{ {
std::vector<std::string> files; CmdRepl(){
evalSettings.pureEval = false;
CmdRepl() }
void prepare()
{ {
expectArgs({ if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
.label = "files", warn("future versions of Nix will require using `--file` to load a file");
.handler = {&files}, if (this->_installables.size() > 1)
.completer = completePath warn("more than one input file is not currently supported");
}); auto filePath = this->_installables[0].data();
file = std::optional(filePath);
_installables.front() = _installables.back();
_installables.pop_back();
}
installables = InstallablesCommand::load();
}
std::vector<std::string> files;
Strings getDefaultFlakeAttrPaths() override
{
return {""};
}
virtual bool useDefaultInstallables() override
{
return file.has_value() or expr.has_value();
} }
bool forceImpureByDefault() override bool forceImpureByDefault() override
@ -1058,12 +1082,37 @@ struct CmdRepl : StoreCommand, MixEvalArgs
void run(ref<Store> store) override void run(ref<Store> store) override
{ {
auto evalState = make_ref<EvalState>(searchPath, store); auto state = getEvalState();
auto getValues = [&]()->NixRepl::AnnotatedValues{
auto repl = std::make_unique<NixRepl>(evalState); auto installables = load();
NixRepl::AnnotatedValues values;
for (auto & installable: installables){
auto what = installable->what();
if (file){
auto [val, pos] = installable->toValue(*state);
auto what = installable->what();
state->forceValue(*val, pos);
auto autoArgs = getAutoArgs(*state);
auto valPost = state->allocValue();
state->autoCallFunction(*autoArgs, *val, *valPost);
state->forceValue(*valPost, pos);
values.push_back( {valPost, what });
} else {
auto [val, pos] = installable->toValue(*state);
values.push_back( {val, what} );
}
}
return values;
};
auto repl = std::make_unique<NixRepl>(
searchPath,
openStore(),
state,
getValues
);
repl->autoArgs = getAutoArgs(*repl->state); repl->autoArgs = getAutoArgs(*repl->state);
repl->initEnv(); repl->initEnv();
repl->mainLoop(files); repl->mainLoop();
} }
}; };

View file

@ -282,7 +282,7 @@ struct AttrDb
auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second])); auto queryAttribute(state->queryAttribute.use()(key.first)(symbols[key.second]));
if (!queryAttribute.next()) return {}; if (!queryAttribute.next()) return {};
auto rowId = (AttrType) queryAttribute.getInt(0); auto rowId = (AttrId) queryAttribute.getInt(0);
auto type = (AttrType) queryAttribute.getInt(1); auto type = (AttrType) queryAttribute.getInt(1);
switch (type) { switch (type) {
@ -486,7 +486,7 @@ std::shared_ptr<AttrCursor> AttrCursor::maybeGetAttr(Symbol name, bool forceErro
return nullptr; return nullptr;
else if (std::get_if<failed_t>(&attr->second)) { else if (std::get_if<failed_t>(&attr->second)) {
if (forceErrors) if (forceErrors)
debug("reevaluating failed cached attribute '%s'"); debug("reevaluating failed cached attribute '%s'", getAttrPathStr(name));
else else
throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(name)); throw CachedEvalError("cached failure of attribute '%s'", getAttrPathStr(name));
} else } else

View file

@ -464,9 +464,10 @@ EvalState::EvalState(
, emptyBindings(0) , emptyBindings(0)
, store(store) , store(store)
, buildStore(buildStore ? buildStore : store) , buildStore(buildStore ? buildStore : store)
, debugRepl(0) , debugRepl(nullptr)
, debugStop(false) , debugStop(false)
, debugQuit(false) , debugQuit(false)
, trylevel(0)
, regexCache(makeRegexCache()) , regexCache(makeRegexCache())
#if HAVE_BOEHMGC #if HAVE_BOEHMGC
, valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr)) , valueAllocCache(std::allocate_shared<void *>(traceable_allocator<void *>(), nullptr))
@ -832,7 +833,14 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr &
: nullptr; : nullptr;
if (error) if (error)
printError("%s\n\n" ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL, error->what()); {
printError("%s\n\n", error->what());
if (trylevel > 0 && error->info().level != lvlInfo)
printError("This exception occurred in a 'tryEval' call. Use " ANSI_GREEN "--ignore-try" ANSI_NORMAL " to skip these.\n");
printError(ANSI_BOLD "Starting REPL to allow you to inspect the current state of the evaluator.\n" ANSI_NORMAL);
}
auto se = getStaticEnv(expr); auto se = getStaticEnv(expr);
if (se) { if (se) {

View file

@ -130,6 +130,7 @@ public:
void (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv); void (* debugRepl)(ref<EvalState> es, const ValMap & extraEnv);
bool debugStop; bool debugStop;
bool debugQuit; bool debugQuit;
int trylevel;
std::list<DebugTrace> debugTraces; std::list<DebugTrace> debugTraces;
std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs; std::map<const Expr*, const std::shared_ptr<const StaticEnv>> exprEnvs;
const std::shared_ptr<const StaticEnv> getStaticEnv(const Expr & expr) const const std::shared_ptr<const StaticEnv> getStaticEnv(const Expr & expr) const
@ -646,6 +647,15 @@ struct EvalSettings : Config
Setting<bool> useEvalCache{this, true, "eval-cache", Setting<bool> useEvalCache{this, true, "eval-cache",
"Whether to use the flake evaluation cache."}; "Whether to use the flake evaluation cache."};
Setting<bool> ignoreExceptionsDuringTry{this, false, "ignore-try",
R"(
If set to true, ignore exceptions inside 'tryEval' calls when evaluating nix expressions in
debug mode (using the --debugger flag). By default the debugger will pause on all exceptions.
)"};
Setting<bool> traceVerbose{this, false, "trace-verbose",
"Whether `builtins.traceVerbose` should trace its first argument when evaluated."};
}; };
extern EvalSettings evalSettings; extern EvalSettings evalSettings;

View file

@ -384,6 +384,18 @@ LockedFlake lockFlake(
} }
} }
/* Check whether this input has overrides for a
non-existent input. */
for (auto [inputPath, inputOverride] : overrides) {
auto inputPath2(inputPath);
auto follow = inputPath2.back();
inputPath2.pop_back();
if (inputPath2 == inputPathPrefix && !flakeInputs.count(follow))
warn(
"input '%s' has an override for a non-existent input '%s'",
printInputPath(inputPathPrefix), follow);
}
/* Go over the flake inputs, resolve/fetch them if /* Go over the flake inputs, resolve/fetch them if
necessary (i.e. if they're new or the flakeref changed necessary (i.e. if they're new or the flakeref changed
from what's in the lock file). */ from what's in the lock file). */

View file

@ -522,6 +522,12 @@ path_start
$$ = new ExprPath(path); $$ = new ExprPath(path);
} }
| HPATH { | HPATH {
if (evalSettings.pureEval) {
throw Error(
"the path '%s' can not be resolved in pure mode",
std::string_view($1.p, $1.l)
);
}
Path path(getHome() + std::string($1.p + 1, $1.l - 1)); Path path(getHome() + std::string($1.p + 1, $1.l - 1));
$$ = new ExprPath(path); $$ = new ExprPath(path);
} }

View file

@ -851,6 +851,18 @@ static RegisterPrimOp primop_floor({
static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Value & v) static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{ {
auto attrs = state.buildBindings(2); auto attrs = state.buildBindings(2);
/* increment state.trylevel, and decrement it when this function returns. */
MaintainCount trylevel(state.trylevel);
void (* savedDebugRepl)(ref<EvalState> es, const ValMap & extraEnv) = nullptr;
if (state.debugRepl && evalSettings.ignoreExceptionsDuringTry)
{
/* to prevent starting the repl from exceptions withing a tryEval, null it. */
savedDebugRepl = state.debugRepl;
state.debugRepl = nullptr;
}
try { try {
state.forceValue(*args[0], pos); state.forceValue(*args[0], pos);
attrs.insert(state.sValue, args[0]); attrs.insert(state.sValue, args[0]);
@ -859,6 +871,11 @@ static void prim_tryEval(EvalState & state, const PosIdx pos, Value * * args, Va
attrs.alloc(state.sValue).mkBool(false); attrs.alloc(state.sValue).mkBool(false);
attrs.alloc("success").mkBool(false); attrs.alloc("success").mkBool(false);
} }
// restore the debugRepl pointer if we saved it earlier.
if (savedDebugRepl)
state.debugRepl = savedDebugRepl;
v.mkAttrs(attrs); v.mkAttrs(attrs);
} }
@ -970,6 +987,15 @@ static RegisterPrimOp primop_trace({
}); });
/* Takes two arguments and evaluates to the second one. Used as the
* builtins.traceVerbose implementation when --trace-verbose is not enabled
*/
static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Value & v)
{
state.forceValue(*args[1], pos);
v = *args[1];
}
/************************************************************* /*************************************************************
* Derivations * Derivations
*************************************************************/ *************************************************************/
@ -3929,6 +3955,18 @@ void EvalState::createBaseEnv()
addPrimOp("__exec", 1, prim_exec); addPrimOp("__exec", 1, prim_exec);
} }
addPrimOp({
.fun = evalSettings.traceVerbose ? prim_trace : prim_second,
.arity = 2,
.name = "__traceVerbose",
.args = { "e1", "e2" },
.doc = R"(
Evaluate *e1* and print its abstract syntax representation on standard
error if `--trace-verbose` is enabled. Then return *e2*. This function
is useful for debugging.
)",
});
/* Add a value containing the current Nix expression search path. */ /* Add a value containing the current Nix expression search path. */
mkList(v, searchPath.size()); mkList(v, searchPath.size());
int n = 0; int n = 0;

View file

@ -364,6 +364,10 @@ static RegisterPrimOp primop_fetchGit({
A Boolean parameter that specifies whether submodules should be A Boolean parameter that specifies whether submodules should be
checked out. Defaults to `false`. checked out. Defaults to `false`.
- shallow\
A Boolean parameter that specifies whether fetching a shallow clone
is allowed. Defaults to `false`.
- allRefs\ - allRefs\
Whether to fetch all refs of the repository. With this argument being Whether to fetch all refs of the repository. With this argument being
true, it's possible to load a `rev` from *any* `ref` (by default only true, it's possible to load a `rev` from *any* `ref` (by default only

View file

@ -540,22 +540,22 @@ namespace nix {
ASSERT_THAT(v, IsStringEq(output)); ASSERT_THAT(v, IsStringEq(output));
} }
#define CASE(input, output) (std::make_tuple(std::string_view("builtins.toString " #input), std::string_view(output))) #define CASE(input, output) (std::make_tuple(std::string_view("builtins.toString " input), std::string_view(output)))
INSTANTIATE_TEST_SUITE_P( INSTANTIATE_TEST_SUITE_P(
toString, toString,
ToStringPrimOpTest, ToStringPrimOpTest,
testing::Values( testing::Values(
CASE("foo", "foo"), CASE(R"("foo")", "foo"),
CASE(1, "1"), CASE(R"(1)", "1"),
CASE([1 2 3], "1 2 3"), CASE(R"([1 2 3])", "1 2 3"),
CASE(.123, "0.123000"), CASE(R"(.123)", "0.123000"),
CASE(true, "1"), CASE(R"(true)", "1"),
CASE(false, ""), CASE(R"(false)", ""),
CASE(null, ""), CASE(R"(null)", ""),
CASE({ v = "bar"; __toString = self: self.v; }, "bar"), CASE(R"({ v = "bar"; __toString = self: self.v; })", "bar"),
CASE({ v = "bar"; __toString = self: self.v; outPath = "foo"; }, "bar"), CASE(R"({ v = "bar"; __toString = self: self.v; outPath = "foo"; })", "bar"),
CASE({ outPath = "foo"; }, "foo"), CASE(R"({ outPath = "foo"; })", "foo"),
CASE(./test, "/test") CASE(R"(./test)", "/test")
) )
); );
#undef CASE #undef CASE

View file

@ -70,7 +70,7 @@ struct FetchSettings : public Config
Setting<bool> warnDirty{this, true, "warn-dirty", Setting<bool> warnDirty{this, true, "warn-dirty",
"Whether to warn about dirty Git/Mercurial trees."}; "Whether to warn about dirty Git/Mercurial trees."};
Setting<std::string> flakeRegistry{this, "https://github.com/NixOS/flake-registry/raw/master/flake-registry.json", "flake-registry", Setting<std::string> flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry",
"Path or URI of the global flake registry."}; "Path or URI of the global flake registry."};
Setting<bool> useRegistries{this, true, "use-registries", Setting<bool> useRegistries{this, true, "use-registries",

View file

@ -575,7 +575,7 @@ struct GitInputScheme : InputScheme
bool isShallow = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-parse", "--is-shallow-repository" })) == "true"; bool isShallow = chomp(runProgram("git", true, { "-C", repoDir, "--git-dir", gitDir, "rev-parse", "--is-shallow-repository" })) == "true";
if (isShallow && !shallow) if (isShallow && !shallow)
throw Error("'%s' is a shallow Git repository, but a non-shallow repository is needed", actualUrl); throw Error("'%s' is a shallow Git repository, but shallow repositories are only allowed when `shallow = true;` is specified.", actualUrl);
// FIXME: check whether rev is an ancestor of ref. // FIXME: check whether rev is an ancestor of ref.

View file

@ -7,6 +7,22 @@ HookInstance::HookInstance()
{ {
debug("starting build hook '%s'", settings.buildHook); debug("starting build hook '%s'", settings.buildHook);
auto buildHookArgs = tokenizeString<std::list<std::string>>(settings.buildHook.get());
if (buildHookArgs.empty())
throw Error("'build-hook' setting is empty");
auto buildHook = buildHookArgs.front();
buildHookArgs.pop_front();
Strings args;
for (auto & arg : buildHookArgs)
args.push_back(arg);
args.push_back(std::string(baseNameOf(settings.buildHook.get())));
args.push_back(std::to_string(verbosity));
/* Create a pipe to get the output of the child. */ /* Create a pipe to get the output of the child. */
fromHook.create(); fromHook.create();
@ -36,14 +52,9 @@ HookInstance::HookInstance()
if (dup2(builderOut.readSide.get(), 5) == -1) if (dup2(builderOut.readSide.get(), 5) == -1)
throw SysError("dupping builder's stdout/stderr"); throw SysError("dupping builder's stdout/stderr");
Strings args = { execv(buildHook.c_str(), stringsToCharPtrs(args).data());
std::string(baseNameOf(settings.buildHook.get())),
std::to_string(verbosity),
};
execv(settings.buildHook.get().c_str(), stringsToCharPtrs(args).data()); throw SysError("executing '%s'", buildHook);
throw SysError("executing '%s'", settings.buildHook);
}); });
pid.setSeparatePG(true); pid.setSeparatePG(true);

View file

@ -845,18 +845,43 @@ void LocalDerivationGoal::startBuilder()
/* Some distros patch Linux to not allow unprivileged /* Some distros patch Linux to not allow unprivileged
* user namespaces. If we get EPERM or EINVAL, try * user namespaces. If we get EPERM or EINVAL, try
* without CLONE_NEWUSER and see if that works. * without CLONE_NEWUSER and see if that works.
* Details: https://salsa.debian.org/kernel-team/linux/-/commit/d98e00eda6bea437e39b9e80444eee84a32438a6
*/ */
usingUserNamespace = false; usingUserNamespace = false;
flags &= ~CLONE_NEWUSER; flags &= ~CLONE_NEWUSER;
child = clone(childEntry, stack + stackSize, flags, this); child = clone(childEntry, stack + stackSize, flags, this);
} }
/* Otherwise exit with EPERM so we can handle this in the if (child == -1) {
parent. This is only done when sandbox-fallback is set switch(errno) {
to true (the default). */ case EPERM:
if (child == -1 && (errno == EPERM || errno == EINVAL) && settings.sandboxFallback) case EINVAL: {
_exit(1); int errno_ = errno;
if (child == -1) throw SysError("cloning builder process"); if (!userNamespacesEnabled && errno==EPERM)
notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/user/max_user_namespaces");
if (userNamespacesEnabled) {
Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
if (pathExists(procSysKernelUnprivilegedUsernsClone)
&& trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0") {
notice("user namespaces appear to be disabled; they are required for sandboxing; check /proc/sys/kernel/unprivileged_userns_clone");
}
}
Path procSelfNsUser = "/proc/self/ns/user";
if (!pathExists(procSelfNsUser))
notice("/proc/self/ns/user does not exist; your kernel was likely built without CONFIG_USER_NS=y, which is required for sandboxing");
/* Otherwise exit with EPERM so we can handle this in the
parent. This is only done when sandbox-fallback is set
to true (the default). */
if (settings.sandboxFallback)
_exit(1);
/* Mention sandbox-fallback in the error message so the user
knows that having it disabled contributed to the
unrecoverability of this failure */
throw SysError(errno_, "creating sandboxed builder process using clone(), without sandbox-fallback");
}
default:
throw SysError("creating sandboxed builder process using clone()");
}
}
writeFull(builderOut.writeSide.get(), writeFull(builderOut.writeSide.get(),
fmt("%d %d\n", usingUserNamespace, child)); fmt("%d %d\n", usingUserNamespace, child));
_exit(0); _exit(0);
@ -1717,7 +1742,19 @@ void LocalDerivationGoal::runChild()
for (auto & i : dirsInChroot) { for (auto & i : dirsInChroot) {
if (i.second.source == "/proc") continue; // backwards compatibility if (i.second.source == "/proc") continue; // backwards compatibility
doBind(i.second.source, chrootRootDir + i.first, i.second.optional);
#if HAVE_EMBEDDED_SANDBOX_SHELL
if (i.second.source == "__embedded_sandbox_shell__") {
static unsigned char sh[] = {
#include "embedded-sandbox-shell.gen.hh"
};
auto dst = chrootRootDir + i.first;
createDirs(dirOf(dst));
writeFile(dst, std::string_view((const char *) sh, sizeof(sh)));
chmod_(dst, 0555);
} else
#endif
doBind(i.second.source, chrootRootDir + i.first, i.second.optional);
} }
/* Bind a new instance of procfs on /proc. */ /* Bind a new instance of procfs on /proc. */

View file

@ -154,7 +154,7 @@ void PathSubstitutionGoal::tryNext()
only after we've downloaded the path. */ only after we've downloaded the path. */
if (!sub->isTrusted && worker.store.pathInfoIsUntrusted(*info)) if (!sub->isTrusted && worker.store.pathInfoIsUntrusted(*info))
{ {
warn("the substitute for '%s' from '%s' is not signed by any of the keys in 'trusted-public-keys'", warn("ignoring substitute for '%s' from '%s', as it's not signed by any of the keys in 'trusted-public-keys'",
worker.store.printStorePath(storePath), sub->getUri()); worker.store.printStorePath(storePath), sub->getUri());
tryNext(); tryNext();
return; return;

View file

@ -36,7 +36,6 @@ Settings::Settings()
, nixStateDir(canonPath(getEnv("NIX_STATE_DIR").value_or(NIX_STATE_DIR))) , nixStateDir(canonPath(getEnv("NIX_STATE_DIR").value_or(NIX_STATE_DIR)))
, nixConfDir(canonPath(getEnv("NIX_CONF_DIR").value_or(NIX_CONF_DIR))) , nixConfDir(canonPath(getEnv("NIX_CONF_DIR").value_or(NIX_CONF_DIR)))
, nixUserConfFiles(getUserConfigFiles()) , nixUserConfFiles(getUserConfigFiles())
, nixLibexecDir(canonPath(getEnv("NIX_LIBEXEC_DIR").value_or(NIX_LIBEXEC_DIR)))
, nixBinDir(canonPath(getEnv("NIX_BIN_DIR").value_or(NIX_BIN_DIR))) , nixBinDir(canonPath(getEnv("NIX_BIN_DIR").value_or(NIX_BIN_DIR)))
, nixManDir(canonPath(NIX_MAN_DIR)) , nixManDir(canonPath(NIX_MAN_DIR))
, nixDaemonSocketFile(canonPath(getEnv("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH))) , nixDaemonSocketFile(canonPath(getEnv("NIX_DAEMON_SOCKET_PATH").value_or(nixStateDir + DEFAULT_SOCKET_PATH)))
@ -67,12 +66,13 @@ Settings::Settings()
sandboxPaths = tokenizeString<StringSet>("/bin/sh=" SANDBOX_SHELL); sandboxPaths = tokenizeString<StringSet>("/bin/sh=" SANDBOX_SHELL);
#endif #endif
/* chroot-like behavior from Apple's sandbox */
/* chroot-like behavior from Apple's sandbox */
#if __APPLE__ #if __APPLE__
sandboxPaths = tokenizeString<StringSet>("/System/Library/Frameworks /System/Library/PrivateFrameworks /bin/sh /bin/bash /private/tmp /private/var/tmp /usr/lib"); sandboxPaths = tokenizeString<StringSet>("/System/Library/Frameworks /System/Library/PrivateFrameworks /bin/sh /bin/bash /private/tmp /private/var/tmp /usr/lib");
allowedImpureHostPrefixes = tokenizeString<StringSet>("/System/Library /usr/lib /dev /bin/sh"); allowedImpureHostPrefixes = tokenizeString<StringSet>("/System/Library /usr/lib /dev /bin/sh");
#endif #endif
buildHook = getSelfExe().value_or("nix") + " __build-remote";
} }
void loadConfFile() void loadConfFile()
@ -114,7 +114,13 @@ std::vector<Path> getUserConfigFiles()
unsigned int Settings::getDefaultCores() unsigned int Settings::getDefaultCores()
{ {
return std::max(1U, std::thread::hardware_concurrency()); const unsigned int concurrency = std::max(1U, std::thread::hardware_concurrency());
const unsigned int maxCPU = getMaxCPU();
if (maxCPU > 0)
return maxCPU;
else
return concurrency;
} }
StringSet Settings::getDefaultSystemFeatures() StringSet Settings::getDefaultSystemFeatures()

View file

@ -79,9 +79,6 @@ public:
/* A list of user configuration files to load. */ /* A list of user configuration files to load. */
std::vector<Path> nixUserConfFiles; std::vector<Path> nixUserConfFiles;
/* The directory where internal helper programs are stored. */
Path nixLibexecDir;
/* The directory where the main programs are stored. */ /* The directory where the main programs are stored. */
Path nixBinDir; Path nixBinDir;
@ -195,7 +192,7 @@ public:
)", )",
{"build-timeout"}}; {"build-timeout"}};
PathSetting buildHook{this, true, nixLibexecDir + "/nix/build-remote", "build-hook", PathSetting buildHook{this, true, "", "build-hook",
"The path of the helper program that executes builds to remote machines."}; "The path of the helper program that executes builds to remote machines."};
Setting<std::string> builders{ Setting<std::string> builders{

View file

@ -39,14 +39,23 @@ libstore_CXXFLAGS += \
-DNIX_STATE_DIR=\"$(localstatedir)/nix\" \ -DNIX_STATE_DIR=\"$(localstatedir)/nix\" \
-DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \ -DNIX_LOG_DIR=\"$(localstatedir)/log/nix\" \
-DNIX_CONF_DIR=\"$(sysconfdir)/nix\" \ -DNIX_CONF_DIR=\"$(sysconfdir)/nix\" \
-DNIX_LIBEXEC_DIR=\"$(libexecdir)\" \
-DNIX_BIN_DIR=\"$(bindir)\" \ -DNIX_BIN_DIR=\"$(bindir)\" \
-DNIX_MAN_DIR=\"$(mandir)\" \ -DNIX_MAN_DIR=\"$(mandir)\" \
-DLSOF=\"$(lsof)\" -DLSOF=\"$(lsof)\"
ifeq ($(embedded_sandbox_shell),yes)
libstore_CXXFLAGS += -DSANDBOX_SHELL=\"__embedded_sandbox_shell__\"
$(d)/build/local-derivation-goal.cc: $(d)/embedded-sandbox-shell.gen.hh
$(d)/embedded-sandbox-shell.gen.hh: $(sandbox_shell)
$(trace-gen) hexdump -v -e '1/1 "0x%x," "\n"' < $< > $@.tmp
@mv $@.tmp $@
else
ifneq ($(sandbox_shell),) ifneq ($(sandbox_shell),)
libstore_CXXFLAGS += -DSANDBOX_SHELL="\"$(sandbox_shell)\"" libstore_CXXFLAGS += -DSANDBOX_SHELL="\"$(sandbox_shell)\""
endif endif
endif
$(d)/local-store.cc: $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh $(d)/local-store.cc: $(d)/schema.sql.gen.hh $(d)/ca-specific-schema.sql.gen.hh

View file

@ -62,6 +62,9 @@ public:
/* How often to purge expired entries from the cache. */ /* How often to purge expired entries from the cache. */
const int purgeInterval = 24 * 3600; const int purgeInterval = 24 * 3600;
/* How long to cache binary cache info (i.e. /nix-cache-info) */
const int cacheInfoTtl = 7 * 24 * 3600;
struct Cache struct Cache
{ {
int id; int id;
@ -98,7 +101,7 @@ public:
"insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?)"); "insert or replace into BinaryCaches(url, timestamp, storeDir, wantMassQuery, priority) values (?, ?, ?, ?, ?)");
state->queryCache.create(state->db, state->queryCache.create(state->db,
"select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ?"); "select id, storeDir, wantMassQuery, priority from BinaryCaches where url = ? and timestamp > ?");
state->insertNAR.create(state->db, state->insertNAR.create(state->db,
"insert or replace into NARs(cache, hashPart, namePart, url, compression, fileHash, fileSize, narHash, " "insert or replace into NARs(cache, hashPart, namePart, url, compression, fileHash, fileSize, narHash, "
@ -183,7 +186,7 @@ public:
auto i = state->caches.find(uri); auto i = state->caches.find(uri);
if (i == state->caches.end()) { if (i == state->caches.end()) {
auto queryCache(state->queryCache.use()(uri)); auto queryCache(state->queryCache.use()(uri)(time(0) - cacheInfoTtl));
if (!queryCache.next()) if (!queryCache.next())
return std::nullopt; return std::nullopt;
state->caches.emplace(uri, state->caches.emplace(uri,

View file

@ -69,8 +69,6 @@ NarInfo::NarInfo(const Store & store, const std::string & s, const std::string &
if (value != "unknown-deriver") if (value != "unknown-deriver")
deriver = StorePath(value); deriver = StorePath(value);
} }
else if (name == "System")
system = value;
else if (name == "Sig") else if (name == "Sig")
sigs.insert(value); sigs.insert(value);
else if (name == "CA") { else if (name == "CA") {
@ -106,9 +104,6 @@ std::string NarInfo::to_string(const Store & store) const
if (deriver) if (deriver)
res += "Deriver: " + std::string(deriver->to_string()) + "\n"; res += "Deriver: " + std::string(deriver->to_string()) + "\n";
if (!system.empty())
res += "System: " + system + "\n";
for (auto sig : sigs) for (auto sig : sigs)
res += "Sig: " + sig + "\n"; res += "Sig: " + sig + "\n";

View file

@ -14,7 +14,6 @@ struct NarInfo : ValidPathInfo
std::string compression; std::string compression;
std::optional<Hash> fileHash; std::optional<Hash> fileHash;
uint64_t fileSize = 0; uint64_t fileSize = 0;
std::string system;
NarInfo() = delete; NarInfo() = delete;
NarInfo(StorePath && path, Hash narHash) : ValidPathInfo(std::move(path), narHash) { } NarInfo(StorePath && path, Hash narHash) : ValidPathInfo(std::move(path), narHash) { }

View file

@ -98,7 +98,9 @@
(allow file* (allow file*
(literal "/private/var/select/sh")) (literal "/private/var/select/sh"))
; Allow Rosetta 2 to run x86_64 binaries on aarch64-darwin. ; Allow Rosetta 2 to run x86_64 binaries on aarch64-darwin (and vice versa).
(allow file-read* (allow file-read*
(subpath "/Library/Apple/usr/libexec/oah") (subpath "/Library/Apple/usr/libexec/oah")
(subpath "/System/Library/Apple/usr/libexec/oah")) (subpath "/System/Library/Apple/usr/libexec/oah")
(subpath "/System/Library/LaunchDaemons/com.apple.oahd.plist")
(subpath "/Library/Apple/System/Library/LaunchDaemons/com.apple.oahd.plist"))

View file

@ -1302,7 +1302,8 @@ std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri_
return {uri, params}; return {uri, params};
} }
static bool isNonUriPath(const std::string & spec) { static bool isNonUriPath(const std::string & spec)
{
return return
// is not a URL // is not a URL
spec.find("://") == std::string::npos spec.find("://") == std::string::npos
@ -1319,6 +1320,31 @@ std::shared_ptr<Store> openFromNonUri(const std::string & uri, const Store::Para
return std::make_shared<LocalStore>(params); return std::make_shared<LocalStore>(params);
else if (pathExists(settings.nixDaemonSocketFile)) else if (pathExists(settings.nixDaemonSocketFile))
return std::make_shared<UDSRemoteStore>(params); return std::make_shared<UDSRemoteStore>(params);
#if __linux__
else if (!pathExists(stateDir)
&& params.empty()
&& getuid() != 0
&& !getEnv("NIX_STORE_DIR").has_value()
&& !getEnv("NIX_STATE_DIR").has_value())
{
/* If /nix doesn't exist, there is no daemon socket, and
we're not root, then automatically set up a chroot
store in ~/.local/share/nix/root. */
auto chrootStore = getDataDir() + "/nix/root";
if (!pathExists(chrootStore)) {
try {
createDirs(chrootStore);
} catch (Error & e) {
return std::make_shared<LocalStore>(params);
}
warn("'/nix' does not exist, so Nix will use '%s' as a chroot store", chrootStore);
} else
debug("'/nix' does not exist, so Nix will use '%s' as a chroot store", chrootStore);
Store::Params params2;
params2["root"] = chrootStore;
return std::make_shared<LocalStore>(params2);
}
#endif
else else
return std::make_shared<LocalStore>(params); return std::make_shared<LocalStore>(params);
} else if (uri == "daemon") { } else if (uri == "daemon") {

View file

@ -124,7 +124,7 @@ bool Args::processFlag(Strings::iterator & pos, Strings::iterator end)
bool anyCompleted = false; bool anyCompleted = false;
for (size_t n = 0 ; n < flag.handler.arity; ++n) { for (size_t n = 0 ; n < flag.handler.arity; ++n) {
if (pos == end) { if (pos == end) {
if (flag.handler.arity == ArityAny) break; if (flag.handler.arity == ArityAny || anyCompleted) break;
throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity); throw UsageError("flag '%s' requires %d argument(s)", name, flag.handler.arity);
} }
if (auto prefix = needsCompletion(*pos)) { if (auto prefix = needsCompletion(*pos)) {
@ -362,6 +362,14 @@ bool MultiCommand::processArgs(const Strings & args, bool finish)
return Args::processArgs(args, finish); return Args::processArgs(args, finish);
} }
void MultiCommand::completionHook()
{
if (command)
return command->second->completionHook();
else
return Args::completionHook();
}
nlohmann::json MultiCommand::toJSON() nlohmann::json MultiCommand::toJSON()
{ {
auto cmds = nlohmann::json::object(); auto cmds = nlohmann::json::object();

View file

@ -148,6 +148,11 @@ protected:
argument (if any) have been processed. */ argument (if any) have been processed. */
virtual void initialFlagsProcessed() {} virtual void initialFlagsProcessed() {}
/* Called after the command line has been processed if we need to generate
completions. Useful for commands that need to know the whole command line
in order to know what completions to generate. */
virtual void completionHook() { }
public: public:
void addFlag(Flag && flag); void addFlag(Flag && flag);
@ -223,6 +228,8 @@ public:
bool processArgs(const Strings & args, bool finish) override; bool processArgs(const Strings & args, bool finish) override;
void completionHook() override;
nlohmann::json toJSON() override; nlohmann::json toJSON() override;
}; };

View file

@ -204,13 +204,19 @@ public:
int errNo; int errNo;
template<typename... Args> template<typename... Args>
SysError(const Args & ... args) SysError(int errNo_, const Args & ... args)
: Error("") : Error("")
{ {
errNo = errno; errNo = errNo_;
auto hf = hintfmt(args...); auto hf = hintfmt(args...);
err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo)); err.msg = hintfmt("%1%: %2%", normaltxt(hf.str()), strerror(errNo));
} }
template<typename... Args>
SysError(const Args & ... args)
: SysError(errno, args ...)
{
}
}; };
} }

View file

@ -13,6 +13,7 @@ std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
{ Xp::RecursiveNix, "recursive-nix" }, { Xp::RecursiveNix, "recursive-nix" },
{ Xp::NoUrlLiterals, "no-url-literals" }, { Xp::NoUrlLiterals, "no-url-literals" },
{ Xp::FetchClosure, "fetch-closure" }, { Xp::FetchClosure, "fetch-closure" },
{ Xp::ReplFlake, "repl-flake" },
}; };
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name) const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)

View file

@ -22,6 +22,7 @@ enum struct ExperimentalFeature
RecursiveNix, RecursiveNix,
NoUrlLiterals, NoUrlLiterals,
FetchClosure, FetchClosure,
ReplFlake,
}; };
/** /**

View file

@ -29,11 +29,15 @@
#ifdef __APPLE__ #ifdef __APPLE__
#include <sys/syscall.h> #include <sys/syscall.h>
#include <mach-o/dyld.h>
#endif #endif
#ifdef __linux__ #ifdef __linux__
#include <sys/prctl.h> #include <sys/prctl.h>
#include <sys/resource.h> #include <sys/resource.h>
#include <mntent.h>
#include <cmath>
#endif #endif
@ -574,6 +578,20 @@ Path getHome()
static Path homeDir = []() static Path homeDir = []()
{ {
auto homeDir = getEnv("HOME"); auto homeDir = getEnv("HOME");
if (homeDir) {
// Only use $HOME if doesn't exist or is owned by the current user.
struct stat st;
int result = stat(homeDir->c_str(), &st);
if (result != 0) {
if (errno != ENOENT) {
warn("couldn't stat $HOME ('%s') for reason other than not existing ('%d'), falling back to the one defined in the 'passwd' file", *homeDir, errno);
homeDir.reset();
}
} else if (st.st_uid != geteuid()) {
warn("$HOME ('%s') is not owned by you, falling back to the one defined in the 'passwd' file", *homeDir);
homeDir.reset();
}
}
if (!homeDir) { if (!homeDir) {
std::vector<char> buf(16384); std::vector<char> buf(16384);
struct passwd pwbuf; struct passwd pwbuf;
@ -619,6 +637,27 @@ Path getDataDir()
} }
std::optional<Path> getSelfExe()
{
static auto cached = []() -> std::optional<Path>
{
#if __linux__
return readLink("/proc/self/exe");
#elif __APPLE__
char buf[1024];
uint32_t size = sizeof(buf);
if (_NSGetExecutablePath(buf, &size) == 0)
return buf;
else
return std::nullopt;
#else
return std::nullopt;
#endif
}();
return cached;
}
Paths createDirs(const Path & path) Paths createDirs(const Path & path)
{ {
Paths created; Paths created;
@ -752,7 +791,55 @@ void drainFD(int fd, Sink & sink, bool block)
} }
} }
//////////////////////////////////////////////////////////////////////
unsigned int getMaxCPU()
{
#if __linux__
try {
FILE *fp = fopen("/proc/mounts", "r");
if (!fp)
return 0;
Strings cgPathParts;
struct mntent *ent;
while ((ent = getmntent(fp))) {
std::string mountType, mountPath;
mountType = ent->mnt_type;
mountPath = ent->mnt_dir;
if (mountType == "cgroup2") {
cgPathParts.push_back(mountPath);
break;
}
}
fclose(fp);
if (cgPathParts.size() > 0 && pathExists("/proc/self/cgroup")) {
std::string currentCgroup = readFile("/proc/self/cgroup");
Strings cgValues = tokenizeString<Strings>(currentCgroup, ":");
cgPathParts.push_back(trim(cgValues.back(), "\n"));
cgPathParts.push_back("cpu.max");
std::string fullCgPath = canonPath(concatStringsSep("/", cgPathParts));
if (pathExists(fullCgPath)) {
std::string cpuMax = readFile(fullCgPath);
std::vector<std::string> cpuMaxParts = tokenizeString<std::vector<std::string>>(cpuMax, " ");
std::string quota = cpuMaxParts[0];
std::string period = trim(cpuMaxParts[1], "\n");
if (quota != "max")
return std::ceil(std::stoi(quota) / std::stof(period));
}
}
} catch (Error &) { ignoreException(); }
#endif
return 0;
}
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////

View file

@ -149,10 +149,14 @@ std::vector<Path> getConfigDirs();
/* Return $XDG_DATA_HOME or $HOME/.local/share. */ /* Return $XDG_DATA_HOME or $HOME/.local/share. */
Path getDataDir(); Path getDataDir();
/* Return the path of the current executable. */
std::optional<Path> getSelfExe();
/* Create a directory and all its parents, if necessary. Returns the /* Create a directory and all its parents, if necessary. Returns the
list of created directories, in order of creation. */ list of created directories, in order of creation. */
Paths createDirs(const Path & path); Paths createDirs(const Path & path);
inline Paths createDirs(PathView path) { inline Paths createDirs(PathView path)
{
return createDirs(Path(path)); return createDirs(Path(path));
} }
@ -178,6 +182,9 @@ std::string drainFD(int fd, bool block = true, const size_t reserveSize=0);
void drainFD(int fd, Sink & sink, bool block = true); void drainFD(int fd, Sink & sink, bool block = true);
/* If cgroups are active, attempt to calculate the number of CPUs available.
If cgroups are unavailable or if cpu.max is set to "max", return 0. */
unsigned int getMaxCPU();
/* Automatic cleanup of resources. */ /* Automatic cleanup of resources. */

View file

@ -257,11 +257,12 @@ static void main_nix_build(int argc, char * * argv)
auto autoArgs = myArgs.getAutoArgs(*state); auto autoArgs = myArgs.getAutoArgs(*state);
auto autoArgsWithInNixShell = autoArgs;
if (runEnv) { if (runEnv) {
auto newArgs = state->buildBindings(autoArgs->size() + 1); auto newArgs = state->buildBindings(autoArgsWithInNixShell->size() + 1);
newArgs.alloc("inNixShell").mkBool(true); newArgs.alloc("inNixShell").mkBool(true);
for (auto & i : *autoArgs) newArgs.insert(i); for (auto & i : *autoArgs) newArgs.insert(i);
autoArgs = newArgs.finish(); autoArgsWithInNixShell = newArgs.finish();
} }
if (packages) { if (packages) {
@ -316,10 +317,39 @@ static void main_nix_build(int argc, char * * argv)
Value vRoot; Value vRoot;
state->eval(e, vRoot); state->eval(e, vRoot);
std::function<bool(const Value & v)> takesNixShellAttr;
takesNixShellAttr = [&](const Value & v) {
if (!runEnv) {
return false;
}
bool add = false;
if (v.type() == nFunction && v.lambda.fun->hasFormals()) {
for (auto & i : v.lambda.fun->formals->formals) {
if (state->symbols[i.name] == "inNixShell") {
add = true;
break;
}
}
}
return add;
};
for (auto & i : attrPaths) { for (auto & i : attrPaths) {
Value & v(*findAlongAttrPath(*state, i, *autoArgs, vRoot).first); Value & v(*findAlongAttrPath(
*state,
i,
takesNixShellAttr(vRoot) ? *autoArgsWithInNixShell : *autoArgs,
vRoot
).first);
state->forceValue(v, [&]() { return v.determinePos(noPos); }); state->forceValue(v, [&]() { return v.determinePos(noPos); });
getDerivations(*state, v, "", *autoArgs, drvs, false); getDerivations(
*state,
v,
"",
takesNixShellAttr(v) ? *autoArgsWithInNixShell : *autoArgs,
drvs,
false
);
} }
} }

View file

@ -276,15 +276,27 @@ struct Common : InstallableCommand, MixProfile
const BuildEnvironment & buildEnvironment, const BuildEnvironment & buildEnvironment,
const Path & outputsDir = absPath(".") + "/outputs") const Path & outputsDir = absPath(".") + "/outputs")
{ {
// A list of colon-separated environment variables that should be
// prepended to, rather than overwritten, in order to keep the shell usable.
// Please keep this list minimal in order to avoid impurities.
static const char * const savedVars[] = {
"PATH", // for commands
"XDG_DATA_DIRS", // for loadable completion
};
std::ostringstream out; std::ostringstream out;
out << "unset shellHook\n"; out << "unset shellHook\n";
out << "nix_saved_PATH=\"$PATH\"\n"; for (auto & var : savedVars) {
out << fmt("%s=${%s:-}\n", var, var);
out << fmt("nix_saved_%s=\"$%s\"\n", var, var);
}
buildEnvironment.toBash(out, ignoreVars); buildEnvironment.toBash(out, ignoreVars);
out << "PATH=\"$PATH:$nix_saved_PATH\"\n"; for (auto & var : savedVars)
out << fmt("%s=\"$%s:$nix_saved_%s\"\n", var, var, var);
out << "export NIX_BUILD_TOP=\"$(mktemp -d -t nix-shell.XXXXXX)\"\n"; out << "export NIX_BUILD_TOP=\"$(mktemp -d -t nix-shell.XXXXXX)\"\n";
for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"}) for (auto & i : {"TMP", "TMPDIR", "TEMP", "TEMPDIR"})

View file

@ -50,9 +50,9 @@ public:
return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags); return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags);
} }
std::optional<FlakeRef> getFlakeRefForCompletion() override std::vector<std::string> getFlakesForCompletion() override
{ {
return getFlakeRef(); return {flakeUrl};
} }
}; };
@ -738,7 +738,8 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
"If you've set '%s' to a string, try using a path instead.", "If you've set '%s' to a string, try using a path instead.",
templateDir, templateDirAttr->getAttrPathStr()); templateDir, templateDirAttr->getAttrPathStr());
std::vector<Path> files; std::vector<Path> changedFiles;
std::vector<Path> conflictedFiles;
std::function<void(const Path & from, const Path & to)> copyDir; std::function<void(const Path & from, const Path & to)> copyDir;
copyDir = [&](const Path & from, const Path & to) copyDir = [&](const Path & from, const Path & to)
@ -755,31 +756,41 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
auto contents = readFile(from2); auto contents = readFile(from2);
if (pathExists(to2)) { if (pathExists(to2)) {
auto contents2 = readFile(to2); auto contents2 = readFile(to2);
if (contents != contents2) if (contents != contents2) {
throw Error("refusing to overwrite existing file '%s'", to2); printError("refusing to overwrite existing file '%s'\n please merge it manually with '%s'", to2, from2);
conflictedFiles.push_back(to2);
} else {
notice("skipping identical file: %s", from2);
}
continue;
} else } else
writeFile(to2, contents); writeFile(to2, contents);
} }
else if (S_ISLNK(st.st_mode)) { else if (S_ISLNK(st.st_mode)) {
auto target = readLink(from2); auto target = readLink(from2);
if (pathExists(to2)) { if (pathExists(to2)) {
if (readLink(to2) != target) if (readLink(to2) != target) {
throw Error("refusing to overwrite existing symlink '%s'", to2); printError("refusing to overwrite existing file '%s'\n please merge it manually with '%s'", to2, from2);
conflictedFiles.push_back(to2);
} else {
notice("skipping identical file: %s", from2);
}
continue;
} else } else
createSymlink(target, to2); createSymlink(target, to2);
} }
else else
throw Error("file '%s' has unsupported type", from2); throw Error("file '%s' has unsupported type", from2);
files.push_back(to2); changedFiles.push_back(to2);
notice("wrote: %s", to2); notice("wrote: %s", to2);
} }
}; };
copyDir(templateDir, flakeDir); copyDir(templateDir, flakeDir);
if (pathExists(flakeDir + "/.git")) { if (!changedFiles.empty() && pathExists(flakeDir + "/.git")) {
Strings args = { "-C", flakeDir, "add", "--intent-to-add", "--force", "--" }; Strings args = { "-C", flakeDir, "add", "--intent-to-add", "--force", "--" };
for (auto & s : files) args.push_back(s); for (auto & s : changedFiles) args.push_back(s);
runProgram("git", true, args); runProgram("git", true, args);
} }
auto welcomeText = cursor->maybeGetAttr("welcomeText"); auto welcomeText = cursor->maybeGetAttr("welcomeText");
@ -787,6 +798,9 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand
notice("\n"); notice("\n");
notice(renderMarkdownToTerminal(welcomeText->getString())); notice(renderMarkdownToTerminal(welcomeText->getString()));
} }
if (!conflictedFiles.empty())
throw Error("Encountered %d conflicts - see above", conflictedFiles.size());
} }
}; };

View file

@ -266,6 +266,11 @@ void mainWrapped(int argc, char * * argv)
programPath = argv[0]; programPath = argv[0];
auto programName = std::string(baseNameOf(programPath)); auto programName = std::string(baseNameOf(programPath));
if (argc > 0 && std::string_view(argv[0]) == "__build-remote") {
programName = "build-remote";
argv++; argc--;
}
{ {
auto legacy = (*RegisterLegacyCommand::commands)[programName]; auto legacy = (*RegisterLegacyCommand::commands)[programName];
if (legacy) return legacy(argc, argv); if (legacy) return legacy(argc, argv);
@ -342,7 +347,10 @@ void mainWrapped(int argc, char * * argv)
if (!completions) throw; if (!completions) throw;
} }
if (completions) return; if (completions) {
args.completionHook();
return;
}
if (args.showVersion) { if (args.showVersion) {
printVersion(programName); printVersion(programName);

View file

@ -29,7 +29,7 @@ highest precedence:
can be specified using the NixOS option `nix.registry`. can be specified using the NixOS option `nix.registry`.
* The user registry `~/.config/nix/registry.json`. This registry can * The user registry `~/.config/nix/registry.json`. This registry can
be modified by commands such as `nix flake pin`. be modified by commands such as `nix registry pin`.
* Overrides specified on the command line using the option * Overrides specified on the command line using the option
`--override-flake`. `--override-flake`.

View file

@ -24,10 +24,34 @@ R""(
* Interact with Nixpkgs in the REPL: * Interact with Nixpkgs in the REPL:
```console ```console
# nix repl '<nixpkgs>' # nix repl --file example.nix
Loading Installable ''...
Added 3 variables.
Loading '<nixpkgs>'... # nix repl --expr '{a={b=3;c=4;};}'
Added 12428 variables. Loading Installable ''...
Added 1 variables.
# nix repl --expr '{a={b=3;c=4;};}' a
Loading Installable ''...
Added 1 variables.
# nix repl --extra_experimental_features 'flakes repl-flake' nixpkgs
Loading Installable 'flake:nixpkgs#'...
Added 5 variables.
nix-repl> legacyPackages.x86_64-linux.emacs.name
"emacs-27.1"
nix-repl> legacyPackages.x86_64-linux.emacs.name
"emacs-27.1"
nix-repl> :q
# nix repl --expr 'import <nixpkgs>{}'
Loading Installable ''...
Added 12439 variables.
nix-repl> emacs.name nix-repl> emacs.name
"emacs-27.1" "emacs-27.1"

View file

@ -47,7 +47,7 @@ void runProgramInStore(ref<Store> store,
Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program }; Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program };
for (auto & arg : args) helperArgs.push_back(arg); for (auto & arg : args) helperArgs.push_back(arg);
execv(readLink("/proc/self/exe").c_str(), stringsToCharPtrs(helperArgs).data()); execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data());
throw SysError("could not execute chroot helper"); throw SysError("could not execute chroot helper");
} }

View file

@ -54,7 +54,9 @@ struct CmdSearch : SourceExprCommand, MixJSON
.shortName = 'e', .shortName = 'e',
.description = "Hide packages whose attribute path, name or description contain *regex*.", .description = "Hide packages whose attribute path, name or description contain *regex*.",
.labels = {"regex"}, .labels = {"regex"},
.handler = Handler(&excludeRes), .handler = {[this](std::string s) {
excludeRes.push_back(s);
}},
}); });
} }

View file

@ -1 +1 @@
{ ... }@args: import ./shell.nix (args // { contentAddressed = true; }) { inNixShell ? false, ... }@args: import ./shell.nix (args // { contentAddressed = true; })

View file

@ -75,7 +75,7 @@ rec {
buildCommand = '' buildCommand = ''
mkdir -p $out/bin mkdir -p $out/bin
echo ${rootCA} # Just to make it depend on it echo ${rootCA} # Just to make it depend on it
echo "" > $out/bin/${name} echo "#! ${shell}" > $out/bin/${name}
chmod +x $out/bin/${name} chmod +x $out/bin/${name}
''; '';
}; };

View file

@ -50,6 +50,8 @@ export busybox="@sandbox_shell@"
export version=@PACKAGE_VERSION@ export version=@PACKAGE_VERSION@
export system=@system@ export system=@system@
export BUILD_SHARED_LIBS=@BUILD_SHARED_LIBS@
export IMPURE_VAR1=foo export IMPURE_VAR1=foo
export IMPURE_VAR2=bar export IMPURE_VAR2=bar
@ -117,11 +119,11 @@ killDaemon() {
} }
restartDaemon() { restartDaemon() {
[[ -z "${pidDaemon:-}" ]] && return 0 [[ -z "${pidDaemon:-}" ]] && return 0
killDaemon killDaemon
unset NIX_REMOTE unset NIX_REMOTE
startDaemon startDaemon
} }
if [[ $(uname) == Linux ]] && [[ -L /proc/self/ns/user ]] && unshare --user true; then if [[ $(uname) == Linux ]] && [[ -L /proc/self/ns/user ]] && unshare --user true; then
@ -188,4 +190,15 @@ if [[ -n "${NIX_DAEMON_PACKAGE:-}" ]]; then
startDaemon startDaemon
fi fi
onError() {
set +x
echo "$0: test failed at:" >&2
for ((i = 1; i < 16; i++)); do
if [[ -z ${BASH_SOURCE[i]} ]]; then break; fi
echo " ${FUNCNAME[i]} in ${BASH_SOURCE[i]}:${BASH_LINENO[i-1]}" >&2
done
}
trap onError ERR
fi # COMMON_SH_SOURCED fi # COMMON_SH_SOURCED

62
tests/completions.sh Normal file
View file

@ -0,0 +1,62 @@
source common.sh
cd "$TEST_ROOT"
mkdir -p dep
cat <<EOF > dep/flake.nix
{
outputs = i: { };
}
EOF
mkdir -p foo
cat <<EOF > foo/flake.nix
{
inputs.a.url = "path:$(realpath dep)";
outputs = i: {
sampleOutput = 1;
};
}
EOF
mkdir -p bar
cat <<EOF > bar/flake.nix
{
inputs.b.url = "path:$(realpath dep)";
outputs = i: {
sampleOutput = 1;
};
}
EOF
# Test the completion of a subcommand
[[ "$(NIX_GET_COMPLETIONS=1 nix buil)" == $'normal\nbuild\t' ]]
[[ "$(NIX_GET_COMPLETIONS=2 nix flake metad)" == $'normal\nmetadata\t' ]]
# Filename completion
[[ "$(NIX_GET_COMPLETIONS=2 nix build ./f)" == $'filenames\n./foo\t' ]]
[[ "$(NIX_GET_COMPLETIONS=2 nix build ./nonexistent)" == $'filenames' ]]
# Input override completion
[[ "$(NIX_GET_COMPLETIONS=4 nix build ./foo --override-input '')" == $'normal\na\t' ]]
[[ "$(NIX_GET_COMPLETIONS=5 nix flake show ./foo --override-input '')" == $'normal\na\t' ]]
## With multiple input flakes
[[ "$(NIX_GET_COMPLETIONS=5 nix build ./foo ./bar --override-input '')" == $'normal\na\t\nb\t' ]]
## With tilde expansion
[[ "$(HOME=$PWD NIX_GET_COMPLETIONS=4 nix build '~/foo' --override-input '')" == $'normal\na\t' ]]
## Out of order
[[ "$(NIX_GET_COMPLETIONS=3 nix build --update-input '' ./foo)" == $'normal\na\t' ]]
[[ "$(NIX_GET_COMPLETIONS=4 nix build ./foo --update-input '' ./bar)" == $'normal\na\t\nb\t' ]]
# Cli flag completion
NIX_GET_COMPLETIONS=2 nix build --log-form | grep -- "--log-format"
# Config option completion
## With `--option`
NIX_GET_COMPLETIONS=3 nix build --option allow-import-from | grep -- "allow-import-from-derivation"
## As a cli flag not working atm
# NIX_GET_COMPLETIONS=2 nix build --allow-import-from | grep -- "allow-import-from-derivation"
# Attr path completions
[[ "$(NIX_GET_COMPLETIONS=2 nix eval ./foo\#sam)" == $'attrs\n./foo#sampleOutput\t' ]]
[[ "$(NIX_GET_COMPLETIONS=4 nix eval --file ./foo/flake.nix outp)" == $'attrs\noutputs\t' ]]

View file

@ -1,9 +1,6 @@
source common.sh source common.sh
clearStore cp ../simple.nix ../simple.builder.sh ../config.nix $TEST_HOME
rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local
cp ./simple.nix ./simple.builder.sh ./config.nix $TEST_HOME
cd $TEST_HOME cd $TEST_HOME
@ -25,6 +22,7 @@ cat <<EOF > flake.nix
}; };
} }
EOF EOF
nix build .# nix build .#
nix bundle --bundler .# .# nix bundle --bundler .# .#
nix bundle --bundler .#bundlers.$system.default .#packages.$system.default nix bundle --bundler .#bundlers.$system.default .#packages.$system.default
@ -32,6 +30,3 @@ nix bundle --bundler .#bundlers.$system.simple .#packages.$system.default
nix bundle --bundler .#bundlers.$system.default .#apps.$system.default nix bundle --bundler .#bundlers.$system.default .#apps.$system.default
nix bundle --bundler .#bundlers.$system.simple .#apps.$system.default nix bundle --bundler .#bundlers.$system.simple .#apps.$system.default
clearStore

89
tests/flakes/check.sh Normal file
View file

@ -0,0 +1,89 @@
source common.sh
flakeDir=$TEST_ROOT/flake3
mkdir -p $flakeDir
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self }: {
overlay = final: prev: {
};
};
}
EOF
nix flake check $flakeDir
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self }: {
overlay = finalll: prev: {
};
};
}
EOF
(! nix flake check $flakeDir)
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self }: {
nixosModules.foo = {
a.b.c = 123;
foo = true;
};
};
}
EOF
nix flake check $flakeDir
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self }: {
nixosModules.foo = {
a.b.c = 123;
foo = assert false; true;
};
};
}
EOF
(! nix flake check $flakeDir)
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self }: {
nixosModule = { config, pkgs, ... }: {
a.b.c = 123;
};
};
}
EOF
nix flake check $flakeDir
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self }: {
nixosModule = { config, pkgs }: {
a.b.c = 123;
};
};
}
EOF
(! nix flake check $flakeDir)
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self }: {
packages.system-1.default = "foo";
packages.system-2.default = "bar";
};
}
EOF
checkRes=$(nix flake check --keep-going $flakeDir 2>&1 && fail "nix flake check should have failed" || true)
echo "$checkRes" | grep -q "packages.system-1.default"
echo "$checkRes" | grep -q "packages.system-2.default"

49
tests/flakes/circular.sh Normal file
View file

@ -0,0 +1,49 @@
# Test circular flake dependencies.
source ./common.sh
requireGit
flakeA=$TEST_ROOT/flakeA
flakeB=$TEST_ROOT/flakeB
createGitRepo $flakeA
createGitRepo $flakeB
cat > $flakeA/flake.nix <<EOF
{
inputs.b.url = git+file://$flakeB;
inputs.b.inputs.a.follows = "/";
outputs = { self, b }: {
foo = 123 + b.bar;
xyzzy = 1000;
};
}
EOF
git -C $flakeA add flake.nix
cat > $flakeB/flake.nix <<EOF
{
inputs.a.url = git+file://$flakeA;
outputs = { self, a }: {
bar = 456 + a.xyzzy;
};
}
EOF
git -C $flakeB add flake.nix
git -C $flakeB commit -a -m 'Foo'
[[ $(nix eval $flakeA#foo) = 1579 ]]
[[ $(nix eval $flakeA#foo) = 1579 ]]
sed -i $flakeB/flake.nix -e 's/456/789/'
git -C $flakeB commit -a -m 'Foo'
[[ $(nix eval --update-input b $flakeA#foo) = 1912 ]]
# Test list-inputs with circular dependencies
nix flake metadata $flakeA

73
tests/flakes/common.sh Normal file
View file

@ -0,0 +1,73 @@
source ../common.sh
registry=$TEST_ROOT/registry.json
requireGit() {
if [[ -z $(type -p git) ]]; then
echo "Git not installed; skipping flake tests"
exit 99
fi
}
writeSimpleFlake() {
local flakeDir="$1"
cat > $flakeDir/flake.nix <<EOF
{
description = "Bla bla";
outputs = inputs: rec {
packages.$system = rec {
foo = import ./simple.nix;
default = foo;
};
# To test "nix flake init".
legacyPackages.x86_64-linux.hello = import ./simple.nix;
};
}
EOF
cp ../simple.nix ../simple.builder.sh ../config.nix $flakeDir/
}
createSimpleGitFlake() {
local flakeDir="$1"
writeSimpleFlake $flakeDir
git -C $flakeDir add flake.nix simple.nix simple.builder.sh config.nix
git -C $flakeDir commit -m 'Initial'
}
writeDependentFlake() {
local flakeDir="$1"
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self, flake1 }: {
packages.$system.default = flake1.packages.$system.default;
expr = assert builtins.pathExists ./flake.lock; 123;
};
}
EOF
}
writeTrivialFlake() {
local flakeDir="$1"
cat > $flakeDir/flake.nix <<EOF
{
outputs = { self }: {
expr = 123;
};
}
EOF
}
createGitRepo() {
local repo="$1"
local extraArgs="$2"
rm -rf $repo $repo.tmp
mkdir -p $repo
git -C $repo init $extraArgs
git -C $repo config user.email "foobar@example.com"
git -C $repo config user.name "Foobar"
}

View file

@ -1,9 +1,6 @@
source common.sh source common.sh
clearStore cp ../simple.nix ../simple.builder.sh ../config.nix $TEST_HOME
rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local
cp ./simple.nix ./simple.builder.sh ./config.nix $TEST_HOME
cd $TEST_HOME cd $TEST_HOME

View file

@ -1,67 +1,30 @@
source common.sh source ./common.sh
if [[ -z $(type -p git) ]]; then requireGit
echo "Git not installed; skipping flake tests"
exit 99
fi
clearStore clearStore
rm -rf $TEST_HOME/.cache $TEST_HOME/.config rm -rf $TEST_HOME/.cache $TEST_HOME/.config
registry=$TEST_ROOT/registry.json
flake1Dir=$TEST_ROOT/flake1 flake1Dir=$TEST_ROOT/flake1
flake2Dir=$TEST_ROOT/flake2 flake2Dir=$TEST_ROOT/flake2
flake3Dir=$TEST_ROOT/flake3 flake3Dir=$TEST_ROOT/flake3
flake5Dir=$TEST_ROOT/flake5 flake5Dir=$TEST_ROOT/flake5
flake6Dir=$TEST_ROOT/flake6
flake7Dir=$TEST_ROOT/flake7 flake7Dir=$TEST_ROOT/flake7
templatesDir=$TEST_ROOT/templates
nonFlakeDir=$TEST_ROOT/nonFlake nonFlakeDir=$TEST_ROOT/nonFlake
badFlakeDir=$TEST_ROOT/badFlake badFlakeDir=$TEST_ROOT/badFlake
flakeA=$TEST_ROOT/flakeA
flakeB=$TEST_ROOT/flakeB
flakeGitBare=$TEST_ROOT/flakeGitBare flakeGitBare=$TEST_ROOT/flakeGitBare
flakeFollowsA=$TEST_ROOT/follows/flakeA
flakeFollowsB=$TEST_ROOT/follows/flakeA/flakeB
flakeFollowsC=$TEST_ROOT/follows/flakeA/flakeB/flakeC
flakeFollowsD=$TEST_ROOT/follows/flakeA/flakeD
flakeFollowsE=$TEST_ROOT/follows/flakeA/flakeE
for repo in $flake1Dir $flake2Dir $flake3Dir $flake7Dir $templatesDir $nonFlakeDir $flakeA $flakeB $flakeFollowsA; do
rm -rf $repo $repo.tmp
mkdir -p $repo
for repo in $flake1Dir $flake2Dir $flake3Dir $flake7Dir $nonFlakeDir; do
# Give one repo a non-main initial branch. # Give one repo a non-main initial branch.
extraArgs= extraArgs=
if [[ $repo == $flake2Dir ]]; then if [[ $repo == $flake2Dir ]]; then
extraArgs="--initial-branch=main" extraArgs="--initial-branch=main"
fi fi
git -C $repo init $extraArgs createGitRepo "$repo" "$extraArgs"
git -C $repo config user.email "foobar@example.com"
git -C $repo config user.name "Foobar"
done done
cat > $flake1Dir/flake.nix <<EOF createSimpleGitFlake $flake1Dir
{
description = "Bla bla";
outputs = inputs: rec {
packages.$system = rec {
foo = import ./simple.nix;
default = foo;
};
# To test "nix flake init".
legacyPackages.x86_64-linux.hello = import ./simple.nix;
};
}
EOF
cp ./simple.nix ./simple.builder.sh ./config.nix $flake1Dir/
git -C $flake1Dir add flake.nix simple.nix simple.builder.sh config.nix
git -C $flake1Dir commit -m 'Initial'
cat > $flake2Dir/flake.nix <<EOF cat > $flake2Dir/flake.nix <<EOF
{ {
@ -105,12 +68,10 @@ nix registry add --registry $registry flake1 git+file://$flake1Dir
nix registry add --registry $registry flake2 git+file://$flake2Dir nix registry add --registry $registry flake2 git+file://$flake2Dir
nix registry add --registry $registry flake3 git+file://$flake3Dir nix registry add --registry $registry flake3 git+file://$flake3Dir
nix registry add --registry $registry flake4 flake3 nix registry add --registry $registry flake4 flake3
nix registry add --registry $registry flake5 hg+file://$flake5Dir
nix registry add --registry $registry nixpkgs flake1 nix registry add --registry $registry nixpkgs flake1
nix registry add --registry $registry templates git+file://$templatesDir
# Test 'nix flake list'. # Test 'nix flake list'.
[[ $(nix registry list | wc -l) == 7 ]] [[ $(nix registry list | wc -l) == 5 ]]
# Test 'nix flake metadata'. # Test 'nix flake metadata'.
nix flake metadata flake1 nix flake metadata flake1
@ -291,7 +252,7 @@ cat > $flake3Dir/flake.nix <<EOF
} }
EOF EOF
cp ./config.nix $flake3Dir cp ../config.nix $flake3Dir
git -C $flake3Dir add flake.nix config.nix git -C $flake3Dir add flake.nix config.nix
git -C $flake3Dir commit -m 'Add nonFlakeInputs' git -C $flake3Dir commit -m 'Add nonFlakeInputs'
@ -366,161 +327,19 @@ nix build -o $TEST_ROOT/result flake4/removeXyzzy#sth
# Testing the nix CLI # Testing the nix CLI
nix registry add flake1 flake3 nix registry add flake1 flake3
[[ $(nix registry list | wc -l) == 8 ]] [[ $(nix registry list | wc -l) == 6 ]]
nix registry pin flake1 nix registry pin flake1
[[ $(nix registry list | wc -l) == 8 ]] [[ $(nix registry list | wc -l) == 6 ]]
nix registry pin flake1 flake3 nix registry pin flake1 flake3
[[ $(nix registry list | wc -l) == 8 ]] [[ $(nix registry list | wc -l) == 6 ]]
nix registry remove flake1 nix registry remove flake1
[[ $(nix registry list | wc -l) == 7 ]] [[ $(nix registry list | wc -l) == 5 ]]
# Test 'nix flake init'.
cat > $templatesDir/flake.nix <<EOF
{
description = "Some templates";
outputs = { self }: {
templates = rec {
trivial = {
path = ./trivial;
description = "A trivial flake";
welcomeText = ''
Welcome to my trivial flake
'';
};
default = trivial;
};
};
}
EOF
mkdir $templatesDir/trivial
cat > $templatesDir/trivial/flake.nix <<EOF
{
description = "A flake for building Hello World";
outputs = { self, nixpkgs }: {
packages.x86_64-linux = rec {
hello = nixpkgs.legacyPackages.x86_64-linux.hello;
default = hello;
};
};
}
EOF
git -C $templatesDir add flake.nix trivial/flake.nix
git -C $templatesDir commit -m 'Initial'
nix flake check templates
nix flake show templates
nix flake show templates --json | jq
(cd $flake7Dir && nix flake init)
(cd $flake7Dir && nix flake init) # check idempotence
git -C $flake7Dir add flake.nix
nix flake check $flake7Dir
nix flake show $flake7Dir
nix flake show $flake7Dir --json | jq
git -C $flake7Dir commit -a -m 'Initial'
# Test 'nix flake new'.
rm -rf $flake6Dir
nix flake new -t templates#trivial $flake6Dir
nix flake new -t templates#trivial $flake6Dir # check idempotence
nix flake check $flake6Dir
# Test 'nix flake clone'. # Test 'nix flake clone'.
rm -rf $TEST_ROOT/flake1-v2 rm -rf $TEST_ROOT/flake1-v2
nix flake clone flake1 --dest $TEST_ROOT/flake1-v2 nix flake clone flake1 --dest $TEST_ROOT/flake1-v2
[ -e $TEST_ROOT/flake1-v2/flake.nix ] [ -e $TEST_ROOT/flake1-v2/flake.nix ]
# More 'nix flake check' tests.
cat > $flake3Dir/flake.nix <<EOF
{
outputs = { flake1, self }: {
overlay = final: prev: {
};
};
}
EOF
nix flake check $flake3Dir
cat > $flake3Dir/flake.nix <<EOF
{
outputs = { flake1, self }: {
overlay = finalll: prev: {
};
};
}
EOF
(! nix flake check $flake3Dir)
cat > $flake3Dir/flake.nix <<EOF
{
outputs = { flake1, self }: {
nixosModules.foo = {
a.b.c = 123;
foo = true;
};
};
}
EOF
nix flake check $flake3Dir
cat > $flake3Dir/flake.nix <<EOF
{
outputs = { flake1, self }: {
nixosModules.foo = {
a.b.c = 123;
foo = assert false; true;
};
};
}
EOF
(! nix flake check $flake3Dir)
cat > $flake3Dir/flake.nix <<EOF
{
outputs = { flake1, self }: {
nixosModule = { config, pkgs, ... }: {
a.b.c = 123;
};
};
}
EOF
nix flake check $flake3Dir
cat > $flake3Dir/flake.nix <<EOF
{
outputs = { flake1, self }: {
nixosModule = { config, pkgs }: {
a.b.c = 123;
};
};
}
EOF
(! nix flake check $flake3Dir)
cat > $flake3Dir/flake.nix <<EOF
{
outputs = { flake1, self }: {
packages.system-1.default = "foo";
packages.system-2.default = "bar";
};
}
EOF
checkRes=$(nix flake check --keep-going $flake3Dir 2>&1 && fail "nix flake check should have failed" || true)
echo "$checkRes" | grep -q "packages.system-1.default"
echo "$checkRes" | grep -q "packages.system-2.default"
# Test 'follows' inputs. # Test 'follows' inputs.
cat > $flake3Dir/flake.nix <<EOF cat > $flake3Dir/flake.nix <<EOF
{ {
@ -563,6 +382,10 @@ nix flake lock $flake3Dir
[[ $(jq -c .nodes.root.inputs.bar $flake3Dir/flake.lock) = '["flake2"]' ]] [[ $(jq -c .nodes.root.inputs.bar $flake3Dir/flake.lock) = '["flake2"]' ]]
# Test overriding inputs of inputs. # Test overriding inputs of inputs.
writeTrivialFlake $flake7Dir
git -C $flake7Dir add flake.nix
git -C $flake7Dir commit -m 'Initial'
cat > $flake3Dir/flake.nix <<EOF cat > $flake3Dir/flake.nix <<EOF
{ {
inputs.flake2.inputs.flake1 = { inputs.flake2.inputs.flake1 = {
@ -597,50 +420,9 @@ rm -rf $flakeGitBare
git clone --bare $flake1Dir $flakeGitBare git clone --bare $flake1Dir $flakeGitBare
nix build -o $TEST_ROOT/result git+file://$flakeGitBare nix build -o $TEST_ROOT/result git+file://$flakeGitBare
# Test Mercurial flakes.
rm -rf $flake5Dir
mkdir $flake5Dir
cat > $flake5Dir/flake.nix <<EOF
{
outputs = { self, flake1 }: {
packages.$system.default = flake1.packages.$system.default;
expr = assert builtins.pathExists ./flake.lock; 123;
};
}
EOF
if [[ -n $(type -p hg) ]]; then
hg init $flake5Dir
hg add $flake5Dir/flake.nix
hg commit --config ui.username=foobar@example.org $flake5Dir -m 'Initial commit'
nix build -o $TEST_ROOT/result hg+file://$flake5Dir
[[ -e $TEST_ROOT/result/hello ]]
(! nix flake metadata --json hg+file://$flake5Dir | jq -e -r .revision)
nix eval hg+file://$flake5Dir#expr
nix eval hg+file://$flake5Dir#expr
(! nix eval hg+file://$flake5Dir#expr --no-allow-dirty)
(! nix flake metadata --json hg+file://$flake5Dir | jq -e -r .revision)
hg commit --config ui.username=foobar@example.org $flake5Dir -m 'Add lock file'
nix flake metadata --json hg+file://$flake5Dir --refresh | jq -e -r .revision
nix flake metadata --json hg+file://$flake5Dir
[[ $(nix flake metadata --json hg+file://$flake5Dir | jq -e -r .revCount) = 1 ]]
nix build -o $TEST_ROOT/result hg+file://$flake5Dir --no-registries --no-allow-dirty
nix build -o $TEST_ROOT/result hg+file://$flake5Dir --no-use-registries --no-allow-dirty
fi
# Test path flakes. # Test path flakes.
rm -rf $flake5Dir/.hg $flake5Dir/flake.lock mkdir -p $flake5Dir
writeDependentFlake $flake5Dir
nix flake lock path://$flake5Dir nix flake lock path://$flake5Dir
# Test tarball flakes. # Test tarball flakes.
@ -678,165 +460,6 @@ nix flake lock $flake3Dir --update-input flake2/flake1
# Test 'nix flake metadata --json'. # Test 'nix flake metadata --json'.
nix flake metadata $flake3Dir --json | jq . nix flake metadata $flake3Dir --json | jq .
# Test circular flake dependencies.
cat > $flakeA/flake.nix <<EOF
{
inputs.b.url = git+file://$flakeB;
inputs.b.inputs.a.follows = "/";
outputs = { self, nixpkgs, b }: {
foo = 123 + b.bar;
xyzzy = 1000;
};
}
EOF
git -C $flakeA add flake.nix
cat > $flakeB/flake.nix <<EOF
{
inputs.a.url = git+file://$flakeA;
outputs = { self, nixpkgs, a }: {
bar = 456 + a.xyzzy;
};
}
EOF
git -C $flakeB add flake.nix
git -C $flakeB commit -a -m 'Foo'
[[ $(nix eval $flakeA#foo) = 1579 ]]
[[ $(nix eval $flakeA#foo) = 1579 ]]
sed -i $flakeB/flake.nix -e 's/456/789/'
git -C $flakeB commit -a -m 'Foo'
[[ $(nix eval --update-input b $flakeA#foo) = 1912 ]]
# Test list-inputs with circular dependencies
nix flake metadata $flakeA
# Test flake follow paths
mkdir -p $flakeFollowsB
mkdir -p $flakeFollowsC
mkdir -p $flakeFollowsD
mkdir -p $flakeFollowsE
cat > $flakeFollowsA/flake.nix <<EOF
{
description = "Flake A";
inputs = {
B = {
url = "path:./flakeB";
inputs.foobar.follows = "foobar";
};
foobar.url = "path:$flakeFollowsA/flakeE";
};
outputs = { ... }: {};
}
EOF
cat > $flakeFollowsB/flake.nix <<EOF
{
description = "Flake B";
inputs = {
foobar.url = "path:$flakeFollowsA/flakeE";
goodoo.follows = "C/goodoo";
C = {
url = "path:./flakeC";
inputs.foobar.follows = "foobar";
};
};
outputs = { ... }: {};
}
EOF
cat > $flakeFollowsC/flake.nix <<EOF
{
description = "Flake C";
inputs = {
foobar.url = "path:$flakeFollowsA/flakeE";
goodoo.follows = "foobar";
};
outputs = { ... }: {};
}
EOF
cat > $flakeFollowsD/flake.nix <<EOF
{
description = "Flake D";
inputs = {};
outputs = { ... }: {};
}
EOF
cat > $flakeFollowsE/flake.nix <<EOF
{
description = "Flake E";
inputs = {};
outputs = { ... }: {};
}
EOF
git -C $flakeFollowsA add flake.nix flakeB/flake.nix \
flakeB/flakeC/flake.nix flakeD/flake.nix flakeE/flake.nix
nix flake metadata $flakeFollowsA
nix flake update $flakeFollowsA
oldLock="$(cat "$flakeFollowsA/flake.lock")"
# Ensure that locking twice doesn't change anything
nix flake lock $flakeFollowsA
newLock="$(cat "$flakeFollowsA/flake.lock")"
diff <(echo "$newLock") <(echo "$oldLock")
[[ $(jq -c .nodes.B.inputs.C $flakeFollowsA/flake.lock) = '"C"' ]]
[[ $(jq -c .nodes.B.inputs.foobar $flakeFollowsA/flake.lock) = '["foobar"]' ]]
[[ $(jq -c .nodes.C.inputs.foobar $flakeFollowsA/flake.lock) = '["B","foobar"]' ]]
# Ensure removing follows from flake.nix removes them from the lockfile
cat > $flakeFollowsA/flake.nix <<EOF
{
description = "Flake A";
inputs = {
B = {
url = "path:./flakeB";
inputs.nonFlake.follows = "D";
};
D.url = "path:./flakeD";
};
outputs = { ... }: {};
}
EOF
nix flake lock $flakeFollowsA
[[ $(jq -c .nodes.B.inputs.foobar $flakeFollowsA/flake.lock) = '"foobar"' ]]
jq -r -c '.nodes | keys | .[]' $flakeFollowsA/flake.lock | grep "^foobar$"
# Ensure a relative path is not allowed to go outside the store path
cat > $flakeFollowsA/flake.nix <<EOF
{
description = "Flake A";
inputs = {
B.url = "path:../flakeB";
};
outputs = { ... }: {};
}
EOF
git -C $flakeFollowsA add flake.nix
nix flake lock $flakeFollowsA 2>&1 | grep 'points outside'
# Test flake in store does not evaluate # Test flake in store does not evaluate
rm -rf $badFlakeDir rm -rf $badFlakeDir
mkdir $badFlakeDir mkdir $badFlakeDir

View file

@ -0,0 +1,150 @@
source ./common.sh
requireGit
flakeFollowsA=$TEST_ROOT/follows/flakeA
flakeFollowsB=$TEST_ROOT/follows/flakeA/flakeB
flakeFollowsC=$TEST_ROOT/follows/flakeA/flakeB/flakeC
flakeFollowsD=$TEST_ROOT/follows/flakeA/flakeD
flakeFollowsE=$TEST_ROOT/follows/flakeA/flakeE
# Test following path flakerefs.
createGitRepo $flakeFollowsA
mkdir -p $flakeFollowsB
mkdir -p $flakeFollowsC
mkdir -p $flakeFollowsD
mkdir -p $flakeFollowsE
cat > $flakeFollowsA/flake.nix <<EOF
{
description = "Flake A";
inputs = {
B = {
url = "path:./flakeB";
inputs.foobar.follows = "foobar";
};
foobar.url = "path:$flakeFollowsA/flakeE";
};
outputs = { ... }: {};
}
EOF
cat > $flakeFollowsB/flake.nix <<EOF
{
description = "Flake B";
inputs = {
foobar.url = "path:$flakeFollowsA/flakeE";
goodoo.follows = "C/goodoo";
C = {
url = "path:./flakeC";
inputs.foobar.follows = "foobar";
};
};
outputs = { ... }: {};
}
EOF
cat > $flakeFollowsC/flake.nix <<EOF
{
description = "Flake C";
inputs = {
foobar.url = "path:$flakeFollowsA/flakeE";
goodoo.follows = "foobar";
};
outputs = { ... }: {};
}
EOF
cat > $flakeFollowsD/flake.nix <<EOF
{
description = "Flake D";
inputs = {};
outputs = { ... }: {};
}
EOF
cat > $flakeFollowsE/flake.nix <<EOF
{
description = "Flake E";
inputs = {};
outputs = { ... }: {};
}
EOF
git -C $flakeFollowsA add flake.nix flakeB/flake.nix \
flakeB/flakeC/flake.nix flakeD/flake.nix flakeE/flake.nix
nix flake metadata $flakeFollowsA
nix flake update $flakeFollowsA
nix flake lock $flakeFollowsA
oldLock="$(cat "$flakeFollowsA/flake.lock")"
# Ensure that locking twice doesn't change anything
nix flake lock $flakeFollowsA
newLock="$(cat "$flakeFollowsA/flake.lock")"
diff <(echo "$newLock") <(echo "$oldLock")
[[ $(jq -c .nodes.B.inputs.C $flakeFollowsA/flake.lock) = '"C"' ]]
[[ $(jq -c .nodes.B.inputs.foobar $flakeFollowsA/flake.lock) = '["foobar"]' ]]
[[ $(jq -c .nodes.C.inputs.foobar $flakeFollowsA/flake.lock) = '["B","foobar"]' ]]
# Ensure removing follows from flake.nix removes them from the lockfile
cat > $flakeFollowsA/flake.nix <<EOF
{
description = "Flake A";
inputs = {
B = {
url = "path:./flakeB";
};
D.url = "path:./flakeD";
};
outputs = { ... }: {};
}
EOF
nix flake lock $flakeFollowsA
[[ $(jq -c .nodes.B.inputs.foobar $flakeFollowsA/flake.lock) = '"foobar"' ]]
jq -r -c '.nodes | keys | .[]' $flakeFollowsA/flake.lock | grep "^foobar$"
# Ensure a relative path is not allowed to go outside the store path
cat > $flakeFollowsA/flake.nix <<EOF
{
description = "Flake A";
inputs = {
B.url = "path:../flakeB";
};
outputs = { ... }: {};
}
EOF
git -C $flakeFollowsA add flake.nix
nix flake lock $flakeFollowsA 2>&1 | grep 'points outside'
# Non-existant follows should print a warning.
cat >$flakeFollowsA/flake.nix <<EOF
{
description = "Flake A";
inputs.B = {
url = "path:./flakeB";
inputs.invalid.follows = "D";
inputs.invalid2.url = "path:./flakeD";
};
inputs.D.url = "path:./flakeD";
outputs = { ... }: {};
}
EOF
git -C $flakeFollowsA add flake.nix
nix flake lock $flakeFollowsA 2>&1 | grep "warning: input 'B' has an override for a non-existent input 'invalid'"
nix flake lock $flakeFollowsA 2>&1 | grep "warning: input 'B' has an override for a non-existent input 'invalid2'"

87
tests/flakes/init.sh Normal file
View file

@ -0,0 +1,87 @@
source ./common.sh
requireGit
templatesDir=$TEST_ROOT/templates
flakeDir=$TEST_ROOT/flake
nixpkgsDir=$TEST_ROOT/nixpkgs
nix registry add --registry $registry templates git+file://$templatesDir
nix registry add --registry $registry nixpkgs git+file://$nixpkgsDir
createGitRepo $nixpkgsDir
createSimpleGitFlake $nixpkgsDir
# Test 'nix flake init'.
createGitRepo $templatesDir
cat > $templatesDir/flake.nix <<EOF
{
description = "Some templates";
outputs = { self }: {
templates = rec {
trivial = {
path = ./trivial;
description = "A trivial flake";
welcomeText = ''
Welcome to my trivial flake
'';
};
default = trivial;
};
};
}
EOF
mkdir $templatesDir/trivial
cat > $templatesDir/trivial/flake.nix <<EOF
{
description = "A flake for building Hello World";
outputs = { self, nixpkgs }: {
packages.x86_64-linux = rec {
hello = nixpkgs.legacyPackages.x86_64-linux.hello;
default = hello;
};
};
}
EOF
echo a > $templatesDir/trivial/a
echo b > $templatesDir/trivial/b
git -C $templatesDir add flake.nix trivial/
git -C $templatesDir commit -m 'Initial'
nix flake check templates
nix flake show templates
nix flake show templates --json | jq
createGitRepo $flakeDir
(cd $flakeDir && nix flake init)
(cd $flakeDir && nix flake init) # check idempotence
git -C $flakeDir add flake.nix
nix flake check $flakeDir
nix flake show $flakeDir
nix flake show $flakeDir --json | jq
git -C $flakeDir commit -a -m 'Initial'
# Test 'nix flake init' with benign conflicts
createGitRepo "$flakeDir"
echo a > $flakeDir/a
(cd $flakeDir && nix flake init) # check idempotence
# Test 'nix flake init' with conflicts
createGitRepo "$flakeDir"
echo b > $flakeDir/a
pushd $flakeDir
(! nix flake init) |& grep "refusing to overwrite existing file '$flakeDir/a'"
popd
git -C $flakeDir commit -a -m 'Changed'
# Test 'nix flake new'.
rm -rf $flakeDir
nix flake new -t templates#trivial $flakeDir
nix flake new -t templates#trivial $flakeDir # check idempotence
nix flake check $flakeDir

46
tests/flakes/mercurial.sh Normal file
View file

@ -0,0 +1,46 @@
source ./common.sh
if [[ -z $(type -p hg) ]]; then
echo "Mercurial not installed; skipping"
exit 99
fi
flake1Dir=$TEST_ROOT/flake-hg1
mkdir -p $flake1Dir
writeSimpleFlake $flake1Dir
hg init $flake1Dir
nix registry add --registry $registry flake1 hg+file://$flake1Dir
flake2Dir=$TEST_ROOT/flake-hg2
mkdir -p $flake2Dir
writeDependentFlake $flake2Dir
hg init $flake2Dir
hg add $flake1Dir/*
hg commit --config ui.username=foobar@example.org $flake1Dir -m 'Initial commit'
hg add $flake2Dir/flake.nix
hg commit --config ui.username=foobar@example.org $flake2Dir -m 'Initial commit'
nix build -o $TEST_ROOT/result hg+file://$flake2Dir
[[ -e $TEST_ROOT/result/hello ]]
(! nix flake metadata --json hg+file://$flake2Dir | jq -e -r .revision)
nix eval hg+file://$flake2Dir#expr
nix eval hg+file://$flake2Dir#expr
(! nix eval hg+file://$flake2Dir#expr --no-allow-dirty)
(! nix flake metadata --json hg+file://$flake2Dir | jq -e -r .revision)
hg commit --config ui.username=foobar@example.org $flake2Dir -m 'Add lock file'
nix flake metadata --json hg+file://$flake2Dir --refresh | jq -e -r .revision
nix flake metadata --json hg+file://$flake2Dir
[[ $(nix flake metadata --json hg+file://$flake2Dir | jq -e -r .revCount) = 1 ]]
nix build -o $TEST_ROOT/result hg+file://$flake2Dir --no-registries --no-allow-dirty
nix build -o $TEST_ROOT/result hg+file://$flake2Dir --no-use-registries --no-allow-dirty

View file

@ -1,8 +1,8 @@
source common.sh source ../common.sh
clearStore clearStore
rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local rm -rf $TEST_HOME/.cache $TEST_HOME/.config $TEST_HOME/.local
cp ./shell-hello.nix ./config.nix $TEST_HOME cp ../shell-hello.nix ../config.nix $TEST_HOME
cd $TEST_HOME cd $TEST_HOME
cat <<EOF > flake.nix cat <<EOF > flake.nix

View file

@ -1,15 +1,11 @@
source common.sh source common.sh
if [[ -z $(type -p git) ]]; then
echo "Git not installed; skipping flake search tests"
exit 99
fi
clearStore clearStore
cp ./simple.nix ./simple.builder.sh ./config.nix $TEST_HOME writeSimpleFlake $TEST_HOME
cd $TEST_HOME cd $TEST_HOME
mkdir -p foo/subdir mkdir -p foo/subdir
echo '{ outputs = _: {}; }' > foo/flake.nix echo '{ outputs = _: {}; }' > foo/flake.nix
cat <<EOF > flake.nix cat <<EOF > flake.nix
{ {
@ -43,10 +39,12 @@ nix build --override-input foo . || fail "flake should search up directories whe
sed "s,$PWD/foo,$PWD/foo/subdir,g" -i flake.nix sed "s,$PWD/foo,$PWD/foo/subdir,g" -i flake.nix
! nix build || fail "flake should not search upwards when part of inputs" ! nix build || fail "flake should not search upwards when part of inputs"
pushd subdir if [[ -n $(type -p git) ]]; then
git init pushd subdir
for i in "${success[@]}" "${failure[@]}"; do git init
! nix build $i || fail "flake should not search past a git repository" for i in "${success[@]}" "${failure[@]}"; do
done ! nix build $i || fail "flake should not search past a git repository"
rm -rf .git done
popd rm -rf .git
popd
fi

View file

@ -18,7 +18,12 @@ cat << EOF > flake.nix
with import ./config.nix; with import ./config.nix;
mkDerivation { mkDerivation {
name = "formatter"; name = "formatter";
buildCommand = "mkdir -p \$out/bin; cp \${./fmt.simple.sh} \$out/bin/formatter"; buildCommand = ''
mkdir -p \$out/bin
echo "#! ${shell}" > \$out/bin/formatter
cat \${./fmt.simple.sh} >> \$out/bin/formatter
chmod +x \$out/bin/formatter
'';
}; };
}; };
} }

View file

@ -7,7 +7,7 @@ with import (nixpkgs + "/nixos/lib/testing-python.nix") {
let let
# Generate a fake root CA and a fake github.com certificate. # Generate a fake root CA and a fake api.github.com / channels.nixos.org certificate.
cert = pkgs.runCommand "cert" { buildInputs = [ pkgs.openssl ]; } cert = pkgs.runCommand "cert" { buildInputs = [ pkgs.openssl ]; }
'' ''
mkdir -p $out mkdir -p $out
@ -18,7 +18,7 @@ let
openssl req -newkey rsa:2048 -nodes -keyout $out/server.key \ openssl req -newkey rsa:2048 -nodes -keyout $out/server.key \
-subj "/C=CN/ST=Denial/L=Springfield/O=Dis/CN=github.com" -out server.csr -subj "/C=CN/ST=Denial/L=Springfield/O=Dis/CN=github.com" -out server.csr
openssl x509 -req -extfile <(printf "subjectAltName=DNS:api.github.com,DNS:github.com,DNS:raw.githubusercontent.com") \ openssl x509 -req -extfile <(printf "subjectAltName=DNS:api.github.com,DNS:channels.nixos.org") \
-days 36500 -in server.csr -CA $out/ca.crt -CAkey ca.key -CAcreateserial -out $out/server.crt -days 36500 -in server.csr -CA $out/ca.crt -CAkey ca.key -CAcreateserial -out $out/server.crt
''; '';
@ -67,7 +67,7 @@ makeTest (
name = "github-flakes"; name = "github-flakes";
nodes = nodes =
{ # Impersonate github.com and api.github.com. {
github = github =
{ config, pkgs, ... }: { config, pkgs, ... }:
{ networking.firewall.allowedTCPPorts = [ 80 443 ]; { networking.firewall.allowedTCPPorts = [ 80 443 ];
@ -77,12 +77,12 @@ makeTest (
services.httpd.extraConfig = '' services.httpd.extraConfig = ''
ErrorLog syslog:local6 ErrorLog syslog:local6
''; '';
services.httpd.virtualHosts."github.com" = services.httpd.virtualHosts."channels.nixos.org" =
{ forceSSL = true; { forceSSL = true;
sslServerKey = "${cert}/server.key"; sslServerKey = "${cert}/server.key";
sslServerCert = "${cert}/server.crt"; sslServerCert = "${cert}/server.crt";
servedDirs = servedDirs =
[ { urlPath = "/NixOS/flake-registry/raw/master"; [ { urlPath = "/";
dir = registry; dir = registry;
} }
]; ];
@ -109,7 +109,7 @@ makeTest (
nix.extraOptions = "experimental-features = nix-command flakes"; nix.extraOptions = "experimental-features = nix-command flakes";
environment.systemPackages = [ pkgs.jq ]; environment.systemPackages = [ pkgs.jq ];
networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} = networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} =
[ "github.com" "api.github.com" "raw.githubusercontent.com" ]; [ "channels.nixos.org" "api.github.com" ];
security.pki.certificateFiles = [ "${cert}/ca.crt" ]; security.pki.certificateFiles = [ "${cert}/ca.crt" ];
}; };
}; };
@ -123,7 +123,7 @@ makeTest (
github.wait_for_unit("httpd.service") github.wait_for_unit("httpd.service")
client.succeed("curl -v https://github.com/ >&2") client.succeed("curl -v https://api.github.com/ >&2")
client.succeed("nix registry list | grep nixpkgs") client.succeed("nix registry list | grep nixpkgs")
rev = client.succeed("nix flake info nixpkgs --json | jq -r .revision") rev = client.succeed("nix flake info nixpkgs --json | jq -r .revision")

View file

@ -5,6 +5,8 @@ export NIX_REMOTE=dummy://
nix-instantiate --eval -E 'builtins.trace "Hello" 123' 2>&1 | grep -q Hello nix-instantiate --eval -E 'builtins.trace "Hello" 123' 2>&1 | grep -q Hello
nix-instantiate --eval -E 'builtins.addErrorContext "Hello" 123' 2>&1 nix-instantiate --eval -E 'builtins.addErrorContext "Hello" 123' 2>&1
nix-instantiate --trace-verbose --eval -E 'builtins.traceVerbose "Hello" 123' 2>&1 | grep -q Hello
(! nix-instantiate --eval -E 'builtins.traceVerbose "Hello" 123' 2>&1 | grep -q Hello)
(! nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" 123' 2>&1 | grep -q Hello) (! nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" 123' 2>&1 | grep -q Hello)
nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" (throw "Foo")' 2>&1 | grep -q Hello nix-instantiate --show-trace --eval -E 'builtins.addErrorContext "Hello" (throw "Foo")' 2>&1 | grep -q Hello

View file

@ -1,6 +1,12 @@
nix_tests = \ nix_tests = \
flakes.sh \ flakes/flakes.sh \
flakes-run.sh \ flakes/run.sh \
flakes/mercurial.sh \
flakes/circular.sh \
flakes/init.sh \
flakes/follow-paths.sh \
flakes/bundle.sh \
flakes/check.sh \
ca/gc.sh \ ca/gc.sh \
gc.sh \ gc.sh \
remote-store.sh \ remote-store.sh \
@ -44,7 +50,7 @@ nix_tests = \
secure-drv-outputs.sh \ secure-drv-outputs.sh \
restricted.sh \ restricted.sh \
fetchGitSubmodules.sh \ fetchGitSubmodules.sh \
flake-searching.sh \ flakes/search-root.sh \
ca/duplicate-realisation-in-closure.sh \ ca/duplicate-realisation-in-closure.sh \
readfile-context.sh \ readfile-context.sh \
nix-channel.sh \ nix-channel.sh \
@ -80,7 +86,7 @@ nix_tests = \
nix-copy-ssh.sh \ nix-copy-ssh.sh \
post-hook.sh \ post-hook.sh \
function-trace.sh \ function-trace.sh \
flake-local-settings.sh \ flakes/config.sh \
fmt.sh \ fmt.sh \
eval-store.sh \ eval-store.sh \
why-depends.sh \ why-depends.sh \
@ -115,6 +121,7 @@ nix_tests = \
readfile-context.sh \ readfile-context.sh \
store-ping.sh \ store-ping.sh \
fetchClosure.sh \ fetchClosure.sh \
completions.sh \
impure-derivations.sh impure-derivations.sh
ifeq ($(HAVE_LIBCPUID), 1) ifeq ($(HAVE_LIBCPUID), 1)
@ -127,4 +134,8 @@ tests-environment = NIX_REMOTE= $(bash) -e
clean-files += $(d)/common.sh $(d)/config.nix $(d)/ca/config.nix clean-files += $(d)/common.sh $(d)/config.nix $(d)/ca/config.nix
test-deps += tests/common.sh tests/config.nix tests/ca/config.nix tests/plugins/libplugintest.$(SO_EXT) test-deps += tests/common.sh tests/config.nix tests/ca/config.nix
ifeq ($(BUILD_SHARED_LIBS), 1)
test-deps += tests/plugins/libplugintest.$(SO_EXT)
endif

View file

@ -102,3 +102,11 @@ source <(nix print-dev-env -f "$shellDotNix" shellDrv)
[[ ${arr2[1]} = $'\n' ]] [[ ${arr2[1]} = $'\n' ]]
[[ ${arr2[2]} = $'x\ny' ]] [[ ${arr2[2]} = $'x\ny' ]]
[[ $(fun) = blabla ]] [[ $(fun) = blabla ]]
# Test nix-shell with ellipsis and no `inNixShell` argument (for backwards compat with old nixpkgs)
cat >$TEST_ROOT/shell-ellipsis.nix <<EOF
{ system ? "x86_64-linux", ... }@args:
assert (!(args ? inNixShell));
(import $shellDotNix { }).shellDrv
EOF
nix-shell $TEST_ROOT/shell-ellipsis.nix --run "true"

View file

@ -2,6 +2,11 @@ source common.sh
set -o pipefail set -o pipefail
if [[ $BUILD_SHARED_LIBS != 1 ]]; then
echo "plugins are not supported"
exit 99
fi
res=$(nix --option setting-set true --option plugin-files $PWD/plugins/libplugintest* eval --expr builtins.anotherNull) res=$(nix --option setting-set true --option plugin-files $PWD/plugins/libplugintest* eval --expr builtins.anotherNull)
[ "$res"x = "nullx" ] [ "$res"x = "nullx" ]

View file

@ -30,3 +30,5 @@ nix eval --store dummy:// --write-to $TEST_ROOT/eval-out --expr '{ x = "foo" + "
rm -rf $TEST_ROOT/eval-out rm -rf $TEST_ROOT/eval-out
(! nix eval --store dummy:// --write-to $TEST_ROOT/eval-out --expr '{ "." = "bla"; }') (! nix eval --store dummy:// --write-to $TEST_ROOT/eval-out --expr '{ "." = "bla"; }')
(! nix eval --expr '~/foo')

View file

@ -55,15 +55,17 @@ testRepl
testRepl --store "$TEST_ROOT/store?real=$NIX_STORE_DIR" testRepl --store "$TEST_ROOT/store?real=$NIX_STORE_DIR"
testReplResponse () { testReplResponse () {
local response="$(nix repl <<< "$1")" local commands="$1"; shift
echo "$response" | grep -qs "$2" \ local expectedResponse="$1"; shift
local response="$(nix repl "$@" <<< "$commands")"
echo "$response" | grep -qs "$expectedResponse" \
|| fail "repl command set: || fail "repl command set:
$1 $commands
does not respond with: does not respond with:
$2 $expectedResponse
but with: but with:
@ -76,3 +78,48 @@ testReplResponse '
:a { a = "2"; } :a { a = "2"; }
"result: ${a}" "result: ${a}"
' "result: 2" ' "result: 2"
testReplResponse '
drvPath
' '".*-simple.drv"' \
$testDir/simple.nix
testReplResponse '
drvPath
' '".*-simple.drv"' \
--file $testDir/simple.nix --experimental-features 'ca-derivations'
testReplResponse '
drvPath
' '".*-simple.drv"' \
--file $testDir/simple.nix --extra-experimental-features 'repl-flake ca-derivations'
mkdir -p flake && cat <<EOF > flake/flake.nix
{
outputs = { self }: {
foo = 1;
bar.baz = 2;
changingThing = "beforeChange";
};
}
EOF
testReplResponse '
foo + baz
' "3" \
./flake ./flake\#bar --experimental-features 'flakes repl-flake'
# Test the `:reload` mechansim with flakes:
# - Eval `./flake#changingThing`
# - Modify the flake
# - Re-eval it
# - Check that the result has changed
replResult=$( (
echo "changingThing"
sleep 1 # Leave the repl the time to eval 'foo'
sed -i 's/beforeChange/afterChange/' flake/flake.nix
echo ":reload"
echo "changingThing"
) | nix repl ./flake --experimental-features 'flakes repl-flake')
echo "$replResult" | grep -qs beforeChange
echo "$replResult" | grep -qs afterChange

View file

@ -43,3 +43,4 @@ e=$'\x1b' # grep doesn't support \e, \033 or even \x1b
(( $(nix search -f search.nix foo --exclude 'foo|bar' | grep -Ec 'foo|bar') == 0 )) (( $(nix search -f search.nix foo --exclude 'foo|bar' | grep -Ec 'foo|bar') == 0 ))
(( $(nix search -f search.nix foo -e foo --exclude bar | grep -Ec 'foo|bar') == 0 )) (( $(nix search -f search.nix foo -e foo --exclude bar | grep -Ec 'foo|bar') == 0 ))
[[ $(nix search -f search.nix -e bar --json | jq -c 'keys') == '["foo","hello"]' ]]