diff options
author | John Ericson <John.Ericson@Obsidian.Systems> | 2022-02-28 18:04:39 +0000 |
---|---|---|
committer | John Ericson <John.Ericson@Obsidian.Systems> | 2022-02-28 18:29:33 +0000 |
commit | c863e5f338947ecff275a67725ecf50b2a47bdb5 (patch) | |
tree | 733893d760809edcbc55c7aa8078ab84fcd2aa73 /src/nix | |
parent | 7869be49c2735280ceabbd13c087b4a06444ae63 (diff) | |
parent | b592359c565e0220545ba146b32f367e4ecdb23f (diff) |
Merge remote-tracking branch 'upstream/master' into trustless-remote-builder-simple
Diffstat (limited to 'src/nix')
41 files changed, 668 insertions, 296 deletions
diff --git a/src/nix/add-to-store.cc b/src/nix/add-to-store.cc index 2ae042789..5168413d2 100644 --- a/src/nix/add-to-store.cc +++ b/src/nix/add-to-store.cc @@ -32,7 +32,7 @@ struct CmdAddToStore : MixDryRun, StoreCommand StringSink sink; dumpPath(path, sink); - auto narHash = hashString(htSHA256, *sink.s); + auto narHash = hashString(htSHA256, sink.s); Hash hash = narHash; if (ingestionMethod == FileIngestionMethod::Flat) { @@ -45,14 +45,14 @@ struct CmdAddToStore : MixDryRun, StoreCommand store->makeFixedOutputPath(ingestionMethod, hash, *namePart), narHash, }; - info.narSize = sink.s->size(); + info.narSize = sink.s.size(); info.ca = std::optional { FixedOutputHash { .method = ingestionMethod, .hash = hash, } }; if (!dryRun) { - auto source = StringSource { *sink.s }; + auto source = StringSource(sink.s); store->addToStore(info, source); } diff --git a/src/nix/app.cc b/src/nix/app.cc index 9719a65dd..e104cc9c1 100644 --- a/src/nix/app.cc +++ b/src/nix/app.cc @@ -19,7 +19,7 @@ struct InstallableDerivedPath : Installable } - std::string what() override { return derivedPath.to_string(*store); } + std::string what() const override { return derivedPath.to_string(*store); } DerivedPaths toDerivedPaths() override { @@ -83,11 +83,14 @@ UnresolvedApp Installable::toApp(EvalState & state) auto outPath = cursor->getAttr(state.sOutPath)->getString(); auto outputName = cursor->getAttr(state.sOutputName)->getString(); auto name = cursor->getAttr(state.sName)->getString(); + auto aPname = cursor->maybeGetAttr("pname"); auto aMeta = cursor->maybeGetAttr("meta"); auto aMainProgram = aMeta ? aMeta->maybeGetAttr("mainProgram") : nullptr; auto mainProgram = aMainProgram ? aMainProgram->getString() + : aPname + ? aPname->getString() : DrvName(name).name; auto program = outPath + "/bin/" + mainProgram; return UnresolvedApp { App { diff --git a/src/nix/bundle.cc b/src/nix/bundle.cc index aca024bca..6b891a6ee 100644 --- a/src/nix/bundle.cc +++ b/src/nix/bundle.cc @@ -49,9 +49,13 @@ struct CmdBundle : InstallableCommand Category category() override { return catSecondary; } + // FIXME: cut&paste from CmdRun. Strings getDefaultFlakeAttrPaths() override { - Strings res{"defaultApp." + settings.thisSystem.get()}; + Strings res{ + "apps." + settings.thisSystem.get() + ".default", + "defaultApp." + settings.thisSystem.get() + }; for (auto & s : SourceExprCommand::getDefaultFlakeAttrPaths()) res.push_back(s); return res; @@ -69,29 +73,21 @@ struct CmdBundle : InstallableCommand { auto evalState = getEvalState(); - auto app = installable->toApp(*evalState).resolve(getEvalStore(), store); + auto val = installable->toValue(*evalState).first; auto [bundlerFlakeRef, bundlerName] = parseFlakeRefWithFragment(bundler, absPath(".")); const flake::LockFlags lockFlags{ .writeLockFile = false }; - auto bundler = InstallableFlake(this, - evalState, std::move(bundlerFlakeRef), - Strings{bundlerName == "" ? "defaultBundler" : bundlerName}, - Strings({"bundlers."}), lockFlags); - - Value * arg = evalState->allocValue(); - evalState->mkAttrs(*arg, 2); - - PathSet context; - for (auto & i : app.context) - context.insert("=" + store->printStorePath(i.path)); - mkString(*evalState->allocAttr(*arg, evalState->symbols.create("program")), app.program, context); - - mkString(*evalState->allocAttr(*arg, evalState->symbols.create("system")), settings.thisSystem.get()); - - arg->attrs->sort(); + InstallableFlake bundler{this, + evalState, std::move(bundlerFlakeRef), bundlerName, + {"bundlers." + settings.thisSystem.get() + ".default", + "defaultBundler." + settings.thisSystem.get() + }, + {"bundlers." + settings.thisSystem.get() + "."}, + lockFlags + }; auto vRes = evalState->allocValue(); - evalState->callFunction(*bundler.toValue(*evalState).first, *arg, *vRes, noPos); + evalState->callFunction(*bundler.toValue(*evalState).first, *val, *vRes, noPos); if (!evalState->isDerivation(*vRes)) throw Error("the bundler '%s' does not produce a derivation", bundler.what()); @@ -113,9 +109,12 @@ struct CmdBundle : InstallableCommand auto outPathS = store->printStorePath(outPath); - if (!outLink) - outLink = baseNameOf(app.program); + if (!outLink) { + auto &attr = vRes->attrs->need(evalState->sName); + outLink = evalState->forceStringNoCtx(*attr.value,*attr.pos); + } + // TODO: will crash if not a localFSStore? store.dynamic_pointer_cast<LocalFSStore>()->addPermRoot(outPath, absPath(*outLink)); } }; diff --git a/src/nix/bundle.md b/src/nix/bundle.md index 5e2298376..2bb70711f 100644 --- a/src/nix/bundle.md +++ b/src/nix/bundle.md @@ -18,19 +18,53 @@ R""( nix (Nix) 2.4pre20201215_e3ddffb ``` +* Bundle a Hello using a specific bundler: + + ```console + # nix bundle --bundler github:NixOS/bundlers#toDockerImage nixpkgs#hello + # docker load < hello-2.10.tar.gz + # docker run hello-2.10:latest hello + Hello, world! + ``` + # Description -`nix bundle` packs the closure of the [Nix app](./nix3-run.md) -*installable* into a single self-extracting executable. See the -[`nix-bundle` homepage](https://github.com/matthewbauer/nix-bundle) -for more details. +`nix bundle`, by default, packs the closure of the *installable* into a single +self-extracting executable. See the [`bundlers` +homepage](https://github.com/NixOS/bundlers) for more details. > **Note** > > This command only works on Linux. -# Bundler definitions +# Flake output attributes + +If no flake output attribute is given, `nix bundle` tries the following +flake output attributes: + +* `bundlers.<system>.default` + +If an attribute *name* is given, `nix run` tries the following flake +output attributes: + +* `bundlers.<system>.<name>` + +# Bundlers + +A bundler is specified by a flake output attribute named +`bundlers.<system>.<name>`. It looks like this: + +```nix +bundlers.x86_64-linux = rec { + identity = drv: drv; + + blender_2_79 = drv: self.packages.x86_64-linux.blender_2_79; + + default = identity; +}; +``` -TODO +A bundler must be a function that accepts an arbitrary value (typically a +derivation or app definition) and returns a derivation. )"" diff --git a/src/nix/cat.cc b/src/nix/cat.cc index e28ee3c50..6420a0f79 100644 --- a/src/nix/cat.cc +++ b/src/nix/cat.cc @@ -78,7 +78,7 @@ struct CmdCatNar : StoreCommand, MixCat void run(ref<Store> store) override { - cat(makeNarAccessor(make_ref<std::string>(readFile(narPath)))); + cat(makeNarAccessor(readFile(narPath))); } }; diff --git a/src/nix/copy.cc b/src/nix/copy.cc index 197c85316..8730a9a5c 100644 --- a/src/nix/copy.cc +++ b/src/nix/copy.cc @@ -1,17 +1,11 @@ #include "command.hh" #include "shared.hh" #include "store-api.hh" -#include "sync.hh" -#include "thread-pool.hh" - -#include <atomic> using namespace nix; -struct CmdCopy : BuiltPathsCommand +struct CmdCopy : virtual CopyCommand, virtual BuiltPathsCommand { - std::string srcUri, dstUri; - CheckSigsFlag checkSigs = CheckSigs; SubstituteFlag substitute = NoSubstitute; @@ -22,20 +16,6 @@ struct CmdCopy : BuiltPathsCommand : BuiltPathsCommand(true) { addFlag({ - .longName = "from", - .description = "URL of the source Nix store.", - .labels = {"store-uri"}, - .handler = {&srcUri}, - }); - - addFlag({ - .longName = "to", - .description = "URL of the destination Nix store.", - .labels = {"store-uri"}, - .handler = {&dstUri}, - }); - - addFlag({ .longName = "no-check-sigs", .description = "Do not require that paths are signed by trusted keys.", .handler = {&checkSigs, NoCheckSigs}, @@ -65,22 +45,9 @@ struct CmdCopy : BuiltPathsCommand Category category() override { return catSecondary; } - ref<Store> createStore() override - { - return srcUri.empty() ? StoreCommand::createStore() : openStore(srcUri); - } - - void run(ref<Store> store) override - { - if (srcUri.empty() && dstUri.empty()) - throw UsageError("you must pass '--from' and/or '--to'"); - - BuiltPathsCommand::run(store); - } - void run(ref<Store> srcStore, BuiltPaths && paths) override { - ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri); + auto dstStore = getDstStore(); RealisedPath::Set stuffToCopy; diff --git a/src/nix/daemon.cc b/src/nix/daemon.cc index da8d91029..b592de5f7 100644 --- a/src/nix/daemon.cc +++ b/src/nix/daemon.cc @@ -76,7 +76,7 @@ static void setSigChldAction(bool autoReap) } -bool matchUser(const string & user, const string & group, const Strings & users) +bool matchUser(const std::string & user, const std::string & group, const Strings & users) { if (find(users.begin(), users.end(), "*") != users.end()) return true; @@ -85,12 +85,12 @@ bool matchUser(const string & user, const string & group, const Strings & users) return true; for (auto & i : users) - if (string(i, 0, 1) == "@") { - if (group == string(i, 1)) return true; + if (i.substr(0, 1) == "@") { + if (group == i.substr(1)) return true; struct group * gr = getgrnam(i.c_str() + 1); if (!gr) continue; for (char * * mem = gr->gr_mem; *mem; mem++) - if (user == string(*mem)) return true; + if (user == std::string(*mem)) return true; } return false; @@ -198,10 +198,10 @@ static void daemonLoop() PeerInfo peer = getPeerInfo(remote.get()); struct passwd * pw = peer.uidKnown ? getpwuid(peer.uid) : 0; - string user = pw ? pw->pw_name : std::to_string(peer.uid); + std::string user = pw ? pw->pw_name : std::to_string(peer.uid); struct group * gr = peer.gidKnown ? getgrgid(peer.gid) : 0; - string group = gr ? gr->gr_name : std::to_string(peer.gid); + std::string group = gr ? gr->gr_name : std::to_string(peer.gid); Strings trustedUsers = settings.trustedUsers; Strings allowedUsers = settings.allowedUsers; @@ -212,7 +212,7 @@ static void daemonLoop() if ((!trusted && !matchUser(user, group, allowedUsers)) || group == settings.buildUsersGroup) throw Error("user '%1%' is not allowed to connect to the Nix daemon", user); - printInfo(format((string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : "")) + printInfo(format((std::string) "accepted connection from pid %1%, user %2%" + (trusted ? " (trusted)" : "")) % (peer.pidKnown ? std::to_string(peer.pid) : "<unknown>") % (peer.uidKnown ? user : "<unknown>")); @@ -234,7 +234,7 @@ static void daemonLoop() // For debugging, stuff the pid into argv[1]. if (peer.pidKnown && savedArgv[1]) { - string processName = std::to_string(peer.pid); + auto processName = std::to_string(peer.pid); strncpy(savedArgv[1], processName.c_str(), strlen(savedArgv[1])); } @@ -333,10 +333,10 @@ static int main_nix_daemon(int argc, char * * argv) else if (*arg == "--stdio") stdio = true; else if (*arg == "--trust") { - settings.requireExperimentalFeature("nix-testing"); + settings.requireExperimentalFeature(Xp::NixTesting); isTrustedOpt = Trusted; } else if (*arg == "--no-trust") { - settings.requireExperimentalFeature("nix-testing"); + settings.requireExperimentalFeature(Xp::NixTesting); isTrustedOpt = NotTrusted; } else return false; return true; diff --git a/src/nix/develop.cc b/src/nix/develop.cc index f22335023..92e31599a 100644 --- a/src/nix/develop.cc +++ b/src/nix/develop.cc @@ -5,7 +5,6 @@ #include "store-api.hh" #include "path-with-outputs.hh" #include "derivations.hh" -#include "affinity.hh" #include "progress-bar.hh" #include "run.hh" @@ -195,7 +194,7 @@ static StorePath getDerivationEnvironment(ref<Store> store, ref<Store> evalStore 'buildDerivation', but that's privileged. */ drv.name += "-env"; drv.inputSrcs.insert(std::move(getEnvShPath)); - if (settings.isExperimentalFeatureEnabled("ca-derivations")) { + if (settings.isExperimentalFeatureEnabled(Xp::CaDerivations)) { for (auto & output : drv.outputs) { output.second = { .output = DerivationOutputDeferred{}, @@ -326,8 +325,15 @@ struct Common : InstallableCommand, MixProfile Strings getDefaultFlakeAttrPaths() override { - return {"devShell." + settings.thisSystem.get(), "defaultPackage." + settings.thisSystem.get()}; + Strings paths{ + "devShells." + settings.thisSystem.get() + ".default", + "devShell." + settings.thisSystem.get(), + }; + for (auto & p : SourceExprCommand::getDefaultFlakeAttrPaths()) + paths.push_back(p); + return paths; } + Strings getDefaultFlakeAttrPathPrefixes() override { auto res = SourceExprCommand::getDefaultFlakeAttrPathPrefixes(); @@ -473,9 +479,11 @@ struct CmdDevelop : Common, MixEnvironment else { script = "[ -n \"$PS1\" ] && [ -e ~/.bashrc ] && source ~/.bashrc;\n" + script; if (developSettings.bashPrompt != "") - script += fmt("[ -n \"$PS1\" ] && PS1=%s;\n", shellEscape(developSettings.bashPrompt)); + script += fmt("[ -n \"$PS1\" ] && PS1=%s;\n", + shellEscape(developSettings.bashPrompt.get())); if (developSettings.bashPromptSuffix != "") - script += fmt("[ -n \"$PS1\" ] && PS1+=%s;\n", shellEscape(developSettings.bashPromptSuffix)); + script += fmt("[ -n \"$PS1\" ] && PS1+=%s;\n", + shellEscape(developSettings.bashPromptSuffix.get())); } writeFull(rcFileFd.get(), script); @@ -497,7 +505,8 @@ struct CmdDevelop : Common, MixEnvironment this, state, installable->nixpkgsFlakeRef(), - Strings{"bashInteractive"}, + "bashInteractive", + Strings{}, Strings{"legacyPackages." + settings.thisSystem.get() + "."}, nixpkgsLockFlags); diff --git a/src/nix/develop.md b/src/nix/develop.md index 1f214966a..8bcff66c9 100644 --- a/src/nix/develop.md +++ b/src/nix/develop.md @@ -55,7 +55,7 @@ R""( # nix develop /tmp/my-build-env ``` -* Replace all occurences of the store path corresponding to +* Replace all occurrences of the store path corresponding to `glibc.dev` with a writable directory: ```console @@ -88,9 +88,9 @@ the flake's `nixConfig` attribute. If no flake output attribute is given, `nix develop` tries the following flake output attributes: -* `devShell.<system>` +* `devShells.<system>.default` -* `defaultPackage.<system>` +* `packages.<system>.default` If a flake output *name* is given, `nix develop` tries the following flake output attributes: diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 65d61e005..8cd04d5fe 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -81,7 +81,7 @@ struct CmdEval : MixJSON, InstallableCommand recurse = [&](Value & v, const Pos & pos, const Path & path) { - state->forceValue(v); + state->forceValue(v, pos); if (v.type() == nString) // FIXME: disallow strings with contexts? writeFile(path, v.string.s); @@ -107,12 +107,12 @@ struct CmdEval : MixJSON, InstallableCommand else if (raw) { stopProgressBar(); - std::cout << state->coerceToString(noPos, *v, context); + std::cout << *state->coerceToString(noPos, *v, context); } else if (json) { JSONPlaceholder jsonOut(std::cout); - printValueAsJSON(*state, true, *v, jsonOut, context); + printValueAsJSON(*state, true, *v, pos, jsonOut, context); } else { diff --git a/src/nix/flake-check.md b/src/nix/flake-check.md index d995d6274..07031c909 100644 --- a/src/nix/flake-check.md +++ b/src/nix/flake-check.md @@ -31,38 +31,38 @@ at the first error. The following flake output attributes must be derivations: * `checks.`*system*`.`*name* -* `defaultPackage.`*system*` -* `devShell.`*system*` -* `devShells.`*system*`.`*name*` -* `nixosConfigurations.`*name*`.config.system.build.toplevel +* `defaultPackage.`*system* +* `devShell.`*system* +* `devShells.`*system*`.`*name* +* `nixosConfigurations.`*name*`.config.system.build.toplevel` * `packages.`*system*`.`*name* The following flake output attributes must be [app definitions](./nix3-run.md): * `apps.`*system*`.`*name* -* `defaultApp.`*system*` +* `defaultApp.`*system* The following flake output attributes must be [template definitions](./nix3-flake-init.md): * `defaultTemplate` -* `templates`.`*name* +* `templates.`*name* The following flake output attributes must be *Nixpkgs overlays*: * `overlay` -* `overlays`.`*name* +* `overlays.`*name* The following flake output attributes must be *NixOS modules*: * `nixosModule` -* `nixosModules`.`*name* +* `nixosModules.`*name* The following flake output attributes must be [bundlers](./nix3-bundle.md): -* `bundlers`.`*name* +* `bundlers.`*name* * `defaultBundler` In addition, the `hydraJobs` output is evaluated in the same way as diff --git a/src/nix/flake-init.md b/src/nix/flake-init.md index 890038016..fc1f4f805 100644 --- a/src/nix/flake-init.md +++ b/src/nix/flake-init.md @@ -24,19 +24,23 @@ R""( This command creates a flake in the current directory by copying the files of a template. It will not overwrite existing files. The default -template is `templates#defaultTemplate`, but this can be overridden +template is `templates#templates.default`, but this can be overridden using `-t`. # Template definitions -A flake can declare templates through its `templates` and -`defaultTemplate` output attributes. A template has two attributes: +A flake can declare templates through its `templates` output +attribute. A template has two attributes: * `description`: A one-line description of the template, in CommonMark syntax. * `path`: The path of the directory to be copied. +* `welcomeText`: A block of markdown text to display when a user initializes a + new flake based on this template. + + Here is an example: ``` @@ -45,9 +49,19 @@ outputs = { self }: { templates.rust = { path = ./rust; description = "A simple Rust/Cargo project"; + welcomeText = '' + # Simple Rust/Cargo Template + ## Intended usage + The intended usage of this flake is... + + ## More info + - [Rust language](https://www.rust-lang.org/) + - [Rust on the NixOS Wiki](https://nixos.wiki/wiki/Rust) + - ... + ''; }; - templates.defaultTemplate = self.templates.rust; + templates.default = self.templates.rust; } ``` diff --git a/src/nix/flake-show.md b/src/nix/flake-show.md index e484cf47e..f3b74285d 100644 --- a/src/nix/flake-show.md +++ b/src/nix/flake-show.md @@ -13,10 +13,13 @@ R""( │ │ └───build: derivation 'patchelf-0.12.20201207.f34751b' │ └───x86_64-linux │ └───build: derivation 'patchelf-0.12.20201207.f34751b' - ├───defaultPackage - │ ├───aarch64-linux: package 'patchelf-0.12.20201207.f34751b' - │ ├───i686-linux: package 'patchelf-0.12.20201207.f34751b' - │ └───x86_64-linux: package 'patchelf-0.12.20201207.f34751b' + ├───packages + │ ├───aarch64-linux + │ │ └───default: package 'patchelf-0.12.20201207.f34751b' + │ ├───i686-linux + │ │ └───default: package 'patchelf-0.12.20201207.f34751b' + │ └───x86_64-linux + │ └───default: package 'patchelf-0.12.20201207.f34751b' ├───hydraJobs │ ├───build │ │ ├───aarch64-linux: derivation 'patchelf-0.12.20201207.f34751b' diff --git a/src/nix/flake.cc b/src/nix/flake.cc index 7e4d23f6e..14a235501 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -13,6 +13,7 @@ #include "registry.hh" #include "json.hh" #include "eval-cache.hh" +#include "markdown.hh" #include <nlohmann/json.hpp> #include <queue> @@ -124,12 +125,13 @@ struct CmdFlakeLock : FlakeCommand static void enumerateOutputs(EvalState & state, Value & vFlake, std::function<void(const std::string & name, Value & vProvide, const Pos & pos)> callback) { - state.forceAttrs(vFlake); + auto pos = vFlake.determinePos(noPos); + state.forceAttrs(vFlake, pos); auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs")); assert(aOutputs); - state.forceAttrs(*aOutputs->value); + state.forceAttrs(*aOutputs->value, pos); auto sHydraJobs = state.symbols.create("hydraJobs"); @@ -252,6 +254,14 @@ struct CmdFlakeInfo : CmdFlakeMetadata } }; +static bool argHasName(std::string_view arg, std::string_view expected) +{ + return + arg == expected + || arg == "_" + || (hasPrefix(arg, "_") && arg.substr(1) == expected); +} + struct CmdFlakeCheck : FlakeCommand { bool build = true; @@ -346,10 +356,14 @@ struct CmdFlakeCheck : FlakeCommand auto checkOverlay = [&](const std::string & attrPath, Value & v, const Pos & pos) { try { state->forceValue(v, pos); - if (!v.isLambda() || v.lambda.fun->hasFormals() || std::string(v.lambda.fun->arg) != "final") + if (!v.isLambda() + || v.lambda.fun->hasFormals() + || !argHasName(v.lambda.fun->arg, "final")) throw Error("overlay does not take an argument named 'final'"); auto body = dynamic_cast<ExprLambda *>(v.lambda.fun->body); - if (!body || body->hasFormals() || std::string(body->arg) != "prev") + if (!body + || body->hasFormals() + || !argHasName(body->arg, "prev")) throw Error("overlay does not take an argument named 'prev'"); // FIXME: if we have a 'nixpkgs' input, use it to // evaluate the overlay. @@ -463,10 +477,7 @@ struct CmdFlakeCheck : FlakeCommand state->forceValue(v, pos); if (!v.isLambda()) throw Error("bundler must be a function"); - if (!v.lambda.fun->formals || - !v.lambda.fun->formals->argNames.count(state->symbols.create("program")) || - !v.lambda.fun->formals->argNames.count(state->symbols.create("system"))) - throw Error("bundler must take formal arguments 'program' and 'system'"); + // TODO: check types of inputs/outputs? } catch (Error & e) { e.addTrace(pos, hintfmt("while checking the template '%s'", attrPath)); reportError(e); @@ -490,6 +501,17 @@ struct CmdFlakeCheck : FlakeCommand state->forceValue(vOutput, pos); + std::string_view replacement = + name == "defaultPackage" ? "packages.<system>.default" : + name == "defaultApps" ? "apps.<system>.default" : + name == "defaultTemplate" ? "templates.default" : + name == "defaultBundler" ? "bundlers.<system>.default" : + name == "overlay" ? "overlays.default" : + name == "devShell" ? "devShells.<system>.default" : + ""; + if (replacement != "") + warn("flake output attribute '%s' is deprecated; use '%s' instead", name, replacement); + if (name == "checks") { state->forceAttrs(vOutput, pos); for (auto & attr : *vOutput.attrs) { @@ -597,14 +619,27 @@ struct CmdFlakeCheck : FlakeCommand *attr.value, *attr.pos); } - else if (name == "defaultBundler") - checkBundler(name, vOutput, pos); + else if (name == "defaultBundler") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) { + checkSystemName(attr.name, *attr.pos); + checkBundler( + fmt("%s.%s", name, attr.name), + *attr.value, *attr.pos); + } + } else if (name == "bundlers") { state->forceAttrs(vOutput, pos); - for (auto & attr : *vOutput.attrs) - checkBundler(fmt("%s.%s", name, attr.name), - *attr.value, *attr.pos); + for (auto & attr : *vOutput.attrs) { + checkSystemName(attr.name, *attr.pos); + state->forceAttrs(*attr.value, *attr.pos); + for (auto & attr2 : *attr.value->attrs) { + checkBundler( + fmt("%s.%s.%s", name, attr.name, attr2.name), + *attr2.value, *attr2.pos); + } + } } else @@ -626,12 +661,14 @@ struct CmdFlakeCheck : FlakeCommand } }; +static Strings defaultTemplateAttrPathsPrefixes{"templates."}; +static Strings defaultTemplateAttrPaths = {"templates.default", "defaultTemplate"}; + struct CmdFlakeInitCommon : virtual Args, EvalCommand { std::string templateUrl = "templates"; Path destDir; - const Strings attrsPathPrefixes{"templates."}; const LockFlags lockFlags{ .writeLockFile = false }; CmdFlakeInitCommon() @@ -646,8 +683,8 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand completeFlakeRefWithFragment( getEvalState(), lockFlags, - attrsPathPrefixes, - {"defaultTemplate"}, + defaultTemplateAttrPathsPrefixes, + defaultTemplateAttrPaths, prefix); }} }); @@ -662,9 +699,10 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand auto [templateFlakeRef, templateName] = parseFlakeRefWithFragment(templateUrl, absPath(".")); auto installable = InstallableFlake(nullptr, - evalState, std::move(templateFlakeRef), - Strings{templateName == "" ? "defaultTemplate" : templateName}, - Strings(attrsPathPrefixes), lockFlags); + evalState, std::move(templateFlakeRef), templateName, + defaultTemplateAttrPaths, + defaultTemplateAttrPathsPrefixes, + lockFlags); auto [cursor, attrPath] = installable.getCursor(*evalState); @@ -705,6 +743,7 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand else throw Error("file '%s' has unsupported type", from2); files.push_back(to2); + notice("wrote: %s", to2); } }; @@ -715,6 +754,11 @@ struct CmdFlakeInitCommon : virtual Args, EvalCommand for (auto & s : files) args.push_back(s); runProgram("git", true, args); } + auto welcomeText = cursor->maybeGetAttr("welcomeText"); + if (welcomeText) { + notice("\n"); + notice(renderMarkdownToTerminal(welcomeText->getString())); + } } }; @@ -1040,7 +1084,8 @@ struct CmdFlakeShow : FlakeCommand, MixJSON (attrPath.size() == 1 && attrPath[0] == "overlay") || (attrPath.size() == 2 && attrPath[0] == "overlays") ? std::make_pair("nixpkgs-overlay", "Nixpkgs overlay") : attrPath.size() == 2 && attrPath[0] == "nixosConfigurations" ? std::make_pair("nixos-configuration", "NixOS configuration") : - attrPath.size() == 2 && attrPath[0] == "nixosModules" ? std::make_pair("nixos-module", "NixOS module") : + (attrPath.size() == 1 && attrPath[0] == "nixosModule") + || (attrPath.size() == 2 && attrPath[0] == "nixosModules") ? std::make_pair("nixos-module", "NixOS module") : std::make_pair("unknown", "unknown"); if (json) { j.emplace("type", type); @@ -1138,7 +1183,7 @@ struct CmdFlake : NixMultiCommand { if (!command) throw UsageError("'nix flake' requires a sub-command."); - settings.requireExperimentalFeature("flakes"); + settings.requireExperimentalFeature(Xp::Flakes); command->second->prepare(); command->second->run(); } diff --git a/src/nix/flake.md b/src/nix/flake.md index 3b5812a0a..d59915eeb 100644 --- a/src/nix/flake.md +++ b/src/nix/flake.md @@ -137,15 +137,6 @@ Currently the `type` attribute can be one of the following: *path* must be a directory in the file system containing a file named `flake.nix`. - If the directory or any of its parents is a Git repository, then - this is essentially equivalent to `git+file://<path>` (see below), - except that the `dir` parameter is derived automatically. For - example, if `/foo/bar` is a Git repository, then the flake reference - `/foo/bar/flake` is equivalent to `/foo/bar?dir=flake`. - - If the directory is not inside a Git repository, then the flake - contents is the entire contents of *path*. - *path* generally must be an absolute path. However, on the command line, it can be a relative path (e.g. `.` or `./foo`) which is interpreted as relative to the current directory. In this case, it @@ -218,6 +209,38 @@ Currently the `type` attribute can be one of the following: * `github:edolstra/dwarffs/unstable` * `github:edolstra/dwarffs/d3f2baba8f425779026c6ec04021b2e927f61e31` +* `sourcehut`: Similar to `github`, is a more efficient way to fetch + SourceHut repositories. The following attributes are required: + + * `owner`: The owner of the repository (including leading `~`). + + * `repo`: The name of the repository. + + Like `github`, these are downloaded as tarball archives. + + The URL syntax for `sourcehut` flakes is: + + `sourcehut:<owner>/<repo>(/<rev-or-ref>)?(\?<params>)?` + + `<rev-or-ref>` works the same as `github`. Either a branch or tag name + (`ref`), or a commit hash (`rev`) can be specified. + + Since SourceHut allows for self-hosting, you can specify `host` as + a parameter, to point to any instances other than `git.sr.ht`. + + Currently, `ref` name resolution only works for Git repositories. + You can refer to Mercurial repositories by simply changing `host` to + `hg.sr.ht` (or any other Mercurial instance). With the caveat + that you must explicitly specify a commit hash (`rev`). + + Some examples: + + * `sourcehut:~misterio/nix-colors` + * `sourcehut:~misterio/nix-colors/main` + * `sourcehut:~misterio/nix-colors?host=git.example.org` + * `sourcehut:~misterio/nix-colors/182b4b8709b8ffe4e9774a4c5d6877bf6bb9a21c` + * `sourcehut:~misterio/nix-colors/21c1a380a6915d890d408e9f22203436a35bb2de?host=hg.sr.ht` + * `indirect`: Indirections through the flake registry. These have the form @@ -245,7 +268,7 @@ derivation): outputs = { self, nixpkgs }: { - defaultPackage.x86_64-linux = + packages.x86_64-linux.default = # Notice the reference to nixpkgs here. with import nixpkgs { system = "x86_64-linux"; }; stdenv.mkDerivation { @@ -301,6 +324,12 @@ The following attributes are supported in `flake.nix`: value (e.g. `packages.x86_64-linux` must be an attribute set of derivations built for the `x86_64-linux` platform). +* `nixConfig`: a set of `nix.conf` options to be set when evaluating any + part of a flake. In the interests of security, only a small set of + whitelisted options (currently `bash-prompt`, `bash-prompt-suffix`, + and `flake-registry`) are allowed to be set without confirmation so long as + `accept-flake-config` is not set in the global configuration. + ## Flake inputs The attribute `inputs` specifies the dependencies of a flake, as an diff --git a/src/nix/hash.cc b/src/nix/hash.cc index 4535e4ab0..60d9593a7 100644 --- a/src/nix/hash.cc +++ b/src/nix/hash.cc @@ -177,7 +177,7 @@ static int compatNixHash(int argc, char * * argv) else if (*arg == "--base32") base32 = true; else if (*arg == "--truncate") truncate = true; else if (*arg == "--type") { - string s = getArg(*arg, arg, end); + std::string s = getArg(*arg, arg, end); ht = parseHashType(s); } else if (*arg == "--to-base16") op = opTo16; diff --git a/src/nix/ls.cc b/src/nix/ls.cc index c1dc9a95b..07554994b 100644 --- a/src/nix/ls.cc +++ b/src/nix/ls.cc @@ -157,7 +157,7 @@ struct CmdLsNar : Command, MixLs void run() override { - list(makeNarAccessor(make_ref<std::string>(readFile(narPath)))); + list(makeNarAccessor(readFile(narPath))); } }; diff --git a/src/nix/main.cc b/src/nix/main.cc index 2c3976689..b923f2535 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -59,7 +59,6 @@ struct HelpRequested { }; struct NixArgs : virtual MultiCommand, virtual MixCommonArgs { - bool printBuildLogs = false; bool useNet = true; bool refresh = false; bool showVersion = false; @@ -187,14 +186,11 @@ static void showHelp(std::vector<std::string> subcommand, MultiCommand & topleve , "/"), *vUtils); - auto vArgs = state.allocValue(); - state.mkAttrs(*vArgs, 16); - auto vJson = state.allocAttr(*vArgs, state.symbols.create("command")); - mkString(*vJson, toplevel.toJSON().dump()); - vArgs->attrs->sort(); + auto attrs = state.buildBindings(16); + attrs.alloc("command").mkString(toplevel.toJSON().dump()); auto vRes = state.allocValue(); - state.callFunction(*vGenerateManpage, *vArgs, *vRes, noPos); + state.callFunction(*vGenerateManpage, state.allocValue()->mkAttrs(attrs), *vRes, noPos); auto attr = vRes->attrs->get(state.symbols.create(mdName + ".md")); if (!attr) @@ -255,6 +251,16 @@ void mainWrapped(int argc, char * * argv) initNix(); initGC(); + #if __linux__ + if (getuid() == 0) { + try { + saveMountNamespace(); + if (unshare(CLONE_NEWNS) == -1) + throw SysError("setting up a private mount namespace"); + } catch (Error & e) { } + } + #endif + programPath = argv[0]; auto programName = std::string(baseNameOf(programPath)); @@ -263,11 +269,15 @@ void mainWrapped(int argc, char * * argv) if (legacy) return legacy(argc, argv); } - verbosity = lvlNotice; - settings.verboseBuild = false; evalSettings.pureEval = true; setLogFormat("bar"); + settings.verboseBuild = false; + if (isatty(STDERR_FILENO)) { + verbosity = lvlNotice; + } else { + verbosity = lvlInfo; + } Finally f([] { logger->stop(); }); @@ -300,7 +310,14 @@ void mainWrapped(int argc, char * * argv) Finally printCompletions([&]() { if (completions) { - std::cout << (pathCompletions ? "filenames\n" : "no-filenames\n"); + switch (completionType) { + case ctNormal: + std::cout << "normal\n"; break; + case ctFilenames: + std::cout << "filenames\n"; break; + case ctAttrs: + std::cout << "attrs\n"; break; + } for (auto & s : *completions) std::cout << s.completion << "\t" << s.description << "\n"; } @@ -337,7 +354,7 @@ void mainWrapped(int argc, char * * argv) if (args.command->first != "repl" && args.command->first != "doctor" && args.command->first != "upgrade-nix") - settings.requireExperimentalFeature("nix-command"); + settings.requireExperimentalFeature(Xp::NixCommand); if (args.useNet && !haveInternet()) { warn("you don't have Internet access; disabling some network-dependent features"); diff --git a/src/nix/make-content-addressable.cc b/src/nix/make-content-addressable.cc index 12f303a10..2e75a3b61 100644 --- a/src/nix/make-content-addressable.cc +++ b/src/nix/make-content-addressable.cc @@ -61,10 +61,10 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON } } - *sink.s = rewriteStrings(*sink.s, rewrites); + sink.s = rewriteStrings(sink.s, rewrites); HashModuloSink hashModuloSink(htSHA256, oldHashPart); - hashModuloSink(*sink.s); + hashModuloSink(sink.s); auto narHash = hashModuloSink.finish().first; @@ -74,7 +74,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON }; info.references = std::move(references); if (hasSelfReference) info.references.insert(info.path); - info.narSize = sink.s->size(); + info.narSize = sink.s.size(); info.ca = FixedOutputHash { .method = FileIngestionMethod::Recursive, .hash = info.narHash, @@ -85,7 +85,7 @@ struct CmdMakeContentAddressable : StorePathsCommand, MixJSON auto source = sinkToSource([&](Sink & nextSink) { RewritingSink rsink2(oldHashPart, std::string(info.path.hashPart()), nextSink); - rsink2(*sink.s); + rsink2(sink.s); rsink2.flush(); }); diff --git a/src/nix/nix.md b/src/nix/nix.md index d10de7c01..0dacadee6 100644 --- a/src/nix/nix.md +++ b/src/nix/nix.md @@ -57,13 +57,49 @@ the Nix store. Here are the recognised types of installables: These have the form *flakeref*[`#`*attrpath*], where *flakeref* is a flake reference and *attrpath* is an optional attribute path. For more information on flakes, see [the `nix flake` manual - page](./nix3-flake.md). Flake references are most commonly a flake - identifier in the flake registry (e.g. `nixpkgs`) or a path - (e.g. `/path/to/my-flake` or `.`). + page](./nix3-flake.md). Flake references are most commonly a flake + identifier in the flake registry (e.g. `nixpkgs`), or a raw path + (e.g. `/path/to/my-flake` or `.` or `../foo`), or a full URL + (e.g. `github:nixos/nixpkgs` or `path:.`) + + When the flake reference is a raw path (a path without any URL + scheme), it is interpreted as a `path:` or `git+file:` url in the following + way: + + - If the path is within a Git repository, then the url will be of the form + `git+file://[GIT_REPO_ROOT]?dir=[RELATIVE_FLAKE_DIR_PATH]` + where `GIT_REPO_ROOT` is the path to the root of the git repository, + and `RELATIVE_FLAKE_DIR_PATH` is the path (relative to the directory + root) of the closest parent of the given path that contains a `flake.nix` within + the git repository. + If no such directory exists, then Nix will error-out. + + Note that the search will only include files indexed by git. In particular, files + which are matched by `.gitignore` or have never been `git add`-ed will not be + available in the flake. If this is undesirable, specify `path:<directory>` explicitly; + + For example, if `/foo/bar` is a git repository with the following structure: + ``` + . + └── baz + ├── blah + │ └── file.txt + └── flake.nix + ``` + + Then `/foo/bar/baz/blah` will resolve to `git+file:///foo/bar?dir=baz` + + - If the supplied path is not a git repository, then the url will have the form + `path:FLAKE_DIR_PATH` where `FLAKE_DIR_PATH` is the closest parent + of the supplied path that contains a `flake.nix` file (within the same file-system). + If no such directory exists, then Nix will error-out. + + For example, if `/foo/bar/flake.nix` exists, then `/foo/bar/baz/` will resolve to + `path:/foo/bar` If *attrpath* is omitted, Nix tries some default values; for most - subcommands, the default is `defaultPackage.`*system* - (e.g. `defaultPackage.x86_64-linux`), but some subcommands have + subcommands, the default is `packages.`*system*`.default` + (e.g. `packages.x86_64-linux.default`), but some subcommands have other defaults. If *attrpath* *is* specified, *attrpath* is interpreted as relative to one or more prefixes; for most subcommands, these are `packages.`*system*, diff --git a/src/nix/path-info.cc b/src/nix/path-info.cc index 3743d7504..d690fe594 100644 --- a/src/nix/path-info.cc +++ b/src/nix/path-info.cc @@ -97,7 +97,7 @@ struct CmdPathInfo : StorePathsCommand, MixJSON for (auto & storePath : storePaths) { auto info = store->queryPathInfo(storePath); - auto storePathS = store->printStorePath(storePath); + auto storePathS = store->printStorePath(info->path); std::cout << storePathS; diff --git a/src/nix/ping-store.cc b/src/nix/ping-store.cc index 62b645b06..3c3b7bb45 100644 --- a/src/nix/ping-store.cc +++ b/src/nix/ping-store.cc @@ -20,7 +20,10 @@ struct CmdPingStore : StoreCommand void run(ref<Store> store) override { + notice("Store URL: %s", store->getUri()); store->connect(); + if (auto version = store->getVersion()) + notice("Version: %s", *version); } }; diff --git a/src/nix/prefetch.cc b/src/nix/prefetch.cc index 768d37595..f2dd44ba4 100644 --- a/src/nix/prefetch.cc +++ b/src/nix/prefetch.cc @@ -16,7 +16,7 @@ using namespace nix; /* If ‘url’ starts with ‘mirror://’, then resolve it using the list of mirrors defined in Nixpkgs. */ -string resolveMirrorUrl(EvalState & state, string url) +std::string resolveMirrorUrl(EvalState & state, const std::string & url) { if (url.substr(0, 9) != "mirror://") return url; @@ -28,18 +28,18 @@ string resolveMirrorUrl(EvalState & state, string url) Value vMirrors; // FIXME: use nixpkgs flake state.eval(state.parseExprFromString("import <nixpkgs/pkgs/build-support/fetchurl/mirrors.nix>", "."), vMirrors); - state.forceAttrs(vMirrors); + state.forceAttrs(vMirrors, noPos); auto mirrorList = vMirrors.attrs->find(state.symbols.create(mirrorName)); if (mirrorList == vMirrors.attrs->end()) throw Error("unknown mirror name '%s'", mirrorName); - state.forceList(*mirrorList->value); + state.forceList(*mirrorList->value, noPos); if (mirrorList->value->listSize() < 1) throw Error("mirror URL '%s' did not expand to anything", url); - auto mirror = state.forceString(*mirrorList->value->listElems()[0]); - return mirror + (hasSuffix(mirror, "/") ? "" : "/") + string(s, p + 1); + std::string mirror(state.forceString(*mirrorList->value->listElems()[0])); + return mirror + (hasSuffix(mirror, "/") ? "" : "/") + s.substr(p + 1); } std::tuple<StorePath, Hash> prefetchFile( @@ -128,10 +128,10 @@ static int main_nix_prefetch_url(int argc, char * * argv) { { HashType ht = htSHA256; - std::vector<string> args; + std::vector<std::string> args; bool printPath = getEnv("PRINT_PATH") == "1"; bool fromExpr = false; - string attrPath; + std::string attrPath; bool unpack = false; bool executable = false; std::optional<std::string> name; @@ -147,7 +147,7 @@ static int main_nix_prefetch_url(int argc, char * * argv) else if (*arg == "--version") printVersion("nix-prefetch-url"); else if (*arg == "--type") { - string s = getArg(*arg, arg, end); + auto s = getArg(*arg, arg, end); ht = parseHashType(s); } else if (*arg == "--print-path") @@ -186,7 +186,7 @@ static int main_nix_prefetch_url(int argc, char * * argv) /* If -A is given, get the URL from the specified Nix expression. */ - string url; + std::string url; if (!fromExpr) { if (args.empty()) throw UsageError("you must specify a URL"); @@ -196,11 +196,11 @@ static int main_nix_prefetch_url(int argc, char * * argv) Value vRoot; state->evalFile(path, vRoot); Value & v(*findAlongAttrPath(*state, attrPath, autoArgs, vRoot).first); - state->forceAttrs(v); + state->forceAttrs(v, noPos); /* Extract the URL. */ auto & attr = v.attrs->need(state->symbols.create("urls")); - state->forceList(*attr.value); + state->forceList(*attr.value, noPos); if (attr.value->listSize() < 1) throw Error("'urls' list is empty"); url = state->forceString(*attr.value->listElems()[0]); diff --git a/src/nix/profile-list.md b/src/nix/profile-list.md index 5c29c0b02..bdab9a208 100644 --- a/src/nix/profile-list.md +++ b/src/nix/profile-list.md @@ -8,7 +8,7 @@ R""( # nix profile list 0 flake:nixpkgs#legacyPackages.x86_64-linux.spotify github:NixOS/nixpkgs/c23db78bbd474c4d0c5c3c551877523b4a50db06#legacyPackages.x86_64-linux.spotify /nix/store/akpdsid105phbbvknjsdh7hl4v3fhjkr-spotify-1.1.46.916.g416cacf1 1 flake:nixpkgs#legacyPackages.x86_64-linux.zoom-us github:NixOS/nixpkgs/c23db78bbd474c4d0c5c3c551877523b4a50db06#legacyPackages.x86_64-linux.zoom-us /nix/store/89pmjmbih5qpi7accgacd17ybpgp4xfm-zoom-us-5.4.53350.1027 - 2 flake:blender-bin#defaultPackage.x86_64-linux github:edolstra/nix-warez/d09d7eea893dcb162e89bc67f6dc1ced14abfc27?dir=blender#defaultPackage.x86_64-linux /nix/store/zfgralhqjnam662kqsgq6isjw8lhrflz-blender-bin-2.91.0 + 2 flake:blender-bin#packages.x86_64-linux.default github:edolstra/nix-warez/d09d7eea893dcb162e89bc67f6dc1ced14abfc27?dir=blender#packages.x86_64-linux.default /nix/store/zfgralhqjnam662kqsgq6isjw8lhrflz-blender-bin-2.91.0 ``` # Description diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 96a20f673..0e8dc4380 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -157,17 +157,17 @@ struct ProfileManifest StringSink sink; dumpPath(tempDir, sink); - auto narHash = hashString(htSHA256, *sink.s); + auto narHash = hashString(htSHA256, sink.s); ValidPathInfo info { store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, "profile", references), narHash, }; info.references = std::move(references); - info.narSize = sink.s->size(); + info.narSize = sink.s.size(); info.ca = FixedOutputHash { .method = FileIngestionMethod::Recursive, .hash = info.narHash }; - auto source = StringSource { *sink.s }; + StringSource source(sink.s); store->addToStore(info, source); return std::move(info.path); @@ -295,7 +295,11 @@ public: expectArgs("elements", &_matchers); } - typedef std::variant<size_t, Path, std::regex> Matcher; + struct RegexPattern { + std::string pattern; + std::regex reg; + }; + typedef std::variant<size_t, Path, RegexPattern> Matcher; std::vector<Matcher> getMatchers(ref<Store> store) { @@ -307,7 +311,7 @@ public: else if (store->isStorePath(s)) res.push_back(s); else - res.push_back(std::regex(s, std::regex::extended | std::regex::icase)); + res.push_back(RegexPattern{s,std::regex(s, std::regex::extended | std::regex::icase)}); } return res; @@ -320,9 +324,9 @@ public: if (*n == pos) return true; } else if (auto path = std::get_if<Path>(&matcher)) { if (element.storePaths.count(store.parseStorePath(*path))) return true; - } else if (auto regex = std::get_if<std::regex>(&matcher)) { + } else if (auto regex = std::get_if<RegexPattern>(&matcher)) { if (element.source - && std::regex_match(element.source->attrPath, *regex)) + && std::regex_match(element.source->attrPath, regex->reg)) return true; } } @@ -355,16 +359,30 @@ struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElem for (size_t i = 0; i < oldManifest.elements.size(); ++i) { auto & element(oldManifest.elements[i]); - if (!matches(*store, element, i, matchers)) + if (!matches(*store, element, i, matchers)) { newManifest.elements.push_back(std::move(element)); + } else { + notice("removing '%s'", element.describe()); + } } - // FIXME: warn about unused matchers? - + auto removedCount = oldManifest.elements.size() - newManifest.elements.size(); printInfo("removed %d packages, kept %d packages", - oldManifest.elements.size() - newManifest.elements.size(), + removedCount, newManifest.elements.size()); + if (removedCount == 0) { + for (auto matcher: matchers) { + if (const size_t* index = std::get_if<size_t>(&matcher)){ + warn("'%d' is not a valid index in profile", *index); + } else if (const Path* path = std::get_if<Path>(&matcher)){ + warn("'%s' does not match any paths in profile", *path); + } else if (const RegexPattern* regex = std::get_if<RegexPattern>(&matcher)){ + warn("'%s' does not match any packages in profile", regex->pattern); + } + } + warn ("Try `nix profile list` to see the current profile."); + } updateProfile(newManifest.build(store)); } }; @@ -392,12 +410,16 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf // FIXME: code duplication std::vector<DerivedPath> pathsToBuild; + auto upgradedCount = 0; + for (size_t i = 0; i < manifest.elements.size(); ++i) { auto & element(manifest.elements[i]); if (element.source - && !element.source->originalRef.input.isImmutable() + && !element.source->originalRef.input.isLocked() && matches(*store, element, i, matchers)) { + upgradedCount++; + Activity act(*logger, lvlChatty, actUnknown, fmt("checking '%s' for updates", element.source->attrPath)); @@ -405,6 +427,7 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf this, getEvalState(), FlakeRef(element.source->originalRef), + "", {element.source->attrPath}, {}, lockFlags); @@ -429,6 +452,19 @@ struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProf } } + if (upgradedCount == 0) { + for (auto & matcher : matchers) { + if (const size_t* index = std::get_if<size_t>(&matcher)){ + warn("'%d' is not a valid index in profile", *index); + } else if (const Path* path = std::get_if<Path>(&matcher)){ + warn("'%s' does not match any paths in profile", *path); + } else if (const RegexPattern* regex = std::get_if<RegexPattern>(&matcher)){ + warn("'%s' does not match any packages in profile", regex->pattern); + } + } + warn ("Use 'nix profile list' to see the current profile."); + } + store->buildPaths(pathsToBuild); updateProfile(manifest.build(store)); diff --git a/src/nix/profile.md b/src/nix/profile.md index d3ddcd3d1..0a4ff2fa9 100644 --- a/src/nix/profile.md +++ b/src/nix/profile.md @@ -96,7 +96,7 @@ has the following fields: user specified, but the one resulting from applying the default attribute paths and prefixes; for instance, `hello` might resolve to `packages.x86_64-linux.hello` and the empty string to - `defaultPackage.x86_64-linux`. + `packages.x86_64-linux.default`. * `storePath`: The paths in the Nix store containing the package. diff --git a/src/nix/realisation.cc b/src/nix/realisation.cc index dfa8ff449..c9a7157cd 100644 --- a/src/nix/realisation.cc +++ b/src/nix/realisation.cc @@ -46,7 +46,7 @@ struct CmdRealisationInfo : BuiltPathsCommand, MixJSON void run(ref<Store> store, BuiltPaths && paths) override { - settings.requireExperimentalFeature("ca-derivations"); + settings.requireExperimentalFeature(Xp::CaDerivations); RealisedPath::Set realisations; for (auto & builtPath : paths) { diff --git a/src/nix/realisation/info.md b/src/nix/realisation/info.md index 852240f44..8aa986516 100644 --- a/src/nix/realisation/info.md +++ b/src/nix/realisation/info.md @@ -1,7 +1,7 @@ R"MdBoundary( # Description -Display some informations about the given realisation +Display some information about the given realisation # Examples diff --git a/src/nix/registry.cc b/src/nix/registry.cc index 6a92576c7..c496f94f8 100644 --- a/src/nix/registry.cc +++ b/src/nix/registry.cc @@ -226,6 +226,7 @@ struct CmdRegistry : virtual NixMultiCommand void run() override { + settings.requireExperimentalFeature(Xp::Flakes); if (!command) throw UsageError("'nix registry' requires a sub-command."); command->second->prepare(); diff --git a/src/nix/registry.md b/src/nix/registry.md index a1674bd2e..d5c9ef442 100644 --- a/src/nix/registry.md +++ b/src/nix/registry.md @@ -2,7 +2,7 @@ R""( # Description -`nix flake` provides subcommands for managing *flake +`nix registry` provides subcommands for managing *flake registries*. Flake registries are a convenience feature that allows you to refer to flakes using symbolic identifiers such as `nixpkgs`, rather than full URLs such as `git://github.com/NixOS/nixpkgs`. You diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 9c0d22438..731337004 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -28,7 +28,6 @@ extern "C" { #include "common-eval-args.hh" #include "get-drvs.hh" #include "derivations.hh" -#include "affinity.hh" #include "globals.hh" #include "command.hh" #include "finally.hh" @@ -46,7 +45,7 @@ struct NixRepl : gc #endif { - string curDir; + std::string curDir; std::unique_ptr<EvalState> state; Bindings * autoArgs; @@ -63,30 +62,30 @@ struct NixRepl NixRepl(const Strings & searchPath, nix::ref<Store> store); ~NixRepl(); void mainLoop(const std::vector<std::string> & files); - StringSet completePrefix(string prefix); - bool getLine(string & input, const std::string &prompt); + StringSet completePrefix(const std::string & prefix); + bool getLine(std::string & input, const std::string &prompt); StorePath getDerivationPath(Value & v); - bool processLine(string line); + bool processLine(std::string line); void loadFile(const Path & path); void loadFlake(const std::string & flakeRef); void initEnv(); void reloadFiles(); void addAttrsToScope(Value & attrs); void addVarToScope(const Symbol & name, Value & v); - Expr * parseString(string s); - void evalString(string s, Value & v); + Expr * parseString(std::string s); + void evalString(std::string s, Value & v); - typedef set<Value *> ValuesSeen; + typedef std::set<Value *> ValuesSeen; std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth); std::ostream & printValue(std::ostream & str, Value & v, unsigned int maxDepth, ValuesSeen & seen); }; -string removeWhitespace(string s) +std::string removeWhitespace(std::string s) { s = chomp(s); size_t n = s.find_first_not_of(" \n\r\t"); - if (n != string::npos) s = string(s, n); + if (n != std::string::npos) s = std::string(s, n); return s; } @@ -105,7 +104,7 @@ NixRepl::~NixRepl() write_history(historyFile.c_str()); } -string runNix(Path program, const Strings & args, +std::string runNix(Path program, const Strings & args, const std::optional<std::string> & input = {}) { auto subprocessEnv = getEnv(); @@ -199,7 +198,7 @@ namespace { void NixRepl::mainLoop(const std::vector<std::string> & files) { - string error = ANSI_RED "error:" ANSI_NORMAL " "; + std::string error = ANSI_RED "error:" ANSI_NORMAL " "; notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n"); for (auto & i : files) @@ -253,7 +252,7 @@ void NixRepl::mainLoop(const std::vector<std::string> & files) } -bool NixRepl::getLine(string & input, const std::string &prompt) +bool NixRepl::getLine(std::string & input, const std::string & prompt) { struct sigaction act, old; sigset_t savedSignalMask, set; @@ -279,6 +278,7 @@ bool NixRepl::getLine(string & input, const std::string &prompt) }; setupSignals(); + Finally resetTerminal([&]() { rl_deprep_terminal(); }); char * s = readline(prompt.c_str()); Finally doFree([&]() { free(s); }); restoreSignals(); @@ -297,7 +297,7 @@ bool NixRepl::getLine(string & input, const std::string &prompt) } -StringSet NixRepl::completePrefix(string prefix) +StringSet NixRepl::completePrefix(const std::string & prefix) { StringSet completions; @@ -313,7 +313,7 @@ StringSet NixRepl::completePrefix(string prefix) size_t slash, dot; - if ((slash = cur.rfind('/')) != string::npos) { + if ((slash = cur.rfind('/')) != std::string::npos) { try { auto dir = std::string(cur, 0, slash); auto prefix2 = std::string(cur, slash + 1); @@ -323,11 +323,11 @@ StringSet NixRepl::completePrefix(string prefix) } } catch (Error &) { } - } else if ((dot = cur.rfind('.')) == string::npos) { + } else if ((dot = cur.rfind('.')) == std::string::npos) { /* This is a variable name; look it up in the current scope. */ StringSet::iterator i = varNames.lower_bound(cur); while (i != varNames.end()) { - if (string(*i, 0, cur.size()) != cur) break; + if (i->substr(0, cur.size()) != cur) break; completions.insert(prev + *i); i++; } @@ -336,17 +336,17 @@ StringSet NixRepl::completePrefix(string prefix) /* This is an expression that should evaluate to an attribute set. Evaluate it to get the names of the attributes. */ - string expr(cur, 0, dot); - string cur2 = string(cur, dot + 1); + auto expr = cur.substr(0, dot); + auto cur2 = cur.substr(dot + 1); Expr * e = parseString(expr); Value v; e->eval(*state, *env, v); - state->forceAttrs(v); + state->forceAttrs(v, noPos); for (auto & i : *v.attrs) { - string name = i.name; - if (string(name, 0, cur2.size()) != cur2) continue; + std::string name = i.name; + if (name.substr(0, cur2.size()) != cur2) continue; completions.insert(prev + expr + "." + name); } @@ -356,6 +356,8 @@ StringSet NixRepl::completePrefix(string prefix) // Quietly ignore evaluation errors. } catch (UndefinedVarError & e) { // Quietly ignore undefined variable errors. + } catch (BadURL & e) { + // Quietly ignore BadURL flake-related errors. } } @@ -363,7 +365,7 @@ StringSet NixRepl::completePrefix(string prefix) } -bool isVarName(const string & s) +static bool isVarName(std::string_view s) { if (s.size() == 0) return false; char c = s[0]; @@ -392,18 +394,18 @@ StorePath NixRepl::getDerivationPath(Value & v) { } -bool NixRepl::processLine(string line) +bool NixRepl::processLine(std::string line) { if (line == "") return true; _isInterrupted = false; - string command, arg; + std::string command, arg; if (line[0] == ':') { size_t p = line.find_first_of(" \n\r\t"); - command = string(line, 0, p); - if (p != string::npos) arg = removeWhitespace(string(line, p)); + command = line.substr(0, p); + if (p != std::string::npos) arg = removeWhitespace(line.substr(p)); } else { arg = line; } @@ -427,7 +429,9 @@ bool NixRepl::processLine(string line) << " :s <expr> Build dependencies of derivation, then start nix-shell\n" << " :t <expr> Describe result of evaluation\n" << " :u <expr> Build derivation, then start nix-shell\n" - << " :doc <expr> Show documentation of a builtin function\n"; + << " :doc <expr> Show documentation of a builtin function\n" + << " :log <expr> Show logs for a derivation\n" + << " :st [bool] Enable, disable or toggle showing traces for errors\n"; } else if (command == ":a" || command == ":add") { @@ -459,7 +463,7 @@ bool NixRepl::processLine(string line) if (v.type() == nPath || v.type() == nString) { PathSet context; auto filename = state->coerceToString(noPos, v, context); - pos.file = state->symbols.create(filename); + pos.file = state->symbols.create(*filename); } else if (v.isLambda()) { pos = v.lambda.fun->pos; } else { @@ -471,7 +475,10 @@ bool NixRepl::processLine(string line) auto args = editorFor(pos); auto editor = args.front(); args.pop_front(); - runProgram(editor, true, args); + + // runProgram redirects stdout to a StringSink, + // using runProgram2 to allow editors to display their UI + runProgram2(RunOptions { .program = editor, .searchPath = true, .args = args }); // Reload right after exiting the editor state->resetFileCache(); @@ -494,7 +501,7 @@ bool NixRepl::processLine(string line) runNix("nix-shell", {state->store->printStorePath(drvPath)}); } - else if (command == ":b" || command == ":i" || command == ":s") { + else if (command == ":b" || command == ":i" || command == ":s" || command == ":log") { Value v; evalString(arg, v); StorePath drvPath = getDerivationPath(v); @@ -504,10 +511,31 @@ bool NixRepl::processLine(string line) state->store->buildPaths({DerivedPath::Built{drvPath}}); auto drv = state->store->readDerivation(drvPath); logger->cout("\nThis derivation produced the following outputs:"); - for (auto & i : drv.outputsAndOptPaths(*state->store)) - logger->cout(" %s -> %s", i.first, state->store->printStorePath(*i.second.second)); + for (auto & [outputName, outputPath] : state->store->queryDerivationOutputMap(drvPath)) + logger->cout(" %s -> %s", outputName, state->store->printStorePath(outputPath)); } else if (command == ":i") { runNix("nix-env", {"-i", drvPathRaw}); + } else if (command == ":log") { + settings.readOnlyMode = true; + Finally roModeReset([&]() { + settings.readOnlyMode = false; + }); + auto subs = getDefaultSubstituters(); + + subs.push_front(state->store); + + bool foundLog = false; + RunPager pager; + for (auto & sub : subs) { + auto log = sub->getBuildLog(drvPath); + if (log) { + printInfo("got build log for '%s' from '%s'", drvPathRaw, sub->getUri()); + logger->writeToStdout(*log); + foundLog = true; + break; + } + } + if (!foundLog) throw Error("build log of '%s' is not available", drvPathRaw); } else { runNix("nix-shell", {drvPathRaw}); } @@ -545,18 +573,30 @@ bool NixRepl::processLine(string line) throw Error("value does not have documentation"); } + else if (command == ":st" || command == ":show-trace") { + if (arg == "false" || (arg == "" && loggerSettings.showTrace)) { + std::cout << "not showing error traces\n"; + loggerSettings.showTrace = false; + } else if (arg == "true" || (arg == "" && !loggerSettings.showTrace)) { + std::cout << "showing error traces\n"; + loggerSettings.showTrace = true; + } else { + throw Error("unexpected argument '%s' to %s", arg, command); + }; + } + else if (command != "") throw Error("unknown command '%1%'", command); else { size_t p = line.find('='); - string name; - if (p != string::npos && + std::string name; + if (p != std::string::npos && p < line.size() && line[p + 1] != '=' && - isVarName(name = removeWhitespace(string(line, 0, p)))) + isVarName(name = removeWhitespace(line.substr(0, p)))) { - Expr * e = parseString(string(line, p + 1)); + Expr * e = parseString(line.substr(p + 1)); Value & v(*state->allocValue()); v.mkThunk(env, e); addVarToScope(state->symbols.create(name), v); @@ -583,9 +623,12 @@ void NixRepl::loadFile(const Path & path) void NixRepl::loadFlake(const std::string & flakeRefS) { + if (flakeRefS.empty()) + throw Error("cannot use ':load-flake' without a path specified. (Use '.' for the current working directory.)"); + auto flakeRef = parseFlakeRef(flakeRefS, absPath("."), true); - if (evalSettings.pureEval && !flakeRef.input.isImmutable()) - throw Error("cannot use ':load-flake' on mutable flake reference '%s' (use --impure to override)", flakeRefS); + if (evalSettings.pureEval && !flakeRef.input.isLocked()) + throw Error("cannot use ':load-flake' on locked flake reference '%s' (use --impure to override)", flakeRefS); Value v; @@ -633,9 +676,17 @@ void NixRepl::reloadFiles() void NixRepl::addAttrsToScope(Value & attrs) { - state->forceAttrs(attrs); - for (auto & i : *attrs.attrs) - addVarToScope(i.name, *i.value); + state->forceAttrs(attrs, [&]() { return attrs.determinePos(noPos); }); + if (displ + attrs.attrs->size() >= envSize) + throw Error("environment full; cannot add more variables"); + + for (auto & i : *attrs.attrs) { + staticEnv.vars.emplace_back(i.name, displ); + env->values[displ++] = i.value; + varNames.insert((std::string) i.name); + } + staticEnv.sort(); + staticEnv.deduplicate(); notice("Added %1% variables.", attrs.attrs->size()); } @@ -644,24 +695,27 @@ void NixRepl::addVarToScope(const Symbol & name, Value & v) { if (displ >= envSize) throw Error("environment full; cannot add more variables"); - staticEnv.vars[name] = displ; + if (auto oldVar = staticEnv.find(name); oldVar != staticEnv.vars.end()) + staticEnv.vars.erase(oldVar); + staticEnv.vars.emplace_back(name, displ); + staticEnv.sort(); env->values[displ++] = &v; - varNames.insert((string) name); + varNames.insert((std::string) name); } -Expr * NixRepl::parseString(string s) +Expr * NixRepl::parseString(std::string s) { - Expr * e = state->parseExprFromString(s, curDir, staticEnv); + Expr * e = state->parseExprFromString(std::move(s), curDir, staticEnv); return e; } -void NixRepl::evalString(string s, Value & v) +void NixRepl::evalString(std::string s, Value & v) { Expr * e = parseString(s); e->eval(*state, *env, v); - state->forceValue(v); + state->forceValue(v, [&]() { return v.determinePos(noPos); }); } @@ -691,7 +745,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m str.flush(); checkInterrupt(); - state->forceValue(v); + state->forceValue(v, [&]() { return v.determinePos(noPos); }); switch (v.type()) { @@ -733,7 +787,7 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m else if (maxDepth > 0) { str << "{ "; - typedef std::map<string, Value *> Sorted; + typedef std::map<std::string, Value *> Sorted; Sorted sorted; for (auto & i : *v.attrs) sorted[i.name] = i.value; @@ -767,12 +821,12 @@ std::ostream & NixRepl::printValue(std::ostream & str, Value & v, unsigned int m str << "[ "; if (maxDepth > 0) - for (unsigned int n = 0; n < v.listSize(); ++n) { - if (seen.find(v.listElems()[n]) != seen.end()) + for (auto elem : v.listItems()) { + if (seen.count(elem)) str << "«repeated»"; else try { - printValue(str, *v.listElems()[n], maxDepth - 1, seen); + printValue(str, *elem, maxDepth - 1, seen); } catch (AssertionError & e) { str << ANSI_RED "«error: " << e.msg() << "»" ANSI_NORMAL; } diff --git a/src/nix/repl.md b/src/nix/repl.md index bba60f871..9b6f2bee3 100644 --- a/src/nix/repl.md +++ b/src/nix/repl.md @@ -35,14 +35,17 @@ R""( nix-repl> emacs.drvPath "/nix/store/lp0sjrhgg03y2n0l10n70rg0k7hhyz0l-emacs-27.1.drv" - nix-repl> drv = runCommand "hello" { buildInputs = [ hello ]; } "hello > $out" + nix-repl> drv = runCommand "hello" { buildInputs = [ hello ]; } "hello; hello > $out" - nix-repl> :b x + nix-repl> :b drv this derivation produced the following outputs: out -> /nix/store/0njwbgwmkwls0w5dv9mpc1pq5fj39q0l-hello nix-repl> builtins.readFile drv "Hello, world!\n" + + nix-repl> :log drv + Hello, world! ``` # Description diff --git a/src/nix/run.cc b/src/nix/run.cc index b01fdebaa..a67c23bcb 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -8,7 +8,6 @@ #include "finally.hh" #include "fs-accessor.hh" #include "progress-bar.hh" -#include "affinity.hh" #include "eval.hh" #if __linux__ @@ -159,7 +158,10 @@ struct CmdRun : InstallableCommand Strings getDefaultFlakeAttrPaths() override { - Strings res{"defaultApp." + settings.thisSystem.get()}; + Strings res{ + "apps." + settings.thisSystem.get() + ".default", + "defaultApp." + settings.thisSystem.get(), + }; for (auto & s : SourceExprCommand::getDefaultFlakeAttrPaths()) res.push_back(s); return res; diff --git a/src/nix/run.md b/src/nix/run.md index a76750376..a0f362076 100644 --- a/src/nix/run.md +++ b/src/nix/run.md @@ -43,19 +43,24 @@ program specified by the app definition. If *installable* evaluates to a derivation, it will try to execute the program `<out>/bin/<name>`, where *out* is the primary output store -path of the derivation and *name* is the `meta.mainProgram` attribute -of the derivation if it exists, and otherwise the name part of the -value of the `name` attribute of the derivation (e.g. if `name` is set -to `hello-1.10`, it will run `$out/bin/hello`). +path of the derivation, and *name* is the first of the following that +exists: + +* The `meta.mainProgram` attribute of the derivation. +* The `pname` attribute of the derivation. +* The name part of the value of the `name` attribute of the derivation. + +For instance, if `name` is set to `hello-1.10`, `nix run` will run +`$out/bin/hello`. # Flake output attributes If no flake output attribute is given, `nix run` tries the following flake output attributes: -* `defaultApp.<system>` +* `apps.<system>.default` -* `defaultPackage.<system>` +* `packages.<system>.default` If an attribute *name* is given, `nix run` tries the following flake output attributes: @@ -69,7 +74,7 @@ output attributes: # Apps An app is specified by a flake output attribute named -`apps.<system>.<name>` or `defaultApp.<system>`. It looks like this: +`apps.<system>.<name>`. It looks like this: ```nix apps.x86_64-linux.blender_2_79 = { diff --git a/src/nix/search.cc b/src/nix/search.cc index 0d8fdd5c2..e9307342c 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -9,6 +9,7 @@ #include "shared.hh" #include "eval-cache.hh" #include "attr-path.hh" +#include "fmt.hh" #include <regex> #include <fstream> @@ -20,16 +21,6 @@ std::string wrap(std::string prefix, std::string s) return prefix + s + ANSI_NORMAL; } -std::string hilite(const std::string & s, const std::smatch & m, std::string postfix) -{ - return - m.empty() - ? s - : std::string(m.prefix()) - + ANSI_GREEN + std::string(m.str()) + postfix - + std::string(m.suffix()); -} - struct CmdSearch : InstallableCommand, MixJSON { std::vector<std::string> res; @@ -100,8 +91,6 @@ struct CmdSearch : InstallableCommand, MixJSON }; if (cursor.isDerivation()) { - size_t found = 0; - DrvName name(cursor.getAttr("name")->getString()); auto aMeta = cursor.maybeGetAttr("meta"); @@ -110,21 +99,31 @@ struct CmdSearch : InstallableCommand, MixJSON std::replace(description.begin(), description.end(), '\n', ' '); auto attrPath2 = concatStringsSep(".", attrPath); - std::smatch attrPathMatch; - std::smatch descriptionMatch; - std::smatch nameMatch; + std::vector<std::smatch> attrPathMatches; + std::vector<std::smatch> descriptionMatches; + std::vector<std::smatch> nameMatches; + bool found = false; for (auto & regex : regexes) { - std::regex_search(attrPath2, attrPathMatch, regex); - std::regex_search(name.name, nameMatch, regex); - std::regex_search(description, descriptionMatch, regex); - if (!attrPathMatch.empty() - || !nameMatch.empty() - || !descriptionMatch.empty()) - found++; + found = false; + auto addAll = [&found](std::sregex_iterator it, std::vector<std::smatch> & vec) { + const auto end = std::sregex_iterator(); + while (it != end) { + vec.push_back(*it++); + found = true; + } + }; + + addAll(std::sregex_iterator(attrPath2.begin(), attrPath2.end(), regex), attrPathMatches); + addAll(std::sregex_iterator(name.name.begin(), name.name.end(), regex), nameMatches); + addAll(std::sregex_iterator(description.begin(), description.end(), regex), descriptionMatches); + + if (!found) + break; } - if (found == res.size()) { + if (found) + { results++; if (json) { auto jsonElem = jsonOut->object(attrPath2); @@ -132,15 +131,15 @@ struct CmdSearch : InstallableCommand, MixJSON jsonElem.attr("version", name.version); jsonElem.attr("description", description); } else { - auto name2 = hilite(name.name, nameMatch, "\e[0;2m"); + auto name2 = hiliteMatches(name.name, std::move(nameMatches), ANSI_GREEN, "\e[0;2m"); if (results > 1) logger->cout(""); logger->cout( "* %s%s", - wrap("\e[0;1m", hilite(attrPath2, attrPathMatch, "\e[0;1m")), + wrap("\e[0;1m", hiliteMatches(attrPath2, std::move(attrPathMatches), ANSI_GREEN, "\e[0;1m")), name.version != "" ? " (" + name.version + ")" : ""); if (description != "") logger->cout( - " %s", hilite(description, descriptionMatch, ANSI_NORMAL)); + " %s", hiliteMatches(description, std::move(descriptionMatches), ANSI_GREEN, ANSI_NORMAL)); } } } diff --git a/src/nix/shell.md b/src/nix/shell.md index 2a379e03f..90b81fb2f 100644 --- a/src/nix/shell.md +++ b/src/nix/shell.md @@ -41,8 +41,8 @@ R""( # Description -`nix shell` runs a command in an environment in which the `$PATH` -variable provides the specified *installables*. If not command is -specified, it starts the default shell of your user account. +`nix shell` runs a command in an environment in which the `$PATH` variable +provides the specified *installables*. If no command is specified, it starts the +default shell of your user account specified by `$SHELL`. )"" diff --git a/src/nix/sigs.cc b/src/nix/sigs.cc index 43e0d9148..3d659d6d2 100644 --- a/src/nix/sigs.cc +++ b/src/nix/sigs.cc @@ -218,8 +218,7 @@ struct CmdKey : NixMultiCommand void run() override { if (!command) - throw UsageError("'nix flake' requires a sub-command."); - settings.requireExperimentalFeature("flakes"); + throw UsageError("'nix key' requires a sub-command."); command->second->prepare(); command->second->run(); } diff --git a/src/nix/store-copy-log.cc b/src/nix/store-copy-log.cc new file mode 100644 index 000000000..079cd6b3e --- /dev/null +++ b/src/nix/store-copy-log.cc @@ -0,0 +1,46 @@ +#include "command.hh" +#include "shared.hh" +#include "store-api.hh" +#include "sync.hh" +#include "thread-pool.hh" + +#include <atomic> + +using namespace nix; + +struct CmdCopyLog : virtual CopyCommand, virtual InstallablesCommand +{ + std::string description() override + { + return "copy build logs between Nix stores"; + } + + std::string doc() override + { + return + #include "store-copy-log.md" + ; + } + + Category category() override { return catUtility; } + + void run(ref<Store> srcStore) override + { + auto dstStore = getDstStore(); + + StorePathSet drvPaths; + + for (auto & i : installables) + for (auto & drvPath : i->toDrvPaths(getEvalStore())) + drvPaths.insert(drvPath); + + for (auto & drvPath : drvPaths) { + if (auto log = srcStore->getBuildLog(drvPath)) + dstStore->addBuildLog(drvPath, *log); + else + throw Error("build log for '%s' is not available", srcStore->printStorePath(drvPath)); + } + } +}; + +static auto rCmdCopyLog = registerCommand2<CmdCopyLog>({"store", "copy-log"}); diff --git a/src/nix/store-copy-log.md b/src/nix/store-copy-log.md new file mode 100644 index 000000000..19ae57079 --- /dev/null +++ b/src/nix/store-copy-log.md @@ -0,0 +1,33 @@ +R""( + +# Examples + +* To copy the build log of the `hello` package from + https://cache.nixos.org to the local store: + + ```console + # nix store copy-log --from https://cache.nixos.org --eval-store auto nixpkgs#hello + ``` + + You can verify that the log is available locally: + + ```console + # nix log --substituters '' nixpkgs#hello + ``` + + (The flag `--substituters ''` avoids querying + `https://cache.nixos.org` for the log.) + +* To copy the log for a specific store derivation via SSH: + + ```console + # nix store copy-log --to ssh-ng://machine /nix/store/ilgm50plpmcgjhcp33z6n4qbnpqfhxym-glibc-2.33-59.drv + ``` + +# Description + +`nix store copy-log` copies build logs between two Nix stores. The +source store is specified using `--from` and the destination using +`--to`. If one of these is omitted, it defaults to the local store. + +)"" diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index 9cd567896..17a5a77ee 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -140,7 +140,7 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand auto state = std::make_unique<EvalState>(Strings(), store); auto v = state->allocValue(); - state->eval(state->parseExprFromString(*res.data, "/no-such-path"), *v); + state->eval(state->parseExprFromString(res.data, "/no-such-path"), *v); Bindings & bindings(*state->allocBindings(0)); auto v2 = findAlongAttrPath(*state, settings.thisSystem, bindings, *v).first; diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc index 2f6b361bb..f7a3ec3a0 100644 --- a/src/nix/why-depends.cc +++ b/src/nix/why-depends.cc @@ -31,11 +31,25 @@ struct CmdWhyDepends : SourceExprCommand { std::string _package, _dependency; bool all = false; + bool precise = false; CmdWhyDepends() { - expectArg("package", &_package); - expectArg("dependency", &_dependency); + expectArgs({ + .label = "package", + .handler = {&_package}, + .completer = {[&](size_t, std::string_view prefix) { + completeInstallable(prefix); + }} + }); + + expectArgs({ + .label = "dependency", + .handler = {&_dependency}, + .completer = {[&](size_t, std::string_view prefix) { + completeInstallable(prefix); + }} + }); addFlag({ .longName = "all", @@ -43,6 +57,12 @@ struct CmdWhyDepends : SourceExprCommand .description = "Show all edges in the dependency graph leading from *package* to *dependency*, rather than just a shortest path.", .handler = {&all, true}, }); + + addFlag({ + .longName = "precise", + .description = "For each edge in the dependency graph, show the files in the parent that cause the dependency.", + .handler = {&precise, true}, + }); } std::string description() override @@ -137,26 +157,28 @@ struct CmdWhyDepends : SourceExprCommand closure (i.e., that have a non-infinite distance to 'dependency'). Print every edge on a path between `package` and `dependency`. */ - std::function<void(Node &, const string &, const string &)> printNode; + std::function<void(Node &, const std::string &, const std::string &)> printNode; struct BailOut { }; - printNode = [&](Node & node, const string & firstPad, const string & tailPad) { + printNode = [&](Node & node, const std::string & firstPad, const std::string & tailPad) { auto pathS = store->printStorePath(node.path); assert(node.dist != inf); - logger->cout("%s%s%s%s" ANSI_NORMAL, - firstPad, - node.visited ? "\e[38;5;244m" : "", - firstPad != "" ? "→ " : "", - pathS); + if (precise) { + logger->cout("%s%s%s%s" ANSI_NORMAL, + firstPad, + node.visited ? "\e[38;5;244m" : "", + firstPad != "" ? "→ " : "", + pathS); + } if (node.path == dependencyPath && !all && packagePath != dependencyPath) throw BailOut(); if (node.visited) return; - node.visited = true; + if (precise) node.visited = true; /* Sort the references by distance to `dependency` to ensure that the shortest path is printed first. */ @@ -224,9 +246,8 @@ struct CmdWhyDepends : SourceExprCommand // FIXME: should use scanForReferences(). - visitPath(pathS); + if (precise) visitPath(pathS); - RunPager pager; for (auto & ref : refs) { std::string hash(ref.second->path.hashPart()); @@ -240,13 +261,27 @@ struct CmdWhyDepends : SourceExprCommand if (!all) break; } + if (!precise) { + auto pathS = store->printStorePath(ref.second->path); + logger->cout("%s%s%s%s" ANSI_NORMAL, + firstPad, + ref.second->visited ? "\e[38;5;244m" : "", + last ? treeLast : treeConn, + pathS); + node.visited = true; + } + printNode(*ref.second, tailPad + (last ? treeNull : treeLine), tailPad + (last ? treeNull : treeLine)); } }; + RunPager pager; try { + if (!precise) { + logger->cout("%s", store->printStorePath(graph.at(packagePath).path)); + } printNode(graph.at(packagePath), "", ""); } catch (BailOut & ) { } } |