diff options
Diffstat (limited to 'src/nix')
-rw-r--r-- | src/nix/build.cc | 3 | ||||
-rw-r--r-- | src/nix/command.hh | 51 | ||||
-rw-r--r-- | src/nix/eval.cc | 2 | ||||
-rw-r--r-- | src/nix/flake-template.nix | 11 | ||||
-rw-r--r-- | src/nix/flake.cc | 717 | ||||
-rw-r--r-- | src/nix/installables.cc | 420 | ||||
-rw-r--r-- | src/nix/installables.hh | 51 | ||||
-rw-r--r-- | src/nix/local.mk | 2 | ||||
-rw-r--r-- | src/nix/main.cc | 1 | ||||
-rw-r--r-- | src/nix/profile.cc | 427 | ||||
-rw-r--r-- | src/nix/repl.cc | 1 | ||||
-rw-r--r-- | src/nix/run.cc | 57 | ||||
-rw-r--r-- | src/nix/search.cc | 4 | ||||
-rw-r--r-- | src/nix/shell.cc | 15 | ||||
-rw-r--r-- | src/nix/why-depends.cc | 4 |
15 files changed, 1621 insertions, 145 deletions
diff --git a/src/nix/build.cc b/src/nix/build.cc index 0b0762836..613bd9efb 100644 --- a/src/nix/build.cc +++ b/src/nix/build.cc @@ -1,3 +1,4 @@ +#include "eval.hh" #include "command.hh" #include "common-args.hh" #include "shared.hh" @@ -42,7 +43,7 @@ struct CmdBuild : InstallablesCommand, MixDryRun, MixProfile }, Example{ "To make a profile point at GNU Hello:", - "nix build --profile /tmp/profile nixpkgs.hello" + "nix build --profile /tmp/profile nixpkgs#hello" }, }; } diff --git a/src/nix/command.hh b/src/nix/command.hh index 23f5c9898..8b910ba78 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -4,12 +4,18 @@ #include "args.hh" #include "common-eval-args.hh" #include "path.hh" -#include "eval.hh" +#include "flake/lockfile.hh" + +#include <optional> namespace nix { extern std::string programPath; +class EvalState; +struct Pos; +class Store; + /* A command that requires a Nix store. */ struct StoreCommand : virtual Command { @@ -23,25 +29,36 @@ private: std::shared_ptr<Store> _store; }; -struct SourceExprCommand : virtual StoreCommand, MixEvalArgs +struct EvalCommand : virtual StoreCommand, MixEvalArgs { - Path file; + ref<EvalState> getEvalState(); - SourceExprCommand(); + std::shared_ptr<EvalState> evalState; +}; - /* Return a value representing the Nix expression from which we - are installing. This is either the file specified by ‘--file’, - or an attribute set constructed from $NIX_PATH, e.g. ‘{ nixpkgs - = import ...; bla = import ...; }’. */ - Value * getSourceExpr(EvalState & state); +struct MixFlakeOptions : virtual Args +{ + flake::LockFlags lockFlags; - ref<EvalState> getEvalState(); + MixFlakeOptions(); +}; -private: +struct SourceExprCommand : virtual Args, EvalCommand, MixFlakeOptions +{ + std::optional<Path> file; + std::optional<std::string> expr; - std::shared_ptr<EvalState> evalState; + SourceExprCommand(); - Value * vSourceExpr = 0; + std::vector<std::shared_ptr<Installable>> parseInstallables( + ref<Store> store, std::vector<std::string> ss); + + std::shared_ptr<Installable> parseInstallable( + ref<Store> store, const std::string & installable); + + virtual Strings getDefaultFlakeAttrPaths(); + + virtual Strings getDefaultFlakeAttrPathPrefixes(); }; enum RealiseMode { Build, NoBuild, DryRun }; @@ -73,14 +90,14 @@ struct InstallableCommand : virtual Args, SourceExprCommand InstallableCommand() { - expectArg("installable", &_installable); + expectArg("installable", &_installable, true); } void prepare() override; private: - std::string _installable; + std::string _installable{"."}; }; /* A command that operates on zero or more store paths. */ @@ -137,10 +154,6 @@ static RegisterCommand registerCommand(const std::string & name) return RegisterCommand(name, [](){ return make_ref<T>(); }); } -std::shared_ptr<Installable> parseInstallable( - SourceExprCommand & cmd, ref<Store> store, const std::string & installable, - bool useDefaultInstallables); - Buildables build(ref<Store> store, RealiseMode mode, std::vector<std::shared_ptr<Installable>> installables); diff --git a/src/nix/eval.cc b/src/nix/eval.cc index 6398fc58e..f23625161 100644 --- a/src/nix/eval.cc +++ b/src/nix/eval.cc @@ -28,7 +28,7 @@ struct CmdEval : MixJSON, InstallableCommand return { Example{ "To evaluate a Nix expression given on the command line:", - "nix eval '(1 + 2)'" + "nix eval --expr '1 + 2'" }, Example{ "To evaluate a Nix expression from a file or URI:", diff --git a/src/nix/flake-template.nix b/src/nix/flake-template.nix new file mode 100644 index 000000000..321961013 --- /dev/null +++ b/src/nix/flake-template.nix @@ -0,0 +1,11 @@ +{ + description = "A flake for building Hello World"; + + edition = 201909; + + outputs = { self, nixpkgs }: { + + packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello; + + }; +} diff --git a/src/nix/flake.cc b/src/nix/flake.cc new file mode 100644 index 000000000..e79f4129a --- /dev/null +++ b/src/nix/flake.cc @@ -0,0 +1,717 @@ +#include "command.hh" +#include "common-args.hh" +#include "shared.hh" +#include "progress-bar.hh" +#include "eval.hh" +#include "eval-inline.hh" +#include "flake/flake.hh" +#include "get-drvs.hh" +#include "store-api.hh" +#include "derivations.hh" +#include "attr-path.hh" +#include "fetchers.hh" +#include "registry.hh" +#include "json.hh" + +#include <nlohmann/json.hpp> +#include <queue> +#include <iomanip> + +using namespace nix; +using namespace nix::flake; + +class FlakeCommand : virtual Args, public EvalCommand, public MixFlakeOptions +{ + std::string flakeUrl = "."; + +public: + + FlakeCommand() + { + expectArg("flake-url", &flakeUrl, true); + } + + FlakeRef getFlakeRef() + { + return parseFlakeRef(flakeUrl, absPath(".")); //FIXME + } + + Flake getFlake() + { + auto evalState = getEvalState(); + return flake::getFlake(*evalState, getFlakeRef(), lockFlags.useRegistries); + } + + LockedFlake lockFlake() + { + return flake::lockFlake(*getEvalState(), getFlakeRef(), lockFlags); + } +}; + +struct CmdFlakeList : EvalCommand +{ + std::string description() override + { + return "list available Nix flakes"; + } + + void run(nix::ref<nix::Store> store) override + { + using namespace fetchers; + + auto registries = getRegistries(store); + + stopProgressBar(); + + for (auto & registry : registries) { + for (auto & entry : registry->entries) { + // FIXME: format nicely + std::cout << fmt("%s %s %s\n", + registry->type == Registry::Flag ? "flags " : + registry->type == Registry::User ? "user " : + registry->type == Registry::System ? "system" : + "global", + entry.from->to_string(), + entry.to->to_string()); + } + } + } +}; + +static void printFlakeInfo(const Store & store, const Flake & flake) +{ + std::cout << fmt("Resolved URL: %s\n", flake.resolvedRef.to_string()); + std::cout << fmt("Locked URL: %s\n", flake.lockedRef.to_string()); + std::cout << fmt("Edition: %s\n", flake.edition); + if (flake.description) + std::cout << fmt("Description: %s\n", *flake.description); + std::cout << fmt("Path: %s\n", store.printStorePath(flake.sourceInfo->storePath)); + if (auto rev = flake.lockedRef.input->getRev()) + std::cout << fmt("Revision: %s\n", rev->to_string(Base16, false)); + if (flake.sourceInfo->info.revCount) + std::cout << fmt("Revisions: %s\n", *flake.sourceInfo->info.revCount); + if (flake.sourceInfo->info.lastModified) + std::cout << fmt("Last modified: %s\n", + std::put_time(std::localtime(&*flake.sourceInfo->info.lastModified), "%F %T")); +} + +static nlohmann::json flakeToJson(const Store & store, const Flake & flake) +{ + nlohmann::json j; + if (flake.description) + j["description"] = *flake.description; + j["edition"] = flake.edition; + j["originalUrl"] = flake.originalRef.to_string(); + j["original"] = attrsToJson(flake.originalRef.toAttrs()); + j["resolvedUrl"] = flake.resolvedRef.to_string(); + j["resolved"] = attrsToJson(flake.resolvedRef.toAttrs()); + j["url"] = flake.lockedRef.to_string(); // FIXME: rename to lockedUrl + j["locked"] = attrsToJson(flake.lockedRef.toAttrs()); + j["info"] = flake.sourceInfo->info.toJson(); + if (auto rev = flake.lockedRef.input->getRev()) + j["revision"] = rev->to_string(Base16, false); + if (flake.sourceInfo->info.revCount) + j["revCount"] = *flake.sourceInfo->info.revCount; + if (flake.sourceInfo->info.lastModified) + j["lastModified"] = *flake.sourceInfo->info.lastModified; + j["path"] = store.printStorePath(flake.sourceInfo->storePath); + return j; +} + +struct CmdFlakeUpdate : FlakeCommand +{ + std::string description() override + { + return "update flake lock file"; + } + + void run(nix::ref<nix::Store> store) override + { + /* Use --refresh by default for 'nix flake update'. */ + settings.tarballTtl = 0; + + lockFlake(); + } +}; + +static void enumerateOutputs(EvalState & state, Value & vFlake, + std::function<void(const std::string & name, Value & vProvide, const Pos & pos)> callback) +{ + state.forceAttrs(vFlake); + + auto aOutputs = vFlake.attrs->get(state.symbols.create("outputs")); + assert(aOutputs); + + state.forceAttrs(*aOutputs->value); + + for (auto & attr : *aOutputs->value->attrs) + callback(attr.name, *attr.value, *attr.pos); +} + +struct CmdFlakeInfo : FlakeCommand, MixJSON +{ + std::string description() override + { + return "list info about a given flake"; + } + + void run(nix::ref<nix::Store> store) override + { + auto flake = getFlake(); + stopProgressBar(); + + if (json) { + auto json = flakeToJson(*store, flake); + std::cout << json.dump() << std::endl; + } else + printFlakeInfo(*store, flake); + } +}; + +struct CmdFlakeListInputs : FlakeCommand, MixJSON +{ + std::string description() override + { + return "list flake inputs"; + } + + void run(nix::ref<nix::Store> store) override + { + auto flake = lockFlake(); + + stopProgressBar(); + + if (json) + std::cout << flake.lockFile.toJson() << "\n"; + else { + std::cout << fmt("%s\n", flake.flake.lockedRef); + + std::function<void(const Node & node, const std::string & prefix)> recurse; + + recurse = [&](const Node & node, const std::string & prefix) + { + for (const auto & [i, input] : enumerate(node.inputs)) { + //auto tree2 = tree.child(i + 1 == inputs.inputs.size()); + bool last = i + 1 == node.inputs.size(); + std::cout << fmt("%s" ANSI_BOLD "%s" ANSI_NORMAL ": %s\n", + prefix + (last ? treeLast : treeConn), input.first, + std::dynamic_pointer_cast<const LockedNode>(input.second)->lockedRef); + recurse(*input.second, prefix + (last ? treeNull : treeLine)); + } + }; + + recurse(*flake.lockFile.root, ""); + } + } +}; + +struct CmdFlakeCheck : FlakeCommand +{ + bool build = true; + + CmdFlakeCheck() + { + mkFlag() + .longName("no-build") + .description("do not build checks") + .set(&build, false); + } + + std::string description() override + { + return "check whether the flake evaluates and run its tests"; + } + + void run(nix::ref<nix::Store> store) override + { + settings.readOnlyMode = !build; + + auto state = getEvalState(); + auto flake = lockFlake(); + + auto checkSystemName = [&](const std::string & system, const Pos & pos) { + // FIXME: what's the format of "system"? + if (system.find('-') == std::string::npos) + throw Error("'%s' is not a valid system type, at %s", system, pos); + }; + + auto checkDerivation = [&](const std::string & attrPath, Value & v, const Pos & pos) { + try { + auto drvInfo = getDerivation(*state, v, false); + if (!drvInfo) + throw Error("flake attribute '%s' is not a derivation", attrPath); + // FIXME: check meta attributes + return store->parseStorePath(drvInfo->queryDrvPath()); + } catch (Error & e) { + e.addPrefix(fmt("while checking the derivation '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos)); + throw; + } + }; + + std::vector<StorePathWithOutputs> drvPaths; + + auto checkApp = [&](const std::string & attrPath, Value & v, const Pos & pos) { + try { + auto app = App(*state, v); + for (auto & i : app.context) { + auto [drvPathS, outputName] = decodeContext(i); + auto drvPath = store->parseStorePath(drvPathS); + if (!outputName.empty() && drvPath.isDerivation()) + drvPaths.emplace_back(drvPath); + } + } catch (Error & e) { + e.addPrefix(fmt("while checking the app definition '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos)); + throw; + } + }; + + auto checkOverlay = [&](const std::string & attrPath, Value & v, const Pos & pos) { + try { + state->forceValue(v, pos); + if (v.type != tLambda || v.lambda.fun->matchAttrs || std::string(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->matchAttrs || std::string(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. + } catch (Error & e) { + e.addPrefix(fmt("while checking the overlay '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos)); + throw; + } + }; + + auto checkModule = [&](const std::string & attrPath, Value & v, const Pos & pos) { + try { + state->forceValue(v, pos); + if (v.type == tLambda) { + if (!v.lambda.fun->matchAttrs || !v.lambda.fun->formals->ellipsis) + throw Error("module must match an open attribute set ('{ config, ... }')"); + } else if (v.type == tAttrs) { + for (auto & attr : *v.attrs) + try { + state->forceValue(*attr.value, *attr.pos); + } catch (Error & e) { + e.addPrefix(fmt("while evaluating the option '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attr.name, *attr.pos)); + throw; + } + } else + throw Error("module must be a function or an attribute set"); + // FIXME: if we have a 'nixpkgs' input, use it to + // check the module. + } catch (Error & e) { + e.addPrefix(fmt("while checking the NixOS module '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos)); + throw; + } + }; + + std::function<void(const std::string & attrPath, Value & v, const Pos & pos)> checkHydraJobs; + + checkHydraJobs = [&](const std::string & attrPath, Value & v, const Pos & pos) { + try { + state->forceAttrs(v, pos); + + if (state->isDerivation(v)) + throw Error("jobset should not be a derivation at top-level"); + + for (auto & attr : *v.attrs) { + state->forceAttrs(*attr.value, *attr.pos); + if (!state->isDerivation(*attr.value)) + checkHydraJobs(attrPath + "." + (std::string) attr.name, + *attr.value, *attr.pos); + } + + } catch (Error & e) { + e.addPrefix(fmt("while checking the Hydra jobset '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos)); + throw; + } + }; + + auto checkNixOSConfiguration = [&](const std::string & attrPath, Value & v, const Pos & pos) { + try { + Activity act(*logger, lvlChatty, actUnknown, + fmt("checking NixOS configuration '%s'", attrPath)); + Bindings & bindings(*state->allocBindings(0)); + auto vToplevel = findAlongAttrPath(*state, "config.system.build.toplevel", bindings, v).first; + state->forceAttrs(*vToplevel, pos); + if (!state->isDerivation(*vToplevel)) + throw Error("attribute 'config.system.build.toplevel' is not a derivation"); + } catch (Error & e) { + e.addPrefix(fmt("while checking the NixOS configuration '" ANSI_BOLD "%s" ANSI_NORMAL "' at %s:\n", attrPath, pos)); + throw; + } + }; + + { + Activity act(*logger, lvlInfo, actUnknown, "evaluating flake"); + + auto vFlake = state->allocValue(); + flake::callFlake(*state, flake, *vFlake); + + enumerateOutputs(*state, + *vFlake, + [&](const std::string & name, Value & vOutput, const Pos & pos) { + Activity act(*logger, lvlChatty, actUnknown, + fmt("checking flake output '%s'", name)); + + try { + state->forceValue(vOutput, pos); + + if (name == "checks") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) { + checkSystemName(attr.name, *attr.pos); + state->forceAttrs(*attr.value, *attr.pos); + for (auto & attr2 : *attr.value->attrs) { + auto drvPath = checkDerivation( + fmt("%s.%s.%s", name, attr.name, attr2.name), + *attr2.value, *attr2.pos); + if ((std::string) attr.name == settings.thisSystem.get()) + drvPaths.emplace_back(drvPath); + } + } + } + + else if (name == "packages") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) { + checkSystemName(attr.name, *attr.pos); + state->forceAttrs(*attr.value, *attr.pos); + for (auto & attr2 : *attr.value->attrs) + checkDerivation( + fmt("%s.%s.%s", name, attr.name, attr2.name), + *attr2.value, *attr2.pos); + } + } + + else if (name == "apps") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) { + checkSystemName(attr.name, *attr.pos); + state->forceAttrs(*attr.value, *attr.pos); + for (auto & attr2 : *attr.value->attrs) + checkApp( + fmt("%s.%s.%s", name, attr.name, attr2.name), + *attr2.value, *attr2.pos); + } + } + + else if (name == "defaultPackage" || name == "devShell") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) { + checkSystemName(attr.name, *attr.pos); + checkDerivation( + fmt("%s.%s", name, attr.name), + *attr.value, *attr.pos); + } + } + + else if (name == "defaultApp") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) { + checkSystemName(attr.name, *attr.pos); + checkApp( + fmt("%s.%s", name, attr.name), + *attr.value, *attr.pos); + } + } + + else if (name == "legacyPackages") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) { + checkSystemName(attr.name, *attr.pos); + // FIXME: do getDerivations? + } + } + + else if (name == "overlay") + checkOverlay(name, vOutput, pos); + + else if (name == "overlays") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) + checkOverlay(fmt("%s.%s", name, attr.name), + *attr.value, *attr.pos); + } + + else if (name == "nixosModule") + checkModule(name, vOutput, pos); + + else if (name == "nixosModules") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) + checkModule(fmt("%s.%s", name, attr.name), + *attr.value, *attr.pos); + } + + else if (name == "nixosConfigurations") { + state->forceAttrs(vOutput, pos); + for (auto & attr : *vOutput.attrs) + checkNixOSConfiguration(fmt("%s.%s", name, attr.name), + *attr.value, *attr.pos); + } + + else if (name == "hydraJobs") + checkHydraJobs(name, vOutput, pos); + + else + warn("unknown flake output '%s'", name); + + } catch (Error & e) { + e.addPrefix(fmt("while checking flake output '" ANSI_BOLD "%s" ANSI_NORMAL "':\n", name)); + throw; + } + }); + } + + if (build && !drvPaths.empty()) { + Activity act(*logger, lvlInfo, actUnknown, "running flake checks"); + store->buildPaths(drvPaths); + } + } +}; + +struct CmdFlakeAdd : MixEvalArgs, Command +{ + std::string fromUrl, toUrl; + + std::string description() override + { + return "upsert flake in user flake registry"; + } + + CmdFlakeAdd() + { + expectArg("from-url", &fromUrl); + expectArg("to-url", &toUrl); + } + + void run() override + { + auto fromRef = parseFlakeRef(fromUrl); + auto toRef = parseFlakeRef(toUrl); + fetchers::Attrs extraAttrs; + if (toRef.subdir != "") extraAttrs["dir"] = toRef.subdir; + auto userRegistry = fetchers::getUserRegistry(); + userRegistry->remove(fromRef.input); + userRegistry->add(fromRef.input, toRef.input, extraAttrs); + userRegistry->write(fetchers::getUserRegistryPath()); + } +}; + +struct CmdFlakeRemove : virtual Args, MixEvalArgs, Command +{ + std::string url; + + std::string description() override + { + return "remove flake from user flake registry"; + } + + CmdFlakeRemove() + { + expectArg("url", &url); + } + + void run() override + { + auto userRegistry = fetchers::getUserRegistry(); + userRegistry->remove(parseFlakeRef(url).input); + userRegistry->write(fetchers::getUserRegistryPath()); + } +}; + +struct CmdFlakePin : virtual Args, EvalCommand +{ + std::string url; + + std::string description() override + { + return "pin a flake to its current version in user flake registry"; + } + + CmdFlakePin() + { + expectArg("url", &url); + } + + void run(nix::ref<nix::Store> store) override + { + auto ref = parseFlakeRef(url); + auto userRegistry = fetchers::getUserRegistry(); + userRegistry->remove(ref.input); + auto [tree, resolved] = ref.resolve(store).input->fetchTree(store); + fetchers::Attrs extraAttrs; + if (ref.subdir != "") extraAttrs["dir"] = ref.subdir; + userRegistry->add(ref.input, resolved, extraAttrs); + } +}; + +struct CmdFlakeInit : virtual Args, Command +{ + std::string description() override + { + return "create a skeleton 'flake.nix' file in the current directory"; + } + + void run() override + { + Path flakeDir = absPath("."); + + if (!pathExists(flakeDir + "/.git")) + throw Error("the directory '%s' is not a Git repository", flakeDir); + + Path flakePath = flakeDir + "/flake.nix"; + + if (pathExists(flakePath)) + throw Error("file '%s' already exists", flakePath); + + writeFile(flakePath, +#include "flake-template.nix.gen.hh" + ); + } +}; + +struct CmdFlakeClone : FlakeCommand +{ + Path destDir; + + std::string description() override + { + return "clone flake repository"; + } + + CmdFlakeClone() + { + mkFlag() + .shortName('f') + .longName("dest") + .label("path") + .description("destination path") + .dest(&destDir); + } + + void run(nix::ref<nix::Store> store) override + { + if (destDir.empty()) + throw Error("missing flag '--dest'"); + + getFlakeRef().resolve(store).input->clone(destDir); + } +}; + +struct CmdFlakeArchive : FlakeCommand, MixJSON, MixDryRun +{ + std::string dstUri; + + CmdFlakeArchive() + { + mkFlag() + .longName("to") + .labels({"store-uri"}) + .description("URI of the destination Nix store") + .dest(&dstUri); + } + + std::string description() override + { + return "copy a flake and all its inputs to a store"; + } + + Examples examples() override + { + return { + Example{ + "To copy the dwarffs flake and its dependencies to a binary cache:", + "nix flake archive --to file:///tmp/my-cache dwarffs" + }, + Example{ + "To fetch the dwarffs flake and its dependencies to the local Nix store:", + "nix flake archive dwarffs" + }, + Example{ + "To print the store paths of the flake sources of NixOps without fetching them:", + "nix flake archive --json --dry-run nixops" + }, + }; + } + + void run(nix::ref<nix::Store> store) override + { + auto flake = lockFlake(); + + auto jsonRoot = json ? std::optional<JSONObject>(std::cout) : std::nullopt; + + StorePathSet sources; + + sources.insert(flake.flake.sourceInfo->storePath.clone()); + if (jsonRoot) + jsonRoot->attr("path", store->printStorePath(flake.flake.sourceInfo->storePath)); + + // FIXME: use graph output, handle cycles. + std::function<void(const Node & node, std::optional<JSONObject> & jsonObj)> traverse; + traverse = [&](const Node & node, std::optional<JSONObject> & jsonObj) + { + auto jsonObj2 = jsonObj ? jsonObj->object("inputs") : std::optional<JSONObject>(); + for (auto & input : node.inputs) { + auto lockedInput = std::dynamic_pointer_cast<const LockedNode>(input.second); + assert(lockedInput); + auto jsonObj3 = jsonObj2 ? jsonObj2->object(input.first) : std::optional<JSONObject>(); + if (!dryRun) + lockedInput->lockedRef.input->fetchTree(store); + auto storePath = lockedInput->computeStorePath(*store); + if (jsonObj3) + jsonObj3->attr("path", store->printStorePath(storePath)); + sources.insert(std::move(storePath)); + traverse(*lockedInput, jsonObj3); + } + }; + + traverse(*flake.lockFile.root, jsonRoot); + + if (!dryRun && !dstUri.empty()) { + ref<Store> dstStore = dstUri.empty() ? openStore() : openStore(dstUri); + copyPaths(store, dstStore, sources); + } + } +}; + +struct CmdFlake : virtual MultiCommand, virtual Command +{ + CmdFlake() + : MultiCommand({ + {"list", []() { return make_ref<CmdFlakeList>(); }}, + {"update", []() { return make_ref<CmdFlakeUpdate>(); }}, + {"info", []() { return make_ref<CmdFlakeInfo>(); }}, + {"list-inputs", []() { return make_ref<CmdFlakeListInputs>(); }}, + {"check", []() { return make_ref<CmdFlakeCheck>(); }}, + {"add", []() { return make_ref<CmdFlakeAdd>(); }}, + {"remove", []() { return make_ref<CmdFlakeRemove>(); }}, + {"pin", []() { return make_ref<CmdFlakePin>(); }}, + {"init", []() { return make_ref<CmdFlakeInit>(); }}, + {"clone", []() { return make_ref<CmdFlakeClone>(); }}, + {"archive", []() { return make_ref<CmdFlakeArchive>(); }}, + }) + { + } + + std::string description() override + { + return "manage Nix flakes"; + } + + void run() override + { + if (!command) + throw UsageError("'nix flake' requires a sub-command."); + command->prepare(); + command->run(); + } + + void printHelp(const string & programName, std::ostream & out) override + { + MultiCommand::printHelp(programName, out); + } +}; + +static auto r1 = registerCommand<CmdFlake>("flake"); diff --git a/src/nix/installables.cc b/src/nix/installables.cc index f464d0aa1..99bbe9769 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -1,3 +1,4 @@ +#include "installables.hh" #include "command.hh" #include "attr-path.hh" #include "common-eval-args.hh" @@ -7,72 +8,96 @@ #include "get-drvs.hh" #include "store-api.hh" #include "shared.hh" +#include "flake/flake.hh" +#include "flake/eval-cache.hh" +#include "url.hh" #include <regex> +#include <queue> namespace nix { -SourceExprCommand::SourceExprCommand() +MixFlakeOptions::MixFlakeOptions() { mkFlag() - .shortName('f') - .longName("file") - .label("file") - .description("evaluate FILE rather than the default") - .dest(&file); -} - -Value * SourceExprCommand::getSourceExpr(EvalState & state) -{ - if (vSourceExpr) return vSourceExpr; - - auto sToplevel = state.symbols.create("_toplevel"); - - vSourceExpr = state.allocValue(); - - if (file != "") - state.evalFile(lookupFileArg(state, file), *vSourceExpr); + .longName("recreate-lock-file") + .description("recreate lock file from scratch") + .set(&lockFlags.recreateLockFile, true); - else { + mkFlag() + .longName("no-update-lock-file") + .description("do not allow any updates to the lock file") + .set(&lockFlags.updateLockFile, false); - /* Construct the installation source from $NIX_PATH. */ + mkFlag() + .longName("no-write-lock-file") + .description("do not write the newly generated lock file") + .set(&lockFlags.writeLockFile, false); - auto searchPath = state.getSearchPath(); + mkFlag() + .longName("no-registries") + .description("don't use flake registries") + .set(&lockFlags.useRegistries, false); - state.mkAttrs(*vSourceExpr, 1024); + mkFlag() + .longName("commit-lock-file") + .description("commit changes to the lock file") + .set(&lockFlags.commitLockFile, true); - mkBool(*state.allocAttr(*vSourceExpr, sToplevel), true); + mkFlag() + .longName("update-input") + .description("update a specific flake input") + .label("input-path") + .handler([&](std::vector<std::string> ss) { + lockFlags.inputUpdates.insert(flake::parseInputPath(ss[0])); + }); - std::unordered_set<std::string> seen; + mkFlag() + .longName("override-input") + .description("override a specific flake input (e.g. 'dwarffs/nixpkgs')") + .arity(2) + .labels({"input-path", "flake-url"}) + .handler([&](std::vector<std::string> ss) { + lockFlags.inputOverrides.insert_or_assign( + flake::parseInputPath(ss[0]), + parseFlakeRef(ss[1], absPath("."))); + }); +} - auto addEntry = [&](const std::string & name) { - if (name == "") return; - if (!seen.insert(name).second) return; - Value * v1 = state.allocValue(); - mkPrimOpApp(*v1, state.getBuiltin("findFile"), state.getBuiltin("nixPath")); - Value * v2 = state.allocValue(); - mkApp(*v2, *v1, mkString(*state.allocValue(), name)); - mkApp(*state.allocAttr(*vSourceExpr, state.symbols.create(name)), - state.getBuiltin("import"), *v2); - }; +SourceExprCommand::SourceExprCommand() +{ + mkFlag() + .shortName('f') + .longName("file") + .label("file") + .description("evaluate attributes from FILE") + .dest(&file); - for (auto & i : searchPath) - /* Hack to handle channels. */ - if (i.first.empty() && pathExists(i.second + "/manifest.nix")) { - for (auto & j : readDirectory(i.second)) - if (j.name != "manifest.nix" - && pathExists(fmt("%s/%s/default.nix", i.second, j.name))) - addEntry(j.name); - } else - addEntry(i.first); + mkFlag() + .longName("expr") + .label("expr") + .description("evaluate attributes from EXPR") + .dest(&expr); +} - vSourceExpr->attrs->sort(); - } +Strings SourceExprCommand::getDefaultFlakeAttrPaths() +{ + return {"defaultPackage." + settings.thisSystem.get()}; +} - return vSourceExpr; +Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes() +{ + return { + // As a convenience, look for the attribute in + // 'outputs.packages'. + "packages." + settings.thisSystem.get() + ".", + // As a temporary hack until Nixpkgs is properly converted + // to provide a clean 'packages' set, look in 'legacyPackages'. + "legacyPackages." + settings.thisSystem.get() + "." + }; } -ref<EvalState> SourceExprCommand::getEvalState() +ref<EvalState> EvalCommand::getEvalState() { if (!evalState) evalState = std::make_shared<EvalState>(searchPath, getStore()); @@ -87,6 +112,27 @@ Buildable Installable::toBuildable() return std::move(buildables[0]); } +App::App(EvalState & state, Value & vApp) +{ + state.forceAttrs(vApp); + + auto aType = vApp.attrs->need(state.sType); + if (state.forceStringNoCtx(*aType.value, *aType.pos) != "app") + throw Error("value does not have type 'app', at %s", *aType.pos); + + auto aProgram = vApp.attrs->need(state.symbols.create("program")); + program = state.forceString(*aProgram.value, context, *aProgram.pos); + + // FIXME: check that 'program' is in the closure of 'context'. + if (!state.store->isInStore(program)) + throw Error("app program '%s' is not in the Nix store", program); +} + +App Installable::toApp(EvalState & state) +{ + return App(state, *toValue(state).first); +} + struct InstallableStorePath : Installable { ref<Store> store; @@ -116,54 +162,63 @@ struct InstallableStorePath : Installable } }; -struct InstallableValue : Installable +std::vector<flake::EvalCache::Derivation> InstallableValue::toDerivations() { - SourceExprCommand & cmd; + auto state = cmd.getEvalState(); - InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { } + auto v = toValue(*state).first; - Buildables toBuildables() override - { - auto state = cmd.getEvalState(); + Bindings & autoArgs = *cmd.getAutoArgs(*state); - auto v = toValue(*state).first; + DrvInfos drvInfos; + getDerivations(*state, *v, "", autoArgs, drvInfos, false); - Bindings & autoArgs = *cmd.getAutoArgs(*state); + std::vector<flake::EvalCache::Derivation> res; + for (auto & drvInfo : drvInfos) { + res.push_back({ + state->store->parseStorePath(drvInfo.queryDrvPath()), + state->store->parseStorePath(drvInfo.queryOutPath()), + drvInfo.queryOutputName() + }); + } - DrvInfos drvs; - getDerivations(*state, *v, "", autoArgs, drvs, false); + return res; +} - Buildables res; +Buildables InstallableValue::toBuildables() +{ + auto state = cmd.getEvalState(); - StorePathSet drvPaths; + Buildables res; - for (auto & drv : drvs) { - Buildable b{.drvPath = state->store->parseStorePath(drv.queryDrvPath())}; - drvPaths.insert(b.drvPath->clone()); + StorePathSet drvPaths; - auto outputName = drv.queryOutputName(); - if (outputName == "") - throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath)); + for (auto & drv : toDerivations()) { + Buildable b{.drvPath = drv.drvPath.clone()}; + drvPaths.insert(drv.drvPath.clone()); - b.outputs.emplace(outputName, state->store->parseStorePath(drv.queryOutPath())); + auto outputName = drv.outputName; + if (outputName == "") + throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(*b.drvPath)); - res.push_back(std::move(b)); - } + b.outputs.emplace(outputName, drv.outPath.clone()); - // Hack to recognize .all: if all drvs have the same drvPath, - // merge the buildables. - if (drvPaths.size() == 1) { - Buildable b{.drvPath = drvPaths.begin()->clone()}; - for (auto & b2 : res) - for (auto & output : b2.outputs) - b.outputs.insert_or_assign(output.first, output.second.clone()); - Buildables bs; - bs.push_back(std::move(b)); - return bs; - } else - return res; + res.push_back(std::move(b)); } -}; + + // Hack to recognize .all: if all drvs have the same drvPath, + // merge the buildables. + if (drvPaths.size() == 1) { + Buildable b{.drvPath = drvPaths.begin()->clone()}; + for (auto & b2 : res) + for (auto & output : b2.outputs) + b.outputs.insert_or_assign(output.first, output.second.clone()); + Buildables bs; + bs.push_back(std::move(b)); + return bs; + } else + return res; +} struct InstallableExpr : InstallableValue { @@ -184,70 +239,201 @@ struct InstallableExpr : InstallableValue struct InstallableAttrPath : InstallableValue { + Value * v; std::string attrPath; - InstallableAttrPath(SourceExprCommand & cmd, const std::string & attrPath) - : InstallableValue(cmd), attrPath(attrPath) + InstallableAttrPath(SourceExprCommand & cmd, Value * v, const std::string & attrPath) + : InstallableValue(cmd), v(v), attrPath(attrPath) { } std::string what() override { return attrPath; } std::pair<Value *, Pos> toValue(EvalState & state) override { - auto source = cmd.getSourceExpr(state); + auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), *v); + state.forceValue(*vRes); + return {vRes, pos}; + } +}; - Bindings & autoArgs = *cmd.getAutoArgs(state); +std::vector<std::string> InstallableFlake::getActualAttrPaths() +{ + std::vector<std::string> res; - auto v = findAlongAttrPath(state, attrPath, autoArgs, *source).first; - state.forceValue(*v); + for (auto & prefix : prefixes) + res.push_back(prefix + *attrPaths.begin()); - return {v, noPos}; + for (auto & s : attrPaths) + res.push_back(s); + + return res; +} + +Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake) +{ + auto vFlake = state.allocValue(); + + callFlake(state, lockedFlake, *vFlake); + + auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs")); + assert(aOutputs); + + state.forceValue(*aOutputs->value); + + return aOutputs->value; +} + +std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> InstallableFlake::toDerivation() +{ + auto state = cmd.getEvalState(); + + auto lockedFlake = lockFlake(*state, flakeRef, cmd.lockFlags); + + Value * vOutputs = nullptr; + + auto emptyArgs = state->allocBindings(0); + + auto & evalCache = flake::EvalCache::singleton(); + + auto fingerprint = lockedFlake.getFingerprint(); + + for (auto & attrPath : getActualAttrPaths()) { + auto drv = evalCache.getDerivation(fingerprint, attrPath); + if (drv) { + if (state->store->isValidPath(drv->drvPath)) + return {attrPath, lockedFlake.flake.lockedRef, std::move(*drv)}; + } + + if (!vOutputs) + vOutputs = getFlakeOutputs(*state, lockedFlake); + + try { + auto * v = findAlongAttrPath(*state, attrPath, *emptyArgs, *vOutputs).first; + state->forceValue(*v); + + auto drvInfo = getDerivation(*state, *v, false); + if (!drvInfo) + throw Error("flake output attribute '%s' is not a derivation", attrPath); + + auto drv = flake::EvalCache::Derivation{ + state->store->parseStorePath(drvInfo->queryDrvPath()), + state->store->parseStorePath(drvInfo->queryOutPath()), + drvInfo->queryOutputName() + }; + + evalCache.addDerivation(fingerprint, attrPath, drv); + + return {attrPath, lockedFlake.flake.lockedRef, std::move(drv)}; + } catch (AttrPathNotFound & e) { + } } -}; + + throw Error("flake '%s' does not provide attribute %s", + flakeRef, concatStringsSep(", ", quoteStrings(attrPaths))); +} + +std::vector<flake::EvalCache::Derivation> InstallableFlake::toDerivations() +{ + std::vector<flake::EvalCache::Derivation> res; + res.push_back(std::get<2>(toDerivation())); + return res; +} + +std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state) +{ + auto lockedFlake = lockFlake(state, flakeRef, cmd.lockFlags); + + auto vOutputs = getFlakeOutputs(state, lockedFlake); + + auto emptyArgs = state.allocBindings(0); + + for (auto & attrPath : getActualAttrPaths()) { + try { + auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs); + state.forceValue(*v); + return {v, pos}; + } catch (AttrPathNotFound & e) { + } + } + + throw Error("flake '%s' does not provide attribute %s", + flakeRef, concatStringsSep(", ", quoteStrings(attrPaths))); +} // FIXME: extend std::string attrRegex = R"([A-Za-z_][A-Za-z0-9-_+]*)"; static std::regex attrPathRegex(fmt(R"(%1%(\.%1%)*)", attrRegex)); -static std::vector<std::shared_ptr<Installable>> parseInstallables( - SourceExprCommand & cmd, ref<Store> store, std::vector<std::string> ss, bool useDefaultInstallables) +std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables( + ref<Store> store, std::vector<std::string> ss) { std::vector<std::shared_ptr<Installable>> result; - if (ss.empty() && useDefaultInstallables) { - if (cmd.file == "") - cmd.file = "."; - ss = {""}; - } + if (file || expr) { + if (file && expr) + throw UsageError("'--file' and '--expr' are exclusive"); - for (auto & s : ss) { + // FIXME: backward compatibility hack + if (file) evalSettings.pureEval = false; - if (s.compare(0, 1, "(") == 0) - result.push_back(std::make_shared<InstallableExpr>(cmd, s)); + auto state = getEvalState(); + auto vFile = state->allocValue(); - else if (s.find("/") != std::string::npos) { + if (file) + state->evalFile(lookupFileArg(*state, *file), *vFile); + else { + auto e = state->parseExprFromString(*expr, absPath(".")); + state->eval(e, *vFile); + } - auto path = store->toStorePath(store->followLinksToStore(s)); + for (auto & s : ss) + result.push_back(std::make_shared<InstallableAttrPath>(*this, vFile, s == "." ? "" : s)); - if (store->isStorePath(path)) - result.push_back(std::make_shared<InstallableStorePath>(store, path)); - } + } else { - else if (s == "" || std::regex_match(s, attrPathRegex)) - result.push_back(std::make_shared<InstallableAttrPath>(cmd, s)); + auto follow = [&](const std::string & s) -> std::optional<StorePath> { + try { + return store->followLinksToStorePath(s); + } catch (NotInStore &) { + return {}; + } + }; - else - throw UsageError("don't know what to do with argument '%s'", s); + for (auto & s : ss) { + if (hasPrefix(s, "nixpkgs.")) { + bool static warned; + warnOnce(warned, "the syntax 'nixpkgs.<attr>' is deprecated; use 'nixpkgs#<attr>' instead"); + result.push_back(std::make_shared<InstallableFlake>(*this, + FlakeRef::fromAttrs({{"type", "indirect"}, {"id", "nixpkgs"}}), + Strings{"legacyPackages." + settings.thisSystem.get() + "." + std::string(s, 8)}, Strings{})); + } + + else { + auto res = maybeParseFlakeRefWithFragment(s, absPath(".")); + if (res) { + auto &[flakeRef, fragment] = *res; + result.push_back(std::make_shared<InstallableFlake>( + *this, std::move(flakeRef), + fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment}, + getDefaultFlakeAttrPathPrefixes())); + } else { + std::optional<StorePath> storePath; + if (s.find('/') != std::string::npos && (storePath = follow(s))) + result.push_back(std::make_shared<InstallableStorePath>(store, store->printStorePath(*storePath))); + else + throw Error("unrecognized argument '%s'", s); + } + } + } } return result; } -std::shared_ptr<Installable> parseInstallable( - SourceExprCommand & cmd, ref<Store> store, const std::string & installable, - bool useDefaultInstallables) +std::shared_ptr<Installable> SourceExprCommand::parseInstallable( + ref<Store> store, const std::string & installable) { - auto installables = parseInstallables(cmd, store, {installable}, false); + auto installables = parseInstallables(store, {installable}); assert(installables.size() == 1); return installables.front(); } @@ -302,7 +488,7 @@ StorePath toStorePath(ref<Store> store, RealiseMode mode, auto paths = toStorePaths(store, mode, {installable}); if (paths.size() != 1) - throw Error("argument '%s' should evaluate to one store path", installable->what()); + throw Error("argument '%s' should evaluate to one store path", installable->what()); return paths.begin()->clone(); } @@ -333,12 +519,16 @@ StorePathSet toDerivations(ref<Store> store, void InstallablesCommand::prepare() { - installables = parseInstallables(*this, getStore(), _installables, useDefaultInstallables()); + if (_installables.empty() && useDefaultInstallables()) + // FIXME: commands like "nix install" should not have a + // default, probably. + _installables.push_back("."); + installables = parseInstallables(getStore(), _installables); } void InstallableCommand::prepare() { - installable = parseInstallable(*this, getStore(), _installable, false); + installable = parseInstallable(getStore(), _installable); } } diff --git a/src/nix/installables.hh b/src/nix/installables.hh index 503984220..8f2d50077 100644 --- a/src/nix/installables.hh +++ b/src/nix/installables.hh @@ -3,11 +3,15 @@ #include "util.hh" #include "path.hh" #include "eval.hh" +#include "flake/eval-cache.hh" #include <optional> namespace nix { +struct DrvInfo; +struct SourceExprCommand; + struct Buildable { std::optional<StorePath> drvPath; @@ -16,6 +20,15 @@ struct Buildable typedef std::vector<Buildable> Buildables; +struct App +{ + PathSet context; + Path program; + // FIXME: add args, sandbox settings, metadata, ... + + App(EvalState & state, Value & vApp); +}; + struct Installable { virtual ~Installable() { } @@ -29,6 +42,8 @@ struct Installable Buildable toBuildable(); + App toApp(EvalState & state); + virtual std::pair<Value *, Pos> toValue(EvalState & state) { throw Error("argument '%s' cannot be evaluated", what()); @@ -42,4 +57,40 @@ struct Installable } }; +struct InstallableValue : Installable +{ + SourceExprCommand & cmd; + + InstallableValue(SourceExprCommand & cmd) : cmd(cmd) { } + + virtual std::vector<flake::EvalCache::Derivation> toDerivations(); + + Buildables toBuildables() override; +}; + +struct InstallableFlake : InstallableValue +{ + FlakeRef flakeRef; + Strings attrPaths; + Strings prefixes; + + InstallableFlake(SourceExprCommand & cmd, FlakeRef && flakeRef, + Strings && attrPaths, Strings && prefixes) + : InstallableValue(cmd), flakeRef(flakeRef), attrPaths(attrPaths), + prefixes(prefixes) + { } + + std::string what() override { return flakeRef.to_string() + "#" + *attrPaths.begin(); } + + std::vector<std::string> getActualAttrPaths(); + + Value * getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake); + + std::tuple<std::string, FlakeRef, flake::EvalCache::Derivation> toDerivation(); + + std::vector<flake::EvalCache::Derivation> toDerivations() override; + + std::pair<Value *, Pos> toValue(EvalState & state) override; +}; + } diff --git a/src/nix/local.mk b/src/nix/local.mk index 033675e89..3fcd15dc6 100644 --- a/src/nix/local.mk +++ b/src/nix/local.mk @@ -27,3 +27,5 @@ $(foreach name, \ $(eval $(call install-symlink, $(bindir)/nix, $(libexecdir)/nix/build-remote)) src/nix-env/user-env.cc: src/nix-env/buildenv.nix.gen.hh + +$(d)/flake.cc: $(d)/flake-template.nix.gen.hh diff --git a/src/nix/main.cc b/src/nix/main.cc index 2c64c7476..15f412dae 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -150,6 +150,7 @@ void mainWrapped(int argc, char * * argv) verbosity = lvlWarn; settings.verboseBuild = false; + evalSettings.pureEval = true; NixArgs args; diff --git a/src/nix/profile.cc b/src/nix/profile.cc new file mode 100644 index 000000000..c6a4ddd34 --- /dev/null +++ b/src/nix/profile.cc @@ -0,0 +1,427 @@ +#include "command.hh" +#include "common-args.hh" +#include "shared.hh" +#include "store-api.hh" +#include "derivations.hh" +#include "archive.hh" +#include "builtins/buildenv.hh" +#include "flake/flakeref.hh" +#include "../nix-env/user-env.hh" + +#include <nlohmann/json.hpp> +#include <regex> + +using namespace nix; + +struct ProfileElementSource +{ + FlakeRef originalRef; + // FIXME: record original attrpath. + FlakeRef resolvedRef; + std::string attrPath; + // FIXME: output names +}; + +struct ProfileElement +{ + StorePathSet storePaths; + std::optional<ProfileElementSource> source; + bool active = true; + // FIXME: priority +}; + +struct ProfileManifest +{ + std::vector<ProfileElement> elements; + + ProfileManifest() { } + + ProfileManifest(EvalState & state, const Path & profile) + { + auto manifestPath = profile + "/manifest.json"; + + if (pathExists(manifestPath)) { + auto json = nlohmann::json::parse(readFile(manifestPath)); + + auto version = json.value("version", 0); + if (version != 1) + throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version); + + for (auto & e : json["elements"]) { + ProfileElement element; + for (auto & p : e["storePaths"]) + element.storePaths.insert(state.store->parseStorePath((std::string) p)); + element.active = e["active"]; + if (e.value("uri", "") != "") { + element.source = ProfileElementSource{ + parseFlakeRef(e["originalUri"]), + parseFlakeRef(e["uri"]), + e["attrPath"] + }; + } + elements.emplace_back(std::move(element)); + } + } + + else if (pathExists(profile + "/manifest.nix")) { + // FIXME: needed because of pure mode; ugly. + if (state.allowedPaths) { + state.allowedPaths->insert(state.store->followLinksToStore(profile)); + state.allowedPaths->insert(state.store->followLinksToStore(profile + "/manifest.nix")); + } + + auto drvInfos = queryInstalled(state, state.store->followLinksToStore(profile)); + + for (auto & drvInfo : drvInfos) { + ProfileElement element; + element.storePaths = singleton(state.store->parseStorePath(drvInfo.queryOutPath())); + elements.emplace_back(std::move(element)); + } + } + } + + std::string toJSON(Store & store) const + { + auto array = nlohmann::json::array(); + for (auto & element : elements) { + auto paths = nlohmann::json::array(); + for (auto & path : element.storePaths) + paths.push_back(store.printStorePath(path)); + nlohmann::json obj; + obj["storePaths"] = paths; + obj["active"] = element.active; + if (element.source) { + obj["originalUri"] = element.source->originalRef.to_string(); + obj["uri"] = element.source->resolvedRef.to_string(); + obj["attrPath"] = element.source->attrPath; + } + array.push_back(obj); + } + nlohmann::json json; + json["version"] = 1; + json["elements"] = array; + return json.dump(); + } + + StorePath build(ref<Store> store) + { + auto tempDir = createTempDir(); + + StorePathSet references; + + Packages pkgs; + for (auto & element : elements) { + for (auto & path : element.storePaths) { + if (element.active) + pkgs.emplace_back(store->printStorePath(path), true, 5); + references.insert(path.clone()); + } + } + + buildProfile(tempDir, std::move(pkgs)); + + writeFile(tempDir + "/manifest.json", toJSON(*store)); + + /* Add the symlink tree to the store. */ + StringSink sink; + dumpPath(tempDir, sink); + + auto narHash = hashString(htSHA256, *sink.s); + + ValidPathInfo info(store->makeFixedOutputPath(true, narHash, "profile", references)); + info.references = std::move(references); + info.narHash = narHash; + info.narSize = sink.s->size(); + info.ca = makeFixedOutputCA(true, info.narHash); + + store->addToStore(info, sink.s); + + return std::move(info.path); + } +}; + +struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile +{ + std::string description() override + { + return "install a package into a profile"; + } + + Examples examples() override + { + return { + Example{ + "To install a package from Nixpkgs:", + "nix profile install nixpkgs#hello" + }, + Example{ + "To install a package from a specific branch of Nixpkgs:", + "nix profile install nixpkgs/release-19.09#hello" + }, + Example{ + "To install a package from a specific revision of Nixpkgs:", + "nix profile install nixpkgs/1028bb33859f8dfad7f98e1c8d185f3d1aaa7340#hello" + }, + }; + } + + void run(ref<Store> store) override + { + ProfileManifest manifest(*getEvalState(), *profile); + + std::vector<StorePathWithOutputs> pathsToBuild; + + for (auto & installable : installables) { + if (auto installable2 = std::dynamic_pointer_cast<InstallableFlake>(installable)) { + auto [attrPath, resolvedRef, drv] = installable2->toDerivation(); + + ProfileElement element; + element.storePaths = singleton(drv.outPath.clone()); // FIXME + element.source = ProfileElementSource{ + installable2->flakeRef, + resolvedRef, + attrPath, + }; + + pathsToBuild.emplace_back(drv.drvPath.clone(), StringSet{"out"}); // FIXME + + manifest.elements.emplace_back(std::move(element)); + } else + throw Error("'nix profile install' does not support argument '%s'", installable->what()); + } + + store->buildPaths(pathsToBuild); + + updateProfile(manifest.build(store)); + } +}; + +class MixProfileElementMatchers : virtual Args +{ + std::vector<std::string> _matchers; + +public: + + MixProfileElementMatchers() + { + expectArgs("elements", &_matchers); + } + + typedef std::variant<size_t, Path, std::regex> Matcher; + + std::vector<Matcher> getMatchers(ref<Store> store) + { + std::vector<Matcher> res; + + for (auto & s : _matchers) { + size_t n; + if (string2Int(s, n)) + res.push_back(n); + else if (store->isStorePath(s)) + res.push_back(s); + else + res.push_back(std::regex(s, std::regex::extended | std::regex::icase)); + } + + return res; + } + + bool matches(const Store & store, const ProfileElement & element, size_t pos, const std::vector<Matcher> & matchers) + { + for (auto & matcher : matchers) { + if (auto n = std::get_if<size_t>(&matcher)) { + 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)) { + if (element.source + && std::regex_match(element.source->attrPath, *regex)) + return true; + } + } + + return false; + } +}; + +struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElementMatchers +{ + std::string description() override + { + return "remove packages from a profile"; + } + + Examples examples() override + { + return { + Example{ + "To remove a package by attribute path:", + "nix profile remove packages.x86_64-linux.hello" + }, + Example{ + "To remove all packages:", + "nix profile remove '.*'" + }, + Example{ + "To remove a package by store path:", + "nix profile remove /nix/store/rr3y0c6zyk7kjjl8y19s4lsrhn4aiq1z-hello-2.10" + }, + Example{ + "To remove a package by position:", + "nix profile remove 3" + }, + }; + } + + void run(ref<Store> store) override + { + ProfileManifest oldManifest(*getEvalState(), *profile); + + auto matchers = getMatchers(store); + + ProfileManifest newManifest; + + for (size_t i = 0; i < oldManifest.elements.size(); ++i) { + auto & element(oldManifest.elements[i]); + if (!matches(*store, element, i, matchers)) + newManifest.elements.push_back(std::move(element)); + } + + // FIXME: warn about unused matchers? + + printInfo("removed %d packages, kept %d packages", + oldManifest.elements.size() - newManifest.elements.size(), + newManifest.elements.size()); + + updateProfile(newManifest.build(store)); + } +}; + +struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProfileElementMatchers +{ + std::string description() override + { + return "upgrade packages using their most recent flake"; + } + + Examples examples() override + { + return { + Example{ + "To upgrade all packages that were installed using a mutable flake reference:", + "nix profile upgrade '.*'" + }, + Example{ + "To upgrade a specific package:", + "nix profile upgrade packages.x86_64-linux.hello" + }, + }; + } + + void run(ref<Store> store) override + { + ProfileManifest manifest(*getEvalState(), *profile); + + auto matchers = getMatchers(store); + + // FIXME: code duplication + std::vector<StorePathWithOutputs> pathsToBuild; + + for (size_t i = 0; i < manifest.elements.size(); ++i) { + auto & element(manifest.elements[i]); + if (element.source + && !element.source->originalRef.input->isImmutable() + && matches(*store, element, i, matchers)) + { + Activity act(*logger, lvlChatty, actUnknown, + fmt("checking '%s' for updates", element.source->attrPath)); + + InstallableFlake installable(*this, FlakeRef(element.source->originalRef), {element.source->attrPath}, {}); + + auto [attrPath, resolvedRef, drv] = installable.toDerivation(); + + if (element.source->resolvedRef == resolvedRef) continue; + + printInfo("upgrading '%s' from flake '%s' to '%s'", + element.source->attrPath, element.source->resolvedRef, resolvedRef); + + element.storePaths = singleton(drv.outPath.clone()); // FIXME + element.source = ProfileElementSource{ + installable.flakeRef, + resolvedRef, + attrPath, + }; + + pathsToBuild.emplace_back(drv.drvPath, StringSet{"out"}); // FIXME + } + } + + store->buildPaths(pathsToBuild); + + updateProfile(manifest.build(store)); + } +}; + +struct CmdProfileInfo : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile +{ + std::string description() override + { + return "list installed packages"; + } + + Examples examples() override + { + return { + Example{ + "To show what packages are installed in the default profile:", + "nix profile info" + }, + }; + } + + void run(ref<Store> store) override + { + ProfileManifest manifest(*getEvalState(), *profile); + + for (size_t i = 0; i < manifest.elements.size(); ++i) { + auto & element(manifest.elements[i]); + std::cout << fmt("%d %s %s %s\n", i, + element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath : "-", + element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath : "-", + concatStringsSep(" ", store->printStorePathSet(element.storePaths))); + } + } +}; + +struct CmdProfile : virtual MultiCommand, virtual Command +{ + CmdProfile() + : MultiCommand({ + {"install", []() { return make_ref<CmdProfileInstall>(); }}, + {"remove", []() { return make_ref<CmdProfileRemove>(); }}, + {"upgrade", []() { return make_ref<CmdProfileUpgrade>(); }}, + {"info", []() { return make_ref<CmdProfileInfo>(); }}, + }) + { } + + std::string description() override + { + return "manage Nix profiles"; + } + + void run() override + { + if (!command) + throw UsageError("'nix profile' requires a sub-command."); + command->prepare(); + command->run(); + } + + void printHelp(const string & programName, std::ostream & out) override + { + MultiCommand::printHelp(programName, out); + } +}; + +static auto r1 = registerCommand<CmdProfile>("profile"); + diff --git a/src/nix/repl.cc b/src/nix/repl.cc index 27727bd25..a2632cff8 100644 --- a/src/nix/repl.cc +++ b/src/nix/repl.cc @@ -811,6 +811,7 @@ struct CmdRepl : StoreCommand, MixEvalArgs void run(ref<Store> store) override { + evalSettings.pureEval = false; auto repl = std::make_unique<NixRepl>(searchPath, openStore()); repl->autoArgs = getAutoArgs(*repl->state); repl->mainLoop(files); diff --git a/src/nix/run.cc b/src/nix/run.cc index 5334531fd..901b87fbb 100644 --- a/src/nix/run.cc +++ b/src/nix/run.cc @@ -89,15 +89,15 @@ struct CmdRun : InstallablesCommand, RunCommon, MixEnvironment }, Example{ "To start a shell providing youtube-dl from your 'nixpkgs' channel:", - "nix run nixpkgs.youtube-dl" + "nix run nixpkgs#youtube-dl" }, Example{ "To run GNU Hello:", - "nix run nixpkgs.hello -c hello --greeting 'Hi everybody!'" + "nix run nixpkgs#hello -c hello --greeting 'Hi everybody!'" }, Example{ "To run GNU Hello in a chroot store:", - "nix run --store ~/my-nix nixpkgs.hello -c hello" + "nix run --store ~/my-nix nixpkgs#hello -c hello" }, }; } @@ -143,6 +143,57 @@ struct CmdRun : InstallablesCommand, RunCommon, MixEnvironment static auto r1 = registerCommand<CmdRun>("run"); +struct CmdApp : InstallableCommand, RunCommon +{ + std::vector<std::string> args; + + CmdApp() + { + expectArgs("args", &args); + } + + std::string description() override + { + return "run a Nix application"; + } + + Examples examples() override + { + return { + Example{ + "To run Blender:", + "nix app blender-bin" + }, + }; + } + + Strings getDefaultFlakeAttrPaths() override + { + return {"defaultApp." + settings.thisSystem.get()}; + } + + Strings getDefaultFlakeAttrPathPrefixes() override + { + return {"apps." + settings.thisSystem.get() + "."}; + } + + void run(ref<Store> store) override + { + auto state = getEvalState(); + + auto app = installable->toApp(*state); + + state->realiseContext(app.context); + + Strings allArgs{app.program}; + for (auto & i : args) allArgs.push_back(i); + + runProgram(store, app.program, allArgs); + } +}; + +static auto r2 = registerCommand<CmdApp>("app"); + void chrootHelper(int argc, char * * argv) { int p = 1; diff --git a/src/nix/search.cc b/src/nix/search.cc index 769274543..caea25cdc 100644 --- a/src/nix/search.cc +++ b/src/nix/search.cc @@ -248,7 +248,9 @@ struct CmdSearch : SourceExprCommand, MixJSON auto cache = writeCache ? std::make_unique<JSONObject>(jsonCacheFile, false) : nullptr; - doExpr(getSourceExpr(*state), "", true, cache.get()); + // FIXME + throw Error("NOT IMPLEMENTED"); + //doExpr(getSourceExpr(*state), "", true, cache.get()); } catch (std::exception &) { /* Fun fact: catching std::ios::failure does not work diff --git a/src/nix/shell.cc b/src/nix/shell.cc index 71e640667..439ef02ed 100644 --- a/src/nix/shell.cc +++ b/src/nix/shell.cc @@ -182,6 +182,11 @@ struct Common : InstallableCommand, MixProfile out << "eval \"$shellHook\"\n"; } + Strings getDefaultFlakeAttrPaths() override + { + return {"devShell." + settings.thisSystem.get(), "defaultPackage." + settings.thisSystem.get()}; + } + StorePath getShellOutPath(ref<Store> store) { auto path = installable->getStorePath(); @@ -238,11 +243,15 @@ struct CmdDevShell : Common, MixEnvironment return { Example{ "To get the build environment of GNU hello:", - "nix dev-shell nixpkgs.hello" + "nix dev-shell nixpkgs#hello" + }, + Example{ + "To get the build environment of the default package of flake in the current directory:", + "nix dev-shell" }, Example{ "To store the build environment in a profile:", - "nix dev-shell --profile /tmp/my-shell nixpkgs.hello" + "nix dev-shell --profile /tmp/my-shell nixpkgs#hello" }, Example{ "To use a build environment previously recorded in a profile:", @@ -300,7 +309,7 @@ struct CmdPrintDevEnv : Common return { Example{ "To apply the build environment of GNU hello to the current shell:", - ". <(nix print-dev-env nixpkgs.hello)" + ". <(nix print-dev-env nixpkgs#hello)" }, }; } diff --git a/src/nix/why-depends.cc b/src/nix/why-depends.cc index d3b7a674a..a9a5ec8f8 100644 --- a/src/nix/why-depends.cc +++ b/src/nix/why-depends.cc @@ -69,9 +69,9 @@ struct CmdWhyDepends : SourceExprCommand void run(ref<Store> store) override { - auto package = parseInstallable(*this, store, _package, false); + auto package = parseInstallable(store, _package); auto packagePath = toStorePath(store, Build, package); - auto dependency = parseInstallable(*this, store, _dependency, false); + auto dependency = parseInstallable(store, _dependency); auto dependencyPath = toStorePath(store, NoBuild, dependency); auto dependencyPathHash = storePathToHash(store->printStorePath(dependencyPath)); |