Merge branch 'master' into structured-attrs-shell

Conflicts:
        src/nix/develop.cc
        src/nix/get-env.sh
        tests/shell.nix
This commit is contained in:
Maximilian Bosch 2021-07-12 15:46:41 +02:00
commit 04cd2da84c
No known key found for this signature in database
GPG key ID: 091DBF4D1FC46B8E
104 changed files with 1644 additions and 539 deletions

View file

@ -20,8 +20,7 @@ jobs:
name: '${{ env.CACHIX_NAME }}' name: '${{ env.CACHIX_NAME }}'
signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}'
authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
#- run: nix flake check - run: nix-build -A checks.$(nix-instantiate --eval -E '(builtins.currentSystem)')
- run: nix-build -A checks.$(if [[ `uname` = Linux ]]; then echo x86_64-linux; else echo x86_64-darwin; fi)
check_cachix: check_cachix:
name: Cachix secret present for installer tests name: Cachix secret present for installer tests
runs-on: ubuntu-latest runs-on: ubuntu-latest

1
.gitignore vendored
View file

@ -76,7 +76,6 @@ perl/Makefile.config
# /tests/ # /tests/
/tests/test-tmp /tests/test-tmp
/tests/common.sh /tests/common.sh
/tests/dummy
/tests/result* /tests/result*
/tests/restricted-innocent /tests/restricted-innocent
/tests/shell /tests/shell

View file

@ -12,6 +12,7 @@ makefiles = \
src/resolve-system-dependencies/local.mk \ src/resolve-system-dependencies/local.mk \
scripts/local.mk \ scripts/local.mk \
misc/bash/local.mk \ misc/bash/local.mk \
misc/fish/local.mk \
misc/zsh/local.mk \ misc/zsh/local.mk \
misc/systemd/local.mk \ misc/systemd/local.mk \
misc/launchd/local.mk \ misc/launchd/local.mk \

View file

@ -1,3 +1,4 @@
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@

View file

@ -0,0 +1,42 @@
diff --git a/pthread_stop_world.c b/pthread_stop_world.c
index 1cee6a0b..46c3acd9 100644
--- a/pthread_stop_world.c
+++ b/pthread_stop_world.c
@@ -674,6 +674,8 @@ GC_INNER void GC_push_all_stacks(void)
struct GC_traced_stack_sect_s *traced_stack_sect;
pthread_t self = pthread_self();
word total_size = 0;
+ size_t stack_limit;
+ pthread_attr_t pattr;
if (!EXPECT(GC_thr_initialized, TRUE))
GC_thr_init();
@@ -723,6 +725,28 @@ GC_INNER void GC_push_all_stacks(void)
hi = p->altstack + p->altstack_size;
/* FIXME: Need to scan the normal stack too, but how ? */
/* FIXME: Assume stack grows down */
+ } else {
+ if (pthread_getattr_np(p->id, &pattr)) {
+ ABORT("GC_push_all_stacks: pthread_getattr_np failed!");
+ }
+ if (pthread_attr_getstacksize(&pattr, &stack_limit)) {
+ ABORT("GC_push_all_stacks: pthread_attr_getstacksize failed!");
+ }
+ // When a thread goes into a coroutine, we lose its original sp until
+ // control flow returns to the thread.
+ // While in the coroutine, the sp points outside the thread stack,
+ // so we can detect this and push the entire thread stack instead,
+ // as an approximation.
+ // We assume that the coroutine has similarly added its entire stack.
+ // This could be made accurate by cooperating with the application
+ // via new functions and/or callbacks.
+ #ifndef STACK_GROWS_UP
+ if (lo >= hi || lo < hi - stack_limit) { // sp outside stack
+ lo = hi - stack_limit;
+ }
+ #else
+ #error "STACK_GROWS_UP not supported in boost_coroutine2 (as of june 2021), so we don't support it in Nix."
+ #endif
}
GC_push_all_stack_sections(lo, hi, traced_stack_sect);
# ifdef STACK_GROWS_UP

View file

@ -6,9 +6,11 @@ builtins:
concatStrings (map concatStrings (map
(name: (name:
let builtin = builtins.${name}; in let builtin = builtins.${name}; in
" - `builtins.${name}` " + concatStringsSep " " (map (s: "*${s}*") builtin.args) "<dt><code>${name} "
+ " \n\n" + concatStringsSep " " (map (s: "<var>${s}</var>") builtin.args)
+ concatStrings (map (s: " ${s}\n") (splitLines builtin.doc)) + "\n\n" + "</code></dt>"
+ "<dd>\n\n"
+ builtin.doc
+ "\n\n</dd>"
) )
(attrNames builtins)) (attrNames builtins))

View file

