From 836573a9a2d38935e254702826d250ea39824a1b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 31 Oct 2017 12:22:29 +0100 Subject: [PATCH 001/245] Dynamically allocate UIDs Rather than rely on a nixbld group, we now allocate UIDs/GIDs dynamically starting at a configurable ID (872415232 by default). Also, we allocate 2^18 UIDs and GIDs per build, and run the build as root in its UID namespace. (This should not be the default since it breaks some builds. We probably should enable this conditional on a requiredSystemFeature.) The goal is to be able to run (NixOS) containers in a build. However, this will also require some cgroup initialisation. The 2^18 UIDs/GIDs is intended to provide enough ID space to run multiple containers per build, e.g. for distributed NixOS tests. --- src/libstore/build.cc | 59 +++++++++++++++++++++++++++++++++++------ src/libstore/globals.hh | 10 +++++++ 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 347fe1b99..c853f609d 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -513,7 +513,6 @@ private: AutoCloseFD fdUserLock; bool isEnabled = false; - string user; uid_t uid = 0; gid_t gid = 0; std::vector supplementaryGIDs; @@ -523,9 +522,9 @@ public: void kill(); - string getUser() { return user; } uid_t getUID() { assert(uid); return uid; } - uid_t getGID() { assert(gid); return gid; } + gid_t getGID() { assert(gid); return gid; } + uint32_t getIDCount() { return 1; } std::vector getSupplementaryGIDs() { return supplementaryGIDs; } bool findFreeUser(); @@ -537,13 +536,16 @@ public: UserLock::UserLock() { +#if 0 assert(settings.buildUsersGroup != ""); createDirs(settings.nixStateDir + "/userpool"); +#endif } bool UserLock::findFreeUser() { if (enabled()) return true; +#if 0 /* Get the members of the build-users-group. */ struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); if (!gr) @@ -607,12 +609,46 @@ bool UserLock::findFreeUser() { } } + return false; +#endif + + assert(settings.startId > 0); + assert(settings.startId % settings.idsPerBuild == 0); + assert(settings.uidCount % settings.idsPerBuild == 0); + assert((uint64_t) settings.startId + (uint64_t) settings.uidCount <= std::numeric_limits::max()); + + // FIXME: check whether the id range overlaps any known users + + size_t nrSlots = settings.uidCount / settings.idsPerBuild; + + for (size_t i = 0; i < nrSlots; i++) { + debug("trying user slot '%d'", i); + + createDirs(settings.nixStateDir + "/userpool"); + + fnUserLock = fmt("%s/userpool/slot-%d", settings.nixStateDir, i); + + AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600); + if (!fd) + throw SysError("opening user lock '%1%'", fnUserLock); + + if (lockFile(fd.get(), ltWrite, false)) { + fdUserLock = std::move(fd); + uid = settings.startId + i * settings.idsPerBuild; + gid = settings.startId + i * settings.idsPerBuild; + return true; + } + } + return false; } void UserLock::kill() { + // FIXME: use a cgroup to kill all processes in the build? +#if 0 killUser(uid); +#endif } @@ -1523,7 +1559,7 @@ void DerivationGoal::tryLocalBuild() { /* If `build-users-group' is not empty, then we have to build as one of the members of that group. */ - if (settings.buildUsersGroup != "" && getuid() == 0) { + if ((settings.buildUsersGroup != "" || settings.startId.get() != 0) && getuid() == 0) { #if defined(__linux__) || defined(__APPLE__) if (!buildUser) buildUser = std::make_unique(); @@ -2129,7 +2165,7 @@ void DerivationGoal::startBuilder() printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir); - if (mkdir(chrootRootDir.c_str(), 0750) == -1) + if (mkdir(chrootRootDir.c_str(), 0755) == -1) throw SysError("cannot create '%1%'", chrootRootDir); if (buildUser && chown(chrootRootDir.c_str(), 0, buildUser->getGID()) == -1) @@ -2444,14 +2480,15 @@ void DerivationGoal::startBuilder() the calling user (if build users are disabled). */ uid_t hostUid = buildUser ? buildUser->getUID() : getuid(); uid_t hostGid = buildUser ? buildUser->getGID() : getgid(); + uint32_t nrIds = settings.idsPerBuild; // FIXME writeFile("/proc/" + std::to_string(pid) + "/uid_map", - (format("%d %d 1") % sandboxUid % hostUid).str()); + fmt("%d %d %d", /* sandboxUid */ 0, hostUid, nrIds)); - writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny"); + //writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny"); writeFile("/proc/" + std::to_string(pid) + "/gid_map", - (format("%d %d 1") % sandboxGid % hostGid).str()); + fmt("%d %d %d", /* sandboxGid */ 0, hostGid, nrIds)); /* Save the mount namespace of the child. We have to do this *before* the child does a chroot. */ @@ -3306,10 +3343,16 @@ void DerivationGoal::runChild() /* Switch to the sandbox uid/gid in the user namespace, which corresponds to the build user or calling user in the parent namespace. */ +#if 0 if (setgid(sandboxGid) == -1) throw SysError("setgid failed"); if (setuid(sandboxUid) == -1) throw SysError("setuid failed"); +#endif + if (setgid(0) == -1) + throw SysError("setgid failed"); + if (setuid(0) == -1) + throw SysError("setuid failed"); setUser = false; } diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 58cf08763..7dc842bca 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -148,6 +148,16 @@ public: Setting buildUsersGroup{this, "", "build-users-group", "The Unix group that contains the build users."}; + #if __linux__ + const uint32_t idsPerBuild = 1 << 18; + + Setting startId{this, 872415232, "start-id", + "The first UID and GID to use for dynamic ID allocation. (0 means disable.)"}; + + Setting uidCount{this, idsPerBuild * 128, "id-count", + "The number of UIDs/GIDs to use for dynamic ID allocation."}; + #endif + Setting impersonateLinux26{this, false, "impersonate-linux-26", "Whether to impersonate a Linux 2.6 machine on newer kernels.", {"build-impersonate-linux-26"}}; From c3e0a68c7eeeb4f491c0464392b2146ddec4305a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 14 May 2020 13:52:41 +0200 Subject: [PATCH 002/245] canonicalisePathMetaData(): Support a UID range --- src/libstore/build.cc | 12 +++++++++--- src/libstore/local-store.cc | 27 +++++++++++++++++---------- src/libstore/local-store.hh | 15 ++++++++++++--- src/nix-store/nix-store.cc | 2 +- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/libstore/build.cc b/src/libstore/build.cc index c853f609d..4e654e8ad 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -524,7 +524,7 @@ public: uid_t getUID() { assert(uid); return uid; } gid_t getGID() { assert(gid); return gid; } - uint32_t getIDCount() { return 1; } + uint32_t getIDCount() { return settings.idsPerBuild; } std::vector getSupplementaryGIDs() { return supplementaryGIDs; } bool findFreeUser(); @@ -3744,7 +3744,10 @@ void DerivationGoal::registerOutputs() /* Canonicalise first. This ensures that the path we're rewriting doesn't contain a hard link to /etc/shadow or something like that. */ - canonicalisePathMetaData(actualPath, buildUser ? buildUser->getUID() : -1, inodesSeen); + canonicalisePathMetaData( + actualPath, + buildUser ? std::optional(std::make_pair(buildUser->getUID(), buildUser->getUID() + buildUser->getIDCount() - 1)) : std::nullopt, + inodesSeen); /* FIXME: this is in-memory. */ StringSink sink; @@ -3819,7 +3822,10 @@ void DerivationGoal::registerOutputs() /* Get rid of all weird permissions. This also checks that all files are owned by the build user, if applicable. */ canonicalisePathMetaData(actualPath, - buildUser && !rewritten ? buildUser->getUID() : -1, inodesSeen); + buildUser && !rewritten + ? std::optional(std::make_pair(buildUser->getUID(), buildUser->getUID() + buildUser->getIDCount() - 1)) + : std::nullopt, + inodesSeen); /* For this output path, find the references to other paths contained in it. Compute the SHA-256 NAR hash at the same diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index eed225349..80ebe903f 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -424,7 +424,10 @@ void canonicaliseTimestampAndPermissions(const Path & path) } -static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSeen & inodesSeen) +static void canonicalisePathMetaData_( + const Path & path, + std::optional> uidRange, + InodesSeen & inodesSeen) { checkInterrupt(); @@ -475,7 +478,7 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe However, ignore files that we chown'ed ourselves previously to ensure that we don't fail on hard links within the same build (i.e. "touch $out/foo; ln $out/foo $out/bar"). */ - if (fromUid != (uid_t) -1 && st.st_uid != fromUid) { + if (uidRange && (st.st_uid < uidRange->first || st.st_uid > uidRange->second)) { assert(!S_ISDIR(st.st_mode)); if (inodesSeen.find(Inode(st.st_dev, st.st_ino)) == inodesSeen.end()) throw BuildError("invalid ownership on file '%1%'", path); @@ -509,14 +512,17 @@ static void canonicalisePathMetaData_(const Path & path, uid_t fromUid, InodesSe if (S_ISDIR(st.st_mode)) { DirEntries entries = readDirectory(path); for (auto & i : entries) - canonicalisePathMetaData_(path + "/" + i.name, fromUid, inodesSeen); + canonicalisePathMetaData_(path + "/" + i.name, uidRange, inodesSeen); } } -void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & inodesSeen) +void canonicalisePathMetaData( + const Path & path, + std::optional> uidRange, + InodesSeen & inodesSeen) { - canonicalisePathMetaData_(path, fromUid, inodesSeen); + canonicalisePathMetaData_(path, uidRange, inodesSeen); /* On platforms that don't have lchown(), the top-level path can't be a symlink, since we can't change its ownership. */ @@ -531,10 +537,11 @@ void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & ino } -void canonicalisePathMetaData(const Path & path, uid_t fromUid) +void canonicalisePathMetaData(const Path & path, + std::optional> uidRange) { InodesSeen inodesSeen; - canonicalisePathMetaData(path, fromUid, inodesSeen); + canonicalisePathMetaData(path, uidRange, inodesSeen); } @@ -1021,7 +1028,7 @@ void LocalStore::addToStore(const ValidPathInfo & info, Source & source, autoGC(); - canonicalisePathMetaData(realPath, -1); + canonicalisePathMetaData(realPath, {}); optimisePath(realPath); // FIXME: combine with hashPath() @@ -1064,7 +1071,7 @@ StorePath LocalStore::addToStoreFromDump(const string & dump, const string & nam } else writeFile(realPath, dump); - canonicalisePathMetaData(realPath, -1); + canonicalisePathMetaData(realPath, {}); /* Register the SHA-256 hash of the NAR serialisation of the path in the database. We may just have computed it @@ -1134,7 +1141,7 @@ StorePath LocalStore::addTextToStore(const string & name, const string & s, writeFile(realPath, s); - canonicalisePathMetaData(realPath, -1); + canonicalisePathMetaData(realPath, {}); StringSink sink; dumpString(s, sink); diff --git a/src/libstore/local-store.hh b/src/libstore/local-store.hh index ff36cb00e..79b415875 100644 --- a/src/libstore/local-store.hh +++ b/src/libstore/local-store.hh @@ -311,9 +311,18 @@ typedef set InodesSeen; - the permissions are set of 444 or 555 (i.e., read-only with or without execute permission; setuid bits etc. are cleared) - the owner and group are set to the Nix user and group, if we're - running as root. */ -void canonicalisePathMetaData(const Path & path, uid_t fromUid, InodesSeen & inodesSeen); -void canonicalisePathMetaData(const Path & path, uid_t fromUid); + running as root. + If uidRange is not empty, this function will throw an error if it + encounters files owned by a user outside of the closed interval + [uidRange->first, uidRange->second]. +*/ +void canonicalisePathMetaData( + const Path & path, + std::optional> uidRange, + InodesSeen & inodesSeen); +void canonicalisePathMetaData( + const Path & path, + std::optional> uidRange); void canonicaliseTimestampAndPermissions(const Path & path); diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 7d81bf54f..b948380bb 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -500,7 +500,7 @@ static void registerValidity(bool reregister, bool hashGiven, bool canonicalise) if (!store->isValidPath(info->path) || reregister) { /* !!! races */ if (canonicalise) - canonicalisePathMetaData(store->printStorePath(info->path), -1); + canonicalisePathMetaData(store->printStorePath(info->path), {}); if (!hashGiven) { HashResult hash = hashPath(htSHA256, store->printStorePath(info->path)); info->narHash = hash.first; From f5fa3de759a2b4c1d0107a4304a0b3f9571c87b6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 15 May 2020 00:11:59 +0200 Subject: [PATCH 003/245] Run builds in their own cgroup Also, run builds in a cgroup namespace (ensuring /proc/self/cgroup doesn't leak information about the outside world) and mount /sys. This enables running systemd-nspawn and thus NixOS containers in a Nix build. --- src/libstore/build.cc | 66 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 4e654e8ad..816d695a5 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -2168,7 +2168,8 @@ void DerivationGoal::startBuilder() if (mkdir(chrootRootDir.c_str(), 0755) == -1) throw SysError("cannot create '%1%'", chrootRootDir); - if (buildUser && chown(chrootRootDir.c_str(), 0, buildUser->getGID()) == -1) + // FIXME: only make root writable for user namespace builds. + if (buildUser && chown(chrootRootDir.c_str(), buildUser->getUID(), buildUser->getGID()) == -1) throw SysError("cannot change ownership of '%1%'", chrootRootDir); /* Create a writable /tmp in the chroot. Many builders need @@ -2182,6 +2183,7 @@ void DerivationGoal::startBuilder() nobody account. The latter is kind of a hack to support Samba-in-QEMU. */ createDirs(chrootRootDir + "/etc"); + chownToBuilder(chrootRootDir + "/etc"); writeFile(chrootRootDir + "/etc/passwd", fmt( "root:x:0:0:Nix build user:%3%:/noshell\n" @@ -2372,6 +2374,52 @@ void DerivationGoal::startBuilder() #if __linux__ if (useChroot) { + /* Create a cgroup. */ + // FIXME: do we want to use the parent cgroup? We should + // always use the same cgroup regardless of whether we're the + // daemon or run from a user session via sudo. + std::string msg; + std::vector cgroups; + for (auto & line : tokenizeString>(readFile("/proc/self/cgroup"), "\n")) { + static std::regex regex("([0-9]+):([^:]*):(.*)"); + std::smatch match; + if (!std::regex_match(line, match, regex)) + throw Error("invalid line '%s' in '/proc/self/cgroup'", line); + + /* We only create a systemd cgroup, since that's enough + for running systemd-nspawn. */ + std::string name; + if (match[2] == "name=systemd") + name = "systemd"; + //else if (match[2] == "") + // name = "unified"; + else continue; + + std::string cgroup = match[3]; + + auto hostCgroup = canonPath("/sys/fs/cgroup/" + name + "/" + cgroup); + + if (!pathExists(hostCgroup)) + throw Error("expected unified cgroup directory '%s'", hostCgroup); + + auto childCgroup = fmt("%s/nix-%d", hostCgroup, buildUser->getUID()); + + // FIXME: if the cgroup already exists, kill all processes + // in it and destroy it. + + if (mkdir(childCgroup.c_str(), 0755) == -1 && errno != EEXIST) + throw SysError("creating cgroup '%s'", childCgroup); + + chownToBuilder(childCgroup); + chownToBuilder(childCgroup + "/cgroup.procs"); + if (name == "unified") { + chownToBuilder(childCgroup + "/cgroup.threads"); + chownToBuilder(childCgroup + "/cgroup.subtree_control"); + } + + cgroups.push_back(childCgroup); + } + /* Set up private namespaces for the build: - The PID namespace causes the build to start as PID 1. @@ -2496,6 +2544,10 @@ void DerivationGoal::startBuilder() if (sandboxMountNamespace.get() == -1) throw SysError("getting sandbox mount namespace"); + /* Move the child into its own cgroup. */ + for (auto & childCgroup : cgroups) + writeFile(childCgroup + "/cgroup.procs", fmt("%d", (pid_t) pid)); + /* Signal the builder that we've updated its user namespace. */ writeFull(userNamespaceSync.writeSide.get(), "1"); userNamespaceSync.writeSide = -1; @@ -3279,6 +3331,12 @@ void DerivationGoal::runChild() if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1) throw SysError("mounting /proc"); + /* Mount sysfs on /sys. FIXME: only in user namespace + builds. */ + createDirs(chrootRootDir + "/sys"); + if (mount("none", (chrootRootDir + "/sys").c_str(), "sysfs", 0, 0) == -1) + throw SysError("mounting /sys"); + /* Mount a new tmpfs on /dev/shm to ensure that whatever the builder puts in /dev/shm is cleaned up automatically. */ if (pathExists("/dev/shm") && mount("none", (chrootRootDir + "/dev/shm").c_str(), "tmpfs", 0, @@ -3321,6 +3379,12 @@ void DerivationGoal::runChild() if (unshare(CLONE_NEWNS) == -1) throw SysError("unsharing mount namespace"); + /* Unshare the cgroup namespace. This means + /proc/self/cgroup will show the child's cgroup as '/' + rather than whatever it is in the parent. */ + if (unshare(CLONE_NEWCGROUP) == -1) + throw SysError("unsharing cgroup namespace"); + /* Do the chroot(). */ if (chdir(chrootRootDir.c_str()) == -1) throw SysError("cannot change directory to '%1%'", chrootRootDir); From ca2f64bcdaef5915f5147eac935ecb770511e438 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 15 May 2020 00:32:44 +0200 Subject: [PATCH 004/245] Reduce # of UIDs per build to 65536 2^18 was overkill. The idea was to enable multiple containers to run inside a build. However, those containers can use the same UID range - we don't really care about perfect isolation between containers inside a build. --- src/libstore/globals.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 7dc842bca..89db072b0 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -149,7 +149,7 @@ public: "The Unix group that contains the build users."}; #if __linux__ - const uint32_t idsPerBuild = 1 << 18; + const uint32_t idsPerBuild = 1 << 16; Setting startId{this, 872415232, "start-id", "The first UID and GID to use for dynamic ID allocation. (0 means disable.)"}; From 7bdcf43b401eba6aee29a359c5bce1f9cc01ce52 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 16 May 2020 21:09:48 +0200 Subject: [PATCH 005/245] Destroy the cgroup prior to building --- src/libstore/build.cc | 8 +++---- src/libstore/cgroup.cc | 49 ++++++++++++++++++++++++++++++++++++++++++ src/libstore/cgroup.hh | 13 +++++++++++ 3 files changed, 66 insertions(+), 4 deletions(-) create mode 100644 src/libstore/cgroup.cc create mode 100644 src/libstore/cgroup.hh diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 816d695a5..1f1468d97 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -16,6 +16,7 @@ #include "machines.hh" #include "daemon.hh" #include "worker-protocol.hh" +#include "cgroup.hh" #include #include @@ -2400,14 +2401,13 @@ void DerivationGoal::startBuilder() auto hostCgroup = canonPath("/sys/fs/cgroup/" + name + "/" + cgroup); if (!pathExists(hostCgroup)) - throw Error("expected unified cgroup directory '%s'", hostCgroup); + throw Error("expected cgroup directory '%s'", hostCgroup); auto childCgroup = fmt("%s/nix-%d", hostCgroup, buildUser->getUID()); - // FIXME: if the cgroup already exists, kill all processes - // in it and destroy it. + destroyCgroup(childCgroup); - if (mkdir(childCgroup.c_str(), 0755) == -1 && errno != EEXIST) + if (mkdir(childCgroup.c_str(), 0755) == -1) throw SysError("creating cgroup '%s'", childCgroup); chownToBuilder(childCgroup); diff --git a/src/libstore/cgroup.cc b/src/libstore/cgroup.cc new file mode 100644 index 000000000..8cd682e68 --- /dev/null +++ b/src/libstore/cgroup.cc @@ -0,0 +1,49 @@ +#if __linux__ + +#include "cgroup.hh" +#include "util.hh" + +#include + +#include + +namespace nix { + +void destroyCgroup(const Path & cgroup) +{ + for (auto & entry : readDirectory(cgroup)) { + if (entry.type != DT_DIR) continue; + destroyCgroup(cgroup + "/" + entry.name); + } + + int round = 1; + + while (true) { + auto pids = tokenizeString>(readFile(cgroup + "/cgroup.procs")); + + if (pids.empty()) break; + + if (round > 20) + throw Error("cannot kill cgroup '%s'", cgroup); + + for (auto & pid_s : pids) { + pid_t pid; + if (!string2Int(pid_s, pid)) throw Error("invalid pid '%s'", pid); + // FIXME: pid wraparound + if (kill(pid, SIGKILL) == -1 && errno != ESRCH) + throw SysError("killing member %d of cgroup '%s'", pid, cgroup); + } + + auto sleep = std::chrono::milliseconds((int) std::pow(2.0, std::min(round, 10))); + printError("waiting for %d ms for cgroup '%s' to become empty", sleep.count(), cgroup); + std::this_thread::sleep_for(sleep); + round++; + } + + if (rmdir(cgroup.c_str()) == -1) + throw SysError("deleting cgroup '%s'", cgroup); +} + +} + +#endif diff --git a/src/libstore/cgroup.hh b/src/libstore/cgroup.hh new file mode 100644 index 000000000..c7b09398e --- /dev/null +++ b/src/libstore/cgroup.hh @@ -0,0 +1,13 @@ +#pragma once + +#if __linux__ + +#include "types.hh" + +namespace nix { + +void destroyCgroup(const Path & cgroup); + +} + +#endif From 570c443f560e015cf02e4f96102eaaa0e6853562 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sat, 16 May 2020 21:21:41 +0200 Subject: [PATCH 006/245] Simplify cgroup creation --- src/libstore/build.cc | 53 +++++++++++++----------------------------- src/libstore/cgroup.cc | 17 ++++++++++++++ src/libstore/cgroup.hh | 2 ++ 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 1f1468d97..97554e9cf 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -2375,50 +2375,30 @@ void DerivationGoal::startBuilder() #if __linux__ if (useChroot) { - /* Create a cgroup. */ + /* Create a systemd cgroup since that's the minimum required + by systemd-nspawn. */ // FIXME: do we want to use the parent cgroup? We should // always use the same cgroup regardless of whether we're the // daemon or run from a user session via sudo. - std::string msg; - std::vector cgroups; - for (auto & line : tokenizeString>(readFile("/proc/self/cgroup"), "\n")) { - static std::regex regex("([0-9]+):([^:]*):(.*)"); - std::smatch match; - if (!std::regex_match(line, match, regex)) - throw Error("invalid line '%s' in '/proc/self/cgroup'", line); + auto ourCgroups = getCgroups("/proc/self/cgroup"); + auto systemdCgroup = ourCgroups["systemd"]; + if (systemdCgroup == "") + throw Error("'systemd' cgroup does not exist"); - /* We only create a systemd cgroup, since that's enough - for running systemd-nspawn. */ - std::string name; - if (match[2] == "name=systemd") - name = "systemd"; - //else if (match[2] == "") - // name = "unified"; - else continue; + auto hostCgroup = canonPath("/sys/fs/cgroup/systemd/" + systemdCgroup); - std::string cgroup = match[3]; + if (!pathExists(hostCgroup)) + throw Error("expected cgroup directory '%s'", hostCgroup); - auto hostCgroup = canonPath("/sys/fs/cgroup/" + name + "/" + cgroup); + auto childCgroup = fmt("%s/nix-%d", hostCgroup, buildUser->getUID()); - if (!pathExists(hostCgroup)) - throw Error("expected cgroup directory '%s'", hostCgroup); + destroyCgroup(childCgroup); - auto childCgroup = fmt("%s/nix-%d", hostCgroup, buildUser->getUID()); + if (mkdir(childCgroup.c_str(), 0755) == -1) + throw SysError("creating cgroup '%s'", childCgroup); - destroyCgroup(childCgroup); - - if (mkdir(childCgroup.c_str(), 0755) == -1) - throw SysError("creating cgroup '%s'", childCgroup); - - chownToBuilder(childCgroup); - chownToBuilder(childCgroup + "/cgroup.procs"); - if (name == "unified") { - chownToBuilder(childCgroup + "/cgroup.threads"); - chownToBuilder(childCgroup + "/cgroup.subtree_control"); - } - - cgroups.push_back(childCgroup); - } + chownToBuilder(childCgroup); + chownToBuilder(childCgroup + "/cgroup.procs"); /* Set up private namespaces for the build: @@ -2545,8 +2525,7 @@ void DerivationGoal::startBuilder() throw SysError("getting sandbox mount namespace"); /* Move the child into its own cgroup. */ - for (auto & childCgroup : cgroups) - writeFile(childCgroup + "/cgroup.procs", fmt("%d", (pid_t) pid)); + writeFile(childCgroup + "/cgroup.procs", fmt("%d", (pid_t) pid)); /* Signal the builder that we've updated its user namespace. */ writeFull(userNamespaceSync.writeSide.get(), "1"); diff --git a/src/libstore/cgroup.cc b/src/libstore/cgroup.cc index 8cd682e68..887facdca 100644 --- a/src/libstore/cgroup.cc +++ b/src/libstore/cgroup.cc @@ -9,6 +9,23 @@ namespace nix { +std::map getCgroups(const Path & cgroupFile) +{ + std::map cgroups; + + for (auto & line : tokenizeString>(readFile(cgroupFile), "\n")) { + static std::regex regex("([0-9]+):([^:]*):(.*)"); + std::smatch match; + if (!std::regex_match(line, match, regex)) + throw Error("invalid line '%s' in '%s'", line, cgroupFile); + + std::string name = hasPrefix(match[2], "name=") ? std::string(match[2], 5) : match[2]; + cgroups.insert_or_assign(name, match[3]); + } + + return cgroups; +} + void destroyCgroup(const Path & cgroup) { for (auto & entry : readDirectory(cgroup)) { diff --git a/src/libstore/cgroup.hh b/src/libstore/cgroup.hh index c7b09398e..dc6758957 100644 --- a/src/libstore/cgroup.hh +++ b/src/libstore/cgroup.hh @@ -6,6 +6,8 @@ namespace nix { +std::map getCgroups(const Path & cgroupFile); + void destroyCgroup(const Path & cgroup); } From ba50c3efa3b2394f5a8372939bc600008cd25e7e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 19 May 2020 23:25:44 +0200 Subject: [PATCH 007/245] Add "uid-range" and "systemd-cgroup" system features "uid-range" provides 65536 UIDs to a build and runs the build as root in its user namespace. "systemd-cgroup" allows the build to mount the systemd cgroup controller (needed for running systemd-nspawn and NixOS containers). Also, add a configuration option "auto-allocate-uids" which is needed to enable these features, and some experimental feature gates. So to enable support for containers you need the following in nix.conf: experimental-features = auto-allocate-uids systemd-cgroup auto-allocate-uids = true system-features = uid-range systemd-cgroup --- src/libstore/build.cc | 270 ++++++++++---------------------------- src/libstore/cgroup.cc | 19 ++- src/libstore/globals.hh | 5 +- src/libstore/user-lock.cc | 212 ++++++++++++++++++++++++++++++ src/libstore/user-lock.hh | 39 ++++++ 5 files changed, 340 insertions(+), 205 deletions(-) create mode 100644 src/libstore/user-lock.cc create mode 100644 src/libstore/user-lock.hh diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 97554e9cf..1f79a8d2d 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -16,7 +16,7 @@ #include "machines.hh" #include "daemon.hh" #include "worker-protocol.hh" -#include "cgroup.hh" +#include "user-lock.hh" #include #include @@ -504,154 +504,6 @@ void handleDiffHook( } } -////////////////////////////////////////////////////////////////////// - - -class UserLock -{ -private: - Path fnUserLock; - AutoCloseFD fdUserLock; - - bool isEnabled = false; - uid_t uid = 0; - gid_t gid = 0; - std::vector supplementaryGIDs; - -public: - UserLock(); - - void kill(); - - uid_t getUID() { assert(uid); return uid; } - gid_t getGID() { assert(gid); return gid; } - uint32_t getIDCount() { return settings.idsPerBuild; } - std::vector getSupplementaryGIDs() { return supplementaryGIDs; } - - bool findFreeUser(); - - bool enabled() { return isEnabled; } - -}; - - -UserLock::UserLock() -{ -#if 0 - assert(settings.buildUsersGroup != ""); - createDirs(settings.nixStateDir + "/userpool"); -#endif -} - -bool UserLock::findFreeUser() { - if (enabled()) return true; - -#if 0 - /* Get the members of the build-users-group. */ - struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); - if (!gr) - throw Error("the group '%1%' specified in 'build-users-group' does not exist", - settings.buildUsersGroup); - gid = gr->gr_gid; - - /* Copy the result of getgrnam. */ - Strings users; - for (char * * p = gr->gr_mem; *p; ++p) { - debug("found build user '%1%'", *p); - users.push_back(*p); - } - - if (users.empty()) - throw Error("the build users group '%1%' has no members", - settings.buildUsersGroup); - - /* Find a user account that isn't currently in use for another - build. */ - for (auto & i : users) { - debug("trying user '%1%'", i); - - struct passwd * pw = getpwnam(i.c_str()); - if (!pw) - throw Error("the user '%1%' in the group '%2%' does not exist", - i, settings.buildUsersGroup); - - - fnUserLock = (format("%1%/userpool/%2%") % settings.nixStateDir % pw->pw_uid).str(); - - AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600); - if (!fd) - throw SysError("opening user lock '%1%'", fnUserLock); - - if (lockFile(fd.get(), ltWrite, false)) { - fdUserLock = std::move(fd); - user = i; - uid = pw->pw_uid; - - /* Sanity check... */ - if (uid == getuid() || uid == geteuid()) - throw Error("the Nix user should not be a member of '%1%'", - settings.buildUsersGroup); - -#if __linux__ - /* Get the list of supplementary groups of this build user. This - is usually either empty or contains a group such as "kvm". */ - supplementaryGIDs.resize(10); - int ngroups = supplementaryGIDs.size(); - int err = getgrouplist(pw->pw_name, pw->pw_gid, - supplementaryGIDs.data(), &ngroups); - if (err == -1) - throw Error("failed to get list of supplementary groups for '%1%'", pw->pw_name); - - supplementaryGIDs.resize(ngroups); -#endif - - isEnabled = true; - return true; - } - } - - return false; -#endif - - assert(settings.startId > 0); - assert(settings.startId % settings.idsPerBuild == 0); - assert(settings.uidCount % settings.idsPerBuild == 0); - assert((uint64_t) settings.startId + (uint64_t) settings.uidCount <= std::numeric_limits::max()); - - // FIXME: check whether the id range overlaps any known users - - size_t nrSlots = settings.uidCount / settings.idsPerBuild; - - for (size_t i = 0; i < nrSlots; i++) { - debug("trying user slot '%d'", i); - - createDirs(settings.nixStateDir + "/userpool"); - - fnUserLock = fmt("%s/userpool/slot-%d", settings.nixStateDir, i); - - AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600); - if (!fd) - throw SysError("opening user lock '%1%'", fnUserLock); - - if (lockFile(fd.get(), ltWrite, false)) { - fdUserLock = std::move(fd); - uid = settings.startId + i * settings.idsPerBuild; - gid = settings.startId + i * settings.idsPerBuild; - return true; - } - } - - return false; -} - -void UserLock::kill() -{ - // FIXME: use a cgroup to kill all processes in the build? -#if 0 - killUser(uid); -#endif -} - ////////////////////////////////////////////////////////////////////// @@ -840,6 +692,13 @@ private: Path chrootRootDir; + /* Whether to give the build more than 1 UID. */ + bool useUidRange = false; + + /* Whether to make the 'systemd' cgroup controller available to + the build. */ + bool useSystemdCgroup = false; + /* RAII object to delete the chroot directory. */ std::shared_ptr autoDelChroot; @@ -896,8 +755,8 @@ private: result. */ std::map prevInfos; - const uid_t sandboxUid = 1000; - const gid_t sandboxGid = 100; + uid_t sandboxUid = -1; + gid_t sandboxGid = -1; const static Path homeDir; @@ -1445,6 +1304,7 @@ void DerivationGoal::inputsRealised() result = BuildResult(); } + void DerivationGoal::started() { auto msg = fmt( buildMode == bmRepair ? "repairing outputs of '%s'" : @@ -1459,6 +1319,7 @@ void DerivationGoal::started() { worker.updateProgress(); } + void DerivationGoal::tryToBuild() { trace("trying to build"); @@ -1556,25 +1417,28 @@ void DerivationGoal::tryToBuild() worker.wakeUp(shared_from_this()); } + void DerivationGoal::tryLocalBuild() { /* If `build-users-group' is not empty, then we have to build as one of the members of that group. */ - if ((settings.buildUsersGroup != "" || settings.startId.get() != 0) && getuid() == 0) { + static bool useBuildUsers = (settings.buildUsersGroup != "" || settings.startId.get() != 0) && getuid() == 0; + if (useBuildUsers) { #if defined(__linux__) || defined(__APPLE__) - if (!buildUser) buildUser = std::make_unique(); + if (!buildUser) + buildUser = acquireUserLock(); - if (buildUser->findFreeUser()) { - /* Make sure that no other processes are executing under this - uid. */ - buildUser->kill(); - } else { + if (!buildUser) { if (!actLock) actLock = std::make_unique(*logger, lvlWarn, actBuildWaiting, fmt("waiting for UID to build '%s'", yellowtxt(worker.store.printStorePath(drvPath)))); worker.waitForAWhile(shared_from_this()); return; } + + /* Make sure that no other processes are executing under this + uid. */ + buildUser->kill(); #else /* Don't know how to block the creation of setuid/setgid binaries on this platform. */ @@ -2087,6 +1951,9 @@ void DerivationGoal::startBuilder() } } + useUidRange = parsedDrv->getRequiredSystemFeatures().count("uid-range"); + useSystemdCgroup = parsedDrv->getRequiredSystemFeatures().count("systemd-cgroup"); + if (useChroot) { /* Allow a user-configurable set of directories from the @@ -2166,7 +2033,7 @@ void DerivationGoal::startBuilder() printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir); - if (mkdir(chrootRootDir.c_str(), 0755) == -1) + if (mkdir(chrootRootDir.c_str(), useUidRange ? 0755 : 0750) == -1) throw SysError("cannot create '%1%'", chrootRootDir); // FIXME: only make root writable for user namespace builds. @@ -2186,6 +2053,12 @@ void DerivationGoal::startBuilder() createDirs(chrootRootDir + "/etc"); chownToBuilder(chrootRootDir + "/etc"); + if (useUidRange && (!buildUser || buildUser->getUIDCount() < 65536)) + throw Error("feature 'uid-range' requires '%s' to be enabled", settings.autoAllocateUids.name); + + sandboxUid = useUidRange ? 0 : 1000; + sandboxGid = useUidRange ? 0 : 100; + writeFile(chrootRootDir + "/etc/passwd", fmt( "root:x:0:0:Nix build user:%3%:/noshell\n" "nixbld:x:%1%:%2%:Nix build user:%3%:/noshell\n" @@ -2238,12 +2111,32 @@ void DerivationGoal::startBuilder() for (auto & i : drv->outputs) dirsInChroot.erase(worker.store.printStorePath(i.second.path)); -#elif __APPLE__ - /* We don't really have any parent prep work to do (yet?) - All work happens in the child, instead. */ + if (useSystemdCgroup) { + settings.requireExperimentalFeature("systemd-cgroup"); + std::optional cgroup; + if (!buildUser || !(cgroup = buildUser->getCgroup())) + throw Error("feature 'systemd-cgroup' requires 'auto-allocate-uids = true' in nix.conf"); + chownToBuilder(*cgroup); + chownToBuilder(*cgroup + "/cgroup.procs"); + } + #else - throw Error("sandboxing builds is not supported on this platform"); + if (useUidRange) + throw Error("feature 'uid-range' is not supported on this platform"); + if (useSystemdCgroup) + throw Error("feature 'systemd-cgroup' is not supported on this platform"); + #if __APPLE__ + /* We don't really have any parent prep work to do (yet?) + All work happens in the child, instead. */ + #else + throw Error("sandboxing builds is not supported on this platform"); + #endif #endif + } else { + if (useUidRange) + throw Error("feature 'uid-range' is only supported in sandboxed builds"); + if (useSystemdCgroup) + throw Error("feature 'systemd-cgroup' is only supported in sandboxed builds"); } if (needsHashRewrite()) { @@ -2375,31 +2268,6 @@ void DerivationGoal::startBuilder() #if __linux__ if (useChroot) { - /* Create a systemd cgroup since that's the minimum required - by systemd-nspawn. */ - // FIXME: do we want to use the parent cgroup? We should - // always use the same cgroup regardless of whether we're the - // daemon or run from a user session via sudo. - auto ourCgroups = getCgroups("/proc/self/cgroup"); - auto systemdCgroup = ourCgroups["systemd"]; - if (systemdCgroup == "") - throw Error("'systemd' cgroup does not exist"); - - auto hostCgroup = canonPath("/sys/fs/cgroup/systemd/" + systemdCgroup); - - if (!pathExists(hostCgroup)) - throw Error("expected cgroup directory '%s'", hostCgroup); - - auto childCgroup = fmt("%s/nix-%d", hostCgroup, buildUser->getUID()); - - destroyCgroup(childCgroup); - - if (mkdir(childCgroup.c_str(), 0755) == -1) - throw SysError("creating cgroup '%s'", childCgroup); - - chownToBuilder(childCgroup); - chownToBuilder(childCgroup + "/cgroup.procs"); - /* Set up private namespaces for the build: - The PID namespace causes the build to start as PID 1. @@ -2508,15 +2376,16 @@ void DerivationGoal::startBuilder() the calling user (if build users are disabled). */ uid_t hostUid = buildUser ? buildUser->getUID() : getuid(); uid_t hostGid = buildUser ? buildUser->getGID() : getgid(); - uint32_t nrIds = settings.idsPerBuild; // FIXME + uint32_t nrIds = buildUser && useUidRange ? buildUser->getUIDCount() : 1; writeFile("/proc/" + std::to_string(pid) + "/uid_map", - fmt("%d %d %d", /* sandboxUid */ 0, hostUid, nrIds)); + fmt("%d %d %d", sandboxUid, hostUid, nrIds)); - //writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny"); + if (!useUidRange) + writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny"); writeFile("/proc/" + std::to_string(pid) + "/gid_map", - fmt("%d %d %d", /* sandboxGid */ 0, hostGid, nrIds)); + fmt("%d %d %d", sandboxGid, hostGid, nrIds)); /* Save the mount namespace of the child. We have to do this *before* the child does a chroot. */ @@ -2525,7 +2394,10 @@ void DerivationGoal::startBuilder() throw SysError("getting sandbox mount namespace"); /* Move the child into its own cgroup. */ - writeFile(childCgroup + "/cgroup.procs", fmt("%d", (pid_t) pid)); + if (buildUser) { + if (auto cgroup = buildUser->getCgroup()) + writeFile(*cgroup + "/cgroup.procs", fmt("%d", (pid_t) pid)); + } /* Signal the builder that we've updated its user namespace. */ writeFull(userNamespaceSync.writeSide.get(), "1"); @@ -3361,7 +3233,7 @@ void DerivationGoal::runChild() /* Unshare the cgroup namespace. This means /proc/self/cgroup will show the child's cgroup as '/' rather than whatever it is in the parent. */ - if (unshare(CLONE_NEWCGROUP) == -1) + if (useSystemdCgroup && unshare(CLONE_NEWCGROUP) == -1) throw SysError("unsharing cgroup namespace"); /* Do the chroot(). */ @@ -3386,16 +3258,10 @@ void DerivationGoal::runChild() /* Switch to the sandbox uid/gid in the user namespace, which corresponds to the build user or calling user in the parent namespace. */ -#if 0 if (setgid(sandboxGid) == -1) throw SysError("setgid failed"); if (setuid(sandboxUid) == -1) throw SysError("setuid failed"); -#endif - if (setgid(0) == -1) - throw SysError("setgid failed"); - if (setuid(0) == -1) - throw SysError("setuid failed"); setUser = false; } @@ -3789,7 +3655,7 @@ void DerivationGoal::registerOutputs() something like that. */ canonicalisePathMetaData( actualPath, - buildUser ? std::optional(std::make_pair(buildUser->getUID(), buildUser->getUID() + buildUser->getIDCount() - 1)) : std::nullopt, + buildUser ? std::optional(buildUser->getUIDRange()) : std::nullopt, inodesSeen); /* FIXME: this is in-memory. */ @@ -3866,7 +3732,7 @@ void DerivationGoal::registerOutputs() all files are owned by the build user, if applicable. */ canonicalisePathMetaData(actualPath, buildUser && !rewritten - ? std::optional(std::make_pair(buildUser->getUID(), buildUser->getUID() + buildUser->getIDCount() - 1)) + ? std::optional(buildUser->getUIDRange()) : std::nullopt, inodesSeen); diff --git a/src/libstore/cgroup.cc b/src/libstore/cgroup.cc index 887facdca..9e5e937df 100644 --- a/src/libstore/cgroup.cc +++ b/src/libstore/cgroup.cc @@ -4,6 +4,7 @@ #include "util.hh" #include +#include #include @@ -19,7 +20,7 @@ std::map getCgroups(const Path & cgroupFile) if (!std::regex_match(line, match, regex)) throw Error("invalid line '%s' in '%s'", line, cgroupFile); - std::string name = hasPrefix(match[2], "name=") ? std::string(match[2], 5) : match[2]; + std::string name = hasPrefix(std::string(match[2]), "name=") ? std::string(match[2], 5) : match[2]; cgroups.insert_or_assign(name, match[3]); } @@ -28,6 +29,8 @@ std::map getCgroups(const Path & cgroupFile) void destroyCgroup(const Path & cgroup) { + if (!pathExists(cgroup)) return; + for (auto & entry : readDirectory(cgroup)) { if (entry.type != DT_DIR) continue; destroyCgroup(cgroup + "/" + entry.name); @@ -35,6 +38,8 @@ void destroyCgroup(const Path & cgroup) int round = 1; + std::unordered_set pidsShown; + while (true) { auto pids = tokenizeString>(readFile(cgroup + "/cgroup.procs")); @@ -46,13 +51,23 @@ void destroyCgroup(const Path & cgroup) for (auto & pid_s : pids) { pid_t pid; if (!string2Int(pid_s, pid)) throw Error("invalid pid '%s'", pid); + if (pidsShown.insert(pid).second) { + try { + auto cmdline = readFile(fmt("/proc/%d/cmdline", pid)); + using namespace std::string_literals; + warn("killing stray builder process %d (%s)...", + pid, trim(replaceStrings(cmdline, "\0"s, " "))); + } catch (SysError &) { + } + } // FIXME: pid wraparound if (kill(pid, SIGKILL) == -1 && errno != ESRCH) throw SysError("killing member %d of cgroup '%s'", pid, cgroup); } auto sleep = std::chrono::milliseconds((int) std::pow(2.0, std::min(round, 10))); - printError("waiting for %d ms for cgroup '%s' to become empty", sleep.count(), cgroup); + if (sleep.count() > 100) + printError("waiting for %d ms for cgroup '%s' to become empty", sleep.count(), cgroup); std::this_thread::sleep_for(sleep); round++; } diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 89db072b0..5cf73c7b4 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -149,10 +149,13 @@ public: "The Unix group that contains the build users."}; #if __linux__ + Setting autoAllocateUids{this, false, "auto-allocate-uids", + "Whether to allocate UIDs for builders automatically."}; + const uint32_t idsPerBuild = 1 << 16; Setting startId{this, 872415232, "start-id", - "The first UID and GID to use for dynamic ID allocation. (0 means disable.)"}; + "The first UID and GID to use for dynamic ID allocation."}; Setting uidCount{this, idsPerBuild * 128, "id-count", "The number of UIDs/GIDs to use for dynamic ID allocation."}; diff --git a/src/libstore/user-lock.cc b/src/libstore/user-lock.cc new file mode 100644 index 000000000..8a09df4d1 --- /dev/null +++ b/src/libstore/user-lock.cc @@ -0,0 +1,212 @@ +#include "user-lock.hh" +#include "globals.hh" +#include "pathlocks.hh" +#include "cgroup.hh" + +namespace nix { + +struct SimpleUserLock : UserLock +{ + AutoCloseFD fdUserLock; + uid_t uid; + gid_t gid; + std::vector supplementaryGIDs; + + void kill() override + { + killUser(uid); + } + + std::pair getUIDRange() override + { + assert(uid); + return {uid, uid}; + } + + gid_t getGID() override { assert(gid); return gid; } + + std::vector getSupplementaryGIDs() override { return supplementaryGIDs; } + + static std::unique_ptr acquire() + { + assert(settings.buildUsersGroup != ""); + createDirs(settings.nixStateDir + "/userpool"); + + /* Get the members of the build-users-group. */ + struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); + if (!gr) + throw Error("the group '%s' specified in 'build-users-group' does not exist", settings.buildUsersGroup); + + /* Copy the result of getgrnam. */ + Strings users; + for (char * * p = gr->gr_mem; *p; ++p) { + debug("found build user '%s'", *p); + users.push_back(*p); + } + + if (users.empty()) + throw Error("the build users group '%s' has no members", settings.buildUsersGroup); + + /* Find a user account that isn't currently in use for another + build. */ + for (auto & i : users) { + debug("trying user '%s'", i); + + struct passwd * pw = getpwnam(i.c_str()); + if (!pw) + throw Error("the user '%s' in the group '%s' does not exist", i, settings.buildUsersGroup); + + auto fnUserLock = fmt("%s/userpool/%s", settings.nixStateDir,pw->pw_uid); + + AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600); + if (!fd) + throw SysError("opening user lock '%s'", fnUserLock); + + if (lockFile(fd.get(), ltWrite, false)) { + auto lock = std::make_unique(); + + lock->fdUserLock = std::move(fd); + lock->uid = pw->pw_uid; + lock->gid = gr->gr_gid; + + /* Sanity check... */ + if (lock->uid == getuid() || lock->uid == geteuid()) + throw Error("the Nix user should not be a member of '%s'", settings.buildUsersGroup); + + #if __linux__ + /* Get the list of supplementary groups of this build + user. This is usually either empty or contains a + group such as "kvm". */ + lock->supplementaryGIDs.resize(10); + int ngroups = lock->supplementaryGIDs.size(); + int err = getgrouplist(pw->pw_name, pw->pw_gid, + lock->supplementaryGIDs.data(), &ngroups); + if (err == -1) + throw Error("failed to get list of supplementary groups for '%s'", pw->pw_name); + + lock->supplementaryGIDs.resize(ngroups); + #endif + + return lock; + } + } + + return nullptr; + } +}; + +#if __linux__ +struct CgroupUserLock : UserLock +{ + AutoCloseFD fdUserLock; + uid_t uid; + + void kill() override + { + if (cgroup) { + destroyCgroup(*cgroup); + cgroup.reset(); + } + } + + std::pair getUIDRange() override + { + assert(uid); + return {uid, uid + settings.idsPerBuild - 1}; + } + + gid_t getGID() override + { + // We use the same GID ranges as for the UIDs. + assert(uid); + return uid; + } + + std::vector getSupplementaryGIDs() override { return {}; } // FIXME + + static std::unique_ptr acquire() + { + settings.requireExperimentalFeature("auto-allocate-uids"); + assert(settings.startId > 0); + assert(settings.startId % settings.idsPerBuild == 0); + assert(settings.uidCount % settings.idsPerBuild == 0); + assert((uint64_t) settings.startId + (uint64_t) settings.uidCount <= std::numeric_limits::max()); + + // FIXME: check whether the id range overlaps any known users + + createDirs(settings.nixStateDir + "/userpool2"); + + size_t nrSlots = settings.uidCount / settings.idsPerBuild; + + for (size_t i = 0; i < nrSlots; i++) { + debug("trying user slot '%d'", i); + + createDirs(settings.nixStateDir + "/userpool2"); + + auto fnUserLock = fmt("%s/userpool2/slot-%d", settings.nixStateDir, i); + + AutoCloseFD fd = open(fnUserLock.c_str(), O_RDWR | O_CREAT | O_CLOEXEC, 0600); + if (!fd) + throw SysError("opening user lock '%s'", fnUserLock); + + if (lockFile(fd.get(), ltWrite, false)) { + auto lock = std::make_unique(); + lock->fdUserLock = std::move(fd); + lock->uid = settings.startId + i * settings.idsPerBuild; + auto s = drainFD(lock->fdUserLock.get()); + if (s != "") lock->cgroup = s; + return lock; + } + } + + return nullptr; + } + + std::optional cgroup; + + std::optional getCgroup() override + { + if (!cgroup) { + /* Create a systemd cgroup since that's the minimum + required by systemd-nspawn. */ + auto ourCgroups = getCgroups("/proc/self/cgroup"); + auto systemdCgroup = ourCgroups["systemd"]; + if (systemdCgroup == "") + throw Error("'systemd' cgroup does not exist"); + + auto hostCgroup = canonPath("/sys/fs/cgroup/systemd/" + systemdCgroup); + + if (!pathExists(hostCgroup)) + throw Error("expected cgroup directory '%s'", hostCgroup); + + cgroup = fmt("%s/nix-%d", hostCgroup, uid); + + destroyCgroup(*cgroup); + + if (mkdir(cgroup->c_str(), 0755) == -1) + throw SysError("creating cgroup '%s'", *cgroup); + + /* Record the cgroup in the lock file. This ensures that + if we subsequently get executed under a different parent + cgroup, we kill the previous cgroup first. */ + if (ftruncate(fdUserLock.get(), 0) == -1) + throw Error("truncating user lock"); + writeFull(fdUserLock.get(), *cgroup); + } + + return cgroup; + }; +}; +#endif + +std::unique_ptr acquireUserLock() +{ + #if __linux__ + if (settings.autoAllocateUids) + return CgroupUserLock::acquire(); + else + #endif + return SimpleUserLock::acquire(); +} + +} diff --git a/src/libstore/user-lock.hh b/src/libstore/user-lock.hh new file mode 100644 index 000000000..88d068689 --- /dev/null +++ b/src/libstore/user-lock.hh @@ -0,0 +1,39 @@ +#pragma once + +#include "types.hh" + +namespace nix { + +struct UserLock +{ + virtual ~UserLock() { } + + /* Get the first and last UID. */ + virtual std::pair getUIDRange() = 0; + + /* Get the first UID. */ + uid_t getUID() + { + return getUIDRange().first; + } + + uid_t getUIDCount() + { + return getUIDRange().second - getUIDRange().first + 1; + } + + virtual gid_t getGID() = 0; + + virtual std::vector getSupplementaryGIDs() = 0; + + /* Kill any processes currently executing as this user. */ + virtual void kill() = 0; + + virtual std::optional getCgroup() { return {}; }; +}; + +/* Acquire a user lock. Note that this may return nullptr if no user + is available. */ +std::unique_ptr acquireUserLock(); + +} From 8c4cce553c16438f0ccbbaea6d77f2bd64306dfe Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 May 2020 11:24:21 +0200 Subject: [PATCH 008/245] Fix macOS build --- src/libstore/build.cc | 11 +---------- src/libstore/user-lock.cc | 13 +++++++++++++ src/libstore/user-lock.hh | 2 ++ 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 1f79a8d2d..6c3f94a76 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -1420,11 +1420,7 @@ void DerivationGoal::tryToBuild() void DerivationGoal::tryLocalBuild() { - /* If `build-users-group' is not empty, then we have to build as - one of the members of that group. */ - static bool useBuildUsers = (settings.buildUsersGroup != "" || settings.startId.get() != 0) && getuid() == 0; - if (useBuildUsers) { -#if defined(__linux__) || defined(__APPLE__) + if (useBuildUsers()) { if (!buildUser) buildUser = acquireUserLock(); @@ -1439,11 +1435,6 @@ void DerivationGoal::tryLocalBuild() { /* Make sure that no other processes are executing under this uid. */ buildUser->kill(); -#else - /* Don't know how to block the creation of setuid/setgid - binaries on this platform. */ - throw Error("build users are not supported on this platform for security reasons"); -#endif } actLock.reset(); diff --git a/src/libstore/user-lock.cc b/src/libstore/user-lock.cc index 8a09df4d1..2254386da 100644 --- a/src/libstore/user-lock.cc +++ b/src/libstore/user-lock.cc @@ -209,4 +209,17 @@ std::unique_ptr acquireUserLock() return SimpleUserLock::acquire(); } +bool useBuildUsers() +{ + #if __linux__ + static bool b = (settings.buildUsersGroup != "" || settings.startId.get() != 0) && getuid() == 0; + return b; + #elif __APPLE__ + static bool b = settings.buildUsersGroup != "" && getuid() == 0; + return b; + #else + return false; + #endif +} + } diff --git a/src/libstore/user-lock.hh b/src/libstore/user-lock.hh index 88d068689..bfb55b0d9 100644 --- a/src/libstore/user-lock.hh +++ b/src/libstore/user-lock.hh @@ -36,4 +36,6 @@ struct UserLock is available. */ std::unique_ptr acquireUserLock(); +bool useBuildUsers(); + } From 7349f257da8278af9aae35544b15c9a204e2a57b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 20 May 2020 11:57:33 +0200 Subject: [PATCH 009/245] Only mount /sys in uid-range builds Maybe this should be a separate system feature... /sys exposes a lot of impure info about the host system. --- src/libstore/build.cc | 11 ++++++----- src/libstore/user-lock.cc | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libstore/build.cc b/src/libstore/build.cc index 6c3f94a76..e927a65f0 100644 --- a/src/libstore/build.cc +++ b/src/libstore/build.cc @@ -3173,11 +3173,12 @@ void DerivationGoal::runChild() if (mount("none", (chrootRootDir + "/proc").c_str(), "proc", 0, 0) == -1) throw SysError("mounting /proc"); - /* Mount sysfs on /sys. FIXME: only in user namespace - builds. */ - createDirs(chrootRootDir + "/sys"); - if (mount("none", (chrootRootDir + "/sys").c_str(), "sysfs", 0, 0) == -1) - throw SysError("mounting /sys"); + /* Mount sysfs on /sys. */ + if (useUidRange) { + createDirs(chrootRootDir + "/sys"); + if (mount("none", (chrootRootDir + "/sys").c_str(), "sysfs", 0, 0) == -1) + throw SysError("mounting /sys"); + } /* Mount a new tmpfs on /dev/shm to ensure that whatever the builder puts in /dev/shm is cleaned up automatically. */ diff --git a/src/libstore/user-lock.cc b/src/libstore/user-lock.cc index 2254386da..fb2a45f48 100644 --- a/src/libstore/user-lock.cc +++ b/src/libstore/user-lock.cc @@ -122,7 +122,7 @@ struct CgroupUserLock : UserLock return uid; } - std::vector getSupplementaryGIDs() override { return {}; } // FIXME + std::vector getSupplementaryGIDs() override { return {}; } static std::unique_ptr acquire() { From 8499f32fb2e7fdf09e97d0beb1fe78bef5900d93 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 12 Feb 2021 21:51:36 +0000 Subject: [PATCH 010/245] New "indexed" installable syntax: `!` Being conservative and only doing a single output name for now. --- src/libcmd/installables.cc | 42 +++++++++++++++++++++++++++++++++- src/nix/nix.md | 10 ++++++++ tests/build-explicit-output.sh | 17 ++++++++++++++ tests/build.sh | 5 ++-- tests/local.mk | 1 + 5 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 tests/build-explicit-output.sh diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 5d3026c1a..cf7681d0d 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -349,6 +349,31 @@ struct InstallableStorePath : Installable } }; +struct InstallableIndexedStorePath : Installable +{ + ref store; + DerivedPath::Built req; + + InstallableIndexedStorePath(ref store, DerivedPath::Built && req) + : store(store), req(std::move(req)) + { } + + std::string what() override + { + return req.to_string(*store); + } + + DerivedPathsWithHints toDerivedPathsWithHints() override + { + std::map> outputs; + for (auto & output : req.outputs) + outputs.insert_or_assign(output, std::nullopt); + return { + DerivedPathWithHints { DerivedPathWithHints::Built { req.drvPath, std::move(outputs) } } + }; + } +}; + DerivedPathsWithHints InstallableValue::toDerivedPathsWithHints() { DerivedPathsWithHints res; @@ -638,7 +663,22 @@ std::vector> SourceExprCommand::parseInstallables( ex = std::current_exception(); } - if (s.find('/') != std::string::npos) { + auto found = s.rfind('!'); + if (found != std::string::npos) { + try { + result.push_back(std::make_shared( + store, + DerivedPath::Built::parse(*store, s))); + continue; + } catch (BadStorePath &) { + } catch (...) { + if (!ex) + ex = std::current_exception(); + } + } + + found = s.find('/'); + if (found != std::string::npos) { try { result.push_back(std::make_shared(store, store->followLinksToStorePath(s))); continue; diff --git a/src/nix/nix.md b/src/nix/nix.md index d10de7c01..22cc9d476 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -94,6 +94,16 @@ the Nix store. Here are the recognised types of installables: If you want to operate on the store derivation itself, pass the `--derivation` flag. +* **Indexed store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv!out` + + Store derivations can be indexed with a specific output name. This + allows finer control versus just specifying a derivation (without + `--derivation`) and getting all the outputs. + + This is especially useful for (currently unstable) floating content + addressed derivations, which do not have precomputed output paths that + can be used instead. + * **Nix attributes**: `--file /path/to/nixpkgs hello` When the `-f` / `--file` *path* option is given, installables are diff --git a/tests/build-explicit-output.sh b/tests/build-explicit-output.sh new file mode 100644 index 000000000..17930c2c0 --- /dev/null +++ b/tests/build-explicit-output.sh @@ -0,0 +1,17 @@ +source common.sh + +drv=$(nix eval -f multiple-outputs.nix --raw a.drvPath) +if nix build "$drv!not-an-output" --json; then + fail "'not-an-output' should fail to build" +fi + +nix build "$drv!first" --json | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | + .first and + (has("second") | not))) +' +# TODO use +# (.first | match(".*multiple-outputs-a-first")) and +# once we make it put the result paths in the buildables. diff --git a/tests/build.sh b/tests/build.sh index aa54b88eb..5a2819336 100644 --- a/tests/build.sh +++ b/tests/build.sh @@ -4,8 +4,9 @@ expectedJSONRegex='\[\{"drvPath":".*multiple-outputs-a.drv","outputs":\{"first": nix build -f multiple-outputs.nix --json a.all b.all | jq --exit-status ' (.[0] | (.drvPath | match(".*multiple-outputs-a.drv")) and - (.outputs.first | match(".*multiple-outputs-a-first")) and - (.outputs.second | match(".*multiple-outputs-a-second"))) + (.outputs | + (.first | match(".*multiple-outputs-a-first")) and + (.second | match(".*multiple-outputs-a-second")))) and (.[1] | (.drvPath | match(".*multiple-outputs-b.drv")) and (.outputs.out | match(".*multiple-outputs-b"))) diff --git a/tests/local.mk b/tests/local.mk index e7e85f97e..1d3e89499 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -41,6 +41,7 @@ nix_tests = \ describe-stores.sh \ flakes.sh \ build.sh \ + build-explicit-output.sh \ compute-levels.sh \ ca/build.sh \ ca/substitute.sh \ From 6951b26ed0a806c03ad73069ccc925ef6ac158e6 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 1 Apr 2022 03:57:51 +0000 Subject: [PATCH 011/245] Require (new) computed-derivations experimental feature for ! installable --- src/libcmd/installables.cc | 1 + src/libutil/experimental-features.cc | 1 + src/libutil/experimental-features.hh | 1 + tests/build-explicit-output.sh | 3 +++ 4 files changed, 6 insertions(+) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 1d44ffe84..ab13f11df 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -740,6 +740,7 @@ std::vector> SourceExprCommand::parseInstallables( result.push_back(std::make_shared( store, DerivedPath::Built::parse(*store, s))); + settings.requireExperimentalFeature(Xp::ComputedDerivations); continue; } catch (BadStorePath &) { } catch (...) { diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index e033a4116..c1e574c0d 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -13,6 +13,7 @@ std::map stringifiedXpFeatures = { { Xp::RecursiveNix, "recursive-nix" }, { Xp::NoUrlLiterals, "no-url-literals" }, { Xp::FetchClosure, "fetch-closure" }, + { Xp::ComputedDerivations, "computed-derivations" }, }; const std::optional parseExperimentalFeature(const std::string_view & name) diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index 3a254b423..58e082c72 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -22,6 +22,7 @@ enum struct ExperimentalFeature RecursiveNix, NoUrlLiterals, FetchClosure, + ComputedDerivations, // RFC 92 }; /** diff --git a/tests/build-explicit-output.sh b/tests/build-explicit-output.sh index fcb263913..0f2f428db 100644 --- a/tests/build-explicit-output.sh +++ b/tests/build-explicit-output.sh @@ -1,5 +1,8 @@ source common.sh +enableFeatures "computed-derivations" +restartDaemon + drv=$(nix eval -f multiple-outputs.nix --raw a.drvPath) if nix build "$drv!not-an-output" --json; then fail "'not-an-output' should fail to build" From fda2224b591c2667d18fb815f117f48b45a54cb1 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 7 Apr 2022 19:45:38 +0000 Subject: [PATCH 012/245] Add release notes mark experimental --- doc/manual/src/release-notes/rl-next.md | 3 +++ src/nix/nix.md | 2 ++ 2 files changed, 5 insertions(+) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 8c8c0fd41..97627cc96 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -40,3 +40,6 @@ As before, the old output will continue to work, but `nix flake check` will issue a warning about it. + +* Add experimental *indexed store derivations* installable syntax, part of the + the `computed-derivations` experimental feature. diff --git a/src/nix/nix.md b/src/nix/nix.md index 4919763c4..691aa137b 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -132,6 +132,8 @@ the Nix store. Here are the recognised types of installables: * **Indexed store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv!out` + *(Experimental, part of by the `computed-derivations` experimental feature.)* + Store derivations can be indexed with a specific output name. This allows finer control versus just specifying a derivation (without `--derivation`) and getting all the outputs. From 49119072e72ae160c22a2c0b963cfe732b2819e4 Mon Sep 17 00:00:00 2001 From: Adam Joseph Date: Wed, 27 Apr 2022 17:55:04 -0700 Subject: [PATCH 013/245] local-derivation-goal.cc: seccomp filters for MIPS secondary arch/abi A mips64el Linux MIPS kernel can execute userspace code using any of three ABIs: mips64el-linux-*abin64 mips64el-linux-*abin32 mipsel-linux-* The first of these is the native 64-bit ABI, and the only ABI with 64-bit pointers; this is sometimes called "n64". The last of these is the old legacy 32-bit ABI, whose binaries can execute natively on 32-bit MIPS hardware; this is sometimes called "o32". The second ABI, "n32" is essentially the 64-bit ABI with 32-bit pointers and address space. Hardware 64-bit integer/floating arithmetic is still allowed, as well as the much larger mips64 register set and more-efficient calling convention. Let's enable seccomp filters for all of these. Likewise for big endian (mips64-linux-*). --- src/libstore/build/local-derivation-goal.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 4c91fa4fb..f80a4678b 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -1529,6 +1529,22 @@ void setupSeccomp() seccomp_arch_add(ctx, SCMP_ARCH_ARM) != 0) printError("unable to add ARM seccomp architecture; this may result in spurious build failures if running 32-bit ARM processes"); + if (nativeSystem == "mips64-linux" && + seccomp_arch_add(ctx, SCMP_ARCH_MIPS) != 0) + printError("unable to add mips seccomp architecture"); + + if (nativeSystem == "mips64-linux" && + seccomp_arch_add(ctx, SCMP_ARCH_MIPS64N32) != 0) + printError("unable to add mips64-*abin32 seccomp architecture"); + + if (nativeSystem == "mips64el-linux" && + seccomp_arch_add(ctx, SCMP_ARCH_MIPSEL) != 0) + printError("unable to add mipsel seccomp architecture"); + + if (nativeSystem == "mips64el-linux" && + seccomp_arch_add(ctx, SCMP_ARCH_MIPSEL64N32) != 0) + printError("unable to add mips64el-*abin32 seccomp architecture"); + /* Prevent builders from creating setuid/setgid binaries. */ for (int perm : { S_ISUID, S_ISGID }) { if (seccomp_rule_add(ctx, SCMP_ACT_ERRNO(EPERM), SCMP_SYS(chmod), 1, From 49ad315c0357116787ef45a1249009b6bc00301f Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 12 May 2022 20:10:02 +0000 Subject: [PATCH 014/245] Use `^` not `!` in indexed store derivations installable syntax Match the other syntax that was recently added --- src/libcmd/installables.cc | 8 +++++--- src/libstore/derived-path.cc | 11 ++++------- src/libstore/derived-path.hh | 2 +- src/nix/nix.md | 2 +- tests/build-explicit-output.sh | 4 ++-- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index e0a95118d..575e7f696 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -799,11 +799,12 @@ std::vector> SourceExprCommand::parseInstallables( for (auto & s : ss) { std::exception_ptr ex; - if (s.rfind('!') != std::string::npos) { + auto found = s.rfind('^'); + if (found != std::string::npos) { try { result.push_back(std::make_shared( store, - DerivedPath::Built::parse(*store, s))); + DerivedPath::Built::parse(*store, s.substr(0, found), s.substr(found + 1)))); settings.requireExperimentalFeature(Xp::ComputedDerivations); continue; } catch (BadStorePath &) { @@ -813,7 +814,8 @@ std::vector> SourceExprCommand::parseInstallables( } } - if (s.find('/') != std::string::npos) { + found = s.find('/'); + if (found != std::string::npos) { try { result.push_back(std::make_shared(store, store->followLinksToStorePath(s))); continue; diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 44587ae78..11a3f5e23 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -93,12 +93,9 @@ DerivedPath::Opaque DerivedPath::Opaque::parse(const Store & store, std::string_ return {store.parseStorePath(s)}; } -DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view s) +DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_view drvS, std::string_view outputsS) { - size_t n = s.find("!"); - assert(n != s.npos); - auto drvPath = store.parseStorePath(s.substr(0, n)); - auto outputsS = s.substr(n + 1); + auto drvPath = store.parseStorePath(drvS); std::set outputs; if (outputsS != "*") outputs = tokenizeString>(outputsS, ","); @@ -107,10 +104,10 @@ DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_vi DerivedPath DerivedPath::parse(const Store & store, std::string_view s) { - size_t n = s.find("!"); + size_t n = s.rfind("!"); return n == s.npos ? (DerivedPath) DerivedPath::Opaque::parse(store, s) - : (DerivedPath) DerivedPath::Built::parse(store, s); + : (DerivedPath) DerivedPath::Built::parse(store, s.substr(0, n), s.substr(n + 1)); } RealisedPath::Set BuiltPath::toRealisedPaths(Store & store) const diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh index 24a0ae773..fab1292a7 100644 --- a/src/libstore/derived-path.hh +++ b/src/libstore/derived-path.hh @@ -47,7 +47,7 @@ struct DerivedPathBuilt { std::set outputs; std::string to_string(const Store & store) const; - static DerivedPathBuilt parse(const Store & store, std::string_view); + static DerivedPathBuilt parse(const Store & store, std::string_view, std::string_view); nlohmann::json toJSON(ref store) const; bool operator < (const DerivedPathBuilt & b) const diff --git a/src/nix/nix.md b/src/nix/nix.md index 34c763c69..32112d38d 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -130,7 +130,7 @@ the Nix store. Here are the recognised types of installables: If you want to operate on the store derivation itself, pass the `--derivation` flag. -* **Indexed store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv!out` +* **Indexed store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv^out` *(Experimental, part of by the `computed-derivations` experimental feature.)* diff --git a/tests/build-explicit-output.sh b/tests/build-explicit-output.sh index 0f2f428db..68fd2f128 100644 --- a/tests/build-explicit-output.sh +++ b/tests/build-explicit-output.sh @@ -4,11 +4,11 @@ enableFeatures "computed-derivations" restartDaemon drv=$(nix eval -f multiple-outputs.nix --raw a.drvPath) -if nix build "$drv!not-an-output" --json; then +if nix build "$drv^not-an-output" --json; then fail "'not-an-output' should fail to build" fi -nix build "$drv!first" --json | jq --exit-status ' +nix build "$drv^first" --json | jq --exit-status ' (.[0] | (.drvPath | match(".*multiple-outputs-a.drv")) and (.outputs | From f3262bc2165af90fd20f04f74243aa75137767a2 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 14 Jul 2022 16:36:00 -0400 Subject: [PATCH 015/245] Combine `InstallableStorePath` with `InstallableIndexedStorePath` No behavior should be changed, the `isDerivation` logic is moved from the methods to the constructor. --- src/libcmd/installables.cc | 82 +++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 45 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index b78581a7c..7b8860a88 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -395,53 +395,21 @@ static StorePath getDeriver( struct InstallableStorePath : Installable { ref store; - StorePath storePath; + DerivedPath req; InstallableStorePath(ref store, StorePath && storePath) - : store(store), storePath(std::move(storePath)) { } + : store(store), + req(storePath.isDerivation() + ? (DerivedPath) DerivedPath::Built { + .drvPath = std::move(storePath), + .outputs = {}, + } + : (DerivedPath) DerivedPath::Opaque { + .path = std::move(storePath), + }) + { } - std::string what() const override { return store->printStorePath(storePath); } - - DerivedPaths toDerivedPaths() override - { - if (storePath.isDerivation()) { - auto drv = store->readDerivation(storePath); - return { - DerivedPath::Built { - .drvPath = storePath, - .outputs = drv.outputNames(), - } - }; - } else { - return { - DerivedPath::Opaque { - .path = storePath, - } - }; - } - } - - StorePathSet toDrvPaths(ref store) override - { - if (storePath.isDerivation()) { - return {storePath}; - } else { - return {getDeriver(store, *this, storePath)}; - } - } - - std::optional getStorePath() override - { - return storePath; - } -}; - -struct InstallableIndexedStorePath : Installable -{ - ref store; - DerivedPath::Built req; - - InstallableIndexedStorePath(ref store, DerivedPath::Built && req) + InstallableStorePath(ref store, DerivedPath && req) : store(store), req(std::move(req)) { } @@ -454,6 +422,30 @@ struct InstallableIndexedStorePath : Installable { return { req }; } + + StorePathSet toDrvPaths(ref store) override + { + return std::visit(overloaded { + [&](const DerivedPath::Built & bfd) -> StorePathSet { + return { bfd.drvPath }; + }, + [&](const DerivedPath::Opaque & bo) -> StorePathSet { + return { getDeriver(store, *this, bo.path) }; + }, + }, req.raw()); + } + + std::optional getStorePath() override + { + return std::visit(overloaded { + [&](const DerivedPath::Built & bfd) { + return bfd.drvPath; + }, + [&](const DerivedPath::Opaque & bo) { + return bo.path; + }, + }, req.raw()); + } }; DerivedPaths InstallableValue::toDerivedPaths() @@ -819,7 +811,7 @@ std::vector> SourceExprCommand::parseInstallables( auto found = s.rfind('^'); if (found != std::string::npos) { try { - result.push_back(std::make_shared( + result.push_back(std::make_shared( store, DerivedPath::Built::parse(*store, s.substr(0, found), s.substr(found + 1)))); settings.requireExperimentalFeature(Xp::ComputedDerivations); From 8735f55decab03ecf3571f756a22abc3b3dc6304 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 14 Jul 2022 20:22:46 -0400 Subject: [PATCH 016/245] Fix bug, test more, document more --- src/libstore/derived-path.cc | 6 +++++- src/nix/nix.md | 5 +++-- tests/build-explicit-output.sh | 32 ++++++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 11a3f5e23..f6a0c01df 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -97,8 +97,12 @@ DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_vi { auto drvPath = store.parseStorePath(drvS); std::set outputs; - if (outputsS != "*") + if (outputsS != "*") { outputs = tokenizeString>(outputsS, ","); + if (outputs.empty()) + throw Error( + "Explicit list of wanted outputs '%s' must not be empty. Consider using '*' as a wildcard meaning all outputs if no output in particular is wanted.", outputsS); + } return {drvPath, outputs}; } diff --git a/src/nix/nix.md b/src/nix/nix.md index 32112d38d..5d669e8b1 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -134,8 +134,9 @@ the Nix store. Here are the recognised types of installables: *(Experimental, part of by the `computed-derivations` experimental feature.)* - Store derivations can be indexed with a specific output name. This - allows finer control versus just specifying a derivation (without + Store derivations can be indexed with a non-empty comma-separated list + of specific output names, or `*` meaning all ouptuts. This allows + finer control versus just specifying a derivation (without `--derivation`) and getting all the outputs. This is especially useful for (currently unstable) floating content diff --git a/tests/build-explicit-output.sh b/tests/build-explicit-output.sh index 68fd2f128..a4cb1c5ad 100644 --- a/tests/build-explicit-output.sh +++ b/tests/build-explicit-output.sh @@ -1,14 +1,24 @@ source common.sh +set -o pipefail + enableFeatures "computed-derivations" restartDaemon drv=$(nix eval -f multiple-outputs.nix --raw a.drvPath) -if nix build "$drv^not-an-output" --json; then +if nix build "$drv^not-an-output" --no-link --json; then fail "'not-an-output' should fail to build" fi -nix build "$drv^first" --json | jq --exit-status ' +if nix build "$drv^" --no-link --json; then + fail "'empty outputs list' should fail to build" +fi + +if nix build "$drv^*nope" --no-link --json; then + fail "'* must be entire string' should fail to build" +fi + +nix build "$drv^first" --no-link --json | jq --exit-status ' (.[0] | (.drvPath | match(".*multiple-outputs-a.drv")) and (.outputs | @@ -16,3 +26,21 @@ nix build "$drv^first" --json | jq --exit-status ' (.first | match(".*multiple-outputs-a-first")) and (has("second") | not))) ' + +nix build "$drv^first,second" --no-link --json | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | + (keys | length == 2) and + (.first | match(".*multiple-outputs-a-first")) and + (.second | match(".*multiple-outputs-a-second")))) +' + +nix build "$drv^*" --no-link --json | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | + (keys | length == 2) and + (.first | match(".*multiple-outputs-a-first")) and + (.second | match(".*multiple-outputs-a-second")))) +' From 279ecf7cdee94b3b5e37e4ade3af3a6d20ca9cde Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 15 Jul 2022 13:29:15 +0000 Subject: [PATCH 017/245] Remove `computed-derivations` experimental feature We don't need it yet. --- doc/manual/src/release-notes/rl-next.md | 3 +-- src/libcmd/installables.cc | 1 - src/libutil/experimental-features.cc | 1 - src/libutil/experimental-features.hh | 1 - src/nix/nix.md | 2 -- tests/build-explicit-output.sh | 3 --- 6 files changed, 1 insertion(+), 10 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 3bb12c013..7047e2421 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1,4 +1,3 @@ # Release X.Y (202?-??-??) -* Add experimental *indexed store derivations* installable syntax, part of the - the `computed-derivations` experimental feature. +* Add *indexed store derivations* installable syntax. diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 7b8860a88..0641e99ff 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -814,7 +814,6 @@ std::vector> SourceExprCommand::parseInstallables( result.push_back(std::make_shared( store, DerivedPath::Built::parse(*store, s.substr(0, found), s.substr(found + 1)))); - settings.requireExperimentalFeature(Xp::ComputedDerivations); continue; } catch (BadStorePath &) { } catch (...) { diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index 6b2dd02e6..fa79cca6b 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -14,7 +14,6 @@ std::map stringifiedXpFeatures = { { Xp::NoUrlLiterals, "no-url-literals" }, { Xp::FetchClosure, "fetch-closure" }, { Xp::ReplFlake, "repl-flake" }, - { Xp::ComputedDerivations, "computed-derivations" }, }; const std::optional parseExperimentalFeature(const std::string_view & name) diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index 4cb2708dd..d09ab025c 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -23,7 +23,6 @@ enum struct ExperimentalFeature NoUrlLiterals, FetchClosure, ReplFlake, - ComputedDerivations, // RFC 92 }; /** diff --git a/src/nix/nix.md b/src/nix/nix.md index 5d669e8b1..ede88ebde 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -132,8 +132,6 @@ the Nix store. Here are the recognised types of installables: * **Indexed store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv^out` - *(Experimental, part of by the `computed-derivations` experimental feature.)* - Store derivations can be indexed with a non-empty comma-separated list of specific output names, or `*` meaning all ouptuts. This allows finer control versus just specifying a derivation (without diff --git a/tests/build-explicit-output.sh b/tests/build-explicit-output.sh index a4cb1c5ad..45320d6e3 100644 --- a/tests/build-explicit-output.sh +++ b/tests/build-explicit-output.sh @@ -2,9 +2,6 @@ source common.sh set -o pipefail -enableFeatures "computed-derivations" -restartDaemon - drv=$(nix eval -f multiple-outputs.nix --raw a.drvPath) if nix build "$drv^not-an-output" --no-link --json; then fail "'not-an-output' should fail to build" From 0e4ec98ae8a4ec60b24ebd676a9ace0f4ca81da8 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 15 Jul 2022 09:49:23 -0400 Subject: [PATCH 018/245] Fix typo in docs Thanks! Co-authored-by: Eelco Dolstra --- src/nix/nix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix/nix.md b/src/nix/nix.md index ede88ebde..29ad195ae 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -133,7 +133,7 @@ the Nix store. Here are the recognised types of installables: * **Indexed store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv^out` Store derivations can be indexed with a non-empty comma-separated list - of specific output names, or `*` meaning all ouptuts. This allows + of specific output names, or `*` meaning all outputs. This allows finer control versus just specifying a derivation (without `--derivation`) and getting all the outputs. From 12461e246b02371c6b6981b4e65985e9397474e1 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 15 Jul 2022 13:59:32 +0000 Subject: [PATCH 019/245] Leverage existing docs for new store-path^outputs syntax --- doc/manual/src/release-notes/rl-next.md | 2 +- src/nix/nix.md | 32 ++++++++++++++----------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 7047e2421..36b759a10 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1,3 +1,3 @@ # Release X.Y (202?-??-??) -* Add *indexed store derivations* installable syntax. +* Allow explicitly selecting outputs with *store derivations* installable syntax too. diff --git a/src/nix/nix.md b/src/nix/nix.md index 29ad195ae..811936024 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -130,17 +130,6 @@ the Nix store. Here are the recognised types of installables: If you want to operate on the store derivation itself, pass the `--derivation` flag. -* **Indexed store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv^out` - - Store derivations can be indexed with a non-empty comma-separated list - of specific output names, or `*` meaning all outputs. This allows - finer control versus just specifying a derivation (without - `--derivation`) and getting all the outputs. - - This is especially useful for (currently unstable) floating content - addressed derivations, which do not have precomputed output paths that - can be used instead. - * **Nix attributes**: `--file /path/to/nixpkgs hello` When the `-f` / `--file` *path* option is given, installables are @@ -175,6 +164,13 @@ operate are determined as follows: … ``` + and likewise, using a store path to a "drv" file to specify the derivation: + + ```console + # nix build '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev,static' + … + ``` + * You can also specify that *all* outputs should be used using the syntax *installable*`^*`. For example, the following shows the size of all outputs of the `glibc` package in the binary cache: @@ -188,9 +184,17 @@ operate are determined as follows: /nix/store/q6580lr01jpcsqs4r5arlh4ki2c1m9rv-glibc-2.33-123-dev 44200560 ``` -* If you didn't specify the desired outputs, but the derivation has an - attribute `meta.outputsToInstall`, Nix will use those outputs. For - example, since the package `nixpkgs#libxml2` has this attribute: + and likewise, again using a store path to a "drv" file to specify the derivation: + + ```console + # nix path-info -S --eval-store auto --store https://cache.nixos.org '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*' + … + ``` + +* If you didn't specify the desired outputs, but the derivation comes + from an expression which has an attribute `meta.outputsToInstall`, Nix + will use those outputs. For example, since the package + `nixpkgs#libxml2` has this attribute: ```console # nix eval 'nixpkgs#libxml2.meta.outputsToInstall' From 3378a3bce8a5dc1282d901f7f3ec152676198092 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:07:18 +0200 Subject: [PATCH 020/245] add syntax overview from NixOS manual taken verbatim to keep track of required corrections. made it an HTML table to more easily change structure and keep diffs minimal. --- doc/manual/src/language/index.md | 306 +++++++++++++++++++++++++++++++ 1 file changed, 306 insertions(+) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index c4b3abf75..6c355d923 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -11,3 +11,309 @@ packages, compositions of packages, and the variability within packages. This section presents the various features of the language. +# Syntax Summary + +Below is a summary of the most important syntactic constructs in the Nix +expression language. It's not complete. In particular, there are many +other built-in functions. See the [Nix +manual](https://nixos.org/nix/manual/#chap-writing-nix-expressions) for +the rest. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ Example + + Description +
+ *Basic values* + + +
+ `"Hello world"` + + A string +
+ `"${pkgs.bash}/bin/sh"` + + A string containing an expression (expands to `"/nix/store/hash-bash-version/bin/sh"`) +
+ `true`, `false` + + Booleans +
+ `123` + + An integer +
+ `./foo.png` + + A path (relative to the containing Nix expression) +
+ *Compound values* + + +
+ `{ x = 1; y = 2; }` + + A set with attributes named `x` and `y` +
+ `{ foo.bar = 1; }` + + A nested set, equivalent to `{ foo = { bar = 1; }; }` +
+ `rec { x = "foo"; y = x + "bar"; }` + + A recursive set, equivalent to `{ x = "foo"; y = "foobar"; }` +
+ `[ "foo" "bar" ]` + + A list with two elements +
+ *Operators* + + +
+ `"foo" + "bar"` + + String concatenation +
+ `1 + 2` + + Integer addition +
+ `"foo" == "f" + "oo"` + + Equality test (evaluates to `true`) +
+ `"foo" != "bar"` + + Inequality test (evaluates to `true`) +
+ `!true` + + Boolean negation +
+ `{ x = 1; y = 2; }.x` + + Attribute selection (evaluates to `1`) +
+ `{ x = 1; y = 2; }.z or 3` + + Attribute selection with default (evaluates to `3`) +
+ `{ x = 1; y = 2; } // { z = 3; }` + + Merge two sets (attributes in the right-hand set taking precedence) +
+ *Control structures* + + +
+ `if 1 + 1 == 2 then "yes!" else "no!"` + + Conditional expression +
+ `assert 1 + 1 == 2; "yes!"` + + Assertion check (evaluates to `"yes!"`). See [](#sec-assertions) for using assertions in modules +
+ `let x = "foo"; y = "bar"; in x + y` + + Variable definition +
+ `with pkgs.lib; head [ 1 2 3 ]` + + Add all attributes from the given set to the scope (evaluates to `1`) +
+ *Functions (lambdas)* + + +
+ `x: x + 1` + + A function that expects an integer and returns it increased by 1 +
+ `(x: x + 1) 100` + + A function call (evaluates to 101) +
+ `let inc = x: x + 1; in inc (inc (inc 100))` + + A function bound to a variable and subsequently called by name (evaluates to 103) +
+ `{ x, y }: x + y` + + A function that expects a set with required attributes `x` and `y` and concatenates them +
+ `{ x, y ? "bar" }: x + y` + + A function that expects a set with required attribute `x` and optional `y`, using `"bar"` as default value for `y` +
+ `{ x, y, ... }: x + y` + + A function that expects a set with required attributes `x` and `y` and ignores any other attributes +
+ `{ x, y } @ args: x + y` + + A function that expects a set with required attributes `x` and `y`, and binds the whole set to `args` +
+ *Built-in functions* + + +
+ `import ./foo.nix` + + Load and return Nix expression in given file +
+ `map (x: x + x) [ 1 2 3 ]` + + Apply a function to every element of a list (evaluates to `[ 2 4 6 ]`) +
From 90836397d36080b097740fee587839e458763cf9 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:09:45 +0200 Subject: [PATCH 021/245] remove stale section link --- doc/manual/src/language/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 6c355d923..f22d66351 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -209,7 +209,7 @@ the rest. `assert 1 + 1 == 2; "yes!"` - Assertion check (evaluates to `"yes!"`). See [](#sec-assertions) for using assertions in modules + Assertion check (evaluates to `"yes!"`). From 43188d3d1818214fba6037af7ea3e87df5dfbdce Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:10:15 +0200 Subject: [PATCH 022/245] make hash and version distinguishable as placeholder --- doc/manual/src/language/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index f22d66351..41d20d835 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -49,7 +49,7 @@ the rest. `"${pkgs.bash}/bin/sh"` - A string containing an expression (expands to `"/nix/store/hash-bash-version/bin/sh"`) + A string containing an expression (expands to `"/nix/store/-bash-/bin/sh"`) From f165a8ae0874529f979a2bf33283300bb1ccdef4 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:10:48 +0200 Subject: [PATCH 023/245] flarify relative path semantics --- doc/manual/src/language/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 41d20d835..f601d1e16 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -73,7 +73,7 @@ the rest. `./foo.png` - A path (relative to the containing Nix expression) + A path relative to the file containing this Nix expression From 292cab039d7c2d1dc2e67f74a92dbf4953430935 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:16:13 +0200 Subject: [PATCH 024/245] add multi-line string --- doc/manual/src/language/index.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index f601d1e16..8241cd0cd 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -43,6 +43,18 @@ the rest. A string + + ``` + '' + multi + line + string + '' + ``` + + + A multiline string. Strips common prefixed whitespace. Evaluates to `"multi\n line\n string"`. + From e6f7c180de3553dbb4ebd3969f5da4423dafc4bd Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:18:59 +0200 Subject: [PATCH 025/245] add floating point number --- doc/manual/src/language/index.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 8241cd0cd..e623bc540 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -53,7 +53,7 @@ the rest. ``` - A multiline string. Strips common prefixed whitespace. Evaluates to `"multi\n line\n string"`. + A multi-line string. Strips common prefixed whitespace. Evaluates to `"multi\n line\n string"`. @@ -80,6 +80,14 @@ the rest. An integer + + + `3.141` + + + A floating point number + + `./foo.png` From 21438acc70575134e6d17e280f5172dc2bec971f Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:20:32 +0200 Subject: [PATCH 026/245] add absolute path --- doc/manual/src/language/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index e623bc540..b4ffe171b 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -88,6 +88,14 @@ the rest. A floating point number + + + `/etc` + + + An absolute path + + `./foo.png` From 587ae9ada5e1a94184772542352df85549f0ce99 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:22:41 +0200 Subject: [PATCH 027/245] add search path --- doc/manual/src/language/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index b4ffe171b..3ed00d8e3 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -104,6 +104,14 @@ the rest. A path relative to the file containing this Nix expression + + + + + + Search path. Value determined by [`$NIX_PATH` environment variable](../command-ref/env-common.md#env-NIX_PATH). + + *Compound values* From 391fd10b12b00a3f61b178811fdecda7308e5193 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:25:40 +0200 Subject: [PATCH 028/245] add home path --- doc/manual/src/language/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 3ed00d8e3..5ad235819 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -104,6 +104,14 @@ the rest. A path relative to the file containing this Nix expression + + + `~/.config` + + + A home path. Evaluates to the `"/.config"`. + + From 5c25bdee509af297caddd7b6fdca275940282269 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 13:26:11 +0200 Subject: [PATCH 029/245] add null --- doc/manual/src/language/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 5ad235819..bc20f4316 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -72,6 +72,14 @@ the rest. Booleans + + + `null` + + + Null value + + `123` From 2e4704ca9372c90826022d52360b6d57b564de5b Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 14:13:03 +0200 Subject: [PATCH 030/245] add second @ pattern example --- doc/manual/src/language/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index bc20f4316..f201bbded 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -339,6 +339,8 @@ the rest. `{ x, y } @ args: x + y` + + `args @ { x, y }: x + y` A function that expects a set with required attributes `x` and `y`, and binds the whole set to `args` From 0378531bf2cdb19ab4601c6dbc04c3f2cd962caa Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 14:13:15 +0200 Subject: [PATCH 031/245] add curried function --- doc/manual/src/language/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index f201bbded..72e09b25d 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -296,6 +296,14 @@ the rest. A function that expects an integer and returns it increased by 1 + + + `x: y: x + y` + + + Curried function, equivalent to `x: (y: x + y)`. Can be used like a function that takes two arguments and returns their sum. + + `(x: x + 1) 100` From c209e6e108c95ba657992b208ce12af665eed9d4 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 14:13:36 +0200 Subject: [PATCH 032/245] add more list examples --- doc/manual/src/language/index.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 72e09b25d..65463baa0 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -162,10 +162,14 @@ the rest. - `[ "foo" "bar" ]` + `[ "foo" "bar" "baz" ]` + + `[ 1 2 3 ]` + + `[ (f 1) { a = 1; b = 2; } [ "c" ] ]` - A list with two elements + Lists with three elements. From 6ba8d6dc82f2b44734f5760225435642ae842725 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 12 Aug 2022 14:27:03 +0200 Subject: [PATCH 033/245] add more examples on string interpolation --- doc/manual/src/language/index.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 65463baa0..724484460 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -38,7 +38,7 @@ the rest. - `"Hello world"` + `"hello world"` A string @@ -58,10 +58,14 @@ the rest. + `"hello ${ { a = "world" }.a }"` + + `"1 2 ${3}"` + `"${pkgs.bash}/bin/sh"` - A string containing an expression (expands to `"/nix/store/-bash-/bin/sh"`) + String interpolation (expands to `"hello world"`, `"1 2 3"`, `"/nix/store/-bash-/bin/sh"`) From bc315326fa70876444cc7bc791a02e619f46304e Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 15 Aug 2022 11:12:41 +0200 Subject: [PATCH 034/245] fix whitespace to please markdown keep some indentation to ease source readability --- doc/manual/src/language/index.md | 886 ++++++++++++++++++------------- 1 file changed, 531 insertions(+), 355 deletions(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index 724484460..cc71c3143 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -20,370 +20,546 @@ manual](https://nixos.org/nix/manual/#chap-writing-nix-expressions) for the rest. - - - - - - - + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
- Example - - Description -
- *Basic values* - +
+ Example + + Description +
-
- `"hello world"` - - A string - - ``` - '' - multi - line - string - '' - ``` - - A multi-line string. Strips common prefixed whitespace. Evaluates to `"multi\n line\n string"`. -
- `"hello ${ { a = "world" }.a }"` - `"1 2 ${3}"` + *Basic values* - `"${pkgs.bash}/bin/sh"` - - String interpolation (expands to `"hello world"`, `"1 2 3"`, `"/nix/store/-bash-/bin/sh"`) -
- `true`, `false` - - Booleans -
- `null` - - Null value -
- `123` - - An integer -
- `3.141` - - A floating point number -
- `/etc` - - An absolute path -
- `./foo.png` - - A path relative to the file containing this Nix expression -
- `~/.config` - - A home path. Evaluates to the `"/.config"`. -
- - - Search path. Value determined by [`$NIX_PATH` environment variable](../command-ref/env-common.md#env-NIX_PATH). -
- *Compound values* - -
- `{ x = 1; y = 2; }` - - A set with attributes named `x` and `y` -
- `{ foo.bar = 1; }` - - A nested set, equivalent to `{ foo = { bar = 1; }; }` -
- `rec { x = "foo"; y = x + "bar"; }` - - A recursive set, equivalent to `{ x = "foo"; y = "foobar"; }` -
- `[ "foo" "bar" "baz" ]` + - `[ 1 2 3 ]` - `[ (f 1) { a = 1; b = 2; } [ "c" ] ]` - - Lists with three elements. -
- *Operators* - -
- `"foo" + "bar"` - - String concatenation -
- `1 + 2` - - Integer addition -
- `"foo" == "f" + "oo"` - - Equality test (evaluates to `true`) -
- `"foo" != "bar"` - - Inequality test (evaluates to `true`) -
- `!true` - - Boolean negation -
- `{ x = 1; y = 2; }.x` - - Attribute selection (evaluates to `1`) -
- `{ x = 1; y = 2; }.z or 3` - - Attribute selection with default (evaluates to `3`) -
- `{ x = 1; y = 2; } // { z = 3; }` - - Merge two sets (attributes in the right-hand set taking precedence) -
- *Control structures* - +
-
- `if 1 + 1 == 2 then "yes!" else "no!"` - - Conditional expression -
- `assert 1 + 1 == 2; "yes!"` - - Assertion check (evaluates to `"yes!"`). -
- `let x = "foo"; y = "bar"; in x + y` - - Variable definition -
- `with pkgs.lib; head [ 1 2 3 ]` - - Add all attributes from the given set to the scope (evaluates to `1`) -
- *Functions (lambdas)* - + `"hello world"` -
- `x: x + 1` - - A function that expects an integer and returns it increased by 1 -
- `x: y: x + y` - - Curried function, equivalent to `x: (y: x + y)`. Can be used like a function that takes two arguments and returns their sum. -
- `(x: x + 1) 100` - - A function call (evaluates to 101) -
- `let inc = x: x + 1; in inc (inc (inc 100))` - - A function bound to a variable and subsequently called by name (evaluates to 103) -
- `{ x, y }: x + y` - - A function that expects a set with required attributes `x` and `y` and concatenates them -
- `{ x, y ? "bar" }: x + y` - - A function that expects a set with required attribute `x` and optional `y`, using `"bar"` as default value for `y` -
- `{ x, y, ... }: x + y` - - A function that expects a set with required attributes `x` and `y` and ignores any other attributes -
- `{ x, y } @ args: x + y` + - `args @ { x, y }: x + y` - - A function that expects a set with required attributes `x` and `y`, and binds the whole set to `args` -
- *Built-in functions* - + A string -
- `import ./foo.nix` - - Load and return Nix expression in given file -
- `map (x: x + x) [ 1 2 3 ]` - - Apply a function to every element of a list (evaluates to `[ 2 4 6 ]`) -
+ + ``` + '' + multi + line + string + '' + ``` + + + + A multi-line string. Strips common prefixed whitespace. Evaluates to `"multi\n line\n string"`. + +
+ + `"hello ${ { a = "world" }.a }"` + + `"1 2 ${3}"` + + `"${pkgs.bash}/bin/sh"` + + + + String interpolation (expands to `"hello world"`, `"1 2 3"`, `"/nix/store/-bash-/bin/sh"`) + +
+ + `true`, `false` + + + + Booleans + +
+ + `null` + + + + Null value + +
+ + `123` + + + + An integer + +
+ + `3.141` + + + + A floating point number + +
+ + `/etc` + + + + An absolute path + +
+ + `./foo.png` + + + + A path relative to the file containing this Nix expression + +
+ + `~/.config` + + + + A home path. Evaluates to the `"/.config"`. + +
+ + + + + + Search path. Value determined by [`$NIX_PATH` environment variable](../command-ref/env-common.md#env-NIX_PATH). + +
+ + *Compound values* + + + + + +
+ + `{ x = 1; y = 2; }` + + + + A set with attributes named `x` and `y` + +
+ + `{ foo.bar = 1; }` + + + + A nested set, equivalent to `{ foo = { bar = 1; }; }` + +
+ + `rec { x = "foo"; y = x + "bar"; }` + + + + A recursive set, equivalent to `{ x = "foo"; y = "foobar"; }` + +
+ + `[ "foo" "bar" "baz" ]` + + `[ 1 2 3 ]` + + `[ (f 1) { a = 1; b = 2; } [ "c" ] ]` + + + + Lists with three elements. + +
+ + *Operators* + + + + + +
+ + `"foo" + "bar"` + + + + String concatenation + +
+ + `1 + 2` + + + + Integer addition + +
+ + `"foo" == "f" + "oo"` + + + + Equality test (evaluates to `true`) + +
+ + `"foo" != "bar"` + + + + Inequality test (evaluates to `true`) + +
+ + `!true` + + + + Boolean negation + +
+ + `{ x = 1; y = 2; }.x` + + + + Attribute selection (evaluates to `1`) + +
+ + `{ x = 1; y = 2; }.z or 3` + + + + Attribute selection with default (evaluates to `3`) + +
+ + `{ x = 1; y = 2; } // { z = 3; }` + + + + Merge two sets (attributes in the right-hand set taking precedence) + +
+ + *Control structures* + + + + + +
+ + `if 1 + 1 == 2 then "yes!" else "no!"` + + + + Conditional expression + +
+ + `assert 1 + 1 == 2; "yes!"` + + + + Assertion check (evaluates to `"yes!"`). + +
+ + `let x = "foo"; y = "bar"; in x + y` + + + + Variable definition + +
+ + `with pkgs.lib; head [ 1 2 3 ]` + + + + Add all attributes from the given set to the scope (evaluates to `1`) + +
+ + *Functions (lambdas)* + + + + + +
+ + `x: x + 1` + + + + A function that expects an integer and returns it increased by 1 + +
+ + `x: y: x + y` + + + + Curried function, equivalent to `x: (y: x + y)`. Can be used like a function that takes two arguments and returns their sum. + +
+ + `(x: x + 1) 100` + + + + A function call (evaluates to 101) + +
+ + `let inc = x: x + 1; in inc (inc (inc 100))` + + + + A function bound to a variable and subsequently called by name (evaluates to 103) + +
+ + `{ x, y }: x + y` + + + + A function that expects a set with required attributes `x` and `y` and concatenates them + +
+ + `{ x, y ? "bar" }: x + y` + + + + A function that expects a set with required attribute `x` and optional `y`, using `"bar"` as default value for `y` + +
+ + `{ x, y, ... }: x + y` + + + + A function that expects a set with required attributes `x` and `y` and ignores any other attributes + +
+ + `{ x, y } @ args: x + y` + + `args @ { x, y }: x + y` + + + + A function that expects a set with required attributes `x` and `y`, and binds the whole set to `args` + +
+ + *Built-in functions* + + + + + +
+ + `import ./foo.nix` + + + + Load and return Nix expression in given file + +
+ + `map (x: x + x) [ 1 2 3 ]` + + + + Apply a function to every element of a list (evaluates to `[ 2 4 6 ]`) + +
From 71e9c2869429bb711bf728f4a7acd14f5c2e1eeb Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 15 Aug 2022 11:15:03 +0200 Subject: [PATCH 035/245] reword introduction to overview --- doc/manual/src/language/index.md | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index cc71c3143..cea17bedc 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -11,13 +11,9 @@ packages, compositions of packages, and the variability within packages. This section presents the various features of the language. -# Syntax Summary +# Overview -Below is a summary of the most important syntactic constructs in the Nix -expression language. It's not complete. In particular, there are many -other built-in functions. See the [Nix -manual](https://nixos.org/nix/manual/#chap-writing-nix-expressions) for -the rest. +This is an incomplete overview of language features, by example. From f35b3aa47bc6edc2a929e1803c8429113c34012b Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 24 Aug 2022 08:48:34 +0200 Subject: [PATCH 036/245] do not use unwarranted pkgs in example --- doc/manual/src/language/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index cea17bedc..83484dced 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -403,7 +403,7 @@ This is an incomplete overview of language features, by example.
- `with pkgs.lib; head [ 1 2 3 ]` + `with builtins; head [ 1 2 3 ]` From 59be1e500a24c9e7970fabcc97bad5280300061c Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 5 Sep 2022 22:37:22 +0200 Subject: [PATCH 037/245] generalize anchor redirects renaming section headers and changing manually set `id`s will break URLs in the wild. this change allows keeping track of all changes to ensure backwards compatibility. --- doc/manual/redirects.js | 735 ++++++++++++++++++++++------------------ 1 file changed, 409 insertions(+), 326 deletions(-) diff --git a/doc/manual/redirects.js b/doc/manual/redirects.js index 167e221b8..45fbfffab 100644 --- a/doc/manual/redirects.js +++ b/doc/manual/redirects.js @@ -1,330 +1,413 @@ -// Redirects from old DocBook manual. +// Redirect rules for anchors ensure backwards compatibility of URLs. +// This must be done on the client side, as web servers do not see the anchor part of the URL. + +// Redirections are declared as follows: +// Each entry has as key the matched URL path relative to the mdBook document root. +// Each entry is a set of key-value pairs, where +// - keys are anchors on to the matched path. +// - values are redirection targets relative to the current path. + var redirects = { - "#part-advanced-topics": "advanced-topics/advanced-topics.html", - "#chap-tuning-cores-and-jobs": "advanced-topics/cores-vs-jobs.html", - "#chap-diff-hook": "advanced-topics/diff-hook.html", - "#check-dirs-are-unregistered": "advanced-topics/diff-hook.html#check-dirs-are-unregistered", - "#chap-distributed-builds": "advanced-topics/distributed-builds.html", - "#chap-post-build-hook": "advanced-topics/post-build-hook.html", - "#chap-post-build-hook-caveats": "advanced-topics/post-build-hook.html#implementation-caveats", - "#part-command-ref": "command-ref/command-ref.html", - "#conf-allow-import-from-derivation": "command-ref/conf-file.html#conf-allow-import-from-derivation", - "#conf-allow-new-privileges": "command-ref/conf-file.html#conf-allow-new-privileges", - "#conf-allowed-uris": "command-ref/conf-file.html#conf-allowed-uris", - "#conf-allowed-users": "command-ref/conf-file.html#conf-allowed-users", - "#conf-auto-optimise-store": "command-ref/conf-file.html#conf-auto-optimise-store", - "#conf-binary-cache-public-keys": "command-ref/conf-file.html#conf-binary-cache-public-keys", - "#conf-binary-caches": "command-ref/conf-file.html#conf-binary-caches", - "#conf-build-compress-log": "command-ref/conf-file.html#conf-build-compress-log", - "#conf-build-cores": "command-ref/conf-file.html#conf-build-cores", - "#conf-build-extra-chroot-dirs": "command-ref/conf-file.html#conf-build-extra-chroot-dirs", - "#conf-build-extra-sandbox-paths": "command-ref/conf-file.html#conf-build-extra-sandbox-paths", - "#conf-build-fallback": "command-ref/conf-file.html#conf-build-fallback", - "#conf-build-max-jobs": "command-ref/conf-file.html#conf-build-max-jobs", - "#conf-build-max-log-size": "command-ref/conf-file.html#conf-build-max-log-size", - "#conf-build-max-silent-time": "command-ref/conf-file.html#conf-build-max-silent-time", - "#conf-build-repeat": "command-ref/conf-file.html#conf-build-repeat", - "#conf-build-timeout": "command-ref/conf-file.html#conf-build-timeout", - "#conf-build-use-chroot": "command-ref/conf-file.html#conf-build-use-chroot", - "#conf-build-use-sandbox": "command-ref/conf-file.html#conf-build-use-sandbox", - "#conf-build-use-substitutes": "command-ref/conf-file.html#conf-build-use-substitutes", - "#conf-build-users-group": "command-ref/conf-file.html#conf-build-users-group", - "#conf-builders": "command-ref/conf-file.html#conf-builders", - "#conf-builders-use-substitutes": "command-ref/conf-file.html#conf-builders-use-substitutes", - "#conf-compress-build-log": "command-ref/conf-file.html#conf-compress-build-log", - "#conf-connect-timeout": "command-ref/conf-file.html#conf-connect-timeout", - "#conf-cores": "command-ref/conf-file.html#conf-cores", - "#conf-diff-hook": "command-ref/conf-file.html#conf-diff-hook", - "#conf-enforce-determinism": "command-ref/conf-file.html#conf-enforce-determinism", - "#conf-env-keep-derivations": "command-ref/conf-file.html#conf-env-keep-derivations", - "#conf-extra-binary-caches": "command-ref/conf-file.html#conf-extra-binary-caches", - "#conf-extra-platforms": "command-ref/conf-file.html#conf-extra-platforms", - "#conf-extra-sandbox-paths": "command-ref/conf-file.html#conf-extra-sandbox-paths", - "#conf-extra-substituters": "command-ref/conf-file.html#conf-extra-substituters", - "#conf-fallback": "command-ref/conf-file.html#conf-fallback", - "#conf-fsync-metadata": "command-ref/conf-file.html#conf-fsync-metadata", - "#conf-gc-keep-derivations": "command-ref/conf-file.html#conf-gc-keep-derivations", - "#conf-gc-keep-outputs": "command-ref/conf-file.html#conf-gc-keep-outputs", - "#conf-hashed-mirrors": "command-ref/conf-file.html#conf-hashed-mirrors", - "#conf-http-connections": "command-ref/conf-file.html#conf-http-connections", - "#conf-keep-build-log": "command-ref/conf-file.html#conf-keep-build-log", - "#conf-keep-derivations": "command-ref/conf-file.html#conf-keep-derivations", - "#conf-keep-env-derivations": "command-ref/conf-file.html#conf-keep-env-derivations", - "#conf-keep-outputs": "command-ref/conf-file.html#conf-keep-outputs", - "#conf-max-build-log-size": "command-ref/conf-file.html#conf-max-build-log-size", - "#conf-max-free": "command-ref/conf-file.html#conf-max-free", - "#conf-max-jobs": "command-ref/conf-file.html#conf-max-jobs", - "#conf-max-silent-time": "command-ref/conf-file.html#conf-max-silent-time", - "#conf-min-free": "command-ref/conf-file.html#conf-min-free", - "#conf-narinfo-cache-negative-ttl": "command-ref/conf-file.html#conf-narinfo-cache-negative-ttl", - "#conf-narinfo-cache-positive-ttl": "command-ref/conf-file.html#conf-narinfo-cache-positive-ttl", - "#conf-netrc-file": "command-ref/conf-file.html#conf-netrc-file", - "#conf-plugin-files": "command-ref/conf-file.html#conf-plugin-files", - "#conf-post-build-hook": "command-ref/conf-file.html#conf-post-build-hook", - "#conf-pre-build-hook": "command-ref/conf-file.html#conf-pre-build-hook", - "#conf-repeat": "command-ref/conf-file.html#conf-repeat", - "#conf-require-sigs": "command-ref/conf-file.html#conf-require-sigs", - "#conf-restrict-eval": "command-ref/conf-file.html#conf-restrict-eval", - "#conf-run-diff-hook": "command-ref/conf-file.html#conf-run-diff-hook", - "#conf-sandbox": "command-ref/conf-file.html#conf-sandbox", - "#conf-sandbox-dev-shm-size": "command-ref/conf-file.html#conf-sandbox-dev-shm-size", - "#conf-sandbox-paths": "command-ref/conf-file.html#conf-sandbox-paths", - "#conf-secret-key-files": "command-ref/conf-file.html#conf-secret-key-files", - "#conf-show-trace": "command-ref/conf-file.html#conf-show-trace", - "#conf-stalled-download-timeout": "command-ref/conf-file.html#conf-stalled-download-timeout", - "#conf-substitute": "command-ref/conf-file.html#conf-substitute", - "#conf-substituters": "command-ref/conf-file.html#conf-substituters", - "#conf-system": "command-ref/conf-file.html#conf-system", - "#conf-system-features": "command-ref/conf-file.html#conf-system-features", - "#conf-tarball-ttl": "command-ref/conf-file.html#conf-tarball-ttl", - "#conf-timeout": "command-ref/conf-file.html#conf-timeout", - "#conf-trace-function-calls": "command-ref/conf-file.html#conf-trace-function-calls", - "#conf-trusted-binary-caches": "command-ref/conf-file.html#conf-trusted-binary-caches", - "#conf-trusted-public-keys": "command-ref/conf-file.html#conf-trusted-public-keys", - "#conf-trusted-substituters": "command-ref/conf-file.html#conf-trusted-substituters", - "#conf-trusted-users": "command-ref/conf-file.html#conf-trusted-users", - "#extra-sandbox-paths": "command-ref/conf-file.html#extra-sandbox-paths", - "#sec-conf-file": "command-ref/conf-file.html", - "#env-NIX_PATH": "command-ref/env-common.html#env-NIX_PATH", - "#env-common": "command-ref/env-common.html", - "#envar-remote": "command-ref/env-common.html#env-NIX_REMOTE", - "#sec-common-env": "command-ref/env-common.html", - "#ch-files": "command-ref/files.html", - "#ch-main-commands": "command-ref/main-commands.html", - "#opt-out-link": "command-ref/nix-build.html#opt-out-link", - "#sec-nix-build": "command-ref/nix-build.html", - "#sec-nix-channel": "command-ref/nix-channel.html", - "#sec-nix-collect-garbage": "command-ref/nix-collect-garbage.html", - "#sec-nix-copy-closure": "command-ref/nix-copy-closure.html", - "#sec-nix-daemon": "command-ref/nix-daemon.html", - "#refsec-nix-env-install-examples": "command-ref/nix-env.html#examples", - "#rsec-nix-env-install": "command-ref/nix-env.html#operation---install", - "#rsec-nix-env-set": "command-ref/nix-env.html#operation---set", - "#rsec-nix-env-set-flag": "command-ref/nix-env.html#operation---set-flag", - "#rsec-nix-env-upgrade": "command-ref/nix-env.html#operation---upgrade", - "#sec-nix-env": "command-ref/nix-env.html", - "#ssec-version-comparisons": "command-ref/nix-env.html#versions", - "#sec-nix-hash": "command-ref/nix-hash.html", - "#sec-nix-instantiate": "command-ref/nix-instantiate.html", - "#sec-nix-prefetch-url": "command-ref/nix-prefetch-url.html", - "#sec-nix-shell": "command-ref/nix-shell.html", - "#ssec-nix-shell-shebang": "command-ref/nix-shell.html#use-as-a--interpreter", - "#nixref-queries": "command-ref/nix-store.html#queries", - "#opt-add-root": "command-ref/nix-store.html#opt-add-root", - "#refsec-nix-store-dump": "command-ref/nix-store.html#operation---dump", - "#refsec-nix-store-export": "command-ref/nix-store.html#operation---export", - "#refsec-nix-store-import": "command-ref/nix-store.html#operation---import", - "#refsec-nix-store-query": "command-ref/nix-store.html#operation---query", - "#refsec-nix-store-verify": "command-ref/nix-store.html#operation---verify", - "#rsec-nix-store-gc": "command-ref/nix-store.html#operation---gc", - "#rsec-nix-store-generate-binary-cache-key": "command-ref/nix-store.html#operation---generate-binary-cache-key", - "#rsec-nix-store-realise": "command-ref/nix-store.html#operation---realise", - "#rsec-nix-store-serve": "command-ref/nix-store.html#operation---serve", - "#sec-nix-store": "command-ref/nix-store.html", - "#opt-I": "command-ref/opt-common.html#opt-I", - "#opt-attr": "command-ref/opt-common.html#opt-attr", - "#opt-common": "command-ref/opt-common.html", - "#opt-cores": "command-ref/opt-common.html#opt-cores", - "#opt-log-format": "command-ref/opt-common.html#opt-log-format", - "#opt-max-jobs": "command-ref/opt-common.html#opt-max-jobs", - "#opt-max-silent-time": "command-ref/opt-common.html#opt-max-silent-time", - "#opt-timeout": "command-ref/opt-common.html#opt-timeout", - "#sec-common-options": "command-ref/opt-common.html", - "#ch-utilities": "command-ref/utilities.html", - "#chap-hacking": "contributing/hacking.html", - "#adv-attr-allowSubstitutes": "language/advanced-attributes.html#adv-attr-allowSubstitutes", - "#adv-attr-allowedReferences": "language/advanced-attributes.html#adv-attr-allowedReferences", - "#adv-attr-allowedRequisites": "language/advanced-attributes.html#adv-attr-allowedRequisites", - "#adv-attr-disallowedReferences": "language/advanced-attributes.html#adv-attr-disallowedReferences", - "#adv-attr-disallowedRequisites": "language/advanced-attributes.html#adv-attr-disallowedRequisites", - "#adv-attr-exportReferencesGraph": "language/advanced-attributes.html#adv-attr-exportReferencesGraph", - "#adv-attr-impureEnvVars": "language/advanced-attributes.html#adv-attr-impureEnvVars", - "#adv-attr-outputHash": "language/advanced-attributes.html#adv-attr-outputHash", - "#adv-attr-outputHashAlgo": "language/advanced-attributes.html#adv-attr-outputHashAlgo", - "#adv-attr-outputHashMode": "language/advanced-attributes.html#adv-attr-outputHashMode", - "#adv-attr-passAsFile": "language/advanced-attributes.html#adv-attr-passAsFile", - "#adv-attr-preferLocalBuild": "language/advanced-attributes.html#adv-attr-preferLocalBuild", - "#fixed-output-drvs": "language/advanced-attributes.html#adv-attr-outputHash", - "#sec-advanced-attributes": "language/advanced-attributes.html", - "#builtin-abort": "language/builtins.html#builtins-abort", - "#builtin-add": "language/builtins.html#builtins-add", - "#builtin-all": "language/builtins.html#builtins-all", - "#builtin-any": "language/builtins.html#builtins-any", - "#builtin-attrNames": "language/builtins.html#builtins-attrNames", - "#builtin-attrValues": "language/builtins.html#builtins-attrValues", - "#builtin-baseNameOf": "language/builtins.html#builtins-baseNameOf", - "#builtin-bitAnd": "language/builtins.html#builtins-bitAnd", - "#builtin-bitOr": "language/builtins.html#builtins-bitOr", - "#builtin-bitXor": "language/builtins.html#builtins-bitXor", - "#builtin-builtins": "language/builtins.html#builtins-builtins", - "#builtin-compareVersions": "language/builtins.html#builtins-compareVersions", - "#builtin-concatLists": "language/builtins.html#builtins-concatLists", - "#builtin-concatStringsSep": "language/builtins.html#builtins-concatStringsSep", - "#builtin-currentSystem": "language/builtins.html#builtins-currentSystem", - "#builtin-deepSeq": "language/builtins.html#builtins-deepSeq", - "#builtin-derivation": "language/builtins.html#builtins-derivation", - "#builtin-dirOf": "language/builtins.html#builtins-dirOf", - "#builtin-div": "language/builtins.html#builtins-div", - "#builtin-elem": "language/builtins.html#builtins-elem", - "#builtin-elemAt": "language/builtins.html#builtins-elemAt", - "#builtin-fetchGit": "language/builtins.html#builtins-fetchGit", - "#builtin-fetchTarball": "language/builtins.html#builtins-fetchTarball", - "#builtin-fetchurl": "language/builtins.html#builtins-fetchurl", - "#builtin-filterSource": "language/builtins.html#builtins-filterSource", - "#builtin-foldl-prime": "language/builtins.html#builtins-foldl-prime", - "#builtin-fromJSON": "language/builtins.html#builtins-fromJSON", - "#builtin-functionArgs": "language/builtins.html#builtins-functionArgs", - "#builtin-genList": "language/builtins.html#builtins-genList", - "#builtin-getAttr": "language/builtins.html#builtins-getAttr", - "#builtin-getEnv": "language/builtins.html#builtins-getEnv", - "#builtin-hasAttr": "language/builtins.html#builtins-hasAttr", - "#builtin-hashFile": "language/builtins.html#builtins-hashFile", - "#builtin-hashString": "language/builtins.html#builtins-hashString", - "#builtin-head": "language/builtins.html#builtins-head", - "#builtin-import": "language/builtins.html#builtins-import", - "#builtin-intersectAttrs": "language/builtins.html#builtins-intersectAttrs", - "#builtin-isAttrs": "language/builtins.html#builtins-isAttrs", - "#builtin-isBool": "language/builtins.html#builtins-isBool", - "#builtin-isFloat": "language/builtins.html#builtins-isFloat", - "#builtin-isFunction": "language/builtins.html#builtins-isFunction", - "#builtin-isInt": "language/builtins.html#builtins-isInt", - "#builtin-isList": "language/builtins.html#builtins-isList", - "#builtin-isNull": "language/builtins.html#builtins-isNull", - "#builtin-isString": "language/builtins.html#builtins-isString", - "#builtin-length": "language/builtins.html#builtins-length", - "#builtin-lessThan": "language/builtins.html#builtins-lessThan", - "#builtin-listToAttrs": "language/builtins.html#builtins-listToAttrs", - "#builtin-map": "language/builtins.html#builtins-map", - "#builtin-match": "language/builtins.html#builtins-match", - "#builtin-mul": "language/builtins.html#builtins-mul", - "#builtin-parseDrvName": "language/builtins.html#builtins-parseDrvName", - "#builtin-path": "language/builtins.html#builtins-path", - "#builtin-pathExists": "language/builtins.html#builtins-pathExists", - "#builtin-placeholder": "language/builtins.html#builtins-placeholder", - "#builtin-readDir": "language/builtins.html#builtins-readDir", - "#builtin-readFile": "language/builtins.html#builtins-readFile", - "#builtin-removeAttrs": "language/builtins.html#builtins-removeAttrs", - "#builtin-replaceStrings": "language/builtins.html#builtins-replaceStrings", - "#builtin-seq": "language/builtins.html#builtins-seq", - "#builtin-sort": "language/builtins.html#builtins-sort", - "#builtin-split": "language/builtins.html#builtins-split", - "#builtin-splitVersion": "language/builtins.html#builtins-splitVersion", - "#builtin-stringLength": "language/builtins.html#builtins-stringLength", - "#builtin-sub": "language/builtins.html#builtins-sub", - "#builtin-substring": "language/builtins.html#builtins-substring", - "#builtin-tail": "language/builtins.html#builtins-tail", - "#builtin-throw": "language/builtins.html#builtins-throw", - "#builtin-toFile": "language/builtins.html#builtins-toFile", - "#builtin-toJSON": "language/builtins.html#builtins-toJSON", - "#builtin-toPath": "language/builtins.html#builtins-toPath", - "#builtin-toString": "language/builtins.html#builtins-toString", - "#builtin-toXML": "language/builtins.html#builtins-toXML", - "#builtin-trace": "language/builtins.html#builtins-trace", - "#builtin-tryEval": "language/builtins.html#builtins-tryEval", - "#builtin-typeOf": "language/builtins.html#builtins-typeOf", - "#ssec-builtins": "language/builtins.html", - "#attr-system": "language/derivations.html#attr-system", - "#ssec-derivation": "language/derivations.html", - "#ch-expression-language": "language/index.html", - "#sec-constructs": "language/constructs.html", - "#sect-let-language": "language/constructs.html#let-language", - "#ss-functions": "language/constructs.html#functions", - "#sec-language-operators": "language/operators.html", - "#table-operators": "language/operators.html", - "#ssec-values": "language/values.html", - "#gloss-closure": "glossary.html#gloss-closure", - "#gloss-derivation": "glossary.html#gloss-derivation", - "#gloss-deriver": "glossary.html#gloss-deriver", - "#gloss-nar": "glossary.html#gloss-nar", - "#gloss-output-path": "glossary.html#gloss-output-path", - "#gloss-profile": "glossary.html#gloss-profile", - "#gloss-reachable": "glossary.html#gloss-reachable", - "#gloss-reference": "glossary.html#gloss-reference", - "#gloss-substitute": "glossary.html#gloss-substitute", - "#gloss-user-env": "glossary.html#gloss-user-env", - "#gloss-validity": "glossary.html#gloss-validity", - "#part-glossary": "glossary.html", - "#sec-building-source": "installation/building-source.html", - "#ch-env-variables": "installation/env-variables.html", - "#sec-installer-proxy-settings": "installation/env-variables.html#proxy-environment-variables", - "#sec-nix-ssl-cert-file": "installation/env-variables.html#nix_ssl_cert_file", - "#sec-nix-ssl-cert-file-with-nix-daemon-and-macos": "installation/env-variables.html#nix_ssl_cert_file-with-macos-and-the-nix-daemon", - "#chap-installation": "installation/installation.html", - "#ch-installing-binary": "installation/installing-binary.html", - "#sect-macos-installation": "installation/installing-binary.html#macos-installation", - "#sect-macos-installation-change-store-prefix": "installation/installing-binary.html#macos-installation", - "#sect-macos-installation-encrypted-volume": "installation/installing-binary.html#macos-installation", - "#sect-macos-installation-recommended-notes": "installation/installing-binary.html#macos-installation", - "#sect-macos-installation-symlink": "installation/installing-binary.html#macos-installation", - "#sect-multi-user-installation": "installation/installing-binary.html#multi-user-installation", - "#sect-nix-install-binary-tarball": "installation/installing-binary.html#installing-from-a-binary-tarball", - "#sect-nix-install-pinned-version-url": "installation/installing-binary.html#installing-a-pinned-nix-version-from-a-url", - "#sect-single-user-installation": "installation/installing-binary.html#single-user-installation", - "#ch-installing-source": "installation/installing-source.html", - "#ssec-multi-user": "installation/multi-user.html", - "#ch-nix-security": "installation/nix-security.html", - "#sec-obtaining-source": "installation/obtaining-source.html", - "#sec-prerequisites-source": "installation/prerequisites-source.html", - "#sec-single-user": "installation/single-user.html", - "#ch-supported-platforms": "installation/supported-platforms.html", - "#ch-upgrading-nix": "installation/upgrading.html", - "#ch-about-nix": "introduction.html", - "#chap-introduction": "introduction.html", - "#ch-basic-package-mgmt": "package-management/basic-package-mgmt.html", - "#ssec-binary-cache-substituter": "package-management/binary-cache-substituter.html", - "#sec-channels": "package-management/channels.html", - "#ssec-copy-closure": "package-management/copy-closure.html", - "#sec-garbage-collection": "package-management/garbage-collection.html", - "#ssec-gc-roots": "package-management/garbage-collector-roots.html", - "#chap-package-management": "package-management/package-management.html", - "#sec-profiles": "package-management/profiles.html", - "#ssec-s3-substituter": "package-management/s3-substituter.html", - "#ssec-s3-substituter-anonymous-reads": "package-management/s3-substituter.html#anonymous-reads-to-your-s3-compatible-binary-cache", - "#ssec-s3-substituter-authenticated-reads": "package-management/s3-substituter.html#authenticated-reads-to-your-s3-binary-cache", - "#ssec-s3-substituter-authenticated-writes": "package-management/s3-substituter.html#authenticated-writes-to-your-s3-compatible-binary-cache", - "#sec-sharing-packages": "package-management/sharing-packages.html", - "#ssec-ssh-substituter": "package-management/ssh-substituter.html", - "#chap-quick-start": "quick-start.html", - "#sec-relnotes": "release-notes/release-notes.html", - "#ch-relnotes-0.10.1": "release-notes/rl-0.10.1.html", - "#ch-relnotes-0.10": "release-notes/rl-0.10.html", - "#ssec-relnotes-0.11": "release-notes/rl-0.11.html", - "#ssec-relnotes-0.12": "release-notes/rl-0.12.html", - "#ssec-relnotes-0.13": "release-notes/rl-0.13.html", - "#ssec-relnotes-0.14": "release-notes/rl-0.14.html", - "#ssec-relnotes-0.15": "release-notes/rl-0.15.html", - "#ssec-relnotes-0.16": "release-notes/rl-0.16.html", - "#ch-relnotes-0.5": "release-notes/rl-0.5.html", - "#ch-relnotes-0.6": "release-notes/rl-0.6.html", - "#ch-relnotes-0.7": "release-notes/rl-0.7.html", - "#ch-relnotes-0.8.1": "release-notes/rl-0.8.1.html", - "#ch-relnotes-0.8": "release-notes/rl-0.8.html", - "#ch-relnotes-0.9.1": "release-notes/rl-0.9.1.html", - "#ch-relnotes-0.9.2": "release-notes/rl-0.9.2.html", - "#ch-relnotes-0.9": "release-notes/rl-0.9.html", - "#ssec-relnotes-1.0": "release-notes/rl-1.0.html", - "#ssec-relnotes-1.1": "release-notes/rl-1.1.html", - "#ssec-relnotes-1.10": "release-notes/rl-1.10.html", - "#ssec-relnotes-1.11.10": "release-notes/rl-1.11.10.html", - "#ssec-relnotes-1.11": "release-notes/rl-1.11.html", - "#ssec-relnotes-1.2": "release-notes/rl-1.2.html", - "#ssec-relnotes-1.3": "release-notes/rl-1.3.html", - "#ssec-relnotes-1.4": "release-notes/rl-1.4.html", - "#ssec-relnotes-1.5.1": "release-notes/rl-1.5.1.html", - "#ssec-relnotes-1.5.2": "release-notes/rl-1.5.2.html", - "#ssec-relnotes-1.5": "release-notes/rl-1.5.html", - "#ssec-relnotes-1.6.1": "release-notes/rl-1.6.1.html", - "#ssec-relnotes-1.6.0": "release-notes/rl-1.6.html", - "#ssec-relnotes-1.7": "release-notes/rl-1.7.html", - "#ssec-relnotes-1.8": "release-notes/rl-1.8.html", - "#ssec-relnotes-1.9": "release-notes/rl-1.9.html", - "#ssec-relnotes-2.0": "release-notes/rl-2.0.html", - "#ssec-relnotes-2.1": "release-notes/rl-2.1.html", - "#ssec-relnotes-2.2": "release-notes/rl-2.2.html", - "#ssec-relnotes-2.3": "release-notes/rl-2.3.html" + "index.html": { + "part-advanced-topics": "advanced-topics/advanced-topics.html", + "chap-tuning-cores-and-jobs": "advanced-topics/cores-vs-jobs.html", + "chap-diff-hook": "advanced-topics/diff-hook.html", + "check-dirs-are-unregistered": "advanced-topics/diff-hook.html#check-dirs-are-unregistered", + "chap-distributed-builds": "advanced-topics/distributed-builds.html", + "chap-post-build-hook": "advanced-topics/post-build-hook.html", + "chap-post-build-hook-caveats": "advanced-topics/post-build-hook.html#implementation-caveats", + "part-command-ref": "command-ref/command-ref.html", + "conf-allow-import-from-derivation": "command-ref/conf-file.html#conf-allow-import-from-derivation", + "conf-allow-new-privileges": "command-ref/conf-file.html#conf-allow-new-privileges", + "conf-allowed-uris": "command-ref/conf-file.html#conf-allowed-uris", + "conf-allowed-users": "command-ref/conf-file.html#conf-allowed-users", + "conf-auto-optimise-store": "command-ref/conf-file.html#conf-auto-optimise-store", + "conf-binary-cache-public-keys": "command-ref/conf-file.html#conf-binary-cache-public-keys", + "conf-binary-caches": "command-ref/conf-file.html#conf-binary-caches", + "conf-build-compress-log": "command-ref/conf-file.html#conf-build-compress-log", + "conf-build-cores": "command-ref/conf-file.html#conf-build-cores", + "conf-build-extra-chroot-dirs": "command-ref/conf-file.html#conf-build-extra-chroot-dirs", + "conf-build-extra-sandbox-paths": "command-ref/conf-file.html#conf-build-extra-sandbox-paths", + "conf-build-fallback": "command-ref/conf-file.html#conf-build-fallback", + "conf-build-max-jobs": "command-ref/conf-file.html#conf-build-max-jobs", + "conf-build-max-log-size": "command-ref/conf-file.html#conf-build-max-log-size", + "conf-build-max-silent-time": "command-ref/conf-file.html#conf-build-max-silent-time", + "conf-build-repeat": "command-ref/conf-file.html#conf-build-repeat", + "conf-build-timeout": "command-ref/conf-file.html#conf-build-timeout", + "conf-build-use-chroot": "command-ref/conf-file.html#conf-build-use-chroot", + "conf-build-use-sandbox": "command-ref/conf-file.html#conf-build-use-sandbox", + "conf-build-use-substitutes": "command-ref/conf-file.html#conf-build-use-substitutes", + "conf-build-users-group": "command-ref/conf-file.html#conf-build-users-group", + "conf-builders": "command-ref/conf-file.html#conf-builders", + "conf-builders-use-substitutes": "command-ref/conf-file.html#conf-builders-use-substitutes", + "conf-compress-build-log": "command-ref/conf-file.html#conf-compress-build-log", + "conf-connect-timeout": "command-ref/conf-file.html#conf-connect-timeout", + "conf-cores": "command-ref/conf-file.html#conf-cores", + "conf-diff-hook": "command-ref/conf-file.html#conf-diff-hook", + "conf-enforce-determinism": "command-ref/conf-file.html#conf-enforce-determinism", + "conf-env-keep-derivations": "command-ref/conf-file.html#conf-env-keep-derivations", + "conf-extra-binary-caches": "command-ref/conf-file.html#conf-extra-binary-caches", + "conf-extra-platforms": "command-ref/conf-file.html#conf-extra-platforms", + "conf-extra-sandbox-paths": "command-ref/conf-file.html#conf-extra-sandbox-paths", + "conf-extra-substituters": "command-ref/conf-file.html#conf-extra-substituters", + "conf-fallback": "command-ref/conf-file.html#conf-fallback", + "conf-fsync-metadata": "command-ref/conf-file.html#conf-fsync-metadata", + "conf-gc-keep-derivations": "command-ref/conf-file.html#conf-gc-keep-derivations", + "conf-gc-keep-outputs": "command-ref/conf-file.html#conf-gc-keep-outputs", + "conf-hashed-mirrors": "command-ref/conf-file.html#conf-hashed-mirrors", + "conf-http-connections": "command-ref/conf-file.html#conf-http-connections", + "conf-keep-build-log": "command-ref/conf-file.html#conf-keep-build-log", + "conf-keep-derivations": "command-ref/conf-file.html#conf-keep-derivations", + "conf-keep-env-derivations": "command-ref/conf-file.html#conf-keep-env-derivations", + "conf-keep-outputs": "command-ref/conf-file.html#conf-keep-outputs", + "conf-max-build-log-size": "command-ref/conf-file.html#conf-max-build-log-size", + "conf-max-free": "command-ref/conf-file.html#conf-max-free", + "conf-max-jobs": "command-ref/conf-file.html#conf-max-jobs", + "conf-max-silent-time": "command-ref/conf-file.html#conf-max-silent-time", + "conf-min-free": "command-ref/conf-file.html#conf-min-free", + "conf-narinfo-cache-negative-ttl": "command-ref/conf-file.html#conf-narinfo-cache-negative-ttl", + "conf-narinfo-cache-positive-ttl": "command-ref/conf-file.html#conf-narinfo-cache-positive-ttl", + "conf-netrc-file": "command-ref/conf-file.html#conf-netrc-file", + "conf-plugin-files": "command-ref/conf-file.html#conf-plugin-files", + "conf-post-build-hook": "command-ref/conf-file.html#conf-post-build-hook", + "conf-pre-build-hook": "command-ref/conf-file.html#conf-pre-build-hook", + "conf-repeat": "command-ref/conf-file.html#conf-repeat", + "conf-require-sigs": "command-ref/conf-file.html#conf-require-sigs", + "conf-restrict-eval": "command-ref/conf-file.html#conf-restrict-eval", + "conf-run-diff-hook": "command-ref/conf-file.html#conf-run-diff-hook", + "conf-sandbox": "command-ref/conf-file.html#conf-sandbox", + "conf-sandbox-dev-shm-size": "command-ref/conf-file.html#conf-sandbox-dev-shm-size", + "conf-sandbox-paths": "command-ref/conf-file.html#conf-sandbox-paths", + "conf-secret-key-files": "command-ref/conf-file.html#conf-secret-key-files", + "conf-show-trace": "command-ref/conf-file.html#conf-show-trace", + "conf-stalled-download-timeout": "command-ref/conf-file.html#conf-stalled-download-timeout", + "conf-substitute": "command-ref/conf-file.html#conf-substitute", + "conf-substituters": "command-ref/conf-file.html#conf-substituters", + "conf-system": "command-ref/conf-file.html#conf-system", + "conf-system-features": "command-ref/conf-file.html#conf-system-features", + "conf-tarball-ttl": "command-ref/conf-file.html#conf-tarball-ttl", + "conf-timeout": "command-ref/conf-file.html#conf-timeout", + "conf-trace-function-calls": "command-ref/conf-file.html#conf-trace-function-calls", + "conf-trusted-binary-caches": "command-ref/conf-file.html#conf-trusted-binary-caches", + "conf-trusted-public-keys": "command-ref/conf-file.html#conf-trusted-public-keys", + "conf-trusted-substituters": "command-ref/conf-file.html#conf-trusted-substituters", + "conf-trusted-users": "command-ref/conf-file.html#conf-trusted-users", + "extra-sandbox-paths": "command-ref/conf-file.html#extra-sandbox-paths", + "sec-conf-file": "command-ref/conf-file.html", + "env-NIX_PATH": "command-ref/env-common.html#env-NIX_PATH", + "env-common": "command-ref/env-common.html", + "envar-remote": "command-ref/env-common.html#env-NIX_REMOTE", + "sec-common-env": "command-ref/env-common.html", + "ch-files": "command-ref/files.html", + "ch-main-commands": "command-ref/main-commands.html", + "opt-out-link": "command-ref/nix-build.html#opt-out-link", + "sec-nix-build": "command-ref/nix-build.html", + "sec-nix-channel": "command-ref/nix-channel.html", + "sec-nix-collect-garbage": "command-ref/nix-collect-garbage.html", + "sec-nix-copy-closure": "command-ref/nix-copy-closure.html", + "sec-nix-daemon": "command-ref/nix-daemon.html", + "refsec-nix-env-install-examples": "command-ref/nix-env.html#examples", + "rsec-nix-env-install": "command-ref/nix-env.html#operation---install", + "rsec-nix-env-set": "command-ref/nix-env.html#operation---set", + "rsec-nix-env-set-flag": "command-ref/nix-env.html#operation---set-flag", + "rsec-nix-env-upgrade": "command-ref/nix-env.html#operation---upgrade", + "sec-nix-env": "command-ref/nix-env.html", + "ssec-version-comparisons": "command-ref/nix-env.html#versions", + "sec-nix-hash": "command-ref/nix-hash.html", + "sec-nix-instantiate": "command-ref/nix-instantiate.html", + "sec-nix-prefetch-url": "command-ref/nix-prefetch-url.html", + "sec-nix-shell": "command-ref/nix-shell.html", + "ssec-nix-shell-shebang": "command-ref/nix-shell.html#use-as-a--interpreter", + "nixref-queries": "command-ref/nix-store.html#queries", + "opt-add-root": "command-ref/nix-store.html#opt-add-root", + "refsec-nix-store-dump": "command-ref/nix-store.html#operation---dump", + "refsec-nix-store-export": "command-ref/nix-store.html#operation---export", + "refsec-nix-store-import": "command-ref/nix-store.html#operation---import", + "refsec-nix-store-query": "command-ref/nix-store.html#operation---query", + "refsec-nix-store-verify": "command-ref/nix-store.html#operation---verify", + "rsec-nix-store-gc": "command-ref/nix-store.html#operation---gc", + "rsec-nix-store-generate-binary-cache-key": "command-ref/nix-store.html#operation---generate-binary-cache-key", + "rsec-nix-store-realise": "command-ref/nix-store.html#operation---realise", + "rsec-nix-store-serve": "command-ref/nix-store.html#operation---serve", + "sec-nix-store": "command-ref/nix-store.html", + "opt-I": "command-ref/opt-common.html#opt-I", + "opt-attr": "command-ref/opt-common.html#opt-attr", + "opt-common": "command-ref/opt-common.html", + "opt-cores": "command-ref/opt-common.html#opt-cores", + "opt-log-format": "command-ref/opt-common.html#opt-log-format", + "opt-max-jobs": "command-ref/opt-common.html#opt-max-jobs", + "opt-max-silent-time": "command-ref/opt-common.html#opt-max-silent-time", + "opt-timeout": "command-ref/opt-common.html#opt-timeout", + "sec-common-options": "command-ref/opt-common.html", + "ch-utilities": "command-ref/utilities.html", + "chap-hacking": "contributing/hacking.html", + "adv-attr-allowSubstitutes": "language/advanced-attributes.html#adv-attr-allowSubstitutes", + "adv-attr-allowedReferences": "language/advanced-attributes.html#adv-attr-allowedReferences", + "adv-attr-allowedRequisites": "language/advanced-attributes.html#adv-attr-allowedRequisites", + "adv-attr-disallowedReferences": "language/advanced-attributes.html#adv-attr-disallowedReferences", + "adv-attr-disallowedRequisites": "language/advanced-attributes.html#adv-attr-disallowedRequisites", + "adv-attr-exportReferencesGraph": "language/advanced-attributes.html#adv-attr-exportReferencesGraph", + "adv-attr-impureEnvVars": "language/advanced-attributes.html#adv-attr-impureEnvVars", + "adv-attr-outputHash": "language/advanced-attributes.html#adv-attr-outputHash", + "adv-attr-outputHashAlgo": "language/advanced-attributes.html#adv-attr-outputHashAlgo", + "adv-attr-outputHashMode": "language/advanced-attributes.html#adv-attr-outputHashMode", + "adv-attr-passAsFile": "language/advanced-attributes.html#adv-attr-passAsFile", + "adv-attr-preferLocalBuild": "language/advanced-attributes.html#adv-attr-preferLocalBuild", + "fixed-output-drvs": "language/advanced-attributes.html#adv-attr-outputHash", + "sec-advanced-attributes": "language/advanced-attributes.html", + "builtin-abort": "language/builtins.html#builtins-abort", + "builtin-add": "language/builtins.html#builtins-add", + "builtin-all": "language/builtins.html#builtins-all", + "builtin-any": "language/builtins.html#builtins-any", + "builtin-attrNames": "language/builtins.html#builtins-attrNames", + "builtin-attrValues": "language/builtins.html#builtins-attrValues", + "builtin-baseNameOf": "language/builtins.html#builtins-baseNameOf", + "builtin-bitAnd": "language/builtins.html#builtins-bitAnd", + "builtin-bitOr": "language/builtins.html#builtins-bitOr", + "builtin-bitXor": "language/builtins.html#builtins-bitXor", + "builtin-builtins": "language/builtins.html#builtins-builtins", + "builtin-compareVersions": "language/builtins.html#builtins-compareVersions", + "builtin-concatLists": "language/builtins.html#builtins-concatLists", + "builtin-concatStringsSep": "language/builtins.html#builtins-concatStringsSep", + "builtin-currentSystem": "language/builtins.html#builtins-currentSystem", + "builtin-deepSeq": "language/builtins.html#builtins-deepSeq", + "builtin-derivation": "language/builtins.html#builtins-derivation", + "builtin-dirOf": "language/builtins.html#builtins-dirOf", + "builtin-div": "language/builtins.html#builtins-div", + "builtin-elem": "language/builtins.html#builtins-elem", + "builtin-elemAt": "language/builtins.html#builtins-elemAt", + "builtin-fetchGit": "language/builtins.html#builtins-fetchGit", + "builtin-fetchTarball": "language/builtins.html#builtins-fetchTarball", + "builtin-fetchurl": "language/builtins.html#builtins-fetchurl", + "builtin-filterSource": "language/builtins.html#builtins-filterSource", + "builtin-foldl-prime": "language/builtins.html#builtins-foldl-prime", + "builtin-fromJSON": "language/builtins.html#builtins-fromJSON", + "builtin-functionArgs": "language/builtins.html#builtins-functionArgs", + "builtin-genList": "language/builtins.html#builtins-genList", + "builtin-getAttr": "language/builtins.html#builtins-getAttr", + "builtin-getEnv": "language/builtins.html#builtins-getEnv", + "builtin-hasAttr": "language/builtins.html#builtins-hasAttr", + "builtin-hashFile": "language/builtins.html#builtins-hashFile", + "builtin-hashString": "language/builtins.html#builtins-hashString", + "builtin-head": "language/builtins.html#builtins-head", + "builtin-import": "language/builtins.html#builtins-import", + "builtin-intersectAttrs": "language/builtins.html#builtins-intersectAttrs", + "builtin-isAttrs": "language/builtins.html#builtins-isAttrs", + "builtin-isBool": "language/builtins.html#builtins-isBool", + "builtin-isFloat": "language/builtins.html#builtins-isFloat", + "builtin-isFunction": "language/builtins.html#builtins-isFunction", + "builtin-isInt": "language/builtins.html#builtins-isInt", + "builtin-isList": "language/builtins.html#builtins-isList", + "builtin-isNull": "language/builtins.html#builtins-isNull", + "builtin-isString": "language/builtins.html#builtins-isString", + "builtin-length": "language/builtins.html#builtins-length", + "builtin-lessThan": "language/builtins.html#builtins-lessThan", + "builtin-listToAttrs": "language/builtins.html#builtins-listToAttrs", + "builtin-map": "language/builtins.html#builtins-map", + "builtin-match": "language/builtins.html#builtins-match", + "builtin-mul": "language/builtins.html#builtins-mul", + "builtin-parseDrvName": "language/builtins.html#builtins-parseDrvName", + "builtin-path": "language/builtins.html#builtins-path", + "builtin-pathExists": "language/builtins.html#builtins-pathExists", + "builtin-placeholder": "language/builtins.html#builtins-placeholder", + "builtin-readDir": "language/builtins.html#builtins-readDir", + "builtin-readFile": "language/builtins.html#builtins-readFile", + "builtin-removeAttrs": "language/builtins.html#builtins-removeAttrs", + "builtin-replaceStrings": "language/builtins.html#builtins-replaceStrings", + "builtin-seq": "language/builtins.html#builtins-seq", + "builtin-sort": "language/builtins.html#builtins-sort", + "builtin-split": "language/builtins.html#builtins-split", + "builtin-splitVersion": "language/builtins.html#builtins-splitVersion", + "builtin-stringLength": "language/builtins.html#builtins-stringLength", + "builtin-sub": "language/builtins.html#builtins-sub", + "builtin-substring": "language/builtins.html#builtins-substring", + "builtin-tail": "language/builtins.html#builtins-tail", + "builtin-throw": "language/builtins.html#builtins-throw", + "builtin-toFile": "language/builtins.html#builtins-toFile", + "builtin-toJSON": "language/builtins.html#builtins-toJSON", + "builtin-toPath": "language/builtins.html#builtins-toPath", + "builtin-toString": "language/builtins.html#builtins-toString", + "builtin-toXML": "language/builtins.html#builtins-toXML", + "builtin-trace": "language/builtins.html#builtins-trace", + "builtin-tryEval": "language/builtins.html#builtins-tryEval", + "builtin-typeOf": "language/builtins.html#builtins-typeOf", + "ssec-builtins": "language/builtins.html", + "attr-system": "language/derivations.html#attr-system", + "ssec-derivation": "language/derivations.html", + "ch-expression-language": "language/index.html", + "sec-constructs": "language/constructs.html", + "sect-let-language": "language/constructs.html#let-language", + "ss-functions": "language/constructs.html#functions", + "sec-language-operators": "language/operators.html", + "table-operators": "language/operators.html", + "ssec-values": "language/values.html", + "gloss-closure": "glossary.html#gloss-closure", + "gloss-derivation": "glossary.html#gloss-derivation", + "gloss-deriver": "glossary.html#gloss-deriver", + "gloss-nar": "glossary.html#gloss-nar", + "gloss-output-path": "glossary.html#gloss-output-path", + "gloss-profile": "glossary.html#gloss-profile", + "gloss-reachable": "glossary.html#gloss-reachable", + "gloss-reference": "glossary.html#gloss-reference", + "gloss-substitute": "glossary.html#gloss-substitute", + "gloss-user-env": "glossary.html#gloss-user-env", + "gloss-validity": "glossary.html#gloss-validity", + "part-glossary": "glossary.html", + "sec-building-source": "installation/building-source.html", + "ch-env-variables": "installation/env-variables.html", + "sec-installer-proxy-settings": "installation/env-variables.html#proxy-environment-variables", + "sec-nix-ssl-cert-file": "installation/env-variables.html#nix_ssl_cert_file", + "sec-nix-ssl-cert-file-with-nix-daemon-and-macos": "installation/env-variables.html#nix_ssl_cert_file-with-macos-and-the-nix-daemon", + "chap-installation": "installation/installation.html", + "ch-installing-binary": "installation/installing-binary.html", + "sect-macos-installation": "installation/installing-binary.html#macos-installation", + "sect-macos-installation-change-store-prefix": "installation/installing-binary.html#macos-installation", + "sect-macos-installation-encrypted-volume": "installation/installing-binary.html#macos-installation", + "sect-macos-installation-recommended-notes": "installation/installing-binary.html#macos-installation", + "sect-macos-installation-symlink": "installation/installing-binary.html#macos-installation", + "sect-multi-user-installation": "installation/installing-binary.html#multi-user-installation", + "sect-nix-install-binary-tarball": "installation/installing-binary.html#installing-from-a-binary-tarball", + "sect-nix-install-pinned-version-url": "installation/installing-binary.html#installing-a-pinned-nix-version-from-a-url", + "sect-single-user-installation": "installation/installing-binary.html#single-user-installation", + "ch-installing-source": "installation/installing-source.html", + "ssec-multi-user": "installation/multi-user.html", + "ch-nix-security": "installation/nix-security.html", + "sec-obtaining-source": "installation/obtaining-source.html", + "sec-prerequisites-source": "installation/prerequisites-source.html", + "sec-single-user": "installation/single-user.html", + "ch-supported-platforms": "installation/supported-platforms.html", + "ch-upgrading-nix": "installation/upgrading.html", + "ch-about-nix": "introduction.html", + "chap-introduction": "introduction.html", + "ch-basic-package-mgmt": "package-management/basic-package-mgmt.html", + "ssec-binary-cache-substituter": "package-management/binary-cache-substituter.html", + "sec-channels": "package-management/channels.html", + "ssec-copy-closure": "package-management/copy-closure.html", + "sec-garbage-collection": "package-management/garbage-collection.html", + "ssec-gc-roots": "package-management/garbage-collector-roots.html", + "chap-package-management": "package-management/package-management.html", + "sec-profiles": "package-management/profiles.html", + "ssec-s3-substituter": "package-management/s3-substituter.html", + "ssec-s3-substituter-anonymous-reads": "package-management/s3-substituter.html#anonymous-reads-to-your-s3-compatible-binary-cache", + "ssec-s3-substituter-authenticated-reads": "package-management/s3-substituter.html#authenticated-reads-to-your-s3-binary-cache", + "ssec-s3-substituter-authenticated-writes": "package-management/s3-substituter.html#authenticated-writes-to-your-s3-compatible-binary-cache", + "sec-sharing-packages": "package-management/sharing-packages.html", + "ssec-ssh-substituter": "package-management/ssh-substituter.html", + "chap-quick-start": "quick-start.html", + "sec-relnotes": "release-notes/release-notes.html", + "ch-relnotes-0.10.1": "release-notes/rl-0.10.1.html", + "ch-relnotes-0.10": "release-notes/rl-0.10.html", + "ssec-relnotes-0.11": "release-notes/rl-0.11.html", + "ssec-relnotes-0.12": "release-notes/rl-0.12.html", + "ssec-relnotes-0.13": "release-notes/rl-0.13.html", + "ssec-relnotes-0.14": "release-notes/rl-0.14.html", + "ssec-relnotes-0.15": "release-notes/rl-0.15.html", + "ssec-relnotes-0.16": "release-notes/rl-0.16.html", + "ch-relnotes-0.5": "release-notes/rl-0.5.html", + "ch-relnotes-0.6": "release-notes/rl-0.6.html", + "ch-relnotes-0.7": "release-notes/rl-0.7.html", + "ch-relnotes-0.8.1": "release-notes/rl-0.8.1.html", + "ch-relnotes-0.8": "release-notes/rl-0.8.html", + "ch-relnotes-0.9.1": "release-notes/rl-0.9.1.html", + "ch-relnotes-0.9.2": "release-notes/rl-0.9.2.html", + "ch-relnotes-0.9": "release-notes/rl-0.9.html", + "ssec-relnotes-1.0": "release-notes/rl-1.0.html", + "ssec-relnotes-1.1": "release-notes/rl-1.1.html", + "ssec-relnotes-1.10": "release-notes/rl-1.10.html", + "ssec-relnotes-1.11.10": "release-notes/rl-1.11.10.html", + "ssec-relnotes-1.11": "release-notes/rl-1.11.html", + "ssec-relnotes-1.2": "release-notes/rl-1.2.html", + "ssec-relnotes-1.3": "release-notes/rl-1.3.html", + "ssec-relnotes-1.4": "release-notes/rl-1.4.html", + "ssec-relnotes-1.5.1": "release-notes/rl-1.5.1.html", + "ssec-relnotes-1.5.2": "release-notes/rl-1.5.2.html", + "ssec-relnotes-1.5": "release-notes/rl-1.5.html", + "ssec-relnotes-1.6.1": "release-notes/rl-1.6.1.html", + "ssec-relnotes-1.6.0": "release-notes/rl-1.6.html", + "ssec-relnotes-1.7": "release-notes/rl-1.7.html", + "ssec-relnotes-1.8": "release-notes/rl-1.8.html", + "ssec-relnotes-1.9": "release-notes/rl-1.9.html", + "ssec-relnotes-2.0": "release-notes/rl-2.0.html", + "ssec-relnotes-2.1": "release-notes/rl-2.1.html", + "ssec-relnotes-2.2": "release-notes/rl-2.2.html", + "ssec-relnotes-2.3": "release-notes/rl-2.3.html" + }, + "language/values": { + "simple-values": "#primitives", + "lists": "#list", + "strings": "#string", + "lists": "#list", + "attribute-sets": "#attribute-set" + } }; -var isRoot = (document.location.pathname.endsWith('/') || document.location.pathname.endsWith('/index.html')) && path_to_root === ''; -if (isRoot && redirects[document.location.hash]) { - document.location.href = path_to_root + redirects[document.location.hash]; + +function pathsMatch(a, b, path_to_root) { + // Do paths `a` and `b` match? + // + // This is more involved than it should be: + // + // 1. Path `b` can have an have an arbitrary prefix. + // + // 2. `path_to_root` consists only of `../`s and determines the depth + // of `b` relative to the prefix: + // + // `document.location.pathname` + // |-----------------------------| + // //[[.html]][#] + // |----| + // `path_to_root` has same number of segments + // + // 3. The following paths are equivalent: + // + // foo/bar/ + // foo/bar/index.html + // foo/bar/index + // + // 4. The following paths are also equivalent: + // + // foo/bar/baz + // foo/bar/baz.html + // + // We can use `path_to_root` to discern prefix from path. + // + // The last element of the following split is always empty. + // Example: '../../'.split('/') -> [ '..', '..', '' ] + const depth = path_to_root.split('/').length - 1; + var segmentsB = b.split('/'); + // get file name of `b` + const fileB = segmentsB.pop(); // mutates `segmentsB`! + // get path of `b` without prefix and file name + const pathB = segmentsB.slice(segmentsB.length - depth).join('/'); + + var segmentsA = a.split('/'); + const fileA = segmentsA.pop(); // mutates `segmentsA`! + const pathA = segmentsA.join('/') + + function normalize(file) { + if (file === '') { return "index.html"; } + if (!file.endsWith('.html')) { return file + '.html'; } + return file; + } + + return pathA === pathB && normalize(fileA) === normalize(fileB); +} + +// The anchor starts with the hash character (`#`), +// but our redirect declarations don't, so we strip it. +// Example: document.location.hash -> '#foo' +const anchor = document.location.hash.substring(1); + +for (const [path, redirect] of Object.entries(redirects)) { + // The global variable `path_to_root` is set by `mdBook`: + // + // > This is a path containing exclusively `../`'s that points to the root of the + // > book from the current file. Since the original directory structure is + // > maintained, it is useful to prepend relative links with this `path_to_root`. + // + // Source: https://phaiax.github.io/mdBook/format/theme/index-hbs.html#data + if (pathsMatch(path, document.location.pathname, path_to_root) && redirect[anchor]) { + document.location.href = redirect[anchor]; + break; + } } From 548c904d4007bbf6d03ebe06d700af0b96e976f1 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 8 Sep 2022 11:57:49 +0200 Subject: [PATCH 038/245] optimize performance remove loops and function calls, modify arrays in place this makes the whole thing harder to read, and probably only marginally faster. --- doc/manual/redirects.js | 141 +++++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 66 deletions(-) diff --git a/doc/manual/redirects.js b/doc/manual/redirects.js index 45fbfffab..af3fc8782 100644 --- a/doc/manual/redirects.js +++ b/doc/manual/redirects.js @@ -1,9 +1,12 @@ -// Redirect rules for anchors ensure backwards compatibility of URLs. -// This must be done on the client side, as web servers do not see the anchor part of the URL. +// redirect rules for anchors ensure backwards compatibility of URLs. +// this must be done on the client side, as web servers do not see the anchor part of the URL. -// Redirections are declared as follows: -// Each entry has as key the matched URL path relative to the mdBook document root. -// Each entry is a set of key-value pairs, where +// redirections are declared as follows: +// each entry has as key the matched URL path relative to the mdBook document root. +// +// IMPORTANT: it must specify the full path with file name and suffix +// +// each entry is a set of key-value pairs, where // - keys are anchors on to the matched path. // - values are redirection targets relative to the current path. @@ -332,7 +335,7 @@ var redirects = { "ssec-relnotes-2.2": "release-notes/rl-2.2.html", "ssec-relnotes-2.3": "release-notes/rl-2.3.html" }, - "language/values": { + "language/values.html": { "simple-values": "#primitives", "lists": "#list", "strings": "#string", @@ -341,73 +344,79 @@ var redirects = { } }; +// the following code matches the current page's URL against the set of redirects. +// +// it is written to minimize the latency between page load and redirect. +// therefore we avoid function calls, copying data, and unnecessary loops. +// IMPORTANT: we use stateful array operations and their order matters! +// +// matching URLs is more involved than it should be: +// +// 1. `document.location.pathname` can have an have an arbitrary prefix. +// +// 2. `path_to_root` is set by mdBook and consists only of `../`s and +// determines the depth of `` relative to the prefix: +// +// `document.location.pathname` +// |------------------------------| +// ///[[.html]][#] +// |----| +// `path_to_root` has same number of segments +// +// source: https://phaiax.github.io/mdBook/format/theme/index-hbs.html#data +// +// 3. the following paths are equivalent: +// +// /foo/bar/ +// /foo/bar/index.html +// /foo/bar/index +// +// 4. the following paths are also equivalent: +// +// /foo/bar/baz +// /foo/bar/baz.html +// -function pathsMatch(a, b, path_to_root) { - // Do paths `a` and `b` match? - // - // This is more involved than it should be: - // - // 1. Path `b` can have an have an arbitrary prefix. - // - // 2. `path_to_root` consists only of `../`s and determines the depth - // of `b` relative to the prefix: - // - // `document.location.pathname` - // |-----------------------------| - // //[[.html]][#] - // |----| - // `path_to_root` has same number of segments - // - // 3. The following paths are equivalent: - // - // foo/bar/ - // foo/bar/index.html - // foo/bar/index - // - // 4. The following paths are also equivalent: - // - // foo/bar/baz - // foo/bar/baz.html - // - // We can use `path_to_root` to discern prefix from path. - // - // The last element of the following split is always empty. - // Example: '../../'.split('/') -> [ '..', '..', '' ] - const depth = path_to_root.split('/').length - 1; - var segmentsB = b.split('/'); - // get file name of `b` - const fileB = segmentsB.pop(); // mutates `segmentsB`! - // get path of `b` without prefix and file name - const pathB = segmentsB.slice(segmentsB.length - depth).join('/'); +var segments = document.location.pathname.split('/'); - var segmentsA = a.split('/'); - const fileA = segmentsA.pop(); // mutates `segmentsA`! - const pathA = segmentsA.join('/') +var file = segments.pop(); - function normalize(file) { - if (file === '') { return "index.html"; } - if (!file.endsWith('.html')) { return file + '.html'; } - return file; - } +// normalize file name +if (file === '') { file = "index.html"; } +else if (!file.endsWith('.html')) { file = file + '.html'; } - return pathA === pathB && normalize(fileA) === normalize(fileB); -} +segments.push(file); -// The anchor starts with the hash character (`#`), +// use `path_to_root` to discern prefix from path. +const depth = path_to_root.split('/').length; + +// remove segments containing prefix. the following works because +// 1. the original `document.location.pathname` is absolute, +// hence first element of `segments` is always empty. +// 2. last element of splitting `path_to_root` is also always empty. +// 3. last element of `segments` is the file name. +// +// visual example: +// +// '/foo/bar/baz.html'.split('/') -> [ '', 'foo', 'bar', 'baz.html' ] +// '../'.split('/') -> [ '..', '' ] +// +// the following operations will then result in +// +// path = 'bar/baz.html' +// +segments.splice(0, segments.length - depth); +const path = segments.join('/'); + +// anchor starts with the hash character (`#`), // but our redirect declarations don't, so we strip it. -// Example: document.location.hash -> '#foo' +// example: document.location.hash -> '#foo' const anchor = document.location.hash.substring(1); -for (const [path, redirect] of Object.entries(redirects)) { - // The global variable `path_to_root` is set by `mdBook`: - // - // > This is a path containing exclusively `../`'s that points to the root of the - // > book from the current file. Since the original directory structure is - // > maintained, it is useful to prepend relative links with this `path_to_root`. - // - // Source: https://phaiax.github.io/mdBook/format/theme/index-hbs.html#data - if (pathsMatch(path, document.location.pathname, path_to_root) && redirect[anchor]) { - document.location.href = redirect[anchor]; - break; +const redirect = redirects[path]; +if (redirect) { + const target = redirect[anchor]; + if (target) { + document.location.href = target; } } From 8dd5ba2f472172eb1a8a8df31715726cc53d6344 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 9 Sep 2022 09:54:24 +0200 Subject: [PATCH 039/245] more precise variable types --- doc/manual/redirects.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/redirects.js b/doc/manual/redirects.js index af3fc8782..d9b27d866 100644 --- a/doc/manual/redirects.js +++ b/doc/manual/redirects.js @@ -10,7 +10,7 @@ // - keys are anchors on to the matched path. // - values are redirection targets relative to the current path. -var redirects = { +const redirects = { "index.html": { "part-advanced-topics": "advanced-topics/advanced-topics.html", "chap-tuning-cores-and-jobs": "advanced-topics/cores-vs-jobs.html", @@ -377,9 +377,9 @@ var redirects = { // /foo/bar/baz.html // -var segments = document.location.pathname.split('/'); +let segments = document.location.pathname.split('/'); -var file = segments.pop(); +let file = segments.pop(); // normalize file name if (file === '') { file = "index.html"; } From d9abce4ad4b6888183271c0a4051981dee5fffe3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Mon, 12 Sep 2022 22:50:18 +0200 Subject: [PATCH 040/245] libfetchers: avoid api.github.com ratelimit if no github token is set If we don't have any github token, we won't be able to fetch private repos, but we are also more likely to run into API limits since we don't have a token. To mitigate this only ever use the github api if we actually have a token. --- src/libfetchers/github.cc | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index a491d82a6..2115ce2f5 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -262,17 +262,20 @@ struct GitHubInputScheme : GitArchiveInputScheme DownloadUrl getDownloadUrl(const Input & input) const override { - // FIXME: use regular /archive URLs instead? api.github.com - // might have stricter rate limits. auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); - auto url = fmt( - host == "github.com" - ? "https://api.%s/repos/%s/%s/tarball/%s" - : "https://%s/api/v3/repos/%s/%s/tarball/%s", - host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), + Headers headers = makeHeadersWithAuthTokens(host); + // If we have no auth headers then we default to the public archive + // urls so we do not run into rate limits. + const auto urlFmt = + host != "github.com" + ? "https://%s/api/v3/repos/%s/%s/tarball/%s" + : headers.empty() + ? "https://%s/%s/%s/archive/%s.tar.gz" + : "https://api.%s/repos/%s/%s/tarball/%s"; + + const auto url = fmt(urlFmt, host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), input.getRev()->to_string(Base16, false)); - Headers headers = makeHeadersWithAuthTokens(host); return DownloadUrl { url, headers }; } From 34e3fa3a4a012944ba7819659f5670a197885a2e Mon Sep 17 00:00:00 2001 From: Stel Abrego Date: Mon, 3 Oct 2022 14:37:28 -0700 Subject: [PATCH 041/245] Fix fish shell MANPATH creation Previously the MANPATH was set even if MANPATH was empty beforehand which resulted in a MANPATH of only ~/.nix-profile/share/man which omitted the default man page directory (commonly /opt/local/share/man) from man page results. --- scripts/nix-profile.fish.in | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/nix-profile.fish.in b/scripts/nix-profile.fish.in index 59d247771..8d783d7c0 100644 --- a/scripts/nix-profile.fish.in +++ b/scripts/nix-profile.fish.in @@ -28,7 +28,9 @@ if test -n "$HOME" && test -n "$USER" # Only use MANPATH if it is already set. In general `man` will just simply # pick up `.nix-profile/share/man` because is it close to `.nix-profile/bin` # which is in the $PATH. For more info, run `manpath -d`. - set --export --prepend --path MANPATH "$NIX_LINK/share/man" + if set --query MANPATH + set --export --prepend --path MANPATH "$NIX_LINK/share/man" + end fish_add_path --prepend --global "$NIX_LINK/bin" set --erase NIX_LINK From c7059c9b24b803a0ae039650de5c9103afb46c2d Mon Sep 17 00:00:00 2001 From: Andrew Brooks Date: Fri, 14 Oct 2022 17:27:13 -0500 Subject: [PATCH 042/245] Add test for issue 7146 --- tests/fetchGit.sh | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh index 166bccfc7..4ceba0293 100644 --- a/tests/fetchGit.sh +++ b/tests/fetchGit.sh @@ -24,12 +24,14 @@ touch $repo/.gitignore git -C $repo add hello .gitignore git -C $repo commit -m 'Bla1' rev1=$(git -C $repo rev-parse HEAD) +git -C $repo tag -a tag1 -m tag1 echo world > $repo/hello git -C $repo commit -m 'Bla2' -a git -C $repo worktree add $TEST_ROOT/worktree echo hello >> $TEST_ROOT/worktree/hello rev2=$(git -C $repo rev-parse HEAD) +git -C $repo tag -a tag2 -m tag2 # Fetch a worktree unset _NIX_FORCE_HTTP @@ -217,6 +219,16 @@ rev4_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$ path9=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"HEAD\"; name = \"foo\"; }).outPath") [[ $path9 =~ -foo$ ]] +# Specifying a ref without a rev shouldn't pick a cached rev for a different ref +export _NIX_FORCE_HTTP=1 +rev_tag1_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"refs/tags/tag1\"; }).rev") +rev_tag1=$(git -C $repo rev-parse refs/tags/tag1) +[[ $rev_tag1_nix = $rev_tag1 ]] +rev_tag2_nix=$(nix eval --impure --raw --expr "(builtins.fetchGit { url = \"file://$repo\"; ref = \"refs/tags/tag2\"; }).rev") +rev_tag2=$(git -C $repo rev-parse refs/tags/tag2) +[[ $rev_tag2_nix = $rev_tag2 ]] +unset _NIX_FORCE_HTTP + # should fail if there is no repo rm -rf $repo/.git (! nix eval --impure --raw --expr "(builtins.fetchGit \"file://$repo\").outPath") From a259084c50cdb692a6ba6818bb56ee381e67715b Mon Sep 17 00:00:00 2001 From: Andrew Brooks Date: Fri, 14 Oct 2022 18:04:47 -0500 Subject: [PATCH 043/245] Fix #7146 When fetching a non-local git repo by ref (and no rev), don't consider unrelated cached revs for the same repository. --- src/libfetchers/git.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index c1a21e764..7b7a1be35 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -485,6 +485,10 @@ struct GitInputScheme : InputScheme } input.attrs.insert_or_assign("ref", *head); unlockedAttrs.insert_or_assign("ref", *head); + } else { + if (!input.getRev()) { + unlockedAttrs.insert_or_assign("ref", input.getRef().value()); + } } if (auto res = getCache()->lookup(store, unlockedAttrs)) { From af9c9504ca8c2be9e9854e74a081743d882c8a8d Mon Sep 17 00:00:00 2001 From: Nathan Henrie Date: Mon, 17 Oct 2022 14:15:32 -0600 Subject: [PATCH 044/245] Fix typo -- dashes not underscores --- src/nix/repl.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix/repl.md b/src/nix/repl.md index 23ef0f4e6..c5113be61 100644 --- a/src/nix/repl.md +++ b/src/nix/repl.md @@ -36,7 +36,7 @@ R""( Loading Installable ''... Added 1 variables. - # nix repl --extra_experimental_features 'flakes repl-flake' nixpkgs + # nix repl --extra-experimental-features 'flakes repl-flake' nixpkgs Loading Installable 'flake:nixpkgs#'... Added 5 variables. From dc20395de48ed8a9255971aeacbb4e133af03808 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Oct 2022 22:01:48 +0000 Subject: [PATCH 045/245] Bump cachix/install-nix-action from 17 to 18 Bumps [cachix/install-nix-action](https://github.com/cachix/install-nix-action) from 17 to 18. - [Release notes](https://github.com/cachix/install-nix-action/releases) - [Commits](https://github.com/cachix/install-nix-action/compare/v17...v18) --- updated-dependencies: - dependency-name: cachix/install-nix-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 628d1d192..505717921 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: cachix/install-nix-action@v17 + - uses: cachix/install-nix-action@v18 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - uses: cachix/cachix-action@v10 if: needs.check_secrets.outputs.cachix == 'true' @@ -58,7 +58,7 @@ jobs: with: fetch-depth: 0 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - - uses: cachix/install-nix-action@v17 + - uses: cachix/install-nix-action@v18 - uses: cachix/cachix-action@v10 with: name: '${{ env.CACHIX_NAME }}' @@ -77,7 +77,7 @@ jobs: steps: - uses: actions/checkout@v3 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - - uses: cachix/install-nix-action@v17 + - uses: cachix/install-nix-action@v18 with: install_url: '${{needs.installer.outputs.installerURL}}' install_options: "--tarball-url-prefix https://${{ env.CACHIX_NAME }}.cachix.org/serve" @@ -102,7 +102,7 @@ jobs: - uses: actions/checkout@v3 with: fetch-depth: 0 - - uses: cachix/install-nix-action@v17 + - uses: cachix/install-nix-action@v18 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - run: echo NIX_VERSION="$(nix --experimental-features 'nix-command flakes' eval .\#default.version | tr -d \")" >> $GITHUB_ENV - uses: cachix/cachix-action@v10 From 61f89e954af060c8dbdcd5a4fffcf023ac555686 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Oct 2022 16:42:06 +0200 Subject: [PATCH 046/245] Add command 'nix store path-from-hash-part' This exposes the Store::queryPathFromHashPart() interface in the CLI. --- src/nix/path-from-hash-part.cc | 39 ++++++++++++++++++++++++++++++++++ src/nix/path-from-hash-part.md | 20 +++++++++++++++++ tests/local.mk | 3 ++- tests/path-from-hash-part.sh | 10 +++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 src/nix/path-from-hash-part.cc create mode 100644 src/nix/path-from-hash-part.md create mode 100644 tests/path-from-hash-part.sh diff --git a/src/nix/path-from-hash-part.cc b/src/nix/path-from-hash-part.cc new file mode 100644 index 000000000..7f7cda8d3 --- /dev/null +++ b/src/nix/path-from-hash-part.cc @@ -0,0 +1,39 @@ +#include "command.hh" +#include "store-api.hh" + +using namespace nix; + +struct CmdPathFromHashPart : StoreCommand +{ + std::string hashPart; + + CmdPathFromHashPart() + { + expectArgs({ + .label = "hash-part", + .handler = {&hashPart}, + }); + } + + std::string description() override + { + return "get a store path from its hash part"; + } + + std::string doc() override + { + return + #include "path-from-hash-part.md" + ; + } + + void run(ref store) override + { + if (auto storePath = store->queryPathFromHashPart(hashPart)) + logger->cout(store->printStorePath(*storePath)); + else + throw Error("there is no store path corresponding to '%s'", hashPart); + } +}; + +static auto rCmdPathFromHashPart = registerCommand2({"store", "path-from-hash-part"}); diff --git a/src/nix/path-from-hash-part.md b/src/nix/path-from-hash-part.md new file mode 100644 index 000000000..788e13ab6 --- /dev/null +++ b/src/nix/path-from-hash-part.md @@ -0,0 +1,20 @@ +R""( + +# Examples + +* Return the full store path with the given hash part: + + ```console + # nix store path-from-hash-part --store https://cache.nixos.org/ 0i2jd68mp5g6h2sa5k9c85rb80sn8hi9 + /nix/store/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9-hello-2.10 + ``` + +# Description + +Given the hash part of a store path (that is, the 32 characters +following `/nix/store/`), return the full store path. This is +primarily useful in the implementation of binary caches, where a +request for a `.narinfo` file only supplies the hash part +(e.g. `https://cache.nixos.org/0i2jd68mp5g6h2sa5k9c85rb80sn8hi9.narinfo`). + +)"" diff --git a/tests/local.mk b/tests/local.mk index 5e48ceae1..340817ec3 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -109,7 +109,8 @@ nix_tests = \ store-ping.sh \ fetchClosure.sh \ completions.sh \ - impure-derivations.sh + impure-derivations.sh \ + path-from-hash-part.sh ifeq ($(HAVE_LIBCPUID), 1) nix_tests += compute-levels.sh diff --git a/tests/path-from-hash-part.sh b/tests/path-from-hash-part.sh new file mode 100644 index 000000000..bdd104434 --- /dev/null +++ b/tests/path-from-hash-part.sh @@ -0,0 +1,10 @@ +source common.sh + +path=$(nix build --no-link --print-out-paths -f simple.nix) + +hash_part=$(basename $path) +hash_part=${hash_part:0:32} + +path2=$(nix store path-from-hash-part $hash_part) + +[[ $path = $path2 ]] From e136d57f26155a9f54dfb0ca00212b2016932104 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 18 Oct 2022 17:48:09 +0200 Subject: [PATCH 047/245] Implement BinaryCacheStore::queryPathFromHashPart() --- src/libstore/binary-cache-store.cc | 11 +++++++++++ src/libstore/binary-cache-store.hh | 3 +-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc index 9226c4e19..a26770c79 100644 --- a/src/libstore/binary-cache-store.cc +++ b/src/libstore/binary-cache-store.cc @@ -331,6 +331,17 @@ bool BinaryCacheStore::isValidPathUncached(const StorePath & storePath) return fileExists(narInfoFileFor(storePath)); } +std::optional BinaryCacheStore::queryPathFromHashPart(const std::string & hashPart) +{ + auto pseudoPath = StorePath(hashPart + "-" + MissingName); + try { + auto info = queryPathInfo(pseudoPath); + return info->path; + } catch (InvalidPath &) { + return std::nullopt; + } +} + void BinaryCacheStore::narFromPath(const StorePath & storePath, Sink & sink) { auto info = queryPathInfo(storePath).cast(); diff --git a/src/libstore/binary-cache-store.hh b/src/libstore/binary-cache-store.hh index ca538b3cb..8c82e2387 100644 --- a/src/libstore/binary-cache-store.hh +++ b/src/libstore/binary-cache-store.hh @@ -95,8 +95,7 @@ public: void queryPathInfoUncached(const StorePath & path, Callback> callback) noexcept override; - std::optional queryPathFromHashPart(const std::string & hashPart) override - { unsupported("queryPathFromHashPart"); } + std::optional queryPathFromHashPart(const std::string & hashPart) override; void addToStore(const ValidPathInfo & info, Source & narSource, RepairFlag repair, CheckSigsFlag checkSigs) override; From 11b5cf95615ffb2a2475ac444f4fe103e471748f Mon Sep 17 00:00:00 2001 From: Patrick Jackson Date: Tue, 18 Oct 2022 15:09:17 -0700 Subject: [PATCH 048/245] fix manual defaults --- src/nix/main.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/nix/main.cc b/src/nix/main.cc index d78312944..f008ea2b7 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -369,6 +369,13 @@ void mainWrapped(int argc, char * * argv) && args.command->first != "upgrade-nix") settings.requireExperimentalFeature(Xp::NixCommand); + if (args.command->first == "show-config") { + // show-config must run before any settings are modified so that it outputs the defaults + args.command->second->prepare(); + args.command->second->run(); + return; + } + if (args.useNet && !haveInternet()) { warn("you don't have Internet access; disabling some network-dependent features"); args.useNet = false; From 0b64bac65c7a476797d6238631d693f48a0eecc9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 08:50:25 +0000 Subject: [PATCH 049/245] Bump cachix/cachix-action from 10 to 11 Bumps [cachix/cachix-action](https://github.com/cachix/cachix-action) from 10 to 11. - [Release notes](https://github.com/cachix/cachix-action/releases) - [Commits](https://github.com/cachix/cachix-action/compare/v10...v11) --- updated-dependencies: - dependency-name: cachix/cachix-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 505717921..7efb90913 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: fetch-depth: 0 - uses: cachix/install-nix-action@v18 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - - uses: cachix/cachix-action@v10 + - uses: cachix/cachix-action@v11 if: needs.check_secrets.outputs.cachix == 'true' with: name: '${{ env.CACHIX_NAME }}' @@ -59,7 +59,7 @@ jobs: fetch-depth: 0 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - uses: cachix/install-nix-action@v18 - - uses: cachix/cachix-action@v10 + - uses: cachix/cachix-action@v11 with: name: '${{ env.CACHIX_NAME }}' signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' @@ -105,7 +105,7 @@ jobs: - uses: cachix/install-nix-action@v18 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - run: echo NIX_VERSION="$(nix --experimental-features 'nix-command flakes' eval .\#default.version | tr -d \")" >> $GITHUB_ENV - - uses: cachix/cachix-action@v10 + - uses: cachix/cachix-action@v11 if: needs.check_secrets.outputs.cachix == 'true' with: name: '${{ env.CACHIX_NAME }}' From 1ec8810687859e0af96d844da67ab6c6d6e69f9d Mon Sep 17 00:00:00 2001 From: Johan Herland Date: Wed, 19 Oct 2022 11:50:58 +0200 Subject: [PATCH 050/245] Explain how Nix handles antiquotation of paths Specifically, explain why Nix does not _re_evaluate paths during a `nix repl` session. This is a thing that bit me while playing around with paths and antiquotation in `nix repl` while reading the Nix language tutorial at https://nix.dev/tutorials/nix-language. Co-authored-by: Valentin Gagarin --- doc/manual/src/language/values.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/doc/manual/src/language/values.md b/doc/manual/src/language/values.md index f09400d02..6fc8c0369 100644 --- a/doc/manual/src/language/values.md +++ b/doc/manual/src/language/values.md @@ -150,6 +150,20 @@ recognized as a path. `a.${foo}/b.${bar}` is a syntactically valid division operation. `./a.${foo}/b.${bar}` is a path. + When a path appears in an antiquotation, and is thus coerced into a string, + the path is first copied into the Nix store and the resulting string is + the Nix store path. For instance `"${./foo.txt}" will cause `foo.txt` in + the current directory to be copied into the Nix store and result in the + string `"/nix/store/-foo.txt"`. + + Note that the Nix language assumes that all input files will remain + _unchanged_ during the course of the Nix expression evaluation. + If you for example antiquote a file path during a `nix repl` session, and + then later in the same session, after having changed the file contents, + evaluate the antiquotation with the file path again, then Nix will still + return the first store path. It will _not_ reread the file contents to + produce a different Nix store path. + - Boolean *Booleans* with values `true` and `false`. From c63646b693f6f226e0f3a623b0d09839b08609f5 Mon Sep 17 00:00:00 2001 From: sternenseemann Date: Wed, 19 Oct 2022 18:39:26 +0200 Subject: [PATCH 051/245] tests/lang: re-enable eval-okay-eq.nix tests This is a really old test case (which was originally written before the proper Nix syntax). The tested deep comparison behavior was implemented and reverted soon after due to performance problems, but it has been restored in today's Nix again (thanks to the derivation comparison optimization, presumably). --- tests/lang/eval-okay-eq.exp | 1 + tests/lang/eval-okay-eq.exp.disabled | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 tests/lang/eval-okay-eq.exp delete mode 100644 tests/lang/eval-okay-eq.exp.disabled diff --git a/tests/lang/eval-okay-eq.exp b/tests/lang/eval-okay-eq.exp new file mode 100644 index 000000000..27ba77dda --- /dev/null +++ b/tests/lang/eval-okay-eq.exp @@ -0,0 +1 @@ +true diff --git a/tests/lang/eval-okay-eq.exp.disabled b/tests/lang/eval-okay-eq.exp.disabled deleted file mode 100644 index 2015847b6..000000000 --- a/tests/lang/eval-okay-eq.exp.disabled +++ /dev/null @@ -1 +0,0 @@ -Bool(True) From b44df1370187bea82814a2da50a6ded54fca65f2 Mon Sep 17 00:00:00 2001 From: Patrick Jackson Date: Thu, 20 Oct 2022 12:05:04 -0700 Subject: [PATCH 052/245] manual: update options generation code to use default values --- doc/manual/generate-options.nix | 10 +++++----- src/nix/main.cc | 7 ------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/doc/manual/generate-options.nix b/doc/manual/generate-options.nix index 2d586fa1b..680b709c8 100644 --- a/doc/manual/generate-options.nix +++ b/doc/manual/generate-options.nix @@ -11,16 +11,16 @@ concatStrings (map + concatStrings (map (s: " ${s}\n") (splitLines option.description)) + "\n\n" + (if option.documentDefault then " **Default:** " + ( - if option.value == "" || option.value == [] + if option.defaultValue == "" || option.defaultValue == [] then "*empty*" - else if isBool option.value - then (if option.value then "`true`" else "`false`") + else if isBool option.defaultValue + then (if option.defaultValue then "`true`" else "`false`") else # n.b. a StringMap value type is specified as a string, but # this shows the value type. The empty stringmap is "null" in # JSON, but that converts to "{ }" here. - (if isAttrs option.value then "`\"\"`" - else "`" + toString option.value + "`")) + "\n\n" + (if isAttrs option.defaultValue then "`\"\"`" + else "`" + toString option.defaultValue + "`")) + "\n\n" else " **Default:** *machine-specific*\n") + (if option.aliases != [] then " **Deprecated alias:** " + (concatStringsSep ", " (map (s: "`${s}`") option.aliases)) + "\n\n" diff --git a/src/nix/main.cc b/src/nix/main.cc index f008ea2b7..d78312944 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -369,13 +369,6 @@ void mainWrapped(int argc, char * * argv) && args.command->first != "upgrade-nix") settings.requireExperimentalFeature(Xp::NixCommand); - if (args.command->first == "show-config") { - // show-config must run before any settings are modified so that it outputs the defaults - args.command->second->prepare(); - args.command->second->run(); - return; - } - if (args.useNet && !haveInternet()) { warn("you don't have Internet access; disabling some network-dependent features"); args.useNet = false; From 8e7804273cec1bb3fa05ce09a37f0732b71a72ec Mon Sep 17 00:00:00 2001 From: Austin Kiekintveld Date: Sat, 22 Oct 2022 19:51:22 -0500 Subject: [PATCH 053/245] Defer to SSH config files for ForwardAgent option Currently, Nix passes `-a` when it runs commands on a remote machine via SSH, which disables agent forwarding. This causes issues when the `ForwardAgent` option is set in SSH config files, as the command line operation always overrides those. In particular, this causes issues if the command being run is `sudo` and the remote machine is configured with the equivalent of NixOS's `security.pam.enableSSHAgentAuth` option. Not allowing SSH agent forwarding can cause authentication to fail unexpectedly. This can currently be worked around by setting `NIX_SSHOPTS="-A"`, but we should defer to the options in the SSH config files to be least surprising for users. --- src/libstore/ssh.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/ssh.cc b/src/libstore/ssh.cc index 1bbad71f2..69bfe3418 100644 --- a/src/libstore/ssh.cc +++ b/src/libstore/ssh.cc @@ -67,7 +67,7 @@ std::unique_ptr SSHMaster::startCommand(const std::string if (fakeSSH) { args = { "bash", "-c" }; } else { - args = { "ssh", host.c_str(), "-x", "-a" }; + args = { "ssh", host.c_str(), "-x" }; addCommonSSHOpts(args); if (socketPath != "") args.insert(args.end(), {"-S", socketPath}); From 334fa81d0828f79d832a029ba16e0c807ec15554 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Sun, 23 Oct 2022 06:54:11 -0400 Subject: [PATCH 054/245] Mark flakes with .type = "flake". Fixes #7186 --- src/libexpr/flake/call-flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix index 932ac5e90..87d7a126b 100644 --- a/src/libexpr/flake/call-flake.nix +++ b/src/libexpr/flake/call-flake.nix @@ -43,7 +43,7 @@ let outputs = flake.outputs (inputs // { self = result; }); - result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; }; + result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; type = "flake"; }; in if node.flake or true then assert builtins.isFunction flake.outputs; From a9a868fe6a456a52cf08244dbb87d67798ed3f9c Mon Sep 17 00:00:00 2001 From: Michal Sojka Date: Mon, 24 Oct 2022 08:49:46 +0200 Subject: [PATCH 055/245] Improve --profile description The description of the --profile option talks about the "update" operation. This is probably meant for operations such as "nix profile install", but the same option is reused in other subcommands, which do not update the profile, such as "nix profile {list,history,diff-closures}". We update the description to make sense in both contexts. --- src/libcmd/command.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc index 1fdd9e0bd..0740ea960 100644 --- a/src/libcmd/command.cc +++ b/src/libcmd/command.cc @@ -226,7 +226,7 @@ MixProfile::MixProfile() { addFlag({ .longName = "profile", - .description = "The profile to update.", + .description = "The profile to operate on.", .labels = {"path"}, .handler = {&profile}, .completer = completePath From da2c61637bbf5d22fc2aa3c8d24005f364d28fa2 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 25 Oct 2022 16:48:31 +0200 Subject: [PATCH 056/245] Use _type Co-authored-by: Robert Hensing --- src/libexpr/flake/call-flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/flake/call-flake.nix b/src/libexpr/flake/call-flake.nix index 87d7a126b..8061db3df 100644 --- a/src/libexpr/flake/call-flake.nix +++ b/src/libexpr/flake/call-flake.nix @@ -43,7 +43,7 @@ let outputs = flake.outputs (inputs // { self = result; }); - result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; type = "flake"; }; + result = outputs // sourceInfo // { inherit inputs; inherit outputs; inherit sourceInfo; _type = "flake"; }; in if node.flake or true then assert builtins.isFunction flake.outputs; From a71e3172afc08e4d44ad719c631f731c37bd5491 Mon Sep 17 00:00:00 2001 From: Gabriella Gonzalez Date: Tue, 25 Oct 2022 16:43:00 -0700 Subject: [PATCH 057/245] Add `--yes` option for Nix installation script --- scripts/install-multi-user.sh | 2 +- scripts/install-nix-from-closure.sh | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh index a39339050..96c0f302b 100644 --- a/scripts/install-multi-user.sh +++ b/scripts/install-multi-user.sh @@ -58,7 +58,7 @@ readonly EXTRACTED_NIX_PATH="$(dirname "$0")" readonly ROOT_HOME=~root -if [ -t 0 ]; then +if [ -t 0 ] && [ -z "${NIX_INSTALLER_YES:-}" ]; then readonly IS_HEADLESS='no' else readonly IS_HEADLESS='yes' diff --git a/scripts/install-nix-from-closure.sh b/scripts/install-nix-from-closure.sh index d4eed2efe..ccd19e1a8 100644 --- a/scripts/install-nix-from-closure.sh +++ b/scripts/install-nix-from-closure.sh @@ -71,6 +71,8 @@ while [ $# -gt 0 ]; do # # intentional tail space # ACTIONS="${ACTIONS}uninstall " # ;; + --yes) + export NIX_INSTALLER_YES=1;; --no-channel-add) export NIX_INSTALLER_NO_CHANNEL_ADD=1;; --daemon-user-count) @@ -90,7 +92,7 @@ while [ $# -gt 0 ]; do shift;; *) { - echo "Nix Installer [--daemon|--no-daemon] [--daemon-user-count INT] [--no-channel-add] [--no-modify-profile] [--nix-extra-conf-file FILE]" + echo "Nix Installer [--daemon|--no-daemon] [--daemon-user-count INT] [--yes] [--no-channel-add] [--no-modify-profile] [--nix-extra-conf-file FILE]" echo "Choose installation method." echo "" @@ -104,6 +106,8 @@ while [ $# -gt 0 ]; do echo " trivial to uninstall." echo " (default)" echo "" + echo " --yes: Run the script non-interactively, accepting all prompts." + echo "" echo " --no-channel-add: Don't add any channels. nixpkgs-unstable is installed by default." echo "" echo " --no-modify-profile: Don't modify the user profile to automatically load nix." From aff6d10934f046066ed68b0d54fa077e726008e5 Mon Sep 17 00:00:00 2001 From: Yorick van Pelt Date: Wed, 26 Oct 2022 10:05:27 +0200 Subject: [PATCH 058/245] nix run: fix "'defaultApp.x86_64-linux' should have type 'derivation'" --- src/nix/app.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nix/app.cc b/src/nix/app.cc index 821964f86..48de8fb82 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -66,7 +66,9 @@ UnresolvedApp Installable::toApp(EvalState & state) auto type = cursor->getAttr("type")->getString(); - std::string expected = !attrPath.empty() && state.symbols[attrPath[0]] == "apps" ? "app" : "derivation"; + std::string expected = !attrPath.empty() && + (state.symbols[attrPath[0]] == "apps" || state.symbols[attrPath[0]] == "defaultApp") + ? "app" : "derivation"; if (type != expected) throw Error("attribute '%s' should have type '%s'", cursor->getAttrPathStr(), expected); From 9bff7e8ee26c97441858e1cea097f44c6db61235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <7226587+thufschmitt@users.noreply.github.com> Date: Tue, 25 Oct 2022 21:17:32 +0200 Subject: [PATCH 059/245] Fix `nix __build-remote` Because of a wrong index, `nix __build-remote` wasn't working. Fix the index to restore the command (and the build hook). --- src/nix/main.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix/main.cc b/src/nix/main.cc index d78312944..f8e93e367 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -270,7 +270,7 @@ void mainWrapped(int argc, char * * argv) programPath = argv[0]; auto programName = std::string(baseNameOf(programPath)); - if (argc > 0 && std::string_view(argv[0]) == "__build-remote") { + if (argc > 1 && std::string_view(argv[1]) == "__build-remote") { programName = "build-remote"; argv++; argc--; } From f8d01933838f719b2511a9a73a5fa710cdd59496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Thu, 27 Oct 2022 11:53:04 +0200 Subject: [PATCH 060/245] Pass the right argv when calling the build hook Call it as `['nix', '__build-remote', ... ]` rather than the previous `["__build-remote", "nix __build-remote", ... ]` which seemed to have been most likely unintended --- src/libstore/build/hook-instance.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/build/hook-instance.cc b/src/libstore/build/hook-instance.cc index 1f19ddccc..cb58a1f02 100644 --- a/src/libstore/build/hook-instance.cc +++ b/src/libstore/build/hook-instance.cc @@ -16,11 +16,11 @@ HookInstance::HookInstance() buildHookArgs.pop_front(); Strings args; + args.push_back(std::string(baseNameOf(buildHook))); for (auto & arg : buildHookArgs) args.push_back(arg); - args.push_back(std::string(baseNameOf(settings.buildHook.get()))); args.push_back(std::to_string(verbosity)); /* Create a pipe to get the output of the child. */ From 48f840cff89490b2a8bf87d03345beb7113eef0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Thu, 27 Oct 2022 18:13:15 +0200 Subject: [PATCH 061/245] assign fricklerhandwerk for documentation releated PRs --- .github/assign-by-files.yml | 5 +++++ .github/workflows/assign-reviewer.yml | 12 ++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 .github/assign-by-files.yml create mode 100644 .github/workflows/assign-reviewer.yml diff --git a/.github/assign-by-files.yml b/.github/assign-by-files.yml new file mode 100644 index 000000000..f13b71776 --- /dev/null +++ b/.github/assign-by-files.yml @@ -0,0 +1,5 @@ +--- +# This files is used by https://github.com/marketplace/actions/auto-assign-reviewer-by-files +# to assign maintainers +"doc/**/*": + - fricklerhandwerk diff --git a/.github/workflows/assign-reviewer.yml b/.github/workflows/assign-reviewer.yml new file mode 100644 index 000000000..4371cbff4 --- /dev/null +++ b/.github/workflows/assign-reviewer.yml @@ -0,0 +1,12 @@ +name: "Auto Assign" +on: + - pull_request + +jobs: + assign_reviewer: + runs-on: ubuntu-latest + steps: + - uses: shufo/auto-assign-reviewer-by-files@v1.1.4 + with: + config: ".github/assign-by-files.yml" + token: ${{ secrets.GITHUB_TOKEN }} From cd86eeb693342c79b01ff38ca723088dc1e42291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= Date: Fri, 28 Oct 2022 12:19:37 +0200 Subject: [PATCH 062/245] Move savedArgv into libmain `savedArgv` is not accessible by plugins when defined in main binary. Moving it into one of the nix lib fix the problem. --- src/libmain/shared.cc | 1 + src/nix/main.cc | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index c1cf38565..a58428762 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -33,6 +33,7 @@ namespace nix { +char * * savedArgv; static bool gcWarning = true; diff --git a/src/nix/main.cc b/src/nix/main.cc index d78312944..956730276 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -53,7 +53,6 @@ static bool haveInternet() } std::string programPath; -char * * savedArgv; struct HelpRequested { }; From 9f1dd0df5b54a7dc75b618034482ed42ce34383d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sandro=20J=C3=A4ckel?= Date: Sat, 29 Oct 2022 21:51:29 +0200 Subject: [PATCH 063/245] Update test after api.github.com ratelimit avoidance --- tests/github-flakes.nix | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/tests/github-flakes.nix b/tests/github-flakes.nix index fc481c7e3..1b60a9f9a 100644 --- a/tests/github-flakes.nix +++ b/tests/github-flakes.nix @@ -7,7 +7,7 @@ with import (nixpkgs + "/nixos/lib/testing-python.nix") { let - # Generate a fake root CA and a fake api.github.com / channels.nixos.org certificate. + # Generate a fake root CA and a fake api.github.com / github.com / channels.nixos.org certificate. cert = pkgs.runCommand "cert" { buildInputs = [ pkgs.openssl ]; } '' mkdir -p $out @@ -18,7 +18,7 @@ let openssl req -newkey rsa:2048 -nodes -keyout $out/server.key \ -subj "/C=CN/ST=Denial/L=Springfield/O=Dis/CN=github.com" -out server.csr - openssl x509 -req -extfile <(printf "subjectAltName=DNS:api.github.com,DNS:channels.nixos.org") \ + openssl x509 -req -extfile <(printf "subjectAltName=DNS:api.github.com,DNS:github.com,DNS:channels.nixos.org") \ -days 36500 -in server.csr -CA $out/ca.crt -CAkey ca.key -CAcreateserial -out $out/server.crt ''; @@ -47,18 +47,20 @@ let api = pkgs.runCommand "nixpkgs-flake" {} '' - mkdir -p $out/tarball + mkdir -p $out/commits + echo '{"sha": "${nixpkgs.rev}"}' > $out/commits/HEAD + ''; + + archive = pkgs.runCommand "nixpkgs-flake" {} + '' + mkdir -p $out/archive dir=NixOS-nixpkgs-${nixpkgs.shortRev} cp -prd ${nixpkgs} $dir # Set the correct timestamp in the tarball. find $dir -print0 | xargs -0 touch -t ${builtins.substring 0 12 nixpkgs.lastModifiedDate}.${builtins.substring 12 2 nixpkgs.lastModifiedDate} -- - tar cfz $out/tarball/${nixpkgs.rev} $dir --hard-dereference - - mkdir -p $out/commits - echo '{"sha": "${nixpkgs.rev}"}' > $out/commits/HEAD + tar cfz $out/archive/${nixpkgs.rev}.tar.gz $dir --hard-dereference ''; - in makeTest ( @@ -97,6 +99,16 @@ makeTest ( } ]; }; + services.httpd.virtualHosts."github.com" = + { forceSSL = true; + sslServerKey = "${cert}/server.key"; + sslServerCert = "${cert}/server.crt"; + servedDirs = + [ { urlPath = "/NixOS/nixpkgs"; + dir = archive; + } + ]; + }; }; client = @@ -109,7 +121,7 @@ makeTest ( nix.extraOptions = "experimental-features = nix-command flakes"; environment.systemPackages = [ pkgs.jq ]; networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} = - [ "channels.nixos.org" "api.github.com" ]; + [ "channels.nixos.org" "api.github.com" "github.com" ]; security.pki.certificateFiles = [ "${cert}/ca.crt" ]; }; }; @@ -123,7 +135,7 @@ makeTest ( github.wait_for_unit("httpd.service") - client.succeed("curl -v https://api.github.com/ >&2") + client.succeed("curl -v https://github.com/ >&2") client.succeed("nix registry list | grep nixpkgs") rev = client.succeed("nix flake info nixpkgs --json | jq -r .revision") From e00761af73f68fa7e6b833b4800a17e93f715097 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 30 Oct 2022 07:10:58 +0100 Subject: [PATCH 064/245] Also test github flakes when access tokens are provided --- tests/github-flakes.nix | 66 +++++++++++++++++++++++++++++++++++------ 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/tests/github-flakes.nix b/tests/github-flakes.nix index 1b60a9f9a..43a4f1432 100644 --- a/tests/github-flakes.nix +++ b/tests/github-flakes.nix @@ -37,6 +37,17 @@ let "owner": "NixOS", "repo": "nixpkgs" } + }, + { + "from": { + "type": "indirect", + "id": "private-flake" + }, + "to": { + "type": "github", + "owner": "fancy-enterprise", + "repo": "private-flake" + } } ], "version": 2 @@ -45,9 +56,27 @@ let destination = "/flake-registry.json"; }; - api = pkgs.runCommand "nixpkgs-flake" {} + private-flake-rev = "9f1dd0df5b54a7dc75b618034482ed42ce34383d"; + + private-flake-api = pkgs.runCommand "private-flake" {} + '' + mkdir -p $out/{commits,tarball} + + # Setup https://docs.github.com/en/rest/commits/commits#get-a-commit + echo '{"sha": "${private-flake-rev}"}' > $out/commits/HEAD + + # Setup tarball download via API + dir=private-flake + mkdir $dir + echo '{ outputs = {...}: {}; }' > $dir/flake.nix + tar cfz $out/tarball/${private-flake-rev} $dir --hard-dereference + ''; + + nixpkgs-api = pkgs.runCommand "nixpkgs-flake" {} '' mkdir -p $out/commits + + # Setup https://docs.github.com/en/rest/commits/commits#get-a-commit echo '{"sha": "${nixpkgs.rev}"}' > $out/commits/HEAD ''; @@ -95,7 +124,10 @@ makeTest ( sslServerCert = "${cert}/server.crt"; servedDirs = [ { urlPath = "/repos/NixOS/nixpkgs"; - dir = api; + dir = nixpkgs-api; + } + { urlPath = "/repos/fancy-enterprise/private-flake"; + dir = private-flake-api; } ]; }; @@ -119,7 +151,6 @@ makeTest ( virtualisation.memorySize = 4096; nix.binaryCaches = lib.mkForce [ ]; nix.extraOptions = "experimental-features = nix-command flakes"; - environment.systemPackages = [ pkgs.jq ]; networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} = [ "channels.nixos.org" "api.github.com" "github.com" ]; security.pki.certificateFiles = [ "${cert}/ca.crt" ]; @@ -133,22 +164,39 @@ makeTest ( start_all() + def cat_log(): + github.succeed("cat /var/log/httpd/*.log >&2") + github.wait_for_unit("httpd.service") client.succeed("curl -v https://github.com/ >&2") - client.succeed("nix registry list | grep nixpkgs") + out = client.succeed("nix registry list") + print(out) + assert "github:NixOS/nixpkgs" in out, "nixpkgs flake not found" + assert "github:fancy-enterprise/private-flake" in out, "private flake not found" + cat_log() - rev = client.succeed("nix flake info nixpkgs --json | jq -r .revision") - assert rev.strip() == "${nixpkgs.rev}", "revision mismatch" + # If no github access token is provided, nix should use the public archive url... + out = client.succeed("nix flake metadata nixpkgs --json") + print(out) + info = json.loads(out) + assert info["revision"] == "${nixpkgs.rev}", f"revision mismatch: {info['revision']} != ${nixpkgs.rev}" + cat_log() + + # ... otherwise it should use the API + out = client.succeed("nix flake metadata private-flake --json --access-tokens github.com=ghp_000000000000000000000000000000000000 --tarball-ttl 0") + print(out) + info = json.loads(out) + assert info["revision"] == "${private-flake-rev}", f"revision mismatch: {info['revision']} != ${private-flake-rev}" + cat_log() client.succeed("nix registry pin nixpkgs") - - client.succeed("nix flake info nixpkgs --tarball-ttl 0 >&2") + client.succeed("nix flake metadata nixpkgs --tarball-ttl 0 >&2") # Shut down the web server. The flake should be cached on the client. github.succeed("systemctl stop httpd.service") - info = json.loads(client.succeed("nix flake info nixpkgs --json")) + info = json.loads(client.succeed("nix flake metadata nixpkgs --json")) date = time.strftime("%Y%m%d%H%M%S", time.gmtime(info['lastModified'])) assert date == "${nixpkgs.lastModifiedDate}", "time mismatch" From 06a6a7959eaa859dab591bb5386031c0d3d1b803 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 31 Oct 2022 22:08:02 +0000 Subject: [PATCH 065/245] Bump cachix/cachix-action from 11 to 12 Bumps [cachix/cachix-action](https://github.com/cachix/cachix-action) from 11 to 12. - [Release notes](https://github.com/cachix/cachix-action/releases) - [Commits](https://github.com/cachix/cachix-action/compare/v11...v12) --- updated-dependencies: - dependency-name: cachix/cachix-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7efb90913..dafba6d85 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: fetch-depth: 0 - uses: cachix/install-nix-action@v18 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - - uses: cachix/cachix-action@v11 + - uses: cachix/cachix-action@v12 if: needs.check_secrets.outputs.cachix == 'true' with: name: '${{ env.CACHIX_NAME }}' @@ -59,7 +59,7 @@ jobs: fetch-depth: 0 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - uses: cachix/install-nix-action@v18 - - uses: cachix/cachix-action@v11 + - uses: cachix/cachix-action@v12 with: name: '${{ env.CACHIX_NAME }}' signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' @@ -105,7 +105,7 @@ jobs: - uses: cachix/install-nix-action@v18 - run: echo CACHIX_NAME="$(echo $GITHUB_REPOSITORY-install-tests | tr "[A-Z]/" "[a-z]-")" >> $GITHUB_ENV - run: echo NIX_VERSION="$(nix --experimental-features 'nix-command flakes' eval .\#default.version | tr -d \")" >> $GITHUB_ENV - - uses: cachix/cachix-action@v11 + - uses: cachix/cachix-action@v12 if: needs.check_secrets.outputs.cachix == 'true' with: name: '${{ env.CACHIX_NAME }}' From c068cce107b94f23c5288521770b2c807729a052 Mon Sep 17 00:00:00 2001 From: Sagar Patel Date: Tue, 1 Nov 2022 00:09:00 -0400 Subject: [PATCH 066/245] docs: drop shell prompt character for consistency In addition to consistency, the fancy "Copy to clipboard" button on the website will copy the prompt character. Retaining the prompt character would mean having to edit each command after pasting in the terminal. --- .../src/installation/installing-binary.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index 2d007ca1b..d6d7a61c8 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -3,7 +3,7 @@ The easiest way to install Nix is to run the following command: ```console -$ sh <(curl -L https://nixos.org/nix/install) +sh <(curl -L https://nixos.org/nix/install) ``` This will run the installer interactively (causing it to explain what @@ -27,7 +27,7 @@ you can authenticate with `sudo`. To explicitly select a single-user installation on your system: ```console -$ sh <(curl -L https://nixos.org/nix/install) --no-daemon +sh <(curl -L https://nixos.org/nix/install) --no-daemon ``` This will perform a single-user installation of Nix, meaning that `/nix` @@ -37,8 +37,8 @@ if it doesn’t already exist. If you don’t have `sudo`, you should manually create `/nix` first as root, e.g.: ```console -$ mkdir /nix -$ chown alice /nix +mkdir /nix +chown alice /nix ``` The install script will modify the first writable file from amongst @@ -50,7 +50,7 @@ the install script to disable this behaviour. You can uninstall Nix simply by running: ```console -$ rm -rf /nix +rm -rf /nix ``` # Multi User Installation @@ -66,7 +66,7 @@ You can instruct the installer to perform a multi-user installation on your system: ```console -$ sh <(curl -L https://nixos.org/nix/install) --daemon +sh <(curl -L https://nixos.org/nix/install) --daemon ``` The multi-user installation of Nix will create build users between the @@ -255,7 +255,7 @@ These install scripts can be used the same as the main NixOS.org installation script: ```console -$ sh <(curl -L https://nixos.org/nix/install) +sh <(curl -L https://nixos.org/nix/install) ``` In the same directory of the install script are sha256 sums, and gpg @@ -270,10 +270,10 @@ it somewhere (e.g. in `/tmp`), and then run the script named `install` inside the binary tarball: ```console -$ cd /tmp -$ tar xfj nix-1.8-x86_64-darwin.tar.bz2 -$ cd nix-1.8-x86_64-darwin -$ ./install +cd /tmp +tar xfj nix-1.8-x86_64-darwin.tar.bz2 +cd nix-1.8-x86_64-darwin +./install ``` If you need to edit the multi-user installation script to use different From 34ea0e2e7b72aa70b4b562eef77c7f3617fed1bb Mon Sep 17 00:00:00 2001 From: Yorick van Pelt Date: Tue, 1 Nov 2022 15:46:30 +0100 Subject: [PATCH 067/245] tarfile: set directory mode to at least 0500, don't extract fflags We don't need SGID, or any ACL's. We also want to keep every dir +rx. --- src/libutil/tarfile.cc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/libutil/tarfile.cc b/src/libutil/tarfile.cc index a7db58559..238d0a7a6 100644 --- a/src/libutil/tarfile.cc +++ b/src/libutil/tarfile.cc @@ -77,9 +77,7 @@ TarArchive::~TarArchive() static void extract_archive(TarArchive & archive, const Path & destDir) { - int flags = ARCHIVE_EXTRACT_FFLAGS - | ARCHIVE_EXTRACT_PERM - | ARCHIVE_EXTRACT_TIME + int flags = ARCHIVE_EXTRACT_TIME | ARCHIVE_EXTRACT_SECURE_SYMLINKS | ARCHIVE_EXTRACT_SECURE_NODOTDOT; @@ -98,6 +96,10 @@ static void extract_archive(TarArchive & archive, const Path & destDir) archive_entry_copy_pathname(entry, (destDir + "/" + name).c_str()); + // sources can and do contain dirs with no rx bits + if (archive_entry_filetype(entry) == AE_IFDIR && (archive_entry_mode(entry) & 0500) != 0500) + archive_entry_set_mode(entry, archive_entry_mode(entry) | 0500); + // Patch hardlink path const char *original_hardlink = archive_entry_hardlink(entry); if (original_hardlink) { From 444af855523b00b0959273bab9d7343312817cb3 Mon Sep 17 00:00:00 2001 From: Yorick van Pelt Date: Wed, 5 Oct 2022 12:09:57 +0200 Subject: [PATCH 068/245] Temporarily disable the debugger during completion evaluation readline is not re-entrant, so entering the debugger from the completioncallback results in an eventual segfault. The workaround is to temporarily disable the debugger when searching for possible completions. --- src/libcmd/repl.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index df8932087..bb254ff8d 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -384,6 +384,10 @@ StringSet NixRepl::completePrefix(const std::string & prefix) i++; } } else { + /* Temporarily disable the debugger, to avoid re-entering readline. */ + auto debug_repl = state->debugRepl; + state->debugRepl = nullptr; + Finally restoreDebug([&]() { state->debugRepl = debug_repl; }); try { /* This is an expression that should evaluate to an attribute set. Evaluate it to get the names of the From 16f1720fd2ac1c74043492e71a3e0a3327db4919 Mon Sep 17 00:00:00 2001 From: Yorick van Pelt Date: Thu, 3 Nov 2022 10:11:28 +0100 Subject: [PATCH 069/245] SourceExprCommand: swallow EvalError, add tests for this Completing things that would error would print an ugly error in the middle of your command line. Avoid printing this error. --- src/libcmd/installables.cc | 85 ++++++++++++++++++++------------------ tests/completions.sh | 6 +++ 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index e097f23b3..e8836c247 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -207,55 +207,60 @@ Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() void SourceExprCommand::completeInstallable(std::string_view prefix) { - if (file) { - completionType = ctAttrs; + try { + if (file) { + completionType = ctAttrs; - evalSettings.pureEval = false; - auto state = getEvalState(); - Expr *e = state->parseExprFromFile( - resolveExprPath(state->checkSourcePath(lookupFileArg(*state, *file))) - ); + evalSettings.pureEval = false; + auto state = getEvalState(); + Expr *e = state->parseExprFromFile( + resolveExprPath(state->checkSourcePath(lookupFileArg(*state, *file))) + ); - Value root; - state->eval(e, root); + Value root; + state->eval(e, root); - auto autoArgs = getAutoArgs(*state); + auto autoArgs = getAutoArgs(*state); - std::string prefix_ = std::string(prefix); - auto sep = prefix_.rfind('.'); - std::string searchWord; - if (sep != std::string::npos) { - searchWord = prefix_.substr(sep + 1, std::string::npos); - prefix_ = prefix_.substr(0, sep); - } else { - searchWord = prefix_; - prefix_ = ""; - } + std::string prefix_ = std::string(prefix); + auto sep = prefix_.rfind('.'); + std::string searchWord; + if (sep != std::string::npos) { + searchWord = prefix_.substr(sep + 1, std::string::npos); + prefix_ = prefix_.substr(0, sep); + } else { + searchWord = prefix_; + prefix_ = ""; + } - auto [v, pos] = findAlongAttrPath(*state, prefix_, *autoArgs, root); - Value &v1(*v); - state->forceValue(v1, pos); - Value v2; - state->autoCallFunction(*autoArgs, v1, v2); + auto [v, pos] = findAlongAttrPath(*state, prefix_, *autoArgs, root); + Value &v1(*v); + state->forceValue(v1, pos); + Value v2; + state->autoCallFunction(*autoArgs, v1, v2); - if (v2.type() == nAttrs) { - for (auto & i : *v2.attrs) { - std::string name = state->symbols[i.name]; - if (name.find(searchWord) == 0) { - if (prefix_ == "") - completions->add(name); - else - completions->add(prefix_ + "." + name); + if (v2.type() == nAttrs) { + for (auto & i : *v2.attrs) { + std::string name = state->symbols[i.name]; + if (name.find(searchWord) == 0) { + if (prefix_ == "") + completions->add(name); + else + completions->add(prefix_ + "." + name); + } } } + } else { + completeFlakeRefWithFragment( + getEvalState(), + lockFlags, + getDefaultFlakeAttrPathPrefixes(), + getDefaultFlakeAttrPaths(), + prefix); } - } else { - completeFlakeRefWithFragment( - getEvalState(), - lockFlags, - getDefaultFlakeAttrPathPrefixes(), - getDefaultFlakeAttrPaths(), - prefix); + } catch (EvalError& e) { + // swallow eval error + (void)e; } } diff --git a/tests/completions.sh b/tests/completions.sh index 522aa1c86..19dc61098 100644 --- a/tests/completions.sh +++ b/tests/completions.sh @@ -28,6 +28,10 @@ cat < bar/flake.nix }; } EOF +mkdir -p err +cat < err/flake.nix +throw "error" +EOF # Test the completion of a subcommand [[ "$(NIX_GET_COMPLETIONS=1 nix buil)" == $'normal\nbuild\t' ]] @@ -60,3 +64,5 @@ NIX_GET_COMPLETIONS=3 nix build --option allow-import-from | grep -- "allow-impo # Attr path completions [[ "$(NIX_GET_COMPLETIONS=2 nix eval ./foo\#sam)" == $'attrs\n./foo#sampleOutput\t' ]] [[ "$(NIX_GET_COMPLETIONS=4 nix eval --file ./foo/flake.nix outp)" == $'attrs\noutputs\t' ]] +[[ "$(NIX_GET_COMPLETIONS=4 nix eval --file ./err/flake.nix outp 2>&1)" == $'attrs' ]] +[[ "$(NIX_GET_COMPLETIONS=2 nix eval ./err\# 2>&1)" == $'attrs' ]] From 8e7bbc3c350b5ab1be30f5439e7d05b175b1779d Mon Sep 17 00:00:00 2001 From: Artturin Date: Thu, 3 Nov 2022 21:53:11 +0200 Subject: [PATCH 070/245] tests/impure-derivations.sh: remove unknown experimental feature 'ca-references' ca-references was stabilized in d589a6aa8a5d0c9f391400d7e0e209106e89c857 --- tests/impure-derivations.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/impure-derivations.sh b/tests/impure-derivations.sh index 35ae3f5d3..7ca9ce742 100644 --- a/tests/impure-derivations.sh +++ b/tests/impure-derivations.sh @@ -2,7 +2,7 @@ source common.sh requireDaemonNewerThan "2.8pre20220311" -enableFeatures "ca-derivations ca-references impure-derivations" +enableFeatures "ca-derivations impure-derivations" restartDaemon set -o pipefail From dad859ba0f2d1faad5d687348ad1a4e8ebe8b123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Fri, 4 Nov 2022 12:41:38 +0100 Subject: [PATCH 071/245] Fix printing of eval errors with two format placeholders --- src/libexpr/eval.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index e3716f217..563f24e48 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -904,7 +904,7 @@ void EvalState::throwEvalError(const char * s, const std::string & s2, const std::string & s3) { debugThrowLastTrace(EvalError({ - .msg = hintfmt(s, s2), + .msg = hintfmt(s, s2, s3), .errPos = positions[noPos] })); } @@ -913,7 +913,7 @@ void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::stri const std::string & s3) { debugThrowLastTrace(EvalError({ - .msg = hintfmt(s, s2), + .msg = hintfmt(s, s2, s3), .errPos = positions[pos] })); } @@ -922,7 +922,7 @@ void EvalState::throwEvalError(const PosIdx pos, const char * s, const std::stri const std::string & s3, Env & env, Expr & expr) { debugThrow(EvalError({ - .msg = hintfmt(s, s2), + .msg = hintfmt(s, s2, s3), .errPos = positions[pos] }), env, expr); } From 6259fd7ea6ac331d1037a150b07c01125e80ed8e Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Nov 2022 13:29:39 +0100 Subject: [PATCH 072/245] Fix indentation --- src/nix-store/nix-store.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index eb65e7dde..b59a6d026 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -745,8 +745,8 @@ static void opVerifyPath(Strings opFlags, Strings opArgs) auto current = sink.finish(); if (current.first != info->narHash) { printError("path '%s' was modified! expected hash '%s', got '%s'", - store->printStorePath(path), - info->narHash.to_string(Base32, true), + store->printStorePath(path), + info->narHash.to_string(Base32, true), current.first.to_string(Base32, true)); status = 1; } From 40911d7dec75541a400fe8f556d4c70a7f845fac Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 4 Nov 2022 13:30:35 +0100 Subject: [PATCH 073/245] Remove stray tab --- src/libutil/experimental-features.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index 30d071408..670079019 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -14,8 +14,8 @@ std::map stringifiedXpFeatures = { { Xp::NoUrlLiterals, "no-url-literals" }, { Xp::FetchClosure, "fetch-closure" }, { Xp::ReplFlake, "repl-flake" }, - { Xp::AutoAllocateUids, "auto-allocate-uids" }, - { Xp::SystemdCgroup, "systemd-cgroup" }, + { Xp::AutoAllocateUids, "auto-allocate-uids" }, + { Xp::SystemdCgroup, "systemd-cgroup" }, }; const std::optional parseExperimentalFeature(const std::string_view & name) From 2320a2f93e0aa61e8d45dcfb3fb536361a1fcb6f Mon Sep 17 00:00:00 2001 From: Artturin Date: Fri, 10 Jun 2022 17:46:57 +0300 Subject: [PATCH 074/245] systemd,launchd match nixos open files limit it was bumped in https://github.com/NixOS/nixpkgs/pull/176558 --- misc/launchd/org.nixos.nix-daemon.plist.in | 2 +- misc/systemd/nix-daemon.service.in | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/misc/launchd/org.nixos.nix-daemon.plist.in b/misc/launchd/org.nixos.nix-daemon.plist.in index da1970f69..5fa489b20 100644 --- a/misc/launchd/org.nixos.nix-daemon.plist.in +++ b/misc/launchd/org.nixos.nix-daemon.plist.in @@ -28,7 +28,7 @@ SoftResourceLimits NumberOfFiles - 4096 + 1048576 diff --git a/misc/systemd/nix-daemon.service.in b/misc/systemd/nix-daemon.service.in index e3ac42beb..f46413630 100644 --- a/misc/systemd/nix-daemon.service.in +++ b/misc/systemd/nix-daemon.service.in @@ -9,7 +9,7 @@ ConditionPathIsReadWrite=@localstatedir@/nix/daemon-socket [Service] ExecStart=@@bindir@/nix-daemon nix-daemon --daemon KillMode=process -LimitNOFILE=4096 +LimitNOFILE=1048576 [Install] WantedBy=multi-user.target From 907f52c3376ea764e3fd143fe48c0bbb8958ef22 Mon Sep 17 00:00:00 2001 From: Patrick Jackson Date: Fri, 4 Nov 2022 10:49:44 -0700 Subject: [PATCH 075/245] build-remote: Add brackets to error message --- src/build-remote/build-remote.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc index ff8ba2724..6b81ecc49 100644 --- a/src/build-remote/build-remote.cc +++ b/src/build-remote/build-remote.cc @@ -186,12 +186,12 @@ static int main_build_remote(int argc, char * * argv) // build the hint template. std::string errorText = "Failed to find a machine for remote build!\n" - "derivation: %s\nrequired (system, features): (%s, %s)"; + "derivation: %s\nrequired (system, features): (%s, [%s])"; errorText += "\n%s available machines:"; errorText += "\n(systems, maxjobs, supportedFeatures, mandatoryFeatures)"; for (unsigned int i = 0; i < machines.size(); ++i) - errorText += "\n(%s, %s, %s, %s)"; + errorText += "\n([%s], %s, [%s], [%s])"; // add the template values. std::string drvstr; From f7ab93b0685b653c60b86540639a42b03389c90b Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Sun, 6 Nov 2022 12:25:21 +0100 Subject: [PATCH 076/245] manual: build action -> build task after discussing this with multiple people, I'm convinced that "build task" is more precise: a derivation is not an action, but inert until it is built. also it's easier to pronounce. proposal: use "build task" for the generic concept "description of how to derive new files from the contents of existing files". then it will be easier to distinguish what we mean by "derivation" (a specific data structure and Nix language value type) and "store derivation" (a serialisation of a derivation into a file in the Nix store). --- doc/manual/src/command-ref/nix-daemon.md | 4 ++-- doc/manual/src/command-ref/nix-store.md | 2 +- doc/manual/src/glossary.md | 6 +++--- doc/manual/src/introduction.md | 2 +- doc/manual/src/language/derivations.md | 2 +- src/nix/daemon.md | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/manual/src/command-ref/nix-daemon.md b/doc/manual/src/command-ref/nix-daemon.md index e91cb01dd..04b483be3 100644 --- a/doc/manual/src/command-ref/nix-daemon.md +++ b/doc/manual/src/command-ref/nix-daemon.md @@ -8,6 +8,6 @@ # Description -The Nix daemon is necessary in multi-user Nix installations. It performs -build actions and other operations on the Nix store on behalf of +The Nix daemon is necessary in multi-user Nix installations. It runs +build tasks and other operations on the Nix store on behalf of unprivileged users. diff --git a/doc/manual/src/command-ref/nix-store.md b/doc/manual/src/command-ref/nix-store.md index ecd838e8d..1251888e9 100644 --- a/doc/manual/src/command-ref/nix-store.md +++ b/doc/manual/src/command-ref/nix-store.md @@ -71,7 +71,7 @@ paths. Realisation is a somewhat overloaded term: outputs are already valid, in which case we are done immediately. Otherwise, there may be [substitutes](../glossary.md) that produce the outputs (e.g., by downloading them). Finally, the - outputs can be produced by performing the build action described + outputs can be produced by running the build task described by the derivation. - If the store path is not a derivation, realisation ensures that the diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index 70a0eb994..b13709f8a 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -1,7 +1,7 @@ # Glossary - [derivation]{#gloss-derivation}\ - A description of a build action. The result of a derivation is a + A description of a build task. The result of a derivation is a store object. Derivations are typically specified in Nix expressions using the [`derivation` primitive](language/derivations.md). These are translated into low-level *store derivations* (implicitly by @@ -53,8 +53,8 @@ A file that is an immediate child of the Nix store directory. These can be regular files, but also entire directory trees. Store objects can be sources (objects copied from outside of the store), - derivation outputs (objects produced by running a build action), or - derivations (files describing a build action). + derivation outputs (objects produced by running a build task), or + derivations (files describing a build task). - [input-addressed store object]{#gloss-input-addressed-store-object}\ A store object produced by building a diff --git a/doc/manual/src/introduction.md b/doc/manual/src/introduction.md index d87487a07..b54346db8 100644 --- a/doc/manual/src/introduction.md +++ b/doc/manual/src/introduction.md @@ -104,7 +104,7 @@ a currently running program. Packages are built from _Nix expressions_, which is a simple functional language. A Nix expression describes everything that goes -into a package build action (a “derivation”): other packages, sources, +into a package build task (a “derivation”): other packages, sources, the build script, environment variables for the build script, etc. Nix tries very hard to ensure that Nix expressions are _deterministic_: building a Nix expression twice should yield the same diff --git a/doc/manual/src/language/derivations.md b/doc/manual/src/language/derivations.md index 3391ec0d8..043a38191 100644 --- a/doc/manual/src/language/derivations.md +++ b/doc/manual/src/language/derivations.md @@ -1,7 +1,7 @@ # Derivations The most important built-in function is `derivation`, which is used to -describe a single derivation (a build action). It takes as input a set, +describe a single derivation (a build task). It takes as input a set, the attributes of which specify the inputs of the build. - There must be an attribute named [`system`]{#attr-system} whose value must be a diff --git a/src/nix/daemon.md b/src/nix/daemon.md index e97016a94..d5cdadf08 100644 --- a/src/nix/daemon.md +++ b/src/nix/daemon.md @@ -11,7 +11,7 @@ R""( # Description This command runs the Nix daemon, which is a required component in -multi-user Nix installations. It performs build actions and other +multi-user Nix installations. It runs build tasks and other operations on the Nix store on behalf of non-root users. Usually you don't run the daemon directly; instead it's managed by a service management framework such as `systemd`. From 2fde7e0108d70bcba64ebecc5e5c7ee2863e3446 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Nov 2022 16:03:42 +0100 Subject: [PATCH 077/245] Split auto UID allocation from cgroups Cgroups are now only used for derivations that require the uid-range range feature. This allows auto UID allocation even on systems that don't have cgroups (like macOS). Also, make things work on modern systems that use cgroups v2 (where there is a single hierarchy and no "systemd" controller). --- src/libstore/build/local-derivation-goal.cc | 19 +-- src/libstore/build/local-derivation-goal.hh | 7 +- src/libstore/cgroup.cc | 1 + src/libstore/globals.cc | 4 + src/libstore/globals.hh | 6 +- src/libstore/lock.cc | 149 +++++++++++--------- src/libstore/lock.hh | 24 ++-- src/libstore/parsed-derivations.cc | 6 + src/libstore/parsed-derivations.hh | 2 + 9 files changed, 122 insertions(+), 96 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 64540d262..09da87476 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -160,7 +160,7 @@ void LocalDerivationGoal::tryLocalBuild() { if (useBuildUsers()) { if (!buildUser) - buildUser = acquireUserLock(); + buildUser = acquireUserLock(parsedDrv->useUidRange() ? 65536 : 1); if (!buildUser) { if (!actLock) @@ -495,8 +495,8 @@ void LocalDerivationGoal::startBuilder() } } - useUidRange = parsedDrv->getRequiredSystemFeatures().count("uid-range"); useSystemdCgroup = parsedDrv->getRequiredSystemFeatures().count("Systemd-cgroup"); + assert(!useSystemdCgroup); if (useChroot) { @@ -576,7 +576,8 @@ void LocalDerivationGoal::startBuilder() printMsg(lvlChatty, format("setting up chroot environment in '%1%'") % chrootRootDir); - if (mkdir(chrootRootDir.c_str(), useUidRange ? 0755 : 0750) == -1) + // FIXME: make this 0700 + if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1) throw SysError("cannot create '%1%'", chrootRootDir); // FIXME: only make root writable for user namespace builds. @@ -596,8 +597,8 @@ void LocalDerivationGoal::startBuilder() createDirs(chrootRootDir + "/etc"); chownToBuilder(chrootRootDir + "/etc"); - if (useUidRange && (!buildUser || buildUser->getUIDCount() < 65536)) - throw Error("feature 'uid-range' requires '%s' to be enabled", settings.autoAllocateUids.name); + if (parsedDrv->useUidRange() && (!buildUser || buildUser->getUIDCount() < 65536)) + throw Error("feature 'uid-range' requires the setting '%s' to be enabled", settings.autoAllocateUids.name); /* Declare the build user's group so that programs get a consistent view of the system (e.g., "id -gn"). */ @@ -670,7 +671,7 @@ void LocalDerivationGoal::startBuilder() #endif #endif } else { - if (useUidRange) + if (parsedDrv->useUidRange()) throw Error("feature 'uid-range' is only supported in sandboxed builds"); if (useSystemdCgroup) throw Error("feature 'systemd-cgroup' is only supported in sandboxed builds"); @@ -934,12 +935,12 @@ void LocalDerivationGoal::startBuilder() the calling user (if build users are disabled). */ uid_t hostUid = buildUser ? buildUser->getUID() : getuid(); uid_t hostGid = buildUser ? buildUser->getGID() : getgid(); - uint32_t nrIds = buildUser && useUidRange ? buildUser->getUIDCount() : 1; + uid_t nrIds = buildUser ? buildUser->getUIDCount() : 1; writeFile("/proc/" + std::to_string(pid) + "/uid_map", fmt("%d %d %d", sandboxUid(), hostUid, nrIds)); - if (!useUidRange) + if (!buildUser || buildUser->getUIDCount() == 1) writeFile("/proc/" + std::to_string(pid) + "/setgroups", "deny"); writeFile("/proc/" + std::to_string(pid) + "/gid_map", @@ -1793,7 +1794,7 @@ void LocalDerivationGoal::runChild() throw SysError("mounting /proc"); /* Mount sysfs on /sys. */ - if (useUidRange) { + if (buildUser && buildUser->getUIDCount() != 1) { createDirs(chrootRootDir + "/sys"); if (mount("none", (chrootRootDir + "/sys").c_str(), "sysfs", 0, 0) == -1) throw SysError("mounting /sys"); diff --git a/src/libstore/build/local-derivation-goal.hh b/src/libstore/build/local-derivation-goal.hh index e6700a383..61b0f9145 100644 --- a/src/libstore/build/local-derivation-goal.hh +++ b/src/libstore/build/local-derivation-goal.hh @@ -41,9 +41,6 @@ struct LocalDerivationGoal : public DerivationGoal Path chrootRootDir; - /* Whether to give the build more than 1 UID. */ - bool useUidRange = false; - /* Whether to make the 'systemd' cgroup controller available to the build. */ bool useSystemdCgroup = false; @@ -99,8 +96,8 @@ struct LocalDerivationGoal : public DerivationGoal result. */ std::map prevInfos; - uid_t sandboxUid() { return usingUserNamespace ? (useUidRange ? 0 : 1000) : buildUser->getUID(); } - gid_t sandboxGid() { return usingUserNamespace ? (useUidRange ? 0 : 100) : buildUser->getGID(); } + uid_t sandboxUid() { return usingUserNamespace ? (buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID(); } + gid_t sandboxGid() { return usingUserNamespace ? (buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID(); } const static Path homeDir; diff --git a/src/libstore/cgroup.cc b/src/libstore/cgroup.cc index 5d31609da..56e980be3 100644 --- a/src/libstore/cgroup.cc +++ b/src/libstore/cgroup.cc @@ -13,6 +13,7 @@ namespace nix { +// FIXME: obsolete, check for cgroup2 std::map getCgroups(const Path & cgroupFile) { std::map cgroups; diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index ff658c428..b7f55cae7 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -130,6 +130,10 @@ StringSet Settings::getDefaultSystemFeatures() actually require anything special on the machines. */ StringSet features{"nixos-test", "benchmark", "big-parallel"}; + #if __linux__ + features.insert("uid-range"); + #endif + #if __linux__ if (access("/dev/kvm", R_OK | W_OK) == 0) features.insert("kvm"); diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index d3e86cc55..be741a830 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -46,6 +46,8 @@ struct PluginFilesSetting : public BaseSetting void set(const std::string & str, bool append = false) override; }; +const uint32_t maxIdsPerBuild = 1 << 16; + class Settings : public Config { unsigned int getDefaultCores(); @@ -279,12 +281,10 @@ public: Setting autoAllocateUids{this, false, "auto-allocate-uids", "Whether to allocate UIDs for builders automatically."}; - const uint32_t idsPerBuild = 1 << 16; - Setting startId{this, 872415232, "start-id", "The first UID and GID to use for dynamic ID allocation."}; - Setting uidCount{this, idsPerBuild * 128, "id-count", + Setting uidCount{this, maxIdsPerBuild * 128, "id-count", "The number of UIDs/GIDs to use for dynamic ID allocation."}; #endif diff --git a/src/libstore/lock.cc b/src/libstore/lock.cc index cc3977496..ecc51cebe 100644 --- a/src/libstore/lock.cc +++ b/src/libstore/lock.cc @@ -20,12 +20,8 @@ struct SimpleUserLock : UserLock killUser(uid); } - std::pair getUIDRange() override - { - assert(uid); - return {uid, uid}; - } - + uid_t getUID() override { assert(uid); return uid; } + uid_t getUIDCount() override { return 1; } gid_t getGID() override { assert(gid); return gid; } std::vector getSupplementaryGIDs() override { return supplementaryGIDs; } @@ -115,48 +111,65 @@ struct SimpleUserLock : UserLock } }; -#if __linux__ -struct CgroupUserLock : UserLock +struct AutoUserLock : UserLock { AutoCloseFD fdUserLock; - uid_t uid; + uid_t firstUid = 0; + uid_t nrIds = 1; + #if __linux__ + std::optional cgroup; + #endif + + ~AutoUserLock() + { + // Get rid of our cgroup, ignoring errors. + if (cgroup) rmdir(cgroup->c_str()); + } void kill() override { + #if __linux__ if (cgroup) { + printError("KILL CGROUP %s", *cgroup); destroyCgroup(*cgroup); - cgroup.reset(); + if (mkdir(cgroup->c_str(), 0755) == -1) + throw SysError("creating cgroup '%s'", *cgroup); + } else + #endif + { + assert(firstUid); + printError("KILL USER %d", firstUid); + killUser(firstUid); } } - std::pair getUIDRange() override - { - assert(uid); - return {uid, uid + settings.idsPerBuild - 1}; - } + uid_t getUID() override { assert(firstUid); return firstUid; } + + gid_t getUIDCount() override { return nrIds; } gid_t getGID() override { // We use the same GID ranges as for the UIDs. - assert(uid); - return uid; + assert(firstUid); + return firstUid; } std::vector getSupplementaryGIDs() override { return {}; } - static std::unique_ptr acquire() + static std::unique_ptr acquire(uid_t nrIds) { settings.requireExperimentalFeature(Xp::AutoAllocateUids); assert(settings.startId > 0); - assert(settings.startId % settings.idsPerBuild == 0); - assert(settings.uidCount % settings.idsPerBuild == 0); + assert(settings.startId % maxIdsPerBuild == 0); + assert(settings.uidCount % maxIdsPerBuild == 0); assert((uint64_t) settings.startId + (uint64_t) settings.uidCount <= std::numeric_limits::max()); + assert(nrIds <= maxIdsPerBuild); // FIXME: check whether the id range overlaps any known users createDirs(settings.nixStateDir + "/userpool2"); - size_t nrSlots = settings.uidCount / settings.idsPerBuild; + size_t nrSlots = settings.uidCount / maxIdsPerBuild; for (size_t i = 0; i < nrSlots; i++) { debug("trying user slot '%d'", i); @@ -170,11 +183,47 @@ struct CgroupUserLock : UserLock throw SysError("opening user lock '%s'", fnUserLock); if (lockFile(fd.get(), ltWrite, false)) { - auto lock = std::make_unique(); + auto s = drainFD(fd.get()); + + #if __linux__ + if (s != "") { + /* Kill the old cgroup, to ensure there are no + processes left over from an interrupted build. */ + destroyCgroup(s); + } + #endif + + if (ftruncate(fd.get(), 0) == -1) + throw Error("truncating user lock"); + + auto lock = std::make_unique(); lock->fdUserLock = std::move(fd); - lock->uid = settings.startId + i * settings.idsPerBuild; - auto s = drainFD(lock->fdUserLock.get()); - if (s != "") lock->cgroup = s; + lock->firstUid = settings.startId + i * maxIdsPerBuild; + lock->nrIds = nrIds; + + if (nrIds > 1) { + auto ourCgroups = getCgroups("/proc/self/cgroup"); + auto ourCgroup = ourCgroups[""]; + if (ourCgroup == "") + throw Error("cannot determine cgroup name from /proc/self/cgroup"); + + auto ourCgroupPath = canonPath("/sys/fs/cgroup/" + ourCgroup); + + printError("PARENT CGROUP = %s", ourCgroupPath); + + if (!pathExists(ourCgroupPath)) + throw Error("expected cgroup directory '%s'", ourCgroupPath); + + lock->cgroup = fmt("%s/nix-build-%d", ourCgroupPath, lock->firstUid); + + printError("CHILD CGROUP = %s", *lock->cgroup); + + /* Record the cgroup in the lock file. This ensures that + if we subsequently get executed under a different parent + cgroup, we kill the previous cgroup first. */ + writeFull(lock->fdUserLock.get(), *lock->cgroup); + } + return lock; } } @@ -182,50 +231,16 @@ struct CgroupUserLock : UserLock return nullptr; } - std::optional cgroup; - - std::optional getCgroup() override - { - if (!cgroup) { - /* Create a systemd cgroup since that's the minimum - required by systemd-nspawn. */ - auto ourCgroups = getCgroups("/proc/self/cgroup"); - auto systemdCgroup = ourCgroups["systemd"]; - if (systemdCgroup == "") - throw Error("'systemd' cgroup does not exist"); - - auto hostCgroup = canonPath("/sys/fs/cgroup/systemd/" + systemdCgroup); - - if (!pathExists(hostCgroup)) - throw Error("expected cgroup directory '%s'", hostCgroup); - - cgroup = fmt("%s/nix-%d", hostCgroup, uid); - - destroyCgroup(*cgroup); - - if (mkdir(cgroup->c_str(), 0755) == -1) - throw SysError("creating cgroup '%s'", *cgroup); - - /* Record the cgroup in the lock file. This ensures that - if we subsequently get executed under a different parent - cgroup, we kill the previous cgroup first. */ - if (ftruncate(fdUserLock.get(), 0) == -1) - throw Error("truncating user lock"); - writeFull(fdUserLock.get(), *cgroup); - } - - return cgroup; - }; -}; -#endif - -std::unique_ptr acquireUserLock() -{ #if __linux__ - if (settings.autoAllocateUids) - return CgroupUserLock::acquire(); - else + std::optional getCgroup() override { return cgroup; } #endif +}; + +std::unique_ptr acquireUserLock(uid_t nrIds) +{ + if (settings.autoAllocateUids) + return AutoUserLock::acquire(nrIds); + else return SimpleUserLock::acquire(); } diff --git a/src/libstore/lock.hh b/src/libstore/lock.hh index 4b6d34069..62676a523 100644 --- a/src/libstore/lock.hh +++ b/src/libstore/lock.hh @@ -11,18 +11,16 @@ struct UserLock virtual ~UserLock() { } /* Get the first and last UID. */ - virtual std::pair getUIDRange() = 0; + std::pair getUIDRange() + { + auto first = getUID(); + return {first, first + getUIDCount() - 1}; + } /* Get the first UID. */ - uid_t getUID() - { - return getUIDRange().first; - } + virtual uid_t getUID() = 0; - uid_t getUIDCount() - { - return getUIDRange().second - getUIDRange().first + 1; - } + virtual uid_t getUIDCount() = 0; virtual gid_t getGID() = 0; @@ -31,12 +29,14 @@ struct UserLock /* Kill any processes currently executing as this user. */ virtual void kill() = 0; + #if __linux__ virtual std::optional getCgroup() { return {}; }; + #endif }; -/* Acquire a user lock. Note that this may return nullptr if no user - is available. */ -std::unique_ptr acquireUserLock(); +/* Acquire a user lock for a UID range of size `nrIds`. Note that this + may return nullptr if no user is available. */ +std::unique_ptr acquireUserLock(uid_t nrIds); bool useBuildUsers(); diff --git a/src/libstore/parsed-derivations.cc b/src/libstore/parsed-derivations.cc index f2288a04e..487dbcfbb 100644 --- a/src/libstore/parsed-derivations.cc +++ b/src/libstore/parsed-derivations.cc @@ -90,6 +90,7 @@ std::optional ParsedDerivation::getStringsAttr(const std::string & name StringSet ParsedDerivation::getRequiredSystemFeatures() const { + // FIXME: cache this? StringSet res; for (auto & i : getStringsAttr("requiredSystemFeatures").value_or(Strings())) res.insert(i); @@ -125,6 +126,11 @@ bool ParsedDerivation::substitutesAllowed() const return getBoolAttr("allowSubstitutes", true); } +bool ParsedDerivation::useUidRange() const +{ + return getRequiredSystemFeatures().count("uid-range"); +} + static std::regex shVarName("[A-Za-z_][A-Za-z0-9_]*"); std::optional ParsedDerivation::prepareStructuredAttrs(Store & store, const StorePathSet & inputPaths) diff --git a/src/libstore/parsed-derivations.hh b/src/libstore/parsed-derivations.hh index 95bec21e8..bfb3857c0 100644 --- a/src/libstore/parsed-derivations.hh +++ b/src/libstore/parsed-derivations.hh @@ -38,6 +38,8 @@ public: bool substitutesAllowed() const; + bool useUidRange() const; + std::optional prepareStructuredAttrs(Store & store, const StorePathSet & inputPaths); }; From 05d258667d12b2decda87024a59250c43343b509 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 8 Nov 2022 08:00:29 -0800 Subject: [PATCH 078/245] Fix build on macOS --- src/libstore/build/local-derivation-goal.cc | 2 +- src/libstore/globals.hh | 26 +++++++++++++++++---- src/libstore/lock.cc | 10 ++++---- src/libstore/lock.hh | 2 ++ 4 files changed, 28 insertions(+), 12 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 09da87476..45ea9968f 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -659,7 +659,7 @@ void LocalDerivationGoal::startBuilder() } #else - if (useUidRange) + if (parsedDrv->useUidRange()) throw Error("feature 'uid-range' is not supported on this platform"); if (useSystemdCgroup) throw Error("feature 'systemd-cgroup' is not supported on this platform"); diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index be741a830..88fe72202 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -46,7 +46,13 @@ struct PluginFilesSetting : public BaseSetting void set(const std::string & str, bool append = false) override; }; -const uint32_t maxIdsPerBuild = 1 << 16; +const uint32_t maxIdsPerBuild = + #if __linux__ + 1 << 16 + #else + 1 + #endif + ; class Settings : public Config { @@ -277,16 +283,26 @@ public: multi-user settings with untrusted users. )"}; - #if __linux__ Setting autoAllocateUids{this, false, "auto-allocate-uids", "Whether to allocate UIDs for builders automatically."}; - Setting startId{this, 872415232, "start-id", + Setting startId{this, + #if __linux__ + 872415232, + #else + 56930, + #endif + "start-id", "The first UID and GID to use for dynamic ID allocation."}; - Setting uidCount{this, maxIdsPerBuild * 128, "id-count", + Setting uidCount{this, + #if __linux__ + maxIdsPerBuild * 128, + #else + 128, + #endif + "id-count", "The number of UIDs/GIDs to use for dynamic ID allocation."}; - #endif Setting impersonateLinux26{this, false, "impersonate-linux-26", "Whether to impersonate a Linux 2.6 machine on newer kernels.", diff --git a/src/libstore/lock.cc b/src/libstore/lock.cc index ecc51cebe..f9892bb91 100644 --- a/src/libstore/lock.cc +++ b/src/libstore/lock.cc @@ -122,15 +122,16 @@ struct AutoUserLock : UserLock ~AutoUserLock() { + #if __linux__ // Get rid of our cgroup, ignoring errors. if (cgroup) rmdir(cgroup->c_str()); + #endif } void kill() override { #if __linux__ if (cgroup) { - printError("KILL CGROUP %s", *cgroup); destroyCgroup(*cgroup); if (mkdir(cgroup->c_str(), 0755) == -1) throw SysError("creating cgroup '%s'", *cgroup); @@ -138,7 +139,6 @@ struct AutoUserLock : UserLock #endif { assert(firstUid); - printError("KILL USER %d", firstUid); killUser(firstUid); } } @@ -201,6 +201,7 @@ struct AutoUserLock : UserLock lock->firstUid = settings.startId + i * maxIdsPerBuild; lock->nrIds = nrIds; + #if __linux__ if (nrIds > 1) { auto ourCgroups = getCgroups("/proc/self/cgroup"); auto ourCgroup = ourCgroups[""]; @@ -209,20 +210,17 @@ struct AutoUserLock : UserLock auto ourCgroupPath = canonPath("/sys/fs/cgroup/" + ourCgroup); - printError("PARENT CGROUP = %s", ourCgroupPath); - if (!pathExists(ourCgroupPath)) throw Error("expected cgroup directory '%s'", ourCgroupPath); lock->cgroup = fmt("%s/nix-build-%d", ourCgroupPath, lock->firstUid); - printError("CHILD CGROUP = %s", *lock->cgroup); - /* Record the cgroup in the lock file. This ensures that if we subsequently get executed under a different parent cgroup, we kill the previous cgroup first. */ writeFull(lock->fdUserLock.get(), *lock->cgroup); } + #endif return lock; } diff --git a/src/libstore/lock.hh b/src/libstore/lock.hh index 62676a523..b5536408c 100644 --- a/src/libstore/lock.hh +++ b/src/libstore/lock.hh @@ -4,6 +4,8 @@ #include +#include + namespace nix { struct UserLock From 97a9a8925abb6889fc368f1108c682879abadcdb Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 8 Nov 2022 23:34:57 +0100 Subject: [PATCH 079/245] add CODEOWNERS --- .github/CODEOWNERS | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000..bba083834 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,15 @@ +# Pull requests concerning the listed files will automatically invite the respective maintainers as reviewers. +# This file is not used for denoting any kind of ownership, but is merely a tool for handling notifications. +# +# Merge permissions are required for maintaining an entry in this file. +# For documentation on this mechanism, see https://help.github.com/articles/about-codeowners/ + +# Notified if nothing else matches +* @edolstra @thufschmitt + +# This file +.github/CODEOWNERS @edolstra + +# Public documentation +/doc @fricklerhandwerk +*.md @fricklerhandwerk From daedaa197dbbb688323f2b22b898304c24f9c051 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 9 Nov 2022 00:49:34 +0100 Subject: [PATCH 080/245] fix typos in comments --- doc/manual/redirects.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/manual/redirects.js b/doc/manual/redirects.js index d9b27d866..2e77edd0f 100644 --- a/doc/manual/redirects.js +++ b/doc/manual/redirects.js @@ -2,12 +2,12 @@ // this must be done on the client side, as web servers do not see the anchor part of the URL. // redirections are declared as follows: -// each entry has as key the matched URL path relative to the mdBook document root. +// each entry has as its key a path matching the requested URL path, relative to the mdBook document root. // // IMPORTANT: it must specify the full path with file name and suffix // -// each entry is a set of key-value pairs, where -// - keys are anchors on to the matched path. +// each entry is itself a set of key-value pairs, where +// - keys are anchors on the matched path. // - values are redirection targets relative to the current path. const redirects = { @@ -352,16 +352,16 @@ const redirects = { // // matching URLs is more involved than it should be: // -// 1. `document.location.pathname` can have an have an arbitrary prefix. +// 1. `document.location.pathname` can have an arbitrary prefix. // -// 2. `path_to_root` is set by mdBook and consists only of `../`s and +// 2. `path_to_root` is set by mdBook. it consists only of `../`s and // determines the depth of `` relative to the prefix: // // `document.location.pathname` // |------------------------------| // ///[[.html]][#] // |----| -// `path_to_root` has same number of segments +// `path_to_root` has same number of path segments // // source: https://phaiax.github.io/mdBook/format/theme/index-hbs.html#data // @@ -410,7 +410,9 @@ const path = segments.join('/'); // anchor starts with the hash character (`#`), // but our redirect declarations don't, so we strip it. -// example: document.location.hash -> '#foo' +// example: +// document.location.hash -> '#foo' +// document.location.hash.substring(1) -> 'foo' const anchor = document.location.hash.substring(1); const redirect = redirects[path]; From d8781c4fc5143153812e056df23183bcab505253 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 9 Nov 2022 01:11:47 +0100 Subject: [PATCH 081/245] add removing users to uninstall instructions --- .../src/installation/installing-binary.md | 29 +++++++++++++++---- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index 2d007ca1b..615d862a4 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -88,10 +88,24 @@ extension. The installer will also create `/etc/profile.d/nix.sh`. ### Linux -```console -sudo rm -rf /etc/profile/nix.sh /etc/nix /nix ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels +Remove files created by Nix: -# If you are on Linux with systemd, you will need to run: +```console +sudo rm -rf /nix /etc/nix /etc/profile/nix.sh ~root/.nix-profile ~root/.nix-defexpr ~root/.nix-channels ~/.nix-profile ~/.nix-defexpr ~/.nix-channels +``` + +Remove build users and their group: + +```console +for i in $(seq 30001 30032); do + sudo userdel $i +done +sudo groupdel 30000 +``` + +If you are on Linux with systemd, remove the Nix daemon service: + +```console sudo systemctl stop nix-daemon.socket sudo systemctl stop nix-daemon.service sudo systemctl disable nix-daemon.socket @@ -99,8 +113,13 @@ sudo systemctl disable nix-daemon.service sudo systemctl daemon-reload ``` -There may also be references to Nix in `/etc/profile`, `/etc/bashrc`, -and `/etc/zshrc` which you may remove. +There may also be references to Nix in + +- `/etc/profile` +- `/etc/bashrc`, +- `/etc/zshrc` + +which you may remove. ### macOS From be8744f937150c65ffa5d98ffdc74c502dbcdd5e Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 19 Sep 2022 22:21:40 +0200 Subject: [PATCH 082/245] manual: architecture overview these changes were not merged properly and had to be reverted. see merge commit d8e54d19f71f78540dd967b2e42be6a5d8a0b1bb for full history leading up to here. --- doc/manual/src/SUMMARY.md.in | 1 + doc/manual/src/architecture/architecture.md | 77 +++++++++++++++++++++ 2 files changed, 78 insertions(+) create mode 100644 doc/manual/src/architecture/architecture.md diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in index 908e7e3d9..b386aa14b 100644 --- a/doc/manual/src/SUMMARY.md.in +++ b/doc/manual/src/SUMMARY.md.in @@ -59,6 +59,7 @@ @manpages@ - [Files](command-ref/files.md) - [nix.conf](command-ref/conf-file.md) +- [Architecture](architecture/architecture.md) - [Glossary](glossary.md) - [Contributing](contributing/contributing.md) - [Hacking](contributing/hacking.md) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md new file mode 100644 index 000000000..24150fc2b --- /dev/null +++ b/doc/manual/src/architecture/architecture.md @@ -0,0 +1,77 @@ +# Architecture + +This chapter describes how Nix works. +It should help users understand why Nix behaves as it does, and it should help developers understand how to modify Nix and how to write similar tools. + +## Overview + +Nix consists of [hierarchical layers]. + +[hierarchical layers]: https://en.m.wikipedia.org/wiki/Multitier_architecture#Layers + +``` ++---------------------------------------------------------------+ +| Nix | +| [ commmand line interface ]------, | +| | | | +| evaluates | | +| | manages | +| V | | +| [ configuration language ] | | +| | | | +| +----------------------------|-------------------V----------+ | +| | store evaluates to | | +| | | | | +| | referenced by V builds | | +| | [ build input ] ---> [ build plan ] ---> [ build result ] | | +| | | | +| +-----------------------------------------------------------+ | ++---------------------------------------------------------------+ +``` + +At the top is the [command line interface](../command-ref/command-ref.md), translating from invocations of Nix executables to interactions with the underlying layers. + +Below that is the [Nix language](../language/index.md), a [purely functional] configuration language. +It is used to compose expressions which ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. + +[purely functional]: https://en.m.wikipedia.org/wiki/Purely_functional_programming + +Command line interface and Nix language are what users interact with most. + +> **Note** +> The Nix language itself does not have a notion of *packages* or *configurations*. +> As far as we are concerned here, the inputs and results of a build plan are just data. + +Underlying these is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. +It can also execute build plans to produce new data. + +A build plan is a series of *build tasks*. +Each build task has a special build input, which is used as *build instructions*. +The result of a build task can be input to another build task. + +> **Important** +> A build task in Nix is called [derivation](../glossary#gloss-derivation). + +``` ++----------------------------------------------------------------------------------+ +| store .............................................. | +| : build plan : | +| : : | +| [ build input ]---instructions-, : | +| : | : | +| : v : | +| [ build input ]--------->[ build task ]-instructions-, : | +| : | : | +| : v : | +| [ build input ]---instructions-, [ build task ]--->[ build result ] | +| : | ^ : | +| : v | : | +| [ build input ]--------->[ build task ]--------------' : | +| : ^ : | +| : | : | +| [ build input ]----------------' : | +| : : | +| :............................................: | ++----------------------------------------------------------------------------------+ +``` + From b5728ace5d5250a6d52df05802c9fc325153dc2c Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 23 Sep 2022 14:48:54 +0200 Subject: [PATCH 083/245] add articles --- doc/manual/src/architecture/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 24150fc2b..972ff2d58 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -36,7 +36,7 @@ It is used to compose expressions which ultimately evaluate to self-contained *b [purely functional]: https://en.m.wikipedia.org/wiki/Purely_functional_programming -Command line interface and Nix language are what users interact with most. +The command line interface and the Nix language are what users interact with most. > **Note** > The Nix language itself does not have a notion of *packages* or *configurations*. From 98447c1a7f1538f4d3132c0b79ad5b67e136af85 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 23 Sep 2022 14:49:47 +0200 Subject: [PATCH 084/245] clarify subject of sentence Co-authored-by: Bryan Honof --- doc/manual/src/architecture/architecture.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 972ff2d58..f113cbf4d 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -42,7 +42,7 @@ The command line interface and the Nix language are what users interact with mos > The Nix language itself does not have a notion of *packages* or *configurations*. > As far as we are concerned here, the inputs and results of a build plan are just data. -Underlying these is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. +Underlying the command line interface and the Nix language is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. It can also execute build plans to produce new data. A build plan is a series of *build tasks*. From 3d716df7ce0639e61170ea1e18a370138dce8e5b Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Sat, 15 Oct 2022 23:46:32 +0200 Subject: [PATCH 085/245] make diagrams compatible with svgbob this will at some point enable rendering them nicely for the web --- doc/manual/src/architecture/architecture.md | 53 ++++++++++++--------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index f113cbf4d..0df4fbc68 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -11,20 +11,25 @@ Nix consists of [hierarchical layers]. ``` +---------------------------------------------------------------+ -| Nix | -| [ commmand line interface ]------, | +| Nix .-------------------------. | +| | commmand line interface |------. | +| '-------------------------' | | | | | | | evaluates | | | | manages | | V | | -| [ configuration language ] | | +| .-------------------------. | | +| | configuration language | | | +| '-------------------------' | | | | | | -| +----------------------------|-------------------V----------+ | -| | store evaluates to | | -| | | | | +| evaluates to | | +| | V | +| +----------------------------|------------------------------+ | +| | store | | | | | referenced by V builds | | -| | [ build input ] ---> [ build plan ] ---> [ build result ] | | -| | | | +| | .-------------. .------------. .--------------. | | +| | | build input |----->| build plan | ---->| build result | | | +| | '-------------' '------------' '--------------' | | | +-----------------------------------------------------------+ | +---------------------------------------------------------------+ ``` @@ -54,24 +59,28 @@ The result of a build task can be input to another build task. ``` +----------------------------------------------------------------------------------+ -| store .............................................. | +| store - - - - - - - - - - - - - - - - - - - - - - | | : build plan : | -| : : | -| [ build input ]---instructions-, : | -| : | : | +| .-------------. : : | +| | build input |---instructions-. : | +| '-------------' : | : | | : v : | -| [ build input ]--------->[ build task ]-instructions-, : | -| : | : | +| .-------------. : .------------. : | +| | build input |--------->| build task |-instructions-. : | +| '-------------' : '------------' | : | | : v : | -| [ build input ]---instructions-, [ build task ]--->[ build result ] | -| : | ^ : | -| : v | : | -| [ build input ]--------->[ build task ]--------------' : | +| .-------------. : .------------. : .--------------. | +| | build input |---instructions-. | build task |--->| build result | | +| '-------------' : | '------------' : '--------------' | +| : v ^ : | +| .-------------. : .------------. | : | +| | build input |--------->| build task |--------------' : | +| '-------------' : '------------' : | | : ^ : | -| : | : | -| [ build input ]----------------' : | -| : : | -| :............................................: | +| .-------------. : | : | +| | build input |----------------' : | +| '-------------' : : | +| :_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _: | +----------------------------------------------------------------------------------+ ``` From 9d20a056c82b129ab2584169e692c7f3e324fa29 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 9 Nov 2022 01:36:17 +0100 Subject: [PATCH 086/245] remove external link the language has its own overview page where its properties are described in sufficient detail. --- doc/manual/src/architecture/architecture.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 0df4fbc68..4b0704894 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -36,10 +36,8 @@ Nix consists of [hierarchical layers]. At the top is the [command line interface](../command-ref/command-ref.md), translating from invocations of Nix executables to interactions with the underlying layers. -Below that is the [Nix language](../language/index.md), a [purely functional] configuration language. -It is used to compose expressions which ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. - -[purely functional]: https://en.m.wikipedia.org/wiki/Purely_functional_programming +Below that is the [Nix language](../language/index.md), the configuration language for Nix. +Its expressions ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. The command line interface and the Nix language are what users interact with most. From 6c6eff8ac40e2f5d7b6ff8e772feebb1aa484039 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 10 Nov 2022 17:24:12 +0100 Subject: [PATCH 087/245] Remove the SystemdCgroup feature --- src/libstore/build/local-derivation-goal.cc | 23 +++++++-------------- src/libstore/build/local-derivation-goal.hh | 4 ---- src/libutil/experimental-features.cc | 1 - src/libutil/experimental-features.hh | 1 - 4 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 45ea9968f..e652c425c 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -495,9 +495,6 @@ void LocalDerivationGoal::startBuilder() } } - useSystemdCgroup = parsedDrv->getRequiredSystemFeatures().count("Systemd-cgroup"); - assert(!useSystemdCgroup); - if (useChroot) { /* Allow a user-configurable set of directories from the @@ -649,20 +646,18 @@ void LocalDerivationGoal::startBuilder() dirsInChroot.erase(worker.store.printStorePath(*i.second.second)); } - if (useSystemdCgroup) { - settings.requireExperimentalFeature(Xp::SystemdCgroup); - std::optional cgroup; - if (!buildUser || !(cgroup = buildUser->getCgroup())) - throw Error("feature 'systemd-cgroup' requires 'auto-allocate-uids = true' in nix.conf"); - chownToBuilder(*cgroup); - chownToBuilder(*cgroup + "/cgroup.procs"); + if (buildUser) { + if (auto cgroup = buildUser->getCgroup()) { + chownToBuilder(*cgroup); + chownToBuilder(*cgroup + "/cgroup.procs"); + chownToBuilder(*cgroup + "/cgroup.threads"); + //chownToBuilder(*cgroup + "/cgroup.subtree_control"); + } } #else if (parsedDrv->useUidRange()) throw Error("feature 'uid-range' is not supported on this platform"); - if (useSystemdCgroup) - throw Error("feature 'systemd-cgroup' is not supported on this platform"); #if __APPLE__ /* We don't really have any parent prep work to do (yet?) All work happens in the child, instead. */ @@ -673,8 +668,6 @@ void LocalDerivationGoal::startBuilder() } else { if (parsedDrv->useUidRange()) throw Error("feature 'uid-range' is only supported in sandboxed builds"); - if (useSystemdCgroup) - throw Error("feature 'systemd-cgroup' is only supported in sandboxed builds"); } if (needsHashRewrite() && pathExists(homeDir)) @@ -1845,7 +1838,7 @@ void LocalDerivationGoal::runChild() /* Unshare the cgroup namespace. This means /proc/self/cgroup will show the child's cgroup as '/' rather than whatever it is in the parent. */ - if (useSystemdCgroup && unshare(CLONE_NEWCGROUP) == -1) + if (buildUser && buildUser->getUIDCount() != 1 && unshare(CLONE_NEWCGROUP) == -1) throw SysError("unsharing cgroup namespace"); /* Do the chroot(). */ diff --git a/src/libstore/build/local-derivation-goal.hh b/src/libstore/build/local-derivation-goal.hh index 61b0f9145..070ae53f3 100644 --- a/src/libstore/build/local-derivation-goal.hh +++ b/src/libstore/build/local-derivation-goal.hh @@ -41,10 +41,6 @@ struct LocalDerivationGoal : public DerivationGoal Path chrootRootDir; - /* Whether to make the 'systemd' cgroup controller available to - the build. */ - bool useSystemdCgroup = false; - /* RAII object to delete the chroot directory. */ std::shared_ptr autoDelChroot; diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index 670079019..0f05f3752 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -15,7 +15,6 @@ std::map stringifiedXpFeatures = { { Xp::FetchClosure, "fetch-closure" }, { Xp::ReplFlake, "repl-flake" }, { Xp::AutoAllocateUids, "auto-allocate-uids" }, - { Xp::SystemdCgroup, "systemd-cgroup" }, }; const std::optional parseExperimentalFeature(const std::string_view & name) diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index c749d4767..cf0c06eac 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -24,7 +24,6 @@ enum struct ExperimentalFeature FetchClosure, ReplFlake, AutoAllocateUids, - SystemdCgroup, }; /** From e7ed9ae0c711c4efd83756b16379549ecff52355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Fri, 4 Nov 2022 14:19:31 +0100 Subject: [PATCH 088/245] Restrict `readFile` context to references that appear in the string When calling `builtins.readFile` on a store path, the references of that path are currently added to the resulting string's context. This change makes those references the *possible* context of the string, but filters them to keep only the references whose hash actually appears in the string, similarly to what is done for determining the runtime references of a path. --- src/libexpr/primops.cc | 5 ++++ src/libstore/references.cc | 57 +++++++++++++++++++++++++------------- src/libstore/references.hh | 13 +++++++++ 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 28b998474..ff620ca63 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -5,6 +5,7 @@ #include "globals.hh" #include "json-to-value.hh" #include "names.hh" +#include "references.hh" #include "store-api.hh" #include "util.hh" #include "json.hh" @@ -1542,6 +1543,10 @@ static void prim_readFile(EvalState & state, const PosIdx pos, Value * * args, V refs = state.store->queryPathInfo(state.store->toStorePath(path).first)->references; } catch (Error &) { // FIXME: should be InvalidPathError } + // Re-scan references to filter down to just the ones that actually occur in the file. + auto refsSink = PathRefScanSink::fromPaths(refs); + refsSink << s; + refs = refsSink.getResultPaths(); } auto context = state.store->printStorePathSet(refs); v.mkString(s, context); diff --git a/src/libstore/references.cc b/src/libstore/references.cc index 34dce092c..3bb297fc8 100644 --- a/src/libstore/references.cc +++ b/src/libstore/references.cc @@ -67,6 +67,40 @@ void RefScanSink::operator () (std::string_view data) } +PathRefScanSink::PathRefScanSink(StringSet && hashes, std::map && backMap) + : RefScanSink(std::move(hashes)) + , backMap(std::move(backMap)) +{ } + +PathRefScanSink PathRefScanSink::fromPaths(const StorePathSet & refs) +{ + StringSet hashes; + std::map backMap; + + for (auto & i : refs) { + std::string hashPart(i.hashPart()); + auto inserted = backMap.emplace(hashPart, i).second; + assert(inserted); + hashes.insert(hashPart); + } + + return PathRefScanSink(std::move(hashes), std::move(backMap)); +} + +StorePathSet PathRefScanSink::getResultPaths() +{ + /* Map the hashes found back to their store paths. */ + StorePathSet found; + for (auto & i : getResult()) { + auto j = backMap.find(i); + assert(j != backMap.end()); + found.insert(j->second); + } + + return found; +} + + std::pair scanForReferences( const std::string & path, const StorePathSet & refs) @@ -82,30 +116,13 @@ StorePathSet scanForReferences( const Path & path, const StorePathSet & refs) { - StringSet hashes; - std::map backMap; - - for (auto & i : refs) { - std::string hashPart(i.hashPart()); - auto inserted = backMap.emplace(hashPart, i).second; - assert(inserted); - hashes.insert(hashPart); - } + PathRefScanSink refsSink = PathRefScanSink::fromPaths(refs); + TeeSink sink { refsSink, toTee }; /* Look for the hashes in the NAR dump of the path. */ - RefScanSink refsSink(std::move(hashes)); - TeeSink sink { refsSink, toTee }; dumpPath(path, sink); - /* Map the hashes found back to their store paths. */ - StorePathSet found; - for (auto & i : refsSink.getResult()) { - auto j = backMap.find(i); - assert(j != backMap.end()); - found.insert(j->second); - } - - return found; + return refsSink.getResultPaths(); } diff --git a/src/libstore/references.hh b/src/libstore/references.hh index a6119c861..6f381f96c 100644 --- a/src/libstore/references.hh +++ b/src/libstore/references.hh @@ -27,6 +27,19 @@ public: void operator () (std::string_view data) override; }; +class PathRefScanSink : public RefScanSink +{ + std::map backMap; + + PathRefScanSink(StringSet && hashes, std::map && backMap); + +public: + + static PathRefScanSink fromPaths(const StorePathSet & refs); + + StorePathSet getResultPaths(); +}; + struct RewritingSink : Sink { std::string from, to, prev; From 2af036e5a378c711d8e58d01bdefe5c634c25921 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Fri, 11 Nov 2022 14:01:13 +0100 Subject: [PATCH 089/245] remove stray comma --- doc/manual/src/installation/installing-binary.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index 615d862a4..31faeadc2 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -116,7 +116,7 @@ sudo systemctl daemon-reload There may also be references to Nix in - `/etc/profile` -- `/etc/bashrc`, +- `/etc/bashrc` - `/etc/zshrc` which you may remove. From 07f2cb1e8f03784041475c27c2ba0aac7be6c0b7 Mon Sep 17 00:00:00 2001 From: Tobias Mayer Date: Thu, 10 Nov 2022 08:59:23 +0100 Subject: [PATCH 090/245] libstore: link to aws-crt-cpp This change is needed to support aws-sdk-cpp 1.10 and newer. I opted not to make this dependent on the sdk version because the crt dependency has been in the interface of the older sdk as well, and it was only coincidence that libstore didn't make use of any privately defined symbols directly. --- src/libstore/local.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/local.mk b/src/libstore/local.mk index 1d26ac918..8f28bec6c 100644 --- a/src/libstore/local.mk +++ b/src/libstore/local.mk @@ -20,7 +20,7 @@ endif $(foreach file,$(libstore_FILES),$(eval $(call install-data-in,$(d)/$(file),$(datadir)/nix/sandbox))) 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 -laws-crt-cpp endif ifdef HOST_SOLARIS From efadeee8fd593cd9457c75299165d5d5ac159d0f Mon Sep 17 00:00:00 2001 From: Et7f3 Date: Sat, 12 Nov 2022 23:04:58 +0100 Subject: [PATCH 091/245] build: use pkg-config for lowdown --- src/libcmd/local.mk | 2 +- src/nix/local.mk | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk index 3a4de6bcb..152bc388d 100644 --- a/src/libcmd/local.mk +++ b/src/libcmd/local.mk @@ -8,7 +8,7 @@ libcmd_SOURCES := $(wildcard $(d)/*.cc) libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers -I src/nix -libcmd_LDFLAGS = $(EDITLINE_LIBS) -llowdown -pthread +libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) -pthread libcmd_LIBS = libstore libutil libexpr libmain libfetchers diff --git a/src/nix/local.mk b/src/nix/local.mk index e4ec7634d..0f2f016ec 100644 --- a/src/nix/local.mk +++ b/src/nix/local.mk @@ -18,7 +18,7 @@ nix_CXXFLAGS += -I src/libutil -I src/libstore -I src/libfetchers -I src/libexpr nix_LIBS = libexpr libmain libfetchers libstore libutil libcmd -nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) -llowdown +nix_LDFLAGS = -pthread $(SODIUM_LIBS) $(EDITLINE_LIBS) $(BOOST_LDFLAGS) $(LOWDOWN_LIBS) $(foreach name, \ nix-build nix-channel nix-collect-garbage nix-copy-closure nix-daemon nix-env nix-hash nix-instantiate nix-prefetch-url nix-shell nix-store, \ From 6bf873651740b1552dea6f30c7778dff11bc52ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Mon, 14 Nov 2022 15:03:53 +0100 Subject: [PATCH 092/245] Add release-notes for the context-restriction in readFile --- doc/manual/src/release-notes/rl-next.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 68f7d1a9d..2069e4578 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -5,3 +5,8 @@ arguments will be ignored and the resulting derivation will have `__impure` set to `true`, making it an impure derivation. +* If `builtins.readFile` is called on a file with context, then only the parts + of that context that appear in the content of the file are retained. + This avoids a lot of spurious errors where some benign strings end-up having + a context just because they are read from a store path + ([#7260](https://github.com/NixOS/nix/pull/7260)). From cb39e9a99e21812b424d7d2318157163ab97fc82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Mon, 14 Nov 2022 15:13:46 +0100 Subject: [PATCH 093/245] Test that the result of `readFile` gets ref-scanned --- tests/readfile-context.builder.sh | 1 - tests/readfile-context.nix | 19 ++++++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) delete mode 100644 tests/readfile-context.builder.sh diff --git a/tests/readfile-context.builder.sh b/tests/readfile-context.builder.sh deleted file mode 100644 index 7084a08cb..000000000 --- a/tests/readfile-context.builder.sh +++ /dev/null @@ -1 +0,0 @@ -echo "$input" > $out diff --git a/tests/readfile-context.nix b/tests/readfile-context.nix index 600036a94..54cd1afd9 100644 --- a/tests/readfile-context.nix +++ b/tests/readfile-context.nix @@ -6,14 +6,23 @@ let dependent = mkDerivation { name = "dependent"; - builder = ./readfile-context.builder.sh; - input = "${input}/hello"; + buildCommand = '' + mkdir -p $out + echo -n "$input1" > "$out/file1" + echo -n "$input2" > "$out/file2" + ''; + input1 = "${input}/hello"; + input2 = "hello"; }; readDependent = mkDerivation { - name = "read-dependent"; - builder = ./readfile-context.builder.sh; - input = builtins.readFile dependent; + # Will evaluate correctly because file2 doesn't have any references, + # even though the `dependent` derivation does. + name = builtins.readFile (dependent + "/file2"); + buildCommand = '' + echo "$input" > "$out" + ''; + input = builtins.readFile (dependent + "/file1"); }; in readDependent From 7e162c69fe6cbfb929b5356a7df9de5c25c22565 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 14 Nov 2022 16:26:20 +0100 Subject: [PATCH 094/245] derivation-goal: Fix `requires non-existing output` error It occurred when a output of the dependency was already available, so it didn't need rebuilding and didn't get added to the inputDrvOutputs. This process-related info wasn't suitable for the purpose of finding the actual input paths for the builder. It is better to do this in absolute terms by querying the store. --- src/libstore/build/derivation-goal.cc | 30 +++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 41d2e2a1c..00e375fe9 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -528,13 +528,31 @@ void DerivationGoal::inputsRealised() /* Add the relevant output closures of the input derivation `i' as input paths. Only add the closures of output paths that are specified as inputs. */ - for (auto & j : wantedDepOutputs) - if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) + for (auto & j : wantedDepOutputs) { + /* TODO (impure derivations-induced tech debt): + Tracking input derivation outputs statefully through the + goals is error prone and has led to bugs. + For a robust nix, we need to move towards the `else` branch, + which does not rely on goal state to match up with the + reality of the store, which is our real source of truth. + However, the impure derivations feature still relies on this + fragile way of doing things, because its builds do not have + a representation in the store, which is a usability problem + in itself */ + if (auto outPath = get(inputDrvOutputs, { depDrvPath, j })) { worker.store.computeFSClosure(*outPath, inputPaths); - else - throw Error( - "derivation '%s' requires non-existent output '%s' from input derivation '%s'", - worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath)); + } + else { + auto outMap = worker.evalStore.queryDerivationOutputMap(depDrvPath); + auto outMapPath = outMap.find(j); + if (outMapPath == outMap.end()) { + throw Error( + "derivation '%s' requires non-existent output '%s' from input derivation '%s'", + worker.store.printStorePath(drvPath), j, worker.store.printStorePath(depDrvPath)); + } + worker.store.computeFSClosure(outMapPath->second, inputPaths); + } + } } } From c279ddb18cf3a34b0f6d4e3adcf9455da5397ad7 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 9 Nov 2022 15:21:13 +0100 Subject: [PATCH 095/245] tests: Reproduce #6572 --- tests/build.sh | 51 ++++++++++++++++++++++++++++++++++++++ tests/multiple-outputs.nix | 30 ++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/tests/build.sh b/tests/build.sh index fc6825e25..c7db039b4 100644 --- a/tests/build.sh +++ b/tests/build.sh @@ -70,3 +70,54 @@ testNormalization () { } testNormalization + +# https://github.com/NixOS/nix/issues/6572 +issue_6572_independent_outputs() { + nix build -f multiple-outputs.nix --json independent --no-link > $TEST_ROOT/independent.json + + # Make sure that 'nix build' can build a derivation that depends on both outputs of another derivation. + p=$(nix build -f multiple-outputs.nix use-independent --no-link --print-out-paths) + nix-store --delete "$p" # Clean up for next test + + # Make sure that 'nix build' tracks input-outputs correctly when a single output is already present. + nix-store --delete "$(jq -r <$TEST_ROOT/independent.json .[0].outputs.first)" + p=$(nix build -f multiple-outputs.nix use-independent --no-link --print-out-paths) + cmp $p < $TEST_ROOT/a.json + + # # Make sure that 'nix build' can build a derivation that depends on both outputs of another derivation. + p=$(nix build -f multiple-outputs.nix use-a --no-link --print-out-paths) + nix-store --delete "$p" # Clean up for next test + + # Make sure that 'nix build' tracks input-outputs correctly when a single output is already present. + nix-store --delete "$(jq -r <$TEST_ROOT/a.json .[0].outputs.second)" + p=$(nix build -f multiple-outputs.nix use-a --no-link --print-out-paths) + cmp $p <$out + ''; + }; + b = mkDerivation { defaultOutput = assert a.second.helloString == "Hello, world!"; a; firstOutput = assert a.outputName == "first"; a.first.first; @@ -87,4 +96,25 @@ rec { buildCommand = "mkdir $a $b $c"; }; + independent = mkDerivation { + name = "multiple-outputs-independent"; + outputs = [ "first" "second" ]; + builder = builtins.toFile "builder.sh" + '' + mkdir $first $second + test -z $all + echo "first" > $first/file + echo "second" > $second/file + ''; + }; + + use-independent = mkDerivation { + name = "use-independent"; + inherit (a) first second; + builder = builtins.toFile "builder.sh" + '' + cat $first/file $second/file >$out + ''; + }; + } From 60dea270d0bc430930f5560ebac71a2dc0ab2b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= <7226587+thufschmitt@users.noreply.github.com> Date: Wed, 16 Nov 2022 10:34:32 +0100 Subject: [PATCH 096/245] Swallow the error in a more idiomatic way --- src/libcmd/installables.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index e8836c247..f63b9eeae 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -258,9 +258,8 @@ void SourceExprCommand::completeInstallable(std::string_view prefix) getDefaultFlakeAttrPaths(), prefix); } - } catch (EvalError& e) { - // swallow eval error - (void)e; + } catch (EvalError&) { + // Don't want eval errors to mess-up with the completion engine, so let's just swallow them } } From 09f00dd4d01aa1b6866978d162022133e521614f Mon Sep 17 00:00:00 2001 From: Yorick van Pelt Date: Wed, 16 Nov 2022 16:49:49 +0100 Subject: [PATCH 097/245] Replace src/libutil/json.cc with nlohmann json generation --- src/libexpr/eval.cc | 144 +++++++------- src/libexpr/primops.cc | 13 +- src/libexpr/value-to-json.cc | 50 +++-- src/libexpr/value-to-json.hh | 7 +- src/libexpr/value.hh | 6 +- src/libstore/binary-cache-store.cc | 18 +- src/libstore/build/derivation-goal.cc | 1 - src/libstore/build/local-derivation-goal.cc | 2 +- src/libstore/nar-accessor.cc | 30 +-- src/libstore/nar-accessor.hh | 6 +- src/libstore/parsed-derivations.cc | 16 +- src/libstore/remote-fs-accessor.cc | 8 +- src/libstore/store-api.cc | 48 ++--- src/libstore/store-api.hh | 4 +- src/libutil/json.cc | 203 -------------------- src/libutil/json.hh | 185 ------------------ src/libutil/tests/json.cc | 193 ------------------- src/nix-env/nix-env.cc | 35 ++-- src/nix/eval.cc | 7 +- src/nix/flake.cc | 37 ++-- src/nix/ls.cc | 5 +- src/nix/make-content-addressed.cc | 12 +- src/nix/path-info.cc | 8 +- src/nix/search.cc | 19 +- src/nix/show-derivation.cc | 67 +++---- 25 files changed, 266 insertions(+), 858 deletions(-) delete mode 100644 src/libutil/json.cc delete mode 100644 src/libutil/json.hh delete mode 100644 src/libutil/tests/json.cc diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 563f24e48..e78d28b97 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -7,7 +7,6 @@ #include "globals.hh" #include "eval-inline.hh" #include "filetransfer.hh" -#include "json.hh" #include "function-trace.hh" #include @@ -21,6 +20,7 @@ #include #include +#include #if HAVE_BOEHMGC @@ -35,6 +35,8 @@ #endif +using json = nlohmann::json; + namespace nix { static char * allocString(size_t size) @@ -2441,97 +2443,97 @@ void EvalState::printStats() std::fstream fs; if (outPath != "-") fs.open(outPath, std::fstream::out); - JSONObject topObj(outPath == "-" ? std::cerr : fs, true); - topObj.attr("cpuTime",cpuTime); - { - auto envs = topObj.object("envs"); - envs.attr("number", nrEnvs); - envs.attr("elements", nrValuesInEnvs); - envs.attr("bytes", bEnvs); - } - { - auto lists = topObj.object("list"); - lists.attr("elements", nrListElems); - lists.attr("bytes", bLists); - lists.attr("concats", nrListConcats); - } - { - auto values = topObj.object("values"); - values.attr("number", nrValues); - values.attr("bytes", bValues); - } - { - auto syms = topObj.object("symbols"); - syms.attr("number", symbols.size()); - syms.attr("bytes", symbols.totalSize()); - } - { - auto sets = topObj.object("sets"); - sets.attr("number", nrAttrsets); - sets.attr("bytes", bAttrsets); - sets.attr("elements", nrAttrsInAttrsets); - } - { - auto sizes = topObj.object("sizes"); - sizes.attr("Env", sizeof(Env)); - sizes.attr("Value", sizeof(Value)); - sizes.attr("Bindings", sizeof(Bindings)); - sizes.attr("Attr", sizeof(Attr)); - } - topObj.attr("nrOpUpdates", nrOpUpdates); - topObj.attr("nrOpUpdateValuesCopied", nrOpUpdateValuesCopied); - topObj.attr("nrThunks", nrThunks); - topObj.attr("nrAvoided", nrAvoided); - topObj.attr("nrLookups", nrLookups); - topObj.attr("nrPrimOpCalls", nrPrimOpCalls); - topObj.attr("nrFunctionCalls", nrFunctionCalls); + json topObj = json::object(); + topObj["cpuTime"] = cpuTime; + topObj["envs"] = { + {"number", nrEnvs}, + {"elements", nrValuesInEnvs}, + {"bytes", bEnvs}, + }; + topObj["list"] = { + {"elements", nrListElems}, + {"bytes", bLists}, + {"concats", nrListConcats}, + }; + topObj["values"] = { + {"number", nrValues}, + {"bytes", bValues}, + }; + topObj["symbols"] = { + {"number", symbols.size()}, + {"bytes", symbols.totalSize()}, + }; + topObj["sets"] = { + {"number", nrAttrsets}, + {"bytes", bAttrsets}, + {"elements", nrAttrsInAttrsets}, + }; + topObj["sizes"] = { + {"Env", sizeof(Env)}, + {"Value", sizeof(Value)}, + {"Bindings", sizeof(Bindings)}, + {"Attr", sizeof(Attr)}, + }; + topObj["nrOpUpdates"] = nrOpUpdates; + topObj["nrOpUpdateValuesCopied"] = nrOpUpdateValuesCopied; + topObj["nrThunks"] = nrThunks; + topObj["nrAvoided"] = nrAvoided; + topObj["nrLookups"] = nrLookups; + topObj["nrPrimOpCalls"] = nrPrimOpCalls; + topObj["nrFunctionCalls"] = nrFunctionCalls; #if HAVE_BOEHMGC - { - auto gc = topObj.object("gc"); - gc.attr("heapSize", heapSize); - gc.attr("totalBytes", totalBytes); - } + topObj["gc"] = { + {"heapSize", heapSize}, + {"totalBytes", totalBytes}, + }; #endif if (countCalls) { + topObj["primops"] = primOpCalls; { - auto obj = topObj.object("primops"); - for (auto & i : primOpCalls) - obj.attr(i.first, i.second); - } - { - auto list = topObj.list("functions"); + auto& list = topObj["functions"]; + list = json::array(); for (auto & [fun, count] : functionCalls) { - auto obj = list.object(); + json obj = json::object(); if (fun->name) - obj.attr("name", (std::string_view) symbols[fun->name]); + obj["name"] = (std::string_view) symbols[fun->name]; else - obj.attr("name", nullptr); + obj["name"] = nullptr; if (auto pos = positions[fun->pos]) { - obj.attr("file", (std::string_view) pos.file); - obj.attr("line", pos.line); - obj.attr("column", pos.column); + obj["file"] = (std::string_view) pos.file; + obj["line"] = pos.line; + obj["column"] = pos.column; } - obj.attr("count", count); + obj["count"] = count; + list.push_back(obj); } } { - auto list = topObj.list("attributes"); + auto list = topObj["attributes"]; + list = json::array(); for (auto & i : attrSelects) { - auto obj = list.object(); + json obj = json::object(); if (auto pos = positions[i.first]) { - obj.attr("file", (const std::string &) pos.file); - obj.attr("line", pos.line); - obj.attr("column", pos.column); + obj["file"] = (const std::string &) pos.file; + obj["line"] = pos.line; + obj["column"] = pos.column; } - obj.attr("count", i.second); + obj["count"] = i.second; + list.push_back(obj); } } } if (getEnv("NIX_SHOW_SYMBOLS").value_or("0") != "0") { - auto list = topObj.list("symbols"); - symbols.dump([&](const std::string & s) { list.elem(s); }); + // XXX: overrides earlier assignment + topObj["symbols"] = json::array(); + auto &list = topObj["symbols"]; + symbols.dump([&](const std::string & s) { list.emplace_back(s); }); + } + if (outPath == "-") { + std::cerr << topObj.dump(2) << std::endl; + } else { + fs << topObj.dump(2) << std::endl; } } } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 22f6ad3cc..05265411c 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -8,12 +8,12 @@ #include "references.hh" #include "store-api.hh" #include "util.hh" -#include "json.hh" #include "value-to-json.hh" #include "value-to-xml.hh" #include "primops.hh" #include +#include #include #include @@ -1011,6 +1011,7 @@ static void prim_second(EvalState & state, const PosIdx pos, Value * * args, Val derivation. */ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * args, Value & v) { + using nlohmann::json; state.forceAttrs(*args[0], pos); /* Figure out the name first (for stack backtraces). */ @@ -1032,11 +1033,10 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * } /* Check whether attributes should be passed as a JSON file. */ - std::ostringstream jsonBuf; - std::unique_ptr jsonObject; + std::optional jsonObject; attr = args[0]->attrs->find(state.sStructuredAttrs); if (attr != args[0]->attrs->end() && state.forceBool(*attr->value, pos)) - jsonObject = std::make_unique(jsonBuf); + jsonObject = json::object(); /* Check whether null attributes should be ignored. */ bool ignoreNulls = false; @@ -1138,8 +1138,7 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * if (i->name == state.sStructuredAttrs) continue; - auto placeholder(jsonObject->placeholder(key)); - printValueAsJSON(state, true, *i->value, pos, placeholder, context); + (*jsonObject)[key] = printValueAsJSON(state, true, *i->value, pos, context); if (i->name == state.sBuilder) drv.builder = state.forceString(*i->value, context, posDrvName); @@ -1183,8 +1182,8 @@ static void prim_derivationStrict(EvalState & state, const PosIdx pos, Value * * } if (jsonObject) { + drv.env.emplace("__json", jsonObject->dump()); jsonObject.reset(); - drv.env.emplace("__json", jsonBuf.str()); } /* Everything in the context of the strings in the derivation diff --git a/src/libexpr/value-to-json.cc b/src/libexpr/value-to-json.cc index 4d63d8b49..5dc453b2e 100644 --- a/src/libexpr/value-to-json.cc +++ b/src/libexpr/value-to-json.cc @@ -1,84 +1,82 @@ #include "value-to-json.hh" -#include "json.hh" #include "eval-inline.hh" #include "util.hh" #include #include +#include namespace nix { - -void printValueAsJSON(EvalState & state, bool strict, - Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore) +using json = nlohmann::json; +json printValueAsJSON(EvalState & state, bool strict, + Value & v, const PosIdx pos, PathSet & context, bool copyToStore) { checkInterrupt(); if (strict) state.forceValue(v, pos); + json out; + switch (v.type()) { case nInt: - out.write(v.integer); + out = v.integer; break; case nBool: - out.write(v.boolean); + out = v.boolean; break; case nString: copyContext(v, context); - out.write(v.string.s); + out = v.string.s; break; case nPath: if (copyToStore) - out.write(state.copyPathToStore(context, v.path)); + out = state.copyPathToStore(context, v.path); else - out.write(v.path); + out = v.path; break; case nNull: - out.write(nullptr); break; case nAttrs: { auto maybeString = state.tryAttrsToString(pos, v, context, false, false); if (maybeString) { - out.write(*maybeString); + out = *maybeString; break; } auto i = v.attrs->find(state.sOutPath); if (i == v.attrs->end()) { - auto obj(out.object()); + out = json::object(); StringSet names; for (auto & j : *v.attrs) names.emplace(state.symbols[j.name]); for (auto & j : names) { Attr & a(*v.attrs->find(state.symbols.create(j))); - auto placeholder(obj.placeholder(j)); - printValueAsJSON(state, strict, *a.value, a.pos, placeholder, context, copyToStore); + out[j] = printValueAsJSON(state, strict, *a.value, a.pos, context, copyToStore); } } else - printValueAsJSON(state, strict, *i->value, i->pos, out, context, copyToStore); + return printValueAsJSON(state, strict, *i->value, i->pos, context, copyToStore); break; } case nList: { - auto list(out.list()); - for (auto elem : v.listItems()) { - auto placeholder(list.placeholder()); - printValueAsJSON(state, strict, *elem, pos, placeholder, context, copyToStore); - } + out = json::array(); + for (auto elem : v.listItems()) + out.push_back(printValueAsJSON(state, strict, *elem, pos, context, copyToStore)); break; } case nExternal: - v.external->printValueAsJSON(state, strict, out, context, copyToStore); + return v.external->printValueAsJSON(state, strict, context, copyToStore); break; case nFloat: - out.write(v.fpoint); + out = v.fpoint; break; case nThunk: @@ -91,17 +89,17 @@ void printValueAsJSON(EvalState & state, bool strict, state.debugThrowLastTrace(e); throw e; } + return out; } void printValueAsJSON(EvalState & state, bool strict, Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore) { - JSONPlaceholder out(str); - printValueAsJSON(state, strict, v, pos, out, context, copyToStore); + str << printValueAsJSON(state, strict, v, pos, context, copyToStore); } -void ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, - JSONPlaceholder & out, PathSet & context, bool copyToStore) const +json ExternalValueBase::printValueAsJSON(EvalState & state, bool strict, + PathSet & context, bool copyToStore) const { state.debugThrowLastTrace(TypeError("cannot convert %1% to JSON", showType())); } diff --git a/src/libexpr/value-to-json.hh b/src/libexpr/value-to-json.hh index 7ddc8a5b1..22f26b790 100644 --- a/src/libexpr/value-to-json.hh +++ b/src/libexpr/value-to-json.hh @@ -5,13 +5,12 @@ #include #include +#include namespace nix { -class JSONPlaceholder; - -void printValueAsJSON(EvalState & state, bool strict, - Value & v, const PosIdx pos, JSONPlaceholder & out, PathSet & context, bool copyToStore = true); +nlohmann::json printValueAsJSON(EvalState & state, bool strict, + Value & v, const PosIdx pos, PathSet & context, bool copyToStore = true); void printValueAsJSON(EvalState & state, bool strict, Value & v, const PosIdx pos, std::ostream & str, PathSet & context, bool copyToStore = true); diff --git a/src/libexpr/value.hh b/src/libexpr/value.hh index 590ba7783..5adac72f8 100644 --- a/src/libexpr/value.hh +++ b/src/libexpr/value.hh @@ -7,6 +7,7 @@ #if HAVE_BOEHMGC #include #endif +#include namespace nix { @@ -62,7 +63,6 @@ class StorePath; class Store; class EvalState; class XMLWriter; -class JSONPlaceholder; typedef int64_t NixInt; @@ -98,8 +98,8 @@ class ExternalValueBase virtual bool operator ==(const ExternalValueBase & b) const; /* Print the value as JSON. Defaults to unconvertable, i.e. throws an error */ - virtual void printValueAsJSON(EvalState & state, bool strict, - JSONPlaceholder & out, PathSet & context, bool copyToStore = true) const; + virtual nlohmann::json printValueAsJSON(EvalState & state, bool strict, + PathSet & context, bool copyToStore = true) const; /* Print the value as XML. Defaults to unevaluated */ virtual void printValueAsXML(EvalState & state, bool strict, bool location, diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc index a26770c79..12d0c32fb 100644 --- a/src/libstore/binary-cache-store.cc +++ b/src/libstore/binary-cache-store.cc @@ -9,7 +9,6 @@ #include "remote-fs-accessor.hh" #include "nar-info-disk-cache.hh" #include "nar-accessor.hh" -#include "json.hh" #include "thread-pool.hh" #include "callback.hh" @@ -194,19 +193,12 @@ ref BinaryCacheStore::addToStoreCommon( /* Optionally write a JSON file containing a listing of the contents of the NAR. */ if (writeNARListing) { - std::ostringstream jsonOut; + nlohmann::json j = { + {"version", 1}, + {"root", listNar(ref(narAccessor), "", true)}, + }; - { - JSONObject jsonRoot(jsonOut); - jsonRoot.attr("version", 1); - - { - auto res = jsonRoot.placeholder("root"); - listNar(res, ref(narAccessor), "", true); - } - } - - upsertFile(std::string(info.path.hashPart()) + ".ls", jsonOut.str(), "application/json"); + upsertFile(std::string(info.path.hashPart()) + ".ls", j.dump(), "application/json"); } /* Optionally maintain an index of DWARF debug info files diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 00e375fe9..1938f4bcb 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -7,7 +7,6 @@ #include "finally.hh" #include "util.hh" #include "archive.hh" -#include "json.hh" #include "compression.hh" #include "worker-protocol.hh" #include "topo-sort.hh" diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 5cea3b590..c786e8613 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -8,7 +8,6 @@ #include "finally.hh" #include "util.hh" #include "archive.hh" -#include "json.hh" #include "compression.hh" #include "daemon.hh" #include "worker-protocol.hh" @@ -56,6 +55,7 @@ #include #include +#include namespace nix { diff --git a/src/libstore/nar-accessor.cc b/src/libstore/nar-accessor.cc index 398147fc3..9a0003588 100644 --- a/src/libstore/nar-accessor.cc +++ b/src/libstore/nar-accessor.cc @@ -1,6 +1,5 @@ #include "nar-accessor.hh" #include "archive.hh" -#include "json.hh" #include #include @@ -243,42 +242,43 @@ ref makeLazyNarAccessor(const std::string & listing, return make_ref(listing, getNarBytes); } -void listNar(JSONPlaceholder & res, ref accessor, - const Path & path, bool recurse) +using nlohmann::json; +json listNar(ref accessor, const Path & path, bool recurse) { auto st = accessor->stat(path); - auto obj = res.object(); + json obj = json::object(); switch (st.type) { case FSAccessor::Type::tRegular: - obj.attr("type", "regular"); - obj.attr("size", st.fileSize); + obj["type"] = "regular"; + obj["size"] = st.fileSize; if (st.isExecutable) - obj.attr("executable", true); + obj["executable"] = true; if (st.narOffset) - obj.attr("narOffset", st.narOffset); + obj["narOffset"] = st.narOffset; break; case FSAccessor::Type::tDirectory: - obj.attr("type", "directory"); + obj["type"] = "directory"; { - auto res2 = obj.object("entries"); + obj["entries"] = json::object(); + json &res2 = obj["entries"]; for (auto & name : accessor->readDirectory(path)) { if (recurse) { - auto res3 = res2.placeholder(name); - listNar(res3, accessor, path + "/" + name, true); + res2[name] = listNar(accessor, path + "/" + name, true); } else - res2.object(name); + res2[name] = json::object(); } } break; case FSAccessor::Type::tSymlink: - obj.attr("type", "symlink"); - obj.attr("target", accessor->readLink(path)); + obj["type"] = "symlink"; + obj["target"] = accessor->readLink(path); break; default: throw Error("path '%s' does not exist in NAR", path); } + return obj; } } diff --git a/src/libstore/nar-accessor.hh b/src/libstore/nar-accessor.hh index c2241a04c..7d998ae0b 100644 --- a/src/libstore/nar-accessor.hh +++ b/src/libstore/nar-accessor.hh @@ -2,6 +2,7 @@ #include +#include #include "fs-accessor.hh" namespace nix { @@ -24,11 +25,8 @@ ref makeLazyNarAccessor( const std::string & listing, GetNarBytes getNarBytes); -class JSONPlaceholder; - /* Write a JSON representation of the contents of a NAR (except file contents). */ -void listNar(JSONPlaceholder & res, ref accessor, - const Path & path, bool recurse); +nlohmann::json listNar(ref accessor, const Path & path, bool recurse); } diff --git a/src/libstore/parsed-derivations.cc b/src/libstore/parsed-derivations.cc index f2288a04e..59a30db10 100644 --- a/src/libstore/parsed-derivations.cc +++ b/src/libstore/parsed-derivations.cc @@ -2,7 +2,6 @@ #include #include -#include "json.hh" namespace nix { @@ -144,16 +143,11 @@ std::optional ParsedDerivation::prepareStructuredAttrs(Store & s auto e = json.find("exportReferencesGraph"); if (e != json.end() && e->is_object()) { for (auto i = e->begin(); i != e->end(); ++i) { - std::ostringstream str; - { - JSONPlaceholder jsonRoot(str, true); - StorePathSet storePaths; - for (auto & p : *i) - storePaths.insert(store.parseStorePath(p.get())); - store.pathInfoToJSON(jsonRoot, - store.exportReferences(storePaths, inputPaths), false, true); - } - json[i.key()] = nlohmann::json::parse(str.str()); // urgh + StorePathSet storePaths; + for (auto & p : *i) + storePaths.insert(store.parseStorePath(p.get())); + json[i.key()] = store.pathInfoToJSON( + store.exportReferences(storePaths, inputPaths), false, true); } } diff --git a/src/libstore/remote-fs-accessor.cc b/src/libstore/remote-fs-accessor.cc index 0ce335646..fcfb527f5 100644 --- a/src/libstore/remote-fs-accessor.cc +++ b/src/libstore/remote-fs-accessor.cc @@ -1,6 +1,6 @@ +#include #include "remote-fs-accessor.hh" #include "nar-accessor.hh" -#include "json.hh" #include #include @@ -38,10 +38,8 @@ ref RemoteFSAccessor::addToCache(std::string_view hashPart, std::str if (cacheDir != "") { try { - std::ostringstream str; - JSONPlaceholder jsonRoot(str); - listNar(jsonRoot, narAccessor, "", true); - writeFile(makeCacheFile(hashPart, "ls"), str.str()); + nlohmann::json j = listNar(narAccessor, "", true); + writeFile(makeCacheFile(hashPart, "ls"), j.dump()); } catch (...) { ignoreException(); } diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 06a9758fc..8811ab578 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -6,14 +6,16 @@ #include "util.hh" #include "nar-info-disk-cache.hh" #include "thread-pool.hh" -#include "json.hh" #include "url.hh" #include "archive.hh" #include "callback.hh" #include "remote-store.hh" +#include #include +using json = nlohmann::json; + namespace nix { @@ -838,56 +840,53 @@ StorePathSet Store::exportReferences(const StorePathSet & storePaths, const Stor return paths; } - -void Store::pathInfoToJSON(JSONPlaceholder & jsonOut, const StorePathSet & storePaths, +json Store::pathInfoToJSON(const StorePathSet & storePaths, bool includeImpureInfo, bool showClosureSize, Base hashBase, AllowInvalidFlag allowInvalid) { - auto jsonList = jsonOut.list(); + json::array_t jsonList = json::array(); for (auto & storePath : storePaths) { - auto jsonPath = jsonList.object(); + auto& jsonPath = jsonList.emplace_back(json::object()); try { auto info = queryPathInfo(storePath); - jsonPath.attr("path", printStorePath(info->path)); - jsonPath - .attr("narHash", info->narHash.to_string(hashBase, true)) - .attr("narSize", info->narSize); + jsonPath["path"] = printStorePath(info->path); + jsonPath["narHash"] = info->narHash.to_string(hashBase, true); + jsonPath["narSize"] = info->narSize; { - auto jsonRefs = jsonPath.list("references"); + auto& jsonRefs = (jsonPath["references"] = json::array()); for (auto & ref : info->references) - jsonRefs.elem(printStorePath(ref)); + jsonRefs.emplace_back(printStorePath(ref)); } if (info->ca) - jsonPath.attr("ca", renderContentAddress(info->ca)); + jsonPath["ca"] = renderContentAddress(info->ca); std::pair closureSizes; if (showClosureSize) { closureSizes = getClosureSize(info->path); - jsonPath.attr("closureSize", closureSizes.first); + jsonPath["closureSize"] = closureSizes.first; } if (includeImpureInfo) { if (info->deriver) - jsonPath.attr("deriver", printStorePath(*info->deriver)); + jsonPath["deriver"] = printStorePath(*info->deriver); if (info->registrationTime) - jsonPath.attr("registrationTime", info->registrationTime); + jsonPath["registrationTime"] = info->registrationTime; if (info->ultimate) - jsonPath.attr("ultimate", info->ultimate); + jsonPath["ultimate"] = info->ultimate; if (!info->sigs.empty()) { - auto jsonSigs = jsonPath.list("signatures"); for (auto & sig : info->sigs) - jsonSigs.elem(sig); + jsonPath["signatures"].push_back(sig); } auto narInfo = std::dynamic_pointer_cast( @@ -895,21 +894,22 @@ void Store::pathInfoToJSON(JSONPlaceholder & jsonOut, const StorePathSet & store if (narInfo) { if (!narInfo->url.empty()) - jsonPath.attr("url", narInfo->url); + jsonPath["url"] = narInfo->url; if (narInfo->fileHash) - jsonPath.attr("downloadHash", narInfo->fileHash->to_string(hashBase, true)); + jsonPath["downloadHash"] = narInfo->fileHash->to_string(hashBase, true); if (narInfo->fileSize) - jsonPath.attr("downloadSize", narInfo->fileSize); + jsonPath["downloadSize"] = narInfo->fileSize; if (showClosureSize) - jsonPath.attr("closureDownloadSize", closureSizes.second); + jsonPath["closureDownloadSize"] = closureSizes.second; } } } catch (InvalidPath &) { - jsonPath.attr("path", printStorePath(storePath)); - jsonPath.attr("valid", false); + jsonPath["path"] = printStorePath(storePath); + jsonPath["valid"] = false; } } + return jsonList; } diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index c8a667c6d..151ec10d6 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -14,6 +14,7 @@ #include "path-info.hh" #include "repair-flag.hh" +#include #include #include #include @@ -68,7 +69,6 @@ struct Derivation; class FSAccessor; class NarInfoDiskCache; class Store; -class JSONPlaceholder; enum CheckSigsFlag : bool { NoCheckSigs = false, CheckSigs = true }; @@ -512,7 +512,7 @@ public: variable elements such as the registration time are included. If ‘showClosureSize’ is true, the closure size of each path is included. */ - void pathInfoToJSON(JSONPlaceholder & jsonOut, const StorePathSet & storePaths, + nlohmann::json pathInfoToJSON(const StorePathSet & storePaths, bool includeImpureInfo, bool showClosureSize, Base hashBase = Base32, AllowInvalidFlag allowInvalid = DisallowInvalid); diff --git a/src/libutil/json.cc b/src/libutil/json.cc deleted file mode 100644 index 2f9e97ff5..000000000 --- a/src/libutil/json.cc +++ /dev/null @@ -1,203 +0,0 @@ -#include "json.hh" - -#include -#include -#include - -namespace nix { - -template<> -void toJSON(std::ostream & str, const std::string_view & s) -{ - constexpr size_t BUF_SIZE = 4096; - char buf[BUF_SIZE + 7]; // BUF_SIZE + largest single sequence of puts - size_t bufPos = 0; - - const auto flush = [&] { - str.write(buf, bufPos); - bufPos = 0; - }; - const auto put = [&] (char c) { - buf[bufPos++] = c; - }; - - put('"'); - for (auto i = s.begin(); i != s.end(); i++) { - if (bufPos >= BUF_SIZE) flush(); - if (*i == '\"' || *i == '\\') { put('\\'); put(*i); } - else if (*i == '\n') { put('\\'); put('n'); } - else if (*i == '\r') { put('\\'); put('r'); } - else if (*i == '\t') { put('\\'); put('t'); } - else if (*i >= 0 && *i < 32) { - const char hex[17] = "0123456789abcdef"; - put('\\'); - put('u'); - put(hex[(uint16_t(*i) >> 12) & 0xf]); - put(hex[(uint16_t(*i) >> 8) & 0xf]); - put(hex[(uint16_t(*i) >> 4) & 0xf]); - put(hex[(uint16_t(*i) >> 0) & 0xf]); - } - else put(*i); - } - put('"'); - flush(); -} - -void toJSON(std::ostream & str, const char * s) -{ - if (!s) str << "null"; else toJSON(str, std::string_view(s)); -} - -template<> void toJSON(std::ostream & str, const int & n) { str << n; } -template<> void toJSON(std::ostream & str, const unsigned int & n) { str << n; } -template<> void toJSON(std::ostream & str, const long & n) { str << n; } -template<> void toJSON(std::ostream & str, const unsigned long & n) { str << n; } -template<> void toJSON(std::ostream & str, const long long & n) { str << n; } -template<> void toJSON(std::ostream & str, const unsigned long long & n) { str << n; } -template<> void toJSON(std::ostream & str, const float & n) { str << n; } -template<> void toJSON(std::ostream & str, const double & n) { str << n; } -template<> void toJSON(std::ostream & str, const std::string & s) { toJSON(str, (std::string_view) s); } - -template<> void toJSON(std::ostream & str, const bool & b) -{ - str << (b ? "true" : "false"); -} - -template<> void toJSON(std::ostream & str, const std::nullptr_t & b) -{ - str << "null"; -} - -JSONWriter::JSONWriter(std::ostream & str, bool indent) - : state(new JSONState(str, indent)) -{ - state->stack++; -} - -JSONWriter::JSONWriter(JSONState * state) - : state(state) -{ - state->stack++; -} - -JSONWriter::~JSONWriter() -{ - if (state) { - assertActive(); - state->stack--; - if (state->stack == 0) delete state; - } -} - -void JSONWriter::comma() -{ - assertActive(); - if (first) { - first = false; - } else { - state->str << ','; - } - if (state->indent) indent(); -} - -void JSONWriter::indent() -{ - state->str << '\n' << std::string(state->depth * 2, ' '); -} - -void JSONList::open() -{ - state->depth++; - state->str << '['; -} - -JSONList::~JSONList() -{ - state->depth--; - if (state->indent && !first) indent(); - state->str << "]"; -} - -JSONList JSONList::list() -{ - comma(); - return JSONList(state); -} - -JSONObject JSONList::object() -{ - comma(); - return JSONObject(state); -} - -JSONPlaceholder JSONList::placeholder() -{ - comma(); - return JSONPlaceholder(state); -} - -void JSONObject::open() -{ - state->depth++; - state->str << '{'; -} - -JSONObject::~JSONObject() -{ - if (state) { - state->depth--; - if (state->indent && !first) indent(); - state->str << "}"; - } -} - -void JSONObject::attr(std::string_view s) -{ - comma(); - toJSON(state->str, s); - state->str << ':'; - if (state->indent) state->str << ' '; -} - -JSONList JSONObject::list(std::string_view name) -{ - attr(name); - return JSONList(state); -} - -JSONObject JSONObject::object(std::string_view name) -{ - attr(name); - return JSONObject(state); -} - -JSONPlaceholder JSONObject::placeholder(std::string_view name) -{ - attr(name); - return JSONPlaceholder(state); -} - -JSONList JSONPlaceholder::list() -{ - assertValid(); - first = false; - return JSONList(state); -} - -JSONObject JSONPlaceholder::object() -{ - assertValid(); - first = false; - return JSONObject(state); -} - -JSONPlaceholder::~JSONPlaceholder() -{ - if (first) { - assert(std::uncaught_exceptions()); - if (state->stack != 0) - write(nullptr); - } -} - -} diff --git a/src/libutil/json.hh b/src/libutil/json.hh deleted file mode 100644 index 3790b1a2e..000000000 --- a/src/libutil/json.hh +++ /dev/null @@ -1,185 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace nix { - -void toJSON(std::ostream & str, const char * s); - -template -void toJSON(std::ostream & str, const T & n); - -class JSONWriter -{ -protected: - - struct JSONState - { - std::ostream & str; - bool indent; - size_t depth = 0; - size_t stack = 0; - JSONState(std::ostream & str, bool indent) : str(str), indent(indent) { } - ~JSONState() - { - assert(stack == 0); - } - }; - - JSONState * state; - - bool first = true; - - JSONWriter(std::ostream & str, bool indent); - - JSONWriter(JSONState * state); - - ~JSONWriter(); - - void assertActive() - { - assert(state->stack != 0); - } - - void comma(); - - void indent(); -}; - -class JSONObject; -class JSONPlaceholder; - -class JSONList : JSONWriter -{ -private: - - friend class JSONObject; - friend class JSONPlaceholder; - - void open(); - - JSONList(JSONState * state) - : JSONWriter(state) - { - open(); - } - -public: - - JSONList(std::ostream & str, bool indent = false) - : JSONWriter(str, indent) - { - open(); - } - - ~JSONList(); - - template - JSONList & elem(const T & v) - { - comma(); - toJSON(state->str, v); - return *this; - } - - JSONList list(); - - JSONObject object(); - - JSONPlaceholder placeholder(); -}; - -class JSONObject : JSONWriter -{ -private: - - friend class JSONList; - friend class JSONPlaceholder; - - void open(); - - JSONObject(JSONState * state) - : JSONWriter(state) - { - open(); - } - - void attr(std::string_view s); - -public: - - JSONObject(std::ostream & str, bool indent = false) - : JSONWriter(str, indent) - { - open(); - } - - JSONObject(const JSONObject & obj) = delete; - - JSONObject(JSONObject && obj) - : JSONWriter(obj.state) - { - obj.state = 0; - } - - ~JSONObject(); - - template - JSONObject & attr(std::string_view name, const T & v) - { - attr(name); - toJSON(state->str, v); - return *this; - } - - JSONList list(std::string_view name); - - JSONObject object(std::string_view name); - - JSONPlaceholder placeholder(std::string_view name); -}; - -class JSONPlaceholder : JSONWriter -{ - -private: - - friend class JSONList; - friend class JSONObject; - - JSONPlaceholder(JSONState * state) - : JSONWriter(state) - { - } - - void assertValid() - { - assertActive(); - assert(first); - } - -public: - - JSONPlaceholder(std::ostream & str, bool indent = false) - : JSONWriter(str, indent) - { - } - - ~JSONPlaceholder(); - - template - void write(const T & v) - { - assertValid(); - first = false; - toJSON(state->str, v); - } - - JSONList list(); - - JSONObject object(); -}; - -} diff --git a/src/libutil/tests/json.cc b/src/libutil/tests/json.cc deleted file mode 100644 index 156286999..000000000 --- a/src/libutil/tests/json.cc +++ /dev/null @@ -1,193 +0,0 @@ -#include "json.hh" -#include -#include - -namespace nix { - - /* ---------------------------------------------------------------------------- - * toJSON - * --------------------------------------------------------------------------*/ - - TEST(toJSON, quotesCharPtr) { - const char* input = "test"; - std::stringstream out; - toJSON(out, input); - - ASSERT_EQ(out.str(), "\"test\""); - } - - TEST(toJSON, quotesStdString) { - std::string input = "test"; - std::stringstream out; - toJSON(out, input); - - ASSERT_EQ(out.str(), "\"test\""); - } - - TEST(toJSON, convertsNullptrtoNull) { - auto input = nullptr; - std::stringstream out; - toJSON(out, input); - - ASSERT_EQ(out.str(), "null"); - } - - TEST(toJSON, convertsNullToNull) { - const char* input = 0; - std::stringstream out; - toJSON(out, input); - - ASSERT_EQ(out.str(), "null"); - } - - - TEST(toJSON, convertsFloat) { - auto input = 1.024f; - std::stringstream out; - toJSON(out, input); - - ASSERT_EQ(out.str(), "1.024"); - } - - TEST(toJSON, convertsDouble) { - const double input = 1.024; - std::stringstream out; - toJSON(out, input); - - ASSERT_EQ(out.str(), "1.024"); - } - - TEST(toJSON, convertsBool) { - auto input = false; - std::stringstream out; - toJSON(out, input); - - ASSERT_EQ(out.str(), "false"); - } - - TEST(toJSON, quotesTab) { - std::stringstream out; - toJSON(out, "\t"); - - ASSERT_EQ(out.str(), "\"\\t\""); - } - - TEST(toJSON, quotesNewline) { - std::stringstream out; - toJSON(out, "\n"); - - ASSERT_EQ(out.str(), "\"\\n\""); - } - - TEST(toJSON, quotesCreturn) { - std::stringstream out; - toJSON(out, "\r"); - - ASSERT_EQ(out.str(), "\"\\r\""); - } - - TEST(toJSON, quotesCreturnNewLine) { - std::stringstream out; - toJSON(out, "\r\n"); - - ASSERT_EQ(out.str(), "\"\\r\\n\""); - } - - TEST(toJSON, quotesDoublequotes) { - std::stringstream out; - toJSON(out, "\""); - - ASSERT_EQ(out.str(), "\"\\\"\""); - } - - TEST(toJSON, substringEscape) { - std::stringstream out; - std::string_view s = "foo\t"; - toJSON(out, s.substr(3)); - - ASSERT_EQ(out.str(), "\"\\t\""); - } - - /* ---------------------------------------------------------------------------- - * JSONObject - * --------------------------------------------------------------------------*/ - - TEST(JSONObject, emptyObject) { - std::stringstream out; - { - JSONObject t(out); - } - ASSERT_EQ(out.str(), "{}"); - } - - TEST(JSONObject, objectWithList) { - std::stringstream out; - { - JSONObject t(out); - auto l = t.list("list"); - l.elem("element"); - } - ASSERT_EQ(out.str(), R"#({"list":["element"]})#"); - } - - TEST(JSONObject, objectWithListIndent) { - std::stringstream out; - { - JSONObject t(out, true); - auto l = t.list("list"); - l.elem("element"); - } - ASSERT_EQ(out.str(), -R"#({ - "list": [ - "element" - ] -})#"); - } - - TEST(JSONObject, objectWithPlaceholderAndList) { - std::stringstream out; - { - JSONObject t(out); - auto l = t.placeholder("list"); - l.list().elem("element"); - } - - ASSERT_EQ(out.str(), R"#({"list":["element"]})#"); - } - - TEST(JSONObject, objectWithPlaceholderAndObject) { - std::stringstream out; - { - JSONObject t(out); - auto l = t.placeholder("object"); - l.object().attr("key", "value"); - } - - ASSERT_EQ(out.str(), R"#({"object":{"key":"value"}})#"); - } - - /* ---------------------------------------------------------------------------- - * JSONList - * --------------------------------------------------------------------------*/ - - TEST(JSONList, empty) { - std::stringstream out; - { - JSONList l(out); - } - ASSERT_EQ(out.str(), R"#([])#"); - } - - TEST(JSONList, withElements) { - std::stringstream out; - { - JSONList l(out); - l.elem("one"); - l.object(); - l.placeholder().write("three"); - } - ASSERT_EQ(out.str(), R"#(["one",{},"three"])#"); - } -} - diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index fdd66220a..776c5f6db 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -12,7 +12,6 @@ #include "local-fs-store.hh" #include "user-env.hh" #include "util.hh" -#include "json.hh" #include "value-to-json.hh" #include "xml-writer.hh" #include "legacy.hh" @@ -26,6 +25,7 @@ #include #include #include +#include using namespace nix; using std::cout; @@ -911,43 +911,47 @@ static VersionDiff compareVersionAgainstSet( static void queryJSON(Globals & globals, std::vector & elems, bool printOutPath, bool printMeta) { - JSONObject topObj(cout, true); + using nlohmann::json; + json topObj = json::object(); for (auto & i : elems) { try { if (i.hasFailed()) continue; - JSONObject pkgObj = topObj.object(i.attrPath); auto drvName = DrvName(i.queryName()); - pkgObj.attr("name", drvName.fullName); - pkgObj.attr("pname", drvName.name); - pkgObj.attr("version", drvName.version); - pkgObj.attr("system", i.querySystem()); - pkgObj.attr("outputName", i.queryOutputName()); + json &pkgObj = topObj[i.attrPath]; + pkgObj = { + {"name", drvName.fullName}, + {"pname", drvName.name}, + {"version", drvName.version}, + {"system", i.querySystem()}, + {"outputName", i.queryOutputName()}, + }; { DrvInfo::Outputs outputs = i.queryOutputs(printOutPath); - JSONObject outputObj = pkgObj.object("outputs"); + json &outputObj = pkgObj["outputs"]; + outputObj = json::object(); for (auto & j : outputs) { if (j.second) - outputObj.attr(j.first, globals.state->store->printStorePath(*j.second)); + outputObj[j.first] = globals.state->store->printStorePath(*j.second); else - outputObj.attr(j.first, nullptr); + outputObj[j.first] = nullptr; } } if (printMeta) { - JSONObject metaObj = pkgObj.object("meta"); + json &metaObj = pkgObj["meta"]; + metaObj = json::object(); StringSet metaNames = i.queryMetaNames(); for (auto & j : metaNames) { Value * v = i.queryMeta(j); if (!v) { printError("derivation '%s' has invalid meta attribute '%s'", i.queryName(), j); - metaObj.attr(j, nullptr); + metaObj[j] = nullptr; } else { - auto placeholder = metaObj.placeholder(j); PathSet context; - printValueAsJSON(*globals.state, true, *v, noPos, placeholder, context); + metaObj[j] = printValueAsJSON(*globals.state, true, *v, noPos, context); } } } @@ -958,6 +962,7 @@ static void queryJSON(Globals & globals, std::vector & elems, bool prin throw; } } + std::cout << topObj.dump(2); } diff --git a/src/nix/eval.cc b/src/nix/eval.cc index ddd2790c6..ba82b5772 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -4,10 +4,11 @@ #include "store-api.hh" #include "eval.hh" #include "eval-inline.hh" -#include "json.hh" #include "value-to-json.hh" #include "progress-bar.hh" +#include + using namespace nix; struct CmdEval : MixJSON, InstallableCommand @@ -115,9 +116,7 @@ struct CmdEval : MixJSON, InstallableCommand } else if (json) { - JSONPlaceholder jsonOut(std::cout); - printValueAsJSON(*state, true, *v, pos, jsonOut, context, false); - std::cout << std::endl; + std::cout << printValueAsJSON(*state, true, *v, pos, context, false).dump() << std::endl; } else { diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 3967f1102..336f6723a 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -11,7 +11,6 @@ #include "attr-path.hh" #include "fetchers.hh" #include "registry.hh" -#include "json.hh" #include "eval-cache.hh" #include "markdown.hh" @@ -21,6 +20,7 @@ using namespace nix; using namespace nix::flake; +using json = nlohmann::json; class FlakeCommand : virtual Args, public MixFlakeOptions { @@ -917,35 +917,44 @@ struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun { auto flake = lockFlake(); - auto jsonRoot = json ? std::optional(std::cout) : std::nullopt; - StorePathSet sources; sources.insert(flake.flake.sourceInfo->storePath); - if (jsonRoot) - jsonRoot->attr("path", store->printStorePath(flake.flake.sourceInfo->storePath)); // FIXME: use graph output, handle cycles. - std::function & jsonObj)> traverse; - traverse = [&](const Node & node, std::optional & jsonObj) + std::function traverse; + traverse = [&](const Node & node) { - auto jsonObj2 = jsonObj ? jsonObj->object("inputs") : std::optional(); + nlohmann::json jsonObj2 = json ? json::object() : nlohmann::json(nullptr); for (auto & [inputName, input] : node.inputs) { if (auto inputNode = std::get_if<0>(&input)) { - auto jsonObj3 = jsonObj2 ? jsonObj2->object(inputName) : std::optional(); auto storePath = dryRun ? (*inputNode)->lockedRef.input.computeStorePath(*store) : (*inputNode)->lockedRef.input.fetch(store).first.storePath; - if (jsonObj3) - jsonObj3->attr("path", store->printStorePath(storePath)); - sources.insert(std::move(storePath)); - traverse(**inputNode, jsonObj3); + if (json) { + auto& jsonObj3 = jsonObj2[inputName]; + jsonObj3["path"] = store->printStorePath(storePath); + sources.insert(std::move(storePath)); + jsonObj3["inputs"] = traverse(**inputNode); + } else { + sources.insert(std::move(storePath)); + traverse(**inputNode); + } } } + return jsonObj2; }; - traverse(*flake.lockFile.root, jsonRoot); + if (json) { + nlohmann::json jsonRoot = { + {"path", store->printStorePath(flake.flake.sourceInfo->storePath)}, + {"inputs", traverse(*flake.lockFile.root)}, + }; + std::cout << jsonRoot.dump() << std::endl; + } else { + traverse(*flake.lockFile.root); + } if (!dryRun && !dstUri.empty()) { ref dstStore = dstUri.empty() ? openStore() : openStore(dstUri); diff --git a/src/nix/ls.cc b/src/nix/ls.cc index 07554994b..e964b01b3 100644 --- a/src/nix/ls.cc +++ b/src/nix/ls.cc @@ -3,7 +3,7 @@ #include "fs-accessor.hh" #include "nar-accessor.hh" #include "common-args.hh" -#include "json.hh" +#include using namespace nix; @@ -91,10 +91,9 @@ struct MixLs : virtual Args, MixJSON if (path == "/") path = ""; if (json) { - JSONPlaceholder jsonRoot(std::cout); if (showDirectory) throw UsageError("'--directory' is useless with '--json'"); - listNar(jsonRoot, accessor, path, recursive); + std::cout << listNar(accessor, path, recursive); } else listText(accessor); } diff --git a/src/nix/make-content-addressed.cc b/src/nix/make-content-addressed.cc index 34860c38f..f2e4cefbe 100644 --- a/src/nix/make-content-addressed.cc +++ b/src/nix/make-content-addressed.cc @@ -2,10 +2,13 @@ #include "store-api.hh" #include "make-content-addressed.hh" #include "common-args.hh" -#include "json.hh" + +#include using namespace nix; +using nlohmann::json; + struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand, MixJSON { CmdMakeContentAddressed() @@ -25,6 +28,7 @@ struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand, ; } + using StorePathsCommand::run; void run(ref srcStore, StorePaths && storePaths) override { auto dstStore = dstUri.empty() ? openStore() : openStore(dstUri); @@ -33,13 +37,13 @@ struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand, StorePathSet(storePaths.begin(), storePaths.end())); if (json) { - JSONObject jsonRoot(std::cout); - JSONObject jsonRewrites(jsonRoot.object("rewrites")); + nlohmann::json jsonRewrites = json::object(); for (auto & path : storePaths) { auto i = remappings.find(path); assert(i != remappings.end()); - jsonRewrites.attr(srcStore->printStorePath(path), srcStore->printStorePath(i->second)); + jsonRewrites[srcStore->printStorePath(path)] = srcStore->printStorePath(i->second); } + std::cout << json::object({"rewrites", jsonRewrites}).dump(); } else { for (auto & path : storePaths) { auto i = remappings.find(path); diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc index d690fe594..613c5b191 100644 --- a/src/nix/path-info.cc +++ b/src/nix/path-info.cc @@ -1,12 +1,13 @@ #include "command.hh" #include "shared.hh" #include "store-api.hh" -#include "json.hh" #include "common-args.hh" #include #include +#include + using namespace nix; struct CmdPathInfo : StorePathsCommand, MixJSON @@ -86,11 +87,10 @@ struct CmdPathInfo : StorePathsCommand, MixJSON pathLen = std::max(pathLen, store->printStorePath(storePath).size()); if (json) { - JSONPlaceholder jsonRoot(std::cout); - store->pathInfoToJSON(jsonRoot, + std::cout << store->pathInfoToJSON( // FIXME: preserve order? StorePathSet(storePaths.begin(), storePaths.end()), - true, showClosureSize, SRI, AllowInvalid); + true, showClosureSize, SRI, AllowInvalid).dump(); } else { diff --git a/src/nix/search.cc b/src/nix/search.cc index bdd45cbed..d2a31607d 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -5,7 +5,6 @@ #include "names.hh" #include "get-drvs.hh" #include "common-args.hh" -#include "json.hh" #include "shared.hh" #include "eval-cache.hh" #include "attr-path.hh" @@ -13,8 +12,10 @@ #include #include +#include using namespace nix; +using json = nlohmann::json; std::string wrap(std::string prefix, std::string s) { @@ -84,7 +85,8 @@ struct CmdSearch : InstallableCommand, MixJSON auto state = getEvalState(); - auto jsonOut = json ? std::make_unique(std::cout) : nullptr; + std::optional jsonOut; + if (json) jsonOut = json::object(); uint64_t results = 0; @@ -151,10 +153,11 @@ struct CmdSearch : InstallableCommand, MixJSON { results++; if (json) { - auto jsonElem = jsonOut->object(attrPath2); - jsonElem.attr("pname", name.name); - jsonElem.attr("version", name.version); - jsonElem.attr("description", description); + (*jsonOut)[attrPath2] = { + {"pname", name.name}, + {"version", name.version}, + {"description", description}, + }; } else { auto name2 = hiliteMatches(name.name, nameMatches, ANSI_GREEN, "\e[0;2m"); if (results > 1) logger->cout(""); @@ -193,6 +196,10 @@ struct CmdSearch : InstallableCommand, MixJSON for (auto & cursor : installable->getCursors(*state)) visit(*cursor, cursor->getAttrPath(), true); + if (json) { + std::cout << jsonOut->dump() << std::endl; + } + if (!json && !results) throw Error("no results for the given search term(s)!"); } diff --git a/src/nix/show-derivation.cc b/src/nix/show-derivation.cc index fb46b4dbf..af2e676a4 100644 --- a/src/nix/show-derivation.cc +++ b/src/nix/show-derivation.cc @@ -5,10 +5,11 @@ #include "common-args.hh" #include "store-api.hh" #include "archive.hh" -#include "json.hh" #include "derivations.hh" +#include using namespace nix; +using json = nlohmann::json; struct CmdShowDerivation : InstallablesCommand { @@ -48,77 +49,63 @@ struct CmdShowDerivation : InstallablesCommand drvPaths = std::move(closure); } - { - - JSONObject jsonRoot(std::cout, true); + json jsonRoot = json::object(); for (auto & drvPath : drvPaths) { if (!drvPath.isDerivation()) continue; - auto drvObj(jsonRoot.object(store->printStorePath(drvPath))); + json& drvObj = jsonRoot[store->printStorePath(drvPath)]; auto drv = store->readDerivation(drvPath); { - auto outputsObj(drvObj.object("outputs")); + json& outputsObj = drvObj["outputs"]; + outputsObj = json::object(); for (auto & [_outputName, output] : drv.outputs) { auto & outputName = _outputName; // work around clang bug - auto outputObj { outputsObj.object(outputName) }; + auto& outputObj = outputsObj[outputName]; + outputObj = json::object(); std::visit(overloaded { [&](const DerivationOutput::InputAddressed & doi) { - outputObj.attr("path", store->printStorePath(doi.path)); + outputObj["path"] = store->printStorePath(doi.path); }, [&](const DerivationOutput::CAFixed & dof) { - outputObj.attr("path", store->printStorePath(dof.path(*store, drv.name, outputName))); - outputObj.attr("hashAlgo", dof.hash.printMethodAlgo()); - outputObj.attr("hash", dof.hash.hash.to_string(Base16, false)); + outputObj["path"] = store->printStorePath(dof.path(*store, drv.name, outputName)); + outputObj["hashAlgo"] = dof.hash.printMethodAlgo(); + outputObj["hash"] = dof.hash.hash.to_string(Base16, false); }, [&](const DerivationOutput::CAFloating & dof) { - outputObj.attr("hashAlgo", makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType)); + outputObj["hashAlgo"] = makeFileIngestionPrefix(dof.method) + printHashType(dof.hashType); }, [&](const DerivationOutput::Deferred &) {}, [&](const DerivationOutput::Impure & doi) { - outputObj.attr("hashAlgo", makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType)); - outputObj.attr("impure", true); + outputObj["hashAlgo"] = makeFileIngestionPrefix(doi.method) + printHashType(doi.hashType); + outputObj["impure"] = true; }, }, output.raw()); } } { - auto inputsList(drvObj.list("inputSrcs")); + auto& inputsList = drvObj["inputSrcs"]; + inputsList = json::array(); for (auto & input : drv.inputSrcs) - inputsList.elem(store->printStorePath(input)); + inputsList.emplace_back(store->printStorePath(input)); } { - auto inputDrvsObj(drvObj.object("inputDrvs")); - for (auto & input : drv.inputDrvs) { - auto inputList(inputDrvsObj.list(store->printStorePath(input.first))); - for (auto & outputId : input.second) - inputList.elem(outputId); - } + auto& inputDrvsObj = drvObj["inputDrvs"]; + inputDrvsObj = json::object(); + for (auto & input : drv.inputDrvs) + inputDrvsObj[store->printStorePath(input.first)] = input.second; } - drvObj.attr("system", drv.platform); - drvObj.attr("builder", drv.builder); - - { - auto argsList(drvObj.list("args")); - for (auto & arg : drv.args) - argsList.elem(arg); - } - - { - auto envObj(drvObj.object("env")); - for (auto & var : drv.env) - envObj.attr(var.first, var.second); - } + drvObj["system"] = drv.platform; + drvObj["builder"] = drv.builder; + drvObj["args"] = drv.args; + drvObj["env"] = drv.env; } - - } - - std::cout << "\n"; + std::cout << jsonRoot.dump(2) << std::endl; } }; From f423d4425f6573206045e9626812002906d9493d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 17 Nov 2022 11:56:45 +0100 Subject: [PATCH 098/245] Fix segfault in unprivileged mode --- src/libstore/build/local-derivation-goal.hh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.hh b/src/libstore/build/local-derivation-goal.hh index 070ae53f3..f92280aa1 100644 --- a/src/libstore/build/local-derivation-goal.hh +++ b/src/libstore/build/local-derivation-goal.hh @@ -92,8 +92,8 @@ struct LocalDerivationGoal : public DerivationGoal result. */ std::map prevInfos; - uid_t sandboxUid() { return usingUserNamespace ? (buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID(); } - gid_t sandboxGid() { return usingUserNamespace ? (buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID(); } + uid_t sandboxUid() { return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 1000 : 0) : buildUser->getUID(); } + gid_t sandboxGid() { return usingUserNamespace ? (!buildUser || buildUser->getUIDCount() == 1 ? 100 : 0) : buildUser->getGID(); } const static Path homeDir; From f1ab082ac4f589a36a9eb0cd98d1cc235eedc419 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 18 Nov 2022 09:37:11 +0100 Subject: [PATCH 099/245] createTempDir(): Use std::atomic --- src/libutil/filesystem.cc | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/libutil/filesystem.cc b/src/libutil/filesystem.cc index 403389e60..3a732cff8 100644 --- a/src/libutil/filesystem.cc +++ b/src/libutil/filesystem.cc @@ -1,5 +1,6 @@ #include #include +#include #include "finally.hh" #include "util.hh" @@ -10,7 +11,7 @@ namespace fs = std::filesystem; namespace nix { static Path tempName(Path tmpRoot, const Path & prefix, bool includePid, - int & counter) + std::atomic & counter) { tmpRoot = canonPath(tmpRoot.empty() ? getEnv("TMPDIR").value_or("/tmp") : tmpRoot, true); if (includePid) @@ -22,9 +23,9 @@ static Path tempName(Path tmpRoot, const Path & prefix, bool includePid, Path createTempDir(const Path & tmpRoot, const Path & prefix, bool includePid, bool useGlobalCounter, mode_t mode) { - static int globalCounter = 0; - int localCounter = 0; - int & counter(useGlobalCounter ? globalCounter : localCounter); + static std::atomic globalCounter = 0; + std::atomic localCounter = 0; + auto & counter(useGlobalCounter ? globalCounter : localCounter); while (1) { checkInterrupt(); From 128910ba23f586ba1765a137ecff23cfd22cff89 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 18 Nov 2022 10:39:28 +0100 Subject: [PATCH 100/245] Separate cgroup support from auto-uid-allocation The new experimental feature 'cgroups' enables the use of cgroups for all builds. This allows better containment and enables setting resource limits and getting some build stats. --- src/libstore/build/local-derivation-goal.cc | 114 ++++++++++++++------ src/libstore/build/local-derivation-goal.hh | 7 ++ src/libstore/lock.cc | 71 ------------ src/libstore/lock.hh | 7 -- src/libutil/experimental-features.cc | 1 + src/libutil/experimental-features.hh | 1 + 6 files changed, 93 insertions(+), 108 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index e652c425c..2d1e093ca 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -15,6 +15,7 @@ #include "topo-sort.hh" #include "callback.hh" #include "json-utils.hh" +#include "cgroup.hh" #include #include @@ -129,26 +130,36 @@ void LocalDerivationGoal::killChild() if (pid != -1) { worker.childTerminated(this); - if (buildUser) { - /* If we're using a build user, then there is a tricky - race condition: if we kill the build user before the - child has done its setuid() to the build user uid, then - it won't be killed, and we'll potentially lock up in - pid.wait(). So also send a conventional kill to the - child. */ - ::kill(-pid, SIGKILL); /* ignore the result */ - buildUser->kill(); - pid.wait(); - } else - pid.kill(); + /* If we're using a build user, then there is a tricky race + condition: if we kill the build user before the child has + done its setuid() to the build user uid, then it won't be + killed, and we'll potentially lock up in pid.wait(). So + also send a conventional kill to the child. */ + ::kill(-pid, SIGKILL); /* ignore the result */ - assert(pid == -1); + killSandbox(); + + pid.wait(); } DerivationGoal::killChild(); } +void LocalDerivationGoal::killSandbox() +{ + if (cgroup) { + destroyCgroup(*cgroup); + } + + else if (buildUser) { + auto uid = buildUser->getUID(); + assert(uid != 0); + killUser(uid); + } +} + + void LocalDerivationGoal::tryLocalBuild() { unsigned int curBuilds = worker.getNrLocalBuilds(); if (curBuilds >= settings.maxBuildJobs) { @@ -169,10 +180,6 @@ void LocalDerivationGoal::tryLocalBuild() { worker.waitForAWhile(shared_from_this()); return; } - - /* Make sure that no other processes are executing under this - uid. */ - buildUser->kill(); } actLock.reset(); @@ -263,7 +270,7 @@ void LocalDerivationGoal::cleanupPostChildKill() malicious user from leaving behind a process that keeps files open and modifies them after they have been chown'ed to root. */ - if (buildUser) buildUser->kill(); + killSandbox(); /* Terminate the recursive Nix daemon. */ stopDaemon(); @@ -356,6 +363,55 @@ static void linkOrCopy(const Path & from, const Path & to) void LocalDerivationGoal::startBuilder() { + if ((buildUser && buildUser->getUIDCount() != 1) + || settings.isExperimentalFeatureEnabled(Xp::Cgroups)) + { + #if __linux__ + auto ourCgroups = getCgroups("/proc/self/cgroup"); + auto ourCgroup = ourCgroups[""]; + if (ourCgroup == "") + throw Error("cannot determine cgroup name from /proc/self/cgroup"); + + auto ourCgroupPath = canonPath("/sys/fs/cgroup/" + ourCgroup); + + if (!pathExists(ourCgroupPath)) + throw Error("expected cgroup directory '%s'", ourCgroupPath); + + static std::atomic counter{0}; + + cgroup = buildUser + ? fmt("%s/nix-build-uid-%d", ourCgroupPath, buildUser->getUID()) + : fmt("%s/nix-build-pid-%d-%d", ourCgroupPath, getpid(), counter++); + + debug("using cgroup '%s'", *cgroup); + + /* When using a build user, record the cgroup we used for that + user so that if we got interrupted previously, we can kill + any left-over cgroup first. */ + if (buildUser) { + auto cgroupsDir = settings.nixStateDir + "/cgroups"; + createDirs(cgroupsDir); + + auto cgroupFile = fmt("%s/%d", cgroupsDir, buildUser->getUID()); + + if (pathExists(cgroupFile)) { + auto prevCgroup = readFile(cgroupFile); + destroyCgroup(prevCgroup); + } + + writeFile(cgroupFile, *cgroup); + } + + #else + throw Error("cgroups are not supported on this platform"); + #endif + } + + /* Make sure that no other processes are executing under the + sandbox uids. This must be done before any chownToBuilder() + calls. */ + killSandbox(); + /* Right platform? */ if (!parsedDrv->canBuildLocally(worker.store)) throw Error("a '%s' with features {%s} is required to build '%s', but I am a '%s' with features {%s}", @@ -646,13 +702,13 @@ void LocalDerivationGoal::startBuilder() dirsInChroot.erase(worker.store.printStorePath(*i.second.second)); } - if (buildUser) { - if (auto cgroup = buildUser->getCgroup()) { - chownToBuilder(*cgroup); - chownToBuilder(*cgroup + "/cgroup.procs"); - chownToBuilder(*cgroup + "/cgroup.threads"); - //chownToBuilder(*cgroup + "/cgroup.subtree_control"); - } + if (cgroup) { + if (mkdir(cgroup->c_str(), 0755) != 0) + throw SysError("creating cgroup '%s'", *cgroup); + chownToBuilder(*cgroup); + chownToBuilder(*cgroup + "/cgroup.procs"); + chownToBuilder(*cgroup + "/cgroup.threads"); + //chownToBuilder(*cgroup + "/cgroup.subtree_control"); } #else @@ -965,10 +1021,8 @@ void LocalDerivationGoal::startBuilder() } /* Move the child into its own cgroup. */ - if (buildUser) { - if (auto cgroup = buildUser->getCgroup()) - writeFile(*cgroup + "/cgroup.procs", fmt("%d", (pid_t) pid)); - } + if (cgroup) + writeFile(*cgroup + "/cgroup.procs", fmt("%d", (pid_t) pid)); /* Signal the builder that we've updated its user namespace. */ writeFull(userNamespaceSync.writeSide.get(), "1"); @@ -1838,7 +1892,7 @@ void LocalDerivationGoal::runChild() /* Unshare the cgroup namespace. This means /proc/self/cgroup will show the child's cgroup as '/' rather than whatever it is in the parent. */ - if (buildUser && buildUser->getUIDCount() != 1 && unshare(CLONE_NEWCGROUP) == -1) + if (cgroup && unshare(CLONE_NEWCGROUP) == -1) throw SysError("unsharing cgroup namespace"); /* Do the chroot(). */ diff --git a/src/libstore/build/local-derivation-goal.hh b/src/libstore/build/local-derivation-goal.hh index f92280aa1..1ec6b3649 100644 --- a/src/libstore/build/local-derivation-goal.hh +++ b/src/libstore/build/local-derivation-goal.hh @@ -15,6 +15,9 @@ struct LocalDerivationGoal : public DerivationGoal /* The process ID of the builder. */ Pid pid; + /* The cgroup of the builder, if any. */ + std::optional cgroup; + /* The temporary directory. */ Path tmpDir; @@ -197,6 +200,10 @@ struct LocalDerivationGoal : public DerivationGoal /* Forcibly kill the child process, if any. */ void killChild() override; + /* Kill any processes running under the build user UID or in the + cgroup of the build. */ + void killSandbox(); + /* Create alternative path calculated from but distinct from the input, so we can avoid overwriting outputs (or other store paths) that already exist. */ diff --git a/src/libstore/lock.cc b/src/libstore/lock.cc index f9892bb91..4fad3bfd2 100644 --- a/src/libstore/lock.cc +++ b/src/libstore/lock.cc @@ -1,7 +1,6 @@ #include "lock.hh" #include "globals.hh" #include "pathlocks.hh" -#include "cgroup.hh" #include #include @@ -15,11 +14,6 @@ struct SimpleUserLock : UserLock gid_t gid; std::vector supplementaryGIDs; - void kill() override - { - killUser(uid); - } - uid_t getUID() override { assert(uid); return uid; } uid_t getUIDCount() override { return 1; } gid_t getGID() override { assert(gid); return gid; } @@ -116,32 +110,6 @@ struct AutoUserLock : UserLock AutoCloseFD fdUserLock; uid_t firstUid = 0; uid_t nrIds = 1; - #if __linux__ - std::optional cgroup; - #endif - - ~AutoUserLock() - { - #if __linux__ - // Get rid of our cgroup, ignoring errors. - if (cgroup) rmdir(cgroup->c_str()); - #endif - } - - void kill() override - { - #if __linux__ - if (cgroup) { - destroyCgroup(*cgroup); - if (mkdir(cgroup->c_str(), 0755) == -1) - throw SysError("creating cgroup '%s'", *cgroup); - } else - #endif - { - assert(firstUid); - killUser(firstUid); - } - } uid_t getUID() override { assert(firstUid); return firstUid; } @@ -183,55 +151,16 @@ struct AutoUserLock : UserLock throw SysError("opening user lock '%s'", fnUserLock); if (lockFile(fd.get(), ltWrite, false)) { - auto s = drainFD(fd.get()); - - #if __linux__ - if (s != "") { - /* Kill the old cgroup, to ensure there are no - processes left over from an interrupted build. */ - destroyCgroup(s); - } - #endif - - if (ftruncate(fd.get(), 0) == -1) - throw Error("truncating user lock"); - auto lock = std::make_unique(); lock->fdUserLock = std::move(fd); lock->firstUid = settings.startId + i * maxIdsPerBuild; lock->nrIds = nrIds; - - #if __linux__ - if (nrIds > 1) { - auto ourCgroups = getCgroups("/proc/self/cgroup"); - auto ourCgroup = ourCgroups[""]; - if (ourCgroup == "") - throw Error("cannot determine cgroup name from /proc/self/cgroup"); - - auto ourCgroupPath = canonPath("/sys/fs/cgroup/" + ourCgroup); - - if (!pathExists(ourCgroupPath)) - throw Error("expected cgroup directory '%s'", ourCgroupPath); - - lock->cgroup = fmt("%s/nix-build-%d", ourCgroupPath, lock->firstUid); - - /* Record the cgroup in the lock file. This ensures that - if we subsequently get executed under a different parent - cgroup, we kill the previous cgroup first. */ - writeFull(lock->fdUserLock.get(), *lock->cgroup); - } - #endif - return lock; } } return nullptr; } - - #if __linux__ - std::optional getCgroup() override { return cgroup; } - #endif }; std::unique_ptr acquireUserLock(uid_t nrIds) diff --git a/src/libstore/lock.hh b/src/libstore/lock.hh index b5536408c..e7ceefab8 100644 --- a/src/libstore/lock.hh +++ b/src/libstore/lock.hh @@ -27,13 +27,6 @@ struct UserLock virtual gid_t getGID() = 0; virtual std::vector getSupplementaryGIDs() = 0; - - /* Kill any processes currently executing as this user. */ - virtual void kill() = 0; - - #if __linux__ - virtual std::optional getCgroup() { return {}; }; - #endif }; /* Acquire a user lock for a UID range of size `nrIds`. Note that this diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index 0f05f3752..e0902971e 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -15,6 +15,7 @@ std::map stringifiedXpFeatures = { { Xp::FetchClosure, "fetch-closure" }, { Xp::ReplFlake, "repl-flake" }, { Xp::AutoAllocateUids, "auto-allocate-uids" }, + { Xp::Cgroups, "cgroups" }, }; const std::optional parseExperimentalFeature(const std::string_view & name) diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index cf0c06eac..af775feb0 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -24,6 +24,7 @@ enum struct ExperimentalFeature FetchClosure, ReplFlake, AutoAllocateUids, + Cgroups, }; /** From 20f66c6889aa9d907feee4946702d655b6bd796f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 18 Nov 2022 13:40:48 +0100 Subject: [PATCH 101/245] Indentation --- src/libcmd/installables.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index e097f23b3..5945a2578 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -927,7 +927,7 @@ std::vector, BuiltPath>> Installable::bui case Realise::Outputs: { if (settings.printMissing) - printMissing(store, pathsToBuild, lvlInfo); + printMissing(store, pathsToBuild, lvlInfo); for (auto & buildResult : store->buildPathsWithResults(pathsToBuild, bMode, evalStore)) { if (!buildResult.success()) From fa68eb367e79297bb1c0451cd92ad18a06edce96 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 18 Nov 2022 13:40:59 +0100 Subject: [PATCH 102/245] Get CPU stats from the cgroup --- src/libstore/build-result.hh | 5 ++- src/libstore/build/derivation-goal.cc | 8 +++++ src/libstore/build/local-derivation-goal.cc | 14 +++++--- src/libstore/build/local-derivation-goal.hh | 2 +- src/libstore/cgroup.cc | 39 +++++++++++++++++++-- src/libstore/cgroup.hh | 14 +++++++- 6 files changed, 71 insertions(+), 11 deletions(-) diff --git a/src/libstore/build-result.hh b/src/libstore/build-result.hh index 24fb1f763..a5749cf33 100644 --- a/src/libstore/build-result.hh +++ b/src/libstore/build-result.hh @@ -5,7 +5,7 @@ #include #include - +#include namespace nix { @@ -78,6 +78,9 @@ struct BuildResult was repeated). */ time_t startTime = 0, stopTime = 0; + /* User and system CPU time the build took. */ + std::optional cpuUser, cpuSystem; + bool success() { return status == Built || status == Substituted || status == AlreadyValid || status == ResolvesToAlreadyValid; diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 41d2e2a1c..9bc3dc742 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -869,6 +869,14 @@ void DerivationGoal::buildDone() cleanupPostChildKill(); + if (buildResult.cpuUser && buildResult.cpuSystem) { + debug("builder for '%s' terminated with status %d, user CPU %.3fs, system CPU %.3fs", + worker.store.printStorePath(drvPath), + status, + ((double) buildResult.cpuUser->count()) / 1000000, + ((double) buildResult.cpuSystem->count()) / 1000000); + } + bool diskFull = false; try { diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 2d1e093ca..f273ebe8a 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -137,7 +137,7 @@ void LocalDerivationGoal::killChild() also send a conventional kill to the child. */ ::kill(-pid, SIGKILL); /* ignore the result */ - killSandbox(); + killSandbox(true); pid.wait(); } @@ -146,10 +146,14 @@ void LocalDerivationGoal::killChild() } -void LocalDerivationGoal::killSandbox() +void LocalDerivationGoal::killSandbox(bool getStats) { if (cgroup) { - destroyCgroup(*cgroup); + auto stats = destroyCgroup(*cgroup); + if (getStats) { + buildResult.cpuUser = stats.cpuUser; + buildResult.cpuSystem = stats.cpuSystem; + } } else if (buildUser) { @@ -270,7 +274,7 @@ void LocalDerivationGoal::cleanupPostChildKill() malicious user from leaving behind a process that keeps files open and modifies them after they have been chown'ed to root. */ - killSandbox(); + killSandbox(true); /* Terminate the recursive Nix daemon. */ stopDaemon(); @@ -410,7 +414,7 @@ void LocalDerivationGoal::startBuilder() /* Make sure that no other processes are executing under the sandbox uids. This must be done before any chownToBuilder() calls. */ - killSandbox(); + killSandbox(false); /* Right platform? */ if (!parsedDrv->canBuildLocally(worker.store)) diff --git a/src/libstore/build/local-derivation-goal.hh b/src/libstore/build/local-derivation-goal.hh index 1ec6b3649..34c4e9187 100644 --- a/src/libstore/build/local-derivation-goal.hh +++ b/src/libstore/build/local-derivation-goal.hh @@ -202,7 +202,7 @@ struct LocalDerivationGoal : public DerivationGoal /* Kill any processes running under the build user UID or in the cgroup of the build. */ - void killSandbox(); + void killSandbox(bool getStats); /* Create alternative path calculated from but distinct from the input, so we can avoid overwriting outputs (or other store paths) diff --git a/src/libstore/cgroup.cc b/src/libstore/cgroup.cc index 56e980be3..2a485f0f9 100644 --- a/src/libstore/cgroup.cc +++ b/src/libstore/cgroup.cc @@ -31,13 +31,16 @@ std::map getCgroups(const Path & cgroupFile) return cgroups; } -void destroyCgroup(const Path & cgroup) +static CgroupStats destroyCgroup(const Path & cgroup, bool returnStats) { - if (!pathExists(cgroup)) return; + if (!pathExists(cgroup)) return {}; + + if (!pathExists(cgroup + "/cgroup.procs")) + throw Error("'%s' is not a cgroup", cgroup); for (auto & entry : readDirectory(cgroup)) { if (entry.type != DT_DIR) continue; - destroyCgroup(cgroup + "/" + entry.name); + destroyCgroup(cgroup + "/" + entry.name, false); } int round = 1; @@ -79,8 +82,38 @@ void destroyCgroup(const Path & cgroup) round++; } + CgroupStats stats; + + if (returnStats) { + auto cpustatPath = cgroup + "/cpu.stat"; + + if (pathExists(cpustatPath)) { + for (auto & line : tokenizeString>(readFile(cpustatPath), "\n")) { + std::string_view userPrefix = "user_usec "; + if (hasPrefix(line, userPrefix)) { + auto n = string2Int(line.substr(userPrefix.size())); + if (n) stats.cpuUser = std::chrono::microseconds(*n); + } + + std::string_view systemPrefix = "system_usec "; + if (hasPrefix(line, systemPrefix)) { + auto n = string2Int(line.substr(systemPrefix.size())); + if (n) stats.cpuSystem = std::chrono::microseconds(*n); + } + } + } + + } + if (rmdir(cgroup.c_str()) == -1) throw SysError("deleting cgroup '%s'", cgroup); + + return stats; +} + +CgroupStats destroyCgroup(const Path & cgroup) +{ + return destroyCgroup(cgroup, true); } } diff --git a/src/libstore/cgroup.hh b/src/libstore/cgroup.hh index dc6758957..3ead4735f 100644 --- a/src/libstore/cgroup.hh +++ b/src/libstore/cgroup.hh @@ -2,13 +2,25 @@ #if __linux__ +#include +#include + #include "types.hh" namespace nix { std::map getCgroups(const Path & cgroupFile); -void destroyCgroup(const Path & cgroup); +struct CgroupStats +{ + std::optional cpuUser, cpuSystem; +}; + +/* Destroy the cgroup denoted by 'path'. The postcondition is that + 'path' does not exist, and thus any processes in the cgroup have + been killed. Also return statistics from the cgroup just before + destruction. */ +CgroupStats destroyCgroup(const Path & cgroup); } From e6b71f84a0a766429fdceaf188ea0167e36a20d9 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 18 Nov 2022 16:59:36 +0100 Subject: [PATCH 103/245] Use cgroup.kill to quickly kill cgroups --- src/libstore/cgroup.cc | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/libstore/cgroup.cc b/src/libstore/cgroup.cc index 2a485f0f9..f693d77be 100644 --- a/src/libstore/cgroup.cc +++ b/src/libstore/cgroup.cc @@ -35,9 +35,19 @@ static CgroupStats destroyCgroup(const Path & cgroup, bool returnStats) { if (!pathExists(cgroup)) return {}; - if (!pathExists(cgroup + "/cgroup.procs")) + auto procsFile = cgroup + "/cgroup.procs"; + + if (!pathExists(procsFile)) throw Error("'%s' is not a cgroup", cgroup); + /* Use the fast way to kill every process in a cgroup, if + available. */ + auto killFile = cgroup + "/cgroup.kill"; + if (pathExists(killFile)) + writeFile(killFile, "1"); + + /* Otherwise, manually kill every process in the subcgroups and + this cgroup. */ for (auto & entry : readDirectory(cgroup)) { if (entry.type != DT_DIR) continue; destroyCgroup(cgroup + "/" + entry.name, false); @@ -48,7 +58,7 @@ static CgroupStats destroyCgroup(const Path & cgroup, bool returnStats) std::unordered_set pidsShown; while (true) { - auto pids = tokenizeString>(readFile(cgroup + "/cgroup.procs")); + auto pids = tokenizeString>(readFile(procsFile)); if (pids.empty()) break; From cc620d961f1fa68038ef44f5947fc053708ab3ad Mon Sep 17 00:00:00 2001 From: Liu Xiaoyi Date: Sun, 20 Nov 2022 20:04:22 +0800 Subject: [PATCH 104/245] Updated uninstall guide involving systemd Co-authored-by: sequencer --- .../src/installation/installing-binary.md | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index 31faeadc2..1b7db59cd 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -88,6 +88,28 @@ extension. The installer will also create `/etc/profile.d/nix.sh`. ### Linux +To begin, if you are on Linux with systemd, remove the Nix daemon service: + +```console +sudo systemctl stop nix-daemon.socket +sudo systemctl stop nix-daemon.service +sudo systemctl disable nix-daemon.socket +sudo systemctl disable nix-daemon.service +sudo systemctl daemon-reload +``` + +Then you can remove systemd service files: + +```console +sudo rm -f /etc/systemd/system/nix-daemon.service /etc/systemd/system/nix-daemon.socket +``` + +Also, the installer script uses systemd-tmpfiles (if presents) to create the socket directory. You may also want to remove the configuration for that: + +```console +sudo rm -f /etc/tmpfiles.d/nix-daemon.conf +``` + Remove files created by Nix: ```console @@ -103,16 +125,6 @@ done sudo groupdel 30000 ``` -If you are on Linux with systemd, remove the Nix daemon service: - -```console -sudo systemctl stop nix-daemon.socket -sudo systemctl stop nix-daemon.service -sudo systemctl disable nix-daemon.socket -sudo systemctl disable nix-daemon.service -sudo systemctl daemon-reload -``` - There may also be references to Nix in - `/etc/profile` From 4d55acf5154639cd1a54435ace28cd497b5a9eb8 Mon Sep 17 00:00:00 2001 From: Liu Xiaoyi Date: Mon, 21 Nov 2022 13:46:22 +0800 Subject: [PATCH 105/245] Apply suggestions from code review Co-authored-by: Sandro --- doc/manual/src/installation/installing-binary.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index 1b7db59cd..8cdd64df6 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -91,23 +91,21 @@ extension. The installer will also create `/etc/profile.d/nix.sh`. To begin, if you are on Linux with systemd, remove the Nix daemon service: ```console -sudo systemctl stop nix-daemon.socket sudo systemctl stop nix-daemon.service -sudo systemctl disable nix-daemon.socket -sudo systemctl disable nix-daemon.service +sudo systemctl disable nix-daemon.socket nix-daemon.service sudo systemctl daemon-reload ``` Then you can remove systemd service files: ```console -sudo rm -f /etc/systemd/system/nix-daemon.service /etc/systemd/system/nix-daemon.socket +sudo rm /etc/systemd/system/nix-daemon.service /etc/systemd/system/nix-daemon.socket ``` Also, the installer script uses systemd-tmpfiles (if presents) to create the socket directory. You may also want to remove the configuration for that: ```console -sudo rm -f /etc/tmpfiles.d/nix-daemon.conf +sudo rm /etc/tmpfiles.d/nix-daemon.conf ``` Remove files created by Nix: From f538ee434285304cb61cf10bf13127f13bfced1b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Nov 2022 09:38:08 +0100 Subject: [PATCH 106/245] Rename derivedPathsWithHintsToJSON -> builtPathsToJSON --- src/libstore/derived-path.cc | 2 +- src/libstore/derived-path.hh | 2 +- src/nix/build.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 44587ae78..7fe9b9648 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -64,7 +64,7 @@ nlohmann::json stuffToJSON(const std::vector & ts, ref store) { return res; } -nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref store) +nlohmann::json builtPathsToJSON(const BuiltPaths & buildables, ref store) { return stuffToJSON(buildables, store); } nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref store) { return stuffToJSON(paths, store); } diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh index 24a0ae773..b2d0956b8 100644 --- a/src/libstore/derived-path.hh +++ b/src/libstore/derived-path.hh @@ -125,7 +125,7 @@ struct BuiltPath : _BuiltPathRaw { typedef std::vector DerivedPaths; typedef std::vector BuiltPaths; -nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref store); +nlohmann::json builtPathsToJSON(const BuiltPaths & buildables, ref store); nlohmann::json derivedPathsToJSON(const DerivedPaths & , ref store); } diff --git a/src/nix/build.cc b/src/nix/build.cc index 9c648d28e..2b91f8e0a 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -78,7 +78,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile Realise::Outputs, installables, buildMode); - if (json) logger->cout("%s", derivedPathsWithHintsToJSON(buildables, store).dump()); + if (json) logger->cout("%s", builtPathsToJSON(buildables, store).dump()); if (outLink != "") if (auto store2 = store.dynamic_pointer_cast()) From 300753d594fd7cd818d08f9c7a18a9ebc305bd95 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Nov 2022 10:49:01 +0100 Subject: [PATCH 107/245] nix build --json: Include build statistics Example: # nix build -L --extra-experimental-features cgroups --impure --expr 'with import {}; runCommand "foo" {} "dd if=/dev/urandom bs=1M count=1024 | md5sum; mkdir $out"' --json [ { "cpuSystem": 1.911431, "cpuUser": 1.214249, "drvPath": "/nix/store/xzdqz67xba18hljhycp0hwfigzrs2z69-foo.drv", "outputs": { "out": "/nix/store/rh9mc9l2gkpq8kn2sgzndr6ll7ffjh6l-foo" }, "startTime": 1669024076, "stopTime": 1669024079 } ] --- src/libcmd/installables.cc | 29 ++++++++++++++----------- src/libcmd/installables.hh | 11 ++++++++-- src/libstore/derived-path.cc | 23 ++++---------------- src/libstore/derived-path.hh | 3 --- src/nix/app.cc | 11 +++++++--- src/nix/build.cc | 42 ++++++++++++++++++++++++++++++++---- src/nix/profile.cc | 4 ++-- 7 files changed, 77 insertions(+), 46 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 5945a2578..e036b8836 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -840,20 +840,20 @@ std::shared_ptr SourceExprCommand::parseInstallable( return installables.front(); } -BuiltPaths Installable::build( +std::vector Installable::build( ref evalStore, ref store, Realise mode, const std::vector> & installables, BuildMode bMode) { - BuiltPaths res; - for (auto & [_, builtPath] : build2(evalStore, store, mode, installables, bMode)) - res.push_back(builtPath); + std::vector res; + for (auto & [_, builtPathWithResult] : build2(evalStore, store, mode, installables, bMode)) + res.push_back(builtPathWithResult); return res; } -std::vector, BuiltPath>> Installable::build2( +std::vector, BuiltPathWithResult>> Installable::build2( ref evalStore, ref store, Realise mode, @@ -873,7 +873,7 @@ std::vector, BuiltPath>> Installable::bui } } - std::vector, BuiltPath>> res; + std::vector, BuiltPathWithResult>> res; switch (mode) { @@ -914,10 +914,10 @@ std::vector, BuiltPath>> Installable::bui output, *drvOutput->second); } } - res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }}); + res.push_back({installable, {.path = BuiltPath::Built { bfd.drvPath, outputs }}}); }, [&](const DerivedPath::Opaque & bo) { - res.push_back({installable, BuiltPath::Opaque { bo.path }}); + res.push_back({installable, {.path = BuiltPath::Opaque { bo.path }}}); }, }, path.raw()); } @@ -939,10 +939,10 @@ std::vector, BuiltPath>> Installable::bui std::map outputs; for (auto & path : buildResult.builtOutputs) outputs.emplace(path.first.outputName, path.second.outPath); - res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }}); + res.push_back({installable, {.path = BuiltPath::Built { bfd.drvPath, outputs }, .result = buildResult}}); }, [&](const DerivedPath::Opaque & bo) { - res.push_back({installable, BuiltPath::Opaque { bo.path }}); + res.push_back({installable, {.path = BuiltPath::Opaque { bo.path }, .result = buildResult}}); }, }, buildResult.path.raw()); } @@ -965,9 +965,12 @@ BuiltPaths Installable::toBuiltPaths( OperateOn operateOn, const std::vector> & installables) { - if (operateOn == OperateOn::Output) - return Installable::build(evalStore, store, mode, installables); - else { + if (operateOn == OperateOn::Output) { + BuiltPaths res; + for (auto & p : Installable::build(evalStore, store, mode, installables)) + res.push_back(p.path); + return res; + } else { if (mode == Realise::Nothing) settings.readOnlyMode = true; diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index 948f78919..02ea351d3 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -7,6 +7,7 @@ #include "eval.hh" #include "store-api.hh" #include "flake/flake.hh" +#include "build-result.hh" #include @@ -51,6 +52,12 @@ enum class OperateOn { Derivation }; +struct BuiltPathWithResult +{ + BuiltPath path; + std::optional result; +}; + struct Installable { virtual ~Installable() { } @@ -91,14 +98,14 @@ struct Installable return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}}); } - static BuiltPaths build( + static std::vector build( ref evalStore, ref store, Realise mode, const std::vector> & installables, BuildMode bMode = bmNormal); - static std::vector, BuiltPath>> build2( + static std::vector, BuiltPathWithResult>> build2( ref evalStore, ref store, Realise mode, diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 7fe9b9648..88b59f615 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -53,28 +53,13 @@ StorePathSet BuiltPath::outPaths() const ); } -template -nlohmann::json stuffToJSON(const std::vector & ts, ref store) { - auto res = nlohmann::json::array(); - for (const T & t : ts) { - std::visit([&res, store](const auto & t) { - res.push_back(t.toJSON(store)); - }, t.raw()); - } - return res; -} - -nlohmann::json builtPathsToJSON(const BuiltPaths & buildables, ref store) -{ return stuffToJSON(buildables, store); } -nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref store) -{ return stuffToJSON(paths, store); } - - -std::string DerivedPath::Opaque::to_string(const Store & store) const { +std::string DerivedPath::Opaque::to_string(const Store & store) const +{ return store.printStorePath(path); } -std::string DerivedPath::Built::to_string(const Store & store) const { +std::string DerivedPath::Built::to_string(const Store & store) const +{ return store.printStorePath(drvPath) + "!" + (outputs.empty() ? std::string { "*" } : concatStringsSep(",", outputs)); diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh index b2d0956b8..878696136 100644 --- a/src/libstore/derived-path.hh +++ b/src/libstore/derived-path.hh @@ -125,7 +125,4 @@ struct BuiltPath : _BuiltPathRaw { typedef std::vector DerivedPaths; typedef std::vector BuiltPaths; -nlohmann::json builtPathsToJSON(const BuiltPaths & buildables, ref store); -nlohmann::json derivedPathsToJSON(const DerivedPaths & , ref store); - } diff --git a/src/nix/app.cc b/src/nix/app.cc index 48de8fb82..5658f2a52 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -37,11 +37,13 @@ struct InstallableDerivedPath : Installable * Return the rewrites that are needed to resolve a string whose context is * included in `dependencies`. */ -StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies) +StringPairs resolveRewrites( + Store & store, + const std::vector & dependencies) { StringPairs res; for (auto & dep : dependencies) - if (auto drvDep = std::get_if(&dep)) + if (auto drvDep = std::get_if(&dep.path)) for (auto & [ outputName, outputPath ] : drvDep->outputs) res.emplace( downstreamPlaceholder(store, drvDep->drvPath, outputName), @@ -53,7 +55,10 @@ StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies) /** * Resolve the given string assuming the given context. */ -std::string resolveString(Store & store, const std::string & toResolve, const BuiltPaths dependencies) +std::string resolveString( + Store & store, + const std::string & toResolve, + const std::vector & dependencies) { auto rewrites = resolveRewrites(store, dependencies); return rewriteStrings(toResolve, rewrites); diff --git a/src/nix/build.cc b/src/nix/build.cc index 2b91f8e0a..94b169167 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -10,6 +10,37 @@ using namespace nix; +nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref store) +{ + auto res = nlohmann::json::array(); + for (auto & t : paths) { + std::visit([&res, store](const auto & t) { + res.push_back(t.toJSON(store)); + }, t.raw()); + } + return res; +} + +nlohmann::json builtPathsWithResultToJSON(const std::vector & buildables, ref store) +{ + auto res = nlohmann::json::array(); + for (auto & b : buildables) { + std::visit([&](const auto & t) { + auto j = t.toJSON(store); + if (b.result) { + j["startTime"] = b.result->startTime; + j["stopTime"] = b.result->stopTime; + if (b.result->cpuUser) + j["cpuUser"] = ((double) b.result->cpuUser->count()) / 1000000; + if (b.result->cpuSystem) + j["cpuSystem"] = ((double) b.result->cpuSystem->count()) / 1000000; + } + res.push_back(j); + }, b.path.raw()); + } + return res; +} + struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile { Path outLink = "result"; @@ -78,7 +109,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile Realise::Outputs, installables, buildMode); - if (json) logger->cout("%s", builtPathsToJSON(buildables, store).dump()); + if (json) logger->cout("%s", builtPathsWithResultToJSON(buildables, store).dump()); if (outLink != "") if (auto store2 = store.dynamic_pointer_cast()) @@ -98,7 +129,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile store2->addPermRoot(output.second, absPath(symlink)); } }, - }, buildable.raw()); + }, buildable.path.raw()); } if (printOutputPaths) { @@ -113,11 +144,14 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile std::cout << store->printStorePath(output.second) << std::endl; } }, - }, buildable.raw()); + }, buildable.path.raw()); } } - updateProfile(buildables); + BuiltPaths buildables2; + for (auto & b : buildables) + buildables2.push_back(b.path); + updateProfile(buildables2); } }; diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 3814e7d5a..11910523d 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -253,11 +253,11 @@ struct ProfileManifest static std::map builtPathsPerInstallable( - const std::vector, BuiltPath>> & builtPaths) + const std::vector, BuiltPathWithResult>> & builtPaths) { std::map res; for (auto & [installable, builtPath] : builtPaths) - res[installable.get()].push_back(builtPath); + res[installable.get()].push_back(builtPath.path); return res; } From ec45f4b82eaef8da04f4b828b2b06a77aa3f986f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Nov 2022 11:12:45 +0100 Subject: [PATCH 108/245] Fix indentation --- src/libstore/local-store.cc | 50 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index 04223d860..b67668e52 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -897,48 +897,48 @@ void LocalStore::queryPathInfoUncached(const StorePath & path, std::shared_ptr LocalStore::queryPathInfoInternal(State & state, const StorePath & path) { - /* Get the path info. */ + /* Get the path info. */ auto useQueryPathInfo(state.stmts->QueryPathInfo.use()(printStorePath(path))); - if (!useQueryPathInfo.next()) - return std::shared_ptr(); + if (!useQueryPathInfo.next()) + return std::shared_ptr(); - auto id = useQueryPathInfo.getInt(0); + auto id = useQueryPathInfo.getInt(0); - auto narHash = Hash::dummy; - try { - narHash = Hash::parseAnyPrefixed(useQueryPathInfo.getStr(1)); - } catch (BadHash & e) { - throw Error("invalid-path entry for '%s': %s", printStorePath(path), e.what()); - } + auto narHash = Hash::dummy; + try { + narHash = Hash::parseAnyPrefixed(useQueryPathInfo.getStr(1)); + } catch (BadHash & e) { + throw Error("invalid-path entry for '%s': %s", printStorePath(path), e.what()); + } - auto info = std::make_shared(path, narHash); + auto info = std::make_shared(path, narHash); - info->id = id; + info->id = id; - info->registrationTime = useQueryPathInfo.getInt(2); + info->registrationTime = useQueryPathInfo.getInt(2); auto s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 3); - if (s) info->deriver = parseStorePath(s); + if (s) info->deriver = parseStorePath(s); - /* Note that narSize = NULL yields 0. */ - info->narSize = useQueryPathInfo.getInt(4); + /* Note that narSize = NULL yields 0. */ + info->narSize = useQueryPathInfo.getInt(4); - info->ultimate = useQueryPathInfo.getInt(5) == 1; + info->ultimate = useQueryPathInfo.getInt(5) == 1; s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 6); - if (s) info->sigs = tokenizeString(s, " "); + if (s) info->sigs = tokenizeString(s, " "); s = (const char *) sqlite3_column_text(state.stmts->QueryPathInfo, 7); - if (s) info->ca = parseContentAddressOpt(s); + if (s) info->ca = parseContentAddressOpt(s); - /* Get the references. */ + /* Get the references. */ auto useQueryReferences(state.stmts->QueryReferences.use()(info->id)); - while (useQueryReferences.next()) - info->references.insert(parseStorePath(useQueryReferences.getStr(0))); + while (useQueryReferences.next()) + info->references.insert(parseStorePath(useQueryReferences.getStr(0))); - return info; + return info; } @@ -1041,9 +1041,9 @@ LocalStore::queryPartialDerivationOutputMap(const StorePath & path_) auto path = path_; auto outputs = retrySQLite>>([&]() { auto state(_state.lock()); - std::map> outputs; + std::map> outputs; uint64_t drvId; - drvId = queryValidPathId(*state, path); + drvId = queryValidPathId(*state, path); auto use(state->stmts->QueryDerivationOutputs.use()(drvId)); while (use.next()) outputs.insert_or_assign( From 82d5cf2a76ec009fd94a925c22a5e099a0b7321b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Nov 2022 11:45:41 +0100 Subject: [PATCH 109/245] Fix macOS build --- src/libstore/build/local-derivation-goal.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index f273ebe8a..34f8ab5f1 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -149,11 +149,15 @@ void LocalDerivationGoal::killChild() void LocalDerivationGoal::killSandbox(bool getStats) { if (cgroup) { + #if __linux__ auto stats = destroyCgroup(*cgroup); if (getStats) { buildResult.cpuUser = stats.cpuUser; buildResult.cpuSystem = stats.cpuSystem; } + #else + abort(); + #endif } else if (buildUser) { From e7a5b76844a649645e51a60dd18fd383d14d8755 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Nov 2022 09:38:08 +0100 Subject: [PATCH 110/245] Rename derivedPathsWithHintsToJSON -> builtPathsToJSON --- src/libstore/derived-path.cc | 2 +- src/libstore/derived-path.hh | 2 +- src/nix/build.cc | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 44587ae78..7fe9b9648 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -64,7 +64,7 @@ nlohmann::json stuffToJSON(const std::vector & ts, ref store) { return res; } -nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref store) +nlohmann::json builtPathsToJSON(const BuiltPaths & buildables, ref store) { return stuffToJSON(buildables, store); } nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref store) { return stuffToJSON(paths, store); } diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh index 24a0ae773..b2d0956b8 100644 --- a/src/libstore/derived-path.hh +++ b/src/libstore/derived-path.hh @@ -125,7 +125,7 @@ struct BuiltPath : _BuiltPathRaw { typedef std::vector DerivedPaths; typedef std::vector BuiltPaths; -nlohmann::json derivedPathsWithHintsToJSON(const BuiltPaths & buildables, ref store); +nlohmann::json builtPathsToJSON(const BuiltPaths & buildables, ref store); nlohmann::json derivedPathsToJSON(const DerivedPaths & , ref store); } diff --git a/src/nix/build.cc b/src/nix/build.cc index 9c648d28e..2b91f8e0a 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -78,7 +78,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile Realise::Outputs, installables, buildMode); - if (json) logger->cout("%s", derivedPathsWithHintsToJSON(buildables, store).dump()); + if (json) logger->cout("%s", builtPathsToJSON(buildables, store).dump()); if (outLink != "") if (auto store2 = store.dynamic_pointer_cast()) From f0baa5c1283359a413ca3a254527587c86b2f097 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Nov 2022 10:49:01 +0100 Subject: [PATCH 111/245] nix build --json: Include build statistics Example: # nix build -L --extra-experimental-features cgroups --impure --expr 'with import {}; runCommand "foo" {} "dd if=/dev/urandom bs=1M count=1024 | md5sum; mkdir $out"' --json [ { "cpuSystem": 1.911431, "cpuUser": 1.214249, "drvPath": "/nix/store/xzdqz67xba18hljhycp0hwfigzrs2z69-foo.drv", "outputs": { "out": "/nix/store/rh9mc9l2gkpq8kn2sgzndr6ll7ffjh6l-foo" }, "startTime": 1669024076, "stopTime": 1669024079 } ] --- src/libcmd/installables.cc | 29 +++++++++++++++------------ src/libcmd/installables.hh | 11 +++++++++-- src/libstore/derived-path.cc | 23 ++++------------------ src/libstore/derived-path.hh | 3 --- src/nix/app.cc | 11 ++++++++--- src/nix/build.cc | 38 ++++++++++++++++++++++++++++++++---- src/nix/profile.cc | 4 ++-- 7 files changed, 73 insertions(+), 46 deletions(-) diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index f63b9eeae..d6e62e775 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -844,20 +844,20 @@ std::shared_ptr SourceExprCommand::parseInstallable( return installables.front(); } -BuiltPaths Installable::build( +std::vector Installable::build( ref evalStore, ref store, Realise mode, const std::vector> & installables, BuildMode bMode) { - BuiltPaths res; - for (auto & [_, builtPath] : build2(evalStore, store, mode, installables, bMode)) - res.push_back(builtPath); + std::vector res; + for (auto & [_, builtPathWithResult] : build2(evalStore, store, mode, installables, bMode)) + res.push_back(builtPathWithResult); return res; } -std::vector, BuiltPath>> Installable::build2( +std::vector, BuiltPathWithResult>> Installable::build2( ref evalStore, ref store, Realise mode, @@ -877,7 +877,7 @@ std::vector, BuiltPath>> Installable::bui } } - std::vector, BuiltPath>> res; + std::vector, BuiltPathWithResult>> res; switch (mode) { @@ -918,10 +918,10 @@ std::vector, BuiltPath>> Installable::bui output, *drvOutput->second); } } - res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }}); + res.push_back({installable, {.path = BuiltPath::Built { bfd.drvPath, outputs }}}); }, [&](const DerivedPath::Opaque & bo) { - res.push_back({installable, BuiltPath::Opaque { bo.path }}); + res.push_back({installable, {.path = BuiltPath::Opaque { bo.path }}}); }, }, path.raw()); } @@ -943,10 +943,10 @@ std::vector, BuiltPath>> Installable::bui std::map outputs; for (auto & path : buildResult.builtOutputs) outputs.emplace(path.first.outputName, path.second.outPath); - res.push_back({installable, BuiltPath::Built { bfd.drvPath, outputs }}); + res.push_back({installable, {.path = BuiltPath::Built { bfd.drvPath, outputs }, .result = buildResult}}); }, [&](const DerivedPath::Opaque & bo) { - res.push_back({installable, BuiltPath::Opaque { bo.path }}); + res.push_back({installable, {.path = BuiltPath::Opaque { bo.path }, .result = buildResult}}); }, }, buildResult.path.raw()); } @@ -969,9 +969,12 @@ BuiltPaths Installable::toBuiltPaths( OperateOn operateOn, const std::vector> & installables) { - if (operateOn == OperateOn::Output) - return Installable::build(evalStore, store, mode, installables); - else { + if (operateOn == OperateOn::Output) { + BuiltPaths res; + for (auto & p : Installable::build(evalStore, store, mode, installables)) + res.push_back(p.path); + return res; + } else { if (mode == Realise::Nothing) settings.readOnlyMode = true; diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index 948f78919..02ea351d3 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -7,6 +7,7 @@ #include "eval.hh" #include "store-api.hh" #include "flake/flake.hh" +#include "build-result.hh" #include @@ -51,6 +52,12 @@ enum class OperateOn { Derivation }; +struct BuiltPathWithResult +{ + BuiltPath path; + std::optional result; +}; + struct Installable { virtual ~Installable() { } @@ -91,14 +98,14 @@ struct Installable return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}}); } - static BuiltPaths build( + static std::vector build( ref evalStore, ref store, Realise mode, const std::vector> & installables, BuildMode bMode = bmNormal); - static std::vector, BuiltPath>> build2( + static std::vector, BuiltPathWithResult>> build2( ref evalStore, ref store, Realise mode, diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 7fe9b9648..88b59f615 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -53,28 +53,13 @@ StorePathSet BuiltPath::outPaths() const ); } -template -nlohmann::json stuffToJSON(const std::vector & ts, ref store) { - auto res = nlohmann::json::array(); - for (const T & t : ts) { - std::visit([&res, store](const auto & t) { - res.push_back(t.toJSON(store)); - }, t.raw()); - } - return res; -} - -nlohmann::json builtPathsToJSON(const BuiltPaths & buildables, ref store) -{ return stuffToJSON(buildables, store); } -nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref store) -{ return stuffToJSON(paths, store); } - - -std::string DerivedPath::Opaque::to_string(const Store & store) const { +std::string DerivedPath::Opaque::to_string(const Store & store) const +{ return store.printStorePath(path); } -std::string DerivedPath::Built::to_string(const Store & store) const { +std::string DerivedPath::Built::to_string(const Store & store) const +{ return store.printStorePath(drvPath) + "!" + (outputs.empty() ? std::string { "*" } : concatStringsSep(",", outputs)); diff --git a/src/libstore/derived-path.hh b/src/libstore/derived-path.hh index b2d0956b8..878696136 100644 --- a/src/libstore/derived-path.hh +++ b/src/libstore/derived-path.hh @@ -125,7 +125,4 @@ struct BuiltPath : _BuiltPathRaw { typedef std::vector DerivedPaths; typedef std::vector BuiltPaths; -nlohmann::json builtPathsToJSON(const BuiltPaths & buildables, ref store); -nlohmann::json derivedPathsToJSON(const DerivedPaths & , ref store); - } diff --git a/src/nix/app.cc b/src/nix/app.cc index 48de8fb82..5658f2a52 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -37,11 +37,13 @@ struct InstallableDerivedPath : Installable * Return the rewrites that are needed to resolve a string whose context is * included in `dependencies`. */ -StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies) +StringPairs resolveRewrites( + Store & store, + const std::vector & dependencies) { StringPairs res; for (auto & dep : dependencies) - if (auto drvDep = std::get_if(&dep)) + if (auto drvDep = std::get_if(&dep.path)) for (auto & [ outputName, outputPath ] : drvDep->outputs) res.emplace( downstreamPlaceholder(store, drvDep->drvPath, outputName), @@ -53,7 +55,10 @@ StringPairs resolveRewrites(Store & store, const BuiltPaths dependencies) /** * Resolve the given string assuming the given context. */ -std::string resolveString(Store & store, const std::string & toResolve, const BuiltPaths dependencies) +std::string resolveString( + Store & store, + const std::string & toResolve, + const std::vector & dependencies) { auto rewrites = resolveRewrites(store, dependencies); return rewriteStrings(toResolve, rewrites); diff --git a/src/nix/build.cc b/src/nix/build.cc index 2b91f8e0a..85b1efc33 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -10,6 +10,33 @@ using namespace nix; +nlohmann::json derivedPathsToJSON(const DerivedPaths & paths, ref store) +{ + auto res = nlohmann::json::array(); + for (auto & t : paths) { + std::visit([&res, store](const auto & t) { + res.push_back(t.toJSON(store)); + }, t.raw()); + } + return res; +} + +nlohmann::json builtPathsWithResultToJSON(const std::vector & buildables, ref store) +{ + auto res = nlohmann::json::array(); + for (auto & b : buildables) { + std::visit([&](const auto & t) { + auto j = t.toJSON(store); + if (b.result) { + j["startTime"] = b.result->startTime; + j["stopTime"] = b.result->stopTime; + } + res.push_back(j); + }, b.path.raw()); + } + return res; +} + struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile { Path outLink = "result"; @@ -78,7 +105,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile Realise::Outputs, installables, buildMode); - if (json) logger->cout("%s", builtPathsToJSON(buildables, store).dump()); + if (json) logger->cout("%s", builtPathsWithResultToJSON(buildables, store).dump()); if (outLink != "") if (auto store2 = store.dynamic_pointer_cast()) @@ -98,7 +125,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile store2->addPermRoot(output.second, absPath(symlink)); } }, - }, buildable.raw()); + }, buildable.path.raw()); } if (printOutputPaths) { @@ -113,11 +140,14 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixJSON, MixProfile std::cout << store->printStorePath(output.second) << std::endl; } }, - }, buildable.raw()); + }, buildable.path.raw()); } } - updateProfile(buildables); + BuiltPaths buildables2; + for (auto & b : buildables) + buildables2.push_back(b.path); + updateProfile(buildables2); } }; diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 3814e7d5a..11910523d 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -253,11 +253,11 @@ struct ProfileManifest static std::map builtPathsPerInstallable( - const std::vector, BuiltPath>> & builtPaths) + const std::vector, BuiltPathWithResult>> & builtPaths) { std::map res; for (auto & [installable, builtPath] : builtPaths) - res[installable.get()].push_back(builtPath); + res[installable.get()].push_back(builtPath.path); return res; } From 9d17ce07e872e88057480744414e0d1ef4fd5fa8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Nov 2022 12:55:49 +0100 Subject: [PATCH 112/245] AutoUserLock: If sandboxing is disabled, use the build users group We have to use a gid that has write access to the Nix store. --- src/libstore/build/local-derivation-goal.cc | 60 ++++++++++----------- src/libstore/lock.cc | 22 ++++---- src/libstore/lock.hh | 2 +- 3 files changed, 44 insertions(+), 40 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 34f8ab5f1..b7084384a 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -177,9 +177,38 @@ void LocalDerivationGoal::tryLocalBuild() { return; } + /* Are we doing a chroot build? */ + { + auto noChroot = parsedDrv->getBoolAttr("__noChroot"); + if (settings.sandboxMode == smEnabled) { + if (noChroot) + throw Error("derivation '%s' has '__noChroot' set, " + "but that's not allowed when 'sandbox' is 'true'", worker.store.printStorePath(drvPath)); +#if __APPLE__ + if (additionalSandboxProfile != "") + throw Error("derivation '%s' specifies a sandbox profile, " + "but this is only allowed when 'sandbox' is 'relaxed'", worker.store.printStorePath(drvPath)); +#endif + useChroot = true; + } + else if (settings.sandboxMode == smDisabled) + useChroot = false; + else if (settings.sandboxMode == smRelaxed) + useChroot = derivationType.isSandboxed() && !noChroot; + } + + auto & localStore = getLocalStore(); + if (localStore.storeDir != localStore.realStoreDir.get()) { + #if __linux__ + useChroot = true; + #else + throw Error("building using a diverted store is not supported on this platform"); + #endif + } + if (useBuildUsers()) { if (!buildUser) - buildUser = acquireUserLock(parsedDrv->useUidRange() ? 65536 : 1); + buildUser = acquireUserLock(parsedDrv->useUidRange() ? 65536 : 1, useChroot); if (!buildUser) { if (!actLock) @@ -433,35 +462,6 @@ void LocalDerivationGoal::startBuilder() additionalSandboxProfile = parsedDrv->getStringAttr("__sandboxProfile").value_or(""); #endif - /* Are we doing a chroot build? */ - { - auto noChroot = parsedDrv->getBoolAttr("__noChroot"); - if (settings.sandboxMode == smEnabled) { - if (noChroot) - throw Error("derivation '%s' has '__noChroot' set, " - "but that's not allowed when 'sandbox' is 'true'", worker.store.printStorePath(drvPath)); -#if __APPLE__ - if (additionalSandboxProfile != "") - throw Error("derivation '%s' specifies a sandbox profile, " - "but this is only allowed when 'sandbox' is 'relaxed'", worker.store.printStorePath(drvPath)); -#endif - useChroot = true; - } - else if (settings.sandboxMode == smDisabled) - useChroot = false; - else if (settings.sandboxMode == smRelaxed) - useChroot = derivationType.isSandboxed() && !noChroot; - } - - auto & localStore = getLocalStore(); - if (localStore.storeDir != localStore.realStoreDir.get()) { - #if __linux__ - useChroot = true; - #else - throw Error("building using a diverted store is not supported on this platform"); - #endif - } - /* Create a temporary directory where the build will take place. */ tmpDir = createTempDir("", "nix-build-" + std::string(drvPath.name()), false, false, 0700); diff --git a/src/libstore/lock.cc b/src/libstore/lock.cc index 4fad3bfd2..3b93979a8 100644 --- a/src/libstore/lock.cc +++ b/src/libstore/lock.cc @@ -109,22 +109,18 @@ struct AutoUserLock : UserLock { AutoCloseFD fdUserLock; uid_t firstUid = 0; + gid_t firstGid = 0; uid_t nrIds = 1; uid_t getUID() override { assert(firstUid); return firstUid; } gid_t getUIDCount() override { return nrIds; } - gid_t getGID() override - { - // We use the same GID ranges as for the UIDs. - assert(firstUid); - return firstUid; - } + gid_t getGID() override { assert(firstGid); return firstGid; } std::vector getSupplementaryGIDs() override { return {}; } - static std::unique_ptr acquire(uid_t nrIds) + static std::unique_ptr acquire(uid_t nrIds, bool useChroot) { settings.requireExperimentalFeature(Xp::AutoAllocateUids); assert(settings.startId > 0); @@ -154,6 +150,14 @@ struct AutoUserLock : UserLock auto lock = std::make_unique(); lock->fdUserLock = std::move(fd); lock->firstUid = settings.startId + i * maxIdsPerBuild; + if (useChroot) + lock->firstGid = lock->firstUid; + else { + struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); + if (!gr) + throw Error("the group '%s' specified in 'build-users-group' does not exist", settings.buildUsersGroup); + lock->firstGid = gr->gr_gid; + } lock->nrIds = nrIds; return lock; } @@ -163,10 +167,10 @@ struct AutoUserLock : UserLock } }; -std::unique_ptr acquireUserLock(uid_t nrIds) +std::unique_ptr acquireUserLock(uid_t nrIds, bool useChroot) { if (settings.autoAllocateUids) - return AutoUserLock::acquire(nrIds); + return AutoUserLock::acquire(nrIds, useChroot); else return SimpleUserLock::acquire(); } diff --git a/src/libstore/lock.hh b/src/libstore/lock.hh index e7ceefab8..49ad86de7 100644 --- a/src/libstore/lock.hh +++ b/src/libstore/lock.hh @@ -31,7 +31,7 @@ struct UserLock /* Acquire a user lock for a UID range of size `nrIds`. Note that this may return nullptr if no user is available. */ -std::unique_ptr acquireUserLock(uid_t nrIds); +std::unique_ptr acquireUserLock(uid_t nrIds, bool useChroot); bool useBuildUsers(); From a4af966d5df96d8bb90cb7096985819e99c384c1 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 21 Nov 2022 14:32:38 +0100 Subject: [PATCH 113/245] add maintainers' handbook MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit write down the process we have been developing and following so far. Co-Authored-By: Théophane Hufschmitt Co-Authored-By: John Ericson --- maintainers/README.md | 79 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 maintainers/README.md diff --git a/maintainers/README.md b/maintainers/README.md new file mode 100644 index 000000000..cfb0e26a8 --- /dev/null +++ b/maintainers/README.md @@ -0,0 +1,79 @@ +# Nix maintainers team + +## Motivation + +The goal of the team to help other people to contribute to Nix. + +## Members + +- Eelco Dolstra (@edolstra) – Team lead +- Théophane Hufschmitt (@thufschmitt) +- Valentin Gagarin (@fricklerhandwerk) +- Thomas Bereknyei (@tomberek) +- Robert Hensing (@roberth) + +## Meeting protocol + +The team meets twice a week: + +- Discussion meeting: [Fridays 12:00-13:00 UTC](https://calendar.google.com/calendar/event?eid=MHNtOGVuNWtrZXNpZHR2bW1sM3QyN2ZjaGNfMjAyMjExMjVUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn) + + 1. Triage issues and pull requests from the _No Status_ column (30 min) + 2. Discuss issues and pull requests from the _To discuss_ column (30 min) + +- Work meeting: [Mondays 12:00-14:00 UTC](https://calendar.google.com/calendar/event?eid=NTM1MG1wNGJnOGpmOTZhYms3bTB1bnY5cWxfMjAyMjExMjFUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn) + + 1. Code review on pull requests from _In review_. + 2. Other chores and tasks. + +Meeting notes are collected on a [collaborative scratchpad](https://pad.lassul.us/Cv7FpYx-Ri-4VjUykQOLAw), and published on Discourse under the [Nix category](https://discourse.nixos.org/c/dev/nix/50). + +## Project board protocol + +The team uses a [GitHub project board](https://github.com/orgs/NixOS/projects/19/views/1) for tracking its work. + +Issues on the board progress through the following states: + +- No Status + + Team members can add pull requests or issues to discuss or review together. + + During the discussion meeting, the team triages new items. + If there is disagreement on the general idea behind the issue or pull request, items are moved to _To discuss_, otherwise to _In review_. + +- To discuss + + Pull requests and issues that are important and controverisal enough to warrant a discussion by the whole team. + + This may be where the merit of the change itself or the implementation strategy is contested by a team member. + +- In review + + Pull requests in this column are reviewed together during work meetings. + This is both for spreading implementation knowledge and for establishing common values in code reviews. + + When the overall direction is agreed upon, even when further changes are required, the pull request is assigned to one team member. + +- Assigned for merging + + One team member is assigned to each of these pull requests. + They will communicate with the authors, and make the final approval once all remaining issues are addressed. + + If more substantive issues arise, the assignee can move the pull request back to _To discuss_ to involve the team again. + +The process is illustrated in the following diagram: + +```mermaid +flowchart TD + discuss[To discuss] + + review[To review] + + New --> |Disagreement on idea| discuss + New & discuss --> |Consensus on idea| review + + review --> |Consensus on implementation| Assigned + + Assigned --> |Implementation issues arise| review + Assigned --> |Remaining issues fixed| Merged +``` From 44dc5c6c136b5440b2bdce7c537dc6a4f857c349 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 21 Nov 2022 15:13:19 +0100 Subject: [PATCH 114/245] reword --- maintainers/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/maintainers/README.md b/maintainers/README.md index cfb0e26a8..5a744fb91 100644 --- a/maintainers/README.md +++ b/maintainers/README.md @@ -2,7 +2,7 @@ ## Motivation -The goal of the team to help other people to contribute to Nix. +The goal of the team is to help other people to contribute to Nix. ## Members @@ -39,11 +39,11 @@ Issues on the board progress through the following states: Team members can add pull requests or issues to discuss or review together. During the discussion meeting, the team triages new items. - If there is disagreement on the general idea behind the issue or pull request, items are moved to _To discuss_, otherwise to _In review_. + If there is disagreement on the general idea behind an issue or pull request, it is moved to _To discuss_, otherwise to _In review_. - To discuss - Pull requests and issues that are important and controverisal enough to warrant a discussion by the whole team. + Pull requests and issues that are important and controverisal are discussed by the team during discussion meetings. This may be where the merit of the change itself or the implementation strategy is contested by a team member. From c776dfbb35e961ac3f011ab8665dfc85ab067ef8 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 21 Nov 2022 18:46:55 +0100 Subject: [PATCH 115/245] Use hex for startId Co-authored-by: Linus Heckemann --- src/libstore/globals.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 88fe72202..653d108aa 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -288,7 +288,7 @@ public: Setting startId{this, #if __linux__ - 872415232, + 0x34000000, #else 56930, #endif From b37c2d84b67635fc928ed174166f04d6f4d30c6b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 22 Nov 2022 09:02:17 +0100 Subject: [PATCH 116/245] Always call setgroups() We shouldn't skip this if the supplementary group list is empty, because then the sandbox won't drop the supplementary groups of the parent (like "root"). --- src/libstore/build/local-derivation-goal.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index b7084384a..232440f74 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -1988,9 +1988,8 @@ void LocalDerivationGoal::runChild() if (setUser && buildUser) { /* Preserve supplementary groups of the build user, to allow admins to specify groups such as "kvm". */ - if (!buildUser->getSupplementaryGIDs().empty() && - setgroups(buildUser->getSupplementaryGIDs().size(), - buildUser->getSupplementaryGIDs().data()) == -1) + auto gids = buildUser->getSupplementaryGIDs(); + if (setgroups(gids.size(), gids.data()) == -1) throw SysError("cannot set supplementary groups of build user"); if (setgid(buildUser->getGID()) == -1 || From 3d23b9d0324ff415af9e5f35568aca98c04a90cc Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 22 Nov 2022 09:03:30 +0100 Subject: [PATCH 117/245] SimpleUserLock::getSupplementaryGIDs(): Filter out main gid This avoids having the user's gid in the supplementary group list as well. --- src/libstore/lock.cc | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/libstore/lock.cc b/src/libstore/lock.cc index 3b93979a8..7459d837d 100644 --- a/src/libstore/lock.cc +++ b/src/libstore/lock.cc @@ -71,21 +71,22 @@ struct SimpleUserLock : UserLock user. This is usually either empty or contains a group such as "kvm". */ int ngroups = 32; // arbitrary initial guess - lock->supplementaryGIDs.resize(ngroups); + std::vector gids; + gids.resize(ngroups); int err = getgrouplist( pw->pw_name, pw->pw_gid, - lock->supplementaryGIDs.data(), + gids.data(), &ngroups); /* Our initial size of 32 wasn't sufficient, the correct size has been stored in ngroups, so we try again. */ if (err == -1) { - lock->supplementaryGIDs.resize(ngroups); + gids.resize(ngroups); err = getgrouplist( pw->pw_name, pw->pw_gid, - lock->supplementaryGIDs.data(), + gids.data(), &ngroups); } @@ -94,7 +95,9 @@ struct SimpleUserLock : UserLock throw Error("failed to get list of supplementary groups for '%s'", pw->pw_name); // Finally, trim back the GID list to its real size. - lock->supplementaryGIDs.resize(ngroups); + for (auto i = 0; i < ngroups; i++) + if (gids[i] != lock->gid) + lock->supplementaryGIDs.push_back(gids[i]); #endif return lock; From 52f0c809173fe977ac0b46a8506d0c9af208a197 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 22 Nov 2022 10:36:20 +0100 Subject: [PATCH 118/245] fix error in language overview it is not possible to antiquote numbers. --- doc/manual/src/language/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/language/index.md b/doc/manual/src/language/index.md index f9e9b9781..db34fde75 100644 --- a/doc/manual/src/language/index.md +++ b/doc/manual/src/language/index.md @@ -93,7 +93,7 @@ This is an incomplete overview of language features, by example. `"hello ${ { a = "world" }.a }"` - `"1 2 ${3}"` + `"1 2 ${toString 3}"` `"${pkgs.bash}/bin/sh"` From 989fc8a8b9cf98addbef85bf909be7b00b0462db Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 23 Nov 2022 15:24:50 +0100 Subject: [PATCH 119/245] Add release notes --- doc/manual/src/release-notes/rl-next.md | 40 +++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 2069e4578..47181fd39 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -10,3 +10,43 @@ This avoids a lot of spurious errors where some benign strings end-up having a context just because they are read from a store path ([#7260](https://github.com/NixOS/nix/pull/7260)). + +* Nix can now automatically pick UIDs for builds, removing the need to + create `nixbld*` user accounts. these UIDs are allocated starting at + 872415232 on Linux and 56930 on macOS. + + This is an experimental feature. To enable it, add the following to + `nix.conf`: + + ``` + extra-experimental-features = auto-allocate-uids + auto-allocate-uids = true + ``` + +* On Linux, Nix can now run builds in a user namespace where the build + runs as root (UID 0) and has 65,536 UIDs available. This is + primarily useful for running containers such as `systemd-nspawn` + inside a Nix build. + + A build can enable this by requiring the `uid-range` system feature, + i.e. by setting the derivation attribute + + ``` + requiredSystemFeatures = [ "uid-range" ]; + ``` + + The `uid-range` system feature requires the `auto-allocate-uids` + setting to be enabled (see above). + +* On Linux, Nix has experimental support for running builds inside a + cgroup. It can be enabled by adding + + ``` + extra-experimental-features = cgroups + ``` + + to `nix.conf`. It is also automatically enabled for builds that + require the `uid-range` system feature. + +* `nix build --json` now prints some statistics about top-level + derivations, such as CPU statistics when cgroups are enabled. From 2aa3f2e81020c1c780be6329e1133068779c8f08 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 23 Nov 2022 17:07:59 +0100 Subject: [PATCH 120/245] Include UID in hex --- doc/manual/src/release-notes/rl-next.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 47181fd39..4c91002fb 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -12,8 +12,8 @@ ([#7260](https://github.com/NixOS/nix/pull/7260)). * Nix can now automatically pick UIDs for builds, removing the need to - create `nixbld*` user accounts. these UIDs are allocated starting at - 872415232 on Linux and 56930 on macOS. + create `nixbld*` user accounts. These UIDs are allocated starting at + 872415232 (0x34000000) on Linux and 56930 on macOS. This is an experimental feature. To enable it, add the following to `nix.conf`: From b13fd4c58e81b2b2b0d72caa5ce80de861622610 Mon Sep 17 00:00:00 2001 From: Taeer Bar-Yam Date: Wed, 23 Nov 2022 11:39:50 -0500 Subject: [PATCH 121/245] Fix why-depends for CA derivations why-depends assumed that we knew the output path of the second argument. For CA derivations, we might not know until it's built. One way to solve this would be to build the second installable to get the output path. In this case we don't need to, though. If the first installable (A) depends on the second (B), then getting the store path of A will necessitate having the store path B. The contrapositive is, if the store path of B is not known (i.e. it's a CA derivation which hasn't been built), then A does not depend on B. --- src/nix/why-depends.cc | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc index 1d9ab28ba..285e36722 100644 --- a/src/nix/why-depends.cc +++ b/src/nix/why-depends.cc @@ -84,19 +84,35 @@ struct CmdWhyDepends : SourceExprCommand auto package = parseInstallable(store, _package); auto packagePath = Installable::toStorePath(getEvalStore(), store, Realise::Outputs, operateOn, package); auto dependency = parseInstallable(store, _dependency); - auto dependencyPath = Installable::toStorePath(getEvalStore(), store, Realise::Derivation, operateOn, dependency); - auto dependencyPathHash = dependencyPath.hashPart(); + auto derivedDependency = dependency->toDerivedPath(); + auto optDependencyPath = std::visit(overloaded { + [](const DerivedPath::Opaque & nodrv) -> std::optional { + return { nodrv.path }; + }, + [&](const DerivedPath::Built & hasdrv) -> std::optional { + if (hasdrv.outputs.size() != 1) { + throw Error("argument '%s' should evaluate to one store path", dependency->what()); + } + auto outputMap = store->queryPartialDerivationOutputMap(hasdrv.drvPath); + auto maybePath = outputMap.find(*hasdrv.outputs.begin()); + if (maybePath == outputMap.end()) { + throw Error("unexpected end of iterator"); + } + return maybePath->second; + }, + }, derivedDependency.raw()); StorePathSet closure; store->computeFSClosure({packagePath}, closure, false, false); - if (!closure.count(dependencyPath)) { - printError("'%s' does not depend on '%s'", - store->printStorePath(packagePath), - store->printStorePath(dependencyPath)); + if (!optDependencyPath.has_value() || !closure.count(*optDependencyPath)) { + printError("'%s' does not depend on '%s'", package->what(), dependency->what()); return; } + auto dependencyPath = *optDependencyPath; + auto dependencyPathHash = dependencyPath.hashPart(); + stopProgressBar(); // FIXME auto accessor = store->getFSAccessor(); From bd8571a5c3724ba5917564a5243af173966515c5 Mon Sep 17 00:00:00 2001 From: Taeer Bar-Yam Date: Wed, 23 Nov 2022 12:06:47 -0500 Subject: [PATCH 122/245] add explanation and test --- src/nix/why-depends.cc | 11 +++++++++++ tests/ca/why-depends.sh | 5 +++++ 2 files changed, 16 insertions(+) create mode 100644 tests/ca/why-depends.sh diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc index 285e36722..723017497 100644 --- a/src/nix/why-depends.cc +++ b/src/nix/why-depends.cc @@ -83,6 +83,17 @@ struct CmdWhyDepends : SourceExprCommand { auto package = parseInstallable(store, _package); auto packagePath = Installable::toStorePath(getEvalStore(), store, Realise::Outputs, operateOn, package); + + /* We don't need to build `dependency`. We try to get the store + * path if it's already known, and if not, then it's not a dependency. + * + * Why? If `package` does depends on `dependency`, then getting the + * store path of `package` above necessitated having the store path + * of `dependency`. The contrapositive is, if the store path of + * `dependency` is not already known at this point (i.e. it's a CA + * derivation which hasn't been built), then `package` did not need it + * to build. + */ auto dependency = parseInstallable(store, _dependency); auto derivedDependency = dependency->toDerivedPath(); auto optDependencyPath = std::visit(overloaded { diff --git a/tests/ca/why-depends.sh b/tests/ca/why-depends.sh new file mode 100644 index 000000000..0c079f63b --- /dev/null +++ b/tests/ca/why-depends.sh @@ -0,0 +1,5 @@ +source common.sh + +export NIX_TESTS_CA_BY_DEFAULT=1 + +cd .. && source why-depends.sh From 341a807444a23cb12ed1cf3ad80b0c99f7dc5873 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 24 Nov 2022 11:33:59 +0100 Subject: [PATCH 123/245] the point is setting a default reviewer, not notifications --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bba083834..d58577551 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -4,7 +4,7 @@ # Merge permissions are required for maintaining an entry in this file. # For documentation on this mechanism, see https://help.github.com/articles/about-codeowners/ -# Notified if nothing else matches +# Default reviewers if nothing else matches * @edolstra @thufschmitt # This file From d6318e1638ed17fef701ada19ec7fde4b54d3bcf Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 24 Nov 2022 13:47:08 +0100 Subject: [PATCH 124/245] refactor rendering documentation of builtins as in [1], make the document structure visible, like in a template [1]: 4655563470b59e0ef50a33af003058c2b54db778 --- doc/manual/generate-builtins.nix | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/doc/manual/generate-builtins.nix b/doc/manual/generate-builtins.nix index 6c8b88da2..115bb3f94 100644 --- a/doc/manual/generate-builtins.nix +++ b/doc/manual/generate-builtins.nix @@ -1,16 +1,20 @@ -with builtins; -with import ./utils.nix; +builtinsDump: +let + showBuiltin = name: + let + inherit (builtinsDump.${name}) doc args; + in + '' +
+ ${name} ${listArgs args} +
+
-builtins: + ${doc} + +
+ ''; + listArgs = args: builtins.concatStringsSep " " (map (s: "${s}") args); +in +with builtins; concatStringsSep "\n" (map showBuiltin (attrNames builtinsDump)) -concatStrings (map - (name: - let builtin = builtins.${name}; in - "
${name} " - + concatStringsSep " " (map (s: "${s}") builtin.args) - + "
" - + "
\n\n" - + builtin.doc - + "\n\n
" - ) - (attrNames builtins)) From 0b4c4d74344613a15791e61179a441d80803d67a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 25 Nov 2022 22:30:56 +0100 Subject: [PATCH 125/245] Don't use GC_STRNDUP It calls strlen() on the input (rather than simply copying at most `size` bytes), which can fail if the input is not zero-terminated and is inefficient in any case. Fixes #7347. --- src/libexpr/eval.cc | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 563f24e48..6ba44cc1d 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -69,15 +69,11 @@ static char * dupString(const char * s) // empty string. static const char * makeImmutableStringWithLen(const char * s, size_t size) { - char * t; if (size == 0) return ""; -#if HAVE_BOEHMGC - t = GC_STRNDUP(s, size); -#else - t = strndup(s, size); -#endif - if (!t) throw std::bad_alloc(); + auto t = allocString(size + 1); + memcpy(t, s, size); + t[size] = 0; return t; } From c4ce89f772d85e2a39727927903ca105a8c6a168 Mon Sep 17 00:00:00 2001 From: Liu Xiaoyi Date: Sat, 26 Nov 2022 22:01:51 +0800 Subject: [PATCH 126/245] Clarify uninstallation steps on Linux Co-authored-by: Valentin Gagarin --- .../src/installation/installing-binary.md | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index 8cdd64df6..a9378681d 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -88,25 +88,28 @@ extension. The installer will also create `/etc/profile.d/nix.sh`. ### Linux -To begin, if you are on Linux with systemd, remove the Nix daemon service: +If you are on Linux with systemd: -```console -sudo systemctl stop nix-daemon.service -sudo systemctl disable nix-daemon.socket nix-daemon.service -sudo systemctl daemon-reload -``` +1. Remove the Nix daemon service: -Then you can remove systemd service files: + ```console + sudo systemctl stop nix-daemon.service + sudo systemctl disable nix-daemon.socket nix-daemon.service + sudo systemctl daemon-reload + ``` -```console -sudo rm /etc/systemd/system/nix-daemon.service /etc/systemd/system/nix-daemon.socket -``` +1. Remove systemd service files: -Also, the installer script uses systemd-tmpfiles (if presents) to create the socket directory. You may also want to remove the configuration for that: + ```console + sudo rm /etc/systemd/system/nix-daemon.service /etc/systemd/system/nix-daemon.socket + ``` -```console -sudo rm /etc/tmpfiles.d/nix-daemon.conf -``` +1. The installer script uses systemd-tmpfiles to create the socket directory. + You may also want to remove the configuration for that: + + ```console + sudo rm /etc/tmpfiles.d/nix-daemon.conf + ``` Remove files created by Nix: From 5b798f6caeab64854394e1250e30aab91241cf26 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 27 Nov 2022 12:57:18 +0100 Subject: [PATCH 127/245] Fix random client failures during GC server shutdown We need to close the GC server socket before shutting down the active GC client connections, otherwise a client may (re)connect and get ECONNRESET. But also handle ECONNRESET for resilience. Fixes random failures like GC socket disconnected connecting to '/tmp/nix-shell.y07M0H/nix-test/default/var/nix/gc-socket/socket' sending GC root '/tmp/nix-shell.y07M0H/nix-test/default/store/kb5yzija0f1x5xkqkgclrdzldxj6nnc6-non-blocking' reading GC root from client: error: unexpected EOF reading a line 1 store paths deleted, 0.00 MiB freed error: reading from file: Connection reset by peer in gc-non-blocking.sh. --- src/libstore/gc.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libstore/gc.cc b/src/libstore/gc.cc index 9ef8972f3..5d91829f1 100644 --- a/src/libstore/gc.cc +++ b/src/libstore/gc.cc @@ -147,7 +147,7 @@ void LocalStore::addTempRoot(const StorePath & path) } catch (SysError & e) { /* The garbage collector may have exited, so we need to restart. */ - if (e.errNo == EPIPE) { + if (e.errNo == EPIPE || e.errNo == ECONNRESET) { debug("GC socket disconnected"); state->fdRootsSocket.close(); goto restart; @@ -506,6 +506,7 @@ void LocalStore::collectGarbage(const GCOptions & options, GCResults & results) Finally cleanup([&]() { debug("GC roots server shutting down"); + fdServer.close(); while (true) { auto item = remove_begin(*connections.lock()); if (!item) break; From f1b5c6876bc570ff9ac79410d8e47aadcb9aed52 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 27 Nov 2022 16:38:34 +0100 Subject: [PATCH 128/245] Add tests for auto-uid-allocation, uid-range and cgroups --- flake.nix | 6 ++++ tests/containers.nix | 68 ++++++++++++++++++++++++++++++++++++ tests/id-test.nix | 8 +++++ tests/systemd-nspawn.nix | 75 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 tests/containers.nix create mode 100644 tests/id-test.nix create mode 100644 tests/systemd-nspawn.nix diff --git a/flake.nix b/flake.nix index cc2a48d9c..d9d01da10 100644 --- a/flake.nix +++ b/flake.nix @@ -506,6 +506,12 @@ overlay = self.overlays.default; }); + tests.containers = (import ./tests/containers.nix rec { + system = "x86_64-linux"; + inherit nixpkgs; + overlay = self.overlays.default; + }); + tests.setuid = nixpkgs.lib.genAttrs ["i686-linux" "x86_64-linux"] (system: diff --git a/tests/containers.nix b/tests/containers.nix new file mode 100644 index 000000000..d1e791b8c --- /dev/null +++ b/tests/containers.nix @@ -0,0 +1,68 @@ +# Test whether we can run a NixOS container inside a Nix build using systemd-nspawn. +{ nixpkgs, system, overlay }: + +with import (nixpkgs + "/nixos/lib/testing-python.nix") { + inherit system; + extraConfigurations = [ { nixpkgs.overlays = [ overlay ]; } ]; +}; + +makeTest ({ + name = "containers"; + + nodes = + { + host = + { config, lib, pkgs, nodes, ... }: + { virtualisation.writableStore = true; + virtualisation.diskSize = 2048; + virtualisation.additionalPaths = + [ pkgs.stdenv + (import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel + ]; + virtualisation.memorySize = 4096; + nix.binaryCaches = lib.mkForce [ ]; + nix.extraOptions = + '' + extra-experimental-features = nix-command auto-allocate-uids + extra-system-features = uid-range + ''; + nix.nixPath = [ "nixpkgs=${nixpkgs}" ]; + }; + }; + + testScript = { nodes }: '' + start_all() + + host.succeed("nix --version >&2") + + # Test that 'id' gives the expected result in various configurations. + + # Existing UIDs, sandbox. + host.succeed("nix build --no-auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-1") + host.succeed("[[ $(cat ./result) = 'uid=1000(nixbld) gid=100(nixbld) groups=100(nixbld)' ]]") + + # Existing UIDs, no sandbox. + host.succeed("nix build --no-auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-2") + host.succeed("[[ $(cat ./result) = 'uid=30001(nixbld1) gid=30000(nixbld) groups=30000(nixbld)' ]]") + + # Auto-allocated UIDs, sandbox. + host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-3") + host.succeed("[[ $(cat ./result) = 'uid=1000(nixbld) gid=100(nixbld) groups=100(nixbld)' ]]") + + # Auto-allocated UIDs, no sandbox. + host.succeed("nix build --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-4") + host.succeed("[[ $(cat ./result) = 'uid=872415232 gid=30000(nixbld) groups=30000(nixbld)' ]]") + + # Auto-allocated UIDs, UID range, sandbox. + host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-5 --arg uidRange true") + host.succeed("[[ $(cat ./result) = 'uid=0(root) gid=0(root) groups=0(root)' ]]") + + # Auto-allocated UIDs, UID range, no sandbox. + host.fail("nix build --auto-allocate-uids --no-sandbox -L --offline --impure --file ${./id-test.nix} --argstr name id-test-6 --arg uidRange true") + + # Run systemd-nspawn in a Nix build. + host.succeed("nix build --auto-allocate-uids --sandbox -L --offline --impure --file ${./systemd-nspawn.nix} --argstr nixpkgs ${nixpkgs}") + host.succeed("[[ $(cat ./result/msg) = 'Hello World' ]]") + ''; + +}) diff --git a/tests/id-test.nix b/tests/id-test.nix new file mode 100644 index 000000000..8eb9d38f9 --- /dev/null +++ b/tests/id-test.nix @@ -0,0 +1,8 @@ +{ name, uidRange ? false }: + +with import {}; + +runCommand name + { requiredSystemFeatures = if uidRange then ["uid-range"] else []; + } + "id; id > $out" diff --git a/tests/systemd-nspawn.nix b/tests/systemd-nspawn.nix new file mode 100644 index 000000000..49944eba3 --- /dev/null +++ b/tests/systemd-nspawn.nix @@ -0,0 +1,75 @@ +{ nixpkgs }: + +let + + machine = { config, pkgs, ... }: + { + system.stateVersion = "22.05"; + boot.isContainer = true; + systemd.services.console-getty.enable = false; + networking.dhcpcd.enable = false; + + services.httpd = { + enable = true; + adminAddr = "nixos@example.org"; + }; + + systemd.services.test = { + wantedBy = [ "multi-user.target" ]; + after = [ "httpd.service" ]; + script = '' + source /.env + echo "Hello World" > $out/msg + ls -lR /dev > $out/dev + ${pkgs.curl}/bin/curl -sS --fail http://localhost/ > $out/page.html + ''; + unitConfig = { + FailureAction = "exit-force"; + FailureActionExitStatus = 42; + SuccessAction = "exit-force"; + }; + }; + }; + + config = (import (nixpkgs + "/nixos/lib/eval-config.nix") { + modules = [ machine ]; + }).config; + +in + +with import nixpkgs {}; + +runCommand "test" + { buildInputs = [ config.system.path ]; + requiredSystemFeatures = [ "uid-range" ]; + toplevel = config.system.build.toplevel; + } + '' + root=$(pwd)/root + mkdir -p $root $root/etc + + export > $root/.env + + # Make /run a tmpfs to shut up a systemd warning. + mkdir /run + mount -t tmpfs none /run + chmod 0700 /run + + mount -t cgroup2 none /sys/fs/cgroup + + mkdir -p $out + + touch /etc/os-release + echo a5ea3f98dedc0278b6f3cc8c37eeaeac > /etc/machine-id + + SYSTEMD_NSPAWN_UNIFIED_HIERARCHY=1 \ + ${config.systemd.package}/bin/systemd-nspawn \ + --keep-unit \ + -M ${config.networking.hostName} -D "$root" \ + --register=no \ + --resolv-conf=off \ + --bind-ro=/nix/store \ + --bind=$out \ + --private-network \ + $toplevel/init + '' From fc1458561086a6cf2c1311294c9089785288aea3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Sun, 27 Nov 2022 18:58:21 +0100 Subject: [PATCH 129/245] Fix evaluation --- tests/systemd-nspawn.nix | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tests/systemd-nspawn.nix b/tests/systemd-nspawn.nix index 49944eba3..424436b3f 100644 --- a/tests/systemd-nspawn.nix +++ b/tests/systemd-nspawn.nix @@ -31,13 +31,16 @@ let }; }; - config = (import (nixpkgs + "/nixos/lib/eval-config.nix") { + cfg = (import (nixpkgs + "/nixos/lib/eval-config.nix") { modules = [ machine ]; - }).config; + system = "x86_64-linux"; + }); + + config = cfg.config; in -with import nixpkgs {}; +with cfg._module.args.pkgs; runCommand "test" { buildInputs = [ config.system.path ]; From 04ec1575177709dd3c1bd147cb8d0966237663d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Mon, 28 Nov 2022 10:38:23 +0100 Subject: [PATCH 130/245] repl: print a newline on ctrl-D --- src/libcmd/repl.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index bb254ff8d..ddf0d9a0a 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -270,6 +270,7 @@ void NixRepl::mainLoop() // ctrl-D should exit the debugger. state->debugStop = false; state->debugQuit = true; + std::cout << std::endl; break; } try { From 9b35cc716b392dfb96a0e994349137f653b9c532 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Mon, 28 Nov 2022 14:59:06 +0100 Subject: [PATCH 131/245] use logger->cout in order to avoid potential problems with the progress bar Co-authored-by: Eelco Dolstra --- src/libcmd/repl.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index ddf0d9a0a..557952277 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -270,7 +270,7 @@ void NixRepl::mainLoop() // ctrl-D should exit the debugger. state->debugStop = false; state->debugQuit = true; - std::cout << std::endl; + logger->cout(""); break; } try { From ff12d1c1a1bb0dcea5a9ac6b8a5036d7e5dc11ca Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 28 Nov 2022 20:49:17 +0100 Subject: [PATCH 132/245] Check that auto-allocated UIDs don't clash with existing accounts --- src/libstore/lock.cc | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/libstore/lock.cc b/src/libstore/lock.cc index 7459d837d..2858137d6 100644 --- a/src/libstore/lock.cc +++ b/src/libstore/lock.cc @@ -127,13 +127,10 @@ struct AutoUserLock : UserLock { settings.requireExperimentalFeature(Xp::AutoAllocateUids); assert(settings.startId > 0); - assert(settings.startId % maxIdsPerBuild == 0); assert(settings.uidCount % maxIdsPerBuild == 0); assert((uint64_t) settings.startId + (uint64_t) settings.uidCount <= std::numeric_limits::max()); assert(nrIds <= maxIdsPerBuild); - // FIXME: check whether the id range overlaps any known users - createDirs(settings.nixStateDir + "/userpool2"); size_t nrSlots = settings.uidCount / maxIdsPerBuild; @@ -150,11 +147,18 @@ struct AutoUserLock : UserLock throw SysError("opening user lock '%s'", fnUserLock); if (lockFile(fd.get(), ltWrite, false)) { + + auto firstUid = settings.startId + i * maxIdsPerBuild; + + auto pw = getpwuid(firstUid); + if (pw) + throw Error("auto-allocated UID %d clashes with existing user account '%s'", firstUid, pw->pw_name); + auto lock = std::make_unique(); lock->fdUserLock = std::move(fd); - lock->firstUid = settings.startId + i * maxIdsPerBuild; + lock->firstUid = firstUid; if (useChroot) - lock->firstGid = lock->firstUid; + lock->firstGid = firstUid; else { struct group * gr = getgrnam(settings.buildUsersGroup.get().c_str()); if (!gr) From 67bcb99700a0da1395fa063d7c6586740b304598 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 28 Nov 2022 21:54:02 +0100 Subject: [PATCH 133/245] Add a setting for enabling cgroups --- doc/manual/src/release-notes/rl-next.md | 5 +++-- src/libstore/build/local-derivation-goal.cc | 7 ++++++- src/libstore/globals.hh | 23 +++++++++++++++------ tests/containers.nix | 2 +- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 4c91002fb..db2bd7419 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -43,10 +43,11 @@ ``` extra-experimental-features = cgroups + use-cgroups = true ``` - to `nix.conf`. It is also automatically enabled for builds that - require the `uid-range` system feature. + to `nix.conf`. Cgroups are required for derivations that require the + `uid-range` system feature. * `nix build --json` now prints some statistics about top-level derivations, such as CPU statistics when cgroups are enabled. diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index d44694890..69a7df411 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -401,9 +401,14 @@ static void linkOrCopy(const Path & from, const Path & to) void LocalDerivationGoal::startBuilder() { if ((buildUser && buildUser->getUIDCount() != 1) - || settings.isExperimentalFeatureEnabled(Xp::Cgroups)) + #if __linux__ + || settings.useCgroups + #endif + ) { #if __linux__ + settings.requireExperimentalFeature(Xp::Cgroups); + auto ourCgroups = getCgroups("/proc/self/cgroup"); auto ourCgroup = ourCgroups[""]; if (ourCgroup == "") diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 653d108aa..b40dcfa77 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -304,6 +304,17 @@ public: "id-count", "The number of UIDs/GIDs to use for dynamic ID allocation."}; + #if __linux__ + Setting useCgroups{ + this, false, "use-cgroups", + R"( + Whether to execute builds inside cgroups. Cgroups are + enabled automatically for derivations that require the + `uid-range` system feature. + )" + }; + #endif + Setting impersonateLinux26{this, false, "impersonate-linux-26", "Whether to impersonate a Linux 2.6 machine on newer kernels.", {"build-impersonate-linux-26"}}; @@ -592,10 +603,10 @@ public: cache) must have a signature by a trusted key. A trusted key is one listed in `trusted-public-keys`, or a public key counterpart to a private key stored in a file listed in `secret-key-files`. - + Set to `false` to disable signature checking and trust all non-content-addressed paths unconditionally. - + (Content-addressed paths are inherently trustworthy and thus unaffected by this configuration option.) )"}; @@ -681,7 +692,7 @@ public: is `root`. > **Warning** - > + > > Adding a user to `trusted-users` is essentially equivalent to > giving that user root access to the system. For example, the user > can set `sandbox-paths` and thereby obtain read access to @@ -771,13 +782,13 @@ public: The program executes with no arguments. The program's environment contains the following environment variables: - - `DRV_PATH` + - `DRV_PATH` The derivation for the built paths. Example: `/nix/store/5nihn1a7pa8b25l9zafqaqibznlvvp3f-bash-4.4-p23.drv` - - `OUT_PATHS` + - `OUT_PATHS` Output paths of the built derivation, separated by a space character. @@ -815,7 +826,7 @@ public: documentation](https://ec.haxx.se/usingcurl-netrc.html). > **Note** - > + > > This must be an absolute path, and `~` is not resolved. For > example, `~/.netrc` won't resolve to your home directory's > `.netrc`. diff --git a/tests/containers.nix b/tests/containers.nix index d1e791b8c..59e953c3b 100644 --- a/tests/containers.nix +++ b/tests/containers.nix @@ -23,7 +23,7 @@ makeTest ({ nix.binaryCaches = lib.mkForce [ ]; nix.extraOptions = '' - extra-experimental-features = nix-command auto-allocate-uids + extra-experimental-features = nix-command auto-allocate-uids cgroups extra-system-features = uid-range ''; nix.nixPath = [ "nixpkgs=${nixpkgs}" ]; From 7dd3e1fec47b9dd6aa6a0b9a58962078a8499453 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 28 Nov 2022 22:04:51 +0100 Subject: [PATCH 134/245] Add example --- doc/manual/src/release-notes/rl-next.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index db2bd7419..8b314b5f6 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -26,7 +26,8 @@ * On Linux, Nix can now run builds in a user namespace where the build runs as root (UID 0) and has 65,536 UIDs available. This is primarily useful for running containers such as `systemd-nspawn` - inside a Nix build. + inside a Nix build. For an example, see + https://github.com/NixOS/nix/blob/67bcb99700a0da1395fa063d7c6586740b304598/tests/systemd-nspawn.nix. A build can enable this by requiring the `uid-range` system feature, i.e. by setting the derivation attribute From dc61e1028d73db406d47c0fb979219f193b74777 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 28 Nov 2022 22:00:44 +0000 Subject: [PATCH 135/245] Bump zeebe-io/backport-action from 0.0.8 to 0.0.9 Bumps [zeebe-io/backport-action](https://github.com/zeebe-io/backport-action) from 0.0.8 to 0.0.9. - [Release notes](https://github.com/zeebe-io/backport-action/releases) - [Commits](https://github.com/zeebe-io/backport-action/compare/v0.0.8...v0.0.9) --- updated-dependencies: - dependency-name: zeebe-io/backport-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/backport.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 75be788ef..7568145b6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -21,7 +21,7 @@ jobs: fetch-depth: 0 - name: Create backport PRs # should be kept in sync with `version` - uses: zeebe-io/backport-action@v0.0.8 + uses: zeebe-io/backport-action@v0.0.9 with: # Config README: https://github.com/zeebe-io/backport-action#backport-action github_token: ${{ secrets.GITHUB_TOKEN }} From 46a6be28bef45640de5344a09d56add7068a9aa4 Mon Sep 17 00:00:00 2001 From: Rok Garbas Date: Tue, 29 Nov 2022 10:01:46 +0000 Subject: [PATCH 136/245] Add nobody user/group to Nix docker image --- docker.nix | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docker.nix b/docker.nix index bb2b4e7ff..203a06b53 100644 --- a/docker.nix +++ b/docker.nix @@ -36,6 +36,17 @@ let shell = "${pkgs.bashInteractive}/bin/bash"; home = "/root"; gid = 0; + groups = [ "root" ]; + description = "System administrator"; + }; + + nobody = { + uid = 65534; + shell = "${pkgs.shadow}/bin/nologin"; + home = "/var/empty"; + gid = 65534; + groups = [ "nobody" ]; + description = "Unprivileged account (don't use!)"; }; } // lib.listToAttrs ( @@ -57,6 +68,7 @@ let groups = { root.gid = 0; nixbld.gid = 30000; + nobody.gid = 65534; }; userToPasswd = ( From 9c90452f9d211ebac3657745283fae0f00e2bd71 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Sat, 26 Nov 2022 21:06:29 +0100 Subject: [PATCH 137/245] bump nixpkgs to 22.11 beta --- flake.lock | 8 ++++---- flake.nix | 14 +++++--------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/flake.lock b/flake.lock index a66c9cb1b..be19cca6b 100644 --- a/flake.lock +++ b/flake.lock @@ -18,16 +18,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1657693803, - "narHash": "sha256-G++2CJ9u0E7NNTAi9n5G8TdDmGJXcIjkJ3NF8cetQB8=", + "lastModified": 1669425120, + "narHash": "sha256-m/sEyGBDAq+Th4NVaPRhrJ5sljReCebYiQcbDlqp0ww=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "365e1b3a859281cf11b94f87231adeabbdd878a2", + "rev": "e22d9c397e5e6d92771cc1534e7769f2167c2952", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-22.05-small", + "ref": "nixos-22.11-small", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index cc2a48d9c..e0e0e27fe 100644 --- a/flake.nix +++ b/flake.nix @@ -1,7 +1,7 @@ { description = "The purely functional package manager"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.05-small"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-22.11-small"; inputs.nixpkgs-regression.url = "github:NixOS/nixpkgs/215d4d0fd80ca5163643b03a33fde804a29cc1e2"; inputs.lowdown-src = { url = "github:kristapsdz/lowdown"; flake = false; }; @@ -108,7 +108,7 @@ ++ lib.optionals stdenv.hostPlatform.isLinux [(buildPackages.util-linuxMinimal or buildPackages.utillinuxMinimal)]; buildDeps = - [ (curl.override { patchNetrcRegression = true; }) + [ curl bzip2 xz brotli editline openssl sqlite libarchive @@ -127,13 +127,9 @@ }); propagatedDeps = - [ ((boehmgc.override { + [ (boehmgc.override { enableLargeConfig = true; - }).overrideAttrs(o: { - patches = (o.patches or []) ++ [ - ./boehmgc-coroutine-sp-fallback.diff - ]; - })) + }) nlohmann_json ]; }; @@ -364,7 +360,7 @@ buildInputs = [ nix - (curl.override { patchNetrcRegression = true; }) + curl bzip2 xz pkgs.perl From 4f762e2b023fd451fdbab0de8d6394dd7201640d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 29 Nov 2022 13:10:53 +0100 Subject: [PATCH 138/245] Restore ownership of / for non-uid-range builds --- src/libstore/build/local-derivation-goal.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index 69a7df411..359966288 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -646,8 +646,7 @@ void LocalDerivationGoal::startBuilder() if (mkdir(chrootRootDir.c_str(), buildUser && buildUser->getUIDCount() != 1 ? 0755 : 0750) == -1) throw SysError("cannot create '%1%'", chrootRootDir); - // FIXME: only make root writable for user namespace builds. - if (buildUser && chown(chrootRootDir.c_str(), buildUser->getUID(), buildUser->getGID()) == -1) + if (buildUser && chown(chrootRootDir.c_str(), buildUser->getUIDCount() != 1 ? buildUser->getUID() : 0, buildUser->getGID()) == -1) throw SysError("cannot change ownership of '%1%'", chrootRootDir); /* Create a writable /tmp in the chroot. Many builders need From 0b092bd87f35e463fea66ddd40639e7b260680a0 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 30 Nov 2022 13:46:33 +0100 Subject: [PATCH 139/245] nix store make-content-addressed: Fix JSON construction Fixes error: [json.exception.type_error.301] cannot create object from initializer list in tests/fetchClosure.sh. --- src/nix/make-content-addressed.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nix/make-content-addressed.cc b/src/nix/make-content-addressed.cc index f2e4cefbe..f236bebd6 100644 --- a/src/nix/make-content-addressed.cc +++ b/src/nix/make-content-addressed.cc @@ -43,7 +43,7 @@ struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand, assert(i != remappings.end()); jsonRewrites[srcStore->printStorePath(path)] = srcStore->printStorePath(i->second); } - std::cout << json::object({"rewrites", jsonRewrites}).dump(); + std::cout << nlohmann::json{"rewrites", jsonRewrites}.dump(); } else { for (auto & path : storePaths) { auto i = remappings.find(path); From 3f881e3378743039b32f3d3c650255b448732803 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 30 Nov 2022 23:40:53 +0100 Subject: [PATCH 140/245] add missing newline, for consistent formatting --- doc/manual/generate-manpage.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/manual/generate-manpage.nix b/doc/manual/generate-manpage.nix index 057719e34..8c7c4d358 100644 --- a/doc/manual/generate-manpage.nix +++ b/doc/manual/generate-manpage.nix @@ -99,6 +99,7 @@ let in [ cmd ] ++ concatMap subcommand (attrNames details.commands or {}); parsedToplevel = builtins.fromJSON toplevel; + manpages = processCommand { command = "nix"; details = parsedToplevel; From 0ea62670eda39ebeaff5335fd8225707ec27e7a3 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 1 Dec 2022 04:40:02 +0100 Subject: [PATCH 141/245] move documentation on `auto-allocate-uids` to options docs this is where it belongs and can be found together with the other options. --- doc/manual/src/release-notes/rl-next.md | 42 +++++++++++-------------- src/libstore/globals.hh | 36 +++++++++++++++++---- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 8b314b5f6..bf51aa1f7 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -12,43 +12,37 @@ ([#7260](https://github.com/NixOS/nix/pull/7260)). * Nix can now automatically pick UIDs for builds, removing the need to - create `nixbld*` user accounts. These UIDs are allocated starting at - 872415232 (0x34000000) on Linux and 56930 on macOS. + create `nixbld*` user accounts. - This is an experimental feature. To enable it, add the following to - `nix.conf`: + See [`auto-allocate-uids`]. - ``` - extra-experimental-features = auto-allocate-uids - auto-allocate-uids = true - ``` + [`auto-allocate-uids`]: (../command-ref/conf-file.md#conf-auto-allocate-uids) * On Linux, Nix can now run builds in a user namespace where the build - runs as root (UID 0) and has 65,536 UIDs available. This is - primarily useful for running containers such as `systemd-nspawn` - inside a Nix build. For an example, see - https://github.com/NixOS/nix/blob/67bcb99700a0da1395fa063d7c6586740b304598/tests/systemd-nspawn.nix. + runs as root (UID 0) and has 65,536 UIDs available. - A build can enable this by requiring the `uid-range` system feature, - i.e. by setting the derivation attribute + + + This is primarily useful for running containers such as `systemd-nspawn` + inside a Nix build. For an example, see [`tests/systemd-nspawn/nix`][nspawn]. + + [nspawn]: https://github.com/NixOS/nix/blob/67bcb99700a0da1395fa063d7c6586740b304598/tests/systemd-nspawn.nix. + + A build can enable this by by setting the derivation attribute: ``` requiredSystemFeatures = [ "uid-range" ]; ``` - The `uid-range` system feature requires the `auto-allocate-uids` - setting to be enabled (see above). + The `uid-range` [system feature] requires the [`auto-allocate-uids`] + setting to be enabled. + + [system feature]: (../command-ref/conf-file.md#conf-system-features), * On Linux, Nix has experimental support for running builds inside a - cgroup. It can be enabled by adding + cgroup. - ``` - extra-experimental-features = cgroups - use-cgroups = true - ``` - - to `nix.conf`. Cgroups are required for derivations that require the - `uid-range` system feature. + See [`use-cgroups`](../command-ref/conf-file.md#conf-use-cgroups). * `nix build --json` now prints some statistics about top-level derivations, such as CPU statistics when cgroups are enabled. diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index b40dcfa77..b61a34461 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -284,7 +284,21 @@ public: )"}; Setting autoAllocateUids{this, false, "auto-allocate-uids", - "Whether to allocate UIDs for builders automatically."}; + R"( + Whether to allocate UIDs for builders automatically. + + These UIDs are allocated starting at 872415232 (0x34000000) on Linux and 56930 on macOS. + + > **Warning** + > This is an experimental feature. + + To enable it, add the following to [`nix.conf`](#): + + ``` + extra-experimental-features = auto-allocate-uids + auto-allocate-uids = true + ``` + )"}; Setting startId{this, #if __linux__ @@ -308,11 +322,21 @@ public: Setting useCgroups{ this, false, "use-cgroups", R"( - Whether to execute builds inside cgroups. Cgroups are - enabled automatically for derivations that require the - `uid-range` system feature. - )" - }; + Whether to execute builds inside cgroups. + Only on Linux with systemd. + + cgroups are required and enabled automatically for derivations that require the `uid-range` system feature. + + > **Warning** + > This is an experimental feature. + + To enable it, add the following to [`nix.conf`](#): + + ``` + extra-experimental-features = cgroups + use-cgroups = true + ``` + )"}; #endif Setting impersonateLinux26{this, false, "impersonate-linux-26", From ad467265466dbccc816f550def9455ee720c5d3d Mon Sep 17 00:00:00 2001 From: Alex Ameen Date: Wed, 30 Nov 2022 22:53:41 -0600 Subject: [PATCH 142/245] doc: listToAttrs: document repeated keys --- src/libexpr/primops.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 05265411c..3572ca181 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2420,12 +2420,15 @@ static RegisterPrimOp primop_listToAttrs({ Construct a set from a list specifying the names and values of each attribute. Each element of the list should be a set consisting of a string-valued attribute `name` specifying the name of the attribute, - and an attribute `value` specifying its value. Example: + and an attribute `value` specifying its value. + In case of duplicate occurrences of the same name, the first + takes precedence. Example: ```nix builtins.listToAttrs [ { name = "foo"; value = 123; } { name = "bar"; value = 456; } + { name = "bar"; value = 420; } ] ``` From dfa27e6b2feb082b0a276338868b069458ec00db Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 1 Dec 2022 03:37:14 +0100 Subject: [PATCH 143/245] refactor rendering documentation of options this makes more obvious what the code produces, and the structure of the output easier to change --- doc/manual/generate-options.nix | 66 +++++++++++++++++++-------------- 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/doc/manual/generate-options.nix b/doc/manual/generate-options.nix index 680b709c8..814144c20 100644 --- a/doc/manual/generate-options.nix +++ b/doc/manual/generate-options.nix @@ -1,29 +1,41 @@ -with builtins; -with import ./utils.nix; +let + inherit (builtins) attrNames concatStringsSep isAttrs isBool; + inherit (import ./utils.nix) concatStrings squash splitLines; +in -options: +optionsInfo: +let + showOption = name: + let + inherit (optionsInfo.${name}) description documentDefault defaultValue aliases; + result = squash '' + - [`${name}`]{#conf-${name}} -concatStrings (map - (name: - let option = options.${name}; in - " - [`${name}`](#conf-${name})" - + "

\n\n" - + concatStrings (map (s: " ${s}\n") (splitLines option.description)) + "\n\n" - + (if option.documentDefault - then " **Default:** " + ( - if option.defaultValue == "" || option.defaultValue == [] - then "*empty*" - else if isBool option.defaultValue - then (if option.defaultValue then "`true`" else "`false`") - else - # n.b. a StringMap value type is specified as a string, but - # this shows the value type. The empty stringmap is "null" in - # JSON, but that converts to "{ }" here. - (if isAttrs option.defaultValue then "`\"\"`" - else "`" + toString option.defaultValue + "`")) + "\n\n" - else " **Default:** *machine-specific*\n") - + (if option.aliases != [] - then " **Deprecated alias:** " + (concatStringsSep ", " (map (s: "`${s}`") option.aliases)) + "\n\n" - else "") - ) - (attrNames options)) + ${indent " " body} + ''; + # separate body to cleanly handle indentation + body = '' + ${description} + + **Default:** ${showDefault documentDefault defaultValue} + + ${showAliases aliases} + ''; + showDefault = documentDefault: defaultValue: + if documentDefault then + # a StringMap value type is specified as a string, but + # this shows the value type. The empty stringmap is `null` in + # JSON, but that converts to `{ }` here. + if defaultValue == "" || defaultValue == [] || isAttrs defaultValue + then "*empty*" + else if isBool defaultValue then + if defaultValue then "`true`" else "`false`" + else "`${toString defaultValue}`" + else "*machine-specific*"; + showAliases = aliases: + if aliases == [] then "" else + "**Deprecated alias:** ${(concatStringsSep ", " (map (s: "`${s}`") aliases))}"; + indent = prefix: s: + concatStringsSep "\n" (map (x: if x == "" then x else "${prefix}${x}") (splitLines s)); + in result; +in concatStrings (map showOption (attrNames optionsInfo)) From ec18b7d09b04f3bc4f4de81136fcb79a315bc829 Mon Sep 17 00:00:00 2001 From: Alex Ameen Date: Wed, 30 Nov 2022 23:21:09 -0600 Subject: [PATCH 144/245] doc: listToAttrs: fix line wrapping --- src/libexpr/primops.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 3572ca181..3e7fdf9d6 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2420,9 +2420,8 @@ static RegisterPrimOp primop_listToAttrs({ Construct a set from a list specifying the names and values of each attribute. Each element of the list should be a set consisting of a string-valued attribute `name` specifying the name of the attribute, - and an attribute `value` specifying its value. - In case of duplicate occurrences of the same name, the first - takes precedence. Example: + and an attribute `value` specifying its value. In case of duplicate + occurrences of the same name, the first takes precedence. Example: ```nix builtins.listToAttrs From f1e1ba9fe094a774f0fd05e537228e628d0bc8cb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 1 Dec 2022 16:29:09 +0100 Subject: [PATCH 145/245] Really fix 'nix store make-content-addressed --json' https://hydra.nixos.org/log/mcgypcf9vj4n8vdmw7lj3l05c899v73w-nix-2.12.0pre20221201_16b03f0-x86_64-unknown-linux-musl.drv --- src/nix/make-content-addressed.cc | 6 ++++-- tests/fetchClosure.sh | 21 ++++++++++++--------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/nix/make-content-addressed.cc b/src/nix/make-content-addressed.cc index f236bebd6..d86b90fc7 100644 --- a/src/nix/make-content-addressed.cc +++ b/src/nix/make-content-addressed.cc @@ -37,13 +37,15 @@ struct CmdMakeContentAddressed : virtual CopyCommand, virtual StorePathsCommand, StorePathSet(storePaths.begin(), storePaths.end())); if (json) { - nlohmann::json jsonRewrites = json::object(); + auto jsonRewrites = json::object(); for (auto & path : storePaths) { auto i = remappings.find(path); assert(i != remappings.end()); jsonRewrites[srcStore->printStorePath(path)] = srcStore->printStorePath(i->second); } - std::cout << nlohmann::json{"rewrites", jsonRewrites}.dump(); + auto json = json::object(); + json["rewrites"] = jsonRewrites; + std::cout << json.dump(); } else { for (auto & path : storePaths) { auto i = remappings.find(path); diff --git a/tests/fetchClosure.sh b/tests/fetchClosure.sh index 44050c878..d88c55c3c 100644 --- a/tests/fetchClosure.sh +++ b/tests/fetchClosure.sh @@ -1,7 +1,6 @@ source common.sh enableFeatures "fetch-closure" -needLocalStore "'--no-require-sigs' can’t be used with the daemon" clearStore clearCacheCache @@ -28,15 +27,19 @@ clearStore [ ! -e $nonCaPath ] [ -e $caPath ] -# In impure mode, we can use non-CA paths. -[[ $(nix eval --raw --no-require-sigs --impure --expr " - builtins.fetchClosure { - fromStore = \"file://$cacheDir\"; - fromPath = $nonCaPath; - } -") = $nonCaPath ]] +if [[ "$NIX_REMOTE" != "daemon" ]]; then -[ -e $nonCaPath ] + # In impure mode, we can use non-CA paths. + [[ $(nix eval --raw --no-require-sigs --impure --expr " + builtins.fetchClosure { + fromStore = \"file://$cacheDir\"; + fromPath = $nonCaPath; + } + ") = $nonCaPath ]] + + [ -e $nonCaPath ] + +fi # 'toPath' set to empty string should fail but print the expected path. nix eval -v --json --expr " From ef524013aad45aec846d27a7309cbfb1afdf54c4 Mon Sep 17 00:00:00 2001 From: Alex Ameen Date: Thu, 1 Dec 2022 10:32:45 -0600 Subject: [PATCH 146/245] doc: listToAttrs: add extra whitespace --- src/libexpr/primops.cc | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 3e7fdf9d6..8a4c19f7c 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2420,8 +2420,12 @@ static RegisterPrimOp primop_listToAttrs({ Construct a set from a list specifying the names and values of each attribute. Each element of the list should be a set consisting of a string-valued attribute `name` specifying the name of the attribute, - and an attribute `value` specifying its value. In case of duplicate - occurrences of the same name, the first takes precedence. Example: + and an attribute `value` specifying its value. + + In case of duplicate occurrences of the same name, the first + takes precedence. + + Example: ```nix builtins.listToAttrs From 5b954123282f7d795f4265b02c65b8bc79d28cf3 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Thu, 1 Dec 2022 19:04:04 +0100 Subject: [PATCH 147/245] =?UTF-8?q?encourage=20adding=20=F0=9F=91=8D=20to?= =?UTF-8?q?=20express=20interest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit decided on the @NixOS/documentation-team, see NixOS/nix.dev#359 for more information --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++++ .github/ISSUE_TEMPLATE/feature_request.md | 4 ++++ .github/ISSUE_TEMPLATE/missing_documentation.md | 3 +++ .github/PULL_REQUEST_TEMPLATE/pull_request_template.md | 4 ++++ 4 files changed, 15 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index e6d346bc1..984f9a9ea 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -30,3 +30,7 @@ A clear and concise description of what you expected to happen. **Additional context** Add any other context about the problem here. + +**Priorities** + +Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc). diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 4fe86d5ec..42c658b52 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -18,3 +18,7 @@ A clear and concise description of any alternative solutions or features you've **Additional context** Add any other context or screenshots about the feature request here. + +**Priorities** + +Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc). diff --git a/.github/ISSUE_TEMPLATE/missing_documentation.md b/.github/ISSUE_TEMPLATE/missing_documentation.md index fbabd868e..942d7a971 100644 --- a/.github/ISSUE_TEMPLATE/missing_documentation.md +++ b/.github/ISSUE_TEMPLATE/missing_documentation.md @@ -26,3 +26,6 @@ assignees: '' +## Priorities + +Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc). diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md index 537aa0909..5311be01f 100644 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md @@ -5,3 +5,7 @@ Please include relevant [release notes](https://github.com/NixOS/nix/blob/master **Testing** If this issue is a regression or something that should block release, please consider including a test either in the [testsuite](https://github.com/NixOS/nix/tree/master/tests) or as a [hydraJob]( https://github.com/NixOS/nix/blob/master/flake.nix#L396) so that it can be part of the [automatic checks](https://hydra.nixos.org/jobset/nix/master). + +**Priorities** + +Add :+1: to [pull requests you find important](https://github.com/NixOS/nix/pulls?q=is%3Aopen+sort%3Areactions-%2B1-desc). From ac06c8d2488aeffa79ebff832db0d267139b8b4c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Dec 2022 11:56:06 +0100 Subject: [PATCH 148/245] Revert "docs: drop shell prompt character for consistency" This reverts commit c068cce107b94f23c5288521770b2c807729a052 because it makes the docs *less* consistent. --- .../src/installation/installing-binary.md | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/doc/manual/src/installation/installing-binary.md b/doc/manual/src/installation/installing-binary.md index eea11a428..31faeadc2 100644 --- a/doc/manual/src/installation/installing-binary.md +++ b/doc/manual/src/installation/installing-binary.md @@ -3,7 +3,7 @@ The easiest way to install Nix is to run the following command: ```console -sh <(curl -L https://nixos.org/nix/install) +$ sh <(curl -L https://nixos.org/nix/install) ``` This will run the installer interactively (causing it to explain what @@ -27,7 +27,7 @@ you can authenticate with `sudo`. To explicitly select a single-user installation on your system: ```console -sh <(curl -L https://nixos.org/nix/install) --no-daemon +$ sh <(curl -L https://nixos.org/nix/install) --no-daemon ``` This will perform a single-user installation of Nix, meaning that `/nix` @@ -37,8 +37,8 @@ if it doesn’t already exist. If you don’t have `sudo`, you should manually create `/nix` first as root, e.g.: ```console -mkdir /nix -chown alice /nix +$ mkdir /nix +$ chown alice /nix ``` The install script will modify the first writable file from amongst @@ -50,7 +50,7 @@ the install script to disable this behaviour. You can uninstall Nix simply by running: ```console -rm -rf /nix +$ rm -rf /nix ``` # Multi User Installation @@ -66,7 +66,7 @@ You can instruct the installer to perform a multi-user installation on your system: ```console -sh <(curl -L https://nixos.org/nix/install) --daemon +$ sh <(curl -L https://nixos.org/nix/install) --daemon ``` The multi-user installation of Nix will create build users between the @@ -274,7 +274,7 @@ These install scripts can be used the same as the main NixOS.org installation script: ```console -sh <(curl -L https://nixos.org/nix/install) +$ sh <(curl -L https://nixos.org/nix/install) ``` In the same directory of the install script are sha256 sums, and gpg @@ -289,10 +289,10 @@ it somewhere (e.g. in `/tmp`), and then run the script named `install` inside the binary tarball: ```console -cd /tmp -tar xfj nix-1.8-x86_64-darwin.tar.bz2 -cd nix-1.8-x86_64-darwin -./install +$ cd /tmp +$ tar xfj nix-1.8-x86_64-darwin.tar.bz2 +$ cd nix-1.8-x86_64-darwin +$ ./install ``` If you need to edit the multi-user installation script to use different From 1211e59a038379026496bbee4b203bbd66833b01 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Dec 2022 12:38:03 +0100 Subject: [PATCH 149/245] Move cgroup.{cc,hh} to libutil --- src/{libstore => libutil}/cgroup.cc | 0 src/{libstore => libutil}/cgroup.hh | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/{libstore => libutil}/cgroup.cc (100%) rename src/{libstore => libutil}/cgroup.hh (100%) diff --git a/src/libstore/cgroup.cc b/src/libutil/cgroup.cc similarity index 100% rename from src/libstore/cgroup.cc rename to src/libutil/cgroup.cc diff --git a/src/libstore/cgroup.hh b/src/libutil/cgroup.hh similarity index 100% rename from src/libstore/cgroup.hh rename to src/libutil/cgroup.hh From 1e6a5d1ff6e8ef5bf340502f74c4d5039cedc67a Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Dec 2022 12:57:41 +0100 Subject: [PATCH 150/245] Clean up cgroup handling in getMaxCPU() Also, don't assume in LocalDerivationGoal that cgroups are mounted on /sys/fs/cgroup. --- src/libstore/build/local-derivation-goal.cc | 6 ++- src/libutil/cgroup.cc | 17 +++++++ src/libutil/cgroup.hh | 2 + src/libutil/util.cc | 49 +++++++-------------- 4 files changed, 39 insertions(+), 35 deletions(-) diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index c9b7b24f3..d2798888b 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -409,12 +409,16 @@ void LocalDerivationGoal::startBuilder() #if __linux__ settings.requireExperimentalFeature(Xp::Cgroups); + auto cgroupFS = getCgroupFS(); + if (!cgroupFS) + throw Error("cannot determine the cgroups file system"); + auto ourCgroups = getCgroups("/proc/self/cgroup"); auto ourCgroup = ourCgroups[""]; if (ourCgroup == "") throw Error("cannot determine cgroup name from /proc/self/cgroup"); - auto ourCgroupPath = canonPath("/sys/fs/cgroup/" + ourCgroup); + auto ourCgroupPath = canonPath(*cgroupFS + "/" + ourCgroup); if (!pathExists(ourCgroupPath)) throw Error("expected cgroup directory '%s'", ourCgroupPath); diff --git a/src/libutil/cgroup.cc b/src/libutil/cgroup.cc index f693d77be..a008481ca 100644 --- a/src/libutil/cgroup.cc +++ b/src/libutil/cgroup.cc @@ -2,6 +2,7 @@ #include "cgroup.hh" #include "util.hh" +#include "finally.hh" #include #include @@ -10,9 +11,25 @@ #include #include +#include namespace nix { +std::optional getCgroupFS() +{ + static auto res = [&]() -> std::optional { + auto fp = fopen("/proc/mounts", "r"); + if (!fp) return std::nullopt; + Finally delFP = [&]() { fclose(fp); }; + while (auto ent = getmntent(fp)) + if (std::string_view(ent->mnt_type) == "cgroup2") + return ent->mnt_dir; + + return std::nullopt; + }(); + return res; +} + // FIXME: obsolete, check for cgroup2 std::map getCgroups(const Path & cgroupFile) { diff --git a/src/libutil/cgroup.hh b/src/libutil/cgroup.hh index 3ead4735f..d08c8ad29 100644 --- a/src/libutil/cgroup.hh +++ b/src/libutil/cgroup.hh @@ -9,6 +9,8 @@ namespace nix { +std::optional getCgroupFS(); + std::map getCgroups(const Path & cgroupFile); struct CgroupStats diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 623b74bdd..2c2aae82e 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -2,6 +2,7 @@ #include "sync.hh" #include "finally.hh" #include "serialise.hh" +#include "cgroup.hh" #include #include @@ -36,7 +37,6 @@ #include #include -#include #include #endif @@ -727,43 +727,24 @@ unsigned int getMaxCPU() { #if __linux__ try { - FILE *fp = fopen("/proc/mounts", "r"); - if (!fp) - return 0; + auto cgroupFS = getCgroupFS(); + if (!cgroupFS) return 0; - Strings cgPathParts; + if (!pathExists("/proc/self/cgroup")) return 0; - struct mntent *ent; - while ((ent = getmntent(fp))) { - std::string mountType, mountPath; + auto cgroups = getCgroups("/proc/self/cgroup"); + auto cgroup = cgroups[""]; + if (cgroup == "") return 0; - mountType = ent->mnt_type; - mountPath = ent->mnt_dir; + auto cpuFile = *cgroupFS + "/" + cgroup + "/cpu.max"; - if (mountType == "cgroup2") { - cgPathParts.push_back(mountPath); - break; - } - } - - fclose(fp); - - if (cgPathParts.size() > 0 && pathExists("/proc/self/cgroup")) { - std::string currentCgroup = readFile("/proc/self/cgroup"); - Strings cgValues = tokenizeString(currentCgroup, ":"); - cgPathParts.push_back(trim(cgValues.back(), "\n")); - cgPathParts.push_back("cpu.max"); - std::string fullCgPath = canonPath(concatStringsSep("/", cgPathParts)); - - if (pathExists(fullCgPath)) { - std::string cpuMax = readFile(fullCgPath); - std::vector cpuMaxParts = tokenizeString>(cpuMax, " "); - std::string quota = cpuMaxParts[0]; - std::string period = trim(cpuMaxParts[1], "\n"); - - if (quota != "max") - return std::ceil(std::stoi(quota) / std::stof(period)); - } + if (pathExists(cpuFile)) { + auto cpuMax = readFile(cpuFile); + auto cpuMaxParts = tokenizeString>(cpuMax, " \n"); + auto quota = cpuMaxParts[0]; + auto period = cpuMaxParts[1]; + if (quota != "max") + return std::ceil(std::stoi(quota) / std::stof(period)); } } catch (Error &) { ignoreException(); } #endif From fa99ef6a879e77024d60e73901a4773c6756c1bb Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 2 Dec 2022 15:03:40 +0100 Subject: [PATCH 151/245] getMaxCPU(): Lower verbosity level for ignored exceptions Fixes #7268. --- src/libutil/util.cc | 22 +++++++++------------- src/libutil/util.hh | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 2c2aae82e..a93ef1901 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -730,23 +730,19 @@ unsigned int getMaxCPU() auto cgroupFS = getCgroupFS(); if (!cgroupFS) return 0; - if (!pathExists("/proc/self/cgroup")) return 0; - - auto cgroups = getCgroups("/proc/self/cgroup"); + auto cgroups = getCgroups("/proc/self/cgroupp"); auto cgroup = cgroups[""]; if (cgroup == "") return 0; auto cpuFile = *cgroupFS + "/" + cgroup + "/cpu.max"; - if (pathExists(cpuFile)) { - auto cpuMax = readFile(cpuFile); - auto cpuMaxParts = tokenizeString>(cpuMax, " \n"); - auto quota = cpuMaxParts[0]; - auto period = cpuMaxParts[1]; - if (quota != "max") + auto cpuMax = readFile(cpuFile); + auto cpuMaxParts = tokenizeString>(cpuMax, " \n"); + auto quota = cpuMaxParts[0]; + auto period = cpuMaxParts[1]; + if (quota != "max") return std::ceil(std::stoi(quota) / std::stof(period)); - } - } catch (Error &) { ignoreException(); } + } catch (Error &) { ignoreException(lvlDebug); } #endif return 0; @@ -1408,7 +1404,7 @@ std::string shellEscape(const std::string_view s) } -void ignoreException() +void ignoreException(Verbosity lvl) { /* Make sure no exceptions leave this function. printError() also throws when remote is closed. */ @@ -1416,7 +1412,7 @@ void ignoreException() try { throw; } catch (std::exception & e) { - printError("error (ignored): %1%", e.what()); + printMsg(lvl, "error (ignored): %1%", e.what()); } } catch (...) { } } diff --git a/src/libutil/util.hh b/src/libutil/util.hh index e5c678682..94d8cc555 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -528,7 +528,7 @@ std::string shellEscape(const std::string_view s); /* Exception handling in destructors: print an error message, then ignore the exception. */ -void ignoreException(); +void ignoreException(Verbosity lvl = lvlError); From 19c53949714ffc6bf0e5d78aa6dd94cae2febc22 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 2 Dec 2022 09:00:03 -0500 Subject: [PATCH 152/245] Change "while evaluating " to "while *calling*" in trace MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The old way was not correct. Here is an example: ``` $ nix-instantiate --eval --expr 'let x = a: throw "asdf"; in x 1' --show-trace error: asdf … while evaluating 'x' at «string»:1:9: 1| let x = a: throw "asdf"; in x 1 | ^ … from call site at «string»:1:29: 1| let x = a: throw "asdf"; in x 1 | ^ ``` and yet also: ``` $ nix-instantiate --eval --expr 'let x = a: throw "asdf"; in x' --show-trace ``` Here is the thing: in both cases we are evaluating `x`! Nix is a higher-order languages, and functions are a sort of value. When we write `x = a: ...`, `a: ...` is the expression that `x` is being defined to be, and that is already a value. Therefore, we should *never* get an trace that says "while evaluating `x`", because evaluating `a: ...` is *trival* and nothing happens during it! What is actually happening here is we are applying `x` and evaluating its *body* with arguments substituted for parameters. I think the simplest way to say is just "while *calling* `x`", and so that is what I changed it to. --- src/libexpr/eval.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index c759acc10..76a10b9f8 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1646,7 +1646,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & auto dts = debugRepl ? makeDebugTraceStacker( *this, *lambda.body, env2, positions[lambda.pos], - "while evaluating %s", + "while calling %s", lambda.name ? concatStrings("'", symbols[lambda.name], "'") : "anonymous lambda") @@ -1655,7 +1655,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & lambda.body->eval(*this, env2, vCur); } catch (Error & e) { if (loggerSettings.showTrace.get()) { - addErrorTrace(e, lambda.pos, "while evaluating %s", + addErrorTrace(e, lambda.pos, "while calling %s", (lambda.name ? concatStrings("'", symbols[lambda.name], "'") : "anonymous lambda")); From ff62f6a84b6a845d11ea2cd4551bfb3536249755 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Fri, 2 Dec 2022 20:24:05 +0100 Subject: [PATCH 153/245] tests/fetchGitSubmodules: fix for newer Git --- tests/fetchGitSubmodules.sh | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/fetchGitSubmodules.sh b/tests/fetchGitSubmodules.sh index 5f104355f..50da4cb97 100644 --- a/tests/fetchGitSubmodules.sh +++ b/tests/fetchGitSubmodules.sh @@ -14,6 +14,15 @@ subRepo=$TEST_ROOT/gitSubmodulesSub rm -rf ${rootRepo} ${subRepo} $TEST_HOME/.cache/nix +# Submodules can't be fetched locally by default, which can cause +# information leakage vulnerabilities, but for these tests our +# submodule is intentionally local and it's all trusted, so we +# disable this restriction. Setting it per repo is not sufficient, as +# the repo-local config does not apply to the commands run from +# outside the repos by Nix. +export XDG_CONFIG_HOME=$TEST_HOME/.config +git config --global protocol.file.allow always + initGitRepo() { git init $1 git -C $1 config user.email "foobar@example.com" From cccd57c022753c3ad727847c9c83c9d2c9c639e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 4 Dec 2022 18:22:11 +0100 Subject: [PATCH 154/245] getMaxCPU: fix cgroup path Given this typo I am not sure if it has been tested. --- src/libutil/util.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libutil/util.cc b/src/libutil/util.cc index a93ef1901..4f2caaa40 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -730,7 +730,7 @@ unsigned int getMaxCPU() auto cgroupFS = getCgroupFS(); if (!cgroupFS) return 0; - auto cgroups = getCgroups("/proc/self/cgroupp"); + auto cgroups = getCgroups("/proc/self/cgroup"); auto cgroup = cgroups[""]; if (cgroup == "") return 0; From 67d76cb8df0b484dca5ce907a182f578650e8356 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 5 Dec 2022 16:34:39 +0100 Subject: [PATCH 155/245] Fix typo --- maintainers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maintainers/README.md b/maintainers/README.md index 5a744fb91..b46d08fe3 100644 --- a/maintainers/README.md +++ b/maintainers/README.md @@ -43,7 +43,7 @@ Issues on the board progress through the following states: - To discuss - Pull requests and issues that are important and controverisal are discussed by the team during discussion meetings. + Pull requests and issues that are important and controversial are discussed by the team during discussion meetings. This may be where the merit of the change itself or the implementation strategy is contested by a team member. From 647e60b35674b6d44f5260fd27ea37705c298ecf Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 5 Dec 2022 16:35:25 +0100 Subject: [PATCH 156/245] maintainers/README.md: Use CET --- maintainers/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/maintainers/README.md b/maintainers/README.md index b46d08fe3..60768db0a 100644 --- a/maintainers/README.md +++ b/maintainers/README.md @@ -16,12 +16,12 @@ The goal of the team is to help other people to contribute to Nix. The team meets twice a week: -- Discussion meeting: [Fridays 12:00-13:00 UTC](https://calendar.google.com/calendar/event?eid=MHNtOGVuNWtrZXNpZHR2bW1sM3QyN2ZjaGNfMjAyMjExMjVUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn) +- Discussion meeting: [Fridays 13:00-14:00 CET](https://calendar.google.com/calendar/event?eid=MHNtOGVuNWtrZXNpZHR2bW1sM3QyN2ZjaGNfMjAyMjExMjVUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn) 1. Triage issues and pull requests from the _No Status_ column (30 min) 2. Discuss issues and pull requests from the _To discuss_ column (30 min) -- Work meeting: [Mondays 12:00-14:00 UTC](https://calendar.google.com/calendar/event?eid=NTM1MG1wNGJnOGpmOTZhYms3bTB1bnY5cWxfMjAyMjExMjFUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn) +- Work meeting: [Mondays 13:00-15:00 CET](https://calendar.google.com/calendar/event?eid=NTM1MG1wNGJnOGpmOTZhYms3bTB1bnY5cWxfMjAyMjExMjFUMTIwMDAwWiBiOW81MmZvYnFqYWs4b3E4bGZraGczdDBxZ0Bn) 1. Code review on pull requests from _In review_. 2. Other chores and tasks. From e4f9f3bf246d66c57b07b45583469b98ba0db367 Mon Sep 17 00:00:00 2001 From: Taeer Bar-Yam Date: Mon, 5 Dec 2022 11:27:47 -0500 Subject: [PATCH 157/245] check the store for input before failing (hopefully fix #6700) --- src/libstore/build/derivation-goal.cc | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 67cfc38af..5aed51bcd 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -501,6 +501,14 @@ void DerivationGoal::inputsRealised() now-known results of dependencies. If so, we become a stub goal aliasing that resolved derivation goal. */ std::optional attempt = fullDrv.tryResolve(worker.store, inputDrvOutputs); + if (!attempt) { + /* TODO (impure derivations-induced tech debt) (see below): + The above attempt should have found it, but because we manage + inputDrvOutputs statefully, sometimes it gets out of sync with + the real source of truth (store). So we query the store + directly if there's a problem. */ + attempt = fullDrv.tryResolve(worker.store); + } assert(attempt); Derivation drvResolved { *std::move(attempt) }; From 8c7661da0963a6a578605374cd9b309177563b3d Mon Sep 17 00:00:00 2001 From: Taeer Bar-Yam Date: Mon, 5 Dec 2022 23:21:58 -0500 Subject: [PATCH 158/245] check the store for input before failing (hopefully fix #6383) --- src/libstore/build/derivation-goal.cc | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 5aed51bcd..7dd39051b 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -1016,11 +1016,20 @@ void DerivationGoal::resolvedFinished() throw Error( "derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,resolve)", worker.store.printStorePath(drvPath), wantedOutput); - auto realisation = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput }); - if (!realisation) + + const Realisation * realisation = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput }); + if (!realisation) { + /* The above `get` should work. But sateful tracking of + outputs in resolvedResult, this can get out of sync with the + store, which is our actual source of truth. For now we just + check the store directly if it fails. */ + realisation = worker.evalStore.queryRealisation(DrvOutput { *resolvedHash, wantedOutput }).get(); + if (!realisation) { throw Error( "derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,realisation)", worker.store.printStorePath(resolvedDrvGoal->drvPath), wantedOutput); + } + } if (drv->type().isPure()) { auto newRealisation = *realisation; newRealisation.id = DrvOutput { initialOutput->outputHash, wantedOutput }; From 484578d3f9b15c34c0e3a42cc0d1f87f1c15d7f6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 6 Dec 2022 10:30:36 +0100 Subject: [PATCH 159/245] Tweak option descriptions --- src/libstore/globals.hh | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index b61a34461..ca72ad31e 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -285,9 +285,10 @@ public: Setting autoAllocateUids{this, false, "auto-allocate-uids", R"( - Whether to allocate UIDs for builders automatically. + Whether to select UIDs for builds automatically, instead of using the + users in `build-users-group`. - These UIDs are allocated starting at 872415232 (0x34000000) on Linux and 56930 on macOS. + UIDs are allocated starting at 872415232 (0x34000000) on Linux and 56930 on macOS. > **Warning** > This is an experimental feature. @@ -323,9 +324,10 @@ public: this, false, "use-cgroups", R"( Whether to execute builds inside cgroups. - Only on Linux with systemd. + This is only supported on Linux. - cgroups are required and enabled automatically for derivations that require the `uid-range` system feature. + Cgroups are required and enabled automatically for derivations + that require the `uid-range` system feature. > **Warning** > This is an experimental feature. From 5b4b2eefa1e0f59f02844cacf06077cc21336e17 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 6 Dec 2022 13:55:09 +0100 Subject: [PATCH 160/245] Release notes --- doc/manual/src/SUMMARY.md.in | 1 + doc/manual/src/release-notes/rl-2.12.md | 43 +++++++++++++++++++++++ doc/manual/src/release-notes/rl-next.md | 46 ------------------------- 3 files changed, 44 insertions(+), 46 deletions(-) create mode 100644 doc/manual/src/release-notes/rl-2.12.md diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in index 908e7e3d9..6a514fa2c 100644 --- a/doc/manual/src/SUMMARY.md.in +++ b/doc/manual/src/SUMMARY.md.in @@ -65,6 +65,7 @@ - [CLI guideline](contributing/cli-guideline.md) - [Release Notes](release-notes/release-notes.md) - [Release X.Y (202?-??-??)](release-notes/rl-next.md) + - [Release 2.12 (2022-12-06)](release-notes/rl-2.12.md) - [Release 2.11 (2022-08-25)](release-notes/rl-2.11.md) - [Release 2.10 (2022-07-11)](release-notes/rl-2.10.md) - [Release 2.9 (2022-05-30)](release-notes/rl-2.9.md) diff --git a/doc/manual/src/release-notes/rl-2.12.md b/doc/manual/src/release-notes/rl-2.12.md new file mode 100644 index 000000000..82de22cb4 --- /dev/null +++ b/doc/manual/src/release-notes/rl-2.12.md @@ -0,0 +1,43 @@ +# Release 2.12 (2022-12-06) + +* On Linux, Nix can now run builds in a user namespace where they run + as root (UID 0) and have 65,536 UIDs available. + + This is primarily useful for running containers such as `systemd-nspawn` + inside a Nix build. For an example, see [`tests/systemd-nspawn/nix`][nspawn]. + + [nspawn]: https://github.com/NixOS/nix/blob/67bcb99700a0da1395fa063d7c6586740b304598/tests/systemd-nspawn.nix. + + A build can enable this by setting the derivation attribute: + + ``` + requiredSystemFeatures = [ "uid-range" ]; + ``` + + The `uid-range` [system feature] requires the [`auto-allocate-uids`] + setting to be enabled. + + [system feature]: (../command-ref/conf-file.md#conf-system-features) + +* Nix can now automatically pick UIDs for builds, removing the need to + create `nixbld*` user accounts. See [`auto-allocate-uids`]. + + [`auto-allocate-uids`]: (../command-ref/conf-file.md#conf-auto-allocate-uids) + +* On Linux, Nix has experimental support for running builds inside a + cgroup. See + [`use-cgroups`](../command-ref/conf-file.md#conf-use-cgroups). + +* `` now accepts an additional argument `impure` which + defaults to `false`. If it is set to `true`, the `hash` and `sha256` + arguments will be ignored and the resulting derivation will have + `__impure` set to `true`, making it an impure derivation. + +* If `builtins.readFile` is called on a file with context, then only + the parts of the context that appear in the content of the file are + retained. This avoids a lot of spurious errors where strings end up + having a context just because they are read from a store path + ([#7260](https://github.com/NixOS/nix/pull/7260)). + +* `nix build --json` now prints some statistics about top-level + derivations, such as CPU statistics when cgroups are enabled. diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index bf51aa1f7..78ae99f4b 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -1,48 +1,2 @@ # Release X.Y (202?-??-??) -* `` now accepts an additional argument `impure` which - defaults to `false`. If it is set to `true`, the `hash` and `sha256` - arguments will be ignored and the resulting derivation will have - `__impure` set to `true`, making it an impure derivation. - -* If `builtins.readFile` is called on a file with context, then only the parts - of that context that appear in the content of the file are retained. - This avoids a lot of spurious errors where some benign strings end-up having - a context just because they are read from a store path - ([#7260](https://github.com/NixOS/nix/pull/7260)). - -* Nix can now automatically pick UIDs for builds, removing the need to - create `nixbld*` user accounts. - - See [`auto-allocate-uids`]. - - [`auto-allocate-uids`]: (../command-ref/conf-file.md#conf-auto-allocate-uids) - -* On Linux, Nix can now run builds in a user namespace where the build - runs as root (UID 0) and has 65,536 UIDs available. - - - - This is primarily useful for running containers such as `systemd-nspawn` - inside a Nix build. For an example, see [`tests/systemd-nspawn/nix`][nspawn]. - - [nspawn]: https://github.com/NixOS/nix/blob/67bcb99700a0da1395fa063d7c6586740b304598/tests/systemd-nspawn.nix. - - A build can enable this by by setting the derivation attribute: - - ``` - requiredSystemFeatures = [ "uid-range" ]; - ``` - - The `uid-range` [system feature] requires the [`auto-allocate-uids`] - setting to be enabled. - - [system feature]: (../command-ref/conf-file.md#conf-system-features), - -* On Linux, Nix has experimental support for running builds inside a - cgroup. - - See [`use-cgroups`](../command-ref/conf-file.md#conf-use-cgroups). - -* `nix build --json` now prints some statistics about top-level - derivations, such as CPU statistics when cgroups are enabled. From 8fc9a4e58355286c11322466a54a6f6a18942c2c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 6 Dec 2022 14:00:38 +0100 Subject: [PATCH 161/245] Remove GPG-signing of releases This makes it easier for others to make releases, and probably few people care about GPG signatures anyway. --- maintainers/upload-release.pl | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/maintainers/upload-release.pl b/maintainers/upload-release.pl index d3ef63db8..77469148a 100755 --- a/maintainers/upload-release.pl +++ b/maintainers/upload-release.pl @@ -115,10 +115,6 @@ sub downloadFile { write_file("$tmpFile.sha256", $sha256_actual); - if (! -e "$tmpFile.asc") { - system("gpg2 --detach-sign --armor $tmpFile") == 0 or die "unable to sign $tmpFile\n"; - } - return $sha256_expected; } @@ -194,7 +190,7 @@ for my $fn (glob "$tmpDir/*") { my $configuration = (); $configuration->{content_type} = "application/octet-stream"; - if ($fn =~ /.sha256|.asc|install/) { + if ($fn =~ /.sha256|install/) { # Text files $configuration->{content_type} = "text/plain"; } From 08dcd22582d65e73f29df79b3765e76cea8f3314 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Tue, 6 Dec 2022 16:36:42 +0100 Subject: [PATCH 162/245] tests: don't refer to TMPDIR --- tests/check.nix | 2 +- tests/check.sh | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/check.nix b/tests/check.nix index ed91ff845..ddab8eea9 100644 --- a/tests/check.nix +++ b/tests/check.nix @@ -44,7 +44,7 @@ with import ./config.nix; }; hashmismatch = import { - url = "file://" + builtins.getEnv "TMPDIR" + "/dummy"; + url = "file://" + builtins.getEnv "TEST_ROOT" + "/dummy"; sha256 = "0mdqa9w1p6cmli6976v4wi0sw9r4p5prkj7lzfd1877wk11c9c73"; }; diff --git a/tests/check.sh b/tests/check.sh index 495202781..fbd784fc5 100644 --- a/tests/check.sh +++ b/tests/check.sh @@ -91,13 +91,13 @@ nix-build check.nix -A fetchurl --no-out-link --check nix-build check.nix -A fetchurl --no-out-link --repair [[ $(cat $path) != foo ]] -echo 'Hello World' > $TMPDIR/dummy +echo 'Hello World' > $TEST_ROOT/dummy nix-build check.nix -A hashmismatch --no-out-link || status=$? [ "$status" = "102" ] -echo -n > $TMPDIR/dummy +echo -n > $TEST_ROOT/dummy nix-build check.nix -A hashmismatch --no-out-link -echo 'Hello World' > $TMPDIR/dummy +echo 'Hello World' > $TEST_ROOT/dummy nix-build check.nix -A hashmismatch --no-out-link --check || status=$? [ "$status" = "102" ] From 1c8de7d3d03d7a6ba259387b0698874fa879428c Mon Sep 17 00:00:00 2001 From: Taeer Bar-Yam Date: Tue, 6 Dec 2022 11:25:38 -0500 Subject: [PATCH 163/245] improve style --- src/libstore/build/derivation-goal.cc | 29 +++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 7dd39051b..1f9bfaae0 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -1017,30 +1017,33 @@ void DerivationGoal::resolvedFinished() "derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,resolve)", worker.store.printStorePath(drvPath), wantedOutput); - const Realisation * realisation = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput }); - if (!realisation) { + auto realisation = [&]{ + auto take1 = get(resolvedResult.builtOutputs, DrvOutput { *resolvedHash, wantedOutput }); + if (take1) return *take1; + /* The above `get` should work. But sateful tracking of outputs in resolvedResult, this can get out of sync with the store, which is our actual source of truth. For now we just check the store directly if it fails. */ - realisation = worker.evalStore.queryRealisation(DrvOutput { *resolvedHash, wantedOutput }).get(); - if (!realisation) { - throw Error( - "derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,realisation)", - worker.store.printStorePath(resolvedDrvGoal->drvPath), wantedOutput); - } - } + auto take2 = worker.evalStore.queryRealisation(DrvOutput { *resolvedHash, wantedOutput }); + if (take2) return *take2; + + throw Error( + "derivation '%s' doesn't have expected output '%s' (derivation-goal.cc/resolvedFinished,realisation)", + worker.store.printStorePath(resolvedDrvGoal->drvPath), wantedOutput); + }(); + if (drv->type().isPure()) { - auto newRealisation = *realisation; + auto newRealisation = realisation; newRealisation.id = DrvOutput { initialOutput->outputHash, wantedOutput }; newRealisation.signatures.clear(); if (!drv->type().isFixed()) - newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation->outPath); + newRealisation.dependentRealisations = drvOutputReferences(worker.store, *drv, realisation.outPath); signRealisation(newRealisation); worker.store.registerDrvOutput(newRealisation); } - outputPaths.insert(realisation->outPath); - builtOutputs.emplace(realisation->id, *realisation); + outputPaths.insert(realisation.outPath); + builtOutputs.emplace(realisation.id, realisation); } runPostBuildHook( From 18431a453e5dbdb4e317ec683ab9b9e4a257358b Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 6 Dec 2022 17:26:49 +0100 Subject: [PATCH 164/245] Bump version --- .version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.version b/.version index 3ca2c9b2c..a3ebb9f51 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.12.0 \ No newline at end of file +2.13.0 \ No newline at end of file From e5a2af2832d285f221ea021db3a55257db6e8dfe Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 7 Dec 2022 10:00:27 +0100 Subject: [PATCH 165/245] add template for installer issues since the installer prompts users to file issues, labelling them automatically should reduce triaging effort significantly. --- .github/ISSUE_TEMPLATE/installer.md | 36 +++++++++++++++++++++++++++++ scripts/install-multi-user.sh | 3 ++- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .github/ISSUE_TEMPLATE/installer.md diff --git a/.github/ISSUE_TEMPLATE/installer.md b/.github/ISSUE_TEMPLATE/installer.md new file mode 100644 index 000000000..3768a49c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/installer.md @@ -0,0 +1,36 @@ +--- +name: Installer issue +about: Report problems with installation +title: '' +labels: installer +assignees: '' + +--- + +## Platform + + + +- [ ] Linux: +- [ ] macOS +- [ ] WSL + +## Additional information + + + +## Output + +
Output + +```log + + + +``` + +
+ +## Priorities + +Add :+1: to [issues you find important](https://github.com/NixOS/nix/issues?q=is%3Aissue+is%3Aopen+sort%3Areactions-%2B1-desc). diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh index 96c0f302b..194a263fb 100644 --- a/scripts/install-multi-user.sh +++ b/scripts/install-multi-user.sh @@ -97,7 +97,8 @@ is_os_darwin() { } contact_us() { - echo "You can open an issue at https://github.com/nixos/nix/issues" + echo "You can open an issue at" + echo "https://github.com/NixOS/nix/issues/new?labels=installer&template=installer.md" echo "" echo "Or feel free to contact the team:" echo " - Matrix: #nix:nixos.org" From 6833ded76441a43b94ed0cf1827b9714862fdedc Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 7 Dec 2022 10:03:09 +0100 Subject: [PATCH 166/245] let installer blurb point to community page being too specific about it requires more maintenance (or otherwise produced more confusion and churn), since these points of contact change over time. --- scripts/install-multi-user.sh | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh index 96c0f302b..af8a9b278 100644 --- a/scripts/install-multi-user.sh +++ b/scripts/install-multi-user.sh @@ -99,11 +99,7 @@ is_os_darwin() { contact_us() { echo "You can open an issue at https://github.com/nixos/nix/issues" echo "" - echo "Or feel free to contact the team:" - echo " - Matrix: #nix:nixos.org" - echo " - IRC: in #nixos on irc.libera.chat" - echo " - twitter: @nixos_org" - echo " - forum: https://discourse.nixos.org" + echo "Or get in touch with the community: https://nixos.org/community" } get_help() { echo "We'd love to help if you need it." From 8e0946e8df968391d1430af8377bdb51204e4666 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Mon, 26 Sep 2022 20:55:56 +0200 Subject: [PATCH 167/245] Remove repeat and enforce-determinism options These only functioned if a very narrow combination of conditions held: - The result path does not yet exist (--check did not result in repeated builds), AND - The result path is not available from any configured substituters, AND - No remote builders that can build the path are available. If any of these do not hold, a derivation would be built 0 or 1 times regardless of the repeat option. Thus, remove it to avoid confusion. --- doc/manual/redirects.js | 3 - doc/manual/src/advanced-topics/diff-hook.md | 34 ----------- doc/manual/src/command-ref/nix-store.md | 4 -- doc/manual/src/release-notes/rl-next.md | 3 + src/libmain/progress-bar.cc | 10 ++-- src/libstore/build/derivation-goal.cc | 17 +----- src/libstore/build/derivation-goal.hh | 5 -- src/libstore/build/local-derivation-goal.cc | 62 +-------------------- src/libstore/daemon.cc | 1 - src/libstore/globals.hh | 22 -------- src/libstore/legacy-ssh-store.cc | 4 +- src/nix-store/nix-store.cc | 9 ++- 12 files changed, 21 insertions(+), 153 deletions(-) diff --git a/doc/manual/redirects.js b/doc/manual/redirects.js index 2e77edd0f..69f75d3a0 100644 --- a/doc/manual/redirects.js +++ b/doc/manual/redirects.js @@ -35,7 +35,6 @@ const redirects = { "conf-build-max-jobs": "command-ref/conf-file.html#conf-build-max-jobs", "conf-build-max-log-size": "command-ref/conf-file.html#conf-build-max-log-size", "conf-build-max-silent-time": "command-ref/conf-file.html#conf-build-max-silent-time", - "conf-build-repeat": "command-ref/conf-file.html#conf-build-repeat", "conf-build-timeout": "command-ref/conf-file.html#conf-build-timeout", "conf-build-use-chroot": "command-ref/conf-file.html#conf-build-use-chroot", "conf-build-use-sandbox": "command-ref/conf-file.html#conf-build-use-sandbox", @@ -47,7 +46,6 @@ const redirects = { "conf-connect-timeout": "command-ref/conf-file.html#conf-connect-timeout", "conf-cores": "command-ref/conf-file.html#conf-cores", "conf-diff-hook": "command-ref/conf-file.html#conf-diff-hook", - "conf-enforce-determinism": "command-ref/conf-file.html#conf-enforce-determinism", "conf-env-keep-derivations": "command-ref/conf-file.html#conf-env-keep-derivations", "conf-extra-binary-caches": "command-ref/conf-file.html#conf-extra-binary-caches", "conf-extra-platforms": "command-ref/conf-file.html#conf-extra-platforms", @@ -74,7 +72,6 @@ const redirects = { "conf-plugin-files": "command-ref/conf-file.html#conf-plugin-files", "conf-post-build-hook": "command-ref/conf-file.html#conf-post-build-hook", "conf-pre-build-hook": "command-ref/conf-file.html#conf-pre-build-hook", - "conf-repeat": "command-ref/conf-file.html#conf-repeat", "conf-require-sigs": "command-ref/conf-file.html#conf-require-sigs", "conf-restrict-eval": "command-ref/conf-file.html#conf-restrict-eval", "conf-run-diff-hook": "command-ref/conf-file.html#conf-run-diff-hook", diff --git a/doc/manual/src/advanced-topics/diff-hook.md b/doc/manual/src/advanced-topics/diff-hook.md index 161e64b2a..4a742c160 100644 --- a/doc/manual/src/advanced-topics/diff-hook.md +++ b/doc/manual/src/advanced-topics/diff-hook.md @@ -121,37 +121,3 @@ error: are not valid, so checking is not possible Run the build without `--check`, and then try with `--check` again. - -# Automatic and Optionally Enforced Determinism Verification - -Automatically verify every build at build time by executing the build -multiple times. - -Setting `repeat` and `enforce-determinism` in your `nix.conf` permits -the automated verification of every build Nix performs. - -The following configuration will run each build three times, and will -require the build to be deterministic: - - enforce-determinism = true - repeat = 2 - -Setting `enforce-determinism` to false as in the following -configuration will run the build multiple times, execute the build -hook, but will allow the build to succeed even if it does not build -reproducibly: - - enforce-determinism = false - repeat = 1 - -An example output of this configuration: - -```console -$ nix-build ./test.nix -A unstable -this derivation will be built: - /nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv -building '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 1/2)... -building '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' (round 2/2)... -output '/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable' of '/nix/store/ch6llwpr2h8c3jmnf3f2ghkhx59aa97f-unstable.drv' differs from '/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable.check' from previous round -/nix/store/6xg356v9gl03hpbbg8gws77n19qanh02-unstable -``` diff --git a/doc/manual/src/command-ref/nix-store.md b/doc/manual/src/command-ref/nix-store.md index 1251888e9..9102aff2d 100644 --- a/doc/manual/src/command-ref/nix-store.md +++ b/doc/manual/src/command-ref/nix-store.md @@ -104,10 +104,6 @@ The following flags are available: previous build, the new output path is left in `/nix/store/name.check.` - See also the `build-repeat` configuration option, which repeats a - derivation a number of times and prevents its outputs from being - registered as “valid” in the Nix store unless they are identical. - Special exit codes: - `100`\ diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index bf51aa1f7..faead26c2 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -46,3 +46,6 @@ * `nix build --json` now prints some statistics about top-level derivations, such as CPU statistics when cgroups are enabled. + +* The `repeat` and `enforce-determinism` options have been removed + since they had been broken under many circumstances for a long time. diff --git a/src/libmain/progress-bar.cc b/src/libmain/progress-bar.cc index 961f4e18a..d160a83e9 100644 --- a/src/libmain/progress-bar.cc +++ b/src/libmain/progress-bar.cc @@ -180,10 +180,12 @@ public: auto machineName = getS(fields, 1); if (machineName != "") i->s += fmt(" on " ANSI_BOLD "%s" ANSI_NORMAL, machineName); - auto curRound = getI(fields, 2); - auto nrRounds = getI(fields, 3); - if (nrRounds != 1) - i->s += fmt(" (round %d/%d)", curRound, nrRounds); + + // Used to be curRound and nrRounds, but the + // implementation was broken for a long time. + if (getI(fields, 2) != 1 || getI(fields, 3) != 1) { + throw Error("log message indicated repeating builds, but this is not currently implemented"); + } i->name = DrvName(name).name; } diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 5aed51bcd..98f9d681a 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -571,10 +571,6 @@ void DerivationGoal::inputsRealised() /* What type of derivation are we building? */ derivationType = drv->type(); - /* Don't repeat fixed-output derivations since they're already - verified by their output hash.*/ - nrRounds = derivationType.isFixed() ? 1 : settings.buildRepeat + 1; - /* Okay, try to build. Note that here we don't wait for a build slot to become available, since we don't need one if there is a build hook. */ @@ -589,12 +585,11 @@ void DerivationGoal::started() auto msg = fmt( buildMode == bmRepair ? "repairing outputs of '%s'" : buildMode == bmCheck ? "checking outputs of '%s'" : - nrRounds > 1 ? "building '%s' (round %d/%d)" : - "building '%s'", worker.store.printStorePath(drvPath), curRound, nrRounds); + "building '%s'", worker.store.printStorePath(drvPath)); fmt("building '%s'", worker.store.printStorePath(drvPath)); if (hook) msg += fmt(" on '%s'", machineName); act = std::make_unique(*logger, lvlInfo, actBuild, msg, - Logger::Fields{worker.store.printStorePath(drvPath), hook ? machineName : "", curRound, nrRounds}); + Logger::Fields{worker.store.printStorePath(drvPath), hook ? machineName : "", 1, 1}); mcRunningBuilds = std::make_unique>(worker.runningBuilds); worker.updateProgress(); } @@ -948,14 +943,6 @@ void DerivationGoal::buildDone() cleanupPostOutputsRegisteredModeNonCheck(); - /* Repeat the build if necessary. */ - if (curRound++ < nrRounds) { - outputLocks.unlock(); - state = &DerivationGoal::tryToBuild; - worker.wakeUp(shared_from_this()); - return; - } - /* It is now safe to delete the lock files, since all future lockers will see that the output paths are valid; they will not create new lock files with the same names as the old diff --git a/src/libstore/build/derivation-goal.hh b/src/libstore/build/derivation-goal.hh index 2d8bfd592..d33e04cbc 100644 --- a/src/libstore/build/derivation-goal.hh +++ b/src/libstore/build/derivation-goal.hh @@ -115,11 +115,6 @@ struct DerivationGoal : public Goal BuildMode buildMode; - /* The current round, if we're building multiple times. */ - size_t curRound = 1; - - size_t nrRounds; - std::unique_ptr> mcExpectedBuilds, mcRunningBuilds; std::unique_ptr act; diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index d2798888b..6fe3bc49c 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -2260,7 +2260,6 @@ DrvOutputs LocalDerivationGoal::registerOutputs() InodesSeen inodesSeen; Path checkSuffix = ".check"; - bool keepPreviousRound = settings.keepFailed || settings.runDiffHook; std::exception_ptr delayedException; @@ -2688,10 +2687,8 @@ DrvOutputs LocalDerivationGoal::registerOutputs() debug("unreferenced input: '%1%'", worker.store.printStorePath(i)); } - if (curRound == nrRounds) { - localStore.optimisePath(actualPath, NoRepair); // FIXME: combine with scanForReferences() - worker.markContentsGood(newInfo.path); - } + localStore.optimisePath(actualPath, NoRepair); // FIXME: combine with scanForReferences() + worker.markContentsGood(newInfo.path); newInfo.deriver = drvPath; newInfo.ultimate = true; @@ -2720,61 +2717,6 @@ DrvOutputs LocalDerivationGoal::registerOutputs() /* Apply output checks. */ checkOutputs(infos); - /* Compare the result with the previous round, and report which - path is different, if any.*/ - if (curRound > 1 && prevInfos != infos) { - assert(prevInfos.size() == infos.size()); - for (auto i = prevInfos.begin(), j = infos.begin(); i != prevInfos.end(); ++i, ++j) - if (!(*i == *j)) { - buildResult.isNonDeterministic = true; - Path prev = worker.store.printStorePath(i->second.path) + checkSuffix; - bool prevExists = keepPreviousRound && pathExists(prev); - hintformat hint = prevExists - ? hintfmt("output '%s' of '%s' differs from '%s' from previous round", - worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath), prev) - : hintfmt("output '%s' of '%s' differs from previous round", - worker.store.printStorePath(i->second.path), worker.store.printStorePath(drvPath)); - - handleDiffHook( - buildUser ? buildUser->getUID() : getuid(), - buildUser ? buildUser->getGID() : getgid(), - prev, worker.store.printStorePath(i->second.path), - worker.store.printStorePath(drvPath), tmpDir); - - if (settings.enforceDeterminism) - throw NotDeterministic(hint); - - printError(hint); - - curRound = nrRounds; // we know enough, bail out early - } - } - - /* If this is the first round of several, then move the output out of the way. */ - if (nrRounds > 1 && curRound == 1 && curRound < nrRounds && keepPreviousRound) { - for (auto & [_, outputStorePath] : finalOutputs) { - auto path = worker.store.printStorePath(outputStorePath); - Path prev = path + checkSuffix; - deletePath(prev); - Path dst = path + checkSuffix; - renameFile(path, dst); - } - } - - if (curRound < nrRounds) { - prevInfos = std::move(infos); - return {}; - } - - /* Remove the .check directories if we're done. FIXME: keep them - if the result was not determistic? */ - if (curRound == nrRounds) { - for (auto & [_, outputStorePath] : finalOutputs) { - Path prev = worker.store.printStorePath(outputStorePath) + checkSuffix; - deletePath(prev); - } - } - /* Register each output path as valid, and register the sets of paths referenced by each of them. If there are cycles in the outputs, this will fail. */ diff --git a/src/libstore/daemon.cc b/src/libstore/daemon.cc index 48dd5c247..12596ba49 100644 --- a/src/libstore/daemon.cc +++ b/src/libstore/daemon.cc @@ -238,7 +238,6 @@ struct ClientSettings } else if (trusted || name == settings.buildTimeout.name - || name == settings.buildRepeat.name || name == settings.maxSilentTime.name || name == settings.pollInterval.name || name == "connect-timeout" diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index ca72ad31e..54a5d0fc7 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -373,11 +373,6 @@ public: )", {"build-max-log-size"}}; - /* When buildRepeat > 0 and verboseBuild == true, whether to print - repeated builds (i.e. builds other than the first one) to - stderr. Hack to prevent Hydra logs from being polluted. */ - bool printRepeatedBuilds = true; - Setting pollInterval{this, 5, "build-poll-interval", "How often (in seconds) to poll for locks."}; @@ -501,19 +496,6 @@ public: Setting sandboxFallback{this, true, "sandbox-fallback", "Whether to disable sandboxing when the kernel doesn't allow it."}; - Setting buildRepeat{ - this, 0, "repeat", - R"( - How many times to repeat builds to check whether they are - deterministic. The default value is 0. If the value is non-zero, - every build is repeated the specified number of times. If the - contents of any of the runs differs from the previous ones and - `enforce-determinism` is true, the build is rejected and the - resulting store paths are not registered as “valid” in Nix’s - database. - )", - {"build-repeat"}}; - #if __linux__ Setting sandboxShmSize{ this, "50%", "sandbox-dev-shm-size", @@ -577,10 +559,6 @@ public: configuration file, and cannot be passed at the command line. )"}; - Setting enforceDeterminism{ - this, true, "enforce-determinism", - "Whether to fail if repeated builds produce different output. See `repeat`."}; - Setting trustedPublicKeys{ this, {"cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="}, diff --git a/src/libstore/legacy-ssh-store.cc b/src/libstore/legacy-ssh-store.cc index dd34b19c6..4d398b21d 100644 --- a/src/libstore/legacy-ssh-store.cc +++ b/src/libstore/legacy-ssh-store.cc @@ -255,8 +255,8 @@ private: << settings.maxLogSize; if (GET_PROTOCOL_MINOR(conn.remoteVersion) >= 3) conn.to - << settings.buildRepeat - << settings.enforceDeterminism; + << 0 // buildRepeat hasn't worked for ages anyway + << 0; if (GET_PROTOCOL_MINOR(conn.remoteVersion) >= 7) { conn.to << ((int) settings.keepFailed); diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index b59a6d026..b854ef1e7 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -808,14 +808,17 @@ static void opServe(Strings opFlags, Strings opArgs) if (GET_PROTOCOL_MINOR(clientVersion) >= 2) settings.maxLogSize = readNum(in); if (GET_PROTOCOL_MINOR(clientVersion) >= 3) { - settings.buildRepeat = readInt(in); - settings.enforceDeterminism = readInt(in); + if (readInt(in) != 0) { + throw Error("client requested repeating builds, but this is not currently implemented"); + } + if (readInt(in) != 0) { + throw Error("client requested enforcing determinism, but this is not currently implemented"); + } settings.runDiffHook = true; } if (GET_PROTOCOL_MINOR(clientVersion) >= 7) { settings.keepFailed = (bool) readInt(in); } - settings.printRepeatedBuilds = false; }; while (true) { From cc27bd06339283a8189de5c4b1a981bd9c85c211 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 6 Dec 2022 18:00:10 +0100 Subject: [PATCH 168/245] .version: Add newline --- .version | 2 +- flake.nix | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.version b/.version index a3ebb9f51..fb2c0766b 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -2.13.0 \ No newline at end of file +2.13.0 diff --git a/flake.nix b/flake.nix index d9d01da10..38b469fdb 100644 --- a/flake.nix +++ b/flake.nix @@ -9,14 +9,15 @@ let - version = builtins.readFile ./.version + versionSuffix; + officialRelease = false; + + version = strip (builtins.readFile ./.version) + versionSuffix; + strip = nixpkgs.lib.removeSuffix "\n"; versionSuffix = if officialRelease then "" else "pre${builtins.substring 0 8 (self.lastModifiedDate or self.lastModified or "19700101")}_${self.shortRev or "dirty"}"; - officialRelease = false; - linux64BitSystems = [ "x86_64-linux" "aarch64-linux" ]; linuxSystems = linux64BitSystems ++ [ "i686-linux" ]; systems = linuxSystems ++ [ "x86_64-darwin" "aarch64-darwin" ]; From c710aa1abd51231ce45fc419de2e0e82c8957fbd Mon Sep 17 00:00:00 2001 From: endgame Date: Wed, 7 Dec 2022 22:55:02 +1000 Subject: [PATCH 169/245] Post build hook signing (#7408) * docs: Use secret-key-files when demonstrating post-build-hooks The docs used to recommend calling `nix store sign` in a post-build hook, but on more recent versions of nix, this results in unsigned store paths being copied into binary caches. See https://github.com/NixOS/nix/issues/6960 for details. Instead, use the `secret-key-files` config option, which signs all locally-built derivations with the private key. Co-authored-by: Valentin Gagarin --- doc/manual/src/advanced-topics/post-build-hook.md | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/doc/manual/src/advanced-topics/post-build-hook.md b/doc/manual/src/advanced-topics/post-build-hook.md index fcb52d878..1479cc3a4 100644 --- a/doc/manual/src/advanced-topics/post-build-hook.md +++ b/doc/manual/src/advanced-topics/post-build-hook.md @@ -33,12 +33,17 @@ distribute the public key for verifying the authenticity of the paths. example-nix-cache-1:1/cKDz3QCCOmwcztD2eV6Coggp6rqc9DGjWv7C0G+rM= ``` -Then, add the public key and the cache URL to your `nix.conf`'s -`trusted-public-keys` and `substituters` options: +Then update [`nix.conf`](../command-ref/conf-file.md) on any machine that will access the cache. +Add the cache URL to [`substituters`](../command-ref/conf-file.md#conf-substituters) and the public key to [`trusted-public-keys`](../command-ref/conf-file.md#conf-trusted-public-keys): substituters = https://cache.nixos.org/ s3://example-nix-cache trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= example-nix-cache-1:1/cKDz3QCCOmwcztD2eV6Coggp6rqc9DGjWv7C0G+rM= +Machines that build for the cache must sign derivations using the private key. +On those machines, add the path to the key file to the [`secret-key-files`](../command-ref/conf-file.md#conf-secret-key-files) field in their [`nix.conf`](../command-ref/conf-file.md): + + secret-key-files = /etc/nix/key.private + We will restart the Nix daemon in a later step. # Implementing the build hook @@ -52,14 +57,12 @@ set -eu set -f # disable globbing export IFS=' ' -echo "Signing paths" $OUT_PATHS -nix store sign --key-file /etc/nix/key.private $OUT_PATHS echo "Uploading paths" $OUT_PATHS -exec nix copy --to 's3://example-nix-cache' $OUT_PATHS +exec nix copy --to "s3://example-nix-cache" $OUT_PATHS ``` > **Note** -> +> > The `$OUT_PATHS` variable is a space-separated list of Nix store > paths. In this case, we expect and want the shell to perform word > splitting to make each output path its own argument to `nix From af8136afd4440bbabb4ea214bfd3dc15ffebf469 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 7 Dec 2022 13:55:31 +0100 Subject: [PATCH 170/245] flake.nix: Use nixpkgs.lib.fileContents --- flake.nix | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index 38b469fdb..5127ee2a3 100644 --- a/flake.nix +++ b/flake.nix @@ -11,8 +11,7 @@ officialRelease = false; - version = strip (builtins.readFile ./.version) + versionSuffix; - strip = nixpkgs.lib.removeSuffix "\n"; + version = nixpkgs.lib.fileContents ./.version + versionSuffix; versionSuffix = if officialRelease then "" From 703d863a48f549b2626382eda407ffae779f8725 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 7 Dec 2022 12:58:58 +0100 Subject: [PATCH 171/245] Trivial changes from the lazy-trees branch --- configure.ac | 2 +- doc/manual/src/command-ref/env-common.md | 39 ++------------ src/libcmd/common-eval-args.cc | 47 +++++++++++++++- src/libcmd/installables.cc | 3 +- src/libcmd/repl.cc | 2 +- src/libexpr/eval-cache.cc | 6 +-- src/libexpr/eval.cc | 2 +- src/libexpr/flake/flake.cc | 47 ++++++++++------ src/libexpr/flake/flake.hh | 6 +-- src/libexpr/flake/flakeref.hh | 2 +- src/libexpr/flake/lockfile.cc | 48 +++++++++-------- src/libexpr/flake/lockfile.hh | 9 ++-- src/libexpr/get-drvs.cc | 2 +- src/libexpr/nixexpr.cc | 1 - src/libexpr/primops.cc | 8 +-- src/libfetchers/fetchers.cc | 6 +-- src/libfetchers/fetchers.hh | 13 +++-- src/libfetchers/git.cc | 60 ++++++++++----------- src/libfetchers/github.cc | 53 +++++++++++------- src/libfetchers/indirect.cc | 10 ++-- src/libfetchers/mercurial.cc | 10 ++-- src/libfetchers/path.cc | 8 +-- src/libfetchers/tarball.cc | 11 ++-- src/libmain/progress-bar.cc | 2 +- src/libstore/binary-cache-store.cc | 2 +- src/libstore/build/derivation-goal.cc | 6 +-- src/libstore/build/entry-points.cc | 6 +-- src/libstore/build/local-derivation-goal.cc | 2 +- src/libstore/derivations.cc | 2 +- src/libstore/derivations.hh | 2 +- src/libstore/filetransfer.cc | 6 +-- src/libstore/remote-store.cc | 2 +- src/libstore/store-api.cc | 8 +-- src/libstore/store-api.hh | 4 +- src/libutil/archive.cc | 4 -- src/libutil/archive.hh | 4 +- src/libutil/fmt.hh | 2 +- src/libutil/logging.cc | 12 +---- src/libutil/logging.hh | 8 ++- src/libutil/ref.hh | 5 ++ src/libutil/serialise.cc | 2 +- src/libutil/serialise.hh | 12 +---- src/libutil/util.cc | 15 ++++++ src/libutil/util.hh | 24 +++++++++ src/nix-store/nix-store.cc | 1 - src/nix/daemon.cc | 2 +- src/nix/flake-update.md | 2 +- src/nix/flake.cc | 4 +- src/nix/profile-list.md | 6 +-- src/nix/profile-upgrade.md | 6 +-- src/nix/profile.md | 3 +- src/nix/registry.cc | 8 ++- tests/eval.sh | 4 ++ tests/fetchGit.sh | 1 + tests/flakes/absolute-paths.sh | 17 ++++++ tests/flakes/flakes.sh | 13 +++-- tests/flakes/unlocked-override.sh | 30 +++++++++++ tests/function-trace.sh | 2 +- tests/local.mk | 5 +- tests/nix_path.sh | 3 ++ tests/restricted.sh | 2 +- tests/toString-path.sh | 8 +++ 62 files changed, 394 insertions(+), 248 deletions(-) create mode 100644 tests/flakes/absolute-paths.sh create mode 100644 tests/flakes/unlocked-override.sh create mode 100644 tests/toString-path.sh diff --git a/configure.ac b/configure.ac index 64fa12fc7..c0e989d85 100644 --- a/configure.ac +++ b/configure.ac @@ -177,7 +177,7 @@ fi PKG_CHECK_MODULES([OPENSSL], [libcrypto], [CXXFLAGS="$OPENSSL_CFLAGS $CXXFLAGS"]) -# Checks for libarchive +# Look for libarchive. PKG_CHECK_MODULES([LIBARCHIVE], [libarchive >= 3.1.2], [CXXFLAGS="$LIBARCHIVE_CFLAGS $CXXFLAGS"]) # Workaround until https://github.com/libarchive/libarchive/issues/1446 is fixed if test "$shared" != yes; then diff --git a/doc/manual/src/command-ref/env-common.md b/doc/manual/src/command-ref/env-common.md index 3f3eb6915..6947dbf4c 100644 --- a/doc/manual/src/command-ref/env-common.md +++ b/doc/manual/src/command-ref/env-common.md @@ -8,41 +8,10 @@ Most Nix commands interpret the following environment variables: - [`NIX_PATH`]{#env-NIX_PATH}\ A colon-separated list of directories used to look up Nix - expressions enclosed in angle brackets (i.e., ``). For - instance, the value - - /home/eelco/Dev:/etc/nixos - - will cause Nix to look for paths relative to `/home/eelco/Dev` and - `/etc/nixos`, in this order. It is also possible to match paths - against a prefix. For example, the value - - nixpkgs=/home/eelco/Dev/nixpkgs-branch:/etc/nixos - - will cause Nix to search for `` in - `/home/eelco/Dev/nixpkgs-branch/path` and `/etc/nixos/nixpkgs/path`. - - If a path in the Nix search path starts with `http://` or - `https://`, it is interpreted as the URL of a tarball that will be - downloaded and unpacked to a temporary location. The tarball must - consist of a single top-level directory. For example, setting - `NIX_PATH` to - - nixpkgs=https://github.com/NixOS/nixpkgs/archive/master.tar.gz - - tells Nix to download and use the current contents of the - `master` branch in the `nixpkgs` repository. - - 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:`. For instance, the following two - values of `NIX_PATH` are equivalent: - - nixpkgs=channel:nixos-21.05 - nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz - - The Nix search path can also be extended using the `-I` option to - many Nix commands, which takes precedence over `NIX_PATH`. + expressions enclosed in angle brackets (i.e., ``), + e.g. `/home/eelco/Dev:/etc/nixos`. It can be extended using the + `-I` option. For more information about the semantics of the Nix + search path, see the documentation for `-I`. - [`NIX_IGNORE_SYMLINK_STORE`]{#env-NIX_IGNORE_SYMLINK_STORE}\ Normally, the Nix store directory (typically `/nix/store`) is not diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index 140ed3b88..2c94d7e6c 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -32,7 +32,52 @@ MixEvalArgs::MixEvalArgs() addFlag({ .longName = "include", .shortName = 'I', - .description = "Add *path* to the list of locations used to look up `<...>` file names.", + .description = R"( + Add *path* to the Nix search path. The Nix search path is + initialized from the colon-separated `NIX_PATH` environment + variable, and is used to look up Nix expressions enclosed in angle + brackets (i.e., ``). For instance, if the Nix search path + consists of the entries + + ``` + /home/eelco/Dev + /etc/nixos + ``` + + Nix will look for paths relative to `/home/eelco/Dev` and + `/etc/nixos`, in this order. It is also possible to match paths + against a prefix. For example, the search path + + ``` + nixpkgs=/home/eelco/Dev/nixpkgs-branch + /etc/nixos + ``` + + will cause Nix to search for `` in + `/home/eelco/Dev/nixpkgs-branch/path` and `/etc/nixos/nixpkgs/path`. + + If a path in the Nix search path starts with `http://` or `https://`, + it is interpreted as the URL of a tarball that will be downloaded and + unpacked to a temporary location. The tarball must consist of a single + top-level directory. For example, setting `NIX_PATH` to + + ``` + nixpkgs=https://github.com/NixOS/nixpkgs/archive/master.tar.gz + ``` + + tells Nix to download and use the current contents of the `master` + branch in the `nixpkgs` repository. + + The URLs of the tarballs from the official `nixos.org` channels + (see [the manual page for `nix-channel`](nix-channel.md)) can be + abbreviated as `channel:`. For instance, the + following two values of `NIX_PATH` are equivalent: + + ``` + nixpkgs=channel:nixos-21.05 + nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz + ``` + )", .category = category, .labels = {"path"}, .handler = {[&](std::string s) { searchPath.push_back(s); }} diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index dbe4a449d..f8adbf90d 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -781,7 +781,8 @@ std::vector> SourceExprCommand::parseInstallables( if (file == "-") { auto e = state->parseStdin(); state->eval(e, *vFile); - } else if (file) + } + else if (file) state->evalFile(lookupFileArg(*state, *file), *vFile); else { auto e = state->parseExprFromString(*expr, absPath(".")); diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index 557952277..c704fcfb1 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -787,7 +787,7 @@ void NixRepl::loadFlake(const std::string & flakeRefS) flake::LockFlags { .updateLockFile = false, .useRegistries = !evalSettings.pureEval, - .allowMutable = !evalSettings.pureEval, + .allowUnlocked = !evalSettings.pureEval, }), v); addAttrsToScope(v); diff --git a/src/libexpr/eval-cache.cc b/src/libexpr/eval-cache.cc index b259eec63..3e2a8665e 100644 --- a/src/libexpr/eval-cache.cc +++ b/src/libexpr/eval-cache.cc @@ -645,17 +645,17 @@ NixInt AttrCursor::getInt() cachedValue = root->db->getAttr(getKey()); if (cachedValue && !std::get_if(&cachedValue->second)) { if (auto i = std::get_if(&cachedValue->second)) { - debug("using cached Integer attribute '%s'", getAttrPathStr()); + debug("using cached integer attribute '%s'", getAttrPathStr()); return i->x; } else - throw TypeError("'%s' is not an Integer", getAttrPathStr()); + throw TypeError("'%s' is not an integer", getAttrPathStr()); } } auto & v = forceValue(); if (v.type() != nInt) - throw TypeError("'%s' is not an Integer", getAttrPathStr()); + throw TypeError("'%s' is not an integer", getAttrPathStr()); return v.integer; } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 76a10b9f8..538a739af 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1806,7 +1806,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name], +https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name], *fun.lambda.env, *fun.lambda.fun); } } diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 119c556ac..8d7d08928 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -353,7 +353,7 @@ LockedFlake lockFlake( std::function node, + ref node, const InputPath & inputPathPrefix, std::shared_ptr oldNode, const InputPath & lockRootPath, @@ -362,9 +362,15 @@ LockedFlake lockFlake( computeLocks; computeLocks = [&]( + /* The inputs of this node, either from flake.nix or + flake.lock. */ const FlakeInputs & flakeInputs, - std::shared_ptr node, + /* The node whose locks are to be updated.*/ + ref node, + /* The path to this node in the lock file graph. */ const InputPath & inputPathPrefix, + /* The old node, if any, from which locks can be + copied. */ std::shared_ptr oldNode, const InputPath & lockRootPath, const Path & parentPath, @@ -452,7 +458,7 @@ LockedFlake lockFlake( /* Copy the input from the old lock since its flakeref didn't change and there is no override from a higher level flake. */ - auto childNode = std::make_shared( + auto childNode = make_ref( oldLock->lockedRef, oldLock->originalRef, oldLock->isFlake); node->inputs.insert_or_assign(id, childNode); @@ -481,7 +487,7 @@ LockedFlake lockFlake( .isFlake = (*lockedNode)->isFlake, }); } else if (auto follows = std::get_if<1>(&i.second)) { - if (! trustLock) { + if (!trustLock) { // It is possible that the flake has changed, // so we must confirm all the follows that are in the lock file are also in the flake. auto overridePath(inputPath); @@ -521,8 +527,8 @@ LockedFlake lockFlake( this input. */ debug("creating new input '%s'", inputPathS); - if (!lockFlags.allowMutable && !input.ref->input.isLocked()) - throw Error("cannot update flake input '%s' in pure mode", inputPathS); + if (!lockFlags.allowUnlocked && !input.ref->input.isLocked()) + throw Error("cannot update unlocked flake input '%s' in pure mode", inputPathS); /* Note: in case of an --override-input, we use the *original* ref (input2.ref) for the @@ -544,7 +550,7 @@ LockedFlake lockFlake( auto inputFlake = getFlake(state, localRef, useRegistries, flakeCache, inputPath); - auto childNode = std::make_shared(inputFlake.lockedRef, ref); + auto childNode = make_ref(inputFlake.lockedRef, ref); node->inputs.insert_or_assign(id, childNode); @@ -563,16 +569,20 @@ LockedFlake lockFlake( inputFlake.inputs, childNode, inputPath, oldLock ? std::dynamic_pointer_cast(oldLock) - : LockFile::read( + : (std::shared_ptr) LockFile::read( inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root, - oldLock ? lockRootPath : inputPath, localPath, false); + oldLock ? lockRootPath : inputPath, + localPath, + false); } else { auto [sourceInfo, resolvedRef, lockedRef] = fetchOrSubstituteTree( state, *input.ref, useRegistries, flakeCache); - node->inputs.insert_or_assign(id, - std::make_shared(lockedRef, ref, false)); + + auto childNode = make_ref(lockedRef, ref, false); + + node->inputs.insert_or_assign(id, childNode); } } @@ -587,8 +597,13 @@ LockedFlake lockFlake( auto parentPath = canonPath(flake.sourceInfo->actualPath + "/" + flake.lockedRef.subdir, true); computeLocks( - flake.inputs, newLockFile.root, {}, - lockFlags.recreateLockFile ? nullptr : oldLockFile.root, {}, parentPath, false); + flake.inputs, + newLockFile.root, + {}, + lockFlags.recreateLockFile ? nullptr : (std::shared_ptr) oldLockFile.root, + {}, + parentPath, + false); for (auto & i : lockFlags.inputOverrides) if (!overridesUsed.count(i.first)) @@ -611,9 +626,9 @@ LockedFlake lockFlake( if (lockFlags.writeLockFile) { if (auto sourcePath = topRef.input.getSourcePath()) { - if (!newLockFile.isImmutable()) { + if (auto unlockedInput = newLockFile.isUnlocked()) { if (fetchSettings.warnDirty) - warn("will not write lock file of flake '%s' because it has a mutable input", topRef); + warn("will not write lock file of flake '%s' because it has an unlocked input ('%s')", topRef, *unlockedInput); } else { if (!lockFlags.updateLockFile) throw Error("flake '%s' requires lock file changes but they're not allowed due to '--no-update-lock-file'", topRef); @@ -737,7 +752,7 @@ static void prim_getFlake(EvalState & state, const PosIdx pos, Value * * args, V .updateLockFile = false, .writeLockFile = false, .useRegistries = !evalSettings.pureEval && fetchSettings.useRegistries, - .allowMutable = !evalSettings.pureEval, + .allowUnlocked = !evalSettings.pureEval, }), v); } diff --git a/src/libexpr/flake/flake.hh b/src/libexpr/flake/flake.hh index 524b18af1..10301d8aa 100644 --- a/src/libexpr/flake/flake.hh +++ b/src/libexpr/flake/flake.hh @@ -108,11 +108,11 @@ struct LockFlags bool applyNixConfig = false; - /* Whether mutable flake references (i.e. those without a Git + /* Whether unlocked flake references (i.e. those without a Git revision or similar) without a corresponding lock are - allowed. Mutable flake references with a lock are always + allowed. Unlocked flake references with a lock are always allowed. */ - bool allowMutable = true; + bool allowUnlocked = true; /* Whether to commit changes to flake.lock. */ bool commitLockFile = false; diff --git a/src/libexpr/flake/flakeref.hh b/src/libexpr/flake/flakeref.hh index fe4f67193..a36d852a8 100644 --- a/src/libexpr/flake/flakeref.hh +++ b/src/libexpr/flake/flakeref.hh @@ -35,7 +35,7 @@ typedef std::string FlakeId; struct FlakeRef { - /* fetcher-specific representation of the input, sufficient to + /* Fetcher-specific representation of the input, sufficient to perform the fetch operation. */ fetchers::Input input; diff --git a/src/libexpr/flake/lockfile.cc b/src/libexpr/flake/lockfile.cc index 629d2e669..a3ed90e1f 100644 --- a/src/libexpr/flake/lockfile.cc +++ b/src/libexpr/flake/lockfile.cc @@ -31,7 +31,7 @@ FlakeRef getFlakeRef( } LockedNode::LockedNode(const nlohmann::json & json) - : lockedRef(getFlakeRef(json, "locked", "info")) + : lockedRef(getFlakeRef(json, "locked", "info")) // FIXME: remove "info" , originalRef(getFlakeRef(json, "original", nullptr)) , isFlake(json.find("flake") != json.end() ? (bool) json["flake"] : true) { @@ -49,15 +49,15 @@ std::shared_ptr LockFile::findInput(const InputPath & path) { auto pos = root; - if (!pos) return {}; - for (auto & elem : path) { if (auto i = get(pos->inputs, elem)) { if (auto node = std::get_if<0>(&*i)) pos = *node; else if (auto follows = std::get_if<1>(&*i)) { - pos = findInput(*follows); - if (!pos) return {}; + if (auto p = findInput(*follows)) + pos = ref(p); + else + return {}; } } else return {}; @@ -72,7 +72,7 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path) if (version < 5 || version > 7) throw Error("lock file '%s' has unsupported version %d", path, version); - std::unordered_map> nodeMap; + std::map> nodeMap; std::function getInputs; @@ -93,12 +93,12 @@ LockFile::LockFile(const nlohmann::json & json, const Path & path) auto jsonNode2 = nodes.find(inputKey); if (jsonNode2 == nodes.end()) throw Error("lock file references missing node '%s'", inputKey); - auto input = std::make_shared(*jsonNode2); + auto input = make_ref(*jsonNode2); k = nodeMap.insert_or_assign(inputKey, input).first; getInputs(*input, *jsonNode2); } - if (auto child = std::dynamic_pointer_cast(k->second)) - node.inputs.insert_or_assign(i.key(), child); + if (auto child = k->second.dynamic_pointer_cast()) + node.inputs.insert_or_assign(i.key(), ref(child)); else // FIXME: replace by follows node throw Error("lock file contains cycle to root node"); @@ -122,9 +122,9 @@ nlohmann::json LockFile::toJSON() const std::unordered_map, std::string> nodeKeys; std::unordered_set keys; - std::function node)> dumpNode; + std::function node)> dumpNode; - dumpNode = [&](std::string key, std::shared_ptr node) -> std::string + dumpNode = [&](std::string key, ref node) -> std::string { auto k = nodeKeys.find(node); if (k != nodeKeys.end()) @@ -159,10 +159,11 @@ nlohmann::json LockFile::toJSON() const n["inputs"] = std::move(inputs); } - if (auto lockedNode = std::dynamic_pointer_cast(node)) { + if (auto lockedNode = node.dynamic_pointer_cast()) { n["original"] = fetchers::attrsToJSON(lockedNode->originalRef.toAttrs()); n["locked"] = fetchers::attrsToJSON(lockedNode->lockedRef.toAttrs()); - if (!lockedNode->isFlake) n["flake"] = false; + if (!lockedNode->isFlake) + n["flake"] = false; } nodes[key] = std::move(n); @@ -201,13 +202,13 @@ void LockFile::write(const Path & path) const writeFile(path, fmt("%s\n", *this)); } -bool LockFile::isImmutable() const +std::optional LockFile::isUnlocked() const { - std::unordered_set> nodes; + std::set> nodes; - std::function node)> visit; + std::function node)> visit; - visit = [&](std::shared_ptr node) + visit = [&](ref node) { if (!nodes.insert(node).second) return; for (auto & i : node->inputs) @@ -219,11 +220,12 @@ bool LockFile::isImmutable() const for (auto & i : nodes) { if (i == root) continue; - auto lockedNode = std::dynamic_pointer_cast(i); - if (lockedNode && !lockedNode->lockedRef.input.isLocked()) return false; + auto node = i.dynamic_pointer_cast(); + if (node && !node->lockedRef.input.isLocked()) + return node->lockedRef; } - return true; + return {}; } bool LockFile::operator ==(const LockFile & other) const @@ -247,12 +249,12 @@ InputPath parseInputPath(std::string_view s) std::map LockFile::getAllInputs() const { - std::unordered_set> done; + std::set> done; std::map res; - std::function node)> recurse; + std::function node)> recurse; - recurse = [&](const InputPath & prefix, std::shared_ptr node) + recurse = [&](const InputPath & prefix, ref node) { if (!done.insert(node).second) return; diff --git a/src/libexpr/flake/lockfile.hh b/src/libexpr/flake/lockfile.hh index 96f1edc76..02e9bdfbc 100644 --- a/src/libexpr/flake/lockfile.hh +++ b/src/libexpr/flake/lockfile.hh @@ -20,7 +20,7 @@ struct LockedNode; type LockedNode. */ struct Node : std::enable_shared_from_this { - typedef std::variant, InputPath> Edge; + typedef std::variant, InputPath> Edge; std::map inputs; @@ -47,11 +47,13 @@ struct LockedNode : Node struct LockFile { - std::shared_ptr root = std::make_shared(); + ref root = make_ref(); LockFile() {}; LockFile(const nlohmann::json & json, const Path & path); + typedef std::map, std::string> KeyMap; + nlohmann::json toJSON() const; std::string to_string() const; @@ -60,7 +62,8 @@ struct LockFile void write(const Path & path) const; - bool isImmutable() const; + /* Check whether this lock file has any unlocked inputs. */ + std::optional isUnlocked() const; bool operator ==(const LockFile & other) const; diff --git a/src/libexpr/get-drvs.cc b/src/libexpr/get-drvs.cc index 346741dd5..5ad5d1fd4 100644 --- a/src/libexpr/get-drvs.cc +++ b/src/libexpr/get-drvs.cc @@ -150,7 +150,7 @@ DrvInfo::Outputs DrvInfo::queryOutputs(bool withPaths, bool onlyOutputsToInstall /* Check for `meta.outputsToInstall` and return `outputs` reduced to that. */ const Value * outTI = queryMeta("outputsToInstall"); if (!outTI) return outputs; - const auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); + auto errMsg = Error("this derivation has bad 'meta.outputsToInstall'"); /* ^ this shows during `nix-env -i` right under the bad derivation */ if (!outTI->isList()) throw errMsg; Outputs result; diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 7c623a07d..2be560d76 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -289,7 +289,6 @@ std::string showAttrPath(const SymbolTable & symbols, const AttrPath & attrPath) } - /* Computing levels/displacements for variables. */ void Expr::bindVars(EvalState & es, const std::shared_ptr & env) diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 8a4c19f7c..283d2746b 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1461,10 +1461,10 @@ static RegisterPrimOp primop_storePath({ static void prim_pathExists(EvalState & state, const PosIdx pos, Value * * args, Value & v) { /* We don’t check the path right now, because we don’t want to - throw if the path isn’t allowed, but just return false (and we - can’t just catch the exception here because we still want to - throw if something in the evaluation of `*args[0]` tries to - access an unauthorized path). */ + throw if the path isn’t allowed, but just return false (and we + can’t just catch the exception here because we still want to + throw if something in the evaluation of `*args[0]` tries to + access an unauthorized path). */ auto path = realisePath(state, pos, *args[0], { .checkForPureEval = false }); try { diff --git a/src/libfetchers/fetchers.cc b/src/libfetchers/fetchers.cc index 6957d2da4..c767e72e5 100644 --- a/src/libfetchers/fetchers.cc +++ b/src/libfetchers/fetchers.cc @@ -266,7 +266,7 @@ std::optional Input::getLastModified() const return {}; } -ParsedURL InputScheme::toURL(const Input & input) +ParsedURL InputScheme::toURL(const Input & input) const { throw Error("don't know how to convert input '%s' to a URL", attrsToJSON(input.attrs)); } @@ -274,7 +274,7 @@ ParsedURL InputScheme::toURL(const Input & input) Input InputScheme::applyOverrides( const Input & input, std::optional ref, - std::optional rev) + std::optional rev) const { if (ref) throw Error("don't know how to set branch/tag name of input '%s' to '%s'", input.to_string(), *ref); @@ -293,7 +293,7 @@ void InputScheme::markChangedFile(const Input & input, std::string_view file, st assert(false); } -void InputScheme::clone(const Input & input, const Path & destDir) +void InputScheme::clone(const Input & input, const Path & destDir) const { throw Error("do not know how to clone input '%s'", input.to_string()); } diff --git a/src/libfetchers/fetchers.hh b/src/libfetchers/fetchers.hh index bc9a76b0b..17da37f47 100644 --- a/src/libfetchers/fetchers.hh +++ b/src/libfetchers/fetchers.hh @@ -107,26 +107,25 @@ public: * recognized. The Input object contains the information the fetcher * needs to actually perform the "fetch()" when called. */ - struct InputScheme { virtual ~InputScheme() { } - virtual std::optional inputFromURL(const ParsedURL & url) = 0; + virtual std::optional inputFromURL(const ParsedURL & url) const = 0; - virtual std::optional inputFromAttrs(const Attrs & attrs) = 0; + virtual std::optional inputFromAttrs(const Attrs & attrs) const = 0; - virtual ParsedURL toURL(const Input & input); + virtual ParsedURL toURL(const Input & input) const; - virtual bool hasAllInfo(const Input & input) = 0; + virtual bool hasAllInfo(const Input & input) const = 0; virtual Input applyOverrides( const Input & input, std::optional ref, - std::optional rev); + std::optional rev) const; - virtual void clone(const Input & input, const Path & destDir); + virtual void clone(const Input & input, const Path & destDir) const; virtual std::optional getSourcePath(const Input & input); diff --git a/src/libfetchers/git.cc b/src/libfetchers/git.cc index 7b7a1be35..1f7d7c07d 100644 --- a/src/libfetchers/git.cc +++ b/src/libfetchers/git.cc @@ -18,6 +18,7 @@ using namespace std::string_literals; namespace nix::fetchers { + namespace { // Explicit initial branch of our bare repo to suppress warnings from new version of git. @@ -26,23 +27,23 @@ namespace { // old version of git, which will ignore unrecognized `-c` options. const std::string gitInitialBranch = "__nix_dummy_branch"; -bool isCacheFileWithinTtl(const time_t now, const struct stat & st) +bool isCacheFileWithinTtl(time_t now, const struct stat & st) { return st.st_mtime + settings.tarballTtl > now; } -bool touchCacheFile(const Path& path, const time_t& touch_time) +bool touchCacheFile(const Path & path, time_t touch_time) { - struct timeval times[2]; - times[0].tv_sec = touch_time; - times[0].tv_usec = 0; - times[1].tv_sec = touch_time; - times[1].tv_usec = 0; + struct timeval times[2]; + times[0].tv_sec = touch_time; + times[0].tv_usec = 0; + times[1].tv_sec = touch_time; + times[1].tv_usec = 0; - return lutimes(path.c_str(), times) == 0; + return lutimes(path.c_str(), times) == 0; } -Path getCachePath(std::string key) +Path getCachePath(std::string_view key) { return getCacheDir() + "/nix/gitv3/" + hashString(htSHA256, key).to_string(Base32, false); @@ -57,13 +58,12 @@ Path getCachePath(std::string key) // ... std::optional readHead(const Path & path) { - auto [exit_code, output] = runProgram(RunOptions { + auto [status, output] = runProgram(RunOptions { .program = "git", + // FIXME: use 'HEAD' to avoid returning all refs .args = {"ls-remote", "--symref", path}, }); - if (exit_code != 0) { - return std::nullopt; - } + if (status != 0) return std::nullopt; std::string_view line = output; line = line.substr(0, line.find("\n")); @@ -82,12 +82,11 @@ std::optional readHead(const Path & path) } // Persist the HEAD ref from the remote repo in the local cached repo. -bool storeCachedHead(const std::string& actualUrl, const std::string& headRef) +bool storeCachedHead(const std::string & actualUrl, const std::string & headRef) { Path cacheDir = getCachePath(actualUrl); - auto gitDir = "."; try { - runProgram("git", true, { "-C", cacheDir, "--git-dir", gitDir, "symbolic-ref", "--", "HEAD", headRef }); + runProgram("git", true, { "-C", cacheDir, "--git-dir", ".", "symbolic-ref", "--", "HEAD", headRef }); } catch (ExecError &e) { if (!WIFEXITED(e.status)) throw; return false; @@ -96,7 +95,7 @@ bool storeCachedHead(const std::string& actualUrl, const std::string& headRef) return true; } -std::optional readHeadCached(const std::string& actualUrl) +std::optional readHeadCached(const std::string & actualUrl) { // Create a cache path to store the branch of the HEAD ref. Append something // in front of the URL to prevent collision with the repository itself. @@ -110,16 +109,15 @@ std::optional readHeadCached(const std::string& actualUrl) cachedRef = readHead(cacheDir); if (cachedRef != std::nullopt && *cachedRef != gitInitialBranch && - isCacheFileWithinTtl(now, st)) { + isCacheFileWithinTtl(now, st)) + { debug("using cached HEAD ref '%s' for repo '%s'", *cachedRef, actualUrl); return cachedRef; } } auto ref = readHead(actualUrl); - if (ref) { - return ref; - } + if (ref) return ref; if (cachedRef) { // If the cached git ref is expired in fetch() below, and the 'git fetch' @@ -250,7 +248,7 @@ std::pair fetchFromWorkdir(ref store, Input & input, co struct GitInputScheme : InputScheme { - std::optional inputFromURL(const ParsedURL & url) override + std::optional inputFromURL(const ParsedURL & url) const override { if (url.scheme != "git" && url.scheme != "git+http" && @@ -265,7 +263,7 @@ struct GitInputScheme : InputScheme Attrs attrs; attrs.emplace("type", "git"); - for (auto &[name, value] : url.query) { + for (auto & [name, value] : url.query) { if (name == "rev" || name == "ref") attrs.emplace(name, value); else if (name == "shallow" || name == "submodules") @@ -279,7 +277,7 @@ struct GitInputScheme : InputScheme return inputFromAttrs(attrs); } - std::optional inputFromAttrs(const Attrs & attrs) override + std::optional inputFromAttrs(const Attrs & attrs) const override { if (maybeGetStrAttr(attrs, "type") != "git") return {}; @@ -302,7 +300,7 @@ struct GitInputScheme : InputScheme return input; } - ParsedURL toURL(const Input & input) override + ParsedURL toURL(const Input & input) const override { auto url = parseURL(getStrAttr(input.attrs, "url")); if (url.scheme != "git") url.scheme = "git+" + url.scheme; @@ -313,7 +311,7 @@ struct GitInputScheme : InputScheme return url; } - bool hasAllInfo(const Input & input) override + bool hasAllInfo(const Input & input) const override { bool maybeDirty = !input.getRef(); bool shallow = maybeGetBoolAttr(input.attrs, "shallow").value_or(false); @@ -325,7 +323,7 @@ struct GitInputScheme : InputScheme Input applyOverrides( const Input & input, std::optional ref, - std::optional rev) override + std::optional rev) const override { auto res(input); if (rev) res.attrs.insert_or_assign("rev", rev->gitRev()); @@ -335,7 +333,7 @@ struct GitInputScheme : InputScheme return res; } - void clone(const Input & input, const Path & destDir) override + void clone(const Input & input, const Path & destDir) const override { auto [isLocal, actualUrl] = getActualUrl(input); @@ -603,9 +601,9 @@ struct GitInputScheme : InputScheme { throw Error( "Cannot find Git revision '%s' in ref '%s' of repository '%s'! " - "Please make sure that the " ANSI_BOLD "rev" ANSI_NORMAL " exists on the " - ANSI_BOLD "ref" ANSI_NORMAL " you've specified or add " ANSI_BOLD - "allRefs = true;" ANSI_NORMAL " to " ANSI_BOLD "fetchGit" ANSI_NORMAL ".", + "Please make sure that the " ANSI_BOLD "rev" ANSI_NORMAL " exists on the " + ANSI_BOLD "ref" ANSI_NORMAL " you've specified or add " ANSI_BOLD + "allRefs = true;" ANSI_NORMAL " to " ANSI_BOLD "fetchGit" ANSI_NORMAL ".", input.getRev()->gitRev(), *input.getRef(), actualUrl diff --git a/src/libfetchers/github.cc b/src/libfetchers/github.cc index 2115ce2f5..1ed09d30d 100644 --- a/src/libfetchers/github.cc +++ b/src/libfetchers/github.cc @@ -26,11 +26,11 @@ std::regex hostRegex(hostRegexS, std::regex::ECMAScript); struct GitArchiveInputScheme : InputScheme { - virtual std::string type() = 0; + virtual std::string type() const = 0; virtual std::optional> accessHeaderFromToken(const std::string & token) const = 0; - std::optional inputFromURL(const ParsedURL & url) override + std::optional inputFromURL(const ParsedURL & url) const override { if (url.scheme != type()) return {}; @@ -100,7 +100,7 @@ struct GitArchiveInputScheme : InputScheme return input; } - std::optional inputFromAttrs(const Attrs & attrs) override + std::optional inputFromAttrs(const Attrs & attrs) const override { if (maybeGetStrAttr(attrs, "type") != type()) return {}; @@ -116,7 +116,7 @@ struct GitArchiveInputScheme : InputScheme return input; } - ParsedURL toURL(const Input & input) override + ParsedURL toURL(const Input & input) const override { auto owner = getStrAttr(input.attrs, "owner"); auto repo = getStrAttr(input.attrs, "repo"); @@ -132,7 +132,7 @@ struct GitArchiveInputScheme : InputScheme }; } - bool hasAllInfo(const Input & input) override + bool hasAllInfo(const Input & input) const override { return input.getRev() && maybeGetIntAttr(input.attrs, "lastModified"); } @@ -140,7 +140,7 @@ struct GitArchiveInputScheme : InputScheme Input applyOverrides( const Input & _input, std::optional ref, - std::optional rev) override + std::optional rev) const override { auto input(_input); if (rev && ref) @@ -227,7 +227,7 @@ struct GitArchiveInputScheme : InputScheme struct GitHubInputScheme : GitArchiveInputScheme { - std::string type() override { return "github"; } + std::string type() const override { return "github"; } std::optional> accessHeaderFromToken(const std::string & token) const override { @@ -240,14 +240,29 @@ struct GitHubInputScheme : GitArchiveInputScheme return std::pair("Authorization", fmt("token %s", token)); } + std::string getHost(const Input & input) const + { + return maybeGetStrAttr(input.attrs, "host").value_or("github.com"); + } + + std::string getOwner(const Input & input) const + { + return getStrAttr(input.attrs, "owner"); + } + + std::string getRepo(const Input & input) const + { + return getStrAttr(input.attrs, "repo"); + } + Hash getRevFromRef(nix::ref store, const Input & input) const override { - auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); + auto host = getHost(input); auto url = fmt( host == "github.com" ? "https://api.%s/repos/%s/%s/commits/%s" : "https://%s/api/v3/repos/%s/%s/commits/%s", - host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), *input.getRef()); + host, getOwner(input), getRepo(input), *input.getRef()); Headers headers = makeHeadersWithAuthTokens(host); @@ -262,8 +277,10 @@ struct GitHubInputScheme : GitArchiveInputScheme DownloadUrl getDownloadUrl(const Input & input) const override { - auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); + auto host = getHost(input); + Headers headers = makeHeadersWithAuthTokens(host); + // If we have no auth headers then we default to the public archive // urls so we do not run into rate limits. const auto urlFmt = @@ -273,17 +290,17 @@ struct GitHubInputScheme : GitArchiveInputScheme ? "https://%s/%s/%s/archive/%s.tar.gz" : "https://api.%s/repos/%s/%s/tarball/%s"; - const auto url = fmt(urlFmt, host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"), + const auto url = fmt(urlFmt, host, getOwner(input), getRepo(input), input.getRev()->to_string(Base16, false)); return DownloadUrl { url, headers }; } - void clone(const Input & input, const Path & destDir) override + void clone(const Input & input, const Path & destDir) const override { - auto host = maybeGetStrAttr(input.attrs, "host").value_or("github.com"); + auto host = getHost(input); Input::fromURL(fmt("git+https://%s/%s/%s.git", - host, getStrAttr(input.attrs, "owner"), getStrAttr(input.attrs, "repo"))) + host, getOwner(input), getRepo(input))) .applyOverrides(input.getRef(), input.getRev()) .clone(destDir); } @@ -291,7 +308,7 @@ struct GitHubInputScheme : GitArchiveInputScheme struct GitLabInputScheme : GitArchiveInputScheme { - std::string type() override { return "gitlab"; } + std::string type() const override { return "gitlab"; } std::optional> accessHeaderFromToken(const std::string & token) const override { @@ -346,7 +363,7 @@ struct GitLabInputScheme : GitArchiveInputScheme return DownloadUrl { url, headers }; } - void clone(const Input & input, const Path & destDir) override + void clone(const Input & input, const Path & destDir) const override { auto host = maybeGetStrAttr(input.attrs, "host").value_or("gitlab.com"); // FIXME: get username somewhere @@ -359,7 +376,7 @@ struct GitLabInputScheme : GitArchiveInputScheme struct SourceHutInputScheme : GitArchiveInputScheme { - std::string type() override { return "sourcehut"; } + std::string type() const override { return "sourcehut"; } std::optional> accessHeaderFromToken(const std::string & token) const override { @@ -433,7 +450,7 @@ struct SourceHutInputScheme : GitArchiveInputScheme return DownloadUrl { url, headers }; } - void clone(const Input & input, const Path & destDir) override + void clone(const Input & input, const Path & destDir) const override { auto host = maybeGetStrAttr(input.attrs, "host").value_or("git.sr.ht"); Input::fromURL(fmt("git+https://%s/%s/%s", diff --git a/src/libfetchers/indirect.cc b/src/libfetchers/indirect.cc index 9288fc6cf..b99504a16 100644 --- a/src/libfetchers/indirect.cc +++ b/src/libfetchers/indirect.cc @@ -7,7 +7,7 @@ std::regex flakeRegex("[a-zA-Z][a-zA-Z0-9_-]*", std::regex::ECMAScript); struct IndirectInputScheme : InputScheme { - std::optional inputFromURL(const ParsedURL & url) override + std::optional inputFromURL(const ParsedURL & url) const override { if (url.scheme != "flake") return {}; @@ -50,7 +50,7 @@ struct IndirectInputScheme : InputScheme return input; } - std::optional inputFromAttrs(const Attrs & attrs) override + std::optional inputFromAttrs(const Attrs & attrs) const override { if (maybeGetStrAttr(attrs, "type") != "indirect") return {}; @@ -68,7 +68,7 @@ struct IndirectInputScheme : InputScheme return input; } - ParsedURL toURL(const Input & input) override + ParsedURL toURL(const Input & input) const override { ParsedURL url; url.scheme = "flake"; @@ -78,7 +78,7 @@ struct IndirectInputScheme : InputScheme return url; } - bool hasAllInfo(const Input & input) override + bool hasAllInfo(const Input & input) const override { return false; } @@ -86,7 +86,7 @@ struct IndirectInputScheme : InputScheme Input applyOverrides( const Input & _input, std::optional ref, - std::optional rev) override + std::optional rev) const override { auto input(_input); if (rev) input.attrs.insert_or_assign("rev", rev->gitRev()); diff --git a/src/libfetchers/mercurial.cc b/src/libfetchers/mercurial.cc index 5c5671681..86e8f81f4 100644 --- a/src/libfetchers/mercurial.cc +++ b/src/libfetchers/mercurial.cc @@ -43,7 +43,7 @@ static std::string runHg(const Strings & args, const std::optional struct MercurialInputScheme : InputScheme { - std::optional inputFromURL(const ParsedURL & url) override + std::optional inputFromURL(const ParsedURL & url) const override { if (url.scheme != "hg+http" && url.scheme != "hg+https" && @@ -69,7 +69,7 @@ struct MercurialInputScheme : InputScheme return inputFromAttrs(attrs); } - std::optional inputFromAttrs(const Attrs & attrs) override + std::optional inputFromAttrs(const Attrs & attrs) const override { if (maybeGetStrAttr(attrs, "type") != "hg") return {}; @@ -89,7 +89,7 @@ struct MercurialInputScheme : InputScheme return input; } - ParsedURL toURL(const Input & input) override + ParsedURL toURL(const Input & input) const override { auto url = parseURL(getStrAttr(input.attrs, "url")); url.scheme = "hg+" + url.scheme; @@ -98,7 +98,7 @@ struct MercurialInputScheme : InputScheme return url; } - bool hasAllInfo(const Input & input) override + bool hasAllInfo(const Input & input) const override { // FIXME: ugly, need to distinguish between dirty and clean // default trees. @@ -108,7 +108,7 @@ struct MercurialInputScheme : InputScheme Input applyOverrides( const Input & input, std::optional ref, - std::optional rev) override + std::optional rev) const override { auto res(input); if (rev) res.attrs.insert_or_assign("rev", rev->gitRev()); diff --git a/src/libfetchers/path.cc b/src/libfetchers/path.cc index f0ef97da5..61541e69d 100644 --- a/src/libfetchers/path.cc +++ b/src/libfetchers/path.cc @@ -6,7 +6,7 @@ namespace nix::fetchers { struct PathInputScheme : InputScheme { - std::optional inputFromURL(const ParsedURL & url) override + std::optional inputFromURL(const ParsedURL & url) const override { if (url.scheme != "path") return {}; @@ -32,7 +32,7 @@ struct PathInputScheme : InputScheme return input; } - std::optional inputFromAttrs(const Attrs & attrs) override + std::optional inputFromAttrs(const Attrs & attrs) const override { if (maybeGetStrAttr(attrs, "type") != "path") return {}; @@ -54,7 +54,7 @@ struct PathInputScheme : InputScheme return input; } - ParsedURL toURL(const Input & input) override + ParsedURL toURL(const Input & input) const override { auto query = attrsToQuery(input.attrs); query.erase("path"); @@ -66,7 +66,7 @@ struct PathInputScheme : InputScheme }; } - bool hasAllInfo(const Input & input) override + bool hasAllInfo(const Input & input) const override { return true; } diff --git a/src/libfetchers/tarball.cc b/src/libfetchers/tarball.cc index 6c551bd93..e9686262a 100644 --- a/src/libfetchers/tarball.cc +++ b/src/libfetchers/tarball.cc @@ -185,7 +185,7 @@ struct CurlInputScheme : InputScheme virtual bool isValidURL(const ParsedURL & url) const = 0; - std::optional inputFromURL(const ParsedURL & url) override + std::optional inputFromURL(const ParsedURL & url) const override { if (!isValidURL(url)) return std::nullopt; @@ -203,7 +203,7 @@ struct CurlInputScheme : InputScheme return input; } - std::optional inputFromAttrs(const Attrs & attrs) override + std::optional inputFromAttrs(const Attrs & attrs) const override { auto type = maybeGetStrAttr(attrs, "type"); if (type != inputType()) return {}; @@ -220,16 +220,17 @@ struct CurlInputScheme : InputScheme return input; } - ParsedURL toURL(const Input & input) override + ParsedURL toURL(const Input & input) const override { auto url = parseURL(getStrAttr(input.attrs, "url")); - // NAR hashes are preferred over file hashes since tar/zip files // don't have a canonical representation. + // NAR hashes are preferred over file hashes since tar/zip + // files don't have a canonical representation. if (auto narHash = input.getNarHash()) url.query.insert_or_assign("narHash", narHash->to_string(SRI, true)); return url; } - bool hasAllInfo(const Input & input) override + bool hasAllInfo(const Input & input) const override { return true; } diff --git a/src/libmain/progress-bar.cc b/src/libmain/progress-bar.cc index 961f4e18a..9855bd2aa 100644 --- a/src/libmain/progress-bar.cc +++ b/src/libmain/progress-bar.cc @@ -132,7 +132,7 @@ public: log(*state, lvl, fs.s); } - void logEI(const ErrorInfo &ei) override + void logEI(const ErrorInfo & ei) override { auto state(state_.lock()); diff --git a/src/libstore/binary-cache-store.cc b/src/libstore/binary-cache-store.cc index 12d0c32fb..149d414d3 100644 --- a/src/libstore/binary-cache-store.cc +++ b/src/libstore/binary-cache-store.cc @@ -346,7 +346,7 @@ void BinaryCacheStore::narFromPath(const StorePath & storePath, Sink & sink) try { getFile(info->url, *decompressor); } catch (NoSuchBinaryCacheFile & e) { - throw SubstituteGone(e.info()); + throw SubstituteGone(std::move(e.info())); } decompressor->finish(); diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index 5aed51bcd..2949a0a1f 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -134,7 +134,7 @@ void DerivationGoal::killChild() void DerivationGoal::timedOut(Error && ex) { killChild(); - done(BuildResult::TimedOut, {}, ex); + done(BuildResult::TimedOut, {}, std::move(ex)); } @@ -984,7 +984,7 @@ void DerivationGoal::buildDone() BuildResult::PermanentFailure; } - done(st, {}, e); + done(st, {}, std::move(e)); return; } } @@ -1435,7 +1435,7 @@ void DerivationGoal::done( fs << worker.store.printStorePath(drvPath) << "\t" << buildResult.toString() << std::endl; } - amDone(buildResult.success() ? ecSuccess : ecFailed, ex); + amDone(buildResult.success() ? ecSuccess : ecFailed, std::move(ex)); } diff --git a/src/libstore/build/entry-points.cc b/src/libstore/build/entry-points.cc index bea7363db..e1b80165e 100644 --- a/src/libstore/build/entry-points.cc +++ b/src/libstore/build/entry-points.cc @@ -30,7 +30,7 @@ void Store::buildPaths(const std::vector & reqs, BuildMode buildMod if (ex) logError(i->ex->info()); else - ex = i->ex; + ex = std::move(i->ex); } if (i->exitCode != Goal::ecSuccess) { if (auto i2 = dynamic_cast(i.get())) failed.insert(i2->drvPath); @@ -40,7 +40,7 @@ void Store::buildPaths(const std::vector & reqs, BuildMode buildMod if (failed.size() == 1 && ex) { ex->status = worker.exitStatus(); - throw *ex; + throw std::move(*ex); } else if (!failed.empty()) { if (ex) logError(ex->info()); throw Error(worker.exitStatus(), "build of %s failed", showPaths(failed)); @@ -109,7 +109,7 @@ void Store::ensurePath(const StorePath & path) if (goal->exitCode != Goal::ecSuccess) { if (goal->ex) { goal->ex->status = worker.exitStatus(); - throw *goal->ex; + throw std::move(*goal->ex); } else throw Error(worker.exitStatus(), "path '%s' does not exist and cannot be created", printStorePath(path)); } diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index d2798888b..dc6f8eeba 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -230,7 +230,7 @@ void LocalDerivationGoal::tryLocalBuild() { outputLocks.unlock(); buildUser.reset(); worker.permanentFailure = true; - done(BuildResult::InputRejected, {}, e); + done(BuildResult::InputRejected, {}, std::move(e)); return; } diff --git a/src/libstore/derivations.cc b/src/libstore/derivations.cc index fe99c3c5e..42a53912e 100644 --- a/src/libstore/derivations.cc +++ b/src/libstore/derivations.cc @@ -448,7 +448,7 @@ std::string Derivation::unparse(const Store & store, bool maskOutputs, // FIXME: remove -bool isDerivation(const std::string & fileName) +bool isDerivation(std::string_view fileName) { return hasSuffix(fileName, drvExtension); } diff --git a/src/libstore/derivations.hh b/src/libstore/derivations.hh index af198a767..f3cd87fb1 100644 --- a/src/libstore/derivations.hh +++ b/src/libstore/derivations.hh @@ -224,7 +224,7 @@ StorePath writeDerivation(Store & store, Derivation parseDerivation(const Store & store, std::string && s, std::string_view name); // FIXME: remove -bool isDerivation(const std::string & fileName); +bool isDerivation(std::string_view fileName); /* Calculate the name that will be used for the store path for this output. diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index 5746c32a3..2ff411e18 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -142,9 +142,9 @@ struct curlFileTransfer : public FileTransfer } template - void fail(const T & e) + void fail(T && e) { - failEx(std::make_exception_ptr(e)); + failEx(std::make_exception_ptr(std::move(e))); } LambdaSink finalSink; @@ -472,7 +472,7 @@ struct curlFileTransfer : public FileTransfer fileTransfer.enqueueItem(shared_from_this()); } else - fail(exc); + fail(std::move(exc)); } } }; diff --git a/src/libstore/remote-store.cc b/src/libstore/remote-store.cc index 96a29155c..48cf731a8 100644 --- a/src/libstore/remote-store.cc +++ b/src/libstore/remote-store.cc @@ -447,7 +447,7 @@ void RemoteStore::queryPathInfoUncached(const StorePath & path, } catch (Error & e) { // Ugly backwards compatibility hack. if (e.msg().find("is not valid") != std::string::npos) - throw InvalidPath(e.info()); + throw InvalidPath(std::move(e.info())); throw; } if (GET_PROTOCOL_MINOR(conn->daemonVersion) >= 17) { diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 8811ab578..80b60ca1b 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -19,21 +19,21 @@ using json = nlohmann::json; namespace nix { -bool Store::isInStore(const Path & path) const +bool Store::isInStore(PathView path) const { return isInDir(path, storeDir); } -std::pair Store::toStorePath(const Path & path) const +std::pair Store::toStorePath(PathView path) const { if (!isInStore(path)) throw Error("path '%1%' is not in the Nix store", path); - Path::size_type slash = path.find('/', storeDir.size() + 1); + auto slash = path.find('/', storeDir.size() + 1); if (slash == Path::npos) return {parseStorePath(path), ""}; else - return {parseStorePath(std::string_view(path).substr(0, slash)), path.substr(slash)}; + return {parseStorePath(path.substr(0, slash)), (Path) path.substr(slash)}; } diff --git a/src/libstore/store-api.hh b/src/libstore/store-api.hh index 151ec10d6..4a88d7216 100644 --- a/src/libstore/store-api.hh +++ b/src/libstore/store-api.hh @@ -179,7 +179,7 @@ public: /* Return true if ‘path’ is in the Nix store (but not the Nix store itself). */ - bool isInStore(const Path & path) const; + bool isInStore(PathView path) const; /* Return true if ‘path’ is a store path, i.e. a direct child of the Nix store. */ @@ -187,7 +187,7 @@ public: /* Split a path like /nix/store/-/ into /nix/store/- and /. */ - std::pair toStorePath(const Path & path) const; + std::pair toStorePath(PathView path) const; /* Follow symlinks until we end up with a path in the Nix store. */ Path followLinksToStore(std::string_view path) const; diff --git a/src/libutil/archive.cc b/src/libutil/archive.cc index 4b0636129..0e2b9d12c 100644 --- a/src/libutil/archive.cc +++ b/src/libutil/archive.cc @@ -35,10 +35,6 @@ static ArchiveSettings archiveSettings; static GlobalConfig::Register rArchiveSettings(&archiveSettings); -const std::string narVersionMagic1 = "nix-archive-1"; - -static std::string caseHackSuffix = "~nix~case~hack~"; - PathFilter defaultPathFilter = [](const Path &) { return true; }; diff --git a/src/libutil/archive.hh b/src/libutil/archive.hh index ac4183bf5..e42dea540 100644 --- a/src/libutil/archive.hh +++ b/src/libutil/archive.hh @@ -103,7 +103,9 @@ void copyNAR(Source & source, Sink & sink); void copyPath(const Path & from, const Path & to); -extern const std::string narVersionMagic1; +inline constexpr std::string_view narVersionMagic1 = "nix-archive-1"; + +inline constexpr std::string_view caseHackSuffix = "~nix~case~hack~"; } diff --git a/src/libutil/fmt.hh b/src/libutil/fmt.hh index 7664e5c04..e879fd3b8 100644 --- a/src/libutil/fmt.hh +++ b/src/libutil/fmt.hh @@ -148,7 +148,7 @@ inline hintformat hintfmt(const std::string & fs, const Args & ... args) return f; } -inline hintformat hintfmt(std::string plain_string) +inline hintformat hintfmt(const std::string & plain_string) { // we won't be receiving any args in this case, so just print the original string return hintfmt("%s", normaltxt(plain_string)); diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index cb2b15b41..ac86d8ac2 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -105,14 +105,6 @@ public: Verbosity verbosity = lvlInfo; -void warnOnce(bool & haveWarned, const FormatOrString & fs) -{ - if (!haveWarned) { - warn(fs.s); - haveWarned = true; - } -} - void writeToStderr(std::string_view s) { try { @@ -130,11 +122,11 @@ Logger * makeSimpleLogger(bool printBuildLogs) return new SimpleLogger(printBuildLogs); } -std::atomic nextId{(uint64_t) getpid() << 32}; +std::atomic nextId{0}; Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type, const std::string & s, const Logger::Fields & fields, ActivityId parent) - : logger(logger), id(nextId++) + : logger(logger), id(nextId++ + (((uint64_t) getpid()) << 32)) { logger.startActivity(id, lvl, type, s, fields, parent); } diff --git a/src/libutil/logging.hh b/src/libutil/logging.hh index d0817b4a9..4642c49f7 100644 --- a/src/libutil/logging.hh +++ b/src/libutil/logging.hh @@ -82,7 +82,7 @@ public: log(lvlInfo, fs); } - virtual void logEI(const ErrorInfo &ei) = 0; + virtual void logEI(const ErrorInfo & ei) = 0; void logEI(Verbosity lvl, ErrorInfo ei) { @@ -225,7 +225,11 @@ inline void warn(const std::string & fs, const Args & ... args) logger->warn(f.str()); } -void warnOnce(bool & haveWarned, const FormatOrString & fs); +#define warnOnce(haveWarned, args...) \ + if (!haveWarned) { \ + haveWarned = true; \ + warn(args); \ + } void writeToStderr(std::string_view s); diff --git a/src/libutil/ref.hh b/src/libutil/ref.hh index bf26321db..7d38b059c 100644 --- a/src/libutil/ref.hh +++ b/src/libutil/ref.hh @@ -83,6 +83,11 @@ public: return p != other.p; } + bool operator < (const ref & other) const + { + return p < other.p; + } + private: template diff --git a/src/libutil/serialise.cc b/src/libutil/serialise.cc index 2c3597775..c653db9d0 100644 --- a/src/libutil/serialise.cc +++ b/src/libutil/serialise.cc @@ -338,7 +338,7 @@ Sink & operator << (Sink & sink, const StringSet & s) Sink & operator << (Sink & sink, const Error & ex) { - auto info = ex.info(); + auto & info = ex.info(); sink << "Error" << info.level diff --git a/src/libutil/serialise.hh b/src/libutil/serialise.hh index 84847835a..7da5b07fd 100644 --- a/src/libutil/serialise.hh +++ b/src/libutil/serialise.hh @@ -331,17 +331,9 @@ T readNum(Source & source) unsigned char buf[8]; source((char *) buf, sizeof(buf)); - uint64_t n = - ((uint64_t) buf[0]) | - ((uint64_t) buf[1] << 8) | - ((uint64_t) buf[2] << 16) | - ((uint64_t) buf[3] << 24) | - ((uint64_t) buf[4] << 32) | - ((uint64_t) buf[5] << 40) | - ((uint64_t) buf[6] << 48) | - ((uint64_t) buf[7] << 56); + auto n = readLittleEndian(buf); - if (n > (uint64_t)std::numeric_limits::max()) + if (n > (uint64_t) std::numeric_limits::max()) throw SerialisationError("serialised integer %d is too large for type '%s'", n, typeid(T).name()); return (T) n; diff --git a/src/libutil/util.cc b/src/libutil/util.cc index 4f2caaa40..993dc1cb6 100644 --- a/src/libutil/util.cc +++ b/src/libutil/util.cc @@ -1594,6 +1594,21 @@ std::string stripIndentation(std::string_view s) } +std::pair getLine(std::string_view s) +{ + auto newline = s.find('\n'); + + if (newline == s.npos) { + return {s, ""}; + } else { + auto line = s.substr(0, newline); + if (!line.empty() && line[line.size() - 1] == '\r') + line = line.substr(0, line.size() - 1); + return {line, s.substr(newline + 1)}; + } +} + + ////////////////////////////////////////////////////////////////////// static Sync> windowSize{{0, 0}}; diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 94d8cc555..3caa95fca 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -510,6 +510,17 @@ std::optional string2Float(const std::string_view s) } +/* Convert a little-endian integer to host order. */ +template +T readLittleEndian(unsigned char * p) +{ + T x = 0; + for (size_t i = 0; i < sizeof(x); ++i) + x |= ((T) *p++) << (i * 8); + return x; +} + + /* Return true iff `s' starts with `prefix'. */ bool hasPrefix(std::string_view s, std::string_view prefix); @@ -563,6 +574,12 @@ std::string base64Decode(std::string_view s); std::string stripIndentation(std::string_view s); +/* Get the prefix of 's' up to and excluding the next line break (LF + optionally preceded by CR), and the remainder following the line + break. */ +std::pair getLine(std::string_view s); + + /* Get a value for the specified key from an associate container. */ template const typename T::mapped_type * get(const T & map, const typename T::key_type & key) @@ -737,4 +754,11 @@ inline std::string operator + (std::string && s, std::string_view s2) return std::move(s); } +inline std::string operator + (std::string_view s1, const char * s2) +{ + std::string s(s1); + s.append(s2); + return s; +} + } diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index b59a6d026..7bb9c630f 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -926,7 +926,6 @@ static void opServe(Strings opFlags, Strings opArgs) worker_proto::write(*store, out, status.builtOutputs); } - break; } diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc index 940923d3b..c527fdb0a 100644 --- a/src/nix/daemon.cc +++ b/src/nix/daemon.cc @@ -257,7 +257,7 @@ static void daemonLoop() } catch (Interrupted & e) { return; } catch (Error & error) { - ErrorInfo ei = error.info(); + auto ei = error.info(); // FIXME: add to trace? ei.msg = hintfmt("error processing connection: %1%", ei.msg.str()); logError(ei); diff --git a/src/nix/flake-update.md b/src/nix/flake-update.md index 2ee8a707d..8c6042d94 100644 --- a/src/nix/flake-update.md +++ b/src/nix/flake-update.md @@ -16,7 +16,7 @@ R""( # Description This command recreates the lock file of a flake (`flake.lock`), thus -updating the lock for every mutable input (like `nixpkgs`) to its +updating the lock for every unlocked input (like `nixpkgs`) to its current version. This is equivalent to passing `--recreate-lock-file` to any command that operates on a flake. That is, diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 336f6723a..96f035117 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -215,7 +215,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON if (!lockedFlake.lockFile.root->inputs.empty()) logger->cout(ANSI_BOLD "Inputs:" ANSI_NORMAL); - std::unordered_set> visited; + std::set> visited; std::function recurse; @@ -227,7 +227,7 @@ struct CmdFlakeMetadata : FlakeCommand, MixJSON if (auto lockedNode = std::get_if<0>(&input.second)) { logger->cout("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s", prefix + (last ? treeLast : treeConn), input.first, - *lockedNode ? (*lockedNode)->lockedRef : flake.lockedRef); + (*lockedNode)->lockedRef); bool firstVisit = visited.insert(*lockedNode).second; diff --git a/src/nix/profile-list.md b/src/nix/profile-list.md index bdab9a208..fa786162f 100644 --- a/src/nix/profile-list.md +++ b/src/nix/profile-list.md @@ -20,11 +20,11 @@ following fields: * An integer that can be used to unambiguously identify the package in invocations of `nix profile remove` and `nix profile upgrade`. -* The original ("mutable") flake reference and output attribute path +* The original ("unlocked") flake reference and output attribute path used at installation time. -* The immutable flake reference to which the mutable flake reference - was resolved. +* The locked flake reference to which the unlocked flake reference was + resolved. * The store path(s) of the package. diff --git a/src/nix/profile-upgrade.md b/src/nix/profile-upgrade.md index e06e74abe..39cca428b 100644 --- a/src/nix/profile-upgrade.md +++ b/src/nix/profile-upgrade.md @@ -2,7 +2,7 @@ R""( # Examples -* Upgrade all packages that were installed using a mutable flake +* Upgrade all packages that were installed using an unlocked flake reference: ```console @@ -32,9 +32,9 @@ the package was installed. > **Warning** > -> This only works if you used a *mutable* flake reference at +> This only works if you used an *unlocked* flake reference at > installation time, e.g. `nixpkgs#hello`. It does not work if you -> used an *immutable* flake reference +> used a *locked* flake reference > (e.g. `github:NixOS/nixpkgs/13d0c311e3ae923a00f734b43fd1d35b47d8943a#hello`), > since in that case the "latest version" is always the same. diff --git a/src/nix/profile.md b/src/nix/profile.md index be3c5ba1a..273e02280 100644 --- a/src/nix/profile.md +++ b/src/nix/profile.md @@ -88,8 +88,7 @@ has the following fields: the user at the time of installation (e.g. `nixpkgs`). This is also the flake reference that will be used by `nix profile upgrade`. -* `uri`: The immutable flake reference to which `originalUrl` - resolved. +* `uri`: The locked flake reference to which `originalUrl` resolved. * `attrPath`: The flake output attribute that provided this package. Note that this is not necessarily the attribute that the diff --git a/src/nix/registry.cc b/src/nix/registry.cc index c496f94f8..b5bdfba95 100644 --- a/src/nix/registry.cc +++ b/src/nix/registry.cc @@ -183,14 +183,12 @@ struct CmdRegistryPin : RegistryCommand, EvalCommand void run(nix::ref store) override { - if (locked.empty()) { - locked = url; - } + if (locked.empty()) locked = url; auto registry = getRegistry(); auto ref = parseFlakeRef(url); - auto locked_ref = parseFlakeRef(locked); + auto lockedRef = parseFlakeRef(locked); registry->remove(ref.input); - auto [tree, resolved] = locked_ref.resolve(store).input.fetch(store); + auto [tree, resolved] = lockedRef.resolve(store).input.fetch(store); fetchers::Attrs extraAttrs; if (ref.subdir != "") extraAttrs["dir"] = ref.subdir; registry->add(ref.input, resolved, extraAttrs); diff --git a/tests/eval.sh b/tests/eval.sh index d74976019..ffae08a6a 100644 --- a/tests/eval.sh +++ b/tests/eval.sh @@ -29,3 +29,7 @@ nix-instantiate --eval -E 'assert 1 + 2 == 3; true' [[ $(nix-instantiate -A attr --eval "./eval.nix") == '{ foo = "bar"; }' ]] [[ $(nix-instantiate -A attr --eval --json "./eval.nix") == '{"foo":"bar"}' ]] [[ $(nix-instantiate -A int --eval - < "./eval.nix") == 123 ]] + +# Check that symlink cycles don't cause a hang. +ln -sfn cycle.nix $TEST_ROOT/cycle.nix +(! nix eval --file $TEST_ROOT/cycle.nix) diff --git a/tests/fetchGit.sh b/tests/fetchGit.sh index 4ceba0293..da09c3f37 100644 --- a/tests/fetchGit.sh +++ b/tests/fetchGit.sh @@ -122,6 +122,7 @@ git -C $repo commit -m 'Bla3' -a path4=$(nix eval --impure --refresh --raw --expr "(builtins.fetchGit file://$repo).outPath") [[ $path2 = $path4 ]] +status=0 nix eval --impure --raw --expr "(builtins.fetchGit { url = $repo; rev = \"$rev2\"; narHash = \"sha256-B5yIPHhEm0eysJKEsO7nqxprh9vcblFxpJG11gXJus1=\"; }).outPath" || status=$? [[ "$status" = "102" ]] diff --git a/tests/flakes/absolute-paths.sh b/tests/flakes/absolute-paths.sh new file mode 100644 index 000000000..e7bfba12d --- /dev/null +++ b/tests/flakes/absolute-paths.sh @@ -0,0 +1,17 @@ +source ./common.sh + +requireGit + +flake1Dir=$TEST_ROOT/flake1 +flake2Dir=$TEST_ROOT/flake2 + +createGitRepo $flake1Dir +cat > $flake1Dir/flake.nix < $flake3Dir/flake.nix < $flake3Dir/default.nix < $nonFlakeDir/README.md < $badFlakeDir/flake.nix diff --git a/tests/flakes/unlocked-override.sh b/tests/flakes/unlocked-override.sh new file mode 100644 index 000000000..8abc8b7d3 --- /dev/null +++ b/tests/flakes/unlocked-override.sh @@ -0,0 +1,30 @@ +source ./common.sh + +requireGit + +flake1Dir=$TEST_ROOT/flake1 +flake2Dir=$TEST_ROOT/flake2 + +createGitRepo $flake1Dir +cat > $flake1Dir/flake.nix < $flake1Dir/x.nix +git -C $flake1Dir add flake.nix x.nix +git -C $flake1Dir commit -m Initial + +createGitRepo $flake2Dir +cat > $flake2Dir/flake.nix < $flake1Dir/x.nix + +[[ $(nix eval --json $flake2Dir#x --override-input flake1 $TEST_ROOT/flake1) = 456 ]] diff --git a/tests/function-trace.sh b/tests/function-trace.sh index 0b7f49d82..d68e10df5 100755 --- a/tests/function-trace.sh +++ b/tests/function-trace.sh @@ -11,7 +11,7 @@ expect_trace() { --expr "$expr" 2>&1 \ | grep "function-trace" \ | sed -e 's/ [0-9]*$//' - ); + ) echo -n "Tracing expression '$expr'" set +e diff --git a/tests/local.mk b/tests/local.mk index 340817ec3..2f7f76261 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -7,6 +7,8 @@ nix_tests = \ flakes/follow-paths.sh \ flakes/bundle.sh \ flakes/check.sh \ + flakes/unlocked-override.sh \ + flakes/absolute-paths.sh \ ca/gc.sh \ gc.sh \ remote-store.sh \ @@ -110,7 +112,8 @@ nix_tests = \ fetchClosure.sh \ completions.sh \ impure-derivations.sh \ - path-from-hash-part.sh + path-from-hash-part.sh \ + toString-path.sh ifeq ($(HAVE_LIBCPUID), 1) nix_tests += compute-levels.sh diff --git a/tests/nix_path.sh b/tests/nix_path.sh index d3657abf0..2b222b4a1 100644 --- a/tests/nix_path.sh +++ b/tests/nix_path.sh @@ -9,3 +9,6 @@ nix-instantiate --eval -E '' --restrict-eval # Should ideally also test this, but there’s no pure way to do it, so just trust me that it works # nix-instantiate --eval -E '' -I nixpkgs=channel:nixos-unstable --restrict-eval + +[[ $(nix-instantiate --find-file by-absolute-path/simple.nix) = $PWD/simple.nix ]] +[[ $(nix-instantiate --find-file by-relative-path/simple.nix) = $PWD/simple.nix ]] diff --git a/tests/restricted.sh b/tests/restricted.sh index 242b901dd..9bd16cf51 100644 --- a/tests/restricted.sh +++ b/tests/restricted.sh @@ -3,7 +3,7 @@ source common.sh clearStore nix-instantiate --restrict-eval --eval -E '1 + 2' -(! nix-instantiate --restrict-eval ./restricted.nix) +(! nix-instantiate --eval --restrict-eval ./restricted.nix) (! nix-instantiate --eval --restrict-eval <(echo '1 + 2')) nix-instantiate --restrict-eval ./simple.nix -I src=. nix-instantiate --restrict-eval ./simple.nix -I src1=simple.nix -I src2=config.nix -I src3=./simple.builder.sh diff --git a/tests/toString-path.sh b/tests/toString-path.sh new file mode 100644 index 000000000..07eb87465 --- /dev/null +++ b/tests/toString-path.sh @@ -0,0 +1,8 @@ +source common.sh + +mkdir -p $TEST_ROOT/foo +echo bla > $TEST_ROOT/foo/bar + +[[ $(nix eval --raw --impure --expr "builtins.readFile (builtins.toString (builtins.fetchTree { type = \"path\"; path = \"$TEST_ROOT/foo\"; } + \"/bar\"))") = bla ]] + +[[ $(nix eval --json --impure --expr "builtins.readDir (builtins.toString (builtins.fetchTree { type = \"path\"; path = \"$TEST_ROOT/foo\"; }))") = '{"bar":"regular"}' ]] From b8a1ff98c1019f36a6425f639bf4c694f42c7edf Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 7 Dec 2022 16:12:26 +0100 Subject: [PATCH 172/245] use HTML anchors for config parameters this avoids incorrect rendering on the man pages, since `lowdown` neither parses the anchor syntax nor HTML. this should rather be fixed in lowdown, as adding more anchors would otherwise produce ever more noise and error-prone repetition. --- doc/manual/generate-options.nix | 2 +- doc/manual/src/command-ref/nix-build.md | 12 +++++++----- doc/manual/src/command-ref/nix-store.md | 3 ++- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/doc/manual/generate-options.nix b/doc/manual/generate-options.nix index 814144c20..a4ec36477 100644 --- a/doc/manual/generate-options.nix +++ b/doc/manual/generate-options.nix @@ -9,7 +9,7 @@ let let inherit (optionsInfo.${name}) description documentDefault defaultValue aliases; result = squash '' - - [`${name}`]{#conf-${name}} + - [`${name}`](#conf-${name}) ${indent " " body} ''; diff --git a/doc/manual/src/command-ref/nix-build.md b/doc/manual/src/command-ref/nix-build.md index 49c6f3f55..3a47feaae 100644 --- a/doc/manual/src/command-ref/nix-build.md +++ b/doc/manual/src/command-ref/nix-build.md @@ -53,16 +53,18 @@ All options not listed here are passed to `nix-store --realise`, except for `--arg` and `--attr` / `-A` which are passed to `nix-instantiate`. - - [`--no-out-link`]{#opt-no-out-link}\ + - [`--no-out-link`](#opt-no-out-link) + Do not create a symlink to the output path. Note that as a result the output does not become a root of the garbage collector, and so - might be deleted by `nix-store - --gc`. + might be deleted by `nix-store --gc`. + + - [`--dry-run`](#opt-dry-run) - - [`--dry-run`]{#opt-dry-run}\ Show what store paths would be built or downloaded. - - [`--out-link`]{#opt-out-link} / `-o` *outlink*\ + - [`--out-link`](#opt-out-link) / `-o` *outlink* + Change the name of the symlink to the output path created from `result` to *outlink*. diff --git a/doc/manual/src/command-ref/nix-store.md b/doc/manual/src/command-ref/nix-store.md index 1251888e9..ec1da72a4 100644 --- a/doc/manual/src/command-ref/nix-store.md +++ b/doc/manual/src/command-ref/nix-store.md @@ -22,7 +22,8 @@ This section lists the options that are common to all operations. These options are allowed for every subcommand, though they may not always have an effect. - - [`--add-root`]{#opt-add-root} *path*\ + - [`--add-root`](#opt-add-root) *path* + Causes the result of a realisation (`--realise` and `--force-realise`) to be registered as a root of the garbage collector. *path* will be created as a symlink to the resulting From ebeaf03558caa62cba6f0bfbd1170dbd5c5944b8 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 7 Dec 2022 16:20:25 +0100 Subject: [PATCH 173/245] do not render links in man pages this is a follow-up on e7dcacb. most links are relative and this should not be too much of a detriment. --- doc/manual/local.mk | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/local.mk b/doc/manual/local.mk index 486dbd7a2..c0f69e00f 100644 --- a/doc/manual/local.mk +++ b/doc/manual/local.mk @@ -29,19 +29,19 @@ nix-eval = $(dummy-env) $(bindir)/nix eval --experimental-features nix-command - $(d)/%.1: $(d)/src/command-ref/%.md @printf "Title: %s\n\n" "$$(basename $@ .1)" > $^.tmp @cat $^ >> $^.tmp - $(trace-gen) lowdown -sT man -M section=1 $^.tmp -o $@ + $(trace-gen) lowdown -sT man --nroff-nolinks -M section=1 $^.tmp -o $@ @rm $^.tmp $(d)/%.8: $(d)/src/command-ref/%.md @printf "Title: %s\n\n" "$$(basename $@ .8)" > $^.tmp @cat $^ >> $^.tmp - $(trace-gen) lowdown -sT man -M section=8 $^.tmp -o $@ + $(trace-gen) lowdown -sT man --nroff-nolinks -M section=8 $^.tmp -o $@ @rm $^.tmp $(d)/nix.conf.5: $(d)/src/command-ref/conf-file.md @printf "Title: %s\n\n" "$$(basename $@ .5)" > $^.tmp @cat $^ >> $^.tmp - $(trace-gen) lowdown -sT man -M section=5 $^.tmp -o $@ + $(trace-gen) lowdown -sT man --nroff-nolinks -M section=5 $^.tmp -o $@ @rm $^.tmp $(d)/src/SUMMARY.md: $(d)/src/SUMMARY.md.in $(d)/src/command-ref/new-cli From 25e87c3c9131613385b71ffa5adae6e9e3644129 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Thu, 8 Dec 2022 12:59:20 +0100 Subject: [PATCH 174/245] flake.lock: Update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Flake lock file updates: • Updated input 'nixpkgs': 'github:NixOS/nixpkgs/e22d9c397e5e6d92771cc1534e7769f2167c2952' (2022-11-26) → 'github:NixOS/nixpkgs/04a75b2eecc0acf6239acf9dd04485ff8d14f425' (2022-12-08) NixOS 22.11 is now no longer beta. --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index be19cca6b..4490b5ead 100644 --- a/flake.lock +++ b/flake.lock @@ -18,11 +18,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1669425120, - "narHash": "sha256-m/sEyGBDAq+Th4NVaPRhrJ5sljReCebYiQcbDlqp0ww=", + "lastModified": 1670461440, + "narHash": "sha256-jy1LB8HOMKGJEGXgzFRLDU1CBGL0/LlkolgnqIsF0D8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e22d9c397e5e6d92771cc1534e7769f2167c2952", + "rev": "04a75b2eecc0acf6239acf9dd04485ff8d14f425", "type": "github" }, "original": { From 6f61f4667fd39d4fa2e02d18a9b818f39cbfd023 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 9 Dec 2022 17:35:40 +0100 Subject: [PATCH 175/245] Remove tests for --repeat https://hydra.nixos.org/build/201125739 --- tests/check.sh | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/tests/check.sh b/tests/check.sh index fbd784fc5..e77c0405d 100644 --- a/tests/check.sh +++ b/tests/check.sh @@ -40,14 +40,6 @@ nix-build check.nix -A deterministic --argstr checkBuildId $checkBuildId \ if grep -q 'may not be deterministic' $TEST_ROOT/log; then false; fi checkBuildTempDirRemoved $TEST_ROOT/log -nix build -f check.nix deterministic --rebuild --repeat 1 \ - --argstr checkBuildId $checkBuildId --keep-failed --no-link \ - 2> $TEST_ROOT/log -if grep -q 'checking is not possible' $TEST_ROOT/log; then false; fi -# Repeat is set to 1, ie. nix should build deterministic twice. -if [ "$(grep "checking outputs" $TEST_ROOT/log | wc -l)" -ne 2 ]; then false; fi -checkBuildTempDirRemoved $TEST_ROOT/log - nix-build check.nix -A nondeterministic --argstr checkBuildId $checkBuildId \ --no-out-link 2> $TEST_ROOT/log checkBuildTempDirRemoved $TEST_ROOT/log @@ -58,12 +50,6 @@ grep 'may not be deterministic' $TEST_ROOT/log [ "$status" = "104" ] checkBuildTempDirRemoved $TEST_ROOT/log -nix build -f check.nix nondeterministic --rebuild --repeat 1 \ - --argstr checkBuildId $checkBuildId --keep-failed --no-link \ - 2> $TEST_ROOT/log || status=$? -grep 'may not be deterministic' $TEST_ROOT/log -checkBuildTempDirRemoved $TEST_ROOT/log - nix-build check.nix -A nondeterministic --argstr checkBuildId $checkBuildId \ --no-out-link --check --keep-failed 2> $TEST_ROOT/log || status=$? grep 'may not be deterministic' $TEST_ROOT/log @@ -72,12 +58,6 @@ if checkBuildTempDirRemoved $TEST_ROOT/log; then false; fi clearStore -nix-build dependencies.nix --no-out-link --repeat 3 - -nix-build check.nix -A nondeterministic --no-out-link --repeat 1 2> $TEST_ROOT/log || status=$? -[ "$status" = "1" ] -grep 'differs from previous round' $TEST_ROOT/log - path=$(nix-build check.nix -A fetchurl --no-out-link) chmod +w $path From dbc854766498818917c47ebce302266e92b41433 Mon Sep 17 00:00:00 2001 From: Andreas Rammhold Date: Sat, 10 Dec 2022 11:26:59 +0100 Subject: [PATCH 176/245] Ignore the enforceDeterminism value We used to set enforceDeterminism to true in the settings (by default) and thus did send a non-zero value over the wire. The value should probably be ignored as it should only matter if nrRounds is non-zero as well. Having the old code here where the value is expected to be zero only works with the same version of Nix where we are sending zero. We should always test this against older Nix versions being client or server as otherwise upgrade in larger networks might be a pain. Fixes 8e0946e8df968391d1430af8377bdb51204e4666 --- src/nix-store/nix-store.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index b854ef1e7..85f142c39 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -808,12 +808,15 @@ static void opServe(Strings opFlags, Strings opArgs) if (GET_PROTOCOL_MINOR(clientVersion) >= 2) settings.maxLogSize = readNum(in); if (GET_PROTOCOL_MINOR(clientVersion) >= 3) { - if (readInt(in) != 0) { + auto nrRepeats = readInt(in); + if (nrRepeats != 0) { throw Error("client requested repeating builds, but this is not currently implemented"); } - if (readInt(in) != 0) { - throw Error("client requested enforcing determinism, but this is not currently implemented"); - } + // Ignore. It used to be true by default, but also only never had any effect when `nrRepeats == 0`. + // We have already asserted that `nrRepeats` in fact is 0, so we can safely ignore this without + // doing something other than what the client asked for. + auto _enforceDeterminism = readInt(in); + settings.runDiffHook = true; } if (GET_PROTOCOL_MINOR(clientVersion) >= 7) { From 8272cd9deca99b84fe18cddd561f24bb69249b57 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 12:36:19 +0100 Subject: [PATCH 177/245] Optimize string concatenation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Théophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com> --- src/libutil/util.hh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 3caa95fca..2f869d909 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -756,7 +756,9 @@ inline std::string operator + (std::string && s, std::string_view s2) inline std::string operator + (std::string_view s1, const char * s2) { - std::string s(s1); + std::string s; + s.reserve(s1.size() + strlen(s2)); + s.append(s1); s.append(s2); return s; } From f3d1e92856fbed53ebaad40f94180bd1ba60ffec Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 12:37:55 +0100 Subject: [PATCH 178/245] Update URL Co-authored-by: Valentin Gagarin --- src/libexpr/eval.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 538a739af..b67ed853e 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1806,7 +1806,7 @@ void EvalState::autoCallFunction(Bindings & args, Value & fun, Value & res) Nix attempted to evaluate a function as a top level expression; in this case it must have its arguments supplied either by default values, or passed explicitly with '--arg' or '--argstr'. See -https://nixos.org/manual/nix/stable/expressions/language-constructs.html#functions.)", symbols[i.name], +https://nixos.org/manual/nix/stable/language/constructs.html#functions.)", symbols[i.name], *fun.lambda.env, *fun.lambda.fun); } } From 786402365e9c819235636d9300bd25c362a29db7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 12:40:51 +0100 Subject: [PATCH 179/245] Cleanup --- src/libutil/util.hh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libutil/util.hh b/src/libutil/util.hh index 2f869d909..9b149de80 100644 --- a/src/libutil/util.hh +++ b/src/libutil/util.hh @@ -515,8 +515,9 @@ template T readLittleEndian(unsigned char * p) { T x = 0; - for (size_t i = 0; i < sizeof(x); ++i) - x |= ((T) *p++) << (i * 8); + for (size_t i = 0; i < sizeof(x); ++i, ++p) { + x |= ((T) *p) << (i * 8); + } return x; } From 037d5c4299354508cd33c0c5e5c382d7dd2431e4 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 12:43:44 +0100 Subject: [PATCH 180/245] Manual improvements Co-authored-by: Valentin Gagarin --- doc/manual/src/command-ref/env-common.md | 2 +- src/libcmd/common-eval-args.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/src/command-ref/env-common.md b/doc/manual/src/command-ref/env-common.md index 6947dbf4c..34a3179a8 100644 --- a/doc/manual/src/command-ref/env-common.md +++ b/doc/manual/src/command-ref/env-common.md @@ -7,7 +7,7 @@ Most Nix commands interpret the following environment variables: `nix-shell`. It can have the values `pure` or `impure`. - [`NIX_PATH`]{#env-NIX_PATH}\ - A colon-separated list of directories used to look up Nix + A colon-separated list of directories used to look up the location of Nix expressions enclosed in angle brackets (i.e., ``), e.g. `/home/eelco/Dev:/etc/nixos`. It can be extended using the `-I` option. For more information about the semantics of the Nix diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index 2c94d7e6c..53595cea6 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -34,8 +34,8 @@ MixEvalArgs::MixEvalArgs() .shortName = 'I', .description = R"( Add *path* to the Nix search path. The Nix search path is - initialized from the colon-separated `NIX_PATH` environment - variable, and is used to look up Nix expressions enclosed in angle + initialized from the colon-separated [`NIX_PATH`](./env-common.md#env-NIX_PATH) environment + variable, and is used to look up the location of Nix expressions using [paths](../language/values.md#type-path) enclosed in angle brackets (i.e., ``). For instance, if the Nix search path consists of the entries From 877ea1dab84846c70dba1763440071fdaf1fe2d7 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 12:46:13 +0100 Subject: [PATCH 181/245] Use get_ptr() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Théophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com> --- src/libexpr/flake/flake.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 8d7d08928..6b5d6f6b3 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -569,8 +569,8 @@ LockedFlake lockFlake( inputFlake.inputs, childNode, inputPath, oldLock ? std::dynamic_pointer_cast(oldLock) - : (std::shared_ptr) LockFile::read( - inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root, + : LockFile::read( + inputFlake.sourceInfo->actualPath + "/" + inputFlake.lockedRef.subdir + "/flake.lock").root.get_ptr(), oldLock ? lockRootPath : inputPath, localPath, false); @@ -600,7 +600,7 @@ LockedFlake lockFlake( flake.inputs, newLockFile.root, {}, - lockFlags.recreateLockFile ? nullptr : (std::shared_ptr) oldLockFile.root, + lockFlags.recreateLockFile ? nullptr : oldLockFile.root.get_ptr(), {}, parentPath, false); From add417ec147c50b12231741684ccc08a95024744 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 12:46:54 +0100 Subject: [PATCH 182/245] Fix indentation --- doc/manual/src/command-ref/env-common.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/manual/src/command-ref/env-common.md b/doc/manual/src/command-ref/env-common.md index 34a3179a8..be5797252 100644 --- a/doc/manual/src/command-ref/env-common.md +++ b/doc/manual/src/command-ref/env-common.md @@ -9,7 +9,7 @@ Most Nix commands interpret the following environment variables: - [`NIX_PATH`]{#env-NIX_PATH}\ A colon-separated list of directories used to look up the location of Nix expressions enclosed in angle brackets (i.e., ``), - e.g. `/home/eelco/Dev:/etc/nixos`. It can be extended using the + e.g. `/home/eelco/Dev:/etc/nixos`. It can be extended using the `-I` option. For more information about the semantics of the Nix search path, see the documentation for `-I`. From e558e089badf2592d1e4540b2fc884a8001a7c06 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 12:51:23 +0100 Subject: [PATCH 183/245] -I description: Use -I examples --- src/libcmd/common-eval-args.cc | 36 +++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index 53595cea6..dc0de4b9f 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -36,21 +36,29 @@ MixEvalArgs::MixEvalArgs() Add *path* to the Nix search path. The Nix search path is initialized from the colon-separated [`NIX_PATH`](./env-common.md#env-NIX_PATH) environment variable, and is used to look up the location of Nix expressions using [paths](../language/values.md#type-path) enclosed in angle - brackets (i.e., ``). For instance, if the Nix search path - consists of the entries + brackets (i.e., ``). + + For instance, passing ``` - /home/eelco/Dev - /etc/nixos + -I /home/eelco/Dev + -I /etc/nixos ``` - Nix will look for paths relative to `/home/eelco/Dev` and - `/etc/nixos`, in this order. It is also possible to match paths - against a prefix. For example, the search path + will cause Nix to look for paths relative to `/home/eelco/Dev` and + `/etc/nixos`, in that order. This is equivalent to setting the + `NIX_PATH` environment variable to ``` - nixpkgs=/home/eelco/Dev/nixpkgs-branch - /etc/nixos + /home/eelco/Dev:/etc/nixos + ``` + + It is also possible to match paths against a prefix. For example, + passing + + ``` + -I nixpkgs=/home/eelco/Dev/nixpkgs-branch + -I /etc/nixos ``` will cause Nix to search for `` in @@ -59,10 +67,10 @@ MixEvalArgs::MixEvalArgs() If a path in the Nix search path starts with `http://` or `https://`, it is interpreted as the URL of a tarball that will be downloaded and unpacked to a temporary location. The tarball must consist of a single - top-level directory. For example, setting `NIX_PATH` to + top-level directory. For example, passing ``` - nixpkgs=https://github.com/NixOS/nixpkgs/archive/master.tar.gz + -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/master.tar.gz ``` tells Nix to download and use the current contents of the `master` @@ -71,11 +79,11 @@ MixEvalArgs::MixEvalArgs() The URLs of the tarballs from the official `nixos.org` channels (see [the manual page for `nix-channel`](nix-channel.md)) can be abbreviated as `channel:`. For instance, the - following two values of `NIX_PATH` are equivalent: + following two flags are equivalent: ``` - nixpkgs=channel:nixos-21.05 - nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz + -I nixpkgs=channel:nixos-21.05 + -I nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz ``` )", .category = category, From c66c904a057fa66c5d5c0d9fdf79196efb28f4b6 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 12:54:15 +0100 Subject: [PATCH 184/245] Tweak NIX_PATH description --- doc/manual/src/command-ref/env-common.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/manual/src/command-ref/env-common.md b/doc/manual/src/command-ref/env-common.md index be5797252..5845bdc43 100644 --- a/doc/manual/src/command-ref/env-common.md +++ b/doc/manual/src/command-ref/env-common.md @@ -8,10 +8,10 @@ Most Nix commands interpret the following environment variables: - [`NIX_PATH`]{#env-NIX_PATH}\ A colon-separated list of directories used to look up the location of Nix - expressions enclosed in angle brackets (i.e., ``), + expressions using [paths](../language/values.md#type-path) + enclosed in angle brackets (i.e., ``), e.g. `/home/eelco/Dev:/etc/nixos`. It can be extended using the - `-I` option. For more information about the semantics of the Nix - search path, see the documentation for `-I`. + [`-I` option](./opt-common#opt-I). - [`NIX_IGNORE_SYMLINK_STORE`]{#env-NIX_IGNORE_SYMLINK_STORE}\ Normally, the Nix store directory (typically `/nix/store`) is not From 2d5a91c71c69cddf5806c5bec9ca7dd468083d50 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 13:01:23 +0100 Subject: [PATCH 185/245] Remove auto assign --- .github/assign-by-files.yml | 5 ----- .github/workflows/assign-reviewer.yml | 12 ------------ 2 files changed, 17 deletions(-) delete mode 100644 .github/assign-by-files.yml delete mode 100644 .github/workflows/assign-reviewer.yml diff --git a/.github/assign-by-files.yml b/.github/assign-by-files.yml deleted file mode 100644 index f13b71776..000000000 --- a/.github/assign-by-files.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -# This files is used by https://github.com/marketplace/actions/auto-assign-reviewer-by-files -# to assign maintainers -"doc/**/*": - - fricklerhandwerk diff --git a/.github/workflows/assign-reviewer.yml b/.github/workflows/assign-reviewer.yml deleted file mode 100644 index 4371cbff4..000000000 --- a/.github/workflows/assign-reviewer.yml +++ /dev/null @@ -1,12 +0,0 @@ -name: "Auto Assign" -on: - - pull_request - -jobs: - assign_reviewer: - runs-on: ubuntu-latest - steps: - - uses: shufo/auto-assign-reviewer-by-files@v1.1.4 - with: - config: ".github/assign-by-files.yml" - token: ${{ secrets.GITHUB_TOKEN }} From ae5f62a894190e0075eb60ae4537ba81ca2a0a8d Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Sep 2022 15:37:09 +0200 Subject: [PATCH 186/245] Move isUri() and resolveUri() out of filetransfer.cc These are purely related to NIX_PATH / -I command line parsing, so put them in libexpr. --- src/libcmd/common-eval-args.cc | 8 ++++---- src/libexpr/eval.cc | 19 ++++++++++++++++++- src/libexpr/eval.hh | 4 ++++ src/libexpr/parser.y | 7 ++++--- src/libexpr/primops/fetchTree.cc | 2 -- src/libstore/filetransfer.cc | 18 ------------------ src/libstore/filetransfer.hh | 5 ----- 7 files changed, 30 insertions(+), 33 deletions(-) diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index dc0de4b9f..782a25964 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -142,10 +142,10 @@ Bindings * MixEvalArgs::getAutoArgs(EvalState & state) Path lookupFileArg(EvalState & state, std::string_view s) { - if (isUri(s)) { - return state.store->toRealPath( - fetchers::downloadTarball( - state.store, resolveUri(s), "source", false).first.storePath); + if (EvalSettings::isPseudoUrl(s)) { + auto storePath = fetchers::downloadTarball( + state.store, EvalSettings::resolvePseudoUrl(s), "source", false).first.storePath; + return state.store->toRealPath(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.cc b/src/libexpr/eval.cc index b67ed853e..ca8d634da 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -402,7 +402,7 @@ static Strings parseNixPath(const std::string & s) } if (*p == ':') { - if (isUri(std::string(start2, s.end()))) { + if (EvalSettings::isPseudoUrl(std::string(start2, s.end()))) { ++p; while (p != s.end() && *p != ':') ++p; } @@ -2583,6 +2583,23 @@ Strings EvalSettings::getDefaultNixPath() return res; } +bool EvalSettings::isPseudoUrl(std::string_view s) +{ + if (s.compare(0, 8, "channel:") == 0) return true; + size_t pos = s.find("://"); + if (pos == std::string::npos) return false; + std::string scheme(s, 0, pos); + return scheme == "http" || scheme == "https" || scheme == "file" || scheme == "channel" || scheme == "git" || scheme == "s3" || scheme == "ssh"; +} + +std::string EvalSettings::resolvePseudoUrl(std::string_view url) +{ + if (hasPrefix(url, "channel:")) + return "https://nixos.org/channels/" + std::string(url.substr(8)) + "/nixexprs.tar.xz"; + else + return std::string(url); +} + EvalSettings evalSettings; static GlobalConfig::Register rEvalSettings(&evalSettings); diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index f07f15d43..cf307d820 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -590,6 +590,10 @@ struct EvalSettings : Config static Strings getDefaultNixPath(); + static bool isPseudoUrl(std::string_view s); + + static std::string resolvePseudoUrl(std::string_view url); + Setting enableNativeCode{this, false, "allow-unsafe-native-code-during-evaluation", "Whether builtin functions that allow executing native code should be enabled."}; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 7c9b5a2db..6ef9407cd 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -805,10 +805,11 @@ std::pair EvalState::resolveSearchPathElem(const SearchPathEl std::pair res; - if (isUri(elem.second)) { + if (EvalSettings::isPseudoUrl(elem.second)) { try { - res = { true, store->toRealPath(fetchers::downloadTarball( - store, resolveUri(elem.second), "source", false).first.storePath) }; + auto storePath = fetchers::downloadTarball( + store, EvalSettings::resolvePseudoUrl(elem.second), "source", false).first.storePath; + res = { true, store->toRealPath(storePath) }; } catch (FileTransferError & e) { logWarning({ .msg = hintfmt("Nix search path entry '%1%' cannot be downloaded, ignoring", elem.second) diff --git a/src/libexpr/primops/fetchTree.cc b/src/libexpr/primops/fetchTree.cc index 84e7f5c02..680446787 100644 --- a/src/libexpr/primops/fetchTree.cc +++ b/src/libexpr/primops/fetchTree.cc @@ -220,8 +220,6 @@ static void fetch(EvalState & state, const PosIdx pos, Value * * args, Value & v } else url = state.forceStringNoCtx(*args[0], pos); - url = resolveUri(*url); - state.checkURI(*url); if (name == "") diff --git a/src/libstore/filetransfer.cc b/src/libstore/filetransfer.cc index 2ff411e18..756bd4423 100644 --- a/src/libstore/filetransfer.cc +++ b/src/libstore/filetransfer.cc @@ -33,14 +33,6 @@ FileTransferSettings fileTransferSettings; static GlobalConfig::Register rFileTransferSettings(&fileTransferSettings); -std::string resolveUri(std::string_view uri) -{ - if (uri.compare(0, 8, "channel:") == 0) - return "https://nixos.org/channels/" + std::string(uri.substr(8)) + "/nixexprs.tar.xz"; - else - return std::string(uri); -} - struct curlFileTransfer : public FileTransfer { CURLM * curlm = 0; @@ -873,14 +865,4 @@ FileTransferError::FileTransferError(FileTransfer::Error error, std::optional response, const Args & ... args); }; -bool isUri(std::string_view s); - -/* Resolve deprecated 'channel:' URLs. */ -std::string resolveUri(std::string_view uri); - } From fd0ed7511818ba871dc3e28796ec1d0ca57b22ec Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 7 Dec 2022 15:23:01 +0100 Subject: [PATCH 187/245] Support flake references in the old CLI Fixes #7026. --- doc/manual/src/release-notes/rl-next.md | 9 +++++++++ src/libcmd/common-eval-args.cc | 15 +++++++++++++-- src/libexpr/eval.cc | 3 ++- src/libexpr/parser.y | 13 ++++++++++++- tests/flakes/flakes.sh | 6 ++++++ 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index c1f4de76f..160537f41 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -2,3 +2,12 @@ * The `repeat` and `enforce-determinism` options have been removed since they had been broken under many circumstances for a long time. + +* You can now use flake references in the old CLI, e.g. + + ``` + # nix-build flake:nixpkgs -A hello + # nix-build -I nixpkgs=flake:github:NixOS/nixpkgs/nixos-22.05 \ + '' -A hello + # NIX_PATH=nixpkgs=flake:nixpkgs nix-build '' -A hello + ``` diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index 782a25964..64be2629b 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -146,10 +146,21 @@ Path lookupFileArg(EvalState & state, std::string_view s) auto storePath = fetchers::downloadTarball( state.store, EvalSettings::resolvePseudoUrl(s), "source", false).first.storePath; return state.store->toRealPath(storePath); - } else if (s.size() > 2 && s.at(0) == '<' && s.at(s.size() - 1) == '>') { + } + + else if (hasPrefix(s, "flake:")) { + settings.requireExperimentalFeature(Xp::Flakes); + auto flakeRef = parseFlakeRef(std::string(s.substr(6)), {}, true, false); + auto storePath = flakeRef.resolve(state.store).fetchTree(state.store).first.storePath; + return state.store->toRealPath(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); - } else + } + + else return absPath(std::string(s)); } diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index ca8d634da..6955aacbf 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -402,7 +402,8 @@ static Strings parseNixPath(const std::string & s) } if (*p == ':') { - if (EvalSettings::isPseudoUrl(std::string(start2, s.end()))) { + auto prefix = std::string(start2, s.end()); + if (EvalSettings::isPseudoUrl(prefix) || hasPrefix(prefix, "flake:")) { ++p; while (p != s.end() && *p != ':') ++p; } diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index 6ef9407cd..fbf865719 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -643,6 +643,7 @@ formal #include "filetransfer.hh" #include "fetchers.hh" #include "store-api.hh" +#include "flake/flake.hh" namespace nix { @@ -816,7 +817,17 @@ std::pair EvalState::resolveSearchPathElem(const SearchPathEl }); res = { false, "" }; } - } else { + } + + else if (hasPrefix(elem.second, "flake:")) { + settings.requireExperimentalFeature(Xp::Flakes); + auto flakeRef = parseFlakeRef(elem.second.substr(6), {}, true, false); + debug("fetching flake search path element '%s''", elem.second); + auto storePath = flakeRef.resolve(store).fetchTree(store).first.storePath; + res = { true, store->toRealPath(storePath) }; + } + + else { auto path = absPath(elem.second); if (pathExists(path)) res = { true, path }; diff --git a/tests/flakes/flakes.sh b/tests/flakes/flakes.sh index 8cdc320fb..5ef4d0a7a 100644 --- a/tests/flakes/flakes.sh +++ b/tests/flakes/flakes.sh @@ -473,3 +473,9 @@ nix store delete $(nix store add-path $badFlakeDir) [[ $(nix path-info $(nix store add-path $flake1Dir)) =~ flake1 ]] [[ $(nix path-info path:$(nix store add-path $flake1Dir)) =~ simple ]] + +# Test fetching flakerefs in the legacy CLI. +[[ $(nix-instantiate --eval flake:flake3 -A x) = 123 ]] +[[ $(nix-instantiate --eval flake:git+file://$flake3Dir -A x) = 123 ]] +[[ $(nix-instantiate -I flake3=flake:flake3 --eval '' -A x) = 123 ]] +[[ $(NIX_PATH=flake3=flake:flake3 nix-instantiate --eval '' -A x) = 123 ]] From fa409131cd74b3eb1bd37230e74f9a9314b34066 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Wed, 7 Dec 2022 17:13:16 +0100 Subject: [PATCH 188/245] Add links to the manual Co-authored-by: Valentin Gagarin --- doc/manual/src/release-notes/rl-next.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 160537f41..f9a0063ee 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -3,7 +3,10 @@ * The `repeat` and `enforce-determinism` options have been removed since they had been broken under many circumstances for a long time. -* You can now use flake references in the old CLI, e.g. +* You can now use [flake references] in the [old command line interface], e.g. + + [flake references]: ../command-ref/new-cli/nix3-flake.md#flake-references + [old command line interface]: ../command-ref/main-commands.md ``` # nix-build flake:nixpkgs -A hello From 7a85199f87217cd99eb340e96b99ddda983f915f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 14:04:12 +0100 Subject: [PATCH 189/245] Add docs from the lazy-trees branch --- src/libcmd/common-eval-args.cc | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index 64be2629b..67fc501c9 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -85,6 +85,23 @@ MixEvalArgs::MixEvalArgs() -I nixpkgs=channel:nixos-21.05 -I nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz ``` + + You can also fetch source trees using flake URLs and add them to the + search path. For instance, + + ``` + -I nixpkgs=flake:nixpkgs + ``` + + specifies that the prefix `nixpkgs` shall refer to the source tree + downloaded from the `nixpkgs` entry in the flake registry. Similarly, + + ``` + -I nixpkgs=flake:github:NixOS/nixpkgs/nixos-22.05 + ``` + + makes `` refer to a particular branch of the + `NixOS/nixpkgs` repository on GitHub. )", .category = category, .labels = {"path"}, From 4ed8bb1cb132a6671a51e96abbd8241fbe01d6ef Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 12 Dec 2022 14:29:02 +0100 Subject: [PATCH 190/245] suggestions from review --- doc/manual/src/architecture/architecture.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 4b0704894..3a7d1d6f5 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -15,14 +15,14 @@ Nix consists of [hierarchical layers]. | | commmand line interface |------. | | '-------------------------' | | | | | | -| evaluates | | +| calls | | | | manages | | V | | | .-------------------------. | | -| | configuration language | | | +| | language evaluator | | | | '-------------------------' | | | | | | -| evaluates to | | +| produces | | | | V | | +----------------------------|------------------------------+ | | | store | | | @@ -36,7 +36,7 @@ Nix consists of [hierarchical layers]. At the top is the [command line interface](../command-ref/command-ref.md), translating from invocations of Nix executables to interactions with the underlying layers. -Below that is the [Nix language](../language/index.md), the configuration language for Nix. +Below that is the evaluator for the [Nix language](../language/index.md), the configuration language for Nix. Its expressions ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. The command line interface and the Nix language are what users interact with most. From a456630a5a93db170bbc5570ae0e1a48a16cbe09 Mon Sep 17 00:00:00 2001 From: Benoit de Chezelles Date: Thu, 28 Oct 2021 00:56:36 +0200 Subject: [PATCH 191/245] Allow to disable global flake-registry with "" --- src/libfetchers/fetch-settings.hh | 7 ++++++- src/libfetchers/registry.cc | 3 +++ tests/flakes/flakes.sh | 14 +++++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/libfetchers/fetch-settings.hh b/src/libfetchers/fetch-settings.hh index 6452143a1..f33cbdcfc 100644 --- a/src/libfetchers/fetch-settings.hh +++ b/src/libfetchers/fetch-settings.hh @@ -71,7 +71,12 @@ struct FetchSettings : public Config "Whether to warn about dirty Git/Mercurial trees."}; Setting flakeRegistry{this, "https://channels.nixos.org/flake-registry.json", "flake-registry", - "Path or URI of the global flake registry."}; + R"( + Path or URI of the global flake registry. + + When empty, disables the global flake registry. + )"}; + Setting useRegistries{this, true, "use-registries", "Whether to use flake registries to resolve flake references."}; diff --git a/src/libfetchers/registry.cc b/src/libfetchers/registry.cc index acd1ff866..43c03beec 100644 --- a/src/libfetchers/registry.cc +++ b/src/libfetchers/registry.cc @@ -153,6 +153,9 @@ static std::shared_ptr getGlobalRegistry(ref store) { static auto reg = [&]() { auto path = fetchSettings.flakeRegistry.get(); + if (path == "") { + return std::make_shared(Registry::Global); // empty registry + } if (!hasPrefix(path, "/")) { auto storePath = downloadFile(store, path, "flake-registry.json", false).storePath; diff --git a/tests/flakes/flakes.sh b/tests/flakes/flakes.sh index 5ef4d0a7a..07f1e6698 100644 --- a/tests/flakes/flakes.sh +++ b/tests/flakes/flakes.sh @@ -74,8 +74,10 @@ nix registry add --registry $registry flake3 git+file://$flake3Dir nix registry add --registry $registry flake4 flake3 nix registry add --registry $registry nixpkgs flake1 -# Test 'nix flake list'. +# Test 'nix registry list'. [[ $(nix registry list | wc -l) == 5 ]] +nix registry list | grep -q '^global' +nix registry list | grep -q -v '^user' # nothing in user registry # Test 'nix flake metadata'. nix flake metadata flake1 @@ -340,6 +342,16 @@ nix registry pin flake1 flake3 nix registry remove flake1 [[ $(nix registry list | wc -l) == 5 ]] +# Test 'nix registry list' with a disabled global registry. +nix registry add user-flake1 git+file://$flake1Dir +nix registry add user-flake2 git+file://$flake2Dir +[[ $(nix --flake-registry "" registry list | wc -l) == 2 ]] +nix --flake-registry "" registry list | grep -q -v '^global' # nothing in global registry +nix --flake-registry "" registry list | grep -q '^user' +nix registry remove user-flake1 +nix registry remove user-flake2 +[[ $(nix registry list | wc -l) == 5 ]] + # Test 'nix flake clone'. rm -rf $TEST_ROOT/flake1-v2 nix flake clone flake1 --dest $TEST_ROOT/flake1-v2 From 8e8a511aa05a84a40102d22db8de4239d3419f53 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 9 Dec 2022 18:12:28 +0100 Subject: [PATCH 192/245] Enable some language tests that were accidentally disabled This didn't run because the corresponding .exp file didn't exist. --- tests/lang.sh | 1 + tests/lang/eval-okay-closure.exp | 1 + tests/lang/eval-okay-functionargs.exp | 1 + tests/lang/eval-okay-path-antiquotation.exp | 1 + tests/lang/eval-okay-path.exp | 1 + 5 files changed, 5 insertions(+) create mode 100644 tests/lang/eval-okay-closure.exp create mode 100644 tests/lang/eval-okay-functionargs.exp create mode 100644 tests/lang/eval-okay-path-antiquotation.exp create mode 100644 tests/lang/eval-okay-path.exp diff --git a/tests/lang.sh b/tests/lang.sh index c0b0fc58c..463f72826 100644 --- a/tests/lang.sh +++ b/tests/lang.sh @@ -2,6 +2,7 @@ source common.sh export TEST_VAR=foo # for eval-okay-getenv.nix export NIX_REMOTE=dummy:// +export NIX_STORE_DIR=/nix/store nix-instantiate --eval -E 'builtins.trace "Hello" 123' 2>&1 | grep -q Hello nix-instantiate --eval -E 'builtins.addErrorContext "Hello" 123' 2>&1 diff --git a/tests/lang/eval-okay-closure.exp b/tests/lang/eval-okay-closure.exp new file mode 100644 index 000000000..e7dbf9781 --- /dev/null +++ b/tests/lang/eval-okay-closure.exp @@ -0,0 +1 @@ +[ { foo = true; key = -13; } { foo = true; key = -12; } { foo = true; key = -11; } { foo = true; key = -9; } { foo = true; key = -8; } { foo = true; key = -7; } { foo = true; key = -5; } { foo = true; key = -4; } { foo = true; key = -3; } { key = -1; } { foo = true; key = 0; } { foo = true; key = 1; } { foo = true; key = 2; } { foo = true; key = 4; } { foo = true; key = 5; } { foo = true; key = 6; } { key = 8; } { foo = true; key = 9; } { foo = true; key = 10; } { foo = true; key = 13; } { foo = true; key = 14; } { foo = true; key = 15; } { key = 17; } { foo = true; key = 18; } { foo = true; key = 19; } { foo = true; key = 22; } { foo = true; key = 23; } { key = 26; } { foo = true; key = 27; } { foo = true; key = 28; } { foo = true; key = 31; } { foo = true; key = 32; } { key = 35; } { foo = true; key = 36; } { foo = true; key = 40; } { foo = true; key = 41; } { key = 44; } { foo = true; key = 45; } { foo = true; key = 49; } { key = 53; } { foo = true; key = 54; } { foo = true; key = 58; } { key = 62; } { foo = true; key = 67; } { key = 71; } { key = 80; } ] diff --git a/tests/lang/eval-okay-functionargs.exp b/tests/lang/eval-okay-functionargs.exp new file mode 100644 index 000000000..c1c9f8ffa --- /dev/null +++ b/tests/lang/eval-okay-functionargs.exp @@ -0,0 +1 @@ +[ "stdenv" "fetchurl" "aterm-stdenv" "aterm-stdenv2" "libX11" "libXv" "mplayer-stdenv2.libXv-libX11" "mplayer-stdenv2.libXv-libX11_2" "nix-stdenv-aterm-stdenv" "nix-stdenv2-aterm2-stdenv2" ] diff --git a/tests/lang/eval-okay-path-antiquotation.exp b/tests/lang/eval-okay-path-antiquotation.exp new file mode 100644 index 000000000..b0e528f2a --- /dev/null +++ b/tests/lang/eval-okay-path-antiquotation.exp @@ -0,0 +1 @@ +{ absolute = /foo; expr = /home/eelco/Dev/nix/tests/lang/foo/bar; home = /tmp/nix-shell.ZeLfDw/nix-test/default/test-home/foo; notfirst = /home/eelco/Dev/nix/tests/lang/bar/foo; simple = /home/eelco/Dev/nix/tests/lang/foo; slashes = /foo/bar; surrounded = /home/eelco/Dev/nix/tests/lang/a-foo-b; } diff --git a/tests/lang/eval-okay-path.exp b/tests/lang/eval-okay-path.exp new file mode 100644 index 000000000..3ce7f8283 --- /dev/null +++ b/tests/lang/eval-okay-path.exp @@ -0,0 +1 @@ +"/nix/store/ya937r4ydw0l6kayq8jkyqaips9c75jm-output" From 17f81d32152178730e8577caa60279bb86bb9372 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 16:41:46 +0100 Subject: [PATCH 193/245] Fix unused variable warning --- src/nix-store/nix-store.cc | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 96e265f9e..3bbefedbe 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -812,10 +812,13 @@ static void opServe(Strings opFlags, Strings opArgs) if (nrRepeats != 0) { throw Error("client requested repeating builds, but this is not currently implemented"); } - // Ignore. It used to be true by default, but also only never had any effect when `nrRepeats == 0`. - // We have already asserted that `nrRepeats` in fact is 0, so we can safely ignore this without - // doing something other than what the client asked for. - auto _enforceDeterminism = readInt(in); + // Ignore 'enforceDeterminism'. It used to be true by + // default, but also only never had any effect when + // `nrRepeats == 0`. We have already asserted that + // `nrRepeats` in fact is 0, so we can safely ignore this + // without doing something other than what the client + // asked for. + readInt(in); settings.runDiffHook = true; } From e86530ee46cc3ccb7ea137889f0d04cf9061664f Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 16:55:42 +0100 Subject: [PATCH 194/245] Fix reference to test directory path --- tests/lang.sh | 4 ++-- tests/lang/eval-okay-path-antiquotation.exp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/lang.sh b/tests/lang.sh index 463f72826..95e795e2e 100644 --- a/tests/lang.sh +++ b/tests/lang.sh @@ -51,10 +51,10 @@ for i in lang/eval-okay-*.nix; do if test -e lang/$i.flags; then flags=$(cat lang/$i.flags) fi - if ! expect 0 env NIX_PATH=lang/dir3:lang/dir4 nix-instantiate $flags --eval --strict lang/$i.nix > lang/$i.out; then + if ! expect 0 env NIX_PATH=lang/dir3:lang/dir4 HOME=/fake-home nix-instantiate $flags --eval --strict lang/$i.nix > lang/$i.out; then echo "FAIL: $i should evaluate" fail=1 - elif ! diff lang/$i.out lang/$i.exp; then + elif ! diff <(< lang/$i.out sed -e "s|$(pwd)|/pwd|g") lang/$i.exp; then echo "FAIL: evaluation result of $i not as expected" fail=1 fi diff --git a/tests/lang/eval-okay-path-antiquotation.exp b/tests/lang/eval-okay-path-antiquotation.exp index b0e528f2a..5b8ea0243 100644 --- a/tests/lang/eval-okay-path-antiquotation.exp +++ b/tests/lang/eval-okay-path-antiquotation.exp @@ -1 +1 @@ -{ absolute = /foo; expr = /home/eelco/Dev/nix/tests/lang/foo/bar; home = /tmp/nix-shell.ZeLfDw/nix-test/default/test-home/foo; notfirst = /home/eelco/Dev/nix/tests/lang/bar/foo; simple = /home/eelco/Dev/nix/tests/lang/foo; slashes = /foo/bar; surrounded = /home/eelco/Dev/nix/tests/lang/a-foo-b; } +{ absolute = /foo; expr = /pwd/lang/foo/bar; home = /fake-home/foo; notfirst = /pwd/lang/bar/foo; simple = /pwd/lang/foo; slashes = /foo/bar; surrounded = /pwd/lang/a-foo-b; } From 173dcb0af9249487c2d9ad5de7218fcf203873bd Mon Sep 17 00:00:00 2001 From: Florian Friesdorf Date: Tue, 22 Nov 2022 12:46:55 +0000 Subject: [PATCH 195/245] Don't reverse stack trace when showing When debugging nix expressions the outermost trace tends to be more useful than the innermost. It is therefore printed last to save developers from scrolling. --- src/libutil/error.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 9172f67a6..9cac6ac91 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -287,7 +287,7 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s // traces if (showTrace && !einfo.traces.empty()) { - for (auto iter = einfo.traces.rbegin(); iter != einfo.traces.rend(); ++iter) { + for (auto iter = einfo.traces.begin(); iter != einfo.traces.end(); ++iter) { oss << "\n" << "… " << iter->hint.str() << "\n"; if (iter->pos.has_value() && (*iter->pos)) { From d269976be6def2928e6a315ab2b85b947f4308f2 Mon Sep 17 00:00:00 2001 From: Florian Friesdorf Date: Tue, 22 Nov 2022 16:45:58 +0000 Subject: [PATCH 196/245] Show stack trace above error message Save developers from scrolling by displaying the error message last, below the stack trace. --- src/libutil/error.cc | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 9cac6ac91..449baaad1 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -262,6 +262,28 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s prefix += ":" ANSI_NORMAL " "; std::ostringstream oss; + + // traces + if (showTrace && !einfo.traces.empty()) { + for (auto iter = einfo.traces.begin(); iter != einfo.traces.end(); ++iter) { + oss << "\n" << "… " << iter->hint.str() << "\n"; + + if (iter->pos.has_value() && (*iter->pos)) { + auto pos = iter->pos.value(); + oss << "\n"; + printAtPos(pos, oss); + + auto loc = getCodeLines(pos); + if (loc.has_value()) { + oss << "\n"; + printCodeLines(oss, "", pos, *loc); + oss << "\n"; + } + } + } + oss << "\n" << prefix; + } + oss << einfo.msg << "\n"; if (einfo.errPos.has_value() && *einfo.errPos) { @@ -285,26 +307,6 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s "?" << std::endl; } - // traces - if (showTrace && !einfo.traces.empty()) { - for (auto iter = einfo.traces.begin(); iter != einfo.traces.end(); ++iter) { - oss << "\n" << "… " << iter->hint.str() << "\n"; - - if (iter->pos.has_value() && (*iter->pos)) { - auto pos = iter->pos.value(); - oss << "\n"; - printAtPos(pos, oss); - - auto loc = getCodeLines(pos); - if (loc.has_value()) { - oss << "\n"; - printCodeLines(oss, "", pos, *loc); - oss << "\n"; - } - } - } - } - out << indent(prefix, std::string(filterANSIEscapes(prefix, true).size(), ' '), chomp(oss.str())); return out; From 7b122d43a49a3bf05436cb2c9b23934ff4bcba2c Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 28 Nov 2022 10:39:28 -0500 Subject: [PATCH 197/245] Fix stack context notes to not rely on order Make everything be in the form "while ..." (most things were already), and in particular *don't* use other propositions that must go after or before specific "while ..." clauses to make sense. --- src/libexpr/eval.cc | 2 +- src/libexpr/flake/flake.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 6955aacbf..0d9226d3b 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1660,7 +1660,7 @@ void EvalState::callFunction(Value & fun, size_t nrArgs, Value * * args, Value & (lambda.name ? concatStrings("'", symbols[lambda.name], "'") : "anonymous lambda")); - addErrorTrace(e, pos, "from call site%s", ""); + addErrorTrace(e, pos, "while evaluating call site%s", ""); } throw; } diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 6b5d6f6b3..6344fb253 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -143,7 +143,7 @@ static FlakeInput parseFlakeInput(EvalState & state, } catch (Error & e) { e.addTrace( state.positions[attr.pos], - hintfmt("in flake attribute '%s'", state.symbols[attr.name])); + hintfmt("while evaluating flake attribute '%s'", state.symbols[attr.name])); throw; } } @@ -152,7 +152,7 @@ static FlakeInput parseFlakeInput(EvalState & state, try { input.ref = FlakeRef::fromAttrs(attrs); } catch (Error & e) { - e.addTrace(state.positions[pos], hintfmt("in flake input")); + e.addTrace(state.positions[pos], hintfmt("while evaluating flake input")); throw; } else { From 8618c6cc75e19ed658649bd806b218de954ea3bc Mon Sep 17 00:00:00 2001 From: Florian Friesdorf Date: Fri, 9 Dec 2022 17:36:25 +0000 Subject: [PATCH 198/245] Simplify loop, feedback from @tfc and @Ericson2314 --- src/libutil/error.cc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 449baaad1..3bb3efb0e 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -265,11 +265,11 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s // traces if (showTrace && !einfo.traces.empty()) { - for (auto iter = einfo.traces.begin(); iter != einfo.traces.end(); ++iter) { - oss << "\n" << "… " << iter->hint.str() << "\n"; + for (const auto & trace : einfo.traces) { + oss << "\n" << "… " << trace.hint.str() << "\n"; - if (iter->pos.has_value() && (*iter->pos)) { - auto pos = iter->pos.value(); + if (trace.pos.has_value() && (*trace.pos)) { + auto pos = trace.pos.value(); oss << "\n"; printAtPos(pos, oss); From 900b85408435f6f7118e5d46f606d377452cdb12 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Mon, 12 Dec 2022 19:57:32 +0100 Subject: [PATCH 199/245] Add CanonPath wrapper to represent canonicalized paths --- src/libutil/canon-path.cc | 103 +++++++++++++++++++ src/libutil/canon-path.hh | 173 ++++++++++++++++++++++++++++++++ src/libutil/tests/canon-path.cc | 155 ++++++++++++++++++++++++++++ 3 files changed, 431 insertions(+) create mode 100644 src/libutil/canon-path.cc create mode 100644 src/libutil/canon-path.hh create mode 100644 src/libutil/tests/canon-path.cc diff --git a/src/libutil/canon-path.cc b/src/libutil/canon-path.cc new file mode 100644 index 000000000..b132b4262 --- /dev/null +++ b/src/libutil/canon-path.cc @@ -0,0 +1,103 @@ +#include "canon-path.hh" +#include "util.hh" + +namespace nix { + +CanonPath CanonPath::root = CanonPath("/"); + +CanonPath::CanonPath(std::string_view raw) + : path(absPath((Path) raw, "/")) +{ } + +CanonPath::CanonPath(std::string_view raw, const CanonPath & root) + : path(absPath((Path) raw, root.abs())) +{ } + +std::optional CanonPath::parent() const +{ + if (isRoot()) return std::nullopt; + return CanonPath(unchecked_t(), path.substr(0, std::max((size_t) 1, path.rfind('/')))); +} + +void CanonPath::pop() +{ + assert(!isRoot()); + path.resize(std::max((size_t) 1, path.rfind('/'))); +} + +bool CanonPath::isWithin(const CanonPath & parent) const +{ + return !( + path.size() < parent.path.size() + || path.substr(0, parent.path.size()) != parent.path + || (parent.path.size() > 1 && path.size() > parent.path.size() + && path[parent.path.size()] != '/')); +} + +CanonPath CanonPath::removePrefix(const CanonPath & prefix) const +{ + assert(isWithin(prefix)); + if (prefix.isRoot()) return *this; + if (path.size() == prefix.path.size()) return root; + return CanonPath(unchecked_t(), path.substr(prefix.path.size())); +} + +void CanonPath::extend(const CanonPath & x) +{ + if (x.isRoot()) return; + if (isRoot()) + path += x.rel(); + else + path += x.abs(); +} + +CanonPath CanonPath::operator + (const CanonPath & x) const +{ + auto res = *this; + res.extend(x); + return res; +} + +void CanonPath::push(std::string_view c) +{ + assert(c.find('/') == c.npos); + assert(c != "." && c != ".."); + if (!isRoot()) path += '/'; + path += c; +} + +CanonPath CanonPath::operator + (std::string_view c) const +{ + auto res = *this; + res.push(c); + return res; +} + +bool CanonPath::isAllowed(const std::set & allowed) const +{ + /* Check if `this` is an exact match or the parent of an + allowed path. */ + auto lb = allowed.lower_bound(*this); + if (lb != allowed.end()) { + if (lb->isWithin(*this)) + return true; + } + + /* Check if a parent of `this` is allowed. */ + auto path = *this; + while (!path.isRoot()) { + path.pop(); + if (allowed.count(path)) + return true; + } + + return false; +} + +std::ostream & operator << (std::ostream & stream, const CanonPath & path) +{ + stream << path.abs(); + return stream; +} + +} diff --git a/src/libutil/canon-path.hh b/src/libutil/canon-path.hh new file mode 100644 index 000000000..c5e7f0596 --- /dev/null +++ b/src/libutil/canon-path.hh @@ -0,0 +1,173 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace nix { + +/* A canonical representation of a path. It ensures the following: + + - It always starts with a slash. + + - It never ends with a slash, except if the path is "/". + + - A slash is never followed by a slash (i.e. no empty components). + + - There are no components equal to '.' or '..'. + + Note that the path does not need to correspond to an actually + existing path, and there is no guarantee that symlinks are + resolved. +*/ +class CanonPath +{ + std::string path; + +public: + + /* Construct a canon path from a non-canonical path. Any '.', '..' + or empty components are removed. */ + CanonPath(std::string_view raw); + + explicit CanonPath(const char * raw) + : CanonPath(std::string_view(raw)) + { } + + struct unchecked_t { }; + + CanonPath(unchecked_t _, std::string path) + : path(std::move(path)) + { } + + static CanonPath root; + + /* If `raw` starts with a slash, return + `CanonPath(raw)`. Otherwise return a `CanonPath` representing + `root + "/" + raw`. */ + CanonPath(std::string_view raw, const CanonPath & root); + + bool isRoot() const + { return path.size() <= 1; } + + explicit operator std::string_view() const + { return path; } + + const std::string & abs() const + { return path; } + + /* Like abs(), but return an empty string if this path is + '/'. Thus the returned string never ends in a slash. */ + const std::string & absOrEmpty() const + { + const static std::string epsilon; + return isRoot() ? epsilon : path; + } + + const char * c_str() const + { return path.c_str(); } + + std::string_view rel() const + { return ((std::string_view) path).substr(1); } + + struct Iterator + { + std::string_view remaining; + size_t slash; + + Iterator(std::string_view remaining) + : remaining(remaining) + , slash(remaining.find('/')) + { } + + bool operator != (const Iterator & x) const + { return remaining.data() != x.remaining.data(); } + + const std::string_view operator * () const + { return remaining.substr(0, slash); } + + void operator ++ () + { + if (slash == remaining.npos) + remaining = remaining.substr(remaining.size()); + else { + remaining = remaining.substr(slash + 1); + slash = remaining.find('/'); + } + } + }; + + Iterator begin() const { return Iterator(rel()); } + Iterator end() const { return Iterator(rel().substr(path.size() - 1)); } + + std::optional parent() const; + + /* Remove the last component. Panics if this path is the root. */ + void pop(); + + std::optional dirOf() const + { + if (isRoot()) return std::nullopt; + return path.substr(0, path.rfind('/')); + } + + std::optional baseName() const + { + if (isRoot()) return std::nullopt; + return ((std::string_view) path).substr(path.rfind('/') + 1); + } + + bool operator == (const CanonPath & x) const + { return path == x.path; } + + bool operator != (const CanonPath & x) const + { return path != x.path; } + + /* Compare paths lexicographically except that path separators + are sorted before any other character. That is, in the sorted order + a directory is always followed directly by its children. For + instance, 'foo' < 'foo/bar' < 'foo!'. */ + bool operator < (const CanonPath & x) const + { + auto i = path.begin(); + auto j = x.path.begin(); + for ( ; i != path.end() && j != x.path.end(); ++i, ++j) { + auto c_i = *i; + if (c_i == '/') c_i = 0; + auto c_j = *j; + if (c_j == '/') c_j = 0; + if (c_i < c_j) return true; + if (c_i > c_j) return false; + } + return i == path.end() && j != x.path.end(); + } + + /* Return true if `this` is equal to `parent` or a child of + `parent`. */ + bool isWithin(const CanonPath & parent) const; + + CanonPath removePrefix(const CanonPath & prefix) const; + + /* Append another path to this one. */ + void extend(const CanonPath & x); + + /* Concatenate two paths. */ + CanonPath operator + (const CanonPath & x) const; + + /* Add a path component to this one. It must not contain any slashes. */ + void push(std::string_view c); + + CanonPath operator + (std::string_view c) const; + + /* Check whether access to this path is allowed, which is the case + if 1) `this` is within any of the `allowed` paths; or 2) any of + the `allowed` paths are within `this`. (The latter condition + ensures access to the parents of allowed paths.) */ + bool isAllowed(const std::set & allowed) const; +}; + +std::ostream & operator << (std::ostream & stream, const CanonPath & path); + +} diff --git a/src/libutil/tests/canon-path.cc b/src/libutil/tests/canon-path.cc new file mode 100644 index 000000000..c1c5adadf --- /dev/null +++ b/src/libutil/tests/canon-path.cc @@ -0,0 +1,155 @@ +#include "canon-path.hh" + +#include + +namespace nix { + + TEST(CanonPath, basic) { + { + CanonPath p("/"); + ASSERT_EQ(p.abs(), "/"); + ASSERT_EQ(p.rel(), ""); + ASSERT_EQ(p.baseName(), std::nullopt); + ASSERT_EQ(p.dirOf(), std::nullopt); + ASSERT_FALSE(p.parent()); + } + + { + CanonPath p("/foo//"); + ASSERT_EQ(p.abs(), "/foo"); + ASSERT_EQ(p.rel(), "foo"); + ASSERT_EQ(*p.baseName(), "foo"); + ASSERT_EQ(*p.dirOf(), ""); // FIXME: do we want this? + ASSERT_EQ(p.parent()->abs(), "/"); + } + + { + CanonPath p("foo/bar"); + ASSERT_EQ(p.abs(), "/foo/bar"); + ASSERT_EQ(p.rel(), "foo/bar"); + ASSERT_EQ(*p.baseName(), "bar"); + ASSERT_EQ(*p.dirOf(), "/foo"); + ASSERT_EQ(p.parent()->abs(), "/foo"); + } + + { + CanonPath p("foo//bar/"); + ASSERT_EQ(p.abs(), "/foo/bar"); + ASSERT_EQ(p.rel(), "foo/bar"); + ASSERT_EQ(*p.baseName(), "bar"); + ASSERT_EQ(*p.dirOf(), "/foo"); + } + } + + TEST(CanonPath, pop) { + CanonPath p("foo/bar/x"); + ASSERT_EQ(p.abs(), "/foo/bar/x"); + p.pop(); + ASSERT_EQ(p.abs(), "/foo/bar"); + p.pop(); + ASSERT_EQ(p.abs(), "/foo"); + p.pop(); + ASSERT_EQ(p.abs(), "/"); + } + + TEST(CanonPath, removePrefix) { + CanonPath p1("foo/bar"); + CanonPath p2("foo/bar/a/b/c"); + ASSERT_EQ(p2.removePrefix(p1).abs(), "/a/b/c"); + ASSERT_EQ(p1.removePrefix(p1).abs(), "/"); + ASSERT_EQ(p1.removePrefix(CanonPath("/")).abs(), "/foo/bar"); + } + + TEST(CanonPath, iter) { + { + CanonPath p("a//foo/bar//"); + std::vector ss; + for (auto & c : p) ss.push_back(c); + ASSERT_EQ(ss, std::vector({"a", "foo", "bar"})); + } + + { + CanonPath p("/"); + std::vector ss; + for (auto & c : p) ss.push_back(c); + ASSERT_EQ(ss, std::vector()); + } + } + + TEST(CanonPath, concat) { + { + CanonPath p1("a//foo/bar//"); + CanonPath p2("xyzzy/bla"); + ASSERT_EQ((p1 + p2).abs(), "/a/foo/bar/xyzzy/bla"); + } + + { + CanonPath p1("/"); + CanonPath p2("/a/b"); + ASSERT_EQ((p1 + p2).abs(), "/a/b"); + } + + { + CanonPath p1("/a/b"); + CanonPath p2("/"); + ASSERT_EQ((p1 + p2).abs(), "/a/b"); + } + + { + CanonPath p("/foo/bar"); + ASSERT_EQ((p + "x").abs(), "/foo/bar/x"); + } + + { + CanonPath p("/"); + ASSERT_EQ((p + "foo" + "bar").abs(), "/foo/bar"); + } + } + + TEST(CanonPath, within) { + { + ASSERT_TRUE(CanonPath("foo").isWithin(CanonPath("foo"))); + ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("bar"))); + ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("fo"))); + ASSERT_TRUE(CanonPath("foo/bar").isWithin(CanonPath("foo"))); + ASSERT_FALSE(CanonPath("foo").isWithin(CanonPath("foo/bar"))); + ASSERT_TRUE(CanonPath("/foo/bar/default.nix").isWithin(CanonPath("/"))); + ASSERT_TRUE(CanonPath("/").isWithin(CanonPath("/"))); + } + } + + TEST(CanonPath, sort) { + ASSERT_FALSE(CanonPath("foo") < CanonPath("foo")); + ASSERT_TRUE (CanonPath("foo") < CanonPath("foo/bar")); + ASSERT_TRUE (CanonPath("foo/bar") < CanonPath("foo!")); + ASSERT_FALSE(CanonPath("foo!") < CanonPath("foo")); + ASSERT_TRUE (CanonPath("foo") < CanonPath("foo!")); + } + + TEST(CanonPath, allowed) { + { + std::set allowed { + CanonPath("foo/bar"), + CanonPath("foo!"), + CanonPath("xyzzy"), + CanonPath("a/b/c"), + }; + + ASSERT_TRUE (CanonPath("foo/bar").isAllowed(allowed)); + ASSERT_TRUE (CanonPath("foo/bar/bla").isAllowed(allowed)); + ASSERT_TRUE (CanonPath("foo").isAllowed(allowed)); + ASSERT_FALSE(CanonPath("bar").isAllowed(allowed)); + ASSERT_FALSE(CanonPath("bar/a").isAllowed(allowed)); + ASSERT_TRUE (CanonPath("a").isAllowed(allowed)); + ASSERT_TRUE (CanonPath("a/b").isAllowed(allowed)); + ASSERT_TRUE (CanonPath("a/b/c").isAllowed(allowed)); + ASSERT_TRUE (CanonPath("a/b/c/d").isAllowed(allowed)); + ASSERT_TRUE (CanonPath("a/b/c/d/e").isAllowed(allowed)); + ASSERT_FALSE(CanonPath("a/b/a").isAllowed(allowed)); + ASSERT_FALSE(CanonPath("a/b/d").isAllowed(allowed)); + ASSERT_FALSE(CanonPath("aaa").isAllowed(allowed)); + ASSERT_FALSE(CanonPath("zzz").isAllowed(allowed)); + ASSERT_TRUE (CanonPath("/").isAllowed(allowed)); + } + } +} From dc075dcdd0306adec911ec8d898b723f464f7c0a Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 12 Dec 2022 16:26:10 -0500 Subject: [PATCH 200/245] Apply suggestions from code review Co-authored-by: Eelco Dolstra --- src/libstore/derived-path.cc | 2 +- src/nix/nix.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 7fe797aa1..05c2303db 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -93,7 +93,7 @@ DerivedPath::Built DerivedPath::Built::parse(const Store & store, std::string_vi DerivedPath DerivedPath::parse(const Store & store, std::string_view s) { - size_t n = s.rfind("!"); + size_t n = s.find("!"); return n == s.npos ? (DerivedPath) DerivedPath::Opaque::parse(store, s) : (DerivedPath) DerivedPath::Built::parse(store, s.substr(0, n), s.substr(n + 1)); diff --git a/src/nix/nix.md b/src/nix/nix.md index 811936024..6ff27e479 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -187,7 +187,7 @@ operate are determined as follows: and likewise, again using a store path to a "drv" file to specify the derivation: ```console - # nix path-info -S --eval-store auto --store https://cache.nixos.org '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*' + # nix path-info -S '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*' … ``` From c7cce3e4e1dc82c504bb4d717e55dce3b1ae008a Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 12 Dec 2022 16:29:29 -0500 Subject: [PATCH 201/245] Improve release notes --- doc/manual/src/release-notes/rl-next.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index a56dc25a2..15c309bdb 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -3,4 +3,13 @@ * The `repeat` and `enforce-determinism` options have been removed since they had been broken under many circumstances for a long time. -* Allow explicitly selecting outputs with *store derivations* installable syntax too. \ No newline at end of file +* Allow explicitly selecting outputs in a store derivation installable, just like we can do with other sorts of installables. + For example, + ```shell-session + $ nix-build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev` + ``` + now works just as + ```shell-session + $ nix-build glibc^dev` + ``` + does already. From 672ee882318f3ae97de068a44e4c09bdf82d04ef Mon Sep 17 00:00:00 2001 From: Rick van Schijndel Date: Mon, 12 Dec 2022 23:31:30 +0100 Subject: [PATCH 202/245] support building with --enable-gc=no Some minor changes fixing the build without boehm. Fixes NixOS#6250 --- src/libexpr/eval.cc | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 6955aacbf..515987db6 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -45,7 +45,7 @@ static char * allocString(size_t size) #if HAVE_BOEHMGC t = (char *) GC_MALLOC_ATOMIC(size); #else - t = malloc(size); + t = (char *) malloc(size); #endif if (!t) throw std::bad_alloc(); return t; @@ -471,9 +471,6 @@ EvalState::EvalState( #if HAVE_BOEHMGC , valueAllocCache(std::allocate_shared(traceable_allocator(), nullptr)) , env1AllocCache(std::allocate_shared(traceable_allocator(), nullptr)) -#else - , valueAllocCache(std::make_shared(nullptr)) - , env1AllocCache(std::make_shared(nullptr)) #endif , baseEnv(allocEnv(128)) , staticBaseEnv{std::make_shared(false, nullptr)} From d8c1c24c78ebeb1f695e29a489be567118eb073e Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 12 Dec 2022 17:32:24 -0500 Subject: [PATCH 203/245] Adjust docs --- src/nix/nix.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/nix/nix.md b/src/nix/nix.md index 6ff27e479..529d5f796 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -190,11 +190,9 @@ operate are determined as follows: # nix path-info -S '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*' … ``` - -* If you didn't specify the desired outputs, but the derivation comes - from an expression which has an attribute `meta.outputsToInstall`, Nix - will use those outputs. For example, since the package - `nixpkgs#libxml2` has this attribute: +* If you didn't specify the desired outputs, but the derivation hs an + attribute `meta.outputsToInstall`, Nix will use those outputs. For + example, since the package `nixpkgs#libxml2` has this attribute: ```console # nix eval 'nixpkgs#libxml2.meta.outputsToInstall' @@ -204,6 +202,9 @@ operate are determined as follows: a command like `nix shell nixpkgs#libxml2` will provide only those two outputs by default. + Note that a store derivation (given by `.drv` file store path) doesn't have + any attributes like `meta`, and thus this case doesn't apply to it. + * Otherwise, Nix will use all outputs of the derivation. # Nix stores From c886b1856184fc180603435197a10ea20df8bcfb Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 12 Dec 2022 17:34:57 -0500 Subject: [PATCH 204/245] Merge new tests into `build.sh` --- tests/build-explicit-output.sh | 43 ---------------------------------- tests/build.sh | 42 +++++++++++++++++++++++++++++++++ tests/local.mk | 1 - 3 files changed, 42 insertions(+), 44 deletions(-) delete mode 100644 tests/build-explicit-output.sh diff --git a/tests/build-explicit-output.sh b/tests/build-explicit-output.sh deleted file mode 100644 index 45320d6e3..000000000 --- a/tests/build-explicit-output.sh +++ /dev/null @@ -1,43 +0,0 @@ -source common.sh - -set -o pipefail - -drv=$(nix eval -f multiple-outputs.nix --raw a.drvPath) -if nix build "$drv^not-an-output" --no-link --json; then - fail "'not-an-output' should fail to build" -fi - -if nix build "$drv^" --no-link --json; then - fail "'empty outputs list' should fail to build" -fi - -if nix build "$drv^*nope" --no-link --json; then - fail "'* must be entire string' should fail to build" -fi - -nix build "$drv^first" --no-link --json | jq --exit-status ' - (.[0] | - (.drvPath | match(".*multiple-outputs-a.drv")) and - (.outputs | - (keys | length == 1) and - (.first | match(".*multiple-outputs-a-first")) and - (has("second") | not))) -' - -nix build "$drv^first,second" --no-link --json | jq --exit-status ' - (.[0] | - (.drvPath | match(".*multiple-outputs-a.drv")) and - (.outputs | - (keys | length == 2) and - (.first | match(".*multiple-outputs-a-first")) and - (.second | match(".*multiple-outputs-a-second")))) -' - -nix build "$drv^*" --no-link --json | jq --exit-status ' - (.[0] | - (.drvPath | match(".*multiple-outputs-a.drv")) and - (.outputs | - (keys | length == 2) and - (.first | match(".*multiple-outputs-a-first")) and - (.second | match(".*multiple-outputs-a-second")))) -' diff --git a/tests/build.sh b/tests/build.sh index 3a3d773b1..036fb037e 100644 --- a/tests/build.sh +++ b/tests/build.sh @@ -58,6 +58,48 @@ nix build -f multiple-outputs.nix --json 'e^*' --no-link | jq --exit-status ' (.outputs | keys == ["a", "b", "c"])) ' +# Test building from raw store path to drv not expression. + +drv=$(nix eval -f multiple-outputs.nix --raw a.drvPath) +if nix build "$drv^not-an-output" --no-link --json; then + fail "'not-an-output' should fail to build" +fi + +if nix build "$drv^" --no-link --json; then + fail "'empty outputs list' should fail to build" +fi + +if nix build "$drv^*nope" --no-link --json; then + fail "'* must be entire string' should fail to build" +fi + +nix build "$drv^first" --no-link --json | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | + (keys | length == 1) and + (.first | match(".*multiple-outputs-a-first")) and + (has("second") | not))) +' + +nix build "$drv^first,second" --no-link --json | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | + (keys | length == 2) and + (.first | match(".*multiple-outputs-a-first")) and + (.second | match(".*multiple-outputs-a-second")))) +' + +nix build "$drv^*" --no-link --json | jq --exit-status ' + (.[0] | + (.drvPath | match(".*multiple-outputs-a.drv")) and + (.outputs | + (keys | length == 2) and + (.first | match(".*multiple-outputs-a-first")) and + (.second | match(".*multiple-outputs-a-second")))) +' + # Make sure that `--impure` works (regression test for https://github.com/NixOS/nix/issues/6488) nix build --impure -f multiple-outputs.nix --json e --no-link | jq --exit-status ' (.[0] | diff --git a/tests/local.mk b/tests/local.mk index aff595d3b..340817ec3 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -98,7 +98,6 @@ nix_tests = \ ssh-relay.sh \ plugins.sh \ build.sh \ - build-explicit-output.sh \ ca/nix-run.sh \ selfref-gc.sh ca/selfref-gc.sh \ db-migration.sh \ From 32ae715db1771342fc356f1521cdda9ecd453358 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Mon, 12 Dec 2022 17:37:45 -0500 Subject: [PATCH 205/245] Fix typos in the docs Thanks! Co-authored-by: Valentin Gagarin --- src/nix/nix.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nix/nix.md b/src/nix/nix.md index 529d5f796..723d3c87e 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -184,13 +184,13 @@ operate are determined as follows: /nix/store/q6580lr01jpcsqs4r5arlh4ki2c1m9rv-glibc-2.33-123-dev 44200560 ``` - and likewise, again using a store path to a "drv" file to specify the derivation: + and likewise, using a store path to a "drv" file to specify the derivation: ```console # nix path-info -S '/nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^*' … ``` -* If you didn't specify the desired outputs, but the derivation hs an +* If you didn't specify the desired outputs, but the derivation has an attribute `meta.outputsToInstall`, Nix will use those outputs. For example, since the package `nixpkgs#libxml2` has this attribute: From b3fdab28a216683365f7f04bfa9bbc5cd122d753 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 13 Dec 2022 00:48:04 +0100 Subject: [PATCH 206/245] Introduce AbstractPos This makes the position object used in exceptions abstract, with a method getSource() to get the source code of the file in which the error originated. This is needed for lazy trees because source files don't necessarily exist in the filesystem, and we don't want to make libutil depend on the InputAccessor type in libfetcher. --- src/libcmd/repl.cc | 28 +++--- src/libexpr/eval.cc | 31 ++++--- src/libexpr/eval.hh | 10 ++- src/libexpr/flake/flake.cc | 2 +- src/libexpr/nixexpr.cc | 79 +++++++++++++---- src/libexpr/nixexpr.hh | 28 ++++-- src/libexpr/parser.y | 39 ++++---- src/libexpr/primops.cc | 7 +- src/libexpr/tests/libexprtests.hh | 2 +- src/libexpr/tests/primops.cc | 15 +--- src/libexpr/value-to-xml.cc | 3 +- src/libutil/error.cc | 142 ++++++++---------------------- src/libutil/error.hh | 58 ++++-------- src/libutil/logging.cc | 32 +++---- src/nix-env/nix-env.cc | 6 +- tests/function-trace.sh | 28 +++--- 16 files changed, 231 insertions(+), 279 deletions(-) diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index c704fcfb1..254c14d28 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -215,17 +215,15 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi out << dt.hint.str() << "\n"; // prefer direct pos, but if noPos then try the expr. - auto pos = *dt.pos - ? *dt.pos - : positions[dt.expr.getPos() ? dt.expr.getPos() : noPos]; + auto pos = dt.pos + ? dt.pos + : (std::shared_ptr) positions[dt.expr.getPos() ? dt.expr.getPos() : noPos]; if (pos) { - printAtPos(pos, out); - - auto loc = getCodeLines(pos); - if (loc.has_value()) { + out << pos; + if (auto loc = pos->getCodeLines()) { out << "\n"; - printCodeLines(out, "", pos, *loc); + printCodeLines(out, "", *pos, *loc); out << "\n"; } } @@ -589,15 +587,17 @@ bool NixRepl::processLine(std::string line) Value v; evalString(arg, v); - const auto [file, line] = [&] () -> std::pair { + const auto [path, line] = [&] () -> std::pair { if (v.type() == nPath || v.type() == nString) { PathSet context; - auto filename = state->coerceToString(noPos, v, context).toOwned(); - state->symbols.create(filename); - return {filename, 0}; + auto path = state->coerceToPath(noPos, v, context); + return {path, 0}; } else if (v.isLambda()) { auto pos = state->positions[v.lambda.fun->pos]; - return {pos.file, pos.line}; + if (auto path = std::get_if(&pos.origin)) + return {*path, pos.line}; + else + throw Error("'%s' cannot be shown in an editor", pos); } else { // assume it's a derivation return findPackageFilename(*state, v, arg); @@ -605,7 +605,7 @@ bool NixRepl::processLine(std::string line) }(); // Open in EDITOR - auto args = editorFor(file, line); + auto args = editorFor(path, line); auto editor = args.front(); args.pop_front(); diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index 0d9226d3b..b5e0051ac 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -823,7 +823,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & ? std::make_unique( *this, DebugTrace { - .pos = error->info().errPos ? *error->info().errPos : positions[expr.getPos()], + .pos = error->info().errPos ? error->info().errPos : (std::shared_ptr) positions[expr.getPos()], .expr = expr, .env = env, .hint = error->info().msg, @@ -1012,7 +1012,7 @@ void EvalState::throwMissingArgumentError(const PosIdx pos, const char * s, cons void EvalState::addErrorTrace(Error & e, const char * s, const std::string & s2) const { - e.addTrace(std::nullopt, s, s2); + e.addTrace(nullptr, s, s2); } void EvalState::addErrorTrace(Error & e, const PosIdx pos, const char * s, const std::string & s2) const @@ -1024,13 +1024,13 @@ static std::unique_ptr makeDebugTraceStacker( EvalState & state, Expr & expr, Env & env, - std::optional pos, + std::shared_ptr && pos, const char * s, const std::string & s2) { return std::make_unique(state, DebugTrace { - .pos = pos, + .pos = std::move(pos), .expr = expr, .env = env, .hint = hintfmt(s, s2), @@ -1136,9 +1136,9 @@ void EvalState::mkThunk_(Value & v, Expr * expr) void EvalState::mkPos(Value & v, PosIdx p) { auto pos = positions[p]; - if (!pos.file.empty()) { + if (auto path = std::get_if(&pos.origin)) { auto attrs = buildBindings(3); - attrs.alloc(sFile).mkString(pos.file); + attrs.alloc(sFile).mkString(*path); attrs.alloc(sLine).mkInt(pos.line); attrs.alloc(sColumn).mkInt(pos.column); v.mkAttrs(attrs); @@ -1246,7 +1246,7 @@ void EvalState::cacheFile( *this, *e, this->baseEnv, - e->getPos() ? std::optional(ErrPos(positions[e->getPos()])) : std::nullopt, + e->getPos() ? (std::shared_ptr) positions[e->getPos()] : nullptr, "while evaluating the file '%1%':", resolvedPath) : nullptr; @@ -1517,10 +1517,13 @@ void ExprSelect::eval(EvalState & state, Env & env, Value & v) state.forceValue(*vAttrs, (pos2 ? pos2 : this->pos ) ); } catch (Error & e) { - auto pos2r = state.positions[pos2]; - if (pos2 && pos2r.file != state.derivationNixPath) - state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'", - showAttrPath(state, env, attrPath)); + if (pos2) { + auto pos2r = state.positions[pos2]; + auto origin = std::get_if(&pos2r.origin); + if (!(origin && *origin == state.derivationNixPath)) + state.addErrorTrace(e, pos2, "while evaluating the attribute '%1%'", + showAttrPath(state, env, attrPath)); + } throw; } @@ -2497,7 +2500,8 @@ void EvalState::printStats() else obj["name"] = nullptr; if (auto pos = positions[fun->pos]) { - obj["file"] = (std::string_view) pos.file; + if (auto path = std::get_if(&pos.origin)) + obj["file"] = *path; obj["line"] = pos.line; obj["column"] = pos.column; } @@ -2511,7 +2515,8 @@ void EvalState::printStats() for (auto & i : attrSelects) { json obj = json::object(); if (auto pos = positions[i.first]) { - obj["file"] = (const std::string &) pos.file; + if (auto path = std::get_if(&pos.origin)) + obj["file"] = *path; obj["line"] = pos.line; obj["column"] = pos.column; } diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index cf307d820..21666339b 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -78,7 +78,7 @@ struct RegexCache; std::shared_ptr makeRegexCache(); struct DebugTrace { - std::optional pos; + std::shared_ptr pos; const Expr & expr; const Env & env; hintformat hint; @@ -457,8 +457,12 @@ private: friend struct ExprAttrs; friend struct ExprLet; - Expr * parse(char * text, size_t length, FileOrigin origin, const PathView path, - const PathView basePath, std::shared_ptr & staticEnv); + Expr * parse( + char * text, + size_t length, + Pos::Origin origin, + Path basePath, + std::shared_ptr & staticEnv); public: diff --git a/src/libexpr/flake/flake.cc b/src/libexpr/flake/flake.cc index 6344fb253..105d32467 100644 --- a/src/libexpr/flake/flake.cc +++ b/src/libexpr/flake/flake.cc @@ -220,7 +220,7 @@ static Flake getFlake( Value vInfo; state.evalFile(flakeFile, vInfo, true); // FIXME: symlink attack - expectType(state, nAttrs, vInfo, state.positions.add({flakeFile, foFile}, 0, 0)); + expectType(state, nAttrs, vInfo, state.positions.add({flakeFile}, 1, 1)); if (auto description = vInfo.attrs->get(state.sDescription)) { expectType(state, nString, *description->value, description->pos); diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index 2be560d76..bdfb6457a 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -8,6 +8,65 @@ namespace nix { +struct SourcePathAdapter : AbstractPos +{ + Path path; + + SourcePathAdapter(Path path) + : path(std::move(path)) + { + } + + std::optional getSource() const override + { + try { + return readFile(path); + } catch (Error &) { + return std::nullopt; + } + } + + void print(std::ostream & out) const override + { + out << path; + } +}; + +struct StringPosAdapter : AbstractPos +{ + void print(std::ostream & out) const override + { + out << "«string»"; + } +}; + +struct StdinPosAdapter : AbstractPos +{ + void print(std::ostream & out) const override + { + out << "«stdin»"; + } +}; + +Pos::operator std::shared_ptr() const +{ + std::shared_ptr pos; + + if (auto path = std::get_if(&origin)) + pos = std::make_shared(*path); + else if (std::get_if(&origin)) + pos = std::make_shared(); + else if (std::get_if(&origin)) + pos = std::make_shared(); + + if (pos) { + pos->line = line; + pos->column = column; + } + + return pos; +} + /* Displaying abstract syntax trees. */ static void showString(std::ostream & str, std::string_view s) @@ -248,24 +307,10 @@ void ExprPos::show(const SymbolTable & symbols, std::ostream & str) const std::ostream & operator << (std::ostream & str, const Pos & pos) { - if (!pos) + if (auto pos2 = (std::shared_ptr) pos) { + str << *pos2; + } else str << "undefined position"; - else - { - auto f = format(ANSI_BOLD "%1%" ANSI_NORMAL ":%2%:%3%"); - switch (pos.origin) { - case foFile: - f % (const std::string &) pos.file; - break; - case foStdin: - case foString: - f % "(string)"; - break; - default: - throw Error("unhandled Pos origin!"); - } - str << (f % pos.line % pos.column).str(); - } return str; } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 5eb022770..0338a8c37 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -23,15 +23,21 @@ MakeError(MissingArgumentError, EvalError); MakeError(RestrictedPathError, Error); /* Position objects. */ - struct Pos { - std::string file; - FileOrigin origin; uint32_t line; uint32_t column; + struct stdin_tag {}; + struct string_tag {}; + + typedef std::variant Origin; + + Origin origin; + explicit operator bool() const { return line > 0; } + + operator std::shared_ptr() const; }; class PosIdx { @@ -47,7 +53,11 @@ public: explicit operator bool() const { return id > 0; } - bool operator<(const PosIdx other) const { return id < other.id; } + bool operator <(const PosIdx other) const { return id < other.id; } + + bool operator ==(const PosIdx other) const { return id == other.id; } + + bool operator !=(const PosIdx other) const { return id != other.id; } }; class PosTable @@ -61,13 +71,13 @@ public: // current origins.back() can be reused or not. mutable uint32_t idx = std::numeric_limits::max(); - explicit Origin(uint32_t idx): idx(idx), file{}, origin{} {} + // Used for searching in PosTable::[]. + explicit Origin(uint32_t idx): idx(idx), origin{Pos::stdin_tag()} {} public: - const std::string file; - const FileOrigin origin; + const Pos::Origin origin; - Origin(std::string file, FileOrigin origin): file(std::move(file)), origin(origin) {} + Origin(Pos::Origin origin): origin(origin) {} }; struct Offset { @@ -107,7 +117,7 @@ public: [] (const auto & a, const auto & b) { return a.idx < b.idx; }); const auto origin = *std::prev(pastOrigin); const auto offset = offsets[idx]; - return {origin.file, origin.origin, offset.line, offset.column}; + return {offset.line, offset.column, origin.origin}; } }; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index fbf865719..ea4e0bfb0 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -34,11 +34,6 @@ namespace nix { Path basePath; PosTable::Origin origin; std::optional error; - ParseData(EvalState & state, PosTable::Origin origin) - : state(state) - , symbols(state.symbols) - , origin(std::move(origin)) - { }; }; struct ParserFormals { @@ -649,24 +644,20 @@ formal namespace nix { -Expr * EvalState::parse(char * text, size_t length, FileOrigin origin, - const PathView path, const PathView basePath, std::shared_ptr & staticEnv) +Expr * EvalState::parse( + char * text, + size_t length, + Pos::Origin origin, + Path basePath, + std::shared_ptr & staticEnv) { yyscan_t scanner; - std::string file; - switch (origin) { - case foFile: - file = path; - break; - case foStdin: - case foString: - file = text; - break; - default: - assert(false); - } - ParseData data(*this, {file, origin}); - data.basePath = basePath; + ParseData data { + .state = *this, + .symbols = symbols, + .basePath = std::move(basePath), + .origin = {origin}, + }; yylex_init(&scanner); yy_scan_buffer(text, length, scanner); @@ -718,14 +709,14 @@ Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr & staticEnv) { s.append("\0\0", 2); - return parse(s.data(), s.size(), foString, "", basePath, staticEnv); + return parse(s.data(), s.size(), Pos::string_tag(), basePath, staticEnv); } @@ -741,7 +732,7 @@ Expr * EvalState::parseStdin() auto buffer = drainFD(0); // drainFD should have left some extra space for terminators buffer.append("\0\0", 2); - return parse(buffer.data(), buffer.size(), foStdin, "", absPath("."), staticBaseEnv); + return parse(buffer.data(), buffer.size(), Pos::stdin_tag(), absPath("."), staticBaseEnv); } diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 283d2746b..7efe50324 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -368,8 +368,7 @@ void prim_exec(EvalState & state, const PosIdx pos, Value * * args, Value & v) auto output = runProgram(program, true, commandArgs); Expr * parsed; try { - auto base = state.positions[pos]; - parsed = state.parseExprFromString(std::move(output), base.file); + parsed = state.parseExprFromString(std::move(output), "/"); } catch (Error & e) { e.addTrace(state.positions[pos], "While parsing the output from '%1%'", program); throw; @@ -798,7 +797,7 @@ static void prim_addErrorContext(EvalState & state, const PosIdx pos, Value * * v = *args[1]; } catch (Error & e) { PathSet context; - e.addTrace(std::nullopt, state.coerceToString(pos, *args[0], context).toOwned()); + e.addTrace(nullptr, state.coerceToString(pos, *args[0], context).toOwned()); throw; } } @@ -4018,7 +4017,7 @@ void EvalState::createBaseEnv() // the parser needs two NUL bytes as terminators; one of them // is implied by being a C string. "\0"; - eval(parse(code, sizeof(code), foFile, derivationNixPath, "/", staticBaseEnv), *vDerivation); + eval(parse(code, sizeof(code), derivationNixPath, "/", staticBaseEnv), *vDerivation); } diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexprtests.hh index 4f6915882..5bb5e66d3 100644 --- a/src/libexpr/tests/libexprtests.hh +++ b/src/libexpr/tests/libexprtests.hh @@ -123,7 +123,7 @@ namespace nix { MATCHER_P(IsAttrsOfSize, n, fmt("Is a set of size [%1%]", n)) { if (arg.type() != nAttrs) { - *result_listener << "Expexted set got " << arg.type(); + *result_listener << "Expected set got " << arg.type(); return false; } else if (arg.attrs->size() != (size_t)n) { *result_listener << "Expected a set with " << n << " attributes but got " << arg.attrs->size(); diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc index 16cf66d2c..49fbc5e98 100644 --- a/src/libexpr/tests/primops.cc +++ b/src/libexpr/tests/primops.cc @@ -151,20 +151,7 @@ namespace nix { // The `y` attribute is at position const char* expr = "builtins.unsafeGetAttrPos \"y\" { y = \"x\"; }"; auto v = eval(expr); - ASSERT_THAT(v, IsAttrsOfSize(3)); - - auto file = v.attrs->find(createSymbol("file")); - ASSERT_NE(file, nullptr); - // FIXME: The file when running these tests is the input string?!? - ASSERT_THAT(*file->value, IsStringEq(expr)); - - auto line = v.attrs->find(createSymbol("line")); - ASSERT_NE(line, nullptr); - ASSERT_THAT(*line->value, IsIntEq(1)); - - auto column = v.attrs->find(createSymbol("column")); - ASSERT_NE(column, nullptr); - ASSERT_THAT(*column->value, IsIntEq(33)); + ASSERT_THAT(v, IsNull()); } TEST_F(PrimOpTest, hasAttr) { diff --git a/src/libexpr/value-to-xml.cc b/src/libexpr/value-to-xml.cc index 7c3bf9492..3f6222768 100644 --- a/src/libexpr/value-to-xml.cc +++ b/src/libexpr/value-to-xml.cc @@ -24,7 +24,8 @@ static void printValueAsXML(EvalState & state, bool strict, bool location, static void posToXML(EvalState & state, XMLAttrs & xmlAttrs, const Pos & pos) { - xmlAttrs["path"] = pos.file; + if (auto path = std::get_if(&pos.origin)) + xmlAttrs["path"] = *path; xmlAttrs["line"] = (format("%1%") % pos.line).str(); xmlAttrs["column"] = (format("%1%") % pos.column).str(); } diff --git a/src/libutil/error.cc b/src/libutil/error.cc index 3bb3efb0e..1a1aecea5 100644 --- a/src/libutil/error.cc +++ b/src/libutil/error.cc @@ -9,9 +9,9 @@ namespace nix { const std::string nativeSystem = SYSTEM; -void BaseError::addTrace(std::optional e, hintformat hint) +void BaseError::addTrace(std::shared_ptr && e, hintformat hint) { - err.traces.push_front(Trace { .pos = e, .hint = hint }); + err.traces.push_front(Trace { .pos = std::move(e), .hint = hint }); } // c++ std::exception descendants must have a 'const char* what()' function. @@ -30,91 +30,46 @@ const std::string & BaseError::calcWhat() const std::optional ErrorInfo::programName = std::nullopt; -std::ostream & operator<<(std::ostream & os, const hintformat & hf) +std::ostream & operator <<(std::ostream & os, const hintformat & hf) { return os << hf.str(); } -std::string showErrPos(const ErrPos & errPos) +std::ostream & operator <<(std::ostream & str, const AbstractPos & pos) { - if (errPos.line > 0) { - if (errPos.column > 0) { - return fmt("%d:%d", errPos.line, errPos.column); - } else { - return fmt("%d", errPos.line); - } - } - else { - return ""; - } + pos.print(str); + str << ":" << pos.line; + if (pos.column > 0) + str << ":" << pos.column; + return str; } -std::optional getCodeLines(const ErrPos & errPos) +std::optional AbstractPos::getCodeLines() const { - if (errPos.line <= 0) + if (line == 0) return std::nullopt; - if (errPos.origin == foFile) { - LinesOfCode loc; - try { - // FIXME: when running as the daemon, make sure we don't - // open a file to which the client doesn't have access. - AutoCloseFD fd = open(errPos.file.c_str(), O_RDONLY | O_CLOEXEC); - if (!fd) return {}; + if (auto source = getSource()) { - // count the newlines. - int count = 0; - std::string line; - int pl = errPos.line - 1; - do - { - line = readLine(fd.get()); - ++count; - if (count < pl) - ; - else if (count == pl) - loc.prevLineOfCode = line; - else if (count == pl + 1) - loc.errLineOfCode = line; - else if (count == pl + 2) { - loc.nextLineOfCode = line; - break; - } - } while (true); - return loc; - } - catch (EndOfFile & eof) { - if (loc.errLineOfCode.has_value()) - return loc; - else - return std::nullopt; - } - catch (std::exception & e) { - return std::nullopt; - } - } else { - std::istringstream iss(errPos.file); + std::istringstream iss(*source); // count the newlines. int count = 0; - std::string line; - int pl = errPos.line - 1; + std::string curLine; + int pl = line - 1; LinesOfCode loc; - do - { - std::getline(iss, line); + do { + std::getline(iss, curLine); ++count; if (count < pl) - { ; - } else if (count == pl) { - loc.prevLineOfCode = line; + loc.prevLineOfCode = curLine; } else if (count == pl + 1) { - loc.errLineOfCode = line; + loc.errLineOfCode = curLine; } else if (count == pl + 2) { - loc.nextLineOfCode = line; + loc.nextLineOfCode = curLine; break; } @@ -124,12 +79,14 @@ std::optional getCodeLines(const ErrPos & errPos) return loc; } + + return std::nullopt; } // print lines of code to the ostream, indicating the error column. void printCodeLines(std::ostream & out, const std::string & prefix, - const ErrPos & errPos, + const AbstractPos & errPos, const LinesOfCode & loc) { // previous line of code. @@ -176,28 +133,6 @@ void printCodeLines(std::ostream & out, } } -void printAtPos(const ErrPos & pos, std::ostream & out) -{ - if (pos) { - switch (pos.origin) { - case foFile: { - out << fmt(ANSI_BLUE "at " ANSI_WARNING "%s:%s" ANSI_NORMAL ":", pos.file, showErrPos(pos)); - break; - } - case foString: { - out << fmt(ANSI_BLUE "at " ANSI_WARNING "«string»:%s" ANSI_NORMAL ":", showErrPos(pos)); - break; - } - case foStdin: { - out << fmt(ANSI_BLUE "at " ANSI_WARNING "«stdin»:%s" ANSI_NORMAL ":", showErrPos(pos)); - break; - } - default: - throw Error("invalid FileOrigin in errPos"); - } - } -} - static std::string indent(std::string_view indentFirst, std::string_view indentRest, std::string_view s) { std::string res; @@ -263,22 +198,22 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s std::ostringstream oss; + auto noSource = ANSI_ITALIC " (source not available)" ANSI_NORMAL "\n"; + // traces if (showTrace && !einfo.traces.empty()) { for (const auto & trace : einfo.traces) { oss << "\n" << "… " << trace.hint.str() << "\n"; - if (trace.pos.has_value() && (*trace.pos)) { - auto pos = trace.pos.value(); - oss << "\n"; - printAtPos(pos, oss); + if (trace.pos) { + oss << "\n" << ANSI_BLUE << "at " ANSI_WARNING << *trace.pos << ANSI_NORMAL << ":"; - auto loc = getCodeLines(pos); - if (loc.has_value()) { + if (auto loc = trace.pos->getCodeLines()) { oss << "\n"; - printCodeLines(oss, "", pos, *loc); + printCodeLines(oss, "", *trace.pos, *loc); oss << "\n"; - } + } else + oss << noSource; } } oss << "\n" << prefix; @@ -286,22 +221,19 @@ std::ostream & showErrorInfo(std::ostream & out, const ErrorInfo & einfo, bool s oss << einfo.msg << "\n"; - if (einfo.errPos.has_value() && *einfo.errPos) { - oss << "\n"; - printAtPos(*einfo.errPos, oss); + if (einfo.errPos) { + oss << "\n" << ANSI_BLUE << "at " ANSI_WARNING << *einfo.errPos << ANSI_NORMAL << ":"; - auto loc = getCodeLines(*einfo.errPos); - - // lines of code. - if (loc.has_value()) { + if (auto loc = einfo.errPos->getCodeLines()) { oss << "\n"; printCodeLines(oss, "", *einfo.errPos, *loc); oss << "\n"; - } + } else + oss << noSource; } auto suggestions = einfo.suggestions.trim(); - if (! suggestions.suggestions.empty()){ + if (!suggestions.suggestions.empty()) { oss << "Did you mean " << suggestions.trim() << "?" << std::endl; diff --git a/src/libutil/error.hh b/src/libutil/error.hh index 3d1479c54..c3bb8c0df 100644 --- a/src/libutil/error.hh +++ b/src/libutil/error.hh @@ -54,13 +54,6 @@ typedef enum { lvlVomit } Verbosity; -/* adjust Pos::origin bit width when adding stuff here */ -typedef enum { - foFile, - foStdin, - foString -} FileOrigin; - // the lines of code surrounding an error. struct LinesOfCode { std::optional prevLineOfCode; @@ -68,54 +61,37 @@ struct LinesOfCode { std::optional nextLineOfCode; }; -// ErrPos indicates the location of an error in a nix file. -struct ErrPos { - int line = 0; - int column = 0; - std::string file; - FileOrigin origin; +/* An abstract type that represents a location in a source file. */ +struct AbstractPos +{ + uint32_t line = 0; + uint32_t column = 0; - operator bool() const - { - return line != 0; - } + /* Return the contents of the source file. */ + virtual std::optional getSource() const + { return std::nullopt; }; - // convert from the Pos struct, found in libexpr. - template - ErrPos & operator=(const P & pos) - { - origin = pos.origin; - line = pos.line; - column = pos.column; - file = pos.file; - return *this; - } + virtual void print(std::ostream & out) const = 0; - template - ErrPos(const P & p) - { - *this = p; - } + std::optional getCodeLines() const; }; -std::optional getCodeLines(const ErrPos & errPos); +std::ostream & operator << (std::ostream & str, const AbstractPos & pos); void printCodeLines(std::ostream & out, const std::string & prefix, - const ErrPos & errPos, + const AbstractPos & errPos, const LinesOfCode & loc); -void printAtPos(const ErrPos & pos, std::ostream & out); - struct Trace { - std::optional pos; + std::shared_ptr pos; hintformat hint; }; struct ErrorInfo { Verbosity level; hintformat msg; - std::optional errPos; + std::shared_ptr errPos; std::list traces; Suggestions suggestions; @@ -177,12 +153,12 @@ public: const ErrorInfo & info() const { calcWhat(); return err; } template - void addTrace(std::optional e, const std::string & fs, const Args & ... args) + void addTrace(std::shared_ptr && e, const std::string & fs, const Args & ... args) { - addTrace(e, hintfmt(fs, args...)); + addTrace(std::move(e), hintfmt(fs, args...)); } - void addTrace(std::optional e, hintformat hint); + void addTrace(std::shared_ptr && e, hintformat hint); bool hasTrace() const { return !err.traces.empty(); } }; diff --git a/src/libutil/logging.cc b/src/libutil/logging.cc index ac86d8ac2..904ba6ebe 100644 --- a/src/libutil/logging.cc +++ b/src/libutil/logging.cc @@ -131,6 +131,21 @@ Activity::Activity(Logger & logger, Verbosity lvl, ActivityType type, logger.startActivity(id, lvl, type, s, fields, parent); } +void to_json(nlohmann::json & json, std::shared_ptr pos) +{ + if (pos) { + json["line"] = pos->line; + json["column"] = pos->column; + std::ostringstream str; + pos->print(str); + json["file"] = str.str(); + } else { + json["line"] = nullptr; + json["column"] = nullptr; + json["file"] = nullptr; + } +} + struct JSONLogger : Logger { Logger & prevLogger; @@ -177,27 +192,14 @@ struct JSONLogger : Logger { json["level"] = ei.level; json["msg"] = oss.str(); json["raw_msg"] = ei.msg.str(); - - if (ei.errPos.has_value() && (*ei.errPos)) { - json["line"] = ei.errPos->line; - json["column"] = ei.errPos->column; - json["file"] = ei.errPos->file; - } else { - json["line"] = nullptr; - json["column"] = nullptr; - json["file"] = nullptr; - } + to_json(json, ei.errPos); if (loggerSettings.showTrace.get() && !ei.traces.empty()) { nlohmann::json traces = nlohmann::json::array(); for (auto iter = ei.traces.rbegin(); iter != ei.traces.rend(); ++iter) { nlohmann::json stackFrame; stackFrame["raw_msg"] = iter->hint.str(); - if (iter->pos.has_value() && (*iter->pos)) { - stackFrame["line"] = iter->pos->line; - stackFrame["column"] = iter->pos->column; - stackFrame["file"] = iter->pos->file; - } + to_json(stackFrame, iter->pos); traces.push_back(stackFrame); } diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 776c5f6db..31823a966 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -647,7 +647,7 @@ static void upgradeDerivations(Globals & globals, } else newElems.push_back(i); } catch (Error & e) { - e.addTrace(std::nullopt, "while trying to find an upgrade for '%s'", i.queryName()); + e.addTrace(nullptr, "while trying to find an upgrade for '%s'", i.queryName()); throw; } } @@ -958,7 +958,7 @@ static void queryJSON(Globals & globals, std::vector & elems, bool prin } catch (AssertionError & e) { printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName()); } catch (Error & e) { - e.addTrace(std::nullopt, "while querying the derivation named '%1%'", i.queryName()); + e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName()); throw; } } @@ -1262,7 +1262,7 @@ static void opQuery(Globals & globals, Strings opFlags, Strings opArgs) } catch (AssertionError & e) { printMsg(lvlTalkative, "skipping derivation named '%1%' which gives an assertion failure", i.queryName()); } catch (Error & e) { - e.addTrace(std::nullopt, "while querying the derivation named '%1%'", i.queryName()); + e.addTrace(nullptr, "while querying the derivation named '%1%'", i.queryName()); throw; } } diff --git a/tests/function-trace.sh b/tests/function-trace.sh index d68e10df5..b0d6c9d59 100755 --- a/tests/function-trace.sh +++ b/tests/function-trace.sh @@ -32,40 +32,40 @@ expect_trace() { # failure inside a tryEval expect_trace 'builtins.tryEval (throw "example")' " -function-trace entered (string):1:1 at -function-trace entered (string):1:19 at -function-trace exited (string):1:19 at -function-trace exited (string):1:1 at +function-trace entered «string»:1:1 at +function-trace entered «string»:1:19 at +function-trace exited «string»:1:19 at +function-trace exited «string»:1:1 at " # Missing argument to a formal function expect_trace '({ x }: x) { }' " -function-trace entered (string):1:1 at -function-trace exited (string):1:1 at +function-trace entered «string»:1:1 at +function-trace exited «string»:1:1 at " # Too many arguments to a formal function expect_trace '({ x }: x) { x = "x"; y = "y"; }' " -function-trace entered (string):1:1 at -function-trace exited (string):1:1 at +function-trace entered «string»:1:1 at +function-trace exited «string»:1:1 at " # Not enough arguments to a lambda expect_trace '(x: y: x + y) 1' " -function-trace entered (string):1:1 at -function-trace exited (string):1:1 at +function-trace entered «string»:1:1 at +function-trace exited «string»:1:1 at " # Too many arguments to a lambda expect_trace '(x: x) 1 2' " -function-trace entered (string):1:1 at -function-trace exited (string):1:1 at +function-trace entered «string»:1:1 at +function-trace exited «string»:1:1 at " # Not a function expect_trace '1 2' " -function-trace entered (string):1:1 at -function-trace exited (string):1:1 at +function-trace entered «string»:1:1 at +function-trace exited «string»:1:1 at " set -e From 2ec6685eb038b295d488ded6e46cc56378e7d646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9ophane=20Hufschmitt?= Date: Tue, 13 Dec 2022 10:44:07 +0100 Subject: [PATCH 207/245] Build Nix with the GC disabled in hydra Make sure that it still compiles as it's easy to accidentally break one of the `#if` guarded clauses --- flake.nix | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flake.nix b/flake.nix index 5127ee2a3..4ba3f04b0 100644 --- a/flake.nix +++ b/flake.nix @@ -420,6 +420,8 @@ buildCross = nixpkgs.lib.genAttrs crossSystems (crossSystem: nixpkgs.lib.genAttrs ["x86_64-linux"] (system: self.packages.${system}."nix-${crossSystem}")); + buildNoGc = nixpkgs.lib.genAttrs systems (system: self.packages.${system}.nix.overrideAttrs (a: { configureFlags = (a.configureFlags or []) ++ ["--enable-gc=no"];})); + # Perl bindings for various platforms. perlBindings = nixpkgs.lib.genAttrs systems (system: self.packages.${system}.nix.perl-bindings); From ae27181f16c3b4be011e6ef23d95ff9ccdaae76b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sol=C3=A8ne=20Rapenne?= Date: Tue, 13 Dec 2022 11:44:56 +0100 Subject: [PATCH 208/245] documentation: fix link to definition --- src/libcmd/common-eval-args.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index 67fc501c9..9efd0158d 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -77,7 +77,7 @@ MixEvalArgs::MixEvalArgs() branch in the `nixpkgs` repository. The URLs of the tarballs from the official `nixos.org` channels - (see [the manual page for `nix-channel`](nix-channel.md)) can be + (see [the manual page for `nix-channel`](../nix-channel.md)) can be abbreviated as `channel:`. For instance, the following two flags are equivalent: From e43b0f5b129109ae3785431d470728cf69e1c621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sol=C3=A8ne=20Rapenne?= Date: Tue, 13 Dec 2022 11:46:03 +0100 Subject: [PATCH 209/245] documentation: link flake URL term to definition --- src/libcmd/common-eval-args.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcmd/common-eval-args.cc b/src/libcmd/common-eval-args.cc index 9efd0158d..0e321e5e4 100644 --- a/src/libcmd/common-eval-args.cc +++ b/src/libcmd/common-eval-args.cc @@ -86,7 +86,7 @@ MixEvalArgs::MixEvalArgs() -I nixpkgs=https://nixos.org/channels/nixos-21.05/nixexprs.tar.xz ``` - You can also fetch source trees using flake URLs and add them to the + You can also fetch source trees using [flake URLs](./nix3-flake.md#url-like-syntax) and add them to the search path. For instance, ``` From 09860c16ce526a079d3bd5e8d8bb6f26dc259b93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sol=C3=A8ne=20Rapenne?= Date: Tue, 13 Dec 2022 11:46:33 +0100 Subject: [PATCH 210/245] documentation: use sections instead of list items --- src/nix/flake.md | 73 ++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 34 deletions(-) diff --git a/src/nix/flake.md b/src/nix/flake.md index a1ab43281..810e9ebea 100644 --- a/src/nix/flake.md +++ b/src/nix/flake.md @@ -18,51 +18,56 @@ values such as packages or NixOS modules provided by the flake). Flake references (*flakerefs*) are a way to specify the location of a flake. These have two different forms: -* An attribute set representation, e.g. - ```nix - { - type = "github"; - owner = "NixOS"; - repo = "nixpkgs"; - } - ``` +## Attribute set representation - The only required attribute is `type`. The supported types are - listed below. +Example: -* A URL-like syntax, e.g. +```nix +{ + type = "github"; + owner = "NixOS"; + repo = "nixpkgs"; +} +``` - ``` - github:NixOS/nixpkgs - ``` +The only required attribute is `type`. The supported types are +listed below. - These are used on the command line as a more convenient alternative - to the attribute set representation. For instance, in the command +## URL-like syntax - ```console - # nix build github:NixOS/nixpkgs#hello - ``` +Example: - `github:NixOS/nixpkgs` is a flake reference (while `hello` is an - output attribute). They are also allowed in the `inputs` attribute - of a flake, e.g. +``` +github:NixOS/nixpkgs +``` - ```nix - inputs.nixpkgs.url = github:NixOS/nixpkgs; - ``` +These are used on the command line as a more convenient alternative +to the attribute set representation. For instance, in the command - is equivalent to +```console +# nix build github:NixOS/nixpkgs#hello +``` - ```nix - inputs.nixpkgs = { - type = "github"; - owner = "NixOS"; - repo = "nixpkgs"; - }; - ``` +`github:NixOS/nixpkgs` is a flake reference (while `hello` is an +output attribute). They are also allowed in the `inputs` attribute +of a flake, e.g. -## Examples +```nix +inputs.nixpkgs.url = github:NixOS/nixpkgs; +``` + +is equivalent to + +```nix +inputs.nixpkgs = { + type = "github"; + owner = "NixOS"; + repo = "nixpkgs"; +}; +``` + +### Examples Here are some examples of flake references in their URL-like representation: From 1315133b50fd836f5b8bafc8bf63c85aef475a04 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 13 Dec 2022 12:38:33 +0100 Subject: [PATCH 211/245] Improve cast safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Théophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com> --- src/libcmd/repl.cc | 2 +- src/libexpr/eval.cc | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index 254c14d28..5400fcd69 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -217,7 +217,7 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi // prefer direct pos, but if noPos then try the expr. auto pos = dt.pos ? dt.pos - : (std::shared_ptr) positions[dt.expr.getPos() ? dt.expr.getPos() : noPos]; + : static_cast>(positions[dt.expr.getPos() ? dt.expr.getPos() : noPos]); if (pos) { out << pos; diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index b5e0051ac..adf928949 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -823,7 +823,7 @@ void EvalState::runDebugRepl(const Error * error, const Env & env, const Expr & ? std::make_unique( *this, DebugTrace { - .pos = error->info().errPos ? error->info().errPos : (std::shared_ptr) positions[expr.getPos()], + .pos = error->info().errPos ? error->info().errPos : static_cast>(positions[expr.getPos()]), .expr = expr, .env = env, .hint = error->info().msg, @@ -1246,7 +1246,7 @@ void EvalState::cacheFile( *this, *e, this->baseEnv, - e->getPos() ? (std::shared_ptr) positions[e->getPos()] : nullptr, + e->getPos() ? static_cast>(positions[e->getPos()] : nullptr), "while evaluating the file '%1%':", resolvedPath) : nullptr; From aea97f07a388915e5a7179f56ab4328fef155f05 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 13 Dec 2022 15:23:12 +0100 Subject: [PATCH 212/245] Fix compilation --- src/libexpr/eval.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index adf928949..de84dd7a5 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -1246,7 +1246,7 @@ void EvalState::cacheFile( *this, *e, this->baseEnv, - e->getPos() ? static_cast>(positions[e->getPos()] : nullptr), + e->getPos() ? static_cast>(positions[e->getPos()]) : nullptr, "while evaluating the file '%1%':", resolvedPath) : nullptr; From 129ece7ce9b88fb79d3f68d030df4e06c4e291cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Tue, 13 Dec 2022 15:49:40 +0100 Subject: [PATCH 213/245] doc: fix links --- doc/manual/src/release-notes/rl-2.12.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/manual/src/release-notes/rl-2.12.md b/doc/manual/src/release-notes/rl-2.12.md index 82de22cb4..e2045d7bf 100644 --- a/doc/manual/src/release-notes/rl-2.12.md +++ b/doc/manual/src/release-notes/rl-2.12.md @@ -17,12 +17,12 @@ The `uid-range` [system feature] requires the [`auto-allocate-uids`] setting to be enabled. - [system feature]: (../command-ref/conf-file.md#conf-system-features) + [system feature]: ../command-ref/conf-file.md#conf-system-features * Nix can now automatically pick UIDs for builds, removing the need to create `nixbld*` user accounts. See [`auto-allocate-uids`]. - [`auto-allocate-uids`]: (../command-ref/conf-file.md#conf-auto-allocate-uids) + [`auto-allocate-uids`]: ../command-ref/conf-file.md#conf-auto-allocate-uids * On Linux, Nix has experimental support for running builds inside a cgroup. See From c9b0a85b088b472eda9818dfaa0cc1a54124933c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 13 Dec 2022 16:00:44 +0100 Subject: [PATCH 214/245] Restore display of source lines for stdin/string inputs --- src/libexpr/nixexpr.cc | 73 +++++++++++++++++++----------------------- src/libexpr/nixexpr.hh | 9 +++--- src/libexpr/parser.y | 10 +++--- 3 files changed, 44 insertions(+), 48 deletions(-) diff --git a/src/libexpr/nixexpr.cc b/src/libexpr/nixexpr.cc index bdfb6457a..eb6f062b4 100644 --- a/src/libexpr/nixexpr.cc +++ b/src/libexpr/nixexpr.cc @@ -8,62 +8,55 @@ namespace nix { -struct SourcePathAdapter : AbstractPos +struct PosAdapter : AbstractPos { - Path path; + Pos::Origin origin; - SourcePathAdapter(Path path) - : path(std::move(path)) + PosAdapter(Pos::Origin origin) + : origin(std::move(origin)) { } std::optional getSource() const override { - try { - return readFile(path); - } catch (Error &) { - return std::nullopt; - } + return std::visit(overloaded { + [](const Pos::none_tag &) -> std::optional { + return std::nullopt; + }, + [](const Pos::Stdin & s) -> std::optional { + // Get rid of the null terminators added by the parser. + return std::string(s.source->c_str()); + }, + [](const Pos::String & s) -> std::optional { + // Get rid of the null terminators added by the parser. + return std::string(s.source->c_str()); + }, + [](const Path & path) -> std::optional { + try { + return readFile(path); + } catch (Error &) { + return std::nullopt; + } + } + }, origin); } void print(std::ostream & out) const override { - out << path; - } -}; - -struct StringPosAdapter : AbstractPos -{ - void print(std::ostream & out) const override - { - out << "«string»"; - } -}; - -struct StdinPosAdapter : AbstractPos -{ - void print(std::ostream & out) const override - { - out << "«stdin»"; + std::visit(overloaded { + [&](const Pos::none_tag &) { out << "«none»"; }, + [&](const Pos::Stdin &) { out << "«stdin»"; }, + [&](const Pos::String & s) { out << "«string»"; }, + [&](const Path & path) { out << path; } + }, origin); } }; Pos::operator std::shared_ptr() const { - std::shared_ptr pos; - - if (auto path = std::get_if(&origin)) - pos = std::make_shared(*path); - else if (std::get_if(&origin)) - pos = std::make_shared(); - else if (std::get_if(&origin)) - pos = std::make_shared(); - - if (pos) { - pos->line = line; - pos->column = column; - } - + auto pos = std::make_shared(origin); + pos->line = line; + pos->column = column; return pos; } diff --git a/src/libexpr/nixexpr.hh b/src/libexpr/nixexpr.hh index 0338a8c37..ac7ce021e 100644 --- a/src/libexpr/nixexpr.hh +++ b/src/libexpr/nixexpr.hh @@ -28,10 +28,11 @@ struct Pos uint32_t line; uint32_t column; - struct stdin_tag {}; - struct string_tag {}; + struct none_tag { }; + struct Stdin { ref source; }; + struct String { ref source; }; - typedef std::variant Origin; + typedef std::variant Origin; Origin origin; @@ -72,7 +73,7 @@ public: mutable uint32_t idx = std::numeric_limits::max(); // Used for searching in PosTable::[]. - explicit Origin(uint32_t idx): idx(idx), origin{Pos::stdin_tag()} {} + explicit Origin(uint32_t idx): idx(idx), origin{Pos::none_tag()} {} public: const Pos::Origin origin; diff --git a/src/libexpr/parser.y b/src/libexpr/parser.y index ea4e0bfb0..e07909f8e 100644 --- a/src/libexpr/parser.y +++ b/src/libexpr/parser.y @@ -713,10 +713,11 @@ Expr * EvalState::parseExprFromFile(const Path & path, std::shared_ptr & staticEnv) +Expr * EvalState::parseExprFromString(std::string s_, const Path & basePath, std::shared_ptr & staticEnv) { - s.append("\0\0", 2); - return parse(s.data(), s.size(), Pos::string_tag(), basePath, staticEnv); + auto s = make_ref(std::move(s_)); + s->append("\0\0", 2); + return parse(s->data(), s->size(), Pos::String{.source = s}, basePath, staticEnv); } @@ -732,7 +733,8 @@ Expr * EvalState::parseStdin() auto buffer = drainFD(0); // drainFD should have left some extra space for terminators buffer.append("\0\0", 2); - return parse(buffer.data(), buffer.size(), Pos::stdin_tag(), absPath("."), staticBaseEnv); + auto s = make_ref(std::move(buffer)); + return parse(s->data(), s->size(), Pos::Stdin{.source = s}, absPath("."), staticBaseEnv); } From 1f3c0a3c1dde7e0348c085e0fbb60729cb067d86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Wed, 14 Dec 2022 00:40:30 +0100 Subject: [PATCH 215/245] Allow disabling build users by unsetting `build-users-group` Unsetting `build-users-group` (without `auto-allocate-uids` enabled) gives the following error: ``` src/libstore/lock.cc:25: static std::unique_ptr nix::SimpleUserLock::acquire(): Assertion `settings.buildUsersGroup != ""' failed. ``` Fix the logic in `useBuildUsers` and document the default value for `build-users-group`. --- src/libstore/globals.hh | 5 ++++- src/libstore/lock.cc | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 54a5d0fc7..274a15dd7 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -281,7 +281,10 @@ public: `NIX_REMOTE` is empty, the uid under which the Nix daemon runs if `NIX_REMOTE` is `daemon`). Obviously, this should not be used in multi-user settings with untrusted users. - )"}; + + Defaults to `nixbld` when running as root, *empty* otherwise. + )", + {}, false}; Setting autoAllocateUids{this, false, "auto-allocate-uids", R"( diff --git a/src/libstore/lock.cc b/src/libstore/lock.cc index 2858137d6..d02d20b4c 100644 --- a/src/libstore/lock.cc +++ b/src/libstore/lock.cc @@ -185,7 +185,7 @@ std::unique_ptr acquireUserLock(uid_t nrIds, bool useChroot) bool useBuildUsers() { #if __linux__ - static bool b = (settings.buildUsersGroup != "" || settings.startId.get() != 0) && getuid() == 0; + static bool b = (settings.buildUsersGroup != "" || settings.autoAllocateUids) && getuid() == 0; return b; #elif __APPLE__ static bool b = settings.buildUsersGroup != "" && getuid() == 0; From 09830ab829f57c20189afe0ee9e7721c274d98ee Mon Sep 17 00:00:00 2001 From: TIAN Yuanhao Date: Wed, 14 Dec 2022 03:03:12 +0000 Subject: [PATCH 216/245] Avoid poly_user_note_set twice f06f810 incorrectly introduces a boolean flip, resulting in a senseless poly_user_note_set even though the user comment has been set correctly. --- scripts/install-multi-user.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/install-multi-user.sh b/scripts/install-multi-user.sh index ec82e0560..f149ea0d7 100644 --- a/scripts/install-multi-user.sh +++ b/scripts/install-multi-user.sh @@ -575,7 +575,7 @@ EOF # to extract _just_ the user's note, instead it is prefixed with # some plist junk. This was causing the user note to always be set, # even if there was no reason for it. - if ! poly_user_note_get "$username" | grep -q "Nix build user $coreid"; then + if poly_user_note_get "$username" | grep -q "Nix build user $coreid"; then row " Note" "Nix build user $coreid" else poly_user_note_set "$username" "Nix build user $coreid" From 98e01da0b122d09ab6ceca3e163f519dce450e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sol=C3=A8ne=20Rapenne?= Date: Wed, 14 Dec 2022 14:11:21 +0100 Subject: [PATCH 217/245] warnings: switch to info level when using a saved substituter --- src/libexpr/flake/config.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc index 6df95f1f0..94d0dc26c 100644 --- a/src/libexpr/flake/config.cc +++ b/src/libexpr/flake/config.cc @@ -56,7 +56,7 @@ void ConfigFile::apply() auto tlname = get(trustedList, name); if (auto saved = tlname ? get(*tlname, valueS) : nullptr) { trusted = *saved; - warn("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS); + printMsg(lvlInfo, "Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS); } else { // FIXME: filter ANSI escapes, newlines, \r, etc. if (std::tolower(logger->ask(fmt("do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "' (y/N)?", name, valueS)).value_or('n')) == 'y') { From 0687e16c4afd131540181cc66136418ac1cb845c Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Thu, 15 Dec 2022 16:00:46 +0100 Subject: [PATCH 218/245] Fix a crash in DerivedPath::Built::toJSON() with impure derivations The use of 'nullptr' here didn't result in a null JSON value, but in a nullptr being cast to a string, which aborts. --- src/libstore/derived-path.cc | 9 +++++---- tests/impure-derivations.sh | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/libstore/derived-path.cc b/src/libstore/derived-path.cc index 05c2303db..3fa5ae4f7 100644 --- a/src/libstore/derived-path.cc +++ b/src/libstore/derived-path.cc @@ -20,11 +20,12 @@ nlohmann::json DerivedPath::Built::toJSON(ref store) const { // Fallback for the input-addressed derivation case: We expect to always be // able to print the output paths, so let’s do it const auto knownOutputs = store->queryPartialDerivationOutputMap(drvPath); - for (const auto& output : outputs) { + for (const auto & output : outputs) { auto knownOutput = get(knownOutputs, output); - res["outputs"][output] = (knownOutput && *knownOutput) - ? store->printStorePath(**knownOutput) - : nullptr; + if (knownOutput && *knownOutput) + res["outputs"][output] = store->printStorePath(**knownOutput); + else + res["outputs"][output] = nullptr; } return res; } diff --git a/tests/impure-derivations.sh b/tests/impure-derivations.sh index 7ca9ce742..23a193833 100644 --- a/tests/impure-derivations.sh +++ b/tests/impure-derivations.sh @@ -12,6 +12,7 @@ clearStore # Basic test of impure derivations: building one a second time should not use the previous result. printf 0 > $TEST_ROOT/counter +nix build --dry-run --json --file ./impure-derivations.nix impure.all json=$(nix build -L --no-link --json --file ./impure-derivations.nix impure.all) path1=$(echo $json | jq -r .[].outputs.out) path1_stuff=$(echo $json | jq -r .[].outputs.stuff) From d0660c6c0b8fc8362220a1b65a6475fde7bf628f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sol=C3=A8ne=20Rapenne?= Date: Fri, 16 Dec 2022 09:34:22 +0100 Subject: [PATCH 219/245] printMsg replacement by printInfo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Théophane Hufschmitt <7226587+thufschmitt@users.noreply.github.com> --- src/libexpr/flake/config.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/flake/config.cc b/src/libexpr/flake/config.cc index 94d0dc26c..89ddbde7e 100644 --- a/src/libexpr/flake/config.cc +++ b/src/libexpr/flake/config.cc @@ -56,7 +56,7 @@ void ConfigFile::apply() auto tlname = get(trustedList, name); if (auto saved = tlname ? get(*tlname, valueS) : nullptr) { trusted = *saved; - printMsg(lvlInfo, "Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name,valueS); + printInfo("Using saved setting for '%s = %s' from ~/.local/share/nix/trusted-settings.json.", name, valueS); } else { // FIXME: filter ANSI escapes, newlines, \r, etc. if (std::tolower(logger->ask(fmt("do you want to allow configuration setting '%s' to be set to '" ANSI_RED "%s" ANSI_NORMAL "' (y/N)?", name, valueS)).value_or('n')) == 'y') { From c965f35de71cc9d88f912f6b90fd7213601e6eb8 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sat, 17 Dec 2022 14:50:43 +0100 Subject: [PATCH 220/245] Improve sqlite error messages They did not include the detailed error message, losing essential information for troubleshooting. Example message: warning: creating statement 'insert or rplace into NARs(cache, hashPart, namePart, url, compression, fileHash, fileSize, narHash, narSize, refs, deriver, sigs, ca, timestamp, present) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1)': at offset 10: SQL logic error, near "rplace": syntax error (in '/tmp/nix-shell.grQ6f7/nix-test/tests/binary-cache/test-home/.cache/nix/binary-cache-v6.sqlite') It's not the best example; more important information will be in the message for e.g. a constraint violation. I don't see why this specific error is printed as a warning, but that's for another commit. --- src/libstore/sqlite.cc | 15 ++++++++++----- src/libstore/sqlite.hh | 11 ++++++----- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/libstore/sqlite.cc b/src/libstore/sqlite.cc index 2090beabd..6c350888f 100644 --- a/src/libstore/sqlite.cc +++ b/src/libstore/sqlite.cc @@ -8,12 +8,15 @@ namespace nix { -SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf) - : Error(""), path(path), errNo(errNo), extendedErrNo(extendedErrNo) +SQLiteError::SQLiteError(const char *path, const char *errMsg, int errNo, int extendedErrNo, int offset, hintformat && hf) + : Error(""), path(path), errMsg(errMsg), errNo(errNo), extendedErrNo(extendedErrNo), offset(offset) { - err.msg = hintfmt("%s: %s (in '%s')", + auto offsetStr = (offset == -1) ? "" : "at offset " + std::to_string(offset) + ": "; + err.msg = hintfmt("%s: %s%s, %s (in '%s')", normaltxt(hf.str()), + offsetStr, sqlite3_errstr(extendedErrNo), + errMsg, path ? path : "(in-memory)"); } @@ -21,11 +24,13 @@ SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, hintfor { int err = sqlite3_errcode(db); int exterr = sqlite3_extended_errcode(db); + int offset = sqlite3_error_offset(db); auto path = sqlite3_db_filename(db, nullptr); + auto errMsg = sqlite3_errmsg(db); if (err == SQLITE_BUSY || err == SQLITE_PROTOCOL) { - auto exp = SQLiteBusy(path, err, exterr, std::move(hf)); + auto exp = SQLiteBusy(path, errMsg, err, exterr, offset, std::move(hf)); exp.err.msg = hintfmt( err == SQLITE_PROTOCOL ? "SQLite database '%s' is busy (SQLITE_PROTOCOL)" @@ -33,7 +38,7 @@ SQLiteError::SQLiteError(const char *path, int errNo, int extendedErrNo, hintfor path ? path : "(in-memory)"); throw exp; } else - throw SQLiteError(path, err, exterr, std::move(hf)); + throw SQLiteError(path, errMsg, err, exterr, offset, std::move(hf)); } SQLite::SQLite(const Path & path, bool create) diff --git a/src/libstore/sqlite.hh b/src/libstore/sqlite.hh index 1d1c553ea..1853731a2 100644 --- a/src/libstore/sqlite.hh +++ b/src/libstore/sqlite.hh @@ -98,21 +98,22 @@ struct SQLiteTxn struct SQLiteError : Error { - const char *path; - int errNo, extendedErrNo; + std::string path; + std::string errMsg; + int errNo, extendedErrNo, offset; template [[noreturn]] static void throw_(sqlite3 * db, const std::string & fs, const Args & ... args) { throw_(db, hintfmt(fs, args...)); } - SQLiteError(const char *path, int errNo, int extendedErrNo, hintformat && hf); + SQLiteError(const char *path, const char *errMsg, int errNo, int extendedErrNo, int offset, hintformat && hf); protected: template - SQLiteError(const char *path, int errNo, int extendedErrNo, const std::string & fs, const Args & ... args) - : SQLiteError(path, errNo, extendedErrNo, hintfmt(fs, args...)) + SQLiteError(const char *path, const char *errMsg, int errNo, int extendedErrNo, int offset, const std::string & fs, const Args & ... args) + : SQLiteError(path, errNo, extendedErrNo, offset, hintfmt(fs, args...)) { } [[noreturn]] static void throw_(sqlite3 * db, hintformat && hf); From 97b2a336ff3c24c56235110f3c457b88f5a51f33 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Wed, 14 Dec 2022 22:23:58 +0000 Subject: [PATCH 221/245] configure.ac: don't clobber CFLAGS=/CXXFLAGS= and allow users to pass in custom flags Reported-by: 0n-s Bug: https://github.com/trofi/nix-guix-gentoo/issues/26 --- configure.ac | 2 -- 1 file changed, 2 deletions(-) diff --git a/configure.ac b/configure.ac index c0e989d85..1b0d6fd27 100644 --- a/configure.ac +++ b/configure.ac @@ -41,8 +41,6 @@ AC_DEFINE_UNQUOTED(SYSTEM, ["$system"], [platform identifier ('cpu-os')]) test "$localstatedir" = '${prefix}/var' && localstatedir=/nix/var -CFLAGS= -CXXFLAGS= AC_PROG_CC AC_PROG_CXX AC_PROG_CPP From 1c40182b12d5fd462c891b597e1a3f9b912502d5 Mon Sep 17 00:00:00 2001 From: Linus Heckemann Date: Mon, 19 Dec 2022 13:35:05 +0100 Subject: [PATCH 222/245] fixup: remove boehmgc patch --- boehmgc-coroutine-sp-fallback.diff | 77 ------------------------------ 1 file changed, 77 deletions(-) delete mode 100644 boehmgc-coroutine-sp-fallback.diff diff --git a/boehmgc-coroutine-sp-fallback.diff b/boehmgc-coroutine-sp-fallback.diff deleted file mode 100644 index 8fdafbecb..000000000 --- a/boehmgc-coroutine-sp-fallback.diff +++ /dev/null @@ -1,77 +0,0 @@ -diff --git a/darwin_stop_world.c b/darwin_stop_world.c -index 3dbaa3fb..36a1d1f7 100644 ---- a/darwin_stop_world.c -+++ b/darwin_stop_world.c -@@ -352,6 +352,7 @@ GC_INNER void GC_push_all_stacks(void) - int nthreads = 0; - word total_size = 0; - mach_msg_type_number_t listcount = (mach_msg_type_number_t)THREAD_TABLE_SZ; -+ size_t stack_limit; - if (!EXPECT(GC_thr_initialized, TRUE)) - GC_thr_init(); - -@@ -407,6 +408,19 @@ GC_INNER void GC_push_all_stacks(void) - GC_push_all_stack_sections(lo, hi, p->traced_stack_sect); - } - if (altstack_lo) { -+ // 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. -+ stack_limit = pthread_get_stacksize_np(p->id); -+ if (altstack_lo >= altstack_hi || altstack_lo < altstack_hi - stack_limit) { // sp outside stack -+ altstack_lo = altstack_hi - stack_limit; -+ } -+ - total_size += altstack_hi - altstack_lo; - GC_push_all_stack(altstack_lo, altstack_hi); - } -diff --git a/pthread_stop_world.c b/pthread_stop_world.c -index 4b2c429..1fb4c52 100644 ---- a/pthread_stop_world.c -+++ b/pthread_stop_world.c -@@ -673,6 +673,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(); -@@ -722,6 +724,31 @@ 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!"); -+ } -+ if (pthread_attr_destroy(&pattr)) { -+ ABORT("GC_push_all_stacks: pthread_attr_destroy 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 From 94cf0da7b2955d5b54a142b9e920332746a61033 Mon Sep 17 00:00:00 2001 From: Timothy DeHerrera Date: Mon, 19 Dec 2022 13:16:06 -0700 Subject: [PATCH 223/245] fix(develop): make `nix develop` drv recreatable --- src/nix/develop.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/nix/develop.cc b/src/nix/develop.cc index 4de109754..6c3a9c6c6 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -192,10 +192,12 @@ static StorePath getDerivationEnvironment(ref store, ref evalStore drv.env.erase("allowedRequisites"); drv.env.erase("disallowedReferences"); drv.env.erase("disallowedRequisites"); + drv.env.erase("name"); /* Rehash and write the derivation. FIXME: would be nice to use 'buildDerivation', but that's privileged. */ drv.name += "-env"; + drv.env.emplace("name", drv.name); drv.inputSrcs.insert(std::move(getEnvShPath)); if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { for (auto & output : drv.outputs) { From c162c90b432590e1dbdeb5f4361bf4b48de82bac Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 09:59:59 +0100 Subject: [PATCH 224/245] add more explanation to diagrams this is to help reading the diagrams, otherwise arrows and labels were reported as being ambiguous. --- doc/manual/src/architecture/architecture.md | 125 ++++++++++++-------- 1 file changed, 78 insertions(+), 47 deletions(-) diff --git a/doc/manual/src/architecture/architecture.md b/doc/manual/src/architecture/architecture.md index 3a7d1d6f5..2d1b26558 100644 --- a/doc/manual/src/architecture/architecture.md +++ b/doc/manual/src/architecture/architecture.md @@ -9,18 +9,29 @@ Nix consists of [hierarchical layers]. [hierarchical layers]: https://en.m.wikipedia.org/wiki/Multitier_architecture#Layers +The following [concept map] shows its main components (rectangles), the objects they operate on (rounded rectangles), and their interactions (connecting phrases): + +[concept map]: https://en.m.wikipedia.org/wiki/Concept_map + ``` -+---------------------------------------------------------------+ -| Nix .-------------------------. | -| | commmand line interface |------. | -| '-------------------------' | | -| | | | -| calls | | -| | manages | -| V | | -| .-------------------------. | | -| | language evaluator | | | -| '-------------------------' | | + + .----------------. + | Nix expression |----------. + '----------------' | + | passed to + | | ++----------|-------------------|--------------------------------+ +| Nix | V | +| | +-------------------------+ | +| | | commmand line interface |------. | +| | +-------------------------+ | | +| | | | | +| evaluated by calls manages | +| | | | | +| | V | | +| | +--------------------+ | | +| '-------->| language evaluator | | | +| +--------------------+ | | | | | | | produces | | | | V | @@ -28,57 +39,77 @@ Nix consists of [hierarchical layers]. | | store | | | | | referenced by V builds | | | | .-------------. .------------. .--------------. | | -| | | build input |----->| build plan | ---->| build result | | | +| | | build input |----->| build plan |----->| build result | | | | | '-------------' '------------' '--------------' | | -| +-----------------------------------------------------------+ | -+---------------------------------------------------------------+ +| +-------------------------------------------------|---------+ | ++---------------------------------------------------|-----------+ + | + represented as + | + V + .---------------. + | file | + '---------------' ``` -At the top is the [command line interface](../command-ref/command-ref.md), translating from invocations of Nix executables to interactions with the underlying layers. +At the top is the [command line interface](../command-ref/command-ref.md) that drives the underlying layers. -Below that is the evaluator for the [Nix language](../language/index.md), the configuration language for Nix. -Its expressions ultimately evaluate to self-contained *build plans*, used to derive *build results* from referenced *build inputs*. +The [Nix language](../language/index.md) evaluator transforms Nix expressions into self-contained *build plans*, which are used to derive *build results* from referenced *build inputs*. -The command line interface and the Nix language are what users interact with most. +The command line interface and Nix expressions are what users deal with most. > **Note** > The Nix language itself does not have a notion of *packages* or *configurations*. > As far as we are concerned here, the inputs and results of a build plan are just data. -Underlying the command line interface and the Nix language is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. -It can also execute build plans to produce new data. +Underlying the command line interface and the Nix language evaluator is the [Nix store](../glossary.md#gloss-store), a mechanism to keep track of build plans, data, and references between them. +It can also execute build plans to produce new data, which are made available to the operating system as files. -A build plan is a series of *build tasks*. -Each build task has a special build input, which is used as *build instructions*. -The result of a build task can be input to another build task. +A build plan itself is a series of *build tasks*, together with their build inputs. > **Important** > A build task in Nix is called [derivation](../glossary#gloss-derivation). +Each build task has a special build input executed as *build instructions* in order to perform the build. +The result of a build task can be input to another build task. + +The following [data flow diagram] shows a build plan for illustration. +Build inputs used as instructions to a build task are marked accordingly: + +[data flow diagram]: https://en.m.wikipedia.org/wiki/Data-flow_diagram + ``` -+----------------------------------------------------------------------------------+ -| store - - - - - - - - - - - - - - - - - - - - - - | -| : build plan : | -| .-------------. : : | -| | build input |---instructions-. : | -| '-------------' : | : | -| : v : | -| .-------------. : .------------. : | -| | build input |--------->| build task |-instructions-. : | -| '-------------' : '------------' | : | -| : v : | -| .-------------. : .------------. : .--------------. | -| | build input |---instructions-. | build task |--->| build result | | -| '-------------' : | '------------' : '--------------' | -| : v ^ : | -| .-------------. : .------------. | : | -| | build input |--------->| build task |--------------' : | -| '-------------' : '------------' : | -| : ^ : | -| .-------------. : | : | -| | build input |----------------' : | -| '-------------' : : | -| :_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _: | -+----------------------------------------------------------------------------------+ ++--------------------------------------------------------------------+ +| build plan | +| | +| .-------------. | +| | build input |---------. | +| '-------------' | | +| instructions | +| | | +| v | +| .-------------. .----------. | +| | build input |-->( build task )-------. | +| '-------------' '----------' | | +| instructions | +| | | +| v | +| .-------------. .----------. .--------------. | +| | build input |---------. ( build task )--->| build result | | +| '-------------' | '----------' '--------------' | +| instructions ^ | +| | | | +| v | | +| .-------------. .----------. | | +| | build input |-->( build task )-------' | +| '-------------' '----------' | +| ^ | +| | | +| | | +| .-------------. | | +| | build input |---------' | +| '-------------' | +| | ++--------------------------------------------------------------------+ ``` From 7e31a991db9fbc7881e5f96a10eea0c030bc8f6e Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 14:28:43 +0100 Subject: [PATCH 225/245] make relative links explicit --- doc/manual/src/glossary.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index b13709f8a..aa775b163 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -3,18 +3,18 @@ - [derivation]{#gloss-derivation}\ A description of a build task. The result of a derivation is a store object. Derivations are typically specified in Nix expressions - using the [`derivation` primitive](language/derivations.md). These are + using the [`derivation` primitive](./language/derivations.md). These are translated into low-level *store derivations* (implicitly by `nix-env` and `nix-build`, or explicitly by `nix-instantiate`). - [content-addressed derivation]{#gloss-content-addressed-derivation}\ A derivation which has the - [`__contentAddressed`](language/advanced-attributes.md#adv-attr-__contentAddressed) + [`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed) attribute set to `true`. - [fixed-output derivation]{#gloss-fixed-output-derivation}\ A derivation which includes the - [`outputHash`](language/advanced-attributes.md#adv-attr-outputHash) attribute. + [`outputHash`](./language/advanced-attributes.md#adv-attr-outputHash) attribute. - [store]{#gloss-store}\ The location in the file system where store objects live. Typically @@ -79,7 +79,7 @@ - [substituter]{#gloss-substituter}\ A *substituter* is an additional store from which Nix will copy store objects it doesn't have. For details, see the - [`substituters` option](command-ref/conf-file.html#conf-substituters). + [`substituters` option](./command-ref/conf-file.md#conf-substituters). - [purity]{#gloss-purity}\ The assumption that equal Nix derivations when run always produce @@ -139,7 +139,7 @@ An automatically generated store object that consists of a set of symlinks to “active” applications, i.e., other store paths. These are generated automatically by - [`nix-env`](command-ref/nix-env.md). See *profiles*. + [`nix-env`](./command-ref/nix-env.md). See *profiles*. - [profile]{#gloss-profile}\ A symlink to the current *user environment* of a user, e.g., @@ -150,7 +150,9 @@ store. It can contain regular files, directories and symbolic links. NARs are generated and unpacked using `nix-store --dump` and `nix-store --restore`. + - [`∅`]{#gloss-emtpy-set}\ The empty set symbol. In the context of profile history, this denotes a package is not present in a particular version of the profile. + - [`ε`]{#gloss-epsilon}\ The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute. From 1437582ccd3c4af51f34e43a77df5c8622e24d6c Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Tue, 20 Dec 2022 16:29:32 +0100 Subject: [PATCH 226/245] doc/book.toml: Improve config (#7300) * doc/book.toml: Improve config - `title` value will be added to the HTML - here - `git-repository-url` adds a link to the GitHub repo in the top right corner - `edit-url-template` adds an edit link, inviting contributions Co-authored-by: Valentin Gagarin --- doc/manual/book.toml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/manual/book.toml b/doc/manual/book.toml index 5f78a7614..46ced7ff7 100644 --- a/doc/manual/book.toml +++ b/doc/manual/book.toml @@ -1,6 +1,11 @@ +[book] +title = "Nix Reference Manual" + [output.html] additional-css = ["custom.css"] additional-js = ["redirects.js"] +edit-url-template = "https://github.com/NixOS/nix/tree/master/doc/manual/{path}" +git-repository-url = "https://github.com/NixOS/nix" [preprocessor.anchors] renderers = ["html"] From 0251d44cc2e991608ea39f2274ad2d197c668468 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Thu, 15 Dec 2022 20:17:08 -0500 Subject: [PATCH 227/245] Make `./mk/run-test.sh` work by itself; add `mk/debug-test.sh` First, logic is consolidated in the shell script instead of being spread between them and makefiles. That makes understanding what is going on a little easier. This would not be super interesting by itself, but it gives us a way to debug tests more easily. *That* in turn I hope is much more compelling. See the updated manual for details. Co-authored-by: Valentin Gagarin Co-authored-by: Eelco Dolstra Co-authored-by: Robert Hensing --- doc/manual/src/contributing/hacking.md | 75 +++++++++++++++++++++++++- mk/common-test.sh | 11 ++++ mk/debug-test.sh | 11 ++++ mk/{run_test.sh => run-test.sh} | 17 +++--- mk/tests.mk | 6 ++- tests/local.mk | 2 - 6 files changed, 111 insertions(+), 11 deletions(-) create mode 100644 mk/common-test.sh create mode 100755 mk/debug-test.sh rename mk/{run_test.sh => run-test.sh} (79%) diff --git a/doc/manual/src/contributing/hacking.md b/doc/manual/src/contributing/hacking.md index 9f7d5057b..b75d2554c 100644 --- a/doc/manual/src/contributing/hacking.md +++ b/doc/manual/src/contributing/hacking.md @@ -99,8 +99,79 @@ You can run the whole testsuite with `make check`, or the tests for a specific c ### Functional tests The functional tests reside under the `tests` directory and are listed in `tests/local.mk`. -The whole testsuite can be run with `make install && make installcheck`. -Individual tests can be run with `make tests/{testName}.sh.test`. +Each test is a bash script. + +The whole testsuite can be run with: + +```shell-session +$ make install && make installcheck +ran test tests/foo.sh... [PASS] +ran test tests/bar.sh... [PASS] +... +``` + +Individual tests can be run with `make`: + +```shell-session +$ make tests/${testName}.sh.test +ran test tests/${testName}.sh... [PASS] +``` + +or without `make`: + +```shell-session +$ ./mk/run-test.sh tests/${testName}.sh +ran test tests/${testName}.sh... [PASS] +``` + +To see the complet eoutput, one can also run: + +```shell-session +$ ./mk/debug-test.sh tests/${testName}.sh ++ foo +output from foo ++ bar +output from bar +... +``` + +The test script will be traced with `set -x` and the output displayed as it happens, regardless of whether the test succeeds or fails. + +#### Debugging failing functional tests + +When a functional test fails, it usually does so somewhere in the middle of the script. + +To figure out what's wrong, it is convenient to run the test regularly up to the failing `nix` command, and then run that command with a debugger like GDB. + +For example, if the script looks like: + +```bash +foo +nix blah blub +bar +``` +one would could edit it like so: + +```diff + foo +-nix blah blub ++gdb --args nix blah blub + bar +``` + +Then, when one runs the script with `./mk/debug-test.sh`, it will drop them into GDB once the script reaches that point: + +```shell-session +$ ./mk/debug-test.sh tests/${testName}.sh +... ++ gdb blash blub +GNU gdb (GDB) 12.1 +... +(gdb) +``` + +One can debug the Nix invocation in all the usual ways. +(For exampling running `run` will run the Nix invocation.) ### Integration tests diff --git a/mk/common-test.sh b/mk/common-test.sh new file mode 100644 index 000000000..0a2e4c1c2 --- /dev/null +++ b/mk/common-test.sh @@ -0,0 +1,11 @@ +TESTS_ENVIRONMENT=("TEST_NAME=${test%.*}" 'NIX_REMOTE=') + +: ${BASH:=/usr/bin/env bash} + +init_test () { + cd tests && env "${TESTS_ENVIRONMENT[@]}" $BASH -e init.sh 2>/dev/null > /dev/null +} + +run_test_proper () { + cd $(dirname $test) && env "${TESTS_ENVIRONMENT[@]}" $BASH -e $(basename $test) +} diff --git a/mk/debug-test.sh b/mk/debug-test.sh new file mode 100755 index 000000000..6299e68a0 --- /dev/null +++ b/mk/debug-test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +set -eu + +test=$1 + +dir="$(dirname "${BASH_SOURCE[0]}")" +source "$dir/common-test.sh" + +(init_test) +run_test_proper diff --git a/mk/run_test.sh b/mk/run-test.sh similarity index 79% rename from mk/run_test.sh rename to mk/run-test.sh index 7e95df2ac..219c8577f 100755 --- a/mk/run_test.sh +++ b/mk/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash set -u @@ -7,7 +7,12 @@ green="" yellow="" normal="" -post_run_msg="ran test $1..." +test=$1 + +dir="$(dirname "${BASH_SOURCE[0]}")" +source "$dir/common-test.sh" + +post_run_msg="ran test $test..." if [ -t 1 ]; then red="" green="" @@ -16,12 +21,12 @@ if [ -t 1 ]; then fi run_test () { - (cd tests && env ${TESTS_ENVIRONMENT} init.sh 2>/dev/null > /dev/null) - log="$(cd $(dirname $1) && env ${TESTS_ENVIRONMENT} $(basename $1) 2>&1)" + (init_test 2>/dev/null > /dev/null) + log="$(run_test_proper 2>&1)" status=$? } -run_test "$1" +run_test # Hack: Retry the test if it fails with “unexpected EOF reading a line” as these # appear randomly without anyone knowing why. @@ -32,7 +37,7 @@ if [[ $status -ne 0 && $status -ne 99 && \ ]]; then echo "$post_run_msg [${yellow}FAIL$normal] (possibly flaky, so will be retried)" echo "$log" | sed 's/^/ /' - run_test "$1" + run_test fi if [ $status -eq 0 ]; then diff --git a/mk/tests.mk b/mk/tests.mk index a2e30a378..3ebbd86e3 100644 --- a/mk/tests.mk +++ b/mk/tests.mk @@ -8,7 +8,11 @@ define run-install-test .PHONY: $1.test $1.test: $1 $(test-deps) - @env TEST_NAME=$(basename $1) TESTS_ENVIRONMENT="$(tests-environment)" mk/run_test.sh $1 < /dev/null + @env BASH=$(bash) $(bash) mk/run-test.sh $1 < /dev/null + + .PHONY: $1.test-debug + $1.test-debug: $1 $(test-deps) + @env BASH=$(bash) $(bash) mk/debug-test.sh $1 < /dev/null endef diff --git a/tests/local.mk b/tests/local.mk index 2f7f76261..bba6ad9c9 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -121,8 +121,6 @@ endif install-tests += $(foreach x, $(nix_tests), tests/$(x)) -tests-environment = NIX_REMOTE= $(bash) -e - clean-files += $(d)/common.sh $(d)/config.nix $(d)/ca/config.nix test-deps += tests/common.sh tests/config.nix tests/ca/config.nix From bc8ab21c5acbc9b94199be97dc66722cc5cf2252 Mon Sep 17 00:00:00 2001 From: mupdt <25388474+mupdt@users.noreply.github.com> Date: Wed, 21 Dec 2022 04:50:40 -0500 Subject: [PATCH 228/245] [PDT] TDE-3114: prevent a race-condition when creating the S3 cache --- src/libstore/nar-info-disk-cache.cc | 42 ++++++++++++++++++----------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/libstore/nar-info-disk-cache.cc b/src/libstore/nar-info-disk-cache.cc index f4ea739b0..3e0689534 100644 --- a/src/libstore/nar-info-disk-cache.cc +++ b/src/libstore/nar-info-disk-cache.cc @@ -166,16 +166,37 @@ public: return i->second; } + std::optional queryCacheRaw(State & state, const std::string & uri) + { + auto i = state.caches.find(uri); + if (i == state.caches.end()) { + auto queryCache(state.queryCache.use()(uri)(time(0) - cacheInfoTtl)); + if (!queryCache.next()) + return std::nullopt; + state.caches.emplace(uri, + Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)}); + } + return getCache(state, uri); + } + void createCache(const std::string & uri, const Path & storeDir, bool wantMassQuery, int priority) override { retrySQLite([&]() { auto state(_state.lock()); + SQLiteTxn txn(state->db); - // FIXME: race + // To avoid the race, we have to check if maybe someone hasn't yet created + // the cache for this URI in the meantime. + auto cache(queryCacheRaw(*state, uri)); + + if (cache) + return; state->insertCache.use()(uri)(time(0))(storeDir)(wantMassQuery)(priority).exec(); assert(sqlite3_changes(state->db) == 1); state->caches[uri] = Cache{(int) sqlite3_last_insert_rowid(state->db), storeDir, wantMassQuery, priority}; + + txn.commit(); }); } @@ -183,21 +204,12 @@ public: { return retrySQLite>([&]() -> std::optional { auto state(_state.lock()); - - auto i = state->caches.find(uri); - if (i == state->caches.end()) { - auto queryCache(state->queryCache.use()(uri)(time(0) - cacheInfoTtl)); - if (!queryCache.next()) - return std::nullopt; - state->caches.emplace(uri, - Cache{(int) queryCache.getInt(0), queryCache.getStr(1), queryCache.getInt(2) != 0, (int) queryCache.getInt(3)}); - } - - auto & cache(getCache(*state, uri)); - + auto cache(queryCacheRaw(*state, uri)); + if (!cache) + return std::nullopt; return CacheInfo { - .wantMassQuery = cache.wantMassQuery, - .priority = cache.priority + .wantMassQuery = cache->wantMassQuery, + .priority = cache->priority }; }); } From 62f4f883a7f63380be578ebb6167641ec111b59e Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 15:11:31 +0100 Subject: [PATCH 229/245] define "store derivation" --- doc/manual/src/glossary.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index aa775b163..117b43a88 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -7,6 +7,18 @@ translated into low-level *store derivations* (implicitly by `nix-env` and `nix-build`, or explicitly by `nix-instantiate`). + [derivation]: #gloss-derivation + + - [store derivation]{#gloss-store-derivation}\ + A [derivation] represented as a `.drv` file in the [store]. + It has a [store path], like any [store object]. + + Example: `/nix/store/g946hcz4c8mdvq2g8vxx42z51qb71rvp-git-2.38.1.drv` + + See [`nix show-derivation`](./command-ref/new-cli/nix3-show-derivation.md) (experimental) for displaying the contents of store derivations. + + [store derivation]: #gloss-store-derivation + - [content-addressed derivation]{#gloss-content-addressed-derivation}\ A derivation which has the [`__contentAddressed`](./language/advanced-attributes.md#adv-attr-__contentAddressed) @@ -34,6 +46,8 @@ directory on another machine, accessed via `ssh` or served by the `nix-serve` Perl script. + [store]: #gloss-store + - [chroot store]{#gloss-chroot-store}\ A local store whose canonical path is anything other than `/nix/store`. @@ -49,6 +63,10 @@ The location in the file system of a store object, i.e., an immediate child of the Nix store directory. + Example: `/nix/store/a040m110amc4h71lds2jmr8qrkj2jhxd-git-2.38.1` + + [store path]: #gloss-store-path + - [store object]{#gloss-store-object}\ A file that is an immediate child of the Nix store directory. These can be regular files, but also entire directory trees. Store objects @@ -56,6 +74,8 @@ derivation outputs (objects produced by running a build task), or derivations (files describing a build task). + [store object]: #gloss-store-object + - [input-addressed store object]{#gloss-input-addressed-store-object}\ A store object produced by building a non-[content-addressed](#gloss-content-addressed-derivation), From 3a66d82e1da1302586055a70903de2f9a7425087 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 15:26:05 +0100 Subject: [PATCH 230/245] update description of "store derivation" in installables section a store derivation is not a store path itself, it has a store path. --- doc/manual/src/glossary.md | 4 +++- src/nix/nix.md | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index 117b43a88..0fcc2b07b 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -144,7 +144,9 @@ references `R` then `R` is also in the closure of `P`. - [output path]{#gloss-output-path}\ - A store path produced by a derivation. + A [store path] produced by a [derivation]. + + [output path]: #gloss-output-path - [deriver]{#gloss-deriver}\ The deriver of an *output path* is the store diff --git a/src/nix/nix.md b/src/nix/nix.md index 723d3c87e..43de6869b 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -115,12 +115,12 @@ the Nix store. Here are the recognised types of installables: * **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv` - Store derivations are store paths with extension `.drv` and are a - low-level representation of a build-time dependency graph used - internally by Nix. By default, if you pass a store derivation to a - `nix` subcommand, it will operate on the *output paths* of the - derivation. For example, `nix path-info` prints information about - the output paths: + Store derivations are a low-level, internal representation of build tasks in Nix, and have store paths with extension `.drv`. + By default, if you pass the path to a store derivation to a `nix` subcommand, the command will operate on the [output path]s of the derivation. + + [output path]: ../../glossary.md#gloss-output-path + + For example, `nix path-info` prints information about the output paths: ```console # nix path-info --json /nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv From 7797661a70bcd6a7caf756964785ea7727ed1be0 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Tue, 20 Dec 2022 15:37:40 +0100 Subject: [PATCH 231/245] link "store derivation" to glossary definition --- doc/manual/src/command-ref/nix-build.md | 4 +++- doc/manual/src/command-ref/nix-copy-closure.md | 4 +++- doc/manual/src/command-ref/nix-env.md | 8 +++++--- doc/manual/src/command-ref/nix-instantiate.md | 10 +++++----- doc/manual/src/command-ref/nix-store.md | 14 ++++++++------ src/libcmd/installables.cc | 2 +- src/nix/nix.md | 4 +++- src/nix/path-info.md | 4 +++- src/nix/show-derivation.md | 6 ++++-- src/nix/store-copy-log.md | 4 +++- 10 files changed, 38 insertions(+), 22 deletions(-) diff --git a/doc/manual/src/command-ref/nix-build.md b/doc/manual/src/command-ref/nix-build.md index 3a47feaae..937b046b8 100644 --- a/doc/manual/src/command-ref/nix-build.md +++ b/doc/manual/src/command-ref/nix-build.md @@ -37,10 +37,12 @@ directory containing at least a file named `default.nix`. `nix-build` is essentially a wrapper around [`nix-instantiate`](nix-instantiate.md) (to translate a high-level Nix -expression to a low-level store derivation) and [`nix-store +expression to a low-level [store derivation]) and [`nix-store --realise`](nix-store.md#operation---realise) (to build the store derivation). +[store derivation]: ../glossary.md#gloss-store-derivation + > **Warning** > > The result of the build is automatically registered as a root of the diff --git a/doc/manual/src/command-ref/nix-copy-closure.md b/doc/manual/src/command-ref/nix-copy-closure.md index 9a29030bd..83e8a2936 100644 --- a/doc/manual/src/command-ref/nix-copy-closure.md +++ b/doc/manual/src/command-ref/nix-copy-closure.md @@ -47,7 +47,9 @@ authentication, you can avoid typing the passphrase with `ssh-agent`. Enable compression of the SSH connection. - `--include-outputs`\ - Also copy the outputs of store derivations included in the closure. + Also copy the outputs of [store derivation]s included in the closure. + + [store derivation]: ../../glossary.md#gloss-store-derivation - `--use-substitutes` / `-s`\ Attempt to download missing paths on the target machine using Nix’s diff --git a/doc/manual/src/command-ref/nix-env.md b/doc/manual/src/command-ref/nix-env.md index a5df35d77..f4fa5b50c 100644 --- a/doc/manual/src/command-ref/nix-env.md +++ b/doc/manual/src/command-ref/nix-env.md @@ -205,10 +205,12 @@ a number of possible ways: unambiguous way, which is necessary if there are multiple derivations with the same name. - - If *args* are store derivations, then these are + - If *args* are [store derivation]s, then these are [realised](nix-store.md#operation---realise), and the resulting output paths are installed. + [store derivation]: ../glossary.md#gloss-store-derivation + - If *args* are store paths that are not store derivations, then these are [realised](nix-store.md#operation---realise) and installed. @@ -280,7 +282,7 @@ To copy the store path with symbolic name `gcc` from another profile: $ nix-env -i --from-profile /nix/var/nix/profiles/foo gcc ``` -To install a specific store derivation (typically created by +To install a specific [store derivation] (typically created by `nix-instantiate`): ```console @@ -665,7 +667,7 @@ derivation is shown unless `--no-name` is specified. Print the `system` attribute of the derivation. - `--drv-path`\ - Print the path of the store derivation. + Print the path of the [store derivation]. - `--out-path`\ Print the output path of the derivation. diff --git a/doc/manual/src/command-ref/nix-instantiate.md b/doc/manual/src/command-ref/nix-instantiate.md index 8f143729e..432fb2608 100644 --- a/doc/manual/src/command-ref/nix-instantiate.md +++ b/doc/manual/src/command-ref/nix-instantiate.md @@ -17,13 +17,14 @@ # Description -The command `nix-instantiate` generates [store -derivations](../glossary.md) from (high-level) Nix expressions. It -evaluates the Nix expressions in each of *files* (which defaults to +The command `nix-instantiate` produces [store derivation]s from (high-level) Nix expressions. +It evaluates the Nix expressions in each of *files* (which defaults to *./default.nix*). Each top-level expression should evaluate to a derivation, a list of derivations, or a set of derivations. The paths of the resulting store derivations are printed on standard output. +[store derivation]: ../glossary.md#gloss-store-derivation + If *files* is the character `-`, then a Nix expression will be read from standard input. @@ -79,8 +80,7 @@ standard input. # Examples -Instantiating store derivations from a Nix expression, and building them -using `nix-store`: +Instantiate [store derivation]s from a Nix expression, and build them using `nix-store`: ```console $ nix-instantiate test.nix (instantiate) diff --git a/doc/manual/src/command-ref/nix-store.md b/doc/manual/src/command-ref/nix-store.md index b712a7463..acf29e4aa 100644 --- a/doc/manual/src/command-ref/nix-store.md +++ b/doc/manual/src/command-ref/nix-store.md @@ -137,8 +137,10 @@ or. ## Examples -This operation is typically used to build store derivations produced by -[`nix-instantiate`](nix-instantiate.md): +This operation is typically used to build [store derivation]s produced by +[`nix-instantiate`](./nix-instantiate.md): + +[store derivation]: ../glossary.md#gloss-store-derivation ```console $ nix-store -r $(nix-instantiate ./test.nix) @@ -298,7 +300,7 @@ symlink. ## Common query options - `--use-output`; `-u`\ - For each argument to the query that is a store derivation, apply the + For each argument to the query that is a [store derivation], apply the query to the output path of the derivation instead. - `--force-realise`; `-f`\ @@ -318,7 +320,7 @@ symlink. This query has one option: - `--include-outputs` - Also include the existing output paths of store derivations, + Also include the existing output paths of [store derivation]s, and their closures. This query can be used to implement various kinds of deployment. A @@ -372,12 +374,12 @@ symlink. Prints the references graph of the store paths *paths* in the [GraphML](http://graphml.graphdrawing.org/) file format. This can be used to visualise dependency graphs. To obtain a build-time - dependency graph, apply this to a store derivation. To obtain a + dependency graph, apply this to a [store derivation]. To obtain a runtime dependency graph, apply it to an output path. - `--binding` *name*; `-b` *name*\ Prints the value of the attribute *name* (i.e., environment - variable) of the store derivations *paths*. It is an error for a + variable) of the [store derivation]s *paths*. It is an error for a derivation to not have the specified attribute. - `--hash`\ diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index 5cdd3e12c..d2600ca91 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -168,7 +168,7 @@ SourceExprCommand::SourceExprCommand(bool supportReadOnlyMode) addFlag({ .longName = "derivation", - .description = "Operate on the store derivation rather than its outputs.", + .description = "Operate on the [store derivation](../../glossary.md#gloss-store-derivation) rather than its outputs.", .category = installablesCategory, .handler = {&operateOn, OperateOn::Derivation}, }); diff --git a/src/nix/nix.md b/src/nix/nix.md index 43de6869b..9641cccb2 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -202,9 +202,11 @@ operate are determined as follows: a command like `nix shell nixpkgs#libxml2` will provide only those two outputs by default. - Note that a store derivation (given by `.drv` file store path) doesn't have + Note that a [store derivation] (given by its `.drv` file store path) doesn't have any attributes like `meta`, and thus this case doesn't apply to it. + [store derivation]: ../../glossary.md#gloss-store-derivation + * Otherwise, Nix will use all outputs of the derivation. # Nix stores diff --git a/src/nix/path-info.md b/src/nix/path-info.md index 7a1714ba4..b30898ac0 100644 --- a/src/nix/path-info.md +++ b/src/nix/path-info.md @@ -68,7 +68,9 @@ R""( ] ``` -* Print the path of the store derivation produced by `nixpkgs#hello`: +* Print the path of the [store derivation] produced by `nixpkgs#hello`: + + [store derivation]: ../../glossary.md#gloss-store-derivation ```console # nix path-info --derivation nixpkgs#hello diff --git a/src/nix/show-derivation.md b/src/nix/show-derivation.md index aa863899c..2cd93aa62 100644 --- a/src/nix/show-derivation.md +++ b/src/nix/show-derivation.md @@ -2,9 +2,11 @@ R""( # Examples -* Show the store derivation that results from evaluating the Hello +* Show the [store derivation] that results from evaluating the Hello package: + [store derivation]: ../../glossary.md#gloss-store-derivation + ```console # nix show-derivation nixpkgs#hello { @@ -37,7 +39,7 @@ R""( # Description This command prints on standard output a JSON representation of the -store derivations to which *installables* evaluate. Store derivations +[store derivation]s to which *installables* evaluate. Store derivations are used internally by Nix. They are store paths with extension `.drv` that represent the build-time dependency graph to which a Nix expression evaluates. diff --git a/src/nix/store-copy-log.md b/src/nix/store-copy-log.md index 19ae57079..0937250f2 100644 --- a/src/nix/store-copy-log.md +++ b/src/nix/store-copy-log.md @@ -18,7 +18,9 @@ R""( (The flag `--substituters ''` avoids querying `https://cache.nixos.org` for the log.) -* To copy the log for a specific store derivation via SSH: +* To copy the log for a specific [store derivation] via SSH: + + [store derivation]: ../../glossary.md#gloss-store-derivation ```console # nix store copy-log --to ssh-ng://machine /nix/store/ilgm50plpmcgjhcp33z6n4qbnpqfhxym-glibc-2.33-59.drv From a33e45b60be5d8dc73bd800defee364d90a8d738 Mon Sep 17 00:00:00 2001 From: mupdt <25388474+mupdt@users.noreply.github.com> Date: Wed, 21 Dec 2022 07:01:57 -0500 Subject: [PATCH 232/245] primops `storeDir` test uses `settings.nixStore` --- src/libexpr/tests/primops.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libexpr/tests/primops.cc b/src/libexpr/tests/primops.cc index 49fbc5e98..bcdc7086b 100644 --- a/src/libexpr/tests/primops.cc +++ b/src/libexpr/tests/primops.cc @@ -604,7 +604,7 @@ namespace nix { TEST_F(PrimOpTest, storeDir) { auto v = eval("builtins.storeDir"); - ASSERT_THAT(v, IsStringEq("/nix/store")); + ASSERT_THAT(v, IsStringEq(settings.nixStore)); } TEST_F(PrimOpTest, nixVersion) { From 64c60f7241217cf4ba652f6502935ac7d523fb55 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 23 Dec 2022 15:32:54 +0100 Subject: [PATCH 233/245] Fix CanonPath::dirOf() returning a string_view of a temporary https://hydra.nixos.org/build/202837872 --- src/libutil/canon-path.hh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libutil/canon-path.hh b/src/libutil/canon-path.hh index c5e7f0596..9d5984584 100644 --- a/src/libutil/canon-path.hh +++ b/src/libutil/canon-path.hh @@ -110,7 +110,7 @@ public: std::optional dirOf() const { if (isRoot()) return std::nullopt; - return path.substr(0, path.rfind('/')); + return ((std::string_view) path).substr(0, path.rfind('/')); } std::optional baseName() const From c164d304f3cd6c5e536e33435084c030f018c2ab Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Fri, 23 Dec 2022 16:28:26 +0100 Subject: [PATCH 234/245] nix develop: Set personality This makes 'nix develop' set the Linux personality in the same way that the actual build does, allowing a command like 'nix develop nix#devShells.i686-linux.default' on x86_64-linux to work correctly. --- doc/manual/src/release-notes/rl-next.md | 6 +++ src/libstore/build/derivation-goal.cc | 1 - src/libstore/build/local-derivation-goal.cc | 31 +-------------- src/libstore/build/personality.cc | 44 +++++++++++++++++++++ src/libstore/build/personality.hh | 11 ++++++ src/nix/develop.cc | 10 ++++- src/nix/run.cc | 13 +++++- src/nix/run.hh | 3 +- 8 files changed, 85 insertions(+), 34 deletions(-) create mode 100644 src/libstore/build/personality.cc create mode 100644 src/libstore/build/personality.hh diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 6c169bd09..906b048f1 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -25,3 +25,9 @@ $ nix-build glibc^dev` ``` does already. + +* On Linux, `nix develop` now sets the + [*personality*](https://man7.org/linux/man-pages/man2/personality.2.html) + for the development shell in the same way as the actual build of the + derivation. This makes shells for `i686-linux` derivations work + correctly on `x86_64-linux`. diff --git a/src/libstore/build/derivation-goal.cc b/src/libstore/build/derivation-goal.cc index d3b995a4f..173058d1b 100644 --- a/src/libstore/build/derivation-goal.cc +++ b/src/libstore/build/derivation-goal.cc @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include diff --git a/src/libstore/build/local-derivation-goal.cc b/src/libstore/build/local-derivation-goal.cc index dccd096ec..9d869d513 100644 --- a/src/libstore/build/local-derivation-goal.cc +++ b/src/libstore/build/local-derivation-goal.cc @@ -15,6 +15,7 @@ #include "callback.hh" #include "json-utils.hh" #include "cgroup.hh" +#include "personality.hh" #include #include @@ -24,7 +25,6 @@ #include #include #include -#include #include #include @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include @@ -1964,33 +1963,7 @@ void LocalDerivationGoal::runChild() /* Close all other file descriptors. */ closeMostFDs({STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO}); -#if __linux__ - /* Change the personality to 32-bit if we're doing an - i686-linux build on an x86_64-linux machine. */ - struct utsname utsbuf; - uname(&utsbuf); - if ((drv->platform == "i686-linux" - && (settings.thisSystem == "x86_64-linux" - || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) - || drv->platform == "armv7l-linux" - || drv->platform == "armv6l-linux") - { - if (personality(PER_LINUX32) == -1) - throw SysError("cannot set 32-bit personality"); - } - - /* Impersonate a Linux 2.6 machine to get some determinism in - builds that depend on the kernel version. */ - if ((drv->platform == "i686-linux" || drv->platform == "x86_64-linux") && settings.impersonateLinux26) { - int cur = personality(0xffffffff); - if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */); - } - - /* Disable address space randomization for improved - determinism. */ - int cur = personality(0xffffffff); - if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE); -#endif + setPersonality(drv->platform); /* Disable core dumps by default. */ struct rlimit limit = { 0, RLIM_INFINITY }; diff --git a/src/libstore/build/personality.cc b/src/libstore/build/personality.cc new file mode 100644 index 000000000..4ad477869 --- /dev/null +++ b/src/libstore/build/personality.cc @@ -0,0 +1,44 @@ +#include "personality.hh" +#include "globals.hh" + +#if __linux__ +#include +#include +#endif + +#include + +namespace nix { + +void setPersonality(std::string_view system) +{ +#if __linux__ + /* Change the personality to 32-bit if we're doing an + i686-linux build on an x86_64-linux machine. */ + struct utsname utsbuf; + uname(&utsbuf); + if ((system == "i686-linux" + && (std::string_view(SYSTEM) == "x86_64-linux" + || (!strcmp(utsbuf.sysname, "Linux") && !strcmp(utsbuf.machine, "x86_64")))) + || system == "armv7l-linux" + || system == "armv6l-linux") + { + if (personality(PER_LINUX32) == -1) + throw SysError("cannot set 32-bit personality"); + } + + /* Impersonate a Linux 2.6 machine to get some determinism in + builds that depend on the kernel version. */ + if ((system == "i686-linux" || system == "x86_64-linux") && settings.impersonateLinux26) { + int cur = personality(0xffffffff); + if (cur != -1) personality(cur | 0x0020000 /* == UNAME26 */); + } + + /* Disable address space randomization for improved + determinism. */ + int cur = personality(0xffffffff); + if (cur != -1) personality(cur | ADDR_NO_RANDOMIZE); +#endif +} + +} diff --git a/src/libstore/build/personality.hh b/src/libstore/build/personality.hh new file mode 100644 index 000000000..30e4f4062 --- /dev/null +++ b/src/libstore/build/personality.hh @@ -0,0 +1,11 @@ +#pragma once + +#include + +namespace nix { + +void setPersonality(std::string_view system); + +} + + diff --git a/src/nix/develop.cc b/src/nix/develop.cc index 6c3a9c6c6..1d90d1dac 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -164,6 +164,14 @@ struct BuildEnvironment { return vars == other.vars && bashFunctions == other.bashFunctions; } + + std::string getSystem() const + { + if (auto v = get(vars, "system")) + return getString(*v); + else + return settings.thisSystem; + } }; const static std::string getEnvSh = @@ -570,7 +578,7 @@ struct CmdDevelop : Common, MixEnvironment } } - runProgramInStore(store, shell, args); + runProgramInStore(store, shell, args, buildEnvironment.getSystem()); } }; diff --git a/src/nix/run.cc b/src/nix/run.cc index 45d2dfd0d..6fca68047 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -9,6 +9,7 @@ #include "fs-accessor.hh" #include "progress-bar.hh" #include "eval.hh" +#include "build/personality.hh" #if __linux__ #include @@ -24,7 +25,8 @@ namespace nix { void runProgramInStore(ref store, const std::string & program, - const Strings & args) + const Strings & args, + std::optional system) { stopProgressBar(); @@ -44,7 +46,7 @@ void runProgramInStore(ref store, throw Error("store '%s' is not a local store so it does not support command execution", store->getUri()); if (store->storeDir != store2->getRealStoreDir()) { - Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), program }; + Strings helperArgs = { chrootHelperName, store->storeDir, store2->getRealStoreDir(), std::string(system.value_or("")), program }; for (auto & arg : args) helperArgs.push_back(arg); execv(getSelfExe().value_or("nix").c_str(), stringsToCharPtrs(helperArgs).data()); @@ -52,6 +54,9 @@ void runProgramInStore(ref store, throw SysError("could not execute chroot helper"); } + if (system) + setPersonality(*system); + execvp(program.c_str(), stringsToCharPtrs(args).data()); throw SysError("unable to execute '%s'", program); @@ -199,6 +204,7 @@ void chrootHelper(int argc, char * * argv) int p = 1; std::string storeDir = argv[p++]; std::string realStoreDir = argv[p++]; + std::string system = argv[p++]; std::string cmd = argv[p++]; Strings args; while (p < argc) @@ -262,6 +268,9 @@ void chrootHelper(int argc, char * * argv) writeFile("/proc/self/uid_map", fmt("%d %d %d", uid, uid, 1)); writeFile("/proc/self/gid_map", fmt("%d %d %d", gid, gid, 1)); + if (system != "") + setPersonality(system); + execvp(cmd.c_str(), stringsToCharPtrs(args).data()); throw SysError("unable to exec '%s'", cmd); diff --git a/src/nix/run.hh b/src/nix/run.hh index 6180a87dd..fed360158 100644 --- a/src/nix/run.hh +++ b/src/nix/run.hh @@ -6,6 +6,7 @@ namespace nix { void runProgramInStore(ref store, const std::string & program, - const Strings & args); + const Strings & args, + std::optional system = std::nullopt); } From efbd1d15c64a10068bdb8aed92b408a05f3b69a8 Mon Sep 17 00:00:00 2001 From: John Ericson Date: Fri, 23 Dec 2022 08:59:36 -0800 Subject: [PATCH 235/245] Apply suggestions from code review Co-authored-by: Valentin Gagarin --- doc/manual/src/contributing/hacking.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/manual/src/contributing/hacking.md b/doc/manual/src/contributing/hacking.md index b75d2554c..c9da1962f 100644 --- a/doc/manual/src/contributing/hacking.md +++ b/doc/manual/src/contributing/hacking.md @@ -101,7 +101,7 @@ You can run the whole testsuite with `make check`, or the tests for a specific c The functional tests reside under the `tests` directory and are listed in `tests/local.mk`. Each test is a bash script. -The whole testsuite can be run with: +The whole test suite can be run with: ```shell-session $ make install && make installcheck @@ -124,7 +124,7 @@ $ ./mk/run-test.sh tests/${testName}.sh ran test tests/${testName}.sh... [PASS] ``` -To see the complet eoutput, one can also run: +To see the complete output, one can also run: ```shell-session $ ./mk/debug-test.sh tests/${testName}.sh @@ -135,7 +135,7 @@ output from bar ... ``` -The test script will be traced with `set -x` and the output displayed as it happens, regardless of whether the test succeeds or fails. +The test script will then be traced with `set -x` and the output displayed as it happens, regardless of whether the test succeeds or fails. #### Debugging failing functional tests @@ -150,7 +150,7 @@ foo nix blah blub bar ``` -one would could edit it like so: +edit it like so: ```diff foo @@ -159,7 +159,7 @@ one would could edit it like so: bar ``` -Then, when one runs the script with `./mk/debug-test.sh`, it will drop them into GDB once the script reaches that point: +Then, running the test with `./mk/debug-test.sh` will drop you into GDB once the script reaches that point: ```shell-session $ ./mk/debug-test.sh tests/${testName}.sh @@ -171,7 +171,7 @@ GNU gdb (GDB) 12.1 ``` One can debug the Nix invocation in all the usual ways. -(For exampling running `run` will run the Nix invocation.) +For example, enter `run` to start the Nix invocation. ### Integration tests From bcc09902725350c8f77d4c9051c5c575e82a5a64 Mon Sep 17 00:00:00 2001 From: Sergei Trofimovich Date: Sat, 24 Dec 2022 09:14:09 +0000 Subject: [PATCH 236/245] tests: switch to non-deprecated nix.settings.* module parameters Without the change checks issue the fllowing warning: $ nix flake check trace: warning: The option `nix.useSandbox' defined in `makeTest parameters' has been renamed to `nix.settings.sandbox'. trace: warning: The option `nix.useSandbox' defined in `makeTest parameters' has been renamed to `nix.settings.sandbox'. trace: warning: The option `nix.maxJobs' defined in `makeTest parameters' has been renamed to `nix.settings.max-jobs'. ... --- tests/containers.nix | 2 +- tests/github-flakes.nix | 2 +- tests/nix-copy-closure.nix | 2 +- tests/nss-preload.nix | 6 +++--- tests/remote-builds.nix | 6 +++--- tests/setuid.nix | 2 +- tests/sourcehut-flakes.nix | 2 +- 7 files changed, 11 insertions(+), 11 deletions(-) diff --git a/tests/containers.nix b/tests/containers.nix index 59e953c3b..f31f22cf6 100644 --- a/tests/containers.nix +++ b/tests/containers.nix @@ -20,7 +20,7 @@ makeTest ({ (import ./systemd-nspawn.nix { inherit nixpkgs; }).toplevel ]; virtualisation.memorySize = 4096; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; nix.extraOptions = '' extra-experimental-features = nix-command auto-allocate-uids cgroups diff --git a/tests/github-flakes.nix b/tests/github-flakes.nix index 43a4f1432..a8b036b17 100644 --- a/tests/github-flakes.nix +++ b/tests/github-flakes.nix @@ -149,7 +149,7 @@ makeTest ( virtualisation.diskSize = 2048; virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ]; virtualisation.memorySize = 4096; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; nix.extraOptions = "experimental-features = nix-command flakes"; networking.hosts.${(builtins.head nodes.github.config.networking.interfaces.eth1.ipv4.addresses).address} = [ "channels.nixos.org" "api.github.com" "github.com" ]; diff --git a/tests/nix-copy-closure.nix b/tests/nix-copy-closure.nix index ba8b2cfc9..2dc164ae4 100644 --- a/tests/nix-copy-closure.nix +++ b/tests/nix-copy-closure.nix @@ -15,7 +15,7 @@ makeTest (let pkgA = pkgs.cowsay; pkgB = pkgs.wget; pkgC = pkgs.hello; pkgD = pk { config, lib, pkgs, ... }: { virtualisation.writableStore = true; virtualisation.additionalPaths = [ pkgA pkgD.drvPath ]; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; }; server = diff --git a/tests/nss-preload.nix b/tests/nss-preload.nix index 64b655ba2..5a6ff3f68 100644 --- a/tests/nss-preload.nix +++ b/tests/nss-preload.nix @@ -98,9 +98,9 @@ rec { { address = "192.168.0.10"; prefixLength = 24; } ]; - nix.sandboxPaths = lib.mkForce []; - nix.binaryCaches = lib.mkForce []; - nix.useSandbox = lib.mkForce true; + nix.settings.extra-sandbox-paths = lib.mkForce []; + nix.settings.substituters = lib.mkForce []; + nix.settings.sandbox = lib.mkForce true; }; }; diff --git a/tests/remote-builds.nix b/tests/remote-builds.nix index 7b2e6f708..9f88217fe 100644 --- a/tests/remote-builds.nix +++ b/tests/remote-builds.nix @@ -16,7 +16,7 @@ let { config, pkgs, ... }: { services.openssh.enable = true; virtualisation.writableStore = true; - nix.useSandbox = true; + nix.settings.sandbox = true; }; # Trivial Nix expression to build remotely. @@ -44,7 +44,7 @@ in client = { config, lib, pkgs, ... }: - { nix.maxJobs = 0; # force remote building + { nix.settings.max-jobs = 0; # force remote building nix.distributedBuilds = true; nix.buildMachines = [ { hostName = "builder1"; @@ -62,7 +62,7 @@ in ]; virtualisation.writableStore = true; virtualisation.additionalPaths = [ config.system.build.extraUtils ]; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; programs.ssh.extraConfig = "ConnectTimeout 30"; }; }; diff --git a/tests/setuid.nix b/tests/setuid.nix index a83b1fc3a..82efd6d54 100644 --- a/tests/setuid.nix +++ b/tests/setuid.nix @@ -13,7 +13,7 @@ makeTest { nodes.machine = { config, lib, pkgs, ... }: { virtualisation.writableStore = true; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; nix.nixPath = [ "nixpkgs=${lib.cleanSource pkgs.path}" ]; virtualisation.additionalPaths = [ pkgs.stdenv pkgs.pkgsi686Linux.stdenv ]; }; diff --git a/tests/sourcehut-flakes.nix b/tests/sourcehut-flakes.nix index daa259dd6..b77496ab6 100644 --- a/tests/sourcehut-flakes.nix +++ b/tests/sourcehut-flakes.nix @@ -108,7 +108,7 @@ makeTest ( virtualisation.diskSize = 2048; virtualisation.additionalPaths = [ pkgs.hello pkgs.fuse ]; virtualisation.memorySize = 4096; - nix.binaryCaches = lib.mkForce [ ]; + nix.settings.substituters = lib.mkForce [ ]; nix.extraOptions = '' experimental-features = nix-command flakes flake-registry = https://git.sr.ht/~NixOS/flake-registry/blob/master/flake-registry.json From a6e9d9cb2f1738a4e713806e9a80438bf716c272 Mon Sep 17 00:00:00 2001 From: Philipp Otterbein Date: Sat, 24 Dec 2022 12:09:06 +0100 Subject: [PATCH 237/245] remove function makeImmutableStringWithLen --- src/libexpr/eval.cc | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/libexpr/eval.cc b/src/libexpr/eval.cc index aee0636b0..da17cca80 100644 --- a/src/libexpr/eval.cc +++ b/src/libexpr/eval.cc @@ -67,22 +67,19 @@ static char * dupString(const char * s) // When there's no need to write to the string, we can optimize away empty // string allocations. -// This function handles makeImmutableStringWithLen(null, 0) by returning the -// empty string. -static const char * makeImmutableStringWithLen(const char * s, size_t size) +// This function handles makeImmutableString(std::string_view()) by returning +// the empty string. +static const char * makeImmutableString(std::string_view s) { + const size_t size = s.size(); if (size == 0) return ""; auto t = allocString(size + 1); - memcpy(t, s, size); - t[size] = 0; + memcpy(t, s.data(), size); + t[size] = '\0'; return t; } -static inline const char * makeImmutableString(std::string_view s) { - return makeImmutableStringWithLen(s.data(), s.size()); -} - RootValue allocRootValue(Value * v) { From 8af839f48c795370df704c8dd40544c88950c1ed Mon Sep 17 00:00:00 2001 From: Philipp Otterbein Date: Sat, 24 Dec 2022 12:19:53 +0100 Subject: [PATCH 238/245] remove undefined function --- src/libexpr/eval.hh | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libexpr/eval.hh b/src/libexpr/eval.hh index cf307d820..983491a31 100644 --- a/src/libexpr/eval.hh +++ b/src/libexpr/eval.hh @@ -60,7 +60,6 @@ void copyContext(const Value & v, PathSet & context); typedef std::map SrcToStore; -std::ostream & printValue(const EvalState & state, std::ostream & str, const Value & v); std::string printValue(const EvalState & state, const Value & v); std::ostream & operator << (std::ostream & os, const ValueType t); From aba6eb348e0ed8177da1e7f1df46ba00d20eab96 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Mon, 19 Dec 2022 14:06:07 +0100 Subject: [PATCH 239/245] libstore: Make sure that initNix has been called Prevent bugs like https://github.com/cachix/cachix/pull/477 --- src/libexpr/tests/libexprtests.hh | 1 + src/libmain/shared.cc | 1 + src/libstore/globals.cc | 14 ++++++++++++++ src/libstore/globals.hh | 8 ++++++++ src/libstore/store-api.cc | 1 + 5 files changed, 25 insertions(+) diff --git a/src/libexpr/tests/libexprtests.hh b/src/libexpr/tests/libexprtests.hh index 4f6915882..4a03d4316 100644 --- a/src/libexpr/tests/libexprtests.hh +++ b/src/libexpr/tests/libexprtests.hh @@ -12,6 +12,7 @@ namespace nix { class LibExprTest : public ::testing::Test { public: static void SetUpTestSuite() { + initLibStore(); initGC(); } diff --git a/src/libmain/shared.cc b/src/libmain/shared.cc index a58428762..99e3d6f56 100644 --- a/src/libmain/shared.cc +++ b/src/libmain/shared.cc @@ -235,6 +235,7 @@ void initNix() #endif preloadNSS(); + initLibStore(); } diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index b7f55cae7..130c5b670 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -291,4 +291,18 @@ void initPlugins() settings.pluginFiles.pluginsLoaded = true; } +static bool initLibStoreDone = false; + +void assertLibStoreInitialized() { + if (!initLibStoreDone) { + printError("The program must call nix::initNix() before calling any libstore library functions."); + abort(); + }; +} + +void initLibStore() { + initLibStoreDone = true; +} + + } diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 274a15dd7..22458efcf 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -984,4 +984,12 @@ std::vector getUserConfigFiles(); extern const std::string nixVersion; +/* NB: This is not sufficient. You need to call initNix() */ +void initLibStore(); + +/* It's important to initialize before doing _anything_, which is why we + call upon the programmer to handle this correctly. However, we only add + this in a key locations, so as not to litter the code. */ +void assertLibStoreInitialized(); + } diff --git a/src/libstore/store-api.cc b/src/libstore/store-api.cc index 80b60ca1b..426230ca5 100644 --- a/src/libstore/store-api.cc +++ b/src/libstore/store-api.cc @@ -458,6 +458,7 @@ Store::Store(const Params & params) : StoreConfig(params) , state({(size_t) pathInfoCacheSize}) { + assertLibStoreInitialized(); } From 336908cf4ccb9708398f4bdc398e90c02a400a04 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Thu, 11 Nov 2021 10:31:19 +0100 Subject: [PATCH 240/245] Optimize intersectAttrs performance Always traverse the shortest set. --- src/libexpr/primops.cc | 62 +++++++++++++++++++++++-- tests/lang/eval-okay-intersectAttrs.exp | 1 + tests/lang/eval-okay-intersectAttrs.nix | 50 ++++++++++++++++++++ 3 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 tests/lang/eval-okay-intersectAttrs.exp create mode 100644 tests/lang/eval-okay-intersectAttrs.nix diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 7efe50324..983cdfe8b 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -2448,12 +2448,62 @@ static void prim_intersectAttrs(EvalState & state, const PosIdx pos, Value * * a state.forceAttrs(*args[0], pos); state.forceAttrs(*args[1], pos); - auto attrs = state.buildBindings(std::min(args[0]->attrs->size(), args[1]->attrs->size())); + Bindings &left = *args[0]->attrs; + Bindings &right = *args[1]->attrs; - for (auto & i : *args[0]->attrs) { - Bindings::iterator j = args[1]->attrs->find(i.name); - if (j != args[1]->attrs->end()) - attrs.insert(*j); + auto attrs = state.buildBindings(std::min(left.size(), right.size())); + + // The current implementation has good asymptotic complexity and is reasonably + // simple. Further optimization may be possible, but does not seem productive, + // considering the state of eval performance in 2022. + // + // I have looked for reusable and/or standard solutions and these are my + // findings: + // + // STL + // === + // std::set_intersection is not suitable, as it only performs a simultaneous + // linear scan; not taking advantage of random access. This is O(n + m), so + // linear in the largest set, which is not acceptable for callPackage in Nixpkgs. + // + // Simultaneous scan, with alternating simple binary search + // === + // One alternative algorithm scans the attrsets simultaneously, jumping + // forward using `lower_bound` in case of inequality. This should perform + // well on very similar sets, having a local and predictable access pattern. + // On dissimilar sets, it seems to need more comparisons than the current + // algorithm, as few consecutive attrs match. `lower_bound` could take + // advantage of the decreasing remaining search space, but this causes + // the medians to move, which can mean that they don't stay in the cache + // like they would with the current naive `find`. + // + // Double binary search + // === + // The optimal algorithm may be "Double binary search", which doesn't + // scan at all, but rather divides both sets simultaneously. + // See "Fast Intersection Algorithms for Sorted Sequences" by Baeza-Yates et al. + // https://cs.uwaterloo.ca/~ajsaling/papers/intersection_alg_app10.pdf + // The only downsides I can think of are not having a linear access pattern + // for similar sets, and having to maintain a more intricate algorithm. + // + // Adaptive + // === + // Finally one could run try a simultaneous scan, count misses and fall back + // to double binary search when the counter hit some threshold and/or ratio. + + if (left.size() < right.size()) { + for (auto & l : left) { + Bindings::iterator r = right.find(l.name); + if (r != right.end()) + attrs.insert(*r); + } + } + else { + for (auto & r : right) { + Bindings::iterator l = left.find(r.name); + if (l != left.end()) + attrs.insert(r); + } } v.mkAttrs(attrs.alreadySorted()); @@ -2465,6 +2515,8 @@ static RegisterPrimOp primop_intersectAttrs({ .doc = R"( Return a set consisting of the attributes in the set *e2* which have the same name as some attribute in *e1*. + + Performs in O(*n* log *m*) where *n* is the size of the smaller set and *m* the larger set's size. )", .fun = prim_intersectAttrs, }); diff --git a/tests/lang/eval-okay-intersectAttrs.exp b/tests/lang/eval-okay-intersectAttrs.exp new file mode 100644 index 000000000..50445bc0e --- /dev/null +++ b/tests/lang/eval-okay-intersectAttrs.exp @@ -0,0 +1 @@ +[ { } { a = 1; } { a = 1; } { a = "a"; } { m = 1; } { m = "m"; } { n = 1; } { n = "n"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { n = 1; p = 2; } { n = "n"; p = "p"; } { a = "a"; b = "b"; c = "c"; d = "d"; e = "e"; f = "f"; g = "g"; h = "h"; i = "i"; j = "j"; k = "k"; l = "l"; m = "m"; n = "n"; o = "o"; p = "p"; q = "q"; r = "r"; s = "s"; t = "t"; u = "u"; v = "v"; w = "w"; x = "x"; y = "y"; z = "z"; } true ] diff --git a/tests/lang/eval-okay-intersectAttrs.nix b/tests/lang/eval-okay-intersectAttrs.nix new file mode 100644 index 000000000..39d49938c --- /dev/null +++ b/tests/lang/eval-okay-intersectAttrs.nix @@ -0,0 +1,50 @@ +let + alphabet = + { a = "a"; + b = "b"; + c = "c"; + d = "d"; + e = "e"; + f = "f"; + g = "g"; + h = "h"; + i = "i"; + j = "j"; + k = "k"; + l = "l"; + m = "m"; + n = "n"; + o = "o"; + p = "p"; + q = "q"; + r = "r"; + s = "s"; + t = "t"; + u = "u"; + v = "v"; + w = "w"; + x = "x"; + y = "y"; + z = "z"; + }; + foo = { + inherit (alphabet) f o b a r z q u x; + aa = throw "aa"; + }; + alphabetFail = builtins.mapAttrs throw alphabet; +in +[ (builtins.intersectAttrs { a = abort "l1"; } { b = abort "r1"; }) + (builtins.intersectAttrs { a = abort "l2"; } { a = 1; }) + (builtins.intersectAttrs alphabetFail { a = 1; }) + (builtins.intersectAttrs { a = abort "laa"; } alphabet) + (builtins.intersectAttrs alphabetFail { m = 1; }) + (builtins.intersectAttrs { m = abort "lam"; } alphabet) + (builtins.intersectAttrs alphabetFail { n = 1; }) + (builtins.intersectAttrs { n = abort "lan"; } alphabet) + (builtins.intersectAttrs alphabetFail { n = 1; p = 2; }) + (builtins.intersectAttrs { n = abort "lan2"; p = abort "lap"; } alphabet) + (builtins.intersectAttrs alphabetFail { n = 1; p = 2; }) + (builtins.intersectAttrs { n = abort "lan2"; p = abort "lap"; } alphabet) + (builtins.intersectAttrs alphabetFail alphabet) + (builtins.intersectAttrs alphabet foo == builtins.intersectAttrs foo alphabet) +] From 81c3f99b3668a1ea6a37792a5bcc3bc6f39729a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Sat, 17 Dec 2022 18:31:00 +0100 Subject: [PATCH 241/245] Release shared lock before acquiring exclusive lock In principle, this should avoid deadlocks where two instances of Nix are holding a shared lock on big-lock and are both waiting to get an exclusive lock. However, it seems like `flock(2)` is supposed to do this automatically, so it's not clear whether this is actually where the problem comes from. --- src/libstore/local-store.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libstore/local-store.cc b/src/libstore/local-store.cc index b67668e52..3bab10af9 100644 --- a/src/libstore/local-store.cc +++ b/src/libstore/local-store.cc @@ -91,6 +91,7 @@ void migrateCASchema(SQLite& db, Path schemaPath, AutoCloseFD& lockFd) if (!lockFile(lockFd.get(), ltWrite, false)) { printInfo("waiting for exclusive access to the Nix store for ca drvs..."); + lockFile(lockFd.get(), ltNone, false); // We have acquired a shared lock; release it to prevent deadlocks lockFile(lockFd.get(), ltWrite, true); } @@ -299,6 +300,7 @@ LocalStore::LocalStore(const Params & params) if (!lockFile(globalLock.get(), ltWrite, false)) { printInfo("waiting for exclusive access to the Nix store..."); + lockFile(globalLock.get(), ltNone, false); // We have acquired a shared lock; release it to prevent deadlocks lockFile(globalLock.get(), ltWrite, true); } From d5d2f50ebbe5ec2b8a9777d4184eb3e604a1f8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Na=C3=AFm=20Favier?= Date: Wed, 28 Dec 2022 17:09:20 +0100 Subject: [PATCH 242/245] doc: sandbox-paths computes closures --- src/libstore/globals.hh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 274a15dd7..f4d53757c 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -329,7 +329,7 @@ public: Whether to execute builds inside cgroups. This is only supported on Linux. - Cgroups are required and enabled automatically for derivations + Cgroups are required and enabled automatically for derivations that require the `uid-range` system feature. > **Warning** @@ -491,6 +491,9 @@ public: for example, `/dev/nvidiactl?` specifies that `/dev/nvidiactl` will only be mounted in the sandbox if it exists in the host filesystem. + If the source is in the Nix store, then its closure will be added to + the sandbox as well. + Depending on how Nix was built, the default value for this option may be empty or provide `/bin/sh` as a bind-mount of `bash`. )", From 84b08937259745ca743c0b305fc63ab1e1a20d83 Mon Sep 17 00:00:00 2001 From: Steven Shaw Date: Sun, 1 Jan 2023 12:37:43 +1000 Subject: [PATCH 243/245] Fix error message --- src/libstore/builtins/buildenv.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libstore/builtins/buildenv.cc b/src/libstore/builtins/buildenv.cc index 47458a388..b1fbda13d 100644 --- a/src/libstore/builtins/buildenv.cc +++ b/src/libstore/builtins/buildenv.cc @@ -95,7 +95,7 @@ static void createLinks(State & state, const Path & srcDir, const Path & dstDir, throw Error( "files '%1%' and '%2%' have the same priority %3%; " "use 'nix-env --set-flag priority NUMBER INSTALLED_PKGNAME' " - "or type 'nix profile install --help' if using 'nix profile' to find out how" + "or type 'nix profile install --help' if using 'nix profile' to find out how " "to change the priority of one of the conflicting packages" " (0 being the highest priority)", srcFile, readLink(dstFile), priority); From 9cdf8ededb2049833213bf498b27ed9537358707 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Mon, 2 Jan 2023 13:37:59 +0100 Subject: [PATCH 244/245] remove redundant re-definition of store derivations --- src/nix/nix.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/nix/nix.md b/src/nix/nix.md index 9641cccb2..db60c59ff 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -115,8 +115,7 @@ the Nix store. Here are the recognised types of installables: * **Store derivations**: `/nix/store/p7gp6lxdg32h4ka1q398wd9r2zkbbz2v-hello-2.10.drv` - Store derivations are a low-level, internal representation of build tasks in Nix, and have store paths with extension `.drv`. - By default, if you pass the path to a store derivation to a `nix` subcommand, the command will operate on the [output path]s of the derivation. + By default, if you pass a [store derivation] path to a `nix` subcommand, the command will operate on the [output path]s of the derivation. [output path]: ../../glossary.md#gloss-output-path From e0c4a95611db4fde942b4e3ee4b6f8e07f956248 Mon Sep 17 00:00:00 2001 From: Valentin Gagarin Date: Wed, 9 Nov 2022 00:31:48 +0100 Subject: [PATCH 245/245] antiquotation -> string interpolation as proposed by @mkaito[1] and @tazjin[2] and discussed with @edolstra and Nix maintainers [1]: https://github.com/NixOS/nix.dev/pull/267#issuecomment-1270076332 [2]: https://github.com/NixOS/nix.dev/pull/267#issuecomment-1270201979 Co-authored-by: John Ericson Co-authored-by: Eelco Dolstra --- doc/manual/src/SUMMARY.md.in | 1 + doc/manual/src/glossary.md | 11 +- .../src/language/string-interpolation.md | 82 ++++++++++++++ doc/manual/src/language/values.md | 101 +++++++----------- doc/manual/src/release-notes/rl-next.md | 17 +-- src/libexpr/primops.cc | 4 +- tests/lang/eval-okay-ind-string.nix | 2 +- 7 files changed, 136 insertions(+), 82 deletions(-) create mode 100644 doc/manual/src/language/string-interpolation.md diff --git a/doc/manual/src/SUMMARY.md.in b/doc/manual/src/SUMMARY.md.in index 6a514fa2c..24dbb739f 100644 --- a/doc/manual/src/SUMMARY.md.in +++ b/doc/manual/src/SUMMARY.md.in @@ -29,6 +29,7 @@ - [Nix Language](language/index.md) - [Data Types](language/values.md) - [Language Constructs](language/constructs.md) + - [String interpolation](language/string-interpolation.md) - [Operators](language/operators.md) - [Derivations](language/derivations.md) - [Advanced Attributes](language/advanced-attributes.md) diff --git a/doc/manual/src/glossary.md b/doc/manual/src/glossary.md index 0fcc2b07b..e63f6becc 100644 --- a/doc/manual/src/glossary.md +++ b/doc/manual/src/glossary.md @@ -60,7 +60,7 @@ cache](https://cache.nixos.org). - [store path]{#gloss-store-path}\ - The location in the file system of a store object, i.e., an + The location of a [store object] in the file system, i.e., an immediate child of the Nix store directory. Example: `/nix/store/a040m110amc4h71lds2jmr8qrkj2jhxd-git-2.38.1` @@ -178,3 +178,12 @@ - [`ε`]{#gloss-epsilon}\ The epsilon symbol. In the context of a package, this means the version is empty. More precisely, the derivation does not have a version attribute. + + - [string interpolation]{#gloss-string-interpolation}\ + Expanding expressions enclosed in `${ }` within a [string], [path], or [attribute name]. + + See [String interpolation](./language/string-interpolation.md) for details. + + [string]: ./language/values.md#type-string + [path]: ./language/values.md#type-path + [attribute name]: ./language/values.md#attribute-set diff --git a/doc/manual/src/language/string-interpolation.md b/doc/manual/src/language/string-interpolation.md new file mode 100644 index 000000000..ddc6b8230 --- /dev/null +++ b/doc/manual/src/language/string-interpolation.md @@ -0,0 +1,82 @@ +# String interpolation + +String interpolation is a language feature where a [string], [path], or [attribute name] can contain expressions enclosed in `${ }` (dollar-sign with curly brackets). + +Such a string is an *interpolated string*, and an expression inside is an *interpolated expression*. + +Interpolated expressions must evaluate to one of the following: + +- a [string] +- a [path] +- a [derivation] + +[string]: ./values.md#type-string +[path]: ./values.md#type-path +[attribute name]: ./values.md#attribute-set +[derivation]: ../glossary.md#gloss-derivation + +## Examples + +### String + +Rather than writing + +```nix +"--with-freetype2-library=" + freetype + "/lib" +``` + +(where `freetype` is a [derivation]), you can instead write + +```nix +"--with-freetype2-library=${freetype}/lib" +``` + +The latter is automatically translated to the former. + +A more complicated example (from the Nix expression for [Qt](http://www.trolltech.com/products/qt)): + +```nix +configureFlags = " + -system-zlib -system-libpng -system-libjpeg + ${if openglSupport then "-dlopen-opengl + -L${mesa}/lib -I${mesa}/include + -L${libXmu}/lib -I${libXmu}/include" else ""} + ${if threadSupport then "-thread" else "-no-thread"} +"; +``` + +Note that Nix expressions and strings can be arbitrarily nested; +in this case the outer string contains various interpolated expressions that themselves contain strings (e.g., `"-thread"`), some of which in turn contain interpolated expressions (e.g., `${mesa}`). + +### Path + +Rather than writing + +```nix +./. + "/" + foo + "-" + bar + ".nix" +``` + +or + +```nix +./. + "/${foo}-${bar}.nix" +``` + +you can instead write + +```nix +./${foo}-${bar}.nix +``` + +### Attribute name + +Attribute names can be created dynamically with string interpolation: + +```nix +let name = "foo"; in +{ + ${name} = "bar"; +} +``` + + { foo = "bar"; } diff --git a/doc/manual/src/language/values.md b/doc/manual/src/language/values.md index 6fc8c0369..08baa8f95 100644 --- a/doc/manual/src/language/values.md +++ b/doc/manual/src/language/values.md @@ -13,41 +13,9 @@ returns and tabs can be written as `\n`, `\r` and `\t`, respectively. - You can include the result of an expression into a string by - enclosing it in `${...}`, a feature known as *antiquotation*. The - enclosed expression must evaluate to something that can be coerced - into a string (meaning that it must be a string, a path, or a - derivation). For instance, rather than writing + You can include the results of other expressions into a string by enclosing them in `${ }`, a feature known as [string interpolation]. - ```nix - "--with-freetype2-library=" + freetype + "/lib" - ``` - - (where `freetype` is a derivation), you can instead write the more - natural - - ```nix - "--with-freetype2-library=${freetype}/lib" - ``` - - The latter is automatically translated to the former. A more - complicated example (from the Nix expression for - [Qt](http://www.trolltech.com/products/qt)): - - ```nix - configureFlags = " - -system-zlib -system-libpng -system-libjpeg - ${if openglSupport then "-dlopen-opengl - -L${mesa}/lib -I${mesa}/include - -L${libXmu}/lib -I${libXmu}/include" else ""} - ${if threadSupport then "-thread" else "-no-thread"} - "; - ``` - - Note that Nix expressions and strings can be arbitrarily nested; in - this case the outer string contains various antiquotations that - themselves contain strings (e.g., `"-thread"`), some of which in - turn contain expressions (e.g., `${mesa}`). + [string interpolation]: ./string-interpolation.md The second way to write string literals is as an *indented string*, which is enclosed between pairs of *double single-quotes*, like so: @@ -75,7 +43,7 @@ Note that the whitespace and newline following the opening `''` is ignored if there is no non-whitespace text on the initial line. - Antiquotation (`${expr}`) is supported in indented strings. + Indented strings support [string interpolation]. Since `${` and `''` have special meaning in indented strings, you need a way to quote them. `$` can be escaped by prefixing it with @@ -143,26 +111,23 @@ environment variable `NIX_PATH` will be searched for the given file or directory name. - Antiquotation is supported in any paths except those in angle brackets. - `./${foo}-${bar}.nix` is a more convenient way of writing - `./. + "/" + foo + "-" + bar + ".nix"` or `./. + "/${foo}-${bar}.nix"`. At - least one slash must appear *before* any antiquotations for this to be - recognized as a path. `a.${foo}/b.${bar}` is a syntactically valid division - operation. `./a.${foo}/b.${bar}` is a path. + When an [interpolated string][string interpolation] evaluates to a path, the path is first copied into the Nix store and the resulting string is the [store path] of the newly created [store object]. - When a path appears in an antiquotation, and is thus coerced into a string, - the path is first copied into the Nix store and the resulting string is - the Nix store path. For instance `"${./foo.txt}" will cause `foo.txt` in - the current directory to be copied into the Nix store and result in the - string `"/nix/store/-foo.txt"`. + [store path]: ../glossary.md#gloss-store-path + [store object]: ../glossary.md#gloss-store-object - Note that the Nix language assumes that all input files will remain - _unchanged_ during the course of the Nix expression evaluation. - If you for example antiquote a file path during a `nix repl` session, and - then later in the same session, after having changed the file contents, - evaluate the antiquotation with the file path again, then Nix will still - return the first store path. It will _not_ reread the file contents to - produce a different Nix store path. + For instance, evaluating `"${./foo.txt}"` will cause `foo.txt` in the current directory to be copied into the Nix store and result in the string `"/nix/store/-foo.txt"`. + + Note that the Nix language assumes that all input files will remain _unchanged_ while evaluating a Nix expression. + For example, assume you used a file path in an interpolated string during a `nix repl` session. + Later in the same session, after having changed the file contents, evaluating the interpolated string with the file path again might not return a new store path, since Nix might not re-read the file contents. + + Paths themselves, except those in angle brackets (`< >`), support [string interpolation]. + + At least one slash (`/`) must appear *before* any interpolated expression for the result to be recognized as a path. + + `a.${foo}/b.${bar}` is a syntactically valid division operation. + `./a.${foo}/b.${bar}` is a path. - Boolean @@ -235,23 +200,33 @@ will evaluate to `"Xyzzy"` because there is no `c` attribute in the set. You can use arbitrary double-quoted strings as attribute names: ```nix -{ "foo ${bar}" = 123; "nix-1.0" = 456; }."foo ${bar}" +{ "$!@#?" = 123; }."$!@#?" ``` -This will evaluate to `123` (Assuming `bar` is antiquotable). In the -case where an attribute name is just a single antiquotation, the quotes -can be dropped: - ```nix -{ foo = 123; }.${bar} or 456 +let bar = "bar"; +{ "foo ${bar}" = 123; }."foo ${bar}" ``` -This will evaluate to `123` if `bar` evaluates to `"foo"` when coerced -to a string and `456` otherwise (again assuming `bar` is antiquotable). +Both will evaluate to `123`. + +Attribute names support [string interpolation]: + +```nix +let bar = "foo"; in +{ foo = 123; }.${bar} +``` + +```nix +let bar = "foo"; in +{ ${bar} = 123; }.foo +``` + +Both will evaluate to `123`. In the special case where an attribute name inside of a set declaration -evaluates to `null` (which is normally an error, as `null` is not -antiquotable), that attribute is simply not added to the set: +evaluates to `null` (which is normally an error, as `null` cannot be coerced to +a string), that attribute is simply not added to the set: ```nix { ${if foo then "bar" else null} = true; } diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 906b048f1..8119b7c31 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -15,19 +15,6 @@ # NIX_PATH=nixpkgs=flake:nixpkgs nix-build '' -A hello ``` -* Allow explicitly selecting outputs in a store derivation installable, just like we can do with other sorts of installables. - For example, - ```shell-session - $ nix-build /nix/store/gzaflydcr6sb3567hap9q6srzx8ggdgg-glibc-2.33-78.drv^dev` - ``` - now works just as - ```shell-session - $ nix-build glibc^dev` - ``` - does already. +* Instead of "antiquotation", the more common term [string interpolation](../language/string-interpolation.md) is now used consistently. + Historical release notes were not changed. -* On Linux, `nix develop` now sets the - [*personality*](https://man7.org/linux/man-pages/man2/personality.2.html) - for the development shell in the same way as the actual build of the - derivation. This makes shells for `i686-linux` derivations work - correctly on `x86_64-linux`. diff --git a/src/libexpr/primops.cc b/src/libexpr/primops.cc index 7efe50324..7cad041af 100644 --- a/src/libexpr/primops.cc +++ b/src/libexpr/primops.cc @@ -1930,8 +1930,8 @@ static RegisterPrimOp primop_toFile({ "; ``` - Note that `${configFile}` is an - [antiquotation](language-values.md), so the result of the + Note that `${configFile}` is a + [string interpolation](language/values.md#type-string), so the result of the expression `configFile` (i.e., a path like `/nix/store/m7p7jfny445k...-foo.conf`) will be spliced into the resulting string. diff --git a/tests/lang/eval-okay-ind-string.nix b/tests/lang/eval-okay-ind-string.nix index 1669dc064..95d59b508 100644 --- a/tests/lang/eval-okay-ind-string.nix +++ b/tests/lang/eval-okay-ind-string.nix @@ -110,7 +110,7 @@ let And finally to interpret \n etc. as in a string: ''\n, ''\r, ''\t. ''; - # Regression test: antiquotation in '${x}' should work, but didn't. + # Regression test: string interpolation in '${x}' should work, but didn't. s15 = let x = "bla"; in '' foo '${x}'