diff --git a/doc/manual/rl-next/drop-recursive-nix.md b/doc/manual/rl-next/drop-recursive-nix.md new file mode 100644 index 000000000..a4b79f725 --- /dev/null +++ b/doc/manual/rl-next/drop-recursive-nix.md @@ -0,0 +1,23 @@ +--- +synopsis: "Removal of the `recursive-nix` experimental feature" +issues: [fj#767] +cls: [2872] +category: "Breaking Changes" +credits: ["raito"] +--- + +The `recursive-nix` experimental feature and all associated code have been removed. + +`recursive-nix` enabled running Nix operations (like evaluations and builds) *inside* a derivation builder. This worked by spawning a temporary Nix daemon socket within the build environment, allowing the derivation to emit outputs that appeared in the outer store. This was primarily used to prototype **dynamic derivations** (dyndrvs), where build plans are generated on-the-fly during a build. + +However, this approach introduced critical issues: + +- It entrenched the legacy Nix daemon protocol as part of the derivation ABI, which is a blocker for future stabilization. +- It imposed tight coupling between sandbox setup code and knowledge of Nix internals, complicating refactoring and long-term maintenance. +- It was never intended to be the final design for dynamic derivations. The original Nix implementation team, who are leading dyndrv development, have agreed it will be replaced (likely via `varlink` or similar) before any stabilization. +- There is currently no known usage of `recursive-nix` on `lix` or elsewhere **in production**. + +If you're using `recursive-nix` for something niche or experimental, we'd love to hear from you on the RFD issue. +You can still run `nix` inside a builder manually if needed — including with isolated user namespaces and fake stores — but the special daemon-handshake machinery is gone. + +This removal unblocks several important internal cleanups. diff --git a/lix/libstore/build/local-derivation-goal.cc b/lix/libstore/build/local-derivation-goal.cc index faac8084b..628948dab 100644 --- a/lix/libstore/build/local-derivation-goal.cc +++ b/lix/libstore/build/local-derivation-goal.cc @@ -110,7 +110,6 @@ LocalDerivationGoal::~LocalDerivationGoal() noexcept(false) /* Careful: we should never ever throw an exception from a destructor. */ try { killChild(); } catch (...) { ignoreExceptionInDestructor(); } - try { stopDaemon(); } catch (...) { ignoreExceptionInDestructor(); } try { deleteTmpDir(false, true); } catch (...) { ignoreExceptionInDestructor(); } } @@ -330,8 +329,6 @@ void LocalDerivationGoal::cleanupHookFinally() void LocalDerivationGoal::cleanupPreChildKill() { - sandboxMountNamespace.reset(); - sandboxUserNamespace.reset(); } @@ -343,9 +340,6 @@ void LocalDerivationGoal::cleanupPostChildKill() open and modifies them after they have been chown'ed to root. */ killSandbox(true); - - /* Terminate the recursive Nix daemon. */ - stopDaemon(); } @@ -734,11 +728,6 @@ try { } } - /* Fire up a Nix daemon to process recursive Nix calls from the - builder. */ - if (parsedDrv->getRequiredSystemFeatures().count("recursive-nix")) - startDaemon(); - /* Run the builder. */ printMsg(lvlChatty, "executing builder '%1%'", drv->builder); printMsg(lvlChatty, "using builder args '%1%'", concatStringsSep(" ", drv->args)); @@ -979,486 +968,6 @@ try { co_return result::current_exception(); } - -static StorePath pathPartOfReq(const SingleDerivedPath & req) -{ - return std::visit(overloaded { - [&](const SingleDerivedPath::Opaque & bo) { - return bo.path; - }, - [&](const SingleDerivedPath::Built & bfd) { - return pathPartOfReq(*bfd.drvPath); - }, - }, req.raw()); -} - - -static StorePath pathPartOfReq(const DerivedPath & req) -{ - return std::visit(overloaded { - [&](const DerivedPath::Opaque & bo) { - return bo.path; - }, - [&](const DerivedPath::Built & bfd) { - return pathPartOfReq(*bfd.drvPath); - }, - }, req.raw()); -} - - -bool LocalDerivationGoal::isAllowed(const DerivedPath & req) -{ - return this->isAllowed(pathPartOfReq(req)); -} - - -struct RestrictedStoreConfig final : LocalFSStoreConfig -{ - using LocalFSStoreConfig::LocalFSStoreConfig; - const std::string name() override { return "Restricted Store"; } -}; - -/* A wrapper around LocalStore that only allows building/querying of - paths that are in the input closures of the build or were added via - recursive Nix calls. */ -struct RestrictedStore : public virtual IndirectRootStore, public virtual GcStore -{ - RestrictedStoreConfig config_; - - RestrictedStoreConfig & config() override { return config_; } - const RestrictedStoreConfig & config() const override { return config_; } - - ref next; - - LocalDerivationGoal & goal; - - RestrictedStore(RestrictedStoreConfig config, ref next, LocalDerivationGoal & goal) - : Store(config) - , config_(std::move(config)) - , next(next), goal(goal) - { } - - Path getRealStoreDir() override - { return next->config().realStoreDir; } - - std::string getUri() override - { return next->getUri(); } - - kj::Promise> queryAllValidPaths() override - try { - StorePathSet paths; - for (auto & p : goal.inputPaths) paths.insert(p); - for (auto & p : goal.addedPaths) paths.insert(p); - co_return paths; - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise>> - queryPathInfoUncached(const StorePath & path) override - try { - if (goal.isAllowed(path)) { - try { - /* Censor impure information. */ - auto info = std::make_shared(*TRY_AWAIT(next->queryPathInfo(path))); - info->deriver.reset(); - info->registrationTime = 0; - info->ultimate = false; - info->sigs.clear(); - co_return info; - } catch (InvalidPath &) { - co_return result::success(nullptr); - } - } else - co_return result::success(nullptr); - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise> - queryReferrers(const StorePath & path, StorePathSet & referrers) override - { return {result::success()}; } - - kj::Promise>>> - queryPartialDerivationOutputMap(const StorePath & path, Store * evalStore = nullptr) override - try { - if (!goal.isAllowed(path)) - throw InvalidPath("cannot query output map for unknown path '%s' in recursive Nix", printStorePath(path)); - co_return TRY_AWAIT(next->queryPartialDerivationOutputMap(path, evalStore)); - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise>> - queryPathFromHashPart(const std::string & hashPart) override - try { - throw Error("queryPathFromHashPart"); - } catch (...) { - return {result::current_exception()}; - } - - kj::Promise> addToStoreRecursive( - std::string_view name, - const PreparedDump & source, - HashType hashAlgo, - RepairFlag repair) override - try { throw Error("addToStoreRecursive"); } catch (...) { return {result::current_exception()}; } - - kj::Promise> addToStoreFlat( - std::string_view name, - const Path & srcPath, - HashType hashAlgo, - RepairFlag repair) override - try { throw Error("addToStoreFlat"); } catch (...) { return {result::current_exception()}; } - - kj::Promise> addToStore(const ValidPathInfo & info, AsyncInputStream & narSource, - RepairFlag repair = NoRepair, CheckSigsFlag checkSigs = CheckSigs) override - try { - TRY_AWAIT(next->addToStore(info, narSource, repair, checkSigs)); - goal.addDependency(info.path); - co_return result::success(); - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise> addTextToStore( - std::string_view name, - std::string_view s, - const StorePathSet & references, - RepairFlag repair = NoRepair) override - try { - auto path = TRY_AWAIT(next->addTextToStore(name, s, references, repair)); - goal.addDependency(path); - co_return path; - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise> addToStoreFromDump( - AsyncInputStream & dump, - std::string_view name, - FileIngestionMethod method, - HashType hashAlgo, - RepairFlag repair, - const StorePathSet & references) override - try { - auto path = TRY_AWAIT(next->addToStoreFromDump(dump, name, method, hashAlgo, repair, references)); - goal.addDependency(path); - co_return path; - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise>> narFromPath(const StorePath & path) override - try { - if (!goal.isAllowed(path)) - throw InvalidPath("cannot dump unknown path '%s' in recursive Nix", printStorePath(path)); - co_return TRY_AWAIT(LocalFSStore::narFromPath(path)); - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise> ensurePath(const StorePath & path) override - try { - if (!goal.isAllowed(path)) - throw InvalidPath("cannot substitute unknown path '%s' in recursive Nix", printStorePath(path)); - /* Nothing to be done; 'path' must already be valid. */ - return {result::success()}; - } catch (...) { - return {result::current_exception()}; - } - - kj::Promise> registerDrvOutput(const Realisation & info) override - // XXX: This should probably be allowed as a no-op if the realisation - // corresponds to an allowed derivation - try { throw Error("registerDrvOutput"); } catch (...) { return {result::current_exception()}; } - - kj::Promise>> - queryRealisationUncached(const DrvOutput & id) override - // XXX: This should probably be allowed if the realisation corresponds to - // an allowed derivation - try { - if (!goal.isAllowed(id)) - co_return result::success(nullptr); - co_return TRY_AWAIT(next->queryRealisation(id)); - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise> buildPaths( - const std::vector & paths, - BuildMode buildMode, - std::shared_ptr evalStore - ) override - try { - for (auto & result : TRY_AWAIT(buildPathsWithResults(paths, buildMode, evalStore))) - if (!result.success()) - result.rethrow(); - co_return result::success(); - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise>> buildPathsWithResults( - const std::vector & paths, - BuildMode buildMode = bmNormal, - std::shared_ptr evalStore = nullptr) override - try { - assert(!evalStore); - - if (buildMode != bmNormal) throw Error("unsupported build mode"); - - StorePathSet newPaths; - std::set newRealisations; - - for (auto & req : paths) { - if (!goal.isAllowed(req)) - throw InvalidPath("cannot build '%s' in recursive Nix because path is unknown", req.to_string(*next)); - } - - auto results = TRY_AWAIT(next->buildPathsWithResults(paths, buildMode)); - - for (auto & result : results) { - for (auto & [outputName, output] : result.builtOutputs) { - newPaths.insert(output.outPath); - newRealisations.insert(output); - } - } - - StorePathSet closure; - TRY_AWAIT(next->computeFSClosure(newPaths, closure)); - for (auto & path : closure) - goal.addDependency(path); - for (auto & real : TRY_AWAIT(Realisation::closure(*next, newRealisations))) - goal.addedDrvOutputs.insert(real.id); - - co_return results; - } catch (...) { - co_return result::current_exception(); - } - - kj::Promise> buildDerivation( - const StorePath & drvPath, const BasicDerivation & drv, BuildMode buildMode = bmNormal - ) override - try { unsupported("buildDerivation"); } catch (...) { return {result::current_exception()}; } - - kj::Promise> addTempRoot(const StorePath & path) override - { return {result::success()}; } - - kj::Promise> addIndirectRoot(const Path & path) override - { return {result::success()}; } - - kj::Promise> findRoots(bool censor) override - { return {Roots()}; } - - kj::Promise> - collectGarbage(const GCOptions & options, GCResults & results) override - { - return {result::success()}; - } - - kj::Promise> - addSignatures(const StorePath & storePath, const StringSet & sigs) override - try { unsupported("addSignatures"); } catch (...) { return {result::current_exception()}; } - - kj::Promise> queryMissing(const std::vector & targets, - StorePathSet & willBuild, StorePathSet & willSubstitute, StorePathSet & unknown, - uint64_t & downloadSize, uint64_t & narSize) override - try { - /* This is slightly impure since it leaks information to the - client about what paths will be built/substituted or are - already present. Probably not a big deal. */ - - std::vector allowed; - for (auto & req : targets) { - if (goal.isAllowed(req)) - allowed.emplace_back(req); - else - unknown.insert(pathPartOfReq(req)); - } - - TRY_AWAIT(next->queryMissing(allowed, willBuild, willSubstitute, - unknown, downloadSize, narSize)); - co_return result::success(); - } catch (...) { - co_return result::current_exception(); - } - - virtual kj::Promise>> getBuildLogExact(const StorePath & path) override - { - return {std::nullopt}; - } - - virtual kj::Promise> - addBuildLog(const StorePath & path, std::string_view log) override - try { - unsupported("addBuildLog"); - } catch (...) { - return {result::current_exception()}; - } - - kj::Promise>> isTrustedClient() override - { return {result::success(NotTrusted)}; } -}; - - -void LocalDerivationGoal::startDaemon() -{ - experimentalFeatureSettings.require(Xp::RecursiveNix); - - StoreConfig::Params params; - params["path-info-cache-size"] = "0"; - params["store"] = worker.store.config().storeDir; - if (auto & optRoot = getLocalStore().config().rootDir.get()) - params["root"] = *optRoot; - params["state"] = "/no-such-path"; - params["log"] = "/no-such-path"; - auto store = make_ref( - params, - ref::unsafeFromPtr( - std::dynamic_pointer_cast(worker.store.shared_from_this()) - ), - *this - ); - - addedPaths.clear(); - - auto socketName = ".nix-socket"; - Path socketPath = tmpDir + "/" + socketName; - env["NIX_REMOTE"] = "unix://" + tmpDirInSandbox + "/" + socketName; - - daemonSocket = createUnixDomainSocket(socketPath, 0600); - - chownToBuilder(socketPath); - - daemonThread = std::thread([this, store]() { - setCurrentThreadName("recursive nix daemon"); - - while (true) { - - /* Accept a connection. */ - struct sockaddr_un remoteAddr; - socklen_t remoteAddrLen = sizeof(remoteAddr); - - AutoCloseFD remote{accept(daemonSocket.get(), - reinterpret_cast(&remoteAddr), &remoteAddrLen)}; - if (!remote) { - if (errno == EINTR || errno == EAGAIN) continue; - if (errno == EINVAL || errno == ECONNABORTED) break; - throw SysError("accepting connection"); - } - - closeOnExec(remote.get()); - - debug("received daemon connection"); - - auto workerThread = std::thread([store, remote{std::move(remote)}]() { - setCurrentThreadName("recursive nix worker"); - FdSource from(remote.get()); - FdSink to(remote.get()); - try { - AsyncIoRoot aio; - daemon::processConnection(aio, store, from, to, - NotTrusted, daemon::Recursive); - debug("terminated daemon connection"); - } catch (SysError &) { - ignoreExceptionExceptInterrupt(); - } - }); - - daemonWorkerThreads.push_back(std::move(workerThread)); - } - - debug("daemon shutting down"); - }); -} - - -void LocalDerivationGoal::stopDaemon() -{ - if (daemonSocket && shutdown(daemonSocket.get(), SHUT_RDWR) == -1) { - // According to the POSIX standard, the 'shutdown' function should - // return an ENOTCONN error when attempting to shut down a socket that - // hasn't been connected yet. This situation occurs when the 'accept' - // function is called on a socket without any accepted connections, - // leaving the socket unconnected. While Linux doesn't seem to produce - // an error for sockets that have only been accepted, more - // POSIX-compliant operating systems like OpenBSD, macOS, and others do - // return the ENOTCONN error. Therefore, we handle this error here to - // avoid raising an exception for compliant behaviour. - if (errno == ENOTCONN) { - daemonSocket.close(); - } else { - throw SysError("shutting down daemon socket"); - } - } - - if (daemonThread.joinable()) - daemonThread.join(); - - // FIXME: should prune worker threads more quickly. - // FIXME: shutdown the client socket to speed up worker termination. - for (auto & thread : daemonWorkerThreads) - thread.join(); - daemonWorkerThreads.clear(); - - // release the socket. - daemonSocket.close(); -} - - -void LocalDerivationGoal::addDependency(const StorePath & path) -{ - if (isAllowed(path)) return; - - addedPaths.insert(path); - - /* If we're doing a sandbox build, then we have to make the path - appear in the sandbox. */ - if (useChroot) { - - debug("materialising '%s' in the sandbox", worker.store.printStorePath(path)); - - #if __linux__ - - Path source = worker.store.Store::toRealPath(path); - Path target = chrootRootDir + worker.store.printStorePath(path); - - if (pathExists(target)) { - // There is a similar debug message in bindPath, so only run it in this block to not have double messages. - debug("bind-mounting %s -> %s", target, source); - throw Error("store path '%s' already exists in the sandbox", worker.store.printStorePath(path)); - } - - /* Bind-mount the path into the sandbox. This requires - entering its mount namespace, which is not possible - in multithreaded programs. So we do this in a - child process.*/ - Pid child = startProcess([&]() { - - if (usingUserNamespace && (setns(sandboxUserNamespace.get(), 0) == -1)) - throw SysError("entering sandbox user namespace"); - - if (setns(sandboxMountNamespace.get(), 0) == -1) - throw SysError("entering sandbox mount namespace"); - - bindPath(source, target); - - _exit(0); - }); - - int status = child.wait(); - if (status != 0) - throw Error("could not add path '%s' to sandbox", worker.store.printStorePath(path)); - - #else - throw Error("don't know how to make path '%s' (produced by a recursive Nix call) appear in the sandbox", - worker.store.printStorePath(path)); - #endif - - } -} - void LocalDerivationGoal::chownToBuilder(const Path & path) { if (!buildUser) return; @@ -1692,6 +1201,10 @@ void LocalDerivationGoal::runChild() if (!parsedDrv->useUidRange()) chmodPath(chrootRootDir + "/etc", 0555); + /* The comment below is now outdated. Recursive Nix has been removed. + * So there's no need to make path appear in the sandbox. + * TODO(Raito): cleanup before a merge. + */ /* Unshare this mount namespace. This is necessary because pivot_root() below changes the root of the mount namespace. This means that the call to setns() in @@ -2010,7 +1523,6 @@ try { StorePathSet referenceablePaths; for (auto & p : inputPaths) referenceablePaths.insert(p); for (auto & i : scratchOutputs) referenceablePaths.insert(i.second); - for (auto & p : addedPaths) referenceablePaths.insert(p); /* FIXME `needsHashRewrite` should probably be removed and we get to the real reason why we aren't using the chroot dir */ diff --git a/lix/libstore/build/local-derivation-goal.hh b/lix/libstore/build/local-derivation-goal.hh index 9bfd28f35..239574673 100644 --- a/lix/libstore/build/local-derivation-goal.hh +++ b/lix/libstore/build/local-derivation-goal.hh @@ -47,13 +47,6 @@ struct LocalDerivationGoal : public DerivationGoal */ Pipe userNamespaceSync; - /** - * The mount namespace and user namespace of the builder, used to add additional - * paths to the sandbox as a result of recursive Nix calls. - */ - AutoCloseFD sandboxMountNamespace; - AutoCloseFD sandboxUserNamespace; - /** * On Linux, whether we're doing the build in its own user * namespace. @@ -134,50 +127,11 @@ struct LocalDerivationGoal : public DerivationGoal const static Path homeDir; - /** - * The recursive Nix daemon socket. - */ - AutoCloseFD daemonSocket; - - /** - * The daemon main thread. - */ - std::thread daemonThread; - /** * The daemon worker threads. */ std::vector daemonWorkerThreads; - /** - * Paths that were added via recursive Nix calls. - */ - StorePathSet addedPaths; - - /** - * Realisations that were added via recursive Nix calls. - */ - std::set addedDrvOutputs; - - /** - * Recursive Nix calls are only allowed to build or realize paths - * in the original input closure or added via a recursive Nix call - * (so e.g. you can't do 'nix-store -r /nix/store/' where - * /nix/store/ is some arbitrary path in a binary cache). - */ - bool isAllowed(const StorePath & path) - { - return inputPaths.count(path) || addedPaths.count(path); - } - bool isAllowed(const DrvOutput & id) - { - return addedDrvOutputs.count(id); - } - - bool isAllowed(const DerivedPath & req); - - friend struct RestrictedStore; - /** * Create a LocalDerivationGoal without an on-disk .drv file, * possibly a platform-specific subclass @@ -236,16 +190,6 @@ struct LocalDerivationGoal : public DerivationGoal */ kj::Promise> writeStructuredAttrs(); - void startDaemon(); - - void stopDaemon(); - - /** - * Add 'path' to the set of paths that may be referenced by the - * outputs, and make it appear in the sandbox. - */ - void addDependency(const StorePath & path); - /** * Make a file owned by the builder. */ diff --git a/lix/libstore/daemon.cc b/lix/libstore/daemon.cc index 15703e419..18f650770 100644 --- a/lix/libstore/daemon.cc +++ b/lix/libstore/daemon.cc @@ -264,7 +264,7 @@ struct ClientSettings }; static void performOp(AsyncIoRoot & aio, TunnelLogger * logger, ref store, - TrustedFlag trusted, RecursiveFlag recursive, WorkerProto::Version clientVersion, + TrustedFlag trusted, WorkerProto::Version clientVersion, Source & from, BufferedSink & to, WorkerProto::Op op) { WorkerProto::ReadConn rconn{from, clientVersion}; @@ -773,12 +773,7 @@ static void performOp(AsyncIoRoot & aio, TunnelLogger * logger, ref store } logger->startWork(); - - // FIXME: use some setting in recursive mode. Will need to use - // non-global variables. - if (!recursive) - clientSettings.apply(trusted); - + clientSettings.apply(trusted); logger->stopWork(); break; } @@ -1014,10 +1009,9 @@ void processConnection( ref store, FdSource & from, FdSink & to, - TrustedFlag trusted, - RecursiveFlag recursive) + TrustedFlag trusted) { - auto monitor = !recursive ? std::make_unique(from.fd) : nullptr; + auto monitor = std::make_unique(from.fd); /* Exchange the greeting. */ unsigned int magic = readInt(from); @@ -1031,9 +1025,7 @@ void processConnection( auto tunnelLogger = new TunnelLogger(to, clientVersion); auto prevLogger = nix::logger; - // FIXME - if (!recursive) - logger = tunnelLogger; + logger = tunnelLogger; unsigned int opCount = 0; @@ -1089,7 +1081,7 @@ void processConnection( debug("performing daemon worker op: %d", op); try { - performOp(aio, tunnelLogger, store, trusted, recursive, clientVersion, from, to, op); + performOp(aio, tunnelLogger, store, trusted, clientVersion, from, to, op); } catch (Error & e) { /* If we're not in a state where we can send replies, then something went wrong processing the input of the diff --git a/lix/libstore/daemon.hh b/lix/libstore/daemon.hh index fbc7180d2..6b8b8eb45 100644 --- a/lix/libstore/daemon.hh +++ b/lix/libstore/daemon.hh @@ -7,14 +7,11 @@ namespace nix::daemon { -enum RecursiveFlag : bool { NotRecursive = false, Recursive = true }; - void processConnection( AsyncIoRoot & aio, ref store, FdSource & from, FdSink & to, - TrustedFlag trusted, - RecursiveFlag recursive); + TrustedFlag trusted); } diff --git a/lix/libstore/platform/linux.cc b/lix/libstore/platform/linux.cc index d1217ccb4..02435e032 100644 --- a/lix/libstore/platform/linux.cc +++ b/lix/libstore/platform/linux.cc @@ -973,18 +973,6 @@ Pid LinuxLocalDerivationGoal::startChild(std::function openSlave) "nixbld:!:%1%:\n" "nogroup:x:65534:\n", sandboxGid())); - /* Save the mount- and user namespace of the child. We have to do this - *before* the child does a chroot. */ - sandboxMountNamespace = AutoCloseFD{open(fmt("/proc/%d/ns/mnt", pid.get()).c_str(), O_RDONLY)}; - if (sandboxMountNamespace.get() == -1) - throw SysError("getting sandbox mount namespace"); - - if (usingUserNamespace) { - sandboxUserNamespace = AutoCloseFD{open(fmt("/proc/%d/ns/user", pid.get()).c_str(), O_RDONLY)}; - if (sandboxUserNamespace.get() == -1) - throw SysError("getting sandbox user namespace"); - } - /* Move the child into its own cgroup. */ if (cgroup) writeFile(*cgroup + "/cgroup.procs", fmt("%d", pid.get())); diff --git a/lix/libstore/store-api.cc b/lix/libstore/store-api.cc index 059f5099c..1a6a9f93e 100644 --- a/lix/libstore/store-api.cc +++ b/lix/libstore/store-api.cc @@ -490,9 +490,6 @@ StringSet StoreConfig::getDefaultSystemFeatures() if (experimentalFeatureSettings.isEnabled(Xp::CaDerivations)) res.insert("ca-derivations"); - if (experimentalFeatureSettings.isEnabled(Xp::RecursiveNix)) - res.insert("recursive-nix"); - return res; } diff --git a/lix/libutil/experimental-features/recursive-nix.md b/lix/libutil/experimental-features/recursive-nix.md deleted file mode 100644 index fd67ed5aa..000000000 --- a/lix/libutil/experimental-features/recursive-nix.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -name: recursive-nix -internalName: RecursiveNix ---- -Allow derivation builders to call Nix, and thus build derivations -recursively. - -Example: - -``` -with import {}; - -runCommand "foo" - { - buildInputs = [ nix jq ]; - NIX_PATH = "nixpkgs=${}"; - } - '' - hello=$(nix-build -E '(import {}).hello.overrideDerivation (args: { name = "recursive-hello"; })') - - mkdir -p $out/bin - ln -s $hello/bin/hello $out/bin/hello - '' -``` - -An important restriction on recursive builders is disallowing -arbitrary substitutions. For example, running - -``` -nix-store -r /nix/store/kmwd1hq55akdb9sc7l3finr175dajlby-hello-2.10 -``` - -in the above `runCommand` script would be disallowed, as this could -lead to derivations with hidden dependencies or breaking -reproducibility by relying on the current state of the Nix store. An -exception would be if -`/nix/store/kmwd1hq55akdb9sc7l3finr175dajlby-hello-2.10` were -already in the build inputs or built by a previous recursive Nix -call. diff --git a/lix/libutil/meson.build b/lix/libutil/meson.build index 2b7214734..5d7a63dc5 100644 --- a/lix/libutil/meson.build +++ b/lix/libutil/meson.build @@ -154,7 +154,6 @@ experimental_feature_definitions = files( 'experimental-features/parse-toml-timestamps.md', 'experimental-features/pipe-operator.md', 'experimental-features/read-only-local-store.md', - 'experimental-features/recursive-nix.md', 'experimental-features/repl-automation.md', # keep-sorted end ) diff --git a/lix/nix/daemon.cc b/lix/nix/daemon.cc index d53ca88c4..a163e0c4b 100644 --- a/lix/nix/daemon.cc +++ b/lix/nix/daemon.cc @@ -368,7 +368,7 @@ static void daemonLoopImpl(std::optional forceTrustClientOpt) FdSource from(remote.get()); FdSink to(remote.get()); processConnection( - aio, aio.blockOn(openUncachedStore()), from, to, trusted, NotRecursive + aio, aio.blockOn(openUncachedStore()), from, to, trusted ); exit(0); @@ -451,7 +451,7 @@ processStdioConnection(AsyncIoRoot & aio, ref store, TrustedFlag trustCli { FdSource from(STDIN_FILENO); FdSink to(STDOUT_FILENO); - processConnection(aio, store, from, to, trustClient, NotRecursive); + processConnection(aio, store, from, to, trustClient); } /** diff --git a/tests/functional/ca/recursive.sh b/tests/functional/ca/recursive.sh deleted file mode 100755 index cd6736b24..000000000 --- a/tests/functional/ca/recursive.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -source common.sh - -requireDaemonNewerThan "2.4pre20210623" - -export NIX_TESTS_CA_BY_DEFAULT=1 -cd .. -source ./recursive.sh diff --git a/tests/functional/dyn-drv/recursive-mod-json.nix b/tests/functional/dyn-drv/recursive-mod-json.nix deleted file mode 100644 index c6a24ca4f..000000000 --- a/tests/functional/dyn-drv/recursive-mod-json.nix +++ /dev/null @@ -1,33 +0,0 @@ -with import ./config.nix; - -let innerName = "foo"; in - -mkDerivation rec { - name = "${innerName}.drv"; - SHELL = shell; - - requiredSystemFeatures = [ "recursive-nix" ]; - - drv = builtins.unsafeDiscardOutputDependency (import ./text-hashed-output.nix).hello.drvPath; - - buildCommand = '' - export NIX_CONFIG='experimental-features = nix-command ca-derivations' - - PATH=${builtins.getEnv "EXTRA_PATH"}:$PATH - - # JSON of pre-existing drv - nix derivation show $drv | jq .[] > drv0.json - - # Fix name - jq < drv0.json '.name = "${innerName}"' > drv1.json - - # Extend `buildCommand` - jq < drv1.json '.env.buildCommand += "echo \"I am alive!\" >> $out/hello\n"' > drv0.json - - # Used as our output - cp $(nix derivation add < drv0.json) $out - ''; - __contentAddressed = true; - outputHashMode = "text"; - outputHashAlgo = "sha256"; -} diff --git a/tests/functional/dyn-drv/recursive-mod-json.sh b/tests/functional/dyn-drv/recursive-mod-json.sh deleted file mode 100644 index 0698b81bd..000000000 --- a/tests/functional/dyn-drv/recursive-mod-json.sh +++ /dev/null @@ -1,27 +0,0 @@ -source common.sh - -# FIXME -if [[ $(uname) != Linux ]]; then skipTest "Not running Linux"; fi - -export NIX_TESTS_CA_BY_DEFAULT=1 - -enableFeatures 'recursive-nix' -restartDaemon - -clearStore - -rm -f $TEST_ROOT/result - -EXTRA_PATH=$(dirname $(type -p nix)):$(dirname $(type -p jq)) -export EXTRA_PATH - -# Will produce a drv -metaDrv=$(nix-instantiate ./recursive-mod-json.nix) - -# computed "dynamic" derivation -drv=$(nix-store -r $metaDrv) - -# build that dyn drv -res=$(nix-store -r $drv) - -grep 'I am alive!' $res/hello diff --git a/tests/functional/meson.build b/tests/functional/meson.build index e5036e1a4..2c3f843d3 100644 --- a/tests/functional/meson.build +++ b/tests/functional/meson.build @@ -49,14 +49,12 @@ functional_tests_scripts = [ 'ca/nix-run.sh', 'ca/nix-shell.sh', 'ca/post-hook.sh', - 'ca/recursive.sh', 'ca/repl.sh', 'ca/selfref-gc.sh', 'ca/signatures.sh', 'ca/substitute.sh', 'ca/why-depends.sh', 'dyn-drv/text-hashed-output.sh', - 'dyn-drv/recursive-mod-json.sh', 'dyn-drv/build-built-drv.sh', 'dyn-drv/eval-outputOf.sh', 'dyn-drv/dep-built-drv.sh', @@ -127,7 +125,6 @@ functional_tests_scripts = [ 'flakes/search-root.sh', 'readfile-context.sh', 'nix-channel.sh', - 'recursive.sh', 'dependencies.sh', 'check-reqs.sh', 'build-remote-content-addressed-fixed.sh', diff --git a/tests/functional/recursive.nix b/tests/functional/recursive.nix deleted file mode 100644 index fa8cc04db..000000000 --- a/tests/functional/recursive.nix +++ /dev/null @@ -1,56 +0,0 @@ -with import ./config.nix; - -mkDerivation rec { - name = "recursive"; - dummy = builtins.toFile "dummy" "bla bla"; - SHELL = shell; - - # Note: this is a string without context. - unreachable = builtins.getEnv "unreachable"; - - NIX_TESTS_CA_BY_DEFAULT = builtins.getEnv "NIX_TESTS_CA_BY_DEFAULT"; - - requiredSystemFeatures = [ "recursive-nix" ]; - - buildCommand = '' - mkdir $out - opts="--experimental-features nix-command ${if (NIX_TESTS_CA_BY_DEFAULT == "1") then "--extra-experimental-features ca-derivations" else ""}" - - PATH=${builtins.getEnv "NIX_BIN_DIR"}:$PATH - - # Check that we can query/build paths in our input closure. - nix $opts path-info $dummy - nix $opts build $dummy - - # Make sure we cannot query/build paths not in out input closure. - [[ -e $unreachable ]] - (! nix $opts path-info $unreachable) - (! nix $opts build $unreachable) - - # Add something to the store. - echo foobar > foobar - foobar=$(nix $opts store add-path ./foobar) - - nix $opts path-info $foobar - nix $opts build $foobar - - # Add it to our closure. - ln -s $foobar $out/foobar - - [[ $(nix $opts path-info --all | wc -l) -eq 4 ]] - - # Build a derivation. - nix $opts build -L --impure --expr ' - with import ${./config.nix}; - mkDerivation { - name = "inner1"; - buildCommand = "echo $fnord blaat > $out"; - fnord = builtins.toFile "fnord" "fnord"; - } - ' - - [[ $(nix $opts path-info --json ./result) =~ fnord ]] - - ln -s $(nix $opts path-info ./result) $out/inner1 - ''; -} diff --git a/tests/functional/recursive.sh b/tests/functional/recursive.sh deleted file mode 100644 index 0bf00f8fa..000000000 --- a/tests/functional/recursive.sh +++ /dev/null @@ -1,19 +0,0 @@ -source common.sh - -enableFeatures 'recursive-nix' -restartDaemon - -clearStore - -rm -f $TEST_ROOT/result - -export unreachable=$(nix store add-path ./recursive.sh) - -NIX_BIN_DIR=$(dirname $(type -p nix)) nix --extra-experimental-features 'nix-command recursive-nix' build -o $TEST_ROOT/result -L --impure --file ./recursive.nix - -[[ $(cat $TEST_ROOT/result/inner1) =~ blaat ]] - -# Make sure the recursively created paths are in the closure. -nix path-info -r $TEST_ROOT/result | grep foobar -nix path-info -r $TEST_ROOT/result | grep fnord -nix path-info -r $TEST_ROOT/result | grep inner1