@ -64,6 +64,7 @@ $(d)/conf-file.json: $(bindir)/nix
$(d)/src/expressions/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/expressions/builtins-prefix.md $(bindir)/nix $(d)/src/expressions/builtins.md: $(d)/builtins.json $(d)/generate-builtins.nix $(d)/src/expressions/builtins-prefix.md $(bindir)/nix
@cat doc/manual/src/expressions/builtins-prefix.md > $@.tmp @cat doc/manual/src/expressions/builtins-prefix.md > $@.tmp
$(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp $(trace-gen) $(nix-eval) --expr 'import doc/manual/generate-builtins.nix (builtins.fromJSON (builtins.readFile $<))' >> $@.tmp
@cat doc/manual/src/expressions/builtins-suffix.md >> $@.tmp
@mv $@.tmp $@ @mv $@.tmp $@
$(d)/builtins.json: $(bindir)/nix $(d)/builtins.json: $(bindir)/nix

View file

@ -28,17 +28,21 @@ Most Nix commands interpret the following environment variables:
consist of a single top-level directory. For example, setting consist of a single top-level directory. For example, setting
`NIX_PATH` to `NIX_PATH` to
nixpkgs=https://github.com/NixOS/nixpkgs/archive/nixos-15.09.tar.gz nixpkgs=https://github.com/NixOS/nixpkgs/archive/master.tar.gz
tells Nix to download the latest revision in the Nixpkgs/NixOS 15.09 tells Nix to download and use the current contents of the
channel. `master` branch in the `nixpkgs` repository.
A following shorthand can be used to refer to the official channels: The URLs of the tarballs from the official nixos.org channels (see
[the manual for `nix-channel`](nix-channel.md)) can be abbreviated
as `channel:<channel-name>`. For instance, the following two
values of `NIX_PATH` are equivalent:
nixpkgs=channel:nixos-15.09 nixpkgs=channel:nixos-21.05
nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz
The search path can be extended using the `-I` option, which takes The Nix search path can also be extended using the `-I` option to
precedence over `NIX_PATH`. many Nix commands, which takes precedence over `NIX_PATH`.
- `NIX_IGNORE_SYMLINK_STORE`\ - `NIX_IGNORE_SYMLINK_STORE`\
Normally, the Nix store directory (typically `/nix/store`) is not Normally, the Nix store directory (typically `/nix/store`) is not

View file

@ -9,7 +9,8 @@ scope. Instead, you can access them through the `builtins` built-in
value, which is a set that contains all built-in functions and values. value, which is a set that contains all built-in functions and values.
For instance, `derivation` is also available as `builtins.derivation`. For instance, `derivation` is also available as `builtins.derivation`.
- `derivation` *attrs*; `builtins.derivation` *attrs*\ <dl>
<dt><code>derivation <var>attrs</var></code>;
`derivation` is described in [its own section](derivations.md). <code>builtins.derivation <var>attrs</var></code></dt>
<dd><p><var>derivation</var> in described in
<a href="derivations.md">its own section</a>.</p></dd>

View file

@ -0,0 +1 @@
</dl>

View file

@ -19,11 +19,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1622593737, "lastModified": 1624862269,
"narHash": "sha256-9loxFJg85AbzJrSkU4pE/divZ1+zOxDy2FSjlrufCB8=", "narHash": "sha256-JFcsh2+7QtfKdJFoPibLFPLgIW6Ycnv8Bts9a7RYme0=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "bb8a5e54845012ed1375ffd5f317d2fdf434b20e", "rev": "f77036342e2b690c61c97202bf48f2ce13acc022",
"type": "github" "type": "github"
}, },
"original": { "original": {

235
flake.nix
View file

@ -20,6 +20,8 @@
linuxSystems = linux64BitSystems ++ [ "i686-linux" ]; linuxSystems = linux64BitSystems ++ [ "i686-linux" ];
systems = linuxSystems ++ [ "x86_64-darwin" "aarch64-darwin" ]; systems = linuxSystems ++ [ "x86_64-darwin" "aarch64-darwin" ];
crossSystems = [ "armv6l-linux" "armv7l-linux" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
# Memoize nixpkgs for different platforms for efficiency. # Memoize nixpkgs for different platforms for efficiency.
@ -79,7 +81,7 @@
buildPackages.mercurial buildPackages.mercurial
buildPackages.jq buildPackages.jq
] ]
++ lib.optionals stdenv.isLinux [(pkgs.util-linuxMinimal or pkgs.utillinuxMinimal)]; ++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)];
buildDeps = buildDeps =
[ curl [ curl
@ -93,7 +95,7 @@
] ]
++ lib.optionals stdenv.isLinux [libseccomp] ++ lib.optionals stdenv.isLinux [libseccomp]
++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
++ lib.optional stdenv.isx86_64 libcpuid; ++ lib.optional stdenv.hostPlatform.isx86_64 libcpuid;
awsDeps = lib.optional (stdenv.isLinux || stdenv.isDarwin) awsDeps = lib.optional (stdenv.isLinux || stdenv.isDarwin)
(aws-sdk-cpp.override { (aws-sdk-cpp.override {
@ -102,7 +104,13 @@
}); });
propagatedDeps = propagatedDeps =
[ (boehmgc.override { enableLargeConfig = true; }) [ ((boehmgc.override {
enableLargeConfig = true;
}).overrideAttrs(o: {
patches = (o.patches or []) ++ [
./boehmgc-coroutine-sp-fallback.diff
];
}))
]; ];
perlDeps = perlDeps =
@ -133,10 +141,11 @@
substitute ${./scripts/install.in} $out/install \ substitute ${./scripts/install.in} $out/install \
${pkgs.lib.concatMapStrings ${pkgs.lib.concatMapStrings
(system: (system: let
'' \ tarball = if builtins.elem system crossSystems then self.hydraJobs.binaryTarballCross.x86_64-linux.${system} else self.hydraJobs.binaryTarball.${system};
--replace '@tarballHash_${system}@' $(nix --experimental-features nix-command hash-file --base16 --type sha256 ${self.hydraJobs.binaryTarball.${system}}/*.tar.xz) \ in '' \
--replace '@tarballPath_${system}@' $(tarballPath ${self.hydraJobs.binaryTarball.${system}}/*.tar.xz) \ --replace '@tarballHash_${system}@' $(nix --experimental-features nix-command hash-file --base16 --type sha256 ${tarball}/*.tar.xz) \
--replace '@tarballPath_${system}@' $(tarballPath ${tarball}/*.tar.xz) \
'' ''
) )
systems systems
@ -174,6 +183,77 @@
}; };
binaryTarball = buildPackages: nix: pkgs: let
inherit (pkgs) cacert;
installerClosureInfo = buildPackages.closureInfo { rootPaths = [ nix cacert ]; };
in
buildPackages.runCommand "nix-binary-tarball-${version}"
{ #nativeBuildInputs = lib.optional (system != "aarch64-linux") shellcheck;
meta.description = "Distribution-independent Nix bootstrap binaries for ${pkgs.system}";
}
''
cp ${installerClosureInfo}/registration $TMPDIR/reginfo
cp ${./scripts/create-darwin-volume.sh} $TMPDIR/create-darwin-volume.sh
substitute ${./scripts/install-nix-from-closure.sh} $TMPDIR/install \
--subst-var-by nix ${nix} \
--subst-var-by cacert ${cacert}
substitute ${./scripts/install-darwin-multi-user.sh} $TMPDIR/install-darwin-multi-user.sh \
--subst-var-by nix ${nix} \
--subst-var-by cacert ${cacert}
substitute ${./scripts/install-systemd-multi-user.sh} $TMPDIR/install-systemd-multi-user.sh \
--subst-var-by nix ${nix} \
--subst-var-by cacert ${cacert}
substitute ${./scripts/install-multi-user.sh} $TMPDIR/install-multi-user \
--subst-var-by nix ${nix} \
--subst-var-by cacert ${cacert}
if type -p shellcheck; then
# SC1090: Don't worry about not being able to find
# $nix/etc/profile.d/nix.sh
shellcheck --exclude SC1090 $TMPDIR/install
shellcheck $TMPDIR/create-darwin-volume.sh
shellcheck $TMPDIR/install-darwin-multi-user.sh
shellcheck $TMPDIR/install-systemd-multi-user.sh
# SC1091: Don't panic about not being able to source
# /etc/profile
# SC2002: Ignore "useless cat" "error", when loading
# .reginfo, as the cat is a much cleaner
# implementation, even though it is "useless"
# SC2116: Allow ROOT_HOME=$(echo ~root) for resolving
# root's home directory
shellcheck --external-sources \
--exclude SC1091,SC2002,SC2116 $TMPDIR/install-multi-user
fi
chmod +x $TMPDIR/install
chmod +x $TMPDIR/create-darwin-volume.sh
chmod +x $TMPDIR/install-darwin-multi-user.sh
chmod +x $TMPDIR/install-systemd-multi-user.sh
chmod +x $TMPDIR/install-multi-user
dir=nix-${version}-${pkgs.system}
fn=$out/$dir.tar.xz
mkdir -p $out/nix-support
echo "file binary-dist $fn" >> $out/nix-support/hydra-build-products
tar cvfJ $fn \
--owner=0 --group=0 --mode=u+rw,uga+r \
--absolute-names \
--hard-dereference \
--transform "s,$TMPDIR/install,$dir/install," \
--transform "s,$TMPDIR/create-darwin-volume.sh,$dir/create-darwin-volume.sh," \
--transform "s,$TMPDIR/reginfo,$dir/.reginfo," \
--transform "s,$NIX_STORE,$dir/store,S" \
$TMPDIR/install \
$TMPDIR/create-darwin-volume.sh \
$TMPDIR/install-darwin-multi-user.sh \
$TMPDIR/install-systemd-multi-user.sh \
$TMPDIR/install-multi-user \
$TMPDIR/reginfo \
$(cat ${installerClosureInfo}/store-paths)
'';
in { in {
# A Nixpkgs overlay that overrides the 'nix' and # A Nixpkgs overlay that overrides the 'nix' and
@ -285,7 +365,7 @@
outputs = [ "out" "bin" "dev" ]; outputs = [ "out" "bin" "dev" ];
nativeBuildInputs = [ which ]; nativeBuildInputs = [ buildPackages.which ];
configurePhase = '' configurePhase = ''
${if (stdenv.isDarwin && stdenv.isAarch64) then "echo \"HAVE_SANDBOX_INIT=false\" > configure.local" else ""} ${if (stdenv.isDarwin && stdenv.isAarch64) then "echo \"HAVE_SANDBOX_INIT=false\" > configure.local" else ""}
@ -304,92 +384,33 @@
buildStatic = nixpkgs.lib.genAttrs linux64BitSystems (system: self.packages.${system}.nix-static); buildStatic = nixpkgs.lib.genAttrs linux64BitSystems (system: self.packages.${system}.nix-static);
buildCross = nixpkgs.lib.genAttrs crossSystems (crossSystem:
nixpkgs.lib.genAttrs ["x86_64-linux"] (system: self.packages.${system}."nix-${crossSystem}"));
# Perl bindings for various platforms. # Perl bindings for various platforms.
perlBindings = nixpkgs.lib.genAttrs systems (system: self.packages.${system}.nix.perl-bindings); perlBindings = nixpkgs.lib.genAttrs systems (system: self.packages.${system}.nix.perl-bindings);
# Binary tarball for various platforms, containing a Nix store # Binary tarball for various platforms, containing a Nix store
# with the closure of 'nix' package, and the second half of # with the closure of 'nix' package, and the second half of
# the installation script. # the installation script.
binaryTarball = nixpkgs.lib.genAttrs systems (system: binaryTarball = nixpkgs.lib.genAttrs systems (system: binaryTarball nixpkgsFor.${system} nixpkgsFor.${system}.nix nixpkgsFor.${system});
with nixpkgsFor.${system}; binaryTarballCross = nixpkgs.lib.genAttrs ["x86_64-linux"] (system: builtins.listToAttrs (map (crossSystem: {
name = crossSystem;
let value = let
installerClosureInfo = closureInfo { rootPaths = [ nix cacert ]; }; nixpkgsCross = import nixpkgs {
in inherit system crossSystem;
overlays = [ self.overlay ];
runCommand "nix-binary-tarball-${version}" };
{ #nativeBuildInputs = lib.optional (system != "aarch64-linux") shellcheck; in binaryTarball nixpkgsFor.${system} self.packages.${system}."nix-${crossSystem}" nixpkgsCross;
meta.description = "Distribution-independent Nix bootstrap binaries for ${system}"; }) crossSystems));
}
''
cp ${installerClosureInfo}/registration $TMPDIR/reginfo
cp ${./scripts/create-darwin-volume.sh} $TMPDIR/create-darwin-volume.sh
substitute ${./scripts/install-nix-from-closure.sh} $TMPDIR/install \
--subst-var-by nix ${nix} \
--subst-var-by cacert ${cacert}
substitute ${./scripts/install-darwin-multi-user.sh} $TMPDIR/install-darwin-multi-user.sh \
--subst-var-by nix ${nix} \
--subst-var-by cacert ${cacert}
substitute ${./scripts/install-systemd-multi-user.sh} $TMPDIR/install-systemd-multi-user.sh \
--subst-var-by nix ${nix} \
--subst-var-by cacert ${cacert}
substitute ${./scripts/install-multi-user.sh} $TMPDIR/install-multi-user \
--subst-var-by nix ${nix} \
--subst-var-by cacert ${cacert}
if type -p shellcheck; then
# SC1090: Don't worry about not being able to find
# $nix/etc/profile.d/nix.sh
shellcheck --exclude SC1090 $TMPDIR/install
shellcheck $TMPDIR/create-darwin-volume.sh
shellcheck $TMPDIR/install-darwin-multi-user.sh
shellcheck $TMPDIR/install-systemd-multi-user.sh
# SC1091: Don't panic about not being able to source
# /etc/profile
# SC2002: Ignore "useless cat" "error", when loading
# .reginfo, as the cat is a much cleaner
# implementation, even though it is "useless"
# SC2116: Allow ROOT_HOME=$(echo ~root) for resolving
# root's home directory
shellcheck --external-sources \
--exclude SC1091,SC2002,SC2116 $TMPDIR/install-multi-user
fi
chmod +x $TMPDIR/install
chmod +x $TMPDIR/create-darwin-volume.sh
chmod +x $TMPDIR/install-darwin-multi-user.sh
chmod +x $TMPDIR/install-systemd-multi-user.sh
chmod +x $TMPDIR/install-multi-user
dir=nix-${version}-${system}
fn=$out/$dir.tar.xz
mkdir -p $out/nix-support
echo "file binary-dist $fn" >> $out/nix-support/hydra-build-products
tar cvfJ $fn \
--owner=0 --group=0 --mode=u+rw,uga+r \
--absolute-names \
--hard-dereference \
--transform "s,$TMPDIR/install,$dir/install," \
--transform "s,$TMPDIR/create-darwin-volume.sh,$dir/create-darwin-volume.sh," \
--transform "s,$TMPDIR/reginfo,$dir/.reginfo," \
--transform "s,$NIX_STORE,$dir/store,S" \
$TMPDIR/install \
$TMPDIR/create-darwin-volume.sh \
$TMPDIR/install-darwin-multi-user.sh \
$TMPDIR/install-systemd-multi-user.sh \
$TMPDIR/install-multi-user \
$TMPDIR/reginfo \
$(cat ${installerClosureInfo}/store-paths)
'');
# The first half of the installation script. This is uploaded # The first half of the installation script. This is uploaded
# to https://nixos.org/nix/install. It downloads the binary # to https://nixos.org/nix/install. It downloads the binary
# tarball for the user's system and calls the second half of the # tarball for the user's system and calls the second half of the
# installation script. # installation script.
installerScript = installScriptFor [ "x86_64-linux" "i686-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ]; installerScript = installScriptFor [ "x86_64-linux" "i686-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" "armv6l-linux" "armv7l-linux" ];
installerScriptForGHA = installScriptFor [ "x86_64-linux" "x86_64-darwin" ]; installerScriptForGHA = installScriptFor [ "x86_64-linux" "x86_64-darwin" "armv6l-linux" "armv7l-linux"];
# Line coverage analysis. # Line coverage analysis.
coverage = coverage =
@ -483,11 +504,14 @@
# `NIX_DAEMON_SOCKET_PATH` which is required for the tests to work # `NIX_DAEMON_SOCKET_PATH` which is required for the tests to work
# againstLatestStable = testNixVersions pkgs pkgs.nix pkgs.nixStable; # againstLatestStable = testNixVersions pkgs pkgs.nix pkgs.nixStable;
} "touch $out"; } "touch $out";
}); } // (if system == "x86_64-linux" then (builtins.listToAttrs (map (crossSystem: {
name = "binaryTarball-${crossSystem}";
value = self.hydraJobs.binaryTarballCross.${system}.${crossSystem};
}) crossSystems)) else {}));
packages = forAllSystems (system: { packages = forAllSystems (system: {
inherit (nixpkgsFor.${system}) nix; inherit (nixpkgsFor.${system}) nix;
} // 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 nixpkgs; nixpkgs.stdenv.mkDerivation {
@ -525,8 +549,49 @@
stripAllList = ["bin"]; stripAllList = ["bin"];
strictDeps = true; strictDeps = true;
hardeningDisable = [ "pie" ];
}; };
}); } // builtins.listToAttrs (map (crossSystem: {
name = "nix-${crossSystem}";
value = let
nixpkgsCross = import nixpkgs {
inherit system crossSystem;
overlays = [ self.overlay ];
};
in with commonDeps nixpkgsCross; nixpkgsCross.stdenv.mkDerivation {
name = "nix-${version}";
src = self;
VERSION_SUFFIX = versionSuffix;
outputs = [ "out" "dev" "doc" ];
nativeBuildInputs = nativeBuildDeps;
buildInputs = buildDeps ++ propagatedDeps;
configureFlags = [ "--sysconfdir=/etc" "--disable-doc-gen" ];
enableParallelBuilding = true;
makeFlags = "profiledir=$(out)/etc/profile.d";
doCheck = true;
installFlags = "sysconfdir=$(out)/etc";
postInstall = ''
mkdir -p $doc/nix-support
echo "doc manual $doc/share/doc/nix/manual" >> $doc/nix-support/hydra-build-products
mkdir -p $out/nix-support
echo "file binary-dist $out/bin/nix" >> $out/nix-support/hydra-build-products
'';
doInstallCheck = true;
installCheckFlags = "sysconfdir=$(out)/etc";
};
}) crossSystems)));
defaultPackage = forAllSystems (system: self.packages.${system}.nix); defaultPackage = forAllSystems (system: self.packages.${system}.nix);

View file

@ -110,6 +110,9 @@ downloadFile("binaryTarball.i686-linux", "1");
downloadFile("binaryTarball.x86_64-linux", "1"); downloadFile("binaryTarball.x86_64-linux", "1");
downloadFile("binaryTarball.aarch64-linux", "1"); downloadFile("binaryTarball.aarch64-linux", "1");
downloadFile("binaryTarball.x86_64-darwin", "1"); downloadFile("binaryTarball.x86_64-darwin", "1");
downloadFile("binaryTarball.aarch64-darwin", "1");
downloadFile("binaryTarballCross.x86_64-linux.armv6l-linux", "1");
downloadFile("binaryTarballCross.x86_64-linux.armv7l-linux", "1");
downloadFile("installerScript", "1"); downloadFile("installerScript", "1");
for my $fn (glob "$tmpDir/*") { for my $fn (glob "$tmpDir/*") {
@ -153,6 +156,7 @@ write_file("$nixpkgsDir/nixos/modules/installer/tools/nix-fallback-paths.nix",
" i686-linux = \"" . getStorePath("build.i686-linux") . "\";\n" . " i686-linux = \"" . getStorePath("build.i686-linux") . "\";\n" .
" aarch64-linux = \"" . getStorePath("build.aarch64-linux") . "\";\n" . " aarch64-linux = \"" . getStorePath("build.aarch64-linux") . "\";\n" .
" x86_64-darwin = \"" . getStorePath("build.x86_64-darwin") . "\";\n" . " x86_64-darwin = \"" . getStorePath("build.x86_64-darwin") . "\";\n" .
" aarch64-darwin = \"" . getStorePath("build.aarch64-darwin") . "\";\n" .
"}\n"); "}\n");
system("cd $nixpkgsDir && git commit -a -m 'nix-fallback-paths.nix: Update to $version'") == 0 or die; system("cd $nixpkgsDir && git commit -a -m 'nix-fallback-paths.nix: Update to $version'") == 0 or die;

37
misc/fish/completion.fish Normal file
View file

@ -0,0 +1,37 @@
function _nix_complete
# Get the current command up to a cursor.
# - Behaves correctly even with pipes and nested in commands like env.
# - TODO: Returns the command verbatim (does not interpolate variables).
# That might not be optimal for arguments like -f.
set -l nix_args (commandline --current-process --tokenize --cut-at-cursor)
# --cut-at-cursor with --tokenize removes the current token so we need to add it separately.
# https://github.com/fish-shell/fish-shell/issues/7375
# Can be an empty string.
set -l current_token (commandline --current-token --cut-at-cursor)
# Nix wants the index of the argv item to complete but the $nix_args variable
# also contains the program name (argv[0]) so we would need to subtract 1.
# But the variable also misses the current token so it cancels out.
set -l nix_arg_to_complete (count $nix_args)
env NIX_GET_COMPLETIONS=$nix_arg_to_complete $nix_args $current_token
end
function _nix_accepts_files
set -l response (_nix_complete)
# First line is either filenames or no-filenames.
test $response[1] = 'filenames'
end
function _nix
set -l response (_nix_complete)
# Skip the first line since it handled by _nix_accepts_files.
# Tail lines each contain a command followed by a tab character and, optionally, a description.
# This is also the format fish expects.
string collect -- $response[2..-1]
end
# Disable file path completion if paths do not belong in the current context.
complete --command nix --condition 'not _nix_accepts_files' --no-files
complete --command nix --arguments '(_nix)'

1
misc/fish/local.mk Normal file
View file

@ -0,0 +1 @@
$(eval $(call install-file-as, $(d)/completion.fish, $(datarootdir)/fish/vendor_completions.d/nix.fish, 0644))

View file

@ -1,4 +1,4 @@
ifeq ($(OS), Darwin) ifdef HOST_DARWIN
$(eval $(call install-data-in, $(d)/org.nixos.nix-daemon.plist, $(prefix)/Library/LaunchDaemons)) $(eval $(call install-data-in, $(d)/org.nixos.nix-daemon.plist, $(prefix)/Library/LaunchDaemons))

View file

@ -1,4 +1,4 @@
ifeq ($(OS), Linux) ifdef HOST_LINUX
$(foreach n, nix-daemon.socket nix-daemon.service, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644))) $(foreach n, nix-daemon.socket nix-daemon.service, $(eval $(call install-file-in, $(d)/$(n), $(prefix)/lib/systemd/system, 0644)))

View file

@ -1,4 +1,4 @@
ifeq ($(OS), Linux) ifdef HOST_LINUX
$(foreach n, nix-daemon.conf, $(eval $(call install-file-in, $(d)/$(n), $(sysconfdir)/init, 0644))) $(foreach n, nix-daemon.conf, $(eval $(call install-file-in, $(d)/$(n), $(sysconfdir)/init, 0644)))

View file

@ -1,3 +1,5 @@
#compdef nix
function _nix() { function _nix() {
local ifs_bk="$IFS" local ifs_bk="$IFS"
local input=("${(Q)words[@]}") local input=("${(Q)words[@]}")
@ -18,4 +20,4 @@ function _nix() {
_describe 'nix' suggestions _describe 'nix' suggestions
} }
compdef _nix nix _nix "$@"

View file

@ -10,8 +10,25 @@ bin-scripts :=
noinst-scripts := noinst-scripts :=
man-pages := man-pages :=
install-tests := install-tests :=
OS = $(shell uname -s)
ifdef HOST_OS
HOST_KERNEL = $(firstword $(subst -, ,$(HOST_OS)))
ifeq ($(HOST_KERNEL), cygwin)
HOST_CYGWIN = 1
endif
ifeq ($(patsubst darwin%,,$(HOST_KERNEL)),)
HOST_DARWIN = 1
endif
ifeq ($(patsubst freebsd%,,$(HOST_KERNEL)),)
HOST_FREEBSD = 1
endif
ifeq ($(HOST_KERNEL), linux)
HOST_LINUX = 1
endif
ifeq ($(patsubst solaris%,,$(HOST_KERNEL)),)
HOST_SOLARIS = 1
endif
endif
# Hack to define a literal space. # Hack to define a literal space.
space := space :=
@ -50,16 +67,16 @@ endif
BUILD_SHARED_LIBS ?= 1 BUILD_SHARED_LIBS ?= 1
ifeq ($(BUILD_SHARED_LIBS), 1) ifeq ($(BUILD_SHARED_LIBS), 1)
ifeq (CYGWIN,$(findstring CYGWIN,$(OS))) ifdef HOST_CYGWIN
GLOBAL_CFLAGS += -U__STRICT_ANSI__ -D_GNU_SOURCE GLOBAL_CFLAGS += -U__STRICT_ANSI__ -D_GNU_SOURCE
GLOBAL_CXXFLAGS += -U__STRICT_ANSI__ -D_GNU_SOURCE GLOBAL_CXXFLAGS += -U__STRICT_ANSI__ -D_GNU_SOURCE
else else
GLOBAL_CFLAGS += -fPIC GLOBAL_CFLAGS += -fPIC
GLOBAL_CXXFLAGS += -fPIC GLOBAL_CXXFLAGS += -fPIC
endif endif
ifneq ($(OS), Darwin) ifndef HOST_DARWIN
ifneq ($(OS), SunOS) ifndef HOST_SOLARIS
ifneq ($(OS), FreeBSD) ifndef HOST_FREEBSD
GLOBAL_LDFLAGS += -Wl,--no-copy-dt-needed-entries GLOBAL_LDFLAGS += -Wl,--no-copy-dt-needed-entries
endif endif
endif endif

View file

@ -1,9 +1,9 @@
libs-list := libs-list :=
ifeq ($(OS), Darwin) ifdef HOST_DARWIN
SO_EXT = dylib SO_EXT = dylib
else else
ifeq (CYGWIN,$(findstring CYGWIN,$(OS))) ifdef HOST_CYGWIN
SO_EXT = dll SO_EXT = dll
else else
SO_EXT = so SO_EXT = so
@ -59,7 +59,7 @@ define build-library
$(1)_OBJS := $$(addprefix $(buildprefix), $$(addsuffix .o, $$(basename $$(_srcs)))) $(1)_OBJS := $$(addprefix $(buildprefix), $$(addsuffix .o, $$(basename $$(_srcs))))
_libs := $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_PATH)) _libs := $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_PATH))
ifeq (CYGWIN,$(findstring CYGWIN,$(OS))) ifdef HOST_CYGWIN
$(1)_INSTALL_DIR ?= $$(bindir) $(1)_INSTALL_DIR ?= $$(bindir)
else else
$(1)_INSTALL_DIR ?= $$(libdir) $(1)_INSTALL_DIR ?= $$(libdir)
@ -73,18 +73,18 @@ define build-library
ifeq ($(BUILD_SHARED_LIBS), 1) ifeq ($(BUILD_SHARED_LIBS), 1)
ifdef $(1)_ALLOW_UNDEFINED ifdef $(1)_ALLOW_UNDEFINED
ifeq ($(OS), Darwin) ifdef HOST_DARWIN
$(1)_LDFLAGS += -undefined suppress -flat_namespace $(1)_LDFLAGS += -undefined suppress -flat_namespace
endif endif
else else
ifneq ($(OS), Darwin) ifndef HOST_DARWIN
ifneq (CYGWIN,$(findstring CYGWIN,$(OS))) ifndef HOST_CYGWIN
$(1)_LDFLAGS += -Wl,-z,defs $(1)_LDFLAGS += -Wl,-z,defs
endif endif
endif endif
endif endif
ifneq ($(OS), Darwin) ifndef HOST_DARWIN
$(1)_LDFLAGS += -Wl,-soname=$$($(1)_NAME).$(SO_EXT) $(1)_LDFLAGS += -Wl,-soname=$$($(1)_NAME).$(SO_EXT)
endif endif
@ -93,7 +93,7 @@ define build-library
$$($(1)_PATH): $$($(1)_OBJS) $$(_libs) | $$(_d)/ $$($(1)_PATH): $$($(1)_OBJS) $$(_libs) | $$(_d)/
$$(trace-ld) $(CXX) -o $$(abspath $$@) -shared $$(LDFLAGS) $$(GLOBAL_LDFLAGS) $$($(1)_OBJS) $$($(1)_LDFLAGS) $$($(1)_LDFLAGS_PROPAGATED) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE)) $$($(1)_LDFLAGS_UNINSTALLED) $$(trace-ld) $(CXX) -o $$(abspath $$@) -shared $$(LDFLAGS) $$(GLOBAL_LDFLAGS) $$($(1)_OBJS) $$($(1)_LDFLAGS) $$($(1)_LDFLAGS_PROPAGATED) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE)) $$($(1)_LDFLAGS_UNINSTALLED)
ifneq ($(OS), Darwin) ifndef HOST_DARWIN
$(1)_LDFLAGS_USE += -Wl,-rpath,$$(abspath $$(_d)) $(1)_LDFLAGS_USE += -Wl,-rpath,$$(abspath $$(_d))
endif endif
$(1)_LDFLAGS_USE += -L$$(_d) -l$$(patsubst lib%,%,$$(strip $$($(1)_NAME))) $(1)_LDFLAGS_USE += -L$$(_d) -l$$(patsubst lib%,%,$$(strip $$($(1)_NAME)))
@ -108,7 +108,7 @@ define build-library
$$(trace-ld) $(CXX) -o $$@ -shared $$(LDFLAGS) $$(GLOBAL_LDFLAGS) $$($(1)_OBJS) $$($(1)_LDFLAGS) $$($(1)_LDFLAGS_PROPAGATED) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE_INSTALLED)) $$(trace-ld) $(CXX) -o $$@ -shared $$(LDFLAGS) $$(GLOBAL_LDFLAGS) $$($(1)_OBJS) $$($(1)_LDFLAGS) $$($(1)_LDFLAGS_PROPAGATED) $$(foreach lib, $$($(1)_LIBS), $$($$(lib)_LDFLAGS_USE_INSTALLED))
$(1)_LDFLAGS_USE_INSTALLED += -L$$(DESTDIR)$$($(1)_INSTALL_DIR) -l$$(patsubst lib%,%,$$(strip $$($(1)_NAME))) $(1)_LDFLAGS_USE_INSTALLED += -L$$(DESTDIR)$$($(1)_INSTALL_DIR) -l$$(patsubst lib%,%,$$(strip $$($(1)_NAME)))
ifneq ($(OS), Darwin) ifndef HOST_DARWIN
ifeq ($(SET_RPATH_TO_LIBS), 1) ifeq ($(SET_RPATH_TO_LIBS), 1)
$(1)_LDFLAGS_USE_INSTALLED += -Wl,-rpath,$$($(1)_INSTALL_DIR) $(1)_LDFLAGS_USE_INSTALLED += -Wl,-rpath,$$($(1)_INSTALL_DIR)
else else

View file

@ -11,12 +11,12 @@ libnixrust_INSTALL_PATH := $(libdir)/libnixrust.$(SO_EXT)
libnixrust_LDFLAGS_USE := -L$(d)/target/$(RUST_DIR) -lnixrust libnixrust_LDFLAGS_USE := -L$(d)/target/$(RUST_DIR) -lnixrust
libnixrust_LDFLAGS_USE_INSTALLED := -L$(libdir) -lnixrust libnixrust_LDFLAGS_USE_INSTALLED := -L$(libdir) -lnixrust
ifeq ($(OS), Linux) ifdef HOST_LINUX
libnixrust_LDFLAGS_USE += -ldl libnixrust_LDFLAGS_USE += -ldl
libnixrust_LDFLAGS_USE_INSTALLED += -ldl libnixrust_LDFLAGS_USE_INSTALLED += -ldl
endif endif
ifeq ($(OS), Darwin) ifdef HOST_DARWIN
libnixrust_BUILD_FLAGS = NIX_LDFLAGS="-undefined dynamic_lookup" libnixrust_BUILD_FLAGS = NIX_LDFLAGS="-undefined dynamic_lookup"
else else
libnixrust_LDFLAGS_USE += -Wl,-rpath,$(abspath $(d)/target/$(RUST_DIR)) libnixrust_LDFLAGS_USE += -Wl,-rpath,$(abspath $(d)/target/$(RUST_DIR))
@ -31,7 +31,7 @@ $(libnixrust_PATH): $(call rwildcard, $(d)/src, *.rs) $(d)/Cargo.toml
$(libnixrust_INSTALL_PATH): $(libnixrust_PATH) $(libnixrust_INSTALL_PATH): $(libnixrust_PATH)
$(target-gen) cp $^ $@ $(target-gen) cp $^ $@
ifeq ($(OS), Darwin) ifdef HOST_DARWIN
install_name_tool -id $@ $@ install_name_tool -id $@ $@
endif endif
@ -40,7 +40,7 @@ clean: clean-rust
clean-rust: clean-rust:
$(suppress) rm -rfv nix-rust/target $(suppress) rm -rfv nix-rust/target
ifneq ($(OS), Darwin) ifndef HOST_DARWIN
check: rust-tests check: rust-tests
rust-tests: rust-tests:

View file

@ -1,3 +1,4 @@
HOST_OS = @host_os@
CC = @CC@ CC = @CC@
CFLAGS = @CFLAGS@ CFLAGS = @CFLAGS@
CXX = @CXX@ CXX = @CXX@

View file

@ -7,6 +7,8 @@ CXXFLAGS=
AC_PROG_CC AC_PROG_CC
AC_PROG_CXX AC_PROG_CXX
AC_CANONICAL_HOST
# Use 64-bit file system calls so that we can support files > 2 GiB. # Use 64-bit file system calls so that we can support files > 2 GiB.
AC_SYS_LARGEFILE AC_SYS_LARGEFILE

View file

@ -28,7 +28,7 @@ Store_CXXFLAGS = \
Store_LDFLAGS := $(SODIUM_LIBS) $(NIX_LIBS) Store_LDFLAGS := $(SODIUM_LIBS) $(NIX_LIBS)
ifeq (CYGWIN,$(findstring CYGWIN,$(OS))) ifdef HOST_CYGWIN
archlib = $(shell perl -E 'use Config; print $$Config{archlib};') archlib = $(shell perl -E 'use Config; print $$Config{archlib};')
libperl = $(shell perl -E 'use Config; print $$Config{libperl};') libperl = $(shell perl -E 'use Config; print $$Config{libperl};')
Store_LDFLAGS += $(shell find ${archlib} -name ${libperl}) Store_LDFLAGS += $(shell find ${archlib} -name ${libperl})

View file

@ -40,13 +40,23 @@ case "$(uname -s).$(uname -m)" in
path=@tarballPath_aarch64-linux@ path=@tarballPath_aarch64-linux@
system=aarch64-linux system=aarch64-linux
;; ;;
Linux.armv6l_linux)
hash=@tarballHash_armv6l-linux@
path=@tarballPath_armv6l-linux@
system=armv6l-linux
;;
Linux.armv7l_linux)
hash=@tarballHash_armv7l-linux@
path=@tarballPath_armv7l-linux@
system=armv7l-linux
;;
Darwin.x86_64) Darwin.x86_64)
hash=@tarballHash_x86_64-darwin@ hash=@tarballHash_x86_64-darwin@
path=@tarballPath_x86_64-darwin@ path=@tarballPath_x86_64-darwin@
system=x86_64-darwin system=x86_64-darwin
;; ;;
Darwin.arm64|Darwin.aarch64) Darwin.arm64|Darwin.aarch64)
hash=@binaryTarball_aarch64-darwin@ hash=@tarballHash_aarch64-darwin@
path=@tarballPath_aarch64-darwin@ path=@tarballPath_aarch64-darwin@
system=aarch64-darwin system=aarch64-darwin
;; ;;

View file

@ -277,7 +277,16 @@ connected:
auto drv = store->readDerivation(*drvPath); auto drv = store->readDerivation(*drvPath);
auto outputHashes = staticOutputHashes(*store, drv); auto outputHashes = staticOutputHashes(*store, drv);
drv.inputSrcs = store->parseStorePathSet(inputs);
// Hijack the inputs paths of the derivation to include all the paths
// that come from the `inputDrvs` set.
// We dont do that for the derivations whose `inputDrvs` is empty
// because
// 1. Its not needed
// 2. Changing the `inputSrcs` set changes the associated output ids,
// which break CA derivations
if (!drv.inputDrvs.empty())
drv.inputSrcs = store->parseStorePathSet(inputs);
auto result = sshStore->buildDerivation(*drvPath, drv); auto result = sshStore->buildDerivation(*drvPath, drv);

View file

@ -188,7 +188,7 @@ void MixProfile::updateProfile(const BuiltPaths & buildables)
} }
if (result.size() != 1) if (result.size() != 1)
throw Error("'--profile' requires that the arguments produce a single store path, but there are %d", result.size()); throw UsageError("'--profile' requires that the arguments produce a single store path, but there are %d", result.size());
updateProfile(result[0]); updateProfile(result[0]);
} }

View file

@ -171,14 +171,50 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
void SourceExprCommand::completeInstallable(std::string_view prefix) void SourceExprCommand::completeInstallable(std::string_view prefix)
{ {
if (file) return; // FIXME if (file) {
evalSettings.pureEval = false;
auto state = getEvalState();
Expr *e = state->parseExprFromFile(
resolveExprPath(state->checkSourcePath(lookupFileArg(*state, *file)))
);
completeFlakeRefWithFragment( Value root;
getEvalState(), state->eval(e, root);
lockFlags,
getDefaultFlakeAttrPathPrefixes(), auto autoArgs = getAutoArgs(*state);
getDefaultFlakeAttrPaths(),
prefix); std::string prefix_ = std::string(prefix);
auto sep = prefix_.rfind('.');
std::string searchWord;
if (sep != std::string::npos) {
searchWord = prefix_.substr(sep, std::string::npos);
prefix_ = prefix_.substr(0, sep);
} else {
searchWord = prefix_;
prefix_ = "";
}
Value &v1(*findAlongAttrPath(*state, prefix_, *autoArgs, root).first);
state->forceValue(v1);
Value v2;
state->autoCallFunction(*autoArgs, v1, v2);
if (v2.type() == nAttrs) {
for (auto & i : *v2.attrs) {
std::string name = i.name;
if (name.find(searchWord) == 0) {
completions->add(i.name);
}
}
}
} else {
completeFlakeRefWithFragment(
getEvalState(),
lockFlags,
getDefaultFlakeAttrPathPrefixes(),
getDefaultFlakeAttrPaths(),
prefix);
}
} }
void completeFlakeRefWithFragment( void completeFlakeRefWithFragment(
@ -573,10 +609,10 @@ InstallableFlake::getCursors(EvalState & state)
std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
{ {
flake::LockFlags lockFlagsApplyConfig = lockFlags;
lockFlagsApplyConfig.applyNixConfig = true;
if (!_lockedFlake) { if (!_lockedFlake) {
_lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlags)); _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
_lockedFlake->flake.config.apply();
// FIXME: send new config to the daemon.
} }
return _lockedFlake; return _lockedFlake;
} }

View file

@ -19,7 +19,7 @@ static Strings parseAttrPath(std::string_view s)
++i; ++i;
while (1) { while (1) {
if (i == s.end()) if (i == s.end())
throw Error("missing closing quote in selection path '%1%'", s); throw ParseError("missing closing quote in selection path '%1%'", s);
if (*i == '"') break; if (*i == '"') break;
cur.push_back(*i++); cur.push_back(*i++);
} }
@ -116,14 +116,14 @@ Pos findDerivationFilename(EvalState & state, Value & v, std::string what)
auto colon = pos.rfind(':'); auto colon = pos.rfind(':');
if (colon == std::string::npos) if (colon == std::string::npos)
throw Error("cannot parse meta.position attribute '%s'", pos); throw ParseError("cannot parse meta.position attribute '%s'", pos);
std::string filename(pos, 0, colon); std::string filename(pos, 0, colon);
unsigned int lineno; unsigned int lineno;
try { try {
lineno = std::stoi(std::string(pos, colon + 1)); lineno = std::stoi(std::string(pos, colon + 1));
} catch (std::invalid_argument & e) { } catch (std::invalid_argument & e) {
throw Error("cannot parse line number '%s'", pos); throw ParseError("cannot parse line number '%s'", pos);
} }
Symbol file = state.symbols.create(filename); Symbol file = state.symbols.create(filename);

View file

@ -64,7 +64,11 @@ static char * dupStringWithLen(const char * s, size_t size)
RootValue allocRootValue(Value * v) RootValue allocRootValue(Value * v)
{ {
#if HAVE_BOEHMGC
return std::allocate_shared<Value *>(traceable_allocator<Value *>(), v); return std::allocate_shared<Value *>(traceable_allocator<Value *>(), v);
#else
return std::make_shared<Value *>(v);
#endif
} }
@ -233,22 +237,34 @@ static void * oomHandler(size_t requested)
} }
class BoehmGCStackAllocator : public StackAllocator { class BoehmGCStackAllocator : public StackAllocator {
boost::coroutines2::protected_fixedsize_stack stack { boost::coroutines2::protected_fixedsize_stack stack {
// We allocate 8 MB, the default max stack size on NixOS. // We allocate 8 MB, the default max stack size on NixOS.
// A smaller stack might be quicker to allocate but reduces the stack // A smaller stack might be quicker to allocate but reduces the stack
// depth available for source filter expressions etc. // depth available for source filter expressions etc.
std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024)) std::max(boost::context::stack_traits::default_size(), static_cast<std::size_t>(8 * 1024 * 1024))
}; };
// This is specific to boost::coroutines2::protected_fixedsize_stack.
// The stack protection page is included in sctx.size, so we have to
// subtract one page size from the stack size.
std::size_t pfss_usable_stack_size(boost::context::stack_context &sctx) {
return sctx.size - boost::context::stack_traits::page_size();
}
public: public:
boost::context::stack_context allocate() override { boost::context::stack_context allocate() override {
auto sctx = stack.allocate(); auto sctx = stack.allocate();
GC_add_roots(static_cast<char *>(sctx.sp) - sctx.size, sctx.sp);
// Stacks generally start at a high address and grow to lower addresses.
// Architectures that do the opposite are rare; in fact so rare that
// boost_routine does not implement it.
// So we subtract the stack size.
GC_add_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
return sctx; return sctx;
} }
void deallocate(boost::context::stack_context sctx) override { void deallocate(boost::context::stack_context sctx) override {
GC_remove_roots(static_cast<char *>(sctx.sp) - sctx.size, sctx.sp); GC_remove_roots(static_cast<char *>(sctx.sp) - pfss_usable_stack_size(sctx), sctx.sp);
stack.deallocate(sctx); stack.deallocate(sctx);
} }
@ -908,7 +924,7 @@ void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
// computation. // computation.
if (mustBeTrivial && if (mustBeTrivial &&
!(dynamic_cast<ExprAttrs *>(e))) !(dynamic_cast<ExprAttrs *>(e)))
throw Error("file '%s' must be an attribute set", path); throw EvalError("file '%s' must be an attribute set", path);
eval(e, v); eval(e, v);
} catch (Error & e) { } catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", path2); addErrorTrace(e, "while evaluating the file '%1%':", path2);

View file

@ -298,6 +298,11 @@ LockedFlake lockFlake(
auto flake = getFlake(state, topRef, lockFlags.useRegistries, flakeCache); auto flake = getFlake(state, topRef, lockFlags.useRegistries, flakeCache);
if (lockFlags.applyNixConfig) {
flake.config.apply();
// FIXME: send new config to the daemon.
}
try { try {
// FIXME: symlink attack // FIXME: symlink attack
@ -359,7 +364,12 @@ LockedFlake lockFlake(
ancestors? */ ancestors? */
auto i = overrides.find(inputPath); auto i = overrides.find(inputPath);
bool hasOverride = i != overrides.end(); bool hasOverride = i != overrides.end();
if (hasOverride) overridesUsed.insert(inputPath); if (hasOverride) {
overridesUsed.insert(inputPath);
// Respect the “flakeness” of the input even if we
// override it
i->second.isFlake = input2.isFlake;
}
auto & input = hasOverride ? i->second : input2; auto & input = hasOverride ? i->second : input2;
/* Resolve 'follows' later (since it may refer to an input /* Resolve 'follows' later (since it may refer to an input

View file

@ -104,6 +104,10 @@ struct LockFlags
references like 'nixpkgs'. */ references like 'nixpkgs'. */
bool useRegistries = true; bool useRegistries = true;
/* Whether to apply flake's nixConfig attribute to the configuration */
bool applyNixConfig = false;
/* Whether mutable flake references (i.e. those without a Git /* Whether mutable flake references (i.e. those without a Git
revision or similar) without a corresponding lock are revision or similar) without a corresponding lock are
allowed. Mutable flake references with a lock are always allowed. Mutable flake references with a lock are always

View file

@ -16,7 +16,7 @@ libexpr_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/lib
libexpr_LIBS = libutil libstore libfetchers libexpr_LIBS = libutil libstore libfetchers
libexpr_LDFLAGS = -lboost_context libexpr_LDFLAGS = -lboost_context
ifeq ($(OS), Linux) ifdef HOST_LINUX
libexpr_LDFLAGS += -ldl libexpr_LDFLAGS += -ldl
endif endif

View file

@ -62,6 +62,7 @@ static void prim_fetchMercurial(EvalState & state, const Pos & pos, Value * * ar
fetchers::Attrs attrs; fetchers::Attrs attrs;
attrs.insert_or_assign("type", "hg"); attrs.insert_or_assign("type", "hg");
attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url); attrs.insert_or_assign("url", url.find("://") != std::string::npos ? url : "file://" + url);
attrs.insert_or_assign("name", name);
if (ref) attrs.insert_or_assign("ref", *ref); if (ref) attrs.insert_or_assign("ref", *ref);
if (rev) attrs.insert_or_assign("rev", rev->gitRev()); if (rev) attrs.insert_or_assign("rev", rev->gitRev());
auto input = fetchers::Input::fromAttrs(std::move(attrs)); auto input = fetchers::Input::fromAttrs(std::move(attrs));

View file

@ -7,6 +7,7 @@
#include <ctime> #include <ctime>
#include <iomanip> #include <iomanip>
#include <regex>
namespace nix { namespace nix {
@ -60,10 +61,19 @@ void emitTreeAttrs(
v.attrs->sort(); v.attrs->sort();
} }
std::string fixURI(std::string uri, EvalState &state) std::string fixURI(std::string uri, EvalState &state, const std::string & defaultScheme = "file")
{ {
state.checkURI(uri); state.checkURI(uri);
return uri.find("://") != std::string::npos ? uri : "file://" + uri; return uri.find("://") != std::string::npos ? uri : defaultScheme + "://" + uri;
}
std::string fixURIForGit(std::string uri, EvalState & state)
{
static std::regex scp_uri("([^/].*)@(.*):(.*)");
if (uri[0] != '/' && std::regex_match(uri, scp_uri))
return fixURI(std::regex_replace(uri, scp_uri, "$1@$2/$3"), state, "ssh");
else
return fixURI(uri, state);
} }
void addURI(EvalState &state, fetchers::Attrs &attrs, Symbol name, std::string v) void addURI(EvalState &state, fetchers::Attrs &attrs, Symbol name, std::string v)
@ -72,13 +82,18 @@ void addURI(EvalState &state, fetchers::Attrs &attrs, Symbol name, std::string v
attrs.emplace(name, n == "url" ? fixURI(v, state) : v); attrs.emplace(name, n == "url" ? fixURI(v, state) : v);
} }
struct FetchTreeParams {
bool emptyRevFallback = false;
bool allowNameArgument = false;
};
static void fetchTree( static void fetchTree(
EvalState &state, EvalState &state,
const Pos &pos, const Pos &pos,
Value **args, Value **args,
Value &v, Value &v,
const std::optional<std::string> type, const std::optional<std::string> type,
bool emptyRevFallback = false const FetchTreeParams & params = FetchTreeParams{}
) { ) {
fetchers::Input input; fetchers::Input input;
PathSet context; PathSet context;
@ -119,17 +134,25 @@ static void fetchTree(
.errPos = pos .errPos = pos
}); });
if (!params.allowNameArgument)
if (auto nameIter = attrs.find("name"); nameIter != attrs.end())
throw Error({
.msg = hintfmt("attribute 'name' isnt supported in call to 'fetchTree'"),
.errPos = pos
});
input = fetchers::Input::fromAttrs(std::move(attrs)); input = fetchers::Input::fromAttrs(std::move(attrs));
} else { } else {
auto url = fixURI(state.coerceToString(pos, *args[0], context, false, false), state); auto url = state.coerceToString(pos, *args[0], context, false, false);
if (type == "git") { if (type == "git") {
fetchers::Attrs attrs; fetchers::Attrs attrs;
attrs.emplace("type", "git"); attrs.emplace("type", "git");
attrs.emplace("url", url); attrs.emplace("url", fixURIForGit(url, state));
input = fetchers::Input::fromAttrs(std::move(attrs)); input = fetchers::Input::fromAttrs(std::move(attrs));
} else { } else {
input = fetchers::Input::fromURL(url); input = fetchers::Input::fromURL(fixURI(url, state));
} }
} }
@ -144,13 +167,13 @@ static void fetchTree(
if (state.allowedPaths) if (state.allowedPaths)
state.allowedPaths->insert(tree.actualPath); state.allowedPaths->insert(tree.actualPath);
emitTreeAttrs(state, tree, input2, v, emptyRevFallback); emitTreeAttrs(state, tree, input2, v, params.emptyRevFallback);
} }
static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v) static void prim_fetchTree(EvalState & state, const Pos & pos, Value * * args, Value & v)
{ {
settings.requireExperimentalFeature("flakes"); settings.requireExperimentalFeature("flakes");
fetchTree(state, pos, args, v, std::nullopt); fetchTree(state, pos, args, v, std::nullopt, FetchTreeParams { .allowNameArgument = false });
} }
// FIXME: document // FIXME: document
@ -292,7 +315,7 @@ static RegisterPrimOp primop_fetchTarball({
static void prim_fetchGit(EvalState &state, const Pos &pos, Value **args, Value &v) static void prim_fetchGit(EvalState &state, const Pos &pos, Value **args, Value &v)
{ {
fetchTree(state, pos, args, v, "git", true); fetchTree(state, pos, args, v, "git", FetchTreeParams { .emptyRevFallback = true, .allowNameArgument = true });
} }
static RegisterPrimOp primop_fetchGit({ static RegisterPrimOp primop_fetchGit({

View file

@ -200,12 +200,17 @@ void Input::markChangedFile(
return scheme->markChangedFile(*this, file, commitMsg); return scheme->markChangedFile(*this, file, commitMsg);
} }
std::string Input::getName() const
{
return maybeGetStrAttr(attrs, "name").value_or("source");
}
StorePath Input::computeStorePath(Store & store) const StorePath Input::computeStorePath(Store & store) const
{ {
auto narHash = getNarHash(); auto narHash = getNarHash();
if (!narHash) if (!narHash)
throw Error("cannot compute store path for mutable input '%s'", to_string()); throw Error("cannot compute store path for mutable input '%s'", to_string());
return store.makeFixedOutputPath(FileIngestionMethod::Recursive, *narHash, "source"); return store.makeFixedOutputPath(FileIngestionMethod::Recursive, *narHash, getName());
} }
std::string Input::getType() const std::string Input::getType() const

View file

@ -81,6 +81,8 @@ public:
std::string_view file, std::string_view file,
std::optional<std::string> commitMsg) const; std::optional<std::string> commitMsg) const;
std::string getName() const;
StorePath computeStorePath(Store & store) const; StorePath computeStorePath(Store & store) const;
// Convenience functions for common attributes. // Convenience functions for common attributes.

View file

@ -60,7 +60,7 @@ struct GitInputScheme : InputScheme
if (maybeGetStrAttr(attrs, "type") != "git") return {}; if (maybeGetStrAttr(attrs, "type") != "git") return {};
for (auto & [name, value] : attrs) for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs") if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "shallow" && name != "submodules" && name != "lastModified" && name != "revCount" && name != "narHash" && name != "allRefs" && name != "name")
throw Error("unsupported Git input attribute '%s'", name); throw Error("unsupported Git input attribute '%s'", name);
parseURL(getStrAttr(attrs, "url")); parseURL(getStrAttr(attrs, "url"));
@ -167,10 +167,10 @@ struct GitInputScheme : InputScheme
std::pair<Tree, Input> fetch(ref<Store> store, const Input & _input) override std::pair<Tree, Input> fetch(ref<Store> store, const Input & _input) override
{ {
auto name = "source";
Input input(_input); Input input(_input);
std::string name = input.getName();
bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false); bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false);
bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false); bool submodules = maybeGetBoolAttr(input.attrs, "submodules").value_or(false);
bool allRefs = maybeGetBoolAttr(input.attrs, "allRefs").value_or(false); bool allRefs = maybeGetBoolAttr(input.attrs, "allRefs").value_or(false);
@ -270,7 +270,7 @@ struct GitInputScheme : InputScheme
return files.count(file); return files.count(file);
}; };
auto storePath = store->addToStore("source", actualUrl, FileIngestionMethod::Recursive, htSHA256, filter); auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
// FIXME: maybe we should use the timestamp of the last // FIXME: maybe we should use the timestamp of the last
// modified dirty file? // modified dirty file?

View file

@ -207,7 +207,7 @@ struct GitArchiveInputScheme : InputScheme
auto url = getDownloadUrl(input); auto url = getDownloadUrl(input);
auto [tree, lastModified] = downloadTarball(store, url.url, "source", true, url.headers); auto [tree, lastModified] = downloadTarball(store, url.url, input.getName(), true, url.headers);
input.attrs.insert_or_assign("lastModified", uint64_t(lastModified)); input.attrs.insert_or_assign("lastModified", uint64_t(lastModified));

View file

@ -74,7 +74,7 @@ struct MercurialInputScheme : InputScheme
if (maybeGetStrAttr(attrs, "type") != "hg") return {}; if (maybeGetStrAttr(attrs, "type") != "hg") return {};
for (auto & [name, value] : attrs) for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "revCount" && name != "narHash") if (name != "type" && name != "url" && name != "ref" && name != "rev" && name != "revCount" && name != "narHash" && name != "name")
throw Error("unsupported Mercurial input attribute '%s'", name); throw Error("unsupported Mercurial input attribute '%s'", name);
parseURL(getStrAttr(attrs, "url")); parseURL(getStrAttr(attrs, "url"));
@ -147,10 +147,10 @@ struct MercurialInputScheme : InputScheme
std::pair<Tree, Input> fetch(ref<Store> store, const Input & _input) override std::pair<Tree, Input> fetch(ref<Store> store, const Input & _input) override
{ {
auto name = "source";
Input input(_input); Input input(_input);
auto name = input.getName();
auto [isLocal, actualUrl_] = getActualUrl(input); auto [isLocal, actualUrl_] = getActualUrl(input);
auto actualUrl = actualUrl_; // work around clang bug auto actualUrl = actualUrl_; // work around clang bug
@ -193,7 +193,7 @@ struct MercurialInputScheme : InputScheme
return files.count(file); return files.count(file);
}; };
auto storePath = store->addToStore("source", actualUrl, FileIngestionMethod::Recursive, htSHA256, filter); auto storePath = store->addToStore(input.getName(), actualUrl, FileIngestionMethod::Recursive, htSHA256, filter);
return { return {
Tree(store->toRealPath(storePath), std::move(storePath)), Tree(store->toRealPath(storePath), std::move(storePath)),

View file

@ -124,6 +124,13 @@ std::shared_ptr<Registry> getUserRegistry()
return userRegistry; return userRegistry;
} }
std::shared_ptr<Registry> getCustomRegistry(const Path & p)
{
static auto customRegistry =
Registry::read(p, Registry::Custom);
return customRegistry;
}
static std::shared_ptr<Registry> flagRegistry = static std::shared_ptr<Registry> flagRegistry =
std::make_shared<Registry>(Registry::Flag); std::make_shared<Registry>(Registry::Flag);

View file

@ -14,6 +14,7 @@ struct Registry
User = 1, User = 1,
System = 2, System = 2,
Global = 3, Global = 3,
Custom = 4,
}; };
RegistryType type; RegistryType type;
@ -48,6 +49,8 @@ typedef std::vector<std::shared_ptr<Registry>> Registries;
std::shared_ptr<Registry> getUserRegistry(); std::shared_ptr<Registry> getUserRegistry();
std::shared_ptr<Registry> getCustomRegistry(const Path & p);
Path getUserRegistryPath(); Path getUserRegistryPath();
Registries getRegistries(ref<Store> store); Registries getRegistries(ref<Store> store);

View file

@ -196,7 +196,7 @@ struct TarballInputScheme : InputScheme
if (maybeGetStrAttr(attrs, "type") != "tarball") return {}; if (maybeGetStrAttr(attrs, "type") != "tarball") return {};
for (auto & [name, value] : attrs) for (auto & [name, value] : attrs)
if (name != "type" && name != "url" && /* name != "hash" && */ name != "narHash") if (name != "type" && name != "url" && /* name != "hash" && */ name != "narHash" && name != "name")
throw Error("unsupported tarball input attribute '%s'", name); throw Error("unsupported tarball input attribute '%s'", name);
Input input; Input input;
@ -226,7 +226,7 @@ struct TarballInputScheme : InputScheme
std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) override std::pair<Tree, Input> fetch(ref<Store> store, const Input & input) override
{ {
auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), "source", false).first; auto tree = downloadTarball(store, getStrAttr(input.attrs, "url"), input.getName(), false).first;
return {std::move(tree), input}; return {std::move(tree), input};
} }
}; };

