From ce279209363cb6a9cbbe68a13fab9d8550b721f3 Mon Sep 17 00:00:00 2001 From: Eelco Dolstra Date: Tue, 22 Oct 2019 00:21:58 +0200 Subject: [PATCH] Add start of 'nix profile' command --- src/nix/command.cc | 5 + src/nix/command.hh | 5 + src/nix/flake.cc | 1 + src/nix/installables.cc | 11 +- src/nix/installables.hh | 3 + src/nix/profile.cc | 234 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 256 insertions(+), 3 deletions(-) create mode 100644 src/nix/profile.cc diff --git a/src/nix/command.cc b/src/nix/command.cc index 57f3754cc..1cb4cc92a 100644 --- a/src/nix/command.cc +++ b/src/nix/command.cc @@ -123,4 +123,9 @@ void MixProfile::updateProfile(const Buildables & buildables) updateProfile(*result); } +MixDefaultProfile::MixDefaultProfile() +{ + profile = getDefaultProfile(); +} + } diff --git a/src/nix/command.hh b/src/nix/command.hh index 546c27a71..ef29381cf 100644 --- a/src/nix/command.hh +++ b/src/nix/command.hh @@ -188,4 +188,9 @@ struct MixProfile : virtual Args, virtual StoreCommand void updateProfile(const Buildables & buildables); }; +struct MixDefaultProfile : MixProfile +{ + MixDefaultProfile(); +}; + } diff --git a/src/nix/flake.cc b/src/nix/flake.cc index d928af3b9..6e7c5e2eb 100644 --- a/src/nix/flake.cc +++ b/src/nix/flake.cc @@ -653,6 +653,7 @@ struct CmdFlake : virtual MultiCommand, virtual Command { if (!command) throw UsageError("'nix flake' requires a sub-command."); + command->prepare(); command->run(); } diff --git a/src/nix/installables.cc b/src/nix/installables.cc index 38f37adb1..671cf513a 100644 --- a/src/nix/installables.cc +++ b/src/nix/installables.cc @@ -294,7 +294,7 @@ Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::Resolv return (*aOutputs)->value; } -std::vector InstallableFlake::toDerivations() +std::tuple InstallableFlake::toDerivation() { auto state = cmd.getEvalState(); @@ -312,7 +312,7 @@ std::vector InstallableFlake::toDerivations() auto drv = evalCache.getDerivation(fingerprint, attrPath); if (drv) { if (state->store->isValidPath(drv->drvPath)) - return {*drv}; + return {attrPath, resFlake.flake.sourceInfo.resolvedRef, *drv}; } if (!vOutputs) @@ -334,7 +334,7 @@ std::vector InstallableFlake::toDerivations() evalCache.addDerivation(fingerprint, attrPath, drv); - return {drv}; + return {attrPath, resFlake.flake.sourceInfo.resolvedRef, drv}; } catch (AttrPathNotFound & e) { } } @@ -343,6 +343,11 @@ std::vector InstallableFlake::toDerivations() flakeRef, concatStringsSep(", ", quoteStrings(attrPaths))); } +std::vector InstallableFlake::toDerivations() +{ + return {std::get<2>(toDerivation())}; +} + Value * InstallableFlake::toValue(EvalState & state) { auto resFlake = resolveFlake(state, flakeRef, cmd.getLockFileMode()); diff --git a/src/nix/installables.hh b/src/nix/installables.hh index a635cb96f..9388c673e 100644 --- a/src/nix/installables.hh +++ b/src/nix/installables.hh @@ -8,6 +8,7 @@ namespace nix { struct Value; +struct DrvInfo; class EvalState; class SourceExprCommand; @@ -89,6 +90,8 @@ struct InstallableFlake : InstallableValue Value * getFlakeOutputs(EvalState & state, const flake::ResolvedFlake & resFlake); + std::tuple toDerivation(); + std::vector toDerivations() override; Value * toValue(EvalState & state) override; diff --git a/src/nix/profile.cc b/src/nix/profile.cc new file mode 100644 index 000000000..bc5c3870e --- /dev/null +++ b/src/nix/profile.cc @@ -0,0 +1,234 @@ +#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 + +using namespace nix; + +struct ProfileElementSource +{ + FlakeRef originalRef; + FlakeRef resolvedRef; + std::string attrPath; + // FIXME: output names +}; + +struct ProfileElement +{ + PathSet storePaths; + std::optional source; + bool active = true; + // FIXME: priority +}; + +struct ProfileManifest +{ + std::vector elements; + + ProfileManifest(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((std::string) p); + element.active = e["active"]; + if (e.value("uri", "") != "") { + element.source = ProfileElementSource{ + FlakeRef(e["originalUri"]), + FlakeRef(e["uri"]), + e["attrPath"] + }; + } + elements.emplace_back(std::move(element)); + } + } + } + + std::string toJSON() const + { + auto array = nlohmann::json::array(); + for (auto & element : elements) { + auto paths = nlohmann::json::array(); + for (auto & path : element.storePaths) + paths.push_back(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(); + } + + Path build(ref store) + { + auto tempDir = createTempDir(); + + ValidPathInfo info; + + Packages pkgs; + for (auto & element : elements) { + for (auto & path : element.storePaths) { + if (element.active) + pkgs.emplace_back(path, true, 5); + info.references.insert(path); + } + } + + buildProfile(tempDir, std::move(pkgs)); + + writeFile(tempDir + "/manifest.json", toJSON()); + + /* Add the symlink tree to the store. */ + StringSink sink; + dumpPath(tempDir, sink); + + info.narHash = hashString(htSHA256, *sink.s); + info.narSize = sink.s->size(); + info.path = store->makeFixedOutputPath(true, info.narHash, "profile", info.references); + info.ca = makeFixedOutputCA(true, info.narHash); + + store->addToStore(info, sink.s); + + return 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) override + { + ProfileManifest manifest(*profile); + + PathSet pathsToBuild; + + for (auto & installable : installables) { + if (auto installable2 = std::dynamic_pointer_cast(installable)) { + auto [attrPath, resolvedRef, drv] = installable2->toDerivation(); + + ProfileElement element; + element.storePaths = {drv.outPath}; // FIXME + element.source = ProfileElementSource{ + installable2->flakeRef, + resolvedRef, + attrPath, + }; + + pathsToBuild.insert(makeDrvPathWithOutputs(drv.drvPath, {"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)); + } +}; + +struct CmdProfileInfo : virtual StoreCommand, MixDefaultProfile +{ + std::string description() override + { + return "info"; + } + + Examples examples() override + { + return { + Example{ + "To show what packages are installed in the default profile:", + "nix profile info" + }, + }; + } + + void run(ref store) override + { + ProfileManifest manifest(*profile); + + for (auto & element : manifest.elements) { + std::cout << fmt("%s %s\n", + element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath : "-", + element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath : "-", + concatStringsSep(" ", element.storePaths)); + } + } +}; + +struct CmdProfile : virtual MultiCommand, virtual Command +{ + CmdProfile() + : MultiCommand({ + {"install", []() { return make_ref(); }}, + {"info", []() { return make_ref(); }}, + }) + { } + + 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("profile"); +