diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 47fa041e9..e16e6c62d 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -10,15 +10,8 @@ jobs:
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
+ with:
+ fetch-depth: 0
- uses: cachix/install-nix-action@v10
- - run: nix-build release.nix --arg nix '{ outPath = ./.; revCount = 123; shortRev = "abcdefgh"; }' --arg systems '[ builtins.currentSystem ]' -A installerScript -A perlBindings
- macos_perf_test:
- runs-on: macos-latest
- steps:
- - name: Disable syspolicy assessments
- run: |
- spctl --status
- sudo spctl --master-disable
- - uses: actions/checkout@v2
- - uses: cachix/install-nix-action@v10
- - run: nix-build release.nix --arg nix '{ outPath = ./.; revCount = 123; shortRev = "abcdefgh"; }' --arg systems '[ builtins.currentSystem ]' -A installerScript -A perlBindings
+ #- run: nix flake check
+ - run: nix-build -A checks.$(if [[ `uname` = Linux ]]; then echo x86_64-linux; else echo x86_64-darwin; fi)
diff --git a/.version b/.version
index 7208c2182..f398a2061 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-2.4
\ No newline at end of file
+3.0
\ No newline at end of file
diff --git a/Makefile b/Makefile
index 332e6e971..f472ca7e5 100644
--- a/Makefile
+++ b/Makefile
@@ -11,6 +11,7 @@ makefiles = \
src/resolve-system-dependencies/local.mk \
scripts/local.mk \
corepkgs/local.mk \
+ misc/bash/local.mk \
misc/systemd/local.mk \
misc/launchd/local.mk \
misc/upstart/local.mk \
diff --git a/Makefile.config.in b/Makefile.config.in
index b632444e8..5c245b8e9 100644
--- a/Makefile.config.in
+++ b/Makefile.config.in
@@ -19,6 +19,7 @@ LIBLZMA_LIBS = @LIBLZMA_LIBS@
OPENSSL_LIBS = @OPENSSL_LIBS@
PACKAGE_NAME = @PACKAGE_NAME@
PACKAGE_VERSION = @PACKAGE_VERSION@
+SHELL = @bash@
SODIUM_LIBS = @SODIUM_LIBS@
SQLITE3_LIBS = @SQLITE3_LIBS@
bash = @bash@
diff --git a/README.md b/README.md
index a1588284d..03c5deb7b 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ for more details.
On Linux and macOS the easiest way to Install Nix is to run the following shell command
(as a user other than root):
-```
+```console
$ curl -L https://nixos.org/nix/install | sh
```
@@ -20,27 +20,8 @@ Information on additional installation methods is available on the [Nix download
## Building And Developing
-### Building Nix
-
-You can build Nix using one of the targets provided by [release.nix](./release.nix):
-
-```
-$ nix-build ./release.nix -A build.aarch64-linux
-$ nix-build ./release.nix -A build.x86_64-darwin
-$ nix-build ./release.nix -A build.i686-linux
-$ nix-build ./release.nix -A build.x86_64-linux
-```
-
-### Development Environment
-
-You can use the provided `shell.nix` to get a working development environment:
-
-```
-$ nix-shell
-$ ./bootstrap.sh
-$ ./configure
-$ make
-```
+See our [Hacking guide](https://hydra.nixos.org/job/nix/master/build.x86_64-linux/latest/download-by-type/doc/manual#chap-hacking) in our manual for instruction on how to
+build nix from source with nix-build or how to get a development environment.
## Additional Resources
diff --git a/configure.ac b/configure.ac
index c3007b4b6..2f29cf864 100644
--- a/configure.ac
+++ b/configure.ac
@@ -123,6 +123,7 @@ AC_PATH_PROG(flex, flex, false)
AC_PATH_PROG(bison, bison, false)
AC_PATH_PROG(dot, dot)
AC_PATH_PROG(lsof, lsof, lsof)
+NEED_PROG(jq, jq)
AC_SUBST(coreutils, [$(dirname $(type -p cat))])
diff --git a/default.nix b/default.nix
new file mode 100644
index 000000000..71d1a80ad
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,3 @@
+(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) {
+ src = ./.;
+}).defaultNix
diff --git a/doc/manual/expressions/builder-syntax.xml b/doc/manual/expressions/builder-syntax.xml
deleted file mode 100644
index e51bade44..000000000
--- a/doc/manual/expressions/builder-syntax.xml
+++ /dev/null
@@ -1,119 +0,0 @@
-
-
-Builder Syntax
-
-Build script for GNU Hello
-(builder.sh)
-
-source $stdenv/setup
-
-PATH=$perl/bin:$PATH
-
-tar xvfz $src
-cd hello-*
-./configure --prefix=$out
-make
-make install
-
-
- shows the builder referenced
-from Hello's Nix expression (stored in
-pkgs/applications/misc/hello/ex-1/builder.sh).
-The builder can actually be made a lot shorter by using the
-generic builder functions provided by
-stdenv, but here we write out the build steps to
-elucidate what a builder does. It performs the following
-steps:
-
-
-
-
-
- When Nix runs a builder, it initially completely clears the
- environment (except for the attributes declared in the
- derivation). For instance, the PATH variable is
- emptyActually, it's initialised to
- /path-not-set to prevent Bash from setting it
- to a default value.. This is done to prevent
- undeclared inputs from being used in the build process. If for
- example the PATH contained
- /usr/bin, then you might accidentally use
- /usr/bin/gcc.
-
- So the first step is to set up the environment. This is
- done by calling the setup script of the
- standard environment. The environment variable
- stdenv points to the location of the standard
- environment being used. (It wasn't specified explicitly as an
- attribute in , but
- mkDerivation adds it automatically.)
-
-
-
-
-
- Since Hello needs Perl, we have to make sure that Perl is in
- the PATH. The perl environment
- variable points to the location of the Perl package (since it
- was passed in as an attribute to the derivation), so
- $perl/bin is the
- directory containing the Perl interpreter.
-
-
-
-
-
- Now we have to unpack the sources. The
- src attribute was bound to the result of
- fetching the Hello source tarball from the network, so the
- src environment variable points to the location in
- the Nix store to which the tarball was downloaded. After
- unpacking, we cd to the resulting source
- directory.
-
- The whole build is performed in a temporary directory
- created in /tmp, by the way. This directory is
- removed after the builder finishes, so there is no need to clean
- up the sources afterwards. Also, the temporary directory is
- always newly created, so you don't have to worry about files from
- previous builds interfering with the current build.
-
-
-
-
-
- GNU Hello is a typical Autoconf-based package, so we first
- have to run its configure script. In Nix
- every package is stored in a separate location in the Nix store,
- for instance
- /nix/store/9a54ba97fb71b65fda531012d0443ce2-hello-2.1.1.
- Nix computes this path by cryptographically hashing all attributes
- of the derivation. The path is passed to the builder through the
- out environment variable. So here we give
- configure the parameter
- --prefix=$out to cause Hello to be installed in
- the expected location.
-
-
-
-
-
- Finally we build Hello (make) and install
- it into the location specified by out
- (make install).
-
-
-
-
-
-If you are wondering about the absence of error checking on the
-result of various commands called in the builder: this is because the
-shell script is evaluated with Bash's option,
-which causes the script to be aborted if any command fails without an
-error check.
-
-
\ No newline at end of file
diff --git a/doc/manual/hacking.xml b/doc/manual/hacking.xml
index b671811d3..d25d4b84a 100644
--- a/doc/manual/hacking.xml
+++ b/doc/manual/hacking.xml
@@ -4,18 +4,37 @@
Hacking
-This section provides some notes on how to hack on Nix. To get
+This section provides some notes on how to hack on Nix. To get
the latest version of Nix from GitHub:
-$ git clone git://github.com/NixOS/nix.git
+$ git clone https://github.com/NixOS/nix.git
$ cd nix
-To build it and its dependencies:
+To build Nix for the current operating system/architecture use
+
-$ nix-build release.nix -A build.x86_64-linux
+$ nix-build
+
+or if you have a flakes-enabled nix:
+
+
+$ nix build
+
+
+This will build defaultPackage attribute defined in the flake.nix file.
+
+To build for other platforms add one of the following suffixes to it: aarch64-linux,
+i686-linux, x86_64-darwin, x86_64-linux.
+
+i.e.
+
+
+nix-build -A defaultPackage.x86_64-linux
+
+
To build all dependencies and start a shell in which all
@@ -27,13 +46,27 @@ $ nix-shell
To build Nix itself in this shell:
[nix-shell]$ ./bootstrap.sh
-[nix-shell]$ configurePhase
-[nix-shell]$ make
+[nix-shell]$ ./configure $configureFlags
+[nix-shell]$ make -j $NIX_BUILD_CORES
To install it in $(pwd)/inst and test it:
[nix-shell]$ make install
[nix-shell]$ make installcheck
+[nix-shell]$ ./inst/bin/nix --version
+nix (Nix) 2.4
+
+
+If you have a flakes-enabled nix you can replace:
+
+
+$ nix-shell
+
+
+by:
+
+
+$ nix develop
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 000000000..74326d294
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,26 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1591633336,
+ "narHash": "sha256-oVXv4xAnDJB03LvZGbC72vSVlIbbJr8tpjEW5o/Fdek=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "70717a337f7ae4e486ba71a500367cad697e5f09",
+ "type": "github"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "ref": "nixos-20.03-small",
+ "type": "indirect"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 6
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 000000000..a707e90e7
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,443 @@
+{
+ description = "The purely functional package manager";
+
+ inputs.nixpkgs.url = "nixpkgs/nixos-20.03-small";
+
+ outputs = { self, nixpkgs }:
+
+ let
+
+ version = builtins.readFile ./.version + versionSuffix;
+ versionSuffix =
+ if officialRelease
+ then ""
+ else "pre${builtins.substring 0 8 (self.lastModifiedDate or self.lastModified)}_${self.shortRev or "dirty"}";
+
+ officialRelease = false;
+
+ systems = [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" ];
+
+ forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system);
+
+ # Memoize nixpkgs for different platforms for efficiency.
+ nixpkgsFor = forAllSystems (system:
+ import nixpkgs {
+ inherit system;
+ overlays = [ self.overlay ];
+ }
+ );
+
+ commonDeps = pkgs: with pkgs; rec {
+ # Use "busybox-sandbox-shell" if present,
+ # if not (legacy) fallback and hope it's sufficient.
+ sh = pkgs.busybox-sandbox-shell or (busybox.override {
+ useMusl = true;
+ enableStatic = true;
+ enableMinimal = true;
+ extraConfig = ''
+ CONFIG_FEATURE_FANCY_ECHO y
+ CONFIG_FEATURE_SH_MATH y
+ CONFIG_FEATURE_SH_MATH_64 y
+
+ CONFIG_ASH y
+ CONFIG_ASH_OPTIMIZE_FOR_SIZE y
+
+ CONFIG_ASH_ALIAS y
+ CONFIG_ASH_BASH_COMPAT y
+ CONFIG_ASH_CMDCMD y
+ CONFIG_ASH_ECHO y
+ CONFIG_ASH_GETOPTS y
+ CONFIG_ASH_INTERNAL_GLOB y
+ CONFIG_ASH_JOB_CONTROL y
+ CONFIG_ASH_PRINTF y
+ CONFIG_ASH_TEST y
+ '';
+ });
+
+ configureFlags =
+ lib.optionals stdenv.isLinux [
+ "--with-sandbox-shell=${sh}/bin/busybox"
+ ];
+
+ buildDeps =
+ [ bison
+ flex
+ libxml2
+ libxslt
+ docbook5
+ docbook_xsl_ns
+ autoconf-archive
+ autoreconfHook
+
+ curl
+ bzip2 xz brotli zlib editline
+ openssl pkgconfig sqlite
+ libarchive
+ boost
+ (if lib.versionAtLeast lib.version "20.03pre"
+ then nlohmann_json
+ else nlohmann_json.override { multipleHeaders = true; })
+ nlohmann_json
+
+ # Tests
+ git
+ mercurial
+ jq
+ gmock
+ ]
+ ++ lib.optionals stdenv.isLinux [libseccomp utillinuxMinimal]
+ ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
+ ++ lib.optional (stdenv.isLinux || stdenv.isDarwin)
+ (aws-sdk-cpp.override {
+ apis = ["s3" "transfer"];
+ customMemoryManagement = false;
+ });
+
+ propagatedDeps =
+ [ (boehmgc.override { enableLargeConfig = true; })
+ ];
+
+ perlDeps =
+ [ perl
+ perlPackages.DBDSQLite
+ ];
+ };
+
+ in {
+
+ # A Nixpkgs overlay that overrides the 'nix' and
+ # 'nix.perl-bindings' packages.
+ overlay = final: prev: {
+
+ nix = with final; with commonDeps pkgs; (stdenv.mkDerivation {
+ name = "nix-${version}";
+
+ src = self;
+
+ VERSION_SUFFIX = versionSuffix;
+
+ outputs = [ "out" "dev" "doc" ];
+
+ buildInputs = buildDeps;
+
+ propagatedBuildInputs = propagatedDeps;
+
+ preConfigure =
+ ''
+ # Copy libboost_context so we don't get all of Boost in our closure.
+ # https://github.com/NixOS/nixpkgs/issues/45462
+ mkdir -p $out/lib
+ cp -pd ${boost}/lib/{libboost_context*,libboost_thread*,libboost_system*} $out/lib
+ rm -f $out/lib/*.a
+ ${lib.optionalString stdenv.isLinux ''
+ chmod u+w $out/lib/*.so.*
+ patchelf --set-rpath $out/lib:${stdenv.cc.cc.lib}/lib $out/lib/libboost_thread.so.*
+ ''}
+ '';
+
+ configureFlags = configureFlags ++
+ [ "--sysconfdir=/etc" ];
+
+ 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
+ '';
+
+ doInstallCheck = true;
+ installCheckFlags = "sysconfdir=$(out)/etc";
+
+ separateDebugInfo = true;
+ }) // {
+
+ perl-bindings = with final; stdenv.mkDerivation {
+ name = "nix-perl-${version}";
+
+ src = self;
+
+ buildInputs =
+ [ autoconf-archive
+ autoreconfHook
+ nix
+ curl
+ bzip2
+ xz
+ pkgconfig
+ pkgs.perl
+ boost
+ ]
+ ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium;
+
+ configureFlags = ''
+ --with-dbi=${perlPackages.DBI}/${pkgs.perl.libPrefix}
+ --with-dbd-sqlite=${perlPackages.DBDSQLite}/${pkgs.perl.libPrefix}
+ '';
+
+ enableParallelBuilding = true;
+
+ postUnpack = "sourceRoot=$sourceRoot/perl";
+ };
+
+ };
+
+ };
+
+ hydraJobs = {
+
+ # Binary package for various platforms.
+ build = nixpkgs.lib.genAttrs systems (system: nixpkgsFor.${system}.nix);
+
+ # Perl bindings for various platforms.
+ perlBindings = nixpkgs.lib.genAttrs systems (system: nixpkgsFor.${system}.nix.perl-bindings);
+
+ # Binary tarball for various platforms, containing a Nix store
+ # with the closure of 'nix' package, and the second half of
+ # the installation script.
+ binaryTarball = nixpkgs.lib.genAttrs systems (system:
+
+ with nixpkgsFor.${system};
+
+ let
+ installerClosureInfo = closureInfo { rootPaths = [ nix cacert ]; };
+ in
+
+ runCommand "nix-binary-tarball-${version}"
+ { #nativeBuildInputs = lib.optional (system != "aarch64-linux") shellcheck;
+ meta.description = "Distribution-independent Nix bootstrap binaries for ${system}";
+ }
+ ''
+ cp ${installerClosureInfo}/registration $TMPDIR/reginfo
+ 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/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/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/reginfo,$dir/.reginfo," \
+ --transform "s,$NIX_STORE,$dir/store,S" \
+ $TMPDIR/install $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
+ # to https://nixos.org/nix/install. It downloads the binary
+ # tarball for the user's system and calls the second half of the
+ # installation script.
+ installerScript =
+ with nixpkgsFor.x86_64-linux;
+ runCommand "installer-script"
+ { buildInputs = [ nix ];
+ }
+ ''
+ mkdir -p $out/nix-support
+
+ substitute ${./scripts/install.in} $out/install \
+ ${pkgs.lib.concatMapStrings
+ (system: "--replace '@binaryTarball_${system}@' $(nix --experimental-features nix-command hash-file --base16 --type sha256 ${self.hydraJobs.binaryTarball.${system}}/*.tar.xz) ")
+ [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" ]
+ } \
+ --replace '@nixVersion@' ${version}
+
+ echo "file installer $out/install" >> $out/nix-support/hydra-build-products
+ '';
+
+ # Line coverage analysis.
+ coverage =
+ with nixpkgsFor.x86_64-linux;
+ with commonDeps pkgs;
+
+ releaseTools.coverageAnalysis {
+ name = "nix-coverage-${version}";
+
+ src = self;
+
+ enableParallelBuilding = true;
+
+ buildInputs = buildDeps ++ propagatedDeps;
+
+ dontInstall = false;
+
+ doInstallCheck = true;
+
+ lcovFilter = [ "*/boost/*" "*-tab.*" ];
+
+ # We call `dot', and even though we just use it to
+ # syntax-check generated dot files, it still requires some
+ # fonts. So provide those.
+ FONTCONFIG_FILE = texFunctions.fontsConf;
+
+ # To test building without precompiled headers.
+ makeFlagsArray = [ "PRECOMPILE_HEADERS=0" ];
+ };
+
+ # System tests.
+ tests.remoteBuilds = import ./tests/remote-builds.nix {
+ system = "x86_64-linux";
+ inherit nixpkgs;
+ inherit (self) overlay;
+ };
+
+ tests.nix-copy-closure = import ./tests/nix-copy-closure.nix {
+ system = "x86_64-linux";
+ inherit nixpkgs;
+ inherit (self) overlay;
+ };
+
+ tests.githubFlakes = (import ./tests/github-flakes.nix rec {
+ system = "x86_64-linux";
+ inherit nixpkgs;
+ inherit (self) overlay;
+ });
+
+ tests.setuid = nixpkgs.lib.genAttrs
+ ["i686-linux" "x86_64-linux"]
+ (system:
+ import ./tests/setuid.nix rec {
+ inherit nixpkgs system;
+ inherit (self) overlay;
+ });
+
+ # Test whether the binary tarball works in an Ubuntu system.
+ tests.binaryTarball =
+ with nixpkgsFor.x86_64-linux;
+ vmTools.runInLinuxImage (runCommand "nix-binary-tarball-test"
+ { diskImage = vmTools.diskImages.ubuntu1204x86_64;
+ }
+ ''
+ set -x
+ useradd -m alice
+ su - alice -c 'tar xf ${self.hydraJobs.binaryTarball.x86_64-linux}/*.tar.*'
+ mkdir /dest-nix
+ mount -o bind /dest-nix /nix # Provide a writable /nix.
+ chown alice /nix
+ su - alice -c '_NIX_INSTALLER_TEST=1 ./nix-*/install'
+ su - alice -c 'nix-store --verify'
+ su - alice -c 'PAGER= nix-store -qR ${self.hydraJobs.build.x86_64-linux}'
+
+ # Check whether 'nix upgrade-nix' works.
+ cat > /tmp/paths.nix <queryPathInfo(store()->parseStorePath(path))->narHash.to_string(Base32, true);
+ auto s = store()->queryPathInfo(store()->parseStorePath(path))->narHash->to_string(Base32, true);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
} catch (Error & e) {
croak("%s", e.what());
@@ -106,7 +106,7 @@ SV * queryPathInfo(char * path, int base32)
XPUSHs(&PL_sv_undef);
else
XPUSHs(sv_2mortal(newSVpv(store()->printStorePath(*info->deriver).c_str(), 0)));
- auto s = info->narHash.to_string(base32 ? Base32 : Base16, true);
+ auto s = info->narHash->to_string(base32 ? Base32 : Base16, true);
XPUSHs(sv_2mortal(newSVpv(s.c_str(), 0)));
mXPUSHi(info->registrationTime);
mXPUSHi(info->narSize);
@@ -182,7 +182,7 @@ void importPaths(int fd, int dontCheckSigs)
PPCODE:
try {
FdSource source(fd);
- store()->importPaths(source, nullptr, dontCheckSigs ? NoCheckSigs : CheckSigs);
+ store()->importPaths(source, dontCheckSigs ? NoCheckSigs : CheckSigs);
} catch (Error & e) {
croak("%s", e.what());
}
@@ -304,7 +304,10 @@ SV * derivationFromPath(char * drvPath)
HV * outputs = newHV();
for (auto & i : drv.outputs)
- hv_store(outputs, i.first.c_str(), i.first.size(), newSVpv(store()->printStorePath(i.second.path).c_str(), 0), 0);
+ hv_store(
+ outputs, i.first.c_str(), i.first.size(),
+ newSVpv(store()->printStorePath(i.second.path(*store(), drv.name)).c_str(), 0),
+ 0);
hv_stores(hash, "outputs", newRV((SV *) outputs));
AV * inputDrvs = newAV();
diff --git a/release-common.nix b/release-common.nix
deleted file mode 100644
index 4316c3c23..000000000
--- a/release-common.nix
+++ /dev/null
@@ -1,82 +0,0 @@
-{ pkgs }:
-
-with pkgs;
-
-rec {
- # Use "busybox-sandbox-shell" if present,
- # if not (legacy) fallback and hope it's sufficient.
- sh = pkgs.busybox-sandbox-shell or (busybox.override {
- useMusl = true;
- enableStatic = true;
- enableMinimal = true;
- extraConfig = ''
- CONFIG_FEATURE_FANCY_ECHO y
- CONFIG_FEATURE_SH_MATH y
- CONFIG_FEATURE_SH_MATH_64 y
-
- CONFIG_ASH y
- CONFIG_ASH_OPTIMIZE_FOR_SIZE y
-
- CONFIG_ASH_ALIAS y
- CONFIG_ASH_BASH_COMPAT y
- CONFIG_ASH_CMDCMD y
- CONFIG_ASH_ECHO y
- CONFIG_ASH_GETOPTS y
- CONFIG_ASH_INTERNAL_GLOB y
- CONFIG_ASH_JOB_CONTROL y
- CONFIG_ASH_PRINTF y
- CONFIG_ASH_TEST y
- '';
- });
-
- configureFlags =
- lib.optionals stdenv.isLinux [
- "--with-sandbox-shell=${sh}/bin/busybox"
- ];
-
- buildDeps =
- [ bison
- flex
- libxml2
- libxslt
- docbook5
- docbook_xsl_ns
- autoconf-archive
- autoreconfHook
-
- curl
- bzip2 xz brotli zlib editline
- openssl pkgconfig sqlite
- libarchive
- boost
- nlohmann_json
-
- # Tests
- git
- mercurial
- gmock
- ]
- ++ lib.optionals stdenv.isLinux [libseccomp utillinuxMinimal]
- ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium
- ++ lib.optional (stdenv.isLinux || stdenv.isDarwin)
- ((aws-sdk-cpp.override {
- apis = ["s3" "transfer"];
- customMemoryManagement = false;
- }).overrideDerivation (args: {
- /*
- patches = args.patches or [] ++ [ (fetchpatch {
- url = https://github.com/edolstra/aws-sdk-cpp/commit/3e07e1f1aae41b4c8b340735ff9e8c735f0c063f.patch;
- sha256 = "1pij0v449p166f9l29x7ppzk8j7g9k9mp15ilh5qxp29c7fnvxy2";
- }) ];
- */
- }));
-
- propagatedDeps =
- [ (boehmgc.override { enableLargeConfig = true; })
- ];
-
- perlDeps =
- [ perl
- perlPackages.DBDSQLite
- ];
-}
diff --git a/release.nix b/release.nix
deleted file mode 100644
index fbf9e4721..000000000
--- a/release.nix
+++ /dev/null
@@ -1,303 +0,0 @@
-{ nix ? builtins.fetchGit ./.
-, nixpkgs ? builtins.fetchTarball https://github.com/NixOS/nixpkgs/archive/nixos-20.03-small.tar.gz
-, officialRelease ? false
-, systems ? [ "x86_64-linux" "i686-linux" "x86_64-darwin" "aarch64-linux" ]
-}:
-
-let
-
- pkgs = import nixpkgs { system = builtins.currentSystem or "x86_64-linux"; };
-
- version =
- builtins.readFile ./.version
- + (if officialRelease then "" else "pre${toString nix.revCount}_${nix.shortRev}");
-
- jobs = rec {
-
- build = pkgs.lib.genAttrs systems (system:
-
- let pkgs = import nixpkgs { inherit system; }; in
-
- with pkgs;
-
- with import ./release-common.nix { inherit pkgs; };
-
- stdenv.mkDerivation {
- name = "nix-${version}";
-
- src = nix;
-
- outputs = [ "out" "dev" "doc" ];
-
- buildInputs = buildDeps;
-
- propagatedBuildInputs = propagatedDeps;
-
- preConfigure =
- ''
- # Copy libboost_context so we don't get all of Boost in our closure.
- # https://github.com/NixOS/nixpkgs/issues/45462
- mkdir -p $out/lib
- cp -pd ${boost}/lib/{libboost_context*,libboost_thread*,libboost_system*} $out/lib
- rm -f $out/lib/*.a
- ${lib.optionalString stdenv.isLinux ''
- chmod u+w $out/lib/*.so.*
- patchelf --set-rpath $out/lib:${stdenv.cc.cc.lib}/lib $out/lib/libboost_thread.so.*
- ''}
-
- (cd perl; autoreconf --install --force --verbose)
- '';
-
- configureFlags = configureFlags ++
- [ "--sysconfdir=/etc" ];
-
- enableParallelBuilding = true;
-
- makeFlags = "profiledir=$(out)/etc/profile.d";
-
- installFlags = "sysconfdir=$(out)/etc";
-
- postInstall = ''
- mkdir -p $doc/nix-support
- echo "doc manual $doc/share/doc/nix/manual" >> $doc/nix-support/hydra-build-products
- '';
-
- doCheck = true;
-
- doInstallCheck = true;
- installCheckFlags = "sysconfdir=$(out)/etc";
-
- separateDebugInfo = true;
- });
-
-
- perlBindings = pkgs.lib.genAttrs systems (system:
-
- let pkgs = import nixpkgs { inherit system; }; in with pkgs;
-
- releaseTools.nixBuild {
- name = "nix-perl-${version}";
-
- src = nix;
-
- buildInputs =
- [ autoconf-archive
- autoreconfHook
- jobs.build.${system}
- curl
- bzip2
- xz
- pkgconfig
- pkgs.perl
- boost
- ]
- ++ lib.optional (stdenv.isLinux || stdenv.isDarwin) libsodium;
-
- configureFlags = ''
- --with-dbi=${perlPackages.DBI}/${pkgs.perl.libPrefix}
- --with-dbd-sqlite=${perlPackages.DBDSQLite}/${pkgs.perl.libPrefix}
- '';
-
- enableParallelBuilding = true;
-
- postUnpack = "sourceRoot=$sourceRoot/perl";
- });
-
-
- binaryTarball = pkgs.lib.genAttrs systems (system:
-
- with import nixpkgs { inherit system; };
-
- let
- toplevel = builtins.getAttr system jobs.build;
- installerClosureInfo = closureInfo { rootPaths = [ toplevel cacert ]; };
- in
-
- runCommand "nix-binary-tarball-${version}"
- { #nativeBuildInputs = lib.optional (system != "aarch64-linux") shellcheck;
- meta.description = "Distribution-independent Nix bootstrap binaries for ${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 ${toplevel} \
- --subst-var-by cacert ${cacert}
- substitute ${./scripts/install-darwin-multi-user.sh} $TMPDIR/install-darwin-multi-user.sh \
- --subst-var-by nix ${toplevel} \
- --subst-var-by cacert ${cacert}
- substitute ${./scripts/install-systemd-multi-user.sh} $TMPDIR/install-systemd-multi-user.sh \
- --subst-var-by nix ${toplevel} \
- --subst-var-by cacert ${cacert}
- substitute ${./scripts/install-multi-user.sh} $TMPDIR/install-multi-user \
- --subst-var-by nix ${toplevel} \
- --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)
- '');
-
-
- coverage =
- with pkgs;
-
- with import ./release-common.nix { inherit pkgs; };
-
- releaseTools.coverageAnalysis {
- name = "nix-coverage-${version}";
-
- src = nix;
-
- enableParallelBuilding = true;
-
- buildInputs = buildDeps ++ propagatedDeps;
-
- dontInstall = false;
-
- doInstallCheck = true;
-
- lcovFilter = [ "*/boost/*" "*-tab.*" ];
-
- # We call `dot', and even though we just use it to
- # syntax-check generated dot files, it still requires some
- # fonts. So provide those.
- FONTCONFIG_FILE = texFunctions.fontsConf;
-
- # To test building without precompiled headers.
- makeFlagsArray = [ "PRECOMPILE_HEADERS=0" ];
- };
-
-
- # System tests.
- tests.remoteBuilds = (import ./tests/remote-builds.nix rec {
- inherit nixpkgs;
- nix = build.x86_64-linux; system = "x86_64-linux";
- });
-
- tests.nix-copy-closure = (import ./tests/nix-copy-closure.nix rec {
- inherit nixpkgs;
- nix = build.x86_64-linux; system = "x86_64-linux";
- });
-
- tests.setuid = pkgs.lib.genAttrs
- ["i686-linux" "x86_64-linux"]
- (system:
- import ./tests/setuid.nix rec {
- inherit nixpkgs;
- nix = build.${system}; inherit system;
- });
-
- tests.binaryTarball =
- with import nixpkgs { system = "x86_64-linux"; };
- vmTools.runInLinuxImage (runCommand "nix-binary-tarball-test"
- { diskImage = vmTools.diskImages.ubuntu1204x86_64;
- }
- ''
- set -x
- useradd -m alice
- su - alice -c 'tar xf ${binaryTarball.x86_64-linux}/*.tar.*'
- mkdir /dest-nix
- mount -o bind /dest-nix /nix # Provide a writable /nix.
- chown alice /nix
- su - alice -c '_NIX_INSTALLER_TEST=1 ./nix-*/install'
- su - alice -c 'nix-store --verify'
- su - alice -c 'PAGER= nix-store -qR ${build.x86_64-linux}'
-
- # Check whether 'nix upgrade-nix' works.
- cat > /tmp/paths.nix <> $out/nix-support/hydra-build-products
- '';
-
- };
-
-
-in jobs
diff --git a/scripts/install-nix-from-closure.sh b/scripts/install-nix-from-closure.sh
index 5824c2217..6fb0beb2b 100644
--- a/scripts/install-nix-from-closure.sh
+++ b/scripts/install-nix-from-closure.sh
@@ -207,7 +207,7 @@ if [ -z "$NIX_INSTALLER_NO_MODIFY_PROFILE" ]; then
if [ -w "$fn" ]; then
if ! grep -q "$p" "$fn"; then
echo "modifying $fn..." >&2
- echo "if [ -e $p ]; then . $p; fi # added by Nix installer" >> "$fn"
+ echo -e "\nif [ -e $p ]; then . $p; fi # added by Nix installer" >> "$fn"
fi
added=1
break
@@ -218,7 +218,7 @@ if [ -z "$NIX_INSTALLER_NO_MODIFY_PROFILE" ]; then
if [ -w "$fn" ]; then
if ! grep -q "$p" "$fn"; then
echo "modifying $fn..." >&2
- echo "if [ -e $p ]; then . $p; fi # added by Nix installer" >> "$fn"
+ echo -e "\nif [ -e $p ]; then . $p; fi # added by Nix installer" >> "$fn"
fi
added=1
break
diff --git a/shell.nix b/shell.nix
index 17aaa05ed..330df0ab6 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,25 +1,3 @@
-{ useClang ? false }:
-
-with import (builtins.fetchTarball https://github.com/NixOS/nixpkgs/archive/nixos-20.03-small.tar.gz) {};
-
-with import ./release-common.nix { inherit pkgs; };
-
-(if useClang then clangStdenv else stdenv).mkDerivation {
- name = "nix";
-
- buildInputs = buildDeps ++ propagatedDeps ++ perlDeps;
-
- inherit configureFlags;
-
- enableParallelBuilding = true;
-
- installFlags = "sysconfdir=$(out)/etc";
-
- shellHook =
- ''
- export prefix=$(pwd)/inst
- configureFlags+=" --prefix=$prefix"
- PKG_CONFIG_PATH=$prefix/lib/pkgconfig:$PKG_CONFIG_PATH
- PATH=$prefix/bin:$PATH
- '';
-}
+(import (fetchTarball https://github.com/edolstra/flake-compat/archive/master.tar.gz) {
+ src = ./.;
+}).shellNix
diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc
index e07117496..3579d8fff 100644
--- a/src/build-remote/build-remote.cc
+++ b/src/build-remote/build-remote.cc
@@ -33,7 +33,7 @@ std::string escapeUri(std::string uri)
static string currentLoad;
-static AutoCloseFD openSlotLock(const Machine & m, unsigned long long slot)
+static AutoCloseFD openSlotLock(const Machine & m, uint64_t slot)
{
return openLockFile(fmt("%s/%s-%d", currentLoad, escapeUri(m.storeUri), slot), true);
}
@@ -119,7 +119,7 @@ static int _main(int argc, char * * argv)
bool rightType = false;
Machine * bestMachine = nullptr;
- unsigned long long bestLoad = 0;
+ uint64_t bestLoad = 0;
for (auto & m : machines) {
debug("considering building on remote machine '%s'", m.storeUri);
@@ -130,8 +130,8 @@ static int _main(int argc, char * * argv)
m.mandatoryMet(requiredFeatures)) {
rightType = true;
AutoCloseFD free;
- unsigned long long load = 0;
- for (unsigned long long slot = 0; slot < m.maxJobs; ++slot) {
+ uint64_t load = 0;
+ for (uint64_t slot = 0; slot < m.maxJobs; ++slot) {
auto slotLock = openSlotLock(m, slot);
if (lockFile(slotLock.get(), ltWrite, false)) {
if (!free) {
diff --git a/src/libexpr/common-eval-args.cc b/src/libexpr/common-eval-args.cc
index 44baadd53..6b48ead1f 100644
--- a/src/libexpr/common-eval-args.cc
+++ b/src/libexpr/common-eval-args.cc
@@ -4,6 +4,8 @@
#include "util.hh"
#include "eval.hh"
#include "fetchers.hh"
+#include "registry.hh"
+#include "flake/flakeref.hh"
#include "store-api.hh"
namespace nix {
@@ -31,6 +33,27 @@ MixEvalArgs::MixEvalArgs()
.labels = {"path"},
.handler = {[&](std::string s) { searchPath.push_back(s); }}
});
+
+ addFlag({
+ .longName = "impure",
+ .description = "allow access to mutable paths and repositories",
+ .handler = {[&]() {
+ evalSettings.pureEval = false;
+ }},
+ });
+
+ addFlag({
+ .longName = "override-flake",
+ .description = "override a flake registry value",
+ .labels = {"original-ref", "resolved-ref"},
+ .handler = {[&](std::string _from, std::string _to) {
+ auto from = parseFlakeRef(_from, absPath("."));
+ auto to = parseFlakeRef(_to, absPath("."));
+ fetchers::Attrs extraAttrs;
+ if (to.subdir != "") extraAttrs["dir"] = to.subdir;
+ fetchers::overrideRegistry(from.input, to.input, extraAttrs);
+ }}
+ });
}
Bindings * MixEvalArgs::getAutoArgs(EvalState & state)
@@ -53,7 +76,7 @@ Path lookupFileArg(EvalState & state, string s)
if (isUri(s)) {
return state.store->toRealPath(
fetchers::downloadTarball(
- state.store, resolveUri(s), "source", false).storePath);
+ state.store, resolveUri(s), "source", false).first.storePath);
} else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') {
Path p = s.substr(1, s.size() - 2);
return state.findFile(p);
diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc
new file mode 100644
index 000000000..deb32484f
--- /dev/null
+++ b/src/libexpr/eval-cache.cc
@@ -0,0 +1,616 @@
+#include "eval-cache.hh"
+#include "sqlite.hh"
+#include "eval.hh"
+#include "eval-inline.hh"
+#include "store-api.hh"
+
+namespace nix::eval_cache {
+
+static const char * schema = R"sql(
+create table if not exists Attributes (
+ parent integer not null,
+ name text,
+ type integer not null,
+ value text,
+ context text,
+ primary key (parent, name)
+);
+)sql";
+
+struct AttrDb
+{
+ std::atomic_bool failed{false};
+
+ struct State
+ {
+ SQLite db;
+ SQLiteStmt insertAttribute;
+ SQLiteStmt insertAttributeWithContext;
+ SQLiteStmt queryAttribute;
+ SQLiteStmt queryAttributes;
+ std::unique_ptr txn;
+ };
+
+ std::unique_ptr> _state;
+
+ AttrDb(const Hash & fingerprint)
+ : _state(std::make_unique>())
+ {
+ auto state(_state->lock());
+
+ Path cacheDir = getCacheDir() + "/nix/eval-cache-v2";
+ createDirs(cacheDir);
+
+ Path dbPath = cacheDir + "/" + fingerprint.to_string(Base16, false) + ".sqlite";
+
+ state->db = SQLite(dbPath);
+ state->db.isCache();
+ state->db.exec(schema);
+
+ state->insertAttribute.create(state->db,
+ "insert or replace into Attributes(parent, name, type, value) values (?, ?, ?, ?)");
+
+ state->insertAttributeWithContext.create(state->db,
+ "insert or replace into Attributes(parent, name, type, value, context) values (?, ?, ?, ?, ?)");
+
+ state->queryAttribute.create(state->db,
+ "select rowid, type, value, context from Attributes where parent = ? and name = ?");
+
+ state->queryAttributes.create(state->db,
+ "select name from Attributes where parent = ?");
+
+ state->txn = std::make_unique(state->db);
+ }
+
+ ~AttrDb()
+ {
+ try {
+ auto state(_state->lock());
+ if (!failed)
+ state->txn->commit();
+ state->txn.reset();
+ } catch (...) {
+ ignoreException();
+ }
+ }
+
+ template
+ AttrId doSQLite(F && fun)
+ {
+ if (failed) return 0;
+ try {
+ return fun();
+ } catch (SQLiteError &) {
+ ignoreException();
+ failed = true;
+ return 0;
+ }
+ }
+
+ AttrId setAttrs(
+ AttrKey key,
+ const std::vector & attrs)
+ {
+ return doSQLite([&]()
+ {
+ auto state(_state->lock());
+
+ state->insertAttribute.use()
+ (key.first)
+ (key.second)
+ (AttrType::FullAttrs)
+ (0, false).exec();
+
+ AttrId rowId = state->db.getLastInsertedRowId();
+ assert(rowId);
+
+ for (auto & attr : attrs)
+ state->insertAttribute.use()
+ (rowId)
+ (attr)
+ (AttrType::Placeholder)
+ (0, false).exec();
+
+ return rowId;
+ });
+ }
+
+ AttrId setString(
+ AttrKey key,
+ std::string_view s,
+ const char * * context = nullptr)
+ {
+ return doSQLite([&]()
+ {
+ auto state(_state->lock());
+
+ if (context) {
+ std::string ctx;
+ for (const char * * p = context; *p; ++p) {
+ if (p != context) ctx.push_back(' ');
+ ctx.append(*p);
+ }
+ state->insertAttributeWithContext.use()
+ (key.first)
+ (key.second)
+ (AttrType::String)
+ (s)
+ (ctx).exec();
+ } else {
+ state->insertAttribute.use()
+ (key.first)
+ (key.second)
+ (AttrType::String)
+ (s).exec();
+ }
+
+ return state->db.getLastInsertedRowId();
+ });
+ }
+
+ AttrId setBool(
+ AttrKey key,
+ bool b)
+ {
+ return doSQLite([&]()
+ {
+ auto state(_state->lock());
+
+ state->insertAttribute.use()
+ (key.first)
+ (key.second)
+ (AttrType::Bool)
+ (b ? 1 : 0).exec();
+
+ return state->db.getLastInsertedRowId();
+ });
+ }
+
+ AttrId setPlaceholder(AttrKey key)
+ {
+ return doSQLite([&]()
+ {
+ auto state(_state->lock());
+
+ state->insertAttribute.use()
+ (key.first)
+ (key.second)
+ (AttrType::Placeholder)
+ (0, false).exec();
+
+ return state->db.getLastInsertedRowId();
+ });
+ }
+
+ AttrId setMissing(AttrKey key)
+ {
+ return doSQLite([&]()
+ {
+ auto state(_state->lock());
+
+ state->insertAttribute.use()
+ (key.first)
+ (key.second)
+ (AttrType::Missing)
+ (0, false).exec();
+
+ return state->db.getLastInsertedRowId();
+ });
+ }
+
+ AttrId setMisc(AttrKey key)
+ {
+ return doSQLite([&]()
+ {
+ auto state(_state->lock());
+
+ state->insertAttribute.use()
+ (key.first)
+ (key.second)
+ (AttrType::Misc)
+ (0, false).exec();
+
+ return state->db.getLastInsertedRowId();
+ });
+ }
+
+ AttrId setFailed(AttrKey key)
+ {
+ return doSQLite([&]()
+ {
+ auto state(_state->lock());
+
+ state->insertAttribute.use()
+ (key.first)
+ (key.second)
+ (AttrType::Failed)
+ (0, false).exec();
+
+ return state->db.getLastInsertedRowId();
+ });
+ }
+
+ std::optional> getAttr(
+ AttrKey key,
+ SymbolTable & symbols)
+ {
+ auto state(_state->lock());
+
+ auto queryAttribute(state->queryAttribute.use()(key.first)(key.second));
+ if (!queryAttribute.next()) return {};
+
+ auto rowId = (AttrType) queryAttribute.getInt(0);
+ auto type = (AttrType) queryAttribute.getInt(1);
+
+ switch (type) {
+ case AttrType::Placeholder:
+ return {{rowId, placeholder_t()}};
+ case AttrType::FullAttrs: {
+ // FIXME: expensive, should separate this out.
+ std::vector attrs;
+ auto queryAttributes(state->queryAttributes.use()(rowId));
+ while (queryAttributes.next())
+ attrs.push_back(symbols.create(queryAttributes.getStr(0)));
+ return {{rowId, attrs}};
+ }
+ case AttrType::String: {
+ std::vector> context;
+ if (!queryAttribute.isNull(3))
+ for (auto & s : tokenizeString>(queryAttribute.getStr(3), ";"))
+ context.push_back(decodeContext(s));
+ return {{rowId, string_t{queryAttribute.getStr(2), context}}};
+ }
+ case AttrType::Bool:
+ return {{rowId, queryAttribute.getInt(2) != 0}};
+ case AttrType::Missing:
+ return {{rowId, missing_t()}};
+ case AttrType::Misc:
+ return {{rowId, misc_t()}};
+ case AttrType::Failed:
+ return {{rowId, failed_t()}};
+ default:
+ throw Error("unexpected type in evaluation cache");
+ }
+ }
+};
+
+static std::shared_ptr makeAttrDb(const Hash & fingerprint)
+{
+ try {
+ return std::make_shared(fingerprint);
+ } catch (SQLiteError &) {
+ ignoreException();
+ return nullptr;
+ }
+}
+
+EvalCache::EvalCache(
+ std::optional> useCache,
+ EvalState & state,
+ RootLoader rootLoader)
+ : db(useCache ? makeAttrDb(*useCache) : nullptr)
+ , state(state)
+ , rootLoader(rootLoader)
+{
+}
+
+Value * EvalCache::getRootValue()
+{
+ if (!value) {
+ debug("getting root value");
+ value = allocRootValue(rootLoader());
+ }
+ return *value;
+}
+
+std::shared_ptr EvalCache::getRoot()
+{
+ return std::make_shared(ref(shared_from_this()), std::nullopt);
+}
+
+AttrCursor::AttrCursor(
+ ref root,
+ Parent parent,
+ Value * value,
+ std::optional> && cachedValue)
+ : root(root), parent(parent), cachedValue(std::move(cachedValue))
+{
+ if (value)
+ _value = allocRootValue(value);
+}
+
+AttrKey AttrCursor::getKey()
+{
+ if (!parent)
+ return {0, root->state.sEpsilon};
+ if (!parent->first->cachedValue) {
+ parent->first->cachedValue = root->db->getAttr(
+ parent->first->getKey(), root->state.symbols);
+ assert(parent->first->cachedValue);
+ }
+ return {parent->first->cachedValue->first, parent->second};
+}
+
+Value & AttrCursor::getValue()
+{
+ if (!_value) {
+ if (parent) {
+ auto & vParent = parent->first->getValue();
+ root->state.forceAttrs(vParent);
+ auto attr = vParent.attrs->get(parent->second);
+ if (!attr)
+ throw Error("attribute '%s' is unexpectedly missing", getAttrPathStr());
+ _value = allocRootValue(attr->value);
+ } else
+ _value = allocRootValue(root->getRootValue());
+ }
+ return **_value;
+}
+
+std::vector AttrCursor::getAttrPath() const
+{
+ if (parent) {
+ auto attrPath = parent->first->getAttrPath();
+ attrPath.push_back(parent->second);
+ return attrPath;
+ } else
+ return {};
+}
+
+std::vector AttrCursor::getAttrPath(Symbol name) const
+{
+ auto attrPath = getAttrPath();
+ attrPath.push_back(name);
+ return attrPath;
+}
+
+std::string AttrCursor::getAttrPathStr() const
+{
+ return concatStringsSep(".", getAttrPath());
+}
+
+std::string AttrCursor::getAttrPathStr(Symbol name) const
+{
+ return concatStringsSep(".", getAttrPath(name));
+}
+
+Value & AttrCursor::forceValue()
+{
+ debug("evaluating uncached attribute %s", getAttrPathStr());
+
+ auto & v = getValue();
+
+ try {
+ root->state.forceValue(v);
+ } catch (EvalError &) {
+ debug("setting '%s' to failed", getAttrPathStr());
+ if (root->db)
+ cachedValue = {root->db->setFailed(getKey()), failed_t()};
+ throw;
+ }
+
+ if (root->db && (!cachedValue || std::get_if(&cachedValue->second))) {
+ if (v.type == tString)
+ cachedValue = {root->db->setString(getKey(), v.string.s, v.string.context), v.string.s};
+ else if (v.type == tPath)
+ cachedValue = {root->db->setString(getKey(), v.path), v.path};
+ else if (v.type == tBool)
+ cachedValue = {root->db->setBool(getKey(), v.boolean), v.boolean};
+ else if (v.type == tAttrs)
+ ; // FIXME: do something?
+ else
+ cachedValue = {root->db->setMisc(getKey()), misc_t()};
+ }
+
+ return v;
+}
+
+std::shared_ptr AttrCursor::maybeGetAttr(Symbol name)
+{
+ if (root->db) {
+ if (!cachedValue)
+ cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+
+ if (cachedValue) {
+ if (auto attrs = std::get_if>(&cachedValue->second)) {
+ for (auto & attr : *attrs)
+ if (attr == name)
+ return std::make_shared(root, std::make_pair(shared_from_this(), name));
+ return nullptr;
+ } else if (std::get_if(&cachedValue->second)) {
+ auto attr = root->db->getAttr({cachedValue->first, name}, root->state.symbols);
+ if (attr) {
+ if (std::get_if(&attr->second))
+ return nullptr;
+ else if (std::get_if(&attr->second))
+ throw EvalError("cached failure of attribute '%s'", getAttrPathStr(name));
+ else
+ return std::make_shared(root,
+ std::make_pair(shared_from_this(), name), nullptr, std::move(attr));
+ }
+ // Incomplete attrset, so need to fall thru and
+ // evaluate to see whether 'name' exists
+ } else
+ return nullptr;
+ //throw TypeError("'%s' is not an attribute set", getAttrPathStr());
+ }
+ }
+
+ auto & v = forceValue();
+
+ if (v.type != tAttrs)
+ return nullptr;
+ //throw TypeError("'%s' is not an attribute set", getAttrPathStr());
+
+ auto attr = v.attrs->get(name);
+
+ if (!attr) {
+ if (root->db) {
+ if (!cachedValue)
+ cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()};
+ root->db->setMissing({cachedValue->first, name});
+ }
+ return nullptr;
+ }
+
+ std::optional> cachedValue2;
+ if (root->db) {
+ if (!cachedValue)
+ cachedValue = {root->db->setPlaceholder(getKey()), placeholder_t()};
+ cachedValue2 = {root->db->setPlaceholder({cachedValue->first, name}), placeholder_t()};
+ }
+
+ return std::make_shared(
+ root, std::make_pair(shared_from_this(), name), attr->value, std::move(cachedValue2));
+}
+
+std::shared_ptr AttrCursor::maybeGetAttr(std::string_view name)
+{
+ return maybeGetAttr(root->state.symbols.create(name));
+}
+
+std::shared_ptr AttrCursor::getAttr(Symbol name)
+{
+ auto p = maybeGetAttr(name);
+ if (!p)
+ throw Error("attribute '%s' does not exist", getAttrPathStr(name));
+ return p;
+}
+
+std::shared_ptr AttrCursor::getAttr(std::string_view name)
+{
+ return getAttr(root->state.symbols.create(name));
+}
+
+std::shared_ptr AttrCursor::findAlongAttrPath(const std::vector & attrPath)
+{
+ auto res = shared_from_this();
+ for (auto & attr : attrPath) {
+ res = res->maybeGetAttr(attr);
+ if (!res) return {};
+ }
+ return res;
+}
+
+std::string AttrCursor::getString()
+{
+ if (root->db) {
+ if (!cachedValue)
+ cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+ if (cachedValue && !std::get_if(&cachedValue->second)) {
+ if (auto s = std::get_if(&cachedValue->second)) {
+ debug("using cached string attribute '%s'", getAttrPathStr());
+ return s->first;
+ } else
+ throw TypeError("'%s' is not a string", getAttrPathStr());
+ }
+ }
+
+ auto & v = forceValue();
+
+ if (v.type != tString && v.type != tPath)
+ throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type));
+
+ return v.type == tString ? v.string.s : v.path;
+}
+
+string_t AttrCursor::getStringWithContext()
+{
+ if (root->db) {
+ if (!cachedValue)
+ cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+ if (cachedValue && !std::get_if(&cachedValue->second)) {
+ if (auto s = std::get_if(&cachedValue->second)) {
+ debug("using cached string attribute '%s'", getAttrPathStr());
+ return *s;
+ } else
+ throw TypeError("'%s' is not a string", getAttrPathStr());
+ }
+ }
+
+ auto & v = forceValue();
+
+ if (v.type == tString)
+ return {v.string.s, v.getContext()};
+ else if (v.type == tPath)
+ return {v.path, {}};
+ else
+ throw TypeError("'%s' is not a string but %s", getAttrPathStr(), showType(v.type));
+}
+
+bool AttrCursor::getBool()
+{
+ if (root->db) {
+ if (!cachedValue)
+ cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+ if (cachedValue && !std::get_if(&cachedValue->second)) {
+ if (auto b = std::get_if(&cachedValue->second)) {
+ debug("using cached Boolean attribute '%s'", getAttrPathStr());
+ return *b;
+ } else
+ throw TypeError("'%s' is not a Boolean", getAttrPathStr());
+ }
+ }
+
+ auto & v = forceValue();
+
+ if (v.type != tBool)
+ throw TypeError("'%s' is not a Boolean", getAttrPathStr());
+
+ return v.boolean;
+}
+
+std::vector AttrCursor::getAttrs()
+{
+ if (root->db) {
+ if (!cachedValue)
+ cachedValue = root->db->getAttr(getKey(), root->state.symbols);
+ if (cachedValue && !std::get_if(&cachedValue->second)) {
+ if (auto attrs = std::get_if>(&cachedValue->second)) {
+ debug("using cached attrset attribute '%s'", getAttrPathStr());
+ return *attrs;
+ } else
+ throw TypeError("'%s' is not an attribute set", getAttrPathStr());
+ }
+ }
+
+ auto & v = forceValue();
+
+ if (v.type != tAttrs)
+ throw TypeError("'%s' is not an attribute set", getAttrPathStr());
+
+ std::vector attrs;
+ for (auto & attr : *getValue().attrs)
+ attrs.push_back(attr.name);
+ std::sort(attrs.begin(), attrs.end(), [](const Symbol & a, const Symbol & b) {
+ return (const string &) a < (const string &) b;
+ });
+
+ if (root->db)
+ cachedValue = {root->db->setAttrs(getKey(), attrs), attrs};
+
+ return attrs;
+}
+
+bool AttrCursor::isDerivation()
+{
+ auto aType = maybeGetAttr("type");
+ return aType && aType->getString() == "derivation";
+}
+
+StorePath AttrCursor::forceDerivation()
+{
+ auto aDrvPath = getAttr(root->state.sDrvPath);
+ auto drvPath = root->state.store->parseStorePath(aDrvPath->getString());
+ if (!root->state.store->isValidPath(drvPath) && !settings.readOnlyMode) {
+ /* The eval cache contains 'drvPath', but the actual path has
+ been garbage-collected. So force it to be regenerated. */
+ aDrvPath->forceValue();
+ if (!root->state.store->isValidPath(drvPath))
+ throw Error("don't know how to recreate store derivation '%s'!",
+ root->state.store->printStorePath(drvPath));
+ }
+ return drvPath;
+}
+
+}
diff --git a/src/libexpr/eval-cache.hh b/src/libexpr/eval-cache.hh
new file mode 100644
index 000000000..afee85fa9
--- /dev/null
+++ b/src/libexpr/eval-cache.hh
@@ -0,0 +1,121 @@
+#pragma once
+
+#include "sync.hh"
+#include "hash.hh"
+#include "eval.hh"
+
+#include
+#include
+
+namespace nix::eval_cache {
+
+class AttrDb;
+class AttrCursor;
+
+class EvalCache : public std::enable_shared_from_this
+{
+ friend class AttrCursor;
+
+ std::shared_ptr db;
+ EvalState & state;
+ typedef std::function RootLoader;
+ RootLoader rootLoader;
+ RootValue value;
+
+ Value * getRootValue();
+
+public:
+
+ EvalCache(
+ std::optional> useCache,
+ EvalState & state,
+ RootLoader rootLoader);
+
+ std::shared_ptr getRoot();
+};
+
+enum AttrType {
+ Placeholder = 0,
+ FullAttrs = 1,
+ String = 2,
+ Missing = 3,
+ Misc = 4,
+ Failed = 5,
+ Bool = 6,
+};
+
+struct placeholder_t {};
+struct missing_t {};
+struct misc_t {};
+struct failed_t {};
+typedef uint64_t AttrId;
+typedef std::pair AttrKey;
+typedef std::pair>> string_t;
+
+typedef std::variant<
+ std::vector,
+ string_t,
+ placeholder_t,
+ missing_t,
+ misc_t,
+ failed_t,
+ bool
+ > AttrValue;
+
+class AttrCursor : public std::enable_shared_from_this
+{
+ friend class EvalCache;
+
+ ref root;
+ typedef std::optional, Symbol>> Parent;
+ Parent parent;
+ RootValue _value;
+ std::optional> cachedValue;
+
+ AttrKey getKey();
+
+ Value & getValue();
+
+public:
+
+ AttrCursor(
+ ref root,
+ Parent parent,
+ Value * value = nullptr,
+ std::optional> && cachedValue = {});
+
+ std::vector getAttrPath() const;
+
+ std::vector getAttrPath(Symbol name) const;
+
+ std::string getAttrPathStr() const;
+
+ std::string getAttrPathStr(Symbol name) const;
+
+ std::shared_ptr maybeGetAttr(Symbol name);
+
+ std::shared_ptr maybeGetAttr(std::string_view name);
+
+ std::shared_ptr getAttr(Symbol name);
+
+ std::shared_ptr getAttr(std::string_view name);
+
+ std::shared_ptr findAlongAttrPath(const std::vector & attrPath);
+
+ std::string getString();
+
+ string_t getStringWithContext();
+
+ bool getBool();
+
+ std::vector getAttrs();
+
+ bool isDerivation();
+
+ Value & forceValue();
+
+ /* Force creation of the .drv file in the Nix store. */
+ StorePath forceDerivation();
+};
+
+}
diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc
index c1a9af9b2..7a2f55504 100644
--- a/src/libexpr/eval.cc
+++ b/src/libexpr/eval.cc
@@ -199,6 +199,18 @@ string showType(const Value & v)
}
+bool Value::isTrivial() const
+{
+ return
+ type != tApp
+ && type != tPrimOpApp
+ && (type != tThunk
+ || (dynamic_cast(thunk.expr)
+ && ((ExprAttrs *) thunk.expr)->dynamicAttrs.empty())
+ || dynamic_cast(thunk.expr));
+}
+
+
#if HAVE_BOEHMGC
/* Called when the Boehm GC runs out of memory. */
static void * oomHandler(size_t requested)
@@ -337,6 +349,9 @@ EvalState::EvalState(const Strings & _searchPath, ref store)
, sOutputHashAlgo(symbols.create("outputHashAlgo"))
, sOutputHashMode(symbols.create("outputHashMode"))
, sRecurseForDerivations(symbols.create("recurseForDerivations"))
+ , sDescription(symbols.create("description"))
+ , sSelf(symbols.create("self"))
+ , sEpsilon(symbols.create(""))
, repair(NoRepair)
, store(store)
, baseEnv(allocEnv(128))
@@ -366,7 +381,7 @@ EvalState::EvalState(const Strings & _searchPath, ref store)
if (store->isInStore(r.second)) {
StorePathSet closure;
- store->computeFSClosure(store->parseStorePath(store->toStorePath(r.second)), closure);
+ store->computeFSClosure(store->toStorePath(r.second).first, closure);
for (auto & path : closure)
allowedPaths->insert(store->printStorePath(path));
} else
@@ -782,7 +797,7 @@ Value * ExprPath::maybeThunk(EvalState & state, Env & env)
}
-void EvalState::evalFile(const Path & path_, Value & v)
+void EvalState::evalFile(const Path & path_, Value & v, bool mustBeTrivial)
{
auto path = checkSourcePath(path_);
@@ -811,6 +826,11 @@ void EvalState::evalFile(const Path & path_, Value & v)
fileParseCache[path2] = e;
try {
+ // Enforce that 'flake.nix' is a direct attrset, not a
+ // computation.
+ if (mustBeTrivial &&
+ !(dynamic_cast(e)))
+ throw Error("file '%s' must be an attribute set", path);
eval(e, v);
} catch (Error & e) {
addErrorTrace(e, "while evaluating the file '%1%':", path2);
@@ -1586,6 +1606,18 @@ string EvalState::forceString(Value & v, const Pos & pos)
}
+/* Decode a context string ‘!!’ into a pair . */
+std::pair decodeContext(std::string_view s)
+{
+ if (s.at(0) == '!') {
+ size_t index = s.find("!", 1);
+ return {std::string(s.substr(index + 1)), std::string(s.substr(1, index - 1))};
+ } else
+ return {s.at(0) == '/' ? std::string(s) : std::string(s.substr(1)), ""};
+}
+
+
void copyContext(const Value & v, PathSet & context)
{
if (v.string.context)
@@ -1594,6 +1626,17 @@ void copyContext(const Value & v, PathSet & context)
}
+std::vector> Value::getContext()
+{
+ std::vector> res;
+ assert(type == tString);
+ if (string.context)
+ for (const char * * p = string.context; *p; ++p)
+ res.push_back(decodeContext(*p));
+ return res;
+}
+
+
string EvalState::forceString(Value & v, PathSet & context, const Pos & pos)
{
string s = forceString(v, pos);
diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh
index 0d52a7f63..8986952e3 100644
--- a/src/libexpr/eval.hh
+++ b/src/libexpr/eval.hh
@@ -4,13 +4,13 @@
#include "value.hh"
#include "nixexpr.hh"
#include "symbol-table.hh"
-#include "hash.hh"
#include "config.hh"
#include
#include