View file

@ -484,7 +484,7 @@ Logger * makeProgressBar(bool printBuildLogs)
{ {
return new ProgressBar( return new ProgressBar(
printBuildLogs, printBuildLogs,
isatty(STDERR_FILENO) && getEnv("TERM").value_or("dumb") != "dumb" shouldANSI()
); );
} }

View file

@ -738,6 +738,63 @@ void DerivationGoal::cleanupPostOutputsRegisteredModeNonCheck()
{ {
} }
void runPostBuildHook(
Store & store,
Logger & logger,
const StorePath & drvPath,
StorePathSet outputPaths
)
{
auto hook = settings.postBuildHook;
if (hook == "")
return;
Activity act(logger, lvlInfo, actPostBuildHook,
fmt("running post-build-hook '%s'", settings.postBuildHook),
Logger::Fields{store.printStorePath(drvPath)});
PushActivity pact(act.id);
std::map<std::string, std::string> hookEnvironment = getEnv();
hookEnvironment.emplace("DRV_PATH", store.printStorePath(drvPath));
hookEnvironment.emplace("OUT_PATHS", chomp(concatStringsSep(" ", store.printStorePathSet(outputPaths))));
RunOptions opts(settings.postBuildHook, {});
opts.environment = hookEnvironment;
struct LogSink : Sink {
Activity & act;
std::string currentLine;
LogSink(Activity & act) : act(act) { }
void operator() (std::string_view data) override {
for (auto c : data) {
if (c == '\n') {
flushLine();
} else {
currentLine += c;
}
}
}
void flushLine() {
act.result(resPostBuildLogLine, currentLine);
currentLine.clear();
}
~LogSink() {
if (currentLine != "") {
currentLine += '\n';
flushLine();
}
}
};
LogSink sink(act);
opts.standardOut = &sink;
opts.mergeStderrToStdout = true;
runProgram2(opts);
}
void DerivationGoal::buildDone() void DerivationGoal::buildDone()
{ {
@ -803,57 +860,15 @@ void DerivationGoal::buildDone()
being valid. */ being valid. */
registerOutputs(); registerOutputs();
if (settings.postBuildHook != "") { StorePathSet outputPaths;
Activity act(*logger, lvlInfo, actPostBuildHook, for (auto & [_, path] : finalOutputs)
fmt("running post-build-hook '%s'", settings.postBuildHook), outputPaths.insert(path);
Logger::Fields{worker.store.printStorePath(drvPath)}); runPostBuildHook(
PushActivity pact(act.id); worker.store,
StorePathSet outputPaths; *logger,
for (auto i : drv->outputs) { drvPath,
outputPaths.insert(finalOutputs.at(i.first)); outputPaths
} );
std::map<std::string, std::string> hookEnvironment = getEnv();
hookEnvironment.emplace("DRV_PATH", worker.store.printStorePath(drvPath));
hookEnvironment.emplace("OUT_PATHS", chomp(concatStringsSep(" ", worker.store.printStorePathSet(outputPaths))));
RunOptions opts(settings.postBuildHook, {});
opts.environment = hookEnvironment;
struct LogSink : Sink {
Activity & act;
std::string currentLine;
LogSink(Activity & act) : act(act) { }
void operator() (std::string_view data) override {
for (auto c : data) {
if (c == '\n') {
flushLine();
} else {
currentLine += c;
}
}
}
void flushLine() {
act.result(resPostBuildLogLine, currentLine);
currentLine.clear();
}
~LogSink() {
if (currentLine != "") {
currentLine += '\n';
flushLine();
}
}
};
LogSink sink(act);
opts.standardOut = &sink;
opts.mergeStderrToStdout = true;
runProgram2(opts);
}
if (buildMode == bmCheck) { if (buildMode == bmCheck) {
cleanupPostOutputsRegisteredModeCheck(); cleanupPostOutputsRegisteredModeCheck();
@ -909,6 +924,8 @@ void DerivationGoal::resolvedFinished() {
auto resolvedHashes = staticOutputHashes(worker.store, *resolvedDrv); auto resolvedHashes = staticOutputHashes(worker.store, *resolvedDrv);
StorePathSet outputPaths;
// `wantedOutputs` might be empty, which means “all the outputs” // `wantedOutputs` might be empty, which means “all the outputs”
auto realWantedOutputs = wantedOutputs; auto realWantedOutputs = wantedOutputs;
if (realWantedOutputs.empty()) if (realWantedOutputs.empty())
@ -926,8 +943,10 @@ void DerivationGoal::resolvedFinished() {
auto newRealisation = *realisation; auto newRealisation = *realisation;
newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput}; newRealisation.id = DrvOutput{initialOutputs.at(wantedOutput).outputHash, wantedOutput};
newRealisation.signatures.clear(); newRealisation.signatures.clear();
newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath);
signRealisation(newRealisation); signRealisation(newRealisation);
worker.store.registerDrvOutput(newRealisation); worker.store.registerDrvOutput(newRealisation);
outputPaths.insert(realisation->outPath);
} else { } else {
// If we don't have a realisation, then it must mean that something // If we don't have a realisation, then it must mean that something
// failed when building the resolved drv // failed when building the resolved drv
@ -935,6 +954,13 @@ void DerivationGoal::resolvedFinished() {
} }
} }
runPostBuildHook(
worker.store,
*logger,
drvPath,
outputPaths
);
// This is potentially a bit fishy in terms of error reporting. Not sure // This is potentially a bit fishy in terms of error reporting. Not sure
// how to do it in a cleaner way // how to do it in a cleaner way
amDone(nrFailed == 0 ? ecSuccess : ecFailed, ex); amDone(nrFailed == 0 ? ecSuccess : ecFailed, ex);

View file

@ -17,6 +17,13 @@ DrvOutputSubstitutionGoal::DrvOutputSubstitutionGoal(const DrvOutput& id, Worker
void DrvOutputSubstitutionGoal::init() void DrvOutputSubstitutionGoal::init()
{ {
trace("init"); trace("init");
/* If the derivation already exists, were done */
if (worker.store.queryRealisation(id)) {
amDone(ecSuccess);
return;
}
subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>(); subs = settings.useSubstitutes ? getDefaultSubstituters() : std::list<ref<Store>>();
tryNext(); tryNext();
} }
@ -53,6 +60,26 @@ void DrvOutputSubstitutionGoal::tryNext()
return; return;
} }
for (const auto & [depId, depPath] : outputInfo->dependentRealisations) {
if (depId != id) {
if (auto localOutputInfo = worker.store.queryRealisation(depId);
localOutputInfo && localOutputInfo->outPath != depPath) {
warn(
"substituter '%s' has an incompatible realisation for '%s', ignoring.\n"
"Local: %s\n"
"Remote: %s",
sub->getUri(),
depId.to_string(),
worker.store.printStorePath(localOutputInfo->outPath),
worker.store.printStorePath(depPath)
);
tryNext();
return;
}
addWaitee(worker.makeDrvOutputSubstitutionGoal(depId));
}
}
addWaitee(worker.makePathSubstitutionGoal(outputInfo->outPath)); addWaitee(worker.makePathSubstitutionGoal(outputInfo->outPath));
if (waitees.empty()) outPathValid(); if (waitees.empty()) outPathValid();

View file

@ -1248,13 +1248,18 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
std::optional<const Realisation> queryRealisation(const DrvOutput & id) override std::optional<const Realisation> queryRealisation(const DrvOutput & id) override
// XXX: This should probably be allowed if the realisation corresponds to // XXX: This should probably be allowed if the realisation corresponds to
// an allowed derivation // an allowed derivation
{ throw Error("queryRealisation"); } {
if (!goal.isAllowed(id))
throw InvalidPath("cannot query an unknown output id '%s' in recursive Nix", id.to_string());
return next->queryRealisation(id);
}
void buildPaths(const std::vector<DerivedPath> & paths, BuildMode buildMode) override void buildPaths(const std::vector<DerivedPath> & paths, BuildMode buildMode) override
{ {
if (buildMode != bmNormal) throw Error("unsupported build mode"); if (buildMode != bmNormal) throw Error("unsupported build mode");
StorePathSet newPaths; StorePathSet newPaths;
std::set<Realisation> newRealisations;
for (auto & req : paths) { for (auto & req : paths) {
if (!goal.isAllowed(req)) if (!goal.isAllowed(req))
@ -1267,16 +1272,28 @@ struct RestrictedStore : public virtual RestrictedStoreConfig, public virtual Lo
auto p = std::get_if<DerivedPath::Built>(&path); auto p = std::get_if<DerivedPath::Built>(&path);
if (!p) continue; if (!p) continue;
auto & bfd = *p; auto & bfd = *p;
auto drv = readDerivation(bfd.drvPath);
auto drvHashes = staticOutputHashes(*this, drv);
auto outputs = next->queryDerivationOutputMap(bfd.drvPath); auto outputs = next->queryDerivationOutputMap(bfd.drvPath);
for (auto & [outputName, outputPath] : outputs) for (auto & [outputName, outputPath] : outputs)
if (wantOutput(outputName, bfd.outputs)) if (wantOutput(outputName, bfd.outputs)) {
newPaths.insert(outputPath); newPaths.insert(outputPath);
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
auto thisRealisation = next->queryRealisation(
DrvOutput{drvHashes.at(outputName), outputName}
);
assert(thisRealisation);
newRealisations.insert(*thisRealisation);
}
}
} }
StorePathSet closure; StorePathSet closure;
next->computeFSClosure(newPaths, closure); next->computeFSClosure(newPaths, closure);
for (auto & path : closure) for (auto & path : closure)
goal.addDependency(path); goal.addDependency(path);
for (auto & real : Realisation::closure(*next, newRealisations))
goal.addedDrvOutputs.insert(real.id);
} }
BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv, BuildResult buildDerivation(const StorePath & drvPath, const BasicDerivation & drv,
@ -2379,6 +2396,7 @@ void LocalDerivationGoal::registerOutputs()
floating CA derivations and hash-mismatching fixed-output floating CA derivations and hash-mismatching fixed-output
derivations. */ derivations. */
PathLocks dynamicOutputLock; PathLocks dynamicOutputLock;
dynamicOutputLock.setDeletion(true);
auto optFixedPath = output.path(worker.store, drv->name, outputName); auto optFixedPath = output.path(worker.store, drv->name, outputName);
if (!optFixedPath || if (!optFixedPath ||
worker.store.printStorePath(*optFixedPath) != finalDestPath) worker.store.printStorePath(*optFixedPath) != finalDestPath)

View file

@ -108,6 +108,9 @@ struct LocalDerivationGoal : public DerivationGoal
/* Paths that were added via recursive Nix calls. */ /* Paths that were added via recursive Nix calls. */
StorePathSet addedPaths; StorePathSet addedPaths;
/* Realisations that were added via recursive Nix calls. */
std::set<DrvOutput> addedDrvOutputs;
/* Recursive Nix calls are only allowed to build or realize paths /* Recursive Nix calls are only allowed to build or realize paths
in the original input closure or added via a recursive Nix call in the original input closure or added via a recursive Nix call
(so e.g. you can't do 'nix-store -r /nix/store/<bla>' where (so e.g. you can't do 'nix-store -r /nix/store/<bla>' where
@ -116,6 +119,11 @@ struct LocalDerivationGoal : public DerivationGoal
{ {
return inputPaths.count(path) || addedPaths.count(path); return inputPaths.count(path) || addedPaths.count(path);
} }
bool isAllowed(const DrvOutput & id)
{
return addedDrvOutputs.count(id);
}
bool isAllowed(const DerivedPath & req); bool isAllowed(const DerivedPath & req);
friend struct RestrictedStore; friend struct RestrictedStore;

View file

@ -3,10 +3,19 @@
-- is enabled -- is enabled
create table if not exists Realisations ( create table if not exists Realisations (
id integer primary key autoincrement not null,
drvPath text not null, drvPath text not null,
outputName text not null, -- symbolic output id, usually "out" outputName text not null, -- symbolic output id, usually "out"
outputPath integer not null, outputPath integer not null,
signatures text, -- space-separated list signatures text, -- space-separated list
primary key (drvPath, outputName),
foreign key (outputPath) references ValidPaths(id) on delete cascade foreign key (outputPath) references ValidPaths(id) on delete cascade
); );
create index if not exists IndexRealisations on Realisations(drvPath, outputName);
create table if not exists RealisationsRefs (
referrer integer not null,
realisationReference integer,
foreign key (referrer) references Realisations(id) on delete cascade,
foreign key (realisationReference) references Realisations(id) on delete restrict
);

View file

@ -53,12 +53,15 @@ struct LocalStore::State::Stmts {
SQLiteStmt InvalidatePath; SQLiteStmt InvalidatePath;
SQLiteStmt AddDerivationOutput; SQLiteStmt AddDerivationOutput;
SQLiteStmt RegisterRealisedOutput; SQLiteStmt RegisterRealisedOutput;
SQLiteStmt UpdateRealisedOutput;
SQLiteStmt QueryValidDerivers; SQLiteStmt QueryValidDerivers;
SQLiteStmt QueryDerivationOutputs; SQLiteStmt QueryDerivationOutputs;
SQLiteStmt QueryRealisedOutput; SQLiteStmt QueryRealisedOutput;
SQLiteStmt QueryAllRealisedOutputs; SQLiteStmt QueryAllRealisedOutputs;
SQLiteStmt QueryPathFromHashPart; SQLiteStmt QueryPathFromHashPart;
SQLiteStmt QueryValidPaths; SQLiteStmt QueryValidPaths;
SQLiteStmt QueryRealisationReferences;
SQLiteStmt AddRealisationReference;
}; };
int getSchema(Path schemaPath) int getSchema(Path schemaPath)
@ -76,7 +79,7 @@ int getSchema(Path schemaPath)
void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
{ {
const int nixCASchemaVersion = 1; const int nixCASchemaVersion = 2;
int curCASchema = getSchema(schemaPath); int curCASchema = getSchema(schemaPath);
if (curCASchema != nixCASchemaVersion) { if (curCASchema != nixCASchemaVersion) {
if (curCASchema > nixCASchemaVersion) { if (curCASchema > nixCASchemaVersion) {
@ -94,7 +97,39 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd)
#include "ca-specific-schema.sql.gen.hh" #include "ca-specific-schema.sql.gen.hh"
; ;
db.exec(schema); db.exec(schema);
curCASchema = nixCASchemaVersion;
} }
if (curCASchema < 2) {
SQLiteTxn txn(db);
// Ugly little sql dance to add a new `id` column and make it the primary key
db.exec(R"(
create table Realisations2 (
id integer primary key autoincrement not null,
drvPath text not null,
outputName text not null, -- symbolic output id, usually "out"
outputPath integer not null,
signatures text, -- space-separated list
foreign key (outputPath) references ValidPaths(id) on delete cascade
);
insert into Realisations2 (drvPath, outputName, outputPath, signatures)
select drvPath, outputName, outputPath, signatures from Realisations;
drop table Realisations;
alter table Realisations2 rename to Realisations;
)");
db.exec(R"(
create index if not exists IndexRealisations on Realisations(drvPath, outputName);
create table if not exists RealisationsRefs (
referrer integer not null,
realisationReference integer,
foreign key (referrer) references Realisations(id) on delete cascade,
foreign key (realisationReference) references Realisations(id) on delete restrict
);
)");
txn.commit();
}
writeFile(schemaPath, fmt("%d", nixCASchemaVersion)); writeFile(schemaPath, fmt("%d", nixCASchemaVersion));
lockFile(lockFd.get(), ltRead, true); lockFile(lockFd.get(), ltRead, true);
} }
@ -311,9 +346,18 @@ LocalStore::LocalStore(const Params & params)
values (?, ?, (select id from ValidPaths where path = ?), ?) values (?, ?, (select id from ValidPaths where path = ?), ?)
; ;
)"); )");
state->stmts->UpdateRealisedOutput.create(state->db,
R"(
update Realisations
set signatures = ?
where
drvPath = ? and
outputName = ?
;
)");
state->stmts->QueryRealisedOutput.create(state->db, state->stmts->QueryRealisedOutput.create(state->db,
R"( R"(
select Output.path, Realisations.signatures from Realisations select Realisations.id, Output.path, Realisations.signatures from Realisations
inner join ValidPaths as Output on Output.id = Realisations.outputPath inner join ValidPaths as Output on Output.id = Realisations.outputPath
where drvPath = ? and outputName = ? where drvPath = ? and outputName = ?
; ;
@ -325,6 +369,19 @@ LocalStore::LocalStore(const Params & params)
where drvPath = ? where drvPath = ?
; ;
)"); )");
state->stmts->QueryRealisationReferences.create(state->db,
R"(
select drvPath, outputName from Realisations
join RealisationsRefs on realisationReference = Realisations.id
where referrer = ?;
)");
state->stmts->AddRealisationReference.create(state->db,
R"(
insert or replace into RealisationsRefs (referrer, realisationReference)
values (
?,
(select id from Realisations where drvPath = ? and outputName = ?));
)");
} }
} }
@ -661,14 +718,54 @@ void LocalStore::registerDrvOutput(const Realisation & info, CheckSigsFlag check
void LocalStore::registerDrvOutput(const Realisation & info) void LocalStore::registerDrvOutput(const Realisation & info)
{ {
settings.requireExperimentalFeature("ca-derivations"); settings.requireExperimentalFeature("ca-derivations");
auto state(_state.lock());
retrySQLite<void>([&]() { retrySQLite<void>([&]() {
state->stmts->RegisterRealisedOutput.use() auto state(_state.lock());
(info.id.strHash()) if (auto oldR = queryRealisation_(*state, info.id)) {
(info.id.outputName) if (info.isCompatibleWith(*oldR)) {
(printStorePath(info.outPath)) auto combinedSignatures = oldR->signatures;
(concatStringsSep(" ", info.signatures)) combinedSignatures.insert(info.signatures.begin(),
.exec(); info.signatures.end());
state->stmts->UpdateRealisedOutput.use()
(concatStringsSep(" ", combinedSignatures))
(info.id.strHash())
(info.id.outputName)
.exec();
} else {
throw Error("Trying to register a realisation of '%s', but we already "
"have another one locally.\n"
"Local: %s\n"
"Remote: %s",
info.id.to_string(),
printStorePath(oldR->outPath),
printStorePath(info.outPath)
);
}
} else {
state->stmts->RegisterRealisedOutput.use()
(info.id.strHash())
(info.id.outputName)
(printStorePath(info.outPath))
(concatStringsSep(" ", info.signatures))
.exec();
}
uint64_t myId = state->db.getLastInsertedRowId();
for (auto & [outputId, depPath] : info.dependentRealisations) {
auto localRealisation = queryRealisationCore_(*state, outputId);
if (!localRealisation)
throw Error("unable to register the derivation '%s' as it "
"depends on the non existent '%s'",
info.id.to_string(), outputId.to_string());
if (localRealisation->second.outPath != depPath)
throw Error("unable to register the derivation '%s' as it "
"depends on a realisation of '%s' that doesnt"
"match what we have locally",
info.id.to_string(), outputId.to_string());
state->stmts->AddRealisationReference.use()
(myId)
(outputId.strHash())
(outputId.outputName)
.exec();
}
}); });
} }
@ -1679,22 +1776,68 @@ void LocalStore::createUser(const std::string & userName, uid_t userId)
} }
} }
std::optional<const Realisation> LocalStore::queryRealisation( std::optional<std::pair<int64_t, Realisation>> LocalStore::queryRealisationCore_(
const DrvOutput& id) { LocalStore::State & state,
typedef std::optional<const Realisation> Ret; const DrvOutput & id)
return retrySQLite<Ret>([&]() -> Ret { {
auto state(_state.lock()); auto useQueryRealisedOutput(
auto use(state->stmts->QueryRealisedOutput.use()(id.strHash())( state.stmts->QueryRealisedOutput.use()
id.outputName)); (id.strHash())
if (!use.next()) (id.outputName));
return std::nullopt; if (!useQueryRealisedOutput.next())
auto outputPath = parseStorePath(use.getStr(0)); return std::nullopt;
auto signatures = tokenizeString<StringSet>(use.getStr(1)); auto realisationDbId = useQueryRealisedOutput.getInt(0);
return Ret{Realisation{ auto outputPath = parseStorePath(useQueryRealisedOutput.getStr(1));
.id = id, .outPath = outputPath, .signatures = signatures}}; auto signatures =
}); tokenizeString<StringSet>(useQueryRealisedOutput.getStr(2));
return {{
realisationDbId,
Realisation{
.id = id,
.outPath = outputPath,
.signatures = signatures,
}
}};
} }
std::optional<const Realisation> LocalStore::queryRealisation_(
LocalStore::State & state,
const DrvOutput & id)
{
auto maybeCore = queryRealisationCore_(state, id);
if (!maybeCore)
return std::nullopt;
auto [realisationDbId, res] = *maybeCore;
std::map<DrvOutput, StorePath> dependentRealisations;
auto useRealisationRefs(
state.stmts->QueryRealisationReferences.use()
(realisationDbId));
while (useRealisationRefs.next()) {
auto depId = DrvOutput {
Hash::parseAnyPrefixed(useRealisationRefs.getStr(0)),
useRealisationRefs.getStr(1),
};
auto dependentRealisation = queryRealisationCore_(state, depId);
assert(dependentRealisation); // Enforced by the db schema
auto outputPath = dependentRealisation->second.outPath;
dependentRealisations.insert({depId, outputPath});
}
res.dependentRealisations = dependentRealisations;
return { res };
}
std::optional<const Realisation>
LocalStore::queryRealisation(const DrvOutput & id)
{
return retrySQLite<std::optional<const Realisation>>([&]() {
auto state(_state.lock());
return queryRealisation_(*state, id);
});
}
FixedOutputHash LocalStore::hashCAPath( FixedOutputHash LocalStore::hashCAPath(
const FileIngestionMethod & method, const HashType & hashType, const FileIngestionMethod & method, const HashType & hashType,

View file

@ -203,6 +203,8 @@ public:
void registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs) override; void registerDrvOutput(const Realisation & info, CheckSigsFlag checkSigs) override;
void cacheDrvOutputMapping(State & state, const uint64_t deriver, const string & outputName, const StorePath & output); void cacheDrvOutputMapping(State & state, const uint64_t deriver, const string & outputName, const StorePath & output);
std::optional<const Realisation> queryRealisation_(State & state, const DrvOutput & id);
std::optional<std::pair<int64_t, Realisation>> queryRealisationCore_(State & state, const DrvOutput & id);
std::optional<const Realisation> queryRealisation(const DrvOutput&) override; std::optional<const Realisation> queryRealisation(const DrvOutput&) override;
private: private:

View file

@ -9,11 +9,11 @@ libstore_SOURCES := $(wildcard $(d)/*.cc $(d)/builtins/*.cc $(d)/build/*.cc)
libstore_LIBS = libutil libstore_LIBS = libutil
libstore_LDFLAGS = $(SQLITE3_LIBS) -lbz2 $(LIBCURL_LIBS) $(SODIUM_LIBS) -pthread libstore_LDFLAGS = $(SQLITE3_LIBS) -lbz2 $(LIBCURL_LIBS) $(SODIUM_LIBS) -pthread
ifeq ($(OS), Linux) ifdef HOST_LINUX
libstore_LDFLAGS += -ldl libstore_LDFLAGS += -ldl
endif endif
ifeq ($(OS), Darwin) ifdef HOST_DARWIN
libstore_FILES = sandbox-defaults.sb sandbox-minimal.sb sandbox-network.sb libstore_FILES = sandbox-defaults.sb sandbox-minimal.sb sandbox-network.sb
endif endif
@ -23,7 +23,7 @@ ifeq ($(ENABLE_S3), 1)
libstore_LDFLAGS += -laws-cpp-sdk-transfer -laws-cpp-sdk-s3 -laws-cpp-sdk-core libstore_LDFLAGS += -laws-cpp-sdk-transfer -laws-cpp-sdk-s3 -laws-cpp-sdk-core
endif endif
ifeq ($(OS), SunOS) ifdef HOST_SOLARIS
libstore_LDFLAGS += -lsocket libstore_LDFLAGS += -lsocket
endif endif

View file

@ -16,13 +16,18 @@ Machine::Machine(decltype(storeUri) storeUri,
decltype(mandatoryFeatures) mandatoryFeatures, decltype(mandatoryFeatures) mandatoryFeatures,
decltype(sshPublicHostKey) sshPublicHostKey) : decltype(sshPublicHostKey) sshPublicHostKey) :
storeUri( storeUri(
// Backwards compatibility: if the URI is a hostname, // Backwards compatibility: if the URI is schemeless, is not a path,
// prepend ssh://. // and is not one of the special store connection words, prepend
// ssh://.
storeUri.find("://") != std::string::npos storeUri.find("://") != std::string::npos
|| hasPrefix(storeUri, "local") || storeUri.find("/") != std::string::npos
|| hasPrefix(storeUri, "remote") || storeUri == "auto"
|| hasPrefix(storeUri, "auto") || storeUri == "daemon"
|| hasPrefix(storeUri, "/") || storeUri == "local"
|| hasPrefix(storeUri, "auto?")
|| hasPrefix(storeUri, "daemon?")
|| hasPrefix(storeUri, "local?")
|| hasPrefix(storeUri, "?")
? storeUri ? storeUri
: "ssh://" + storeUri), : "ssh://" + storeUri),
systemTypes(systemTypes), systemTypes(systemTypes),

View file

@ -29,9 +29,9 @@ void Store::computeFSClosure(const StorePathSet & startPaths,
res.insert(i); res.insert(i);
if (includeDerivers && path.isDerivation()) if (includeDerivers && path.isDerivation())
for (auto& i : queryDerivationOutputs(path)) for (auto& [_, maybeOutPath] : queryPartialDerivationOutputMap(path))
if (isValidPath(i) && queryPathInfo(i)->deriver == path) if (maybeOutPath && isValidPath(*maybeOutPath))
res.insert(i); res.insert(*maybeOutPath);
return res; return res;
}; };
else else
@ -44,9 +44,9 @@ void Store::computeFSClosure(const StorePathSet & startPaths,
res.insert(ref); res.insert(ref);
if (includeOutputs && path.isDerivation()) if (includeOutputs && path.isDerivation())
for (auto& i : queryDerivationOutputs(path)) for (auto& [_, maybeOutPath] : queryPartialDerivationOutputMap(path))
if (isValidPath(i)) if (maybeOutPath && isValidPath(*maybeOutPath))
res.insert(i); res.insert(*maybeOutPath);
if (includeDerivers && info->deriver && isValidPath(*info->deriver)) if (includeDerivers && info->deriver && isValidPath(*info->deriver))
res.insert(*info->deriver); res.insert(*info->deriver);
@ -254,5 +254,44 @@ StorePaths Store::topoSortPaths(const StorePathSet & paths)
}}); }});
} }
std::map<DrvOutput, StorePath> drvOutputReferences(
const std::set<Realisation> & inputRealisations,
const StorePathSet & pathReferences)
{
std::map<DrvOutput, StorePath> res;
for (const auto & input : inputRealisations) {
if (pathReferences.count(input.outPath)) {
res.insert({input.id, input.outPath});
}
}
return res;
}
std::map<DrvOutput, StorePath> drvOutputReferences(
Store & store,
const Derivation & drv,
const StorePath & outputPath)
{
std::set<Realisation> inputRealisations;
for (const auto& [inputDrv, outputNames] : drv.inputDrvs) {
auto outputHashes =
staticOutputHashes(store, store.readDerivation(inputDrv));
for (const auto& outputName : outputNames) {
auto thisRealisation = store.queryRealisation(
DrvOutput{outputHashes.at(outputName), outputName});
if (!thisRealisation)
throw Error(
"output '%s' of derivation '%s' isnt built", outputName,
store.printStorePath(inputDrv));
inputRealisations.insert(*thisRealisation);
}
}
auto info = store.queryPathInfo(outputPath);
return drvOutputReferences(Realisation::closure(store, inputRealisations), info->references);
}
} }

View file

@ -93,6 +93,8 @@ StringSet ParsedDerivation::getRequiredSystemFeatures() const
StringSet res; StringSet res;
for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings())) for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings()))
res.insert(i); res.insert(i);
if (!derivationHasKnownOutputPaths(drv.type()))
res.insert("ca-derivations");
return res; return res;
} }

View file

@ -1,5 +1,6 @@
#include "realisation.hh" #include "realisation.hh"
#include "store-api.hh" #include "store-api.hh"
#include "closure.hh"
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
namespace nix { namespace nix {
@ -21,11 +22,52 @@ std::string DrvOutput::to_string() const {
return strHash() + "!" + outputName; return strHash() + "!" + outputName;
} }
std::set<Realisation> Realisation::closure(Store & store, const std::set<Realisation> & startOutputs)
{
std::set<Realisation> res;
Realisation::closure(store, startOutputs, res);
return res;
}
void Realisation::closure(Store & store, const std::set<Realisation> & startOutputs, std::set<Realisation> & res)
{
auto getDeps = [&](const Realisation& current) -> std::set<Realisation> {
std::set<Realisation> res;
for (auto& [currentDep, _] : current.dependentRealisations) {
if (auto currentRealisation = store.queryRealisation(currentDep))
res.insert(*currentRealisation);
else
throw Error(
"Unrealised derivation '%s'", currentDep.to_string());
}
return res;
};
computeClosure<Realisation>(
startOutputs, res,
[&](const Realisation& current,
std::function<void(std::promise<std::set<Realisation>>&)>
processEdges) {
std::promise<std::set<Realisation>> promise;
try {
auto res = getDeps(current);
promise.set_value(res);
} catch (...) {
promise.set_exception(std::current_exception());
}
return processEdges(promise);
});
}
nlohmann::json Realisation::toJSON() const { nlohmann::json Realisation::toJSON() const {
auto jsonDependentRealisations = nlohmann::json::object();
for (auto & [depId, depOutPath] : dependentRealisations)
jsonDependentRealisations.emplace(depId.to_string(), depOutPath.to_string());
return nlohmann::json{ return nlohmann::json{
{"id", id.to_string()}, {"id", id.to_string()},
{"outPath", outPath.to_string()}, {"outPath", outPath.to_string()},
{"signatures", signatures}, {"signatures", signatures},
{"dependentRealisations", jsonDependentRealisations},
}; };
} }
@ -51,10 +93,16 @@ Realisation Realisation::fromJSON(
if (auto signaturesIterator = json.find("signatures"); signaturesIterator != json.end()) if (auto signaturesIterator = json.find("signatures"); signaturesIterator != json.end())
signatures.insert(signaturesIterator->begin(), signaturesIterator->end()); signatures.insert(signaturesIterator->begin(), signaturesIterator->end());
std::map <DrvOutput, StorePath> dependentRealisations;
if (auto jsonDependencies = json.find("dependentRealisations"); jsonDependencies != json.end())
for (auto & [jsonDepId, jsonDepOutPath] : jsonDependencies->get<std::map<std::string, std::string>>())
dependentRealisations.insert({DrvOutput::parse(jsonDepId), StorePath(jsonDepOutPath)});
return Realisation{ return Realisation{
.id = DrvOutput::parse(getField("id")), .id = DrvOutput::parse(getField("id")),
.outPath = StorePath(getField("outPath")), .outPath = StorePath(getField("outPath")),
.signatures = signatures, .signatures = signatures,
.dependentRealisations = dependentRealisations,
}; };
} }
@ -92,6 +140,16 @@ StorePath RealisedPath::path() const {
return std::visit([](auto && arg) { return arg.getPath(); }, raw); return std::visit([](auto && arg) { return arg.getPath(); }, raw);
} }
bool Realisation::isCompatibleWith(const Realisation & other) const
{
assert (id == other.id);
if (outPath == other.outPath) {
assert(dependentRealisations == other.dependentRealisations);
return true;
}
return false;
}
void RealisedPath::closure( void RealisedPath::closure(
Store& store, Store& store,
const RealisedPath::Set& startPaths, const RealisedPath::Set& startPaths,

View file

@ -28,6 +28,14 @@ struct Realisation {
StringSet signatures; StringSet signatures;
/**
* The realisations that are required for the current one to be valid.
*
* When importing this realisation, the store will first check that all its
* dependencies exist, and map to the correct output path
*/
std::map<DrvOutput, StorePath> dependentRealisations;
nlohmann::json toJSON() const; nlohmann::json toJSON() const;
static Realisation fromJSON(const nlohmann::json& json, const std::string& whence); static Realisation fromJSON(const nlohmann::json& json, const std::string& whence);
@ -36,6 +44,11 @@ struct Realisation {
bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const; bool checkSignature(const PublicKeys & publicKeys, const std::string & sig) const;
size_t checkSignatures(const PublicKeys & publicKeys) const; size_t checkSignatures(const PublicKeys & publicKeys) const;
static std::set<Realisation> closure(Store &, const std::set<Realisation> &);
static void closure(Store &, const std::set<Realisation> &, std::set<Realisation>& res);
bool isCompatibleWith(const Realisation & other) const;
StorePath getPath() const { return outPath; } StorePath getPath() const { return outPath; }
GENERATE_CMP(Realisation, me->id, me->outPath); GENERATE_CMP(Realisation, me->id, me->outPath);

View file

@ -337,6 +337,13 @@ ValidPathInfo Store::addToStoreSlow(std::string_view name, const Path & srcPath,
return info; return info;
} }
StringSet StoreConfig::getDefaultSystemFeatures()
{
auto res = settings.systemFeatures.get();
if (settings.isExperimentalFeatureEnabled("ca-derivations"))
res.insert("ca-derivations");
return res;
}
Store::Store(const Params & params) Store::Store(const Params & params)
: StoreConfig(params) : StoreConfig(params)
@ -816,20 +823,39 @@ std::map<StorePath, StorePath> copyPaths(ref<Store> srcStore, ref<Store> dstStor
RepairFlag repair, CheckSigsFlag checkSigs, SubstituteFlag substitute) RepairFlag repair, CheckSigsFlag checkSigs, SubstituteFlag substitute)
{ {
StorePathSet storePaths; StorePathSet storePaths;
std::set<Realisation> realisations; std::set<Realisation> toplevelRealisations;
for (auto & path : paths) { for (auto & path : paths) {
storePaths.insert(path.path()); storePaths.insert(path.path());
if (auto realisation = std::get_if<Realisation>(&path.raw)) { if (auto realisation = std::get_if<Realisation>(&path.raw)) {
settings.requireExperimentalFeature("ca-derivations"); settings.requireExperimentalFeature("ca-derivations");
realisations.insert(*realisation); toplevelRealisations.insert(*realisation);
} }
} }
auto pathsMap = copyPaths(srcStore, dstStore, storePaths, repair, checkSigs, substitute); auto pathsMap = copyPaths(srcStore, dstStore, storePaths, repair, checkSigs, substitute);
ThreadPool pool;
try { try {
for (auto & realisation : realisations) { // Copy the realisation closure
dstStore->registerDrvOutput(realisation, checkSigs); processGraph<Realisation>(
} pool, Realisation::closure(*srcStore, toplevelRealisations),
} catch (MissingExperimentalFeature & e) { [&](const Realisation& current) -> std::set<Realisation> {
std::set<Realisation> children;
for (const auto& [drvOutput, _] : current.dependentRealisations) {
auto currentChild = srcStore->queryRealisation(drvOutput);
if (!currentChild)
throw Error(
"Incomplete realisation closure: '%s' is a "
"dependency of '%s' but isnt registered",
drvOutput.to_string(), current.id.to_string());
children.insert(*currentChild);
}
return children;
},
[&](const Realisation& current) -> void {
dstStore->registerDrvOutput(current, checkSigs);
});
} catch (MissingExperimentalFeature& e) {
// Don't fail if the remote doesn't support CA derivations is it might // Don't fail if the remote doesn't support CA derivations is it might
// not be within our control to change that, and we might still want // not be within our control to change that, and we might still want
// to at least copy the output paths. // to at least copy the output paths.

View file

@ -180,6 +180,8 @@ struct StoreConfig : public Config
StoreConfig() = delete; StoreConfig() = delete;
StringSet getDefaultSystemFeatures();
virtual ~StoreConfig() { } virtual ~StoreConfig() { }
virtual const std::string name() = 0; virtual const std::string name() = 0;
@ -196,7 +198,7 @@ struct StoreConfig : public Config
Setting<bool> wantMassQuery{this, false, "want-mass-query", "whether this substituter can be queried efficiently for path validity"}; Setting<bool> wantMassQuery{this, false, "want-mass-query", "whether this substituter can be queried efficiently for path validity"};
Setting<StringSet> systemFeatures{this, settings.systemFeatures, Setting<StringSet> systemFeatures{this, getDefaultSystemFeatures(),
"system-features", "system-features",
"Optional features that the system this store builds on implements (like \"kvm\")."}; "Optional features that the system this store builds on implements (like \"kvm\")."};
@ -869,4 +871,9 @@ std::pair<std::string, Store::Params> splitUriAndParams(const std::string & uri)
std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv); std::optional<ContentAddress> getDerivationCA(const BasicDerivation & drv);
std::map<DrvOutput, StorePath> drvOutputReferences(
Store & store,
const Derivation & drv,
const StorePath & outputPath);
} }

View file

@ -25,6 +25,8 @@
} }
#define GENERATE_EQUAL(args...) GENERATE_ONE_CMP(==, args) #define GENERATE_EQUAL(args...) GENERATE_ONE_CMP(==, args)
#define GENERATE_LEQ(args...) GENERATE_ONE_CMP(<, args) #define GENERATE_LEQ(args...) GENERATE_ONE_CMP(<, args)
#define GENERATE_NEQ(args...) GENERATE_ONE_CMP(!=, args)
#define GENERATE_CMP(args...) \ #define GENERATE_CMP(args...) \
GENERATE_EQUAL(args) \ GENERATE_EQUAL(args) \
GENERATE_LEQ(args) GENERATE_LEQ(args) \
GENERATE_NEQ(args)

View file

@ -46,7 +46,7 @@ public:
: printBuildLogs(printBuildLogs) : printBuildLogs(printBuildLogs)
{ {
systemd = getEnv("IN_SYSTEMD") == "1"; systemd = getEnv("IN_SYSTEMD") == "1";
tty = isatty(STDERR_FILENO); tty = shouldANSI();
} }
bool isVerbose() override { bool isVerbose() override {

View file

@ -32,7 +32,7 @@ ParsedURL parseURL(const std::string & url)
auto isFile = scheme.find("file") != std::string::npos; auto isFile = scheme.find("file") != std::string::npos;
if (authority && *authority != "" && isFile) if (authority && *authority != "" && isFile)
throw Error("file:// URL '%s' has unexpected authority '%s'", throw BadURL("file:// URL '%s' has unexpected authority '%s'",
url, *authority); url, *authority);
if (isFile && path.empty()) if (isFile && path.empty())

View file

@ -1372,6 +1372,12 @@ void ignoreException()
} }
} }
bool shouldANSI()
{
return isatty(STDERR_FILENO)
&& getEnv("TERM").value_or("dumb") != "dumb"
&& !getEnv("NO_COLOR").has_value();
}
std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned int width) std::string filterANSIEscapes(const std::string & s, bool filterAll, unsigned int width)
{ {

View file

@ -482,6 +482,9 @@ constexpr char treeLast[] = "└───";
constexpr char treeLine[] = ""; constexpr char treeLine[] = "";
constexpr char treeNull[] = " "; constexpr char treeNull[] = " ";
/* Determine whether ANSI escape sequences are appropriate for the
present output. */
bool shouldANSI();
/* Truncate a string to 'width' printable characters. If 'filterAll' /* Truncate a string to 'width' printable characters. If 'filterAll'
is true, all ANSI escape sequences are filtered out. Otherwise, is true, all ANSI escape sequences are filtered out. Otherwise,

View file

@ -392,6 +392,12 @@ static void main_nix_build(int argc, char * * argv)
if (dryRun) return; if (dryRun) return;
if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
auto resolvedDrv = drv.tryResolve(*store);
assert(resolvedDrv && "Successfully resolved the derivation");
drv = *resolvedDrv;
}
// Set the environment. // Set the environment.
auto env = getEnv(); auto env = getEnv();

View file

@ -8,7 +8,7 @@
#include "affinity.hh" #include "affinity.hh"
#include "progress-bar.hh" #include "progress-bar.hh"
#include <regex> #include <nlohmann/json.hpp>
using namespace nix; using namespace nix;
@ -25,94 +25,142 @@ static DevelopSettings developSettings;
static GlobalConfig::Register rDevelopSettings(&developSettings); static GlobalConfig::Register rDevelopSettings(&developSettings);
struct Var
{
bool exported = true;
bool associative = false;
std::string quoted; // quoted string or array
};
struct BuildEnvironment struct BuildEnvironment
{ {
std::map<std::string, Var> env; struct String
std::string bashFunctions; {
}; bool exported;
std::string value;
BuildEnvironment readEnvironment(const Path & path) bool operator == (const String & other) const
{ {
BuildEnvironment res; return exported == other.exported && value == other.value;
}
};
std::set<std::string> exported; using Array = std::vector<std::string>;
debug("reading environment file '%s'", path); using Associative = std::map<std::string, std::string>;
auto file = readFile(path); using Value = std::variant<String, Array, Associative>;
auto pos = file.cbegin(); std::map<std::string, Value> vars;
std::map<std::string, std::string> bashFunctions;
static std::string varNameRegex = static BuildEnvironment fromJSON(std::string_view in)
R"re((?:[a-zA-Z_][a-zA-Z0-9_]*))re"; {
BuildEnvironment res;
static std::string simpleStringRegex = std::set<std::string> exported;
R"re((?:[a-zA-Z0-9_/:\.\-\+=]*))re";
static std::string dquotedStringRegex = auto json = nlohmann::json::parse(in);
R"re((?:\$?"(?:[^"\\]|\\[$`"\\\n])*"))re";
static std::string squotedStringRegex = for (auto & [name, info] : json["variables"].items()) {
R"re((?:\$?(?:'(?:[^'\\]|\\[abeEfnrtv\\'"?])*'|\\')+))re"; std::string type = info["type"];
if (type == "var" || type == "exported")
static std::string indexedArrayRegex = res.vars.insert({name, BuildEnvironment::String { .exported = type == "exported", .value = info["value"] }});
R"re((?:\(( *\[[0-9]+\]="(?:[^"\\]|\\.)*")*\)))re"; else if (type == "array")
res.vars.insert({name, (Array) info["value"]});
static std::regex declareRegex( else if (type == "associative")
"^declare -a?x (" + varNameRegex + ")(=(" + res.vars.insert({name, (Associative) info["value"]});
dquotedStringRegex + "|" + indexedArrayRegex + "))?\n");
static std::regex varRegex(
"^(" + varNameRegex + ")=(" + simpleStringRegex + "|" + squotedStringRegex + "|" + indexedArrayRegex + ")\n");
/* Note: we distinguish between an indexed and associative array
using the space before the closing parenthesis. Will
undoubtedly regret this some day. */
static std::regex assocArrayRegex(
"^(" + varNameRegex + ")=" + R"re((?:\(( *\[[^\]]+\]="(?:[^"\\]|\\.)*")* *\)))re" + "\n");
static std::regex functionRegex(
"^" + varNameRegex + " \\(\\) *\n");
while (pos != file.end()) {
std::smatch match;
if (std::regex_search(pos, file.cend(), match, declareRegex, std::regex_constants::match_continuous)) {
pos = match[0].second;
exported.insert(match[1]);
} }
else if (std::regex_search(pos, file.cend(), match, varRegex, std::regex_constants::match_continuous)) { for (auto & [name, def] : json["bashFunctions"].items()) {
pos = match[0].second; res.bashFunctions.insert({name, def});
res.env.insert({match[1], Var { .exported = exported.count(match[1]) > 0, .quoted = match[2] }});
} }
else if (std::regex_search(pos, file.cend(), match, assocArrayRegex, std::regex_constants::match_continuous)) { return res;
pos = match[0].second;
res.env.insert({match[1], Var { .associative = true, .quoted = match[2] }});
}
else if (std::regex_search(pos, file.cend(), match, functionRegex, std::regex_constants::match_continuous)) {
res.bashFunctions = std::string(pos, file.cend());
break;
}
else throw Error("shell environment '%s' has unexpected line '%s'",
path, file.substr(pos - file.cbegin(), 60));
} }
res.env.erase("__output"); std::string toJSON() const
{
auto res = nlohmann::json::object();
return res; auto vars2 = nlohmann::json::object();
} for (auto & [name, value] : vars) {
auto info = nlohmann::json::object();
if (auto str = std::get_if<String>(&value)) {
info["type"] = str->exported ? "exported" : "var";
info["value"] = str->value;
}
else if (auto arr = std::get_if<Array>(&value)) {
info["type"] = "array";
info["value"] = *arr;
}
else if (auto arr = std::get_if<Associative>(&value)) {
info["type"] = "associative";
info["value"] = *arr;
}
vars2[name] = std::move(info);
}
res["variables"] = std::move(vars2);
res["bashFunctions"] = bashFunctions;
auto json = res.dump();
assert(BuildEnvironment::fromJSON(json) == *this);
return json;
}
void toBash(std::ostream & out, const std::set<std::string> & ignoreVars) const
{
for (auto & [name, value] : vars) {
if (!ignoreVars.count(name)) {
if (auto str = std::get_if<String>(&value)) {
out << fmt("%s=%s\n", name, shellEscape(str->value));
if (str->exported)
out << fmt("export %s\n", name);
}
else if (auto arr = std::get_if<Array>(&value)) {
out << "declare -a " << name << "=(";
for (auto & s : *arr)
out << shellEscape(s) << " ";
out << ")\n";
}
else if (auto arr = std::get_if<Associative>(&value)) {
out << "declare -A " << name << "=(";
for (auto & [n, v] : *arr)
out << "[" << shellEscape(n) << "]=" << shellEscape(v) << " ";
out << ")\n";
}
}
}
for (auto & [name, def] : bashFunctions) {
out << name << " ()\n{\n" << def << "}\n";
}
}
static std::string getString(const Value & value)
{
if (auto str = std::get_if<String>(&value))
return str->value;
else
throw Error("bash variable is not a string");
}
static Array getStrings(const Value & value)
{
if (auto str = std::get_if<String>(&value))
return tokenizeString<Array>(str->value);
else if (auto arr = std::get_if<Array>(&value)) {
return *arr;
} else if (auto assoc = std::get_if<Associative>(&value)) {
Array assocKeys;
std::for_each(assoc->begin(), assoc->end(), [&](auto & n) { assocKeys.push_back(n.first); });
return assocKeys;
}
else
throw Error("bash variable is not a string or array");
}
bool operator == (const BuildEnvironment & other) const
{
return vars == other.vars && bashFunctions == other.bashFunctions;
}
};
const static std::string getEnvSh = const static std::string getEnvSh =
#include "get-env.sh.gen.hh" #include "get-env.sh.gen.hh"
@ -144,17 +192,26 @@ StorePath getDerivationEnvironment(ref<Store> store, const StorePath & drvPath)
/* Rehash and write the derivation. FIXME: would be nice to use /* Rehash and write the derivation. FIXME: would be nice to use
'buildDerivation', but that's privileged. */ 'buildDerivation', but that's privileged. */
drv.name += "-env"; drv.name += "-env";
for (auto & output : drv.outputs) {
output.second = { .output = DerivationOutputInputAddressed { .path = StorePath::dummy } };
drv.env[output.first] = "";
}
drv.inputSrcs.insert(std::move(getEnvShPath)); drv.inputSrcs.insert(std::move(getEnvShPath));
Hash h = std::get<0>(hashDerivationModulo(*store, drv, true)); if (settings.isExperimentalFeatureEnabled("ca-derivations")) {
for (auto & output : drv.outputs) {
output.second = {
.output = DerivationOutputDeferred{},
};
drv.env[output.first] = hashPlaceholder(output.first);
}
} else {
for (auto & output : drv.outputs) {
output.second = { .output = DerivationOutputInputAddressed { .path = StorePath::dummy } };
drv.env[output.first] = "";
}
Hash h = std::get<0>(hashDerivationModulo(*store, drv, true));
for (auto & output : drv.outputs) { for (auto & output : drv.outputs) {
auto outPath = store->makeOutputPath(output.first, h, drv.name); auto outPath = store->makeOutputPath(output.first, h, drv.name);
output.second = { .output = DerivationOutputInputAddressed { .path = outPath } }; output.second = { .output = DerivationOutputInputAddressed { .path = outPath } };
drv.env[output.first] = store->printStorePath(outPath); drv.env[output.first] = store->printStorePath(outPath);
}
} }
auto shellDrvPath = writeDerivation(*store, drv); auto shellDrvPath = writeDerivation(*store, drv);
@ -162,8 +219,7 @@ StorePath getDerivationEnvironment(ref<Store> store, const StorePath & drvPath)
/* Build the derivation. */ /* Build the derivation. */
store->buildPaths({DerivedPath::Built{shellDrvPath}}); store->buildPaths({DerivedPath::Built{shellDrvPath}});
for (auto & [_0, outputAndOptPath] : drv.outputsAndOptPaths(*store)) { for (auto & [_0, optPath] : store->queryPartialDerivationOutputMap(shellDrvPath)) {
auto & [_1, optPath] = outputAndOptPath;
assert(optPath); assert(optPath);
auto & outPath = *optPath; auto & outPath = *optPath;
assert(store->isValidPath(outPath)); assert(store->isValidPath(outPath));
@ -177,19 +233,15 @@ StorePath getDerivationEnvironment(ref<Store> store, const StorePath & drvPath)
struct Common : InstallableCommand, MixProfile struct Common : InstallableCommand, MixProfile
{ {
std::set<string> ignoreVars{ std::set<std::string> ignoreVars{
"BASHOPTS", "BASHOPTS",
"EUID",
"HOME", // FIXME: don't ignore in pure mode? "HOME", // FIXME: don't ignore in pure mode?
"HOSTNAME",
"NIX_BUILD_TOP", "NIX_BUILD_TOP",
"NIX_ENFORCE_PURITY", "NIX_ENFORCE_PURITY",
"NIX_LOG_FD", "NIX_LOG_FD",
"NIX_REMOTE", "NIX_REMOTE",
"PPID", "PPID",
"PWD",
"SHELLOPTS", "SHELLOPTS",
"SHLVL",
"SSL_CERT_FILE", // FIXME: only want to ignore /no-cert-file.crt "SSL_CERT_FILE", // FIXME: only want to ignore /no-cert-file.crt
"TEMP", "TEMP",
"TEMPDIR", "TEMPDIR",
@ -225,22 +277,10 @@ struct Common : InstallableCommand, MixProfile
out << "nix_saved_PATH=\"$PATH\"\n"; out << "nix_saved_PATH=\"$PATH\"\n";
for (auto & i : buildEnvironment.env) { buildEnvironment.toBash(out, ignoreVars);
if (!ignoreVars.count(i.first) && !hasPrefix(i.first, "BASH_")) {
if (i.second.associative)
out << fmt("declare -A %s=(%s)\n", i.first, i.second.quoted);
else {
out << fmt("%s=%s\n", i.first, i.second.quoted);
if (i.second.exported)
out << fmt("export %s\n", i.first);
}
}
}
out << "PATH=\"$PATH:$nix_saved_PATH\"\n"; out << "PATH=\"$PATH:$nix_saved_PATH\"\n";
out << buildEnvironment.bashFunctions << "\n";
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"})
out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i); out << fmt("export %s=\"$NIX_BUILD_TOP\"\n", i);
@ -250,24 +290,16 @@ struct Common : InstallableCommand, MixProfile
auto script = out.str(); auto script = out.str();
/* Substitute occurrences of output paths. */ /* Substitute occurrences of output paths. */
auto outputs = buildEnvironment.env.find("outputs"); auto outputs = buildEnvironment.vars.find("outputs");
assert(outputs != buildEnvironment.env.end()); assert(outputs != buildEnvironment.vars.end());
// FIXME: properly unquote 'outputs'. // FIXME: properly unquote 'outputs'.
StringMap rewrites; StringMap rewrites;
for (auto & outputName : tokenizeString<std::vector<std::string>>(replaceStrings(outputs->second.quoted, "'", ""))) { for (auto & outputName : BuildEnvironment::getStrings(outputs->second)) {
// Hacky way to obtain the key of an associate array. This is needed for strctured attrs where auto from = buildEnvironment.vars.find(outputName);
// `outputs` is an associative array. If the regex isn't matched, the non-structured-attrs behavior will assert(from != buildEnvironment.vars.end());
// be used.
std::regex ptrn(R"re(\[([A-z0-9]+)\]=.*)re");
std::smatch match;
if (std::regex_match(outputName, match, ptrn)) {
outputName = match[1];
}
auto from = buildEnvironment.env.find(outputName);
assert(from != buildEnvironment.env.end());
// FIXME: unquote // FIXME: unquote
rewrites.insert({from->second.quoted, outputsDir + "/" + outputName}); rewrites.insert({BuildEnvironment::getString(from->second), outputsDir + "/" + outputName});
} }
/* Substitute redirects. */ /* Substitute redirects. */
@ -321,7 +353,9 @@ struct Common : InstallableCommand, MixProfile
updateProfile(shellOutPath); updateProfile(shellOutPath);
return {readEnvironment(strPath), strPath}; debug("reading environment file '%s'", strPath);
return {BuildEnvironment::fromJSON(readFile(strPath)), strPath};
} }
}; };
@ -443,13 +477,17 @@ struct CmdDevelop : Common, MixEnvironment
try { try {
auto state = getEvalState(); auto state = getEvalState();
auto nixpkgsLockFlags = lockFlags;
nixpkgsLockFlags.inputOverrides = {};
nixpkgsLockFlags.inputUpdates = {};
auto bashInstallable = std::make_shared<InstallableFlake>( auto bashInstallable = std::make_shared<InstallableFlake>(
this, this,
state, state,
installable->nixpkgsFlakeRef(), installable->nixpkgsFlakeRef(),
Strings{"bashInteractive"}, Strings{"bashInteractive"},
Strings{"legacyPackages." + settings.thisSystem.get() + "."}, Strings{"legacyPackages." + settings.thisSystem.get() + "."},
lockFlags); nixpkgsLockFlags);
shell = state->store->printStorePath( shell = state->store->printStorePath(
toStorePath(state->store, Realise::Outputs, OperateOn::Output, bashInstallable)) + "/bin/bash"; toStorePath(state->store, Realise::Outputs, OperateOn::Output, bashInstallable)) + "/bin/bash";
@ -470,7 +508,7 @@ struct CmdDevelop : Common, MixEnvironment
} }
}; };
struct CmdPrintDevEnv : Common struct CmdPrintDevEnv : Common, MixJSON
{ {
std::string description() override std::string description() override
{ {
@ -492,7 +530,10 @@ struct CmdPrintDevEnv : Common
stopProgressBar(); stopProgressBar();
std::cout << makeRcScript(store, buildEnvironment); logger->writeToStdout(
json
? buildEnvironment.toJSON()
: makeRcScript(store, buildEnvironment));
} }
}; };

View file

@ -84,6 +84,7 @@ struct CmdFlakeUpdate : FlakeCommand
lockFlags.recreateLockFile = true; lockFlags.recreateLockFile = true;
lockFlags.writeLockFile = true; lockFlags.writeLockFile = true;
lockFlags.applyNixConfig = true;
lockFlake(); lockFlake();
} }
@ -114,6 +115,7 @@ struct CmdFlakeLock : FlakeCommand
settings.tarballTtl = 0; settings.tarballTtl = 0;
lockFlags.writeLockFile = true; lockFlags.writeLockFile = true;
lockFlags.applyNixConfig = true;
lockFlake(); lockFlake();
} }
@ -270,6 +272,8 @@ struct CmdFlakeCheck : FlakeCommand
settings.readOnlyMode = !build; settings.readOnlyMode = !build;
auto state = getEvalState(); auto state = getEvalState();
lockFlags.applyNixConfig = true;
auto flake = lockFlake(); auto flake = lockFlake();
bool hasErrors = false; bool hasErrors = false;

View file

@ -8,6 +8,109 @@ if [[ -n $stdenv ]]; then
source $stdenv/setup source $stdenv/setup
fi fi
# Better to use compgen, but stdenv bash doesn't have it.
__vars="$(declare -p)"
__functions="$(declare -F)"
__dumpEnv() {
printf '{\n'
printf ' "bashFunctions": {\n'
local __first=1
while read __line; do
if ! [[ $__line =~ ^declare\ -f\ (.*) ]]; then continue; fi
__fun_name="${BASH_REMATCH[1]}"
__fun_body="$(type $__fun_name)"
if [[ $__fun_body =~ \{(.*)\} ]]; then
if [[ -z $__first ]]; then printf ',\n'; else __first=; fi
__fun_body="${BASH_REMATCH[1]}"
printf " "
__escapeString "$__fun_name"
printf ':'
__escapeString "$__fun_body"
else
printf "Cannot parse definition of function '%s'.\n" "$__fun_name" >&2
return 1
fi
done < <(printf "%s\n" "$__functions")
printf '\n },\n'
printf ' "variables": {\n'
local __first=1
while read __line; do
if ! [[ $__line =~ ^declare\ (-[^ ])\ ([^=]*) ]]; then continue; fi
local type="${BASH_REMATCH[1]}"
local __var_name="${BASH_REMATCH[2]}"
if [[ $__var_name =~ ^BASH_ || \
$__var_name = _ || \
$__var_name = DIRSTACK || \
$__var_name = EUID || \
$__var_name = FUNCNAME || \
$__var_name = HISTCMD || \
$__var_name = HOSTNAME || \
$__var_name = GROUPS || \
$__var_name = PIPESTATUS || \
$__var_name = PWD || \
$__var_name = RANDOM || \
$__var_name = SHLVL || \
$__var_name = SECONDS \
]]; then continue; fi
if [[ -z $__first ]]; then printf ',\n'; else __first=; fi
printf " "
__escapeString "$__var_name"
printf ': {'
# FIXME: handle -i, -r, -n.
if [[ $type == -x ]]; then
printf '"type": "exported", "value": '
__escapeString "${!__var_name}"
elif [[ $type == -- ]]; then
printf '"type": "var", "value": '
__escapeString "${!__var_name}"
elif [[ $type == -a ]]; then
printf '"type": "array", "value": ['
local __first2=1
__var_name="$__var_name[@]"
for __i in "${!__var_name}"; do
if [[ -z $__first2 ]]; then printf ', '; else __first2=; fi
__escapeString "$__i"
printf ' '
done
printf ']'
elif [[ $type == -A ]]; then
printf '"type": "associative", "value": {\n'
local __first2=1
declare -n __var_name2="$__var_name"
for __i in "${!__var_name2[@]}"; do
if [[ -z $__first2 ]]; then printf ',\n'; else __first2=; fi
printf " "
__escapeString "$__i"
printf ": "
__escapeString "${__var_name2[$__i]}"
done
printf '\n }'
else
printf '"type": "unknown"'
fi
printf "}"
done < <(printf "%s\n" "$__vars")
printf '\n }\n}'
}
__escapeString() {
local __s="$1"
__s="${__s//\\/\\\\}"
__s="${__s//\"/\\\"}"
__s="${__s//$'\n'/\\n}"
__s="${__s//$'\r'/\\r}"
__s="${__s//$'\t'/\\t}"
printf '"%s"' "$__s"
}
# In case of `__structuredAttrs = true;` the list of outputs is an associative # In case of `__structuredAttrs = true;` the list of outputs is an associative
# array with a format like `outname => /nix/store/hash-drvname-outname`, so `__olist` # array with a format like `outname => /nix/store/hash-drvname-outname`, so `__olist`
# must contain the array's keys (hence `${!...[@]}`) in this case. # must contain the array's keys (hence `${!...[@]}`) in this case.
@ -19,8 +122,7 @@ fi
for __output in $__olist; do for __output in $__olist; do
if [[ -z $__done ]]; then if [[ -z $__done ]]; then
export > "${!__output}" __dumpEnv > ${!__output}
set >> "${!__output}"
__done=1 __done=1
else else
echo -n >> "${!__output}" echo -n >> "${!__output}"

View file

@ -8,12 +8,43 @@ R""(
# . <(nix print-dev-env nixpkgs#hello) # . <(nix print-dev-env nixpkgs#hello)
``` ```
* Get the build environment in JSON format:
```console
# nix print-dev-env nixpkgs#hello --json
```
The output will look like this:
```json
{
"bashFunctions": {
"buildPhase": " \n runHook preBuild;\n...",
...
},
"variables": {
"src": {
"type": "exported",
"value": "/nix/store/3x7dwzq014bblazs7kq20p9hyzz0qh8g-hello-2.10.tar.gz"
},
"postUnpackHooks": {
"type": "array",
"value": ["_updateSourceDateEpochFromSourceRoot"]
},
...
}
}
```
# Description # Description
This command prints a shell script that can be sourced by `b`ash and This command prints a shell script that can be sourced by `bash` and
that sets the environment variables and shell functions defined by the that sets the variables and shell functions defined by the build
build process of *installable*. This allows you to get a similar build process of *installable*. This allows you to get a similar build
environment in your current shell rather than in a subshell (as with environment in your current shell rather than in a subshell (as with
`nix develop`). `nix develop`).
With `--json`, the output is a JSON serialisation of the variables and
functions defined by the build process.
)"" )""

View file

@ -15,6 +15,7 @@ R""(
``` ```
* Remove all packages: * Remove all packages:
```console ```console
# nix profile remove '.*' # nix profile remove '.*'
``` ```

View file

@ -21,6 +21,13 @@ R""(
# nix registry add nixpkgs/nixos-20.03 ~/Dev/nixpkgs # nix registry add nixpkgs/nixos-20.03 ~/Dev/nixpkgs
``` ```
* Add `nixpkgs` pointing to `github:nixos/nixpkgs` to your custom flake
registry:
```console
nix registry add --registry ./custom-flake-registry.json nixpkgs github:nixos/nixpkgs
```
# Description # Description
This command adds an entry to the user registry that maps flake This command adds an entry to the user registry that maps flake

View file

@ -24,6 +24,13 @@ R""(
``` ```
* Pin `nixpkgs` in a custom registry to its most recent Git revision:
```console
# nix registry pin --registry ./custom-flake-registry.json nixpkgs
```
# Description # Description
This command adds an entry to the user registry that maps flake This command adds an entry to the user registry that maps flake

View file

@ -8,6 +8,12 @@ R""(
# nix registry remove nixpkgs # nix registry remove nixpkgs
``` ```
* Remove the entry `nixpkgs` from a custom registry:
```console
# nix registry remove --registry ./custom-flake-registry.json nixpkgs
```
# Description # Description
This command removes from the user registry any entry for flake This command removes from the user registry any entry for flake

View file

@ -10,6 +10,46 @@
using namespace nix; using namespace nix;
using namespace nix::flake; using namespace nix::flake;
class RegistryCommand : virtual Args
{
std::string registry_path;
std::shared_ptr<fetchers::Registry> registry;
public:
RegistryCommand()
{
addFlag({
.longName = "registry",
.description = "The registry to operate on.",
.labels = {"registry"},
.handler = {&registry_path},
});
}
std::shared_ptr<fetchers::Registry> getRegistry()
{
if (registry) return registry;
if (registry_path.empty()) {
registry = fetchers::getUserRegistry();
} else {
registry = fetchers::getCustomRegistry(registry_path);
}
return registry;
}
Path getRegistryPath()
{
if (registry_path.empty()) {
return fetchers::getUserRegistryPath();
} else {
return registry_path;
}
}
};
struct CmdRegistryList : StoreCommand struct CmdRegistryList : StoreCommand
{ {
std::string description() override std::string description() override
@ -45,7 +85,7 @@ struct CmdRegistryList : StoreCommand
} }
}; };
struct CmdRegistryAdd : MixEvalArgs, Command struct CmdRegistryAdd : MixEvalArgs, Command, RegistryCommand
{ {
std::string fromUrl, toUrl; std::string fromUrl, toUrl;
@ -71,16 +111,16 @@ struct CmdRegistryAdd : MixEvalArgs, Command
{ {
auto fromRef = parseFlakeRef(fromUrl); auto fromRef = parseFlakeRef(fromUrl);
auto toRef = parseFlakeRef(toUrl); auto toRef = parseFlakeRef(toUrl);
auto registry = getRegistry();
fetchers::Attrs extraAttrs; fetchers::Attrs extraAttrs;
if (toRef.subdir != "") extraAttrs["dir"] = toRef.subdir; if (toRef.subdir != "") extraAttrs["dir"] = toRef.subdir;
auto userRegistry = fetchers::getUserRegistry(); registry->remove(fromRef.input);
userRegistry->remove(fromRef.input); registry->add(fromRef.input, toRef.input, extraAttrs);
userRegistry->add(fromRef.input, toRef.input, extraAttrs); registry->write(getRegistryPath());
userRegistry->write(fetchers::getUserRegistryPath());
} }
}; };
struct CmdRegistryRemove : virtual Args, MixEvalArgs, Command struct CmdRegistryRemove : RegistryCommand, Command
{ {
std::string url; std::string url;
@ -103,19 +143,21 @@ struct CmdRegistryRemove : virtual Args, MixEvalArgs, Command
void run() override void run() override
{ {
auto userRegistry = fetchers::getUserRegistry(); auto registry = getRegistry();
userRegistry->remove(parseFlakeRef(url).input); registry->remove(parseFlakeRef(url).input);
userRegistry->write(fetchers::getUserRegistryPath()); registry->write(getRegistryPath());
} }
}; };
struct CmdRegistryPin : virtual Args, EvalCommand struct CmdRegistryPin : RegistryCommand, EvalCommand
{ {
std::string url; std::string url;
std::string locked;
std::string description() override std::string description() override
{ {
return "pin a flake to its current version in user flake registry"; return "pin a flake to its current version or to the current version of a flake URL";
} }
std::string doc() override std::string doc() override
@ -128,18 +170,31 @@ struct CmdRegistryPin : virtual Args, EvalCommand
CmdRegistryPin() CmdRegistryPin()
{ {
expectArg("url", &url); expectArg("url", &url);
expectArgs({
.label = "locked",
.optional = true,
.handler = {&locked},
.completer = {[&](size_t, std::string_view prefix) {
completeFlakeRef(getStore(), prefix);
}}
});
} }
void run(nix::ref<nix::Store> store) override void run(nix::ref<nix::Store> store) override
{ {
if (locked.empty()) {
locked = url;
}
auto registry = getRegistry();
auto ref = parseFlakeRef(url); auto ref = parseFlakeRef(url);
auto userRegistry = fetchers::getUserRegistry(); auto locked_ref = parseFlakeRef(locked);
userRegistry->remove(ref.input); registry->remove(ref.input);
auto [tree, resolved] = ref.resolve(store).input.fetch(store); auto [tree, resolved] = locked_ref.resolve(store).input.fetch(store);
fetchers::Attrs extraAttrs; fetchers::Attrs extraAttrs;
if (ref.subdir != "") extraAttrs["dir"] = ref.subdir; if (ref.subdir != "") extraAttrs["dir"] = ref.subdir;
userRegistry->add(ref.input, resolved, extraAttrs); registry->add(ref.input, resolved, extraAttrs);
userRegistry->write(fetchers::getUserRegistryPath()); registry->write(getRegistryPath());
} }
}; };

View file

@ -104,6 +104,26 @@ NixRepl::~NixRepl()
write_history(historyFile.c_str()); write_history(historyFile.c_str());
} }
string runNix(Path program, const Strings & args,
const std::optional<std::string> & input = {})
{
auto experimentalFeatures = concatStringsSep(" ", settings.experimentalFeatures.get());
auto nixConf = getEnv("NIX_CONFIG").value_or("");
nixConf.append("\nexperimental-features = " + experimentalFeatures);
auto subprocessEnv = getEnv();
subprocessEnv["NIX_CONFIG"] = nixConf;
RunOptions opts(settings.nixBinDir+ "/" + program, args);
opts.input = input;
opts.environment = subprocessEnv;
auto res = runProgram(opts);
if (!statusOk(res.first))
throw ExecError(res.first, fmt("program '%1%' %2%", program, statusToString(res.first)));
return res.second;
}
static NixRepl * curRepl; // ugly static NixRepl * curRepl; // ugly
static char * completionCallback(char * s, int *match) { static char * completionCallback(char * s, int *match) {
@ -463,7 +483,7 @@ bool NixRepl::processLine(string line)
state->callFunction(f, v, result, Pos()); state->callFunction(f, v, result, Pos());
StorePath drvPath = getDerivationPath(result); StorePath drvPath = getDerivationPath(result);
runProgram(settings.nixBinDir + "/nix-shell", true, {state->store->printStorePath(drvPath)}); runNix("nix-shell", {state->store->printStorePath(drvPath)});
} }
else if (command == ":b" || command == ":i" || command == ":s") { else if (command == ":b" || command == ":i" || command == ":s") {
@ -477,7 +497,7 @@ bool NixRepl::processLine(string line)
but doing it in a child makes it easier to recover from but doing it in a child makes it easier to recover from
problems / SIGINT. */ problems / SIGINT. */
try { try {
runProgram(settings.nixBinDir + "/nix", true, {"build", "--no-link", drvPathRaw}); runNix("nix", {"build", "--no-link", drvPathRaw});
auto drv = state->store->readDerivation(drvPath); auto drv = state->store->readDerivation(drvPath);
std::cout << std::endl << "this derivation produced the following outputs:" << std::endl; std::cout << std::endl << "this derivation produced the following outputs:" << std::endl;
for (auto & i : drv.outputsAndOptPaths(*state->store)) for (auto & i : drv.outputsAndOptPaths(*state->store))
@ -485,9 +505,9 @@ bool NixRepl::processLine(string line)
} catch (ExecError &) { } catch (ExecError &) {
} }
} else if (command == ":i") { } else if (command == ":i") {
runProgram(settings.nixBinDir + "/nix-env", true, {"-i", drvPathRaw}); runNix("nix-env", {"-i", drvPathRaw});
} else { } else {
runProgram(settings.nixBinDir + "/nix-shell", true, {drvPathRaw}); runNix("nix-shell", {drvPathRaw});
} }
} }

View file

@ -1,4 +1,4 @@
ifeq ($(OS), Darwin) ifdef HOST_DARWIN
programs += resolve-system-dependencies programs += resolve-system-dependencies
endif endif

View file

@ -4,4 +4,6 @@ file=build-hook-ca-floating.nix
sed -i 's/experimental-features .*/& ca-derivations/' "$NIX_CONF_DIR"/nix.conf sed -i 's/experimental-features .*/& ca-derivations/' "$NIX_CONF_DIR"/nix.conf
CONTENT_ADDRESSED=true
source build-remote.sh source build-remote.sh

View file

@ -6,12 +6,17 @@ unset NIX_STATE_DIR
function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; } function join_by { local d=$1; shift; echo -n "$1"; shift; printf "%s" "${@/#/$d}"; }
EXTRA_SYSTEM_FEATURES=()
if [[ -n "$CONTENT_ADDRESSED" ]]; then
EXTRA_SYSTEM_FEATURES=("ca-derivations")
fi
builders=( builders=(
# system-features will automatically be added to the outer URL, but not inner # system-features will automatically be added to the outer URL, but not inner
# remote-store URL. # remote-store URL.
"ssh://localhost?remote-store=$TEST_ROOT/machine1?system-features=foo - - 1 1 foo" "ssh://localhost?remote-store=$TEST_ROOT/machine1?system-features=$(join_by "%20" foo ${EXTRA_SYSTEM_FEATURES[@]}) - - 1 1 $(join_by "," foo ${EXTRA_SYSTEM_FEATURES[@]})"
"$TEST_ROOT/machine2 - - 1 1 bar" "$TEST_ROOT/machine2 - - 1 1 $(join_by "," bar ${EXTRA_SYSTEM_FEATURES[@]})"
"ssh-ng://localhost?remote-store=$TEST_ROOT/machine3?system-features=baz - - 1 1 baz" "ssh-ng://localhost?remote-store=$TEST_ROOT/machine3?system-features=$(join_by "%20" baz ${EXTRA_SYSTEM_FEATURES[@]}) - - 1 1 $(join_by "," baz ${EXTRA_SYSTEM_FEATURES[@]})"
) )
chmod -R +w $TEST_ROOT/machine* || true chmod -R +w $TEST_ROOT/machine* || true

View file

@ -1,7 +1,7 @@
source common.sh source common.sh
expectedJSONRegex='\[\{"drvPath":".*multiple-outputs-a.drv","outputs":\{"first":".*multiple-outputs-a-first","second":".*multiple-outputs-a-second"}},\{"drvPath":".*multiple-outputs-b.drv","outputs":\{"out":".*multiple-outputs-b"}}]' expectedJSONRegex='\[\{"drvPath":".*multiple-outputs-a.drv","outputs":\{"first":".*multiple-outputs-a-first","second":".*multiple-outputs-a-second"}},\{"drvPath":".*multiple-outputs-b.drv","outputs":\{"out":".*multiple-outputs-b"}}]'
nix build -f multiple-outputs.nix --json a.all b.all | jq --exit-status ' nix build -f multiple-outputs.nix --json a.all b.all --no-link | jq --exit-status '
(.[0] | (.[0] |
(.drvPath | match(".*multiple-outputs-a.drv")) and (.drvPath | match(".*multiple-outputs-a.drv")) and
(.outputs.first | match(".*multiple-outputs-a-first")) and (.outputs.first | match(".*multiple-outputs-a-first")) and
@ -10,10 +10,10 @@ nix build -f multiple-outputs.nix --json a.all b.all | jq --exit-status '
(.drvPath | match(".*multiple-outputs-b.drv")) and (.drvPath | match(".*multiple-outputs-b.drv")) and
(.outputs.out | match(".*multiple-outputs-b"))) (.outputs.out | match(".*multiple-outputs-b")))
' '
testNormalization () { testNormalization () {
clearStore clearStore
outPath=$(nix-build ./simple.nix) outPath=$(nix-build ./simple.nix --no-out-link)
test "$(stat -c %Y $outPath)" -eq 1 test "$(stat -c %Y $outPath)" -eq 1
} }
testNormalization testNormalization

View file

@ -0,0 +1,26 @@
source ./common.sh
sed -i 's/experimental-features .*/& ca-derivations ca-references/' "$NIX_CONF_DIR"/nix.conf
export REMOTE_STORE_DIR="$TEST_ROOT/remote_store"
export REMOTE_STORE="file://$REMOTE_STORE_DIR"
rm -rf $REMOTE_STORE_DIR
clearStore
# Build dep1 and push that to the binary cache.
# This entails building (and pushing) current-time.
nix copy --to "$REMOTE_STORE" -f nondeterministic.nix dep1
clearStore
sleep 2 # To make sure that `$(date)` will be different
# Build dep2.
# As weve cleared the cache, well have to rebuild current-time. And because
# the current time isnt the same as before, this will yield a new (different)
# realisation
nix build -f nondeterministic.nix dep2 --no-link
# Build something that depends both on dep1 and dep2.
# If everything goes right, we should rebuild dep2 rather than fetch it from
# the cache (because that would mean duplicating `current-time` in the closure),
# and have `dep1 == dep2`.
nix build --substituters "$REMOTE_STORE" -f nondeterministic.nix toplevel --no-require-sigs --no-link

12
tests/ca/gc.sh Executable file
View file

@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Ensure that garbage collection works properly with ca derivations
source common.sh
sed -i 's/experimental-features .*/& ca-derivations ca-references/' "$NIX_CONF_DIR"/nix.conf
export NIX_TESTS_CA_BY_DEFAULT=1
cd ..
source gc.sh

10
tests/ca/nix-shell.sh Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env bash
source common.sh
sed -i 's/experimental-features .*/& ca-derivations ca-references nix-command flakes/' "$NIX_CONF_DIR"/nix.conf
CONTENT_ADDRESSED=true
cd ..
source ./nix-shell.sh

View file

@ -0,0 +1,35 @@
with import ./config.nix;
let mkCADerivation = args: mkDerivation ({
__contentAddressed = true;
outputHashMode = "recursive";
outputHashAlgo = "sha256";
} // args);
in
rec {
currentTime = mkCADerivation {
name = "current-time";
buildCommand = ''
mkdir $out
echo $(date) > $out/current-time
'';
};
dep = seed: mkCADerivation {
name = "dep";
inherit seed;
buildCommand = ''
echo ${currentTime} > $out
'';
};
dep1 = dep 1;
dep2 = dep 2;
toplevel = mkCADerivation {
name = "toplevel";
buildCommand = ''
test ${dep1} == ${dep2}
touch $out
'';
};
}

11
tests/ca/post-hook.sh Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
source common.sh
sed -i 's/experimental-features .*/& ca-derivations ca-references nix-command flakes/' "$NIX_CONF_DIR"/nix.conf
export NIX_TESTS_CA_BY_DEFAULT=1
cd ..
source ./post-hook.sh

11
tests/ca/recursive.sh Executable file
View file

@ -0,0 +1,11 @@
#!/usr/bin/env bash
source common.sh
sed -i 's/experimental-features .*/& ca-derivations ca-references nix-command flakes/' "$NIX_CONF_DIR"/nix.conf
export NIX_TESTS_CA_BY_DEFAULT=1
cd ..
source ./recursive.sh

View file

@ -17,11 +17,15 @@ buildDrvs () {
# Populate the remote cache # Populate the remote cache
clearStore clearStore
buildDrvs --post-build-hook ../push-to-store.sh nix copy --to $REMOTE_STORE --file ./content-addressed.nix
# Restart the build on an empty store, ensuring that we don't build # Restart the build on an empty store, ensuring that we don't build
clearStore clearStore
buildDrvs --substitute --substituters $REMOTE_STORE --no-require-sigs -j0 buildDrvs --substitute --substituters $REMOTE_STORE --no-require-sigs -j0 transitivelyDependentCA
# Check that the thing weve just substituted has its realisation stored
nix realisation info --file ./content-addressed.nix transitivelyDependentCA
# Check that its dependencies have it too
nix realisation info --file ./content-addressed.nix dependentCA rootCA
# Same thing, but # Same thing, but
# 1. With non-ca derivations # 1. With non-ca derivations

View file

@ -44,7 +44,7 @@ with import ./config.nix;
}; };
hashmismatch = import <nix/fetchurl.nix> { hashmismatch = import <nix/fetchurl.nix> {
url = "file://" + toString ./dummy; url = "file://" + builtins.getEnv "TMPDIR" + "/dummy";
sha256 = "0mdqa9w1p6cmli6976v4wi0sw9r4p5prkj7lzfd1877wk11c9c73"; sha256 = "0mdqa9w1p6cmli6976v4wi0sw9r4p5prkj7lzfd1877wk11c9c73";
}; };

View file

@ -74,12 +74,13 @@ nix-build check.nix -A fetchurl --no-out-link --check
nix-build check.nix -A fetchurl --no-out-link --repair nix-build check.nix -A fetchurl --no-out-link --repair
[[ $(cat $path) != foo ]] [[ $(cat $path) != foo ]]
echo 'Hello World' > $TMPDIR/dummy
nix-build check.nix -A hashmismatch --no-out-link || status=$? nix-build check.nix -A hashmismatch --no-out-link || status=$?
[ "$status" = "102" ] [ "$status" = "102" ]
echo -n > ./dummy echo -n > $TMPDIR/dummy
nix-build check.nix -A hashmismatch --no-out-link nix-build check.nix -A hashmismatch --no-out-link
echo 'Hello World' > ./dummy echo 'Hello World' > $TMPDIR/dummy
nix-build check.nix -A hashmismatch --no-out-link --check || status=$? nix-build check.nix -A hashmismatch --no-out-link --check || status=$?
[ "$status" = "102" ] [ "$status" = "102" ]

View file

@ -1,3 +1,12 @@
let
contentAddressedByDefault = builtins.getEnv "NIX_TESTS_CA_BY_DEFAULT" == "1";
caArgs = if contentAddressedByDefault then {
__contentAddressed = true;
outputHashMode = "recursive";
outputHashAlgo = "sha256";
} else {};
in
rec { rec {
shell = "@bash@"; shell = "@bash@";
@ -13,6 +22,6 @@ rec {
builder = shell; builder = shell;
args = ["-e" args.builder or (builtins.toFile "builder-${args.name}.sh" "if [ -e .attrs.sh ]; then source .attrs.sh; fi; eval \"$buildCommand\"")]; args = ["-e" args.builder or (builtins.toFile "builder-${args.name}.sh" "if [ -e .attrs.sh ]; then source .attrs.sh; fi; eval \"$buildCommand\"")];
PATH = path; PATH = path;
} // removeAttrs args ["builder" "meta"]) } // caArgs // removeAttrs args ["builder" "meta"])
// { meta = args.meta or {}; }; // { meta = args.meta or {}; };
} }

1
tests/dummy Normal file
View file

@ -0,0 +1 @@
Hello World

View file

@ -189,3 +189,7 @@ path8=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$rep
rev4=$(git -C $repo rev-parse HEAD) rev4=$(git -C $repo rev-parse HEAD)
rev4_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; }).rev") rev4_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; }).rev")
[[ $rev4 = $rev4_nix ]] [[ $rev4 = $rev4_nix ]]
# The name argument should be handled
path9=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; name = \"foo\"; }).outPath")
[[ $path9 =~ -foo$ ]]

View file

@ -94,3 +94,8 @@ hg commit --cwd $repo -m 'Bla3'
path4=$(nix eval --impure --refresh --raw --expr "(builtins.fetchMercurial file://$repo).outPath") path4=$(nix eval --impure --refresh --raw --expr "(builtins.fetchMercurial file://$repo).outPath")
[[ $path2 = $path4 ]] [[ $path2 = $path4 ]]
echo paris > $repo/hello
# Passing a `name` argument should be reflected in the output path
path5=$(nix eval -vvvvv --impure --refresh --raw --expr "(builtins.fetchMercurial { url = \"file://$repo\"; name = \"foo\"; } ).outPath")
[[ $path5 =~ -foo$ ]]

View file

@ -90,76 +90,14 @@ EOF
git -C $nonFlakeDir add README.md git -C $nonFlakeDir add README.md
git -C $nonFlakeDir commit -m 'Initial' git -C $nonFlakeDir commit -m 'Initial'
cat > $registry <<EOF # Construct a custom registry, additionally test the --registry flag
{ nix registry add --registry $registry flake1 git+file://$flake1Dir
"version": 2, nix registry add --registry $registry flake2 git+file://$flake2Dir
"flakes": [ nix registry add --registry $registry flake3 git+file://$flake3Dir
{ "from": { nix registry add --registry $registry flake4 flake3
"type": "indirect", nix registry add --registry $registry flake5 hg+file://$flake5Dir
"id": "flake1" nix registry add --registry $registry nixpkgs flake1
}, nix registry add --registry $registry templates git+file://$templatesDir
"to": {
"type": "git",
"url": "file://$flake1Dir"
}
},
{ "from": {
"type": "indirect",
"id": "flake2"
},
"to": {
"type": "git",
"url": "file://$flake2Dir"
}
},
{ "from": {
"type": "indirect",
"id": "flake3"
},
"to": {
"type": "git",
"url": "file://$flake3Dir"
}
},
{ "from": {
"type": "indirect",
"id": "flake4"
},
"to": {
"type": "indirect",
"id": "flake3"
}
},
{ "from": {
"type": "indirect",
"id": "flake5"
},
"to": {
"type": "hg",
"url": "file://$flake5Dir"
}
},
{ "from": {
"type": "indirect",
"id": "nixpkgs"
},
"to": {
"type": "indirect",
"id": "flake1"
}
},
{ "from": {
"type": "indirect",
"id": "templates"
},
"to": {
"type": "git",
"url": "file://$templatesDir"
}
}
]
}
EOF
# Test 'nix flake list'. # Test 'nix flake list'.
[[ $(nix registry list | wc -l) == 7 ]] [[ $(nix registry list | wc -l) == 7 ]]
@ -405,6 +343,8 @@ nix registry add flake1 flake3
[[ $(nix registry list | wc -l) == 8 ]] [[ $(nix registry list | wc -l) == 8 ]]
nix registry pin flake1 nix registry pin flake1
[[ $(nix registry list | wc -l) == 8 ]] [[ $(nix registry list | wc -l) == 8 ]]
nix registry pin flake1 flake3
[[ $(nix registry list | wc -l) == 8 ]]
nix registry remove flake1 nix registry remove flake1
[[ $(nix registry list | wc -l) == 7 ]] [[ $(nix registry list | wc -l) == 7 ]]

View file

@ -12,7 +12,7 @@ ln -sf $outPath "$NIX_STATE_DIR"/gcroots/foo
nix-store --gc --print-roots | grep $outPath nix-store --gc --print-roots | grep $outPath
nix-store --gc --print-live | grep $outPath nix-store --gc --print-live | grep $outPath
nix-store --gc --print-dead | grep $drvPath nix-store --gc --print-dead | grep $drvPath
if nix-store --gc --print-dead | grep $outPath; then false; fi if nix-store --gc --print-dead | grep -E $outPath$; then false; fi
nix-store --gc --print-dead nix-store --gc --print-dead

View file

@ -35,5 +35,3 @@ nix-store --init
# Did anything happen? # Did anything happen?
test -e "$NIX_STATE_DIR"/db/db.sqlite test -e "$NIX_STATE_DIR"/db/db.sqlite
echo 'Hello World' > ./dummy

View file

@ -2,6 +2,7 @@ nix_tests = \
hash.sh lang.sh add.sh simple.sh dependencies.sh \ hash.sh lang.sh add.sh simple.sh dependencies.sh \
config.sh \ config.sh \
gc.sh \ gc.sh \
ca/gc.sh \
gc-concurrent.sh \ gc-concurrent.sh \
gc-auto.sh \ gc-auto.sh \
referrers.sh user-envs.sh logging.sh nix-build.sh misc.sh fixed.sh \ referrers.sh user-envs.sh logging.sh nix-build.sh misc.sh fixed.sh \
@ -38,6 +39,7 @@ nix_tests = \
search.sh \ search.sh \
nix-copy-ssh.sh \ nix-copy-ssh.sh \
post-hook.sh \ post-hook.sh \
ca/post-hook.sh \
function-trace.sh \ function-trace.sh \
recursive.sh \ recursive.sh \
describe-stores.sh \ describe-stores.sh \
@ -46,9 +48,12 @@ nix_tests = \
compute-levels.sh \ compute-levels.sh \
ca/build.sh \ ca/build.sh \
ca/build-with-garbage-path.sh \ ca/build-with-garbage-path.sh \
ca/duplicate-realisation-in-closure.sh \
ca/substitute.sh \ ca/substitute.sh \
ca/signatures.sh \ ca/signatures.sh \
ca/nix-shell.sh \
ca/nix-run.sh \ ca/nix-run.sh \
ca/recursive.sh \
ca/nix-copy.sh ca/nix-copy.sh
# parallel.sh # parallel.sh

View file

@ -2,6 +2,20 @@ source common.sh
clearStore clearStore
if [[ -n ${CONTENT_ADDRESSED:-} ]]; then
nix-shell () {
command nix-shell --arg contentAddressed true "$@"
}
nix_develop() {
nix develop --arg contentAddressed true "$@"
}
else
nix_develop() {
nix develop "$@"
}
fi
# Test nix-shell -A # Test nix-shell -A
export IMPURE_VAR=foo export IMPURE_VAR=foo
export SELECTED_IMPURE_VAR=baz export SELECTED_IMPURE_VAR=baz
@ -41,7 +55,7 @@ output=$(NIX_PATH=nixpkgs=shell.nix nix-shell --pure -p foo bar --run 'echo "$(f
[ "$output" = "foo bar" ] [ "$output" = "foo bar" ]
# Test nix-shell shebang mode # Test nix-shell shebang mode
sed -e "s|@ENV_PROG@|$(type -p env)|" shell.shebang.sh > $TEST_ROOT/shell.shebang.sh sed -e "s|@ENV_PROG@|$(type -P env)|" shell.shebang.sh > $TEST_ROOT/shell.shebang.sh
chmod a+rx $TEST_ROOT/shell.shebang.sh chmod a+rx $TEST_ROOT/shell.shebang.sh
output=$($TEST_ROOT/shell.shebang.sh abc def) output=$($TEST_ROOT/shell.shebang.sh abc def)
@ -49,7 +63,7 @@ output=$($TEST_ROOT/shell.shebang.sh abc def)
# Test nix-shell shebang mode again with metacharacters in the filename. # Test nix-shell shebang mode again with metacharacters in the filename.
# First word of filename is chosen to not match any file in the test root. # First word of filename is chosen to not match any file in the test root.
sed -e "s|@ENV_PROG@|$(type -p env)|" shell.shebang.sh > $TEST_ROOT/spaced\ \\\'\"shell.shebang.sh sed -e "s|@ENV_PROG@|$(type -P env)|" shell.shebang.sh > $TEST_ROOT/spaced\ \\\'\"shell.shebang.sh
chmod a+rx $TEST_ROOT/spaced\ \\\'\"shell.shebang.sh chmod a+rx $TEST_ROOT/spaced\ \\\'\"shell.shebang.sh
output=$($TEST_ROOT/spaced\ \\\'\"shell.shebang.sh abc def) output=$($TEST_ROOT/spaced\ \\\'\"shell.shebang.sh abc def)
@ -58,7 +72,7 @@ output=$($TEST_ROOT/spaced\ \\\'\"shell.shebang.sh abc def)
# Test nix-shell shebang mode for ruby # Test nix-shell shebang mode for ruby
# This uses a fake interpreter that returns the arguments passed # This uses a fake interpreter that returns the arguments passed
# This, in turn, verifies the `rc` script is valid and the `load()` script (given using `-e`) is as expected. # This, in turn, verifies the `rc` script is valid and the `load()` script (given using `-e`) is as expected.
sed -e "s|@SHELL_PROG@|$(type -p nix-shell)|" shell.shebang.rb > $TEST_ROOT/shell.shebang.rb sed -e "s|@SHELL_PROG@|$(type -P nix-shell)|" shell.shebang.rb > $TEST_ROOT/shell.shebang.rb
chmod a+rx $TEST_ROOT/shell.shebang.rb chmod a+rx $TEST_ROOT/shell.shebang.rb
output=$($TEST_ROOT/shell.shebang.rb abc ruby) output=$($TEST_ROOT/shell.shebang.rb abc ruby)
@ -66,21 +80,27 @@ output=$($TEST_ROOT/shell.shebang.rb abc ruby)
# Test nix-shell shebang mode for ruby again with metacharacters in the filename. # Test nix-shell shebang mode for ruby again with metacharacters in the filename.
# Note: fake interpreter only space-separates args without adding escapes to its output. # Note: fake interpreter only space-separates args without adding escapes to its output.
sed -e "s|@SHELL_PROG@|$(type -p nix-shell)|" shell.shebang.rb > $TEST_ROOT/spaced\ \\\'\"shell.shebang.rb sed -e "s|@SHELL_PROG@|$(type -P nix-shell)|" shell.shebang.rb > $TEST_ROOT/spaced\ \\\'\"shell.shebang.rb
chmod a+rx $TEST_ROOT/spaced\ \\\'\"shell.shebang.rb chmod a+rx $TEST_ROOT/spaced\ \\\'\"shell.shebang.rb
output=$($TEST_ROOT/spaced\ \\\'\"shell.shebang.rb abc ruby) output=$($TEST_ROOT/spaced\ \\\'\"shell.shebang.rb abc ruby)
[ "$output" = '-e load(ARGV.shift) -- '"$TEST_ROOT"'/spaced \'\''"shell.shebang.rb abc ruby' ] [ "$output" = '-e load(ARGV.shift) -- '"$TEST_ROOT"'/spaced \'\''"shell.shebang.rb abc ruby' ]
# Test 'nix develop'. # Test 'nix develop'.
nix develop -f shell.nix shellDrv -c bash -c '[[ -n $stdenv ]]' nix_develop -f shell.nix shellDrv -c bash -c '[[ -n $stdenv ]]'
# Ensure `nix develop -c` preserves stdin # Ensure `nix develop -c` preserves stdin
echo foo | nix develop -f shell.nix shellDrv -c cat | grep -q foo echo foo | nix develop -f shell.nix shellDrv -c cat | grep -q foo
# Ensure `nix develop -c` actually executes the command if stdout isn't a terminal # Ensure `nix develop -c` actually executes the command if stdout isn't a terminal
nix develop -f shell.nix shellDrv -c echo foo |& grep -q foo nix_develop -f shell.nix shellDrv -c echo foo |& grep -q foo
# Test 'nix print-dev-env'. # Test 'nix print-dev-env'.
[[ $(nix print-dev-env -f shell.nix shellDrv --json | jq -r .variables.arr1.value[2]) = '3 4' ]]
source <(nix print-dev-env -f shell.nix shellDrv) source <(nix print-dev-env -f shell.nix shellDrv)
[[ -n $stdenv ]] [[ -n $stdenv ]]
[[ ${arr1[2]} = "3 4" ]]
[[ ${arr2[1]} = $'\n' ]]
[[ ${arr2[2]} = $'x\ny' ]]
[[ $(fun) = blabla ]]

View file

@ -4,7 +4,7 @@ clearStore
rm -f $TEST_ROOT/result rm -f $TEST_ROOT/result
export REMOTE_STORE=$TEST_ROOT/remote_store export REMOTE_STORE=file:$TEST_ROOT/remote_store
# Build the dependencies and push them to the remote store # Build the dependencies and push them to the remote store
nix-build -o $TEST_ROOT/result dependencies.nix --post-build-hook $PWD/push-to-store.sh nix-build -o $TEST_ROOT/result dependencies.nix --post-build-hook $PWD/push-to-store.sh

View file

@ -9,9 +9,9 @@ rm -f $TEST_ROOT/result
export unreachable=$(nix store add-path ./recursive.sh) export unreachable=$(nix store add-path ./recursive.sh)
NIX_BIN_DIR=$(dirname $(type -p nix)) nix --experimental-features 'nix-command recursive-nix' build -o $TEST_ROOT/result -L --impure --expr ' NIX_BIN_DIR=$(dirname $(type -p nix)) nix --extra-experimental-features 'nix-command recursive-nix' build -o $TEST_ROOT/result -L --impure --expr '
with import ./config.nix; with import ./config.nix;
mkDerivation { mkDerivation rec {
name = "recursive"; name = "recursive";
dummy = builtins.toFile "dummy" "bla bla"; dummy = builtins.toFile "dummy" "bla bla";
SHELL = shell; SHELL = shell;
@ -19,11 +19,13 @@ NIX_BIN_DIR=$(dirname $(type -p nix)) nix --experimental-features 'nix-command r
# Note: this is a string without context. # Note: this is a string without context.
unreachable = builtins.getEnv "unreachable"; unreachable = builtins.getEnv "unreachable";
NIX_TESTS_CA_BY_DEFAULT = builtins.getEnv "NIX_TESTS_CA_BY_DEFAULT";
requiredSystemFeatures = [ "recursive-nix" ]; requiredSystemFeatures = [ "recursive-nix" ];
buildCommand = '\'\'' buildCommand = '\'\''
mkdir $out mkdir $out
opts="--experimental-features nix-command" opts="--experimental-features nix-command ${if (NIX_TESTS_CA_BY_DEFAULT == "1") then "--extra-experimental-features ca-derivations" else ""}"
PATH=${builtins.getEnv "NIX_BIN_DIR"}:$PATH PATH=${builtins.getEnv "NIX_BIN_DIR"}:$PATH
@ -46,16 +48,15 @@ NIX_BIN_DIR=$(dirname $(type -p nix)) nix --experimental-features 'nix-command r
# Add it to our closure. # Add it to our closure.
ln -s $foobar $out/foobar ln -s $foobar $out/foobar
[[ $(nix $opts path-info --all | wc -l) -eq 3 ]] [[ $(nix $opts path-info --all | wc -l) -eq 4 ]]
# Build a derivation. # Build a derivation.
nix $opts build -L --impure --expr '\'' nix $opts build -L --impure --expr '\''
derivation { with import ${./config.nix};
mkDerivation {
name = "inner1"; name = "inner1";
builder = builtins.getEnv "SHELL"; buildCommand = "echo $fnord blaat > $out";
system = builtins.getEnv "system";
fnord = builtins.toFile "fnord" "fnord"; fnord = builtins.toFile "fnord" "fnord";
args = [ "-c" "echo $fnord blaat > $out" ];
} }
'\'' '\''

Some files were not shown because too many files have changed in this diff Show more