2019-10-22 01:21:58 +03:00
|
|
|
#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"
|
2020-03-30 15:29:29 +03:00
|
|
|
#include "../nix-env/user-env.hh"
|
2020-07-16 18:00:42 +03:00
|
|
|
#include "profiles.hh"
|
2019-10-22 01:21:58 +03:00
|
|
|
|
|
|
|
#include <nlohmann/json.hpp>
|
2019-10-22 14:06:32 +03:00
|
|
|
#include <regex>
|
2019-10-22 01:21:58 +03:00
|
|
|
|
|
|
|
using namespace nix;
|
|
|
|
|
|
|
|
struct ProfileElementSource
|
|
|
|
{
|
|
|
|
FlakeRef originalRef;
|
2019-10-22 01:28:16 +03:00
|
|
|
// FIXME: record original attrpath.
|
2019-10-22 01:21:58 +03:00
|
|
|
FlakeRef resolvedRef;
|
|
|
|
std::string attrPath;
|
|
|
|
// FIXME: output names
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ProfileElement
|
|
|
|
{
|
2019-12-11 15:53:30 +02:00
|
|
|
StorePathSet storePaths;
|
2019-10-22 01:21:58 +03:00
|
|
|
std::optional<ProfileElementSource> source;
|
|
|
|
bool active = true;
|
|
|
|
// FIXME: priority
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ProfileManifest
|
|
|
|
{
|
|
|
|
std::vector<ProfileElement> elements;
|
|
|
|
|
2019-10-22 14:06:32 +03:00
|
|
|
ProfileManifest() { }
|
|
|
|
|
2019-10-22 16:16:57 +03:00
|
|
|
ProfileManifest(EvalState & state, const Path & profile)
|
2019-10-22 01:21:58 +03:00
|
|
|
{
|
|
|
|
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"])
|
2019-12-11 15:53:30 +02:00
|
|
|
element.storePaths.insert(state.store->parseStorePath((std::string) p));
|
2019-10-22 01:21:58 +03:00
|
|
|
element.active = e["active"];
|
|
|
|
if (e.value("uri", "") != "") {
|
|
|
|
element.source = ProfileElementSource{
|
2020-01-21 17:27:53 +02:00
|
|
|
parseFlakeRef(e["originalUri"]),
|
|
|
|
parseFlakeRef(e["uri"]),
|
2019-10-22 01:21:58 +03:00
|
|
|
e["attrPath"]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
elements.emplace_back(std::move(element));
|
|
|
|
}
|
|
|
|
}
|
2019-10-22 16:16:57 +03:00
|
|
|
|
|
|
|
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;
|
2020-06-17 11:26:52 +03:00
|
|
|
element.storePaths = {state.store->parseStorePath(drvInfo.queryOutPath())};
|
2019-10-22 16:16:57 +03:00
|
|
|
elements.emplace_back(std::move(element));
|
|
|
|
}
|
|
|
|
}
|
2019-10-22 01:21:58 +03:00
|
|
|
}
|
|
|
|
|
2019-12-11 15:53:30 +02:00
|
|
|
std::string toJSON(Store & store) const
|
2019-10-22 01:21:58 +03:00
|
|
|
{
|
|
|
|
auto array = nlohmann::json::array();
|
|
|
|
for (auto & element : elements) {
|
|
|
|
auto paths = nlohmann::json::array();
|
|
|
|
for (auto & path : element.storePaths)
|
2019-12-11 15:53:30 +02:00
|
|
|
paths.push_back(store.printStorePath(path));
|
2019-10-22 01:21:58 +03:00
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2019-12-11 15:53:30 +02:00
|
|
|
StorePath build(ref<Store> store)
|
2019-10-22 01:21:58 +03:00
|
|
|
{
|
|
|
|
auto tempDir = createTempDir();
|
|
|
|
|
2019-12-11 15:53:30 +02:00
|
|
|
StorePathSet references;
|
2019-10-22 01:21:58 +03:00
|
|
|
|
|
|
|
Packages pkgs;
|
|
|
|
for (auto & element : elements) {
|
|
|
|
for (auto & path : element.storePaths) {
|
|
|
|
if (element.active)
|
2019-12-11 15:53:30 +02:00
|
|
|
pkgs.emplace_back(store->printStorePath(path), true, 5);
|
2020-06-17 11:26:52 +03:00
|
|
|
references.insert(path);
|
2019-10-22 01:21:58 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buildProfile(tempDir, std::move(pkgs));
|
|
|
|
|
2019-12-11 15:53:30 +02:00
|
|
|
writeFile(tempDir + "/manifest.json", toJSON(*store));
|
2019-10-22 01:21:58 +03:00
|
|
|
|
|
|
|
/* Add the symlink tree to the store. */
|
|
|
|
StringSink sink;
|
|
|
|
dumpPath(tempDir, sink);
|
|
|
|
|
2019-12-15 00:09:57 +02:00
|
|
|
auto narHash = hashString(htSHA256, *sink.s);
|
|
|
|
|
2020-08-06 21:31:48 +03:00
|
|
|
ValidPathInfo info {
|
|
|
|
store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, "profile", references),
|
|
|
|
narHash,
|
|
|
|
};
|
2019-12-11 15:53:30 +02:00
|
|
|
info.references = std::move(references);
|
2019-10-22 01:21:58 +03:00
|
|
|
info.narSize = sink.s->size();
|
2020-08-05 21:42:48 +03:00
|
|
|
info.ca = FixedOutputHash { .method = FileIngestionMethod::Recursive, .hash = info.narHash };
|
2019-10-22 01:21:58 +03:00
|
|
|
|
2020-06-03 17:15:22 +03:00
|
|
|
auto source = StringSource { *sink.s };
|
|
|
|
store->addToStore(info, source);
|
2019-10-22 01:21:58 +03:00
|
|
|
|
2019-12-11 15:53:30 +02:00
|
|
|
return std::move(info.path);
|
2019-10-22 01:21:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "install a package into a profile";
|
|
|
|
}
|
|
|
|
|
2020-12-18 15:25:36 +02:00
|
|
|
std::string doc() override
|
2019-10-22 01:21:58 +03:00
|
|
|
{
|
2020-12-18 15:25:36 +02:00
|
|
|
return
|
|
|
|
#include "profile-install.md"
|
|
|
|
;
|
2019-10-22 01:21:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-10-22 16:16:57 +03:00
|
|
|
ProfileManifest manifest(*getEvalState(), *profile);
|
2019-10-22 01:21:58 +03:00
|
|
|
|
2019-12-11 15:53:30 +02:00
|
|
|
std::vector<StorePathWithOutputs> pathsToBuild;
|
2019-10-22 01:21:58 +03:00
|
|
|
|
|
|
|
for (auto & installable : installables) {
|
|
|
|
if (auto installable2 = std::dynamic_pointer_cast<InstallableFlake>(installable)) {
|
|
|
|
auto [attrPath, resolvedRef, drv] = installable2->toDerivation();
|
|
|
|
|
|
|
|
ProfileElement element;
|
2020-08-07 22:09:26 +03:00
|
|
|
if (!drv.outPath)
|
|
|
|
throw UnimplementedError("CA derivations are not yet supported by 'nix profile'");
|
|
|
|
element.storePaths = {*drv.outPath}; // FIXME
|
2019-10-22 01:21:58 +03:00
|
|
|
element.source = ProfileElementSource{
|
|
|
|
installable2->flakeRef,
|
|
|
|
resolvedRef,
|
|
|
|
attrPath,
|
|
|
|
};
|
|
|
|
|
2020-06-17 11:26:52 +03:00
|
|
|
pathsToBuild.push_back({drv.drvPath, StringSet{"out"}}); // FIXME
|
2019-10-22 01:21:58 +03:00
|
|
|
|
|
|
|
manifest.elements.emplace_back(std::move(element));
|
|
|
|
} else
|
2020-08-07 22:09:26 +03:00
|
|
|
throw UnimplementedError("'nix profile install' does not support argument '%s'", installable->what());
|
2019-10-22 01:21:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
store->buildPaths(pathsToBuild);
|
|
|
|
|
|
|
|
updateProfile(manifest.build(store));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-10-22 14:06:32 +03:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-12-11 15:53:30 +02:00
|
|
|
bool matches(const Store & store, const ProfileElement & element, size_t pos, const std::vector<Matcher> & matchers)
|
2019-10-22 14:06:32 +03:00
|
|
|
{
|
|
|
|
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)) {
|
2019-12-11 15:53:30 +02:00
|
|
|
if (element.storePaths.count(store.parseStorePath(*path))) return true;
|
2019-10-22 14:06:32 +03:00
|
|
|
} else if (auto regex = std::get_if<std::regex>(&matcher)) {
|
|
|
|
if (element.source
|
|
|
|
&& std::regex_match(element.source->attrPath, *regex))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-10-22 16:16:57 +03:00
|
|
|
struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElementMatchers
|
2019-10-22 14:06:32 +03:00
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "remove packages from a profile";
|
|
|
|
}
|
|
|
|
|
2020-12-18 15:25:36 +02:00
|
|
|
std::string doc() override
|
2019-10-22 14:06:32 +03:00
|
|
|
{
|
2020-12-18 15:25:36 +02:00
|
|
|
return
|
|
|
|
#include "profile-remove.md"
|
|
|
|
;
|
2019-10-22 14:06:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-10-22 16:16:57 +03:00
|
|
|
ProfileManifest oldManifest(*getEvalState(), *profile);
|
2019-10-22 14:06:32 +03:00
|
|
|
|
|
|
|
auto matchers = getMatchers(store);
|
|
|
|
|
|
|
|
ProfileManifest newManifest;
|
|
|
|
|
|
|
|
for (size_t i = 0; i < oldManifest.elements.size(); ++i) {
|
|
|
|
auto & element(oldManifest.elements[i]);
|
2019-12-11 15:53:30 +02:00
|
|
|
if (!matches(*store, element, i, matchers))
|
|
|
|
newManifest.elements.push_back(std::move(element));
|
2019-10-22 14:06:32 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-10-22 15:44:51 +03:00
|
|
|
struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProfileElementMatchers
|
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "upgrade packages using their most recent flake";
|
|
|
|
}
|
|
|
|
|
2020-12-18 15:25:36 +02:00
|
|
|
std::string doc() override
|
2019-10-22 15:44:51 +03:00
|
|
|
{
|
2020-12-18 15:25:36 +02:00
|
|
|
return
|
|
|
|
#include "profile-upgrade.md"
|
|
|
|
;
|
2019-10-22 15:44:51 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-10-22 16:16:57 +03:00
|
|
|
ProfileManifest manifest(*getEvalState(), *profile);
|
2019-10-22 15:44:51 +03:00
|
|
|
|
|
|
|
auto matchers = getMatchers(store);
|
|
|
|
|
|
|
|
// FIXME: code duplication
|
2019-12-11 15:53:30 +02:00
|
|
|
std::vector<StorePathWithOutputs> pathsToBuild;
|
2019-10-22 15:44:51 +03:00
|
|
|
|
|
|
|
for (size_t i = 0; i < manifest.elements.size(); ++i) {
|
|
|
|
auto & element(manifest.elements[i]);
|
|
|
|
if (element.source
|
Remove TreeInfo
The attributes previously stored in TreeInfo (narHash, revCount,
lastModified) are now stored in Input. This makes it less arbitrary
what attributes are stored where.
As a result, the lock file format has changed. An entry like
"info": {
"lastModified": 1585405475,
"narHash": "sha256-bESW0n4KgPmZ0luxvwJ+UyATrC6iIltVCsGdLiphVeE="
},
"locked": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b88ff468e9850410070d4e0ccd68c7011f15b2be",
"type": "github"
},
is now stored as
"locked": {
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "b88ff468e9850410070d4e0ccd68c7011f15b2be",
"type": "github",
"lastModified": 1585405475,
"narHash": "sha256-bESW0n4KgPmZ0luxvwJ+UyATrC6iIltVCsGdLiphVeE="
},
The 'Input' class is now a dumb set of attributes. All the fetcher
implementations subclass InputScheme, not Input. This simplifies the
API.
Also, fix substitution of flake inputs. This was broken since lazy
flake fetching started using fetchTree internally.
2020-05-30 01:44:11 +03:00
|
|
|
&& !element.source->originalRef.input.isImmutable()
|
2019-12-11 15:53:30 +02:00
|
|
|
&& matches(*store, element, i, matchers))
|
2019-10-22 15:44:51 +03:00
|
|
|
{
|
|
|
|
Activity act(*logger, lvlChatty, actUnknown,
|
|
|
|
fmt("checking '%s' for updates", element.source->attrPath));
|
|
|
|
|
2020-05-09 18:35:33 +03:00
|
|
|
InstallableFlake installable(getEvalState(), FlakeRef(element.source->originalRef), {element.source->attrPath}, {}, lockFlags);
|
2019-10-22 15:44:51 +03:00
|
|
|
|
|
|
|
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);
|
|
|
|
|
2020-08-07 22:09:26 +03:00
|
|
|
if (!drv.outPath)
|
|
|
|
throw UnimplementedError("CA derivations are not yet supported by 'nix profile'");
|
|
|
|
element.storePaths = {*drv.outPath}; // FIXME
|
2019-10-22 15:44:51 +03:00
|
|
|
element.source = ProfileElementSource{
|
|
|
|
installable.flakeRef,
|
|
|
|
resolvedRef,
|
|
|
|
attrPath,
|
|
|
|
};
|
|
|
|
|
2020-06-17 11:26:52 +03:00
|
|
|
pathsToBuild.push_back({drv.drvPath, StringSet{"out"}}); // FIXME
|
2019-10-22 15:44:51 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
store->buildPaths(pathsToBuild);
|
|
|
|
|
|
|
|
updateProfile(manifest.build(store));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-10-22 16:16:57 +03:00
|
|
|
struct CmdProfileInfo : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile
|
2019-10-22 01:21:58 +03:00
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
2019-10-22 14:06:32 +03:00
|
|
|
return "list installed packages";
|
2019-10-22 01:21:58 +03:00
|
|
|
}
|
|
|
|
|
2020-12-18 15:25:36 +02:00
|
|
|
std::string doc() override
|
2019-10-22 01:21:58 +03:00
|
|
|
{
|
2020-12-18 15:25:36 +02:00
|
|
|
return
|
|
|
|
#include "profile-info.md"
|
|
|
|
;
|
2019-10-22 01:21:58 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-10-22 16:16:57 +03:00
|
|
|
ProfileManifest manifest(*getEvalState(), *profile);
|
2019-10-22 01:21:58 +03:00
|
|
|
|
2019-10-22 01:28:16 +03:00
|
|
|
for (size_t i = 0; i < manifest.elements.size(); ++i) {
|
|
|
|
auto & element(manifest.elements[i]);
|
2020-09-25 18:30:04 +03:00
|
|
|
logger->cout("%d %s %s %s", i,
|
2019-10-22 01:21:58 +03:00
|
|
|
element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath : "-",
|
|
|
|
element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath : "-",
|
2019-12-11 15:53:30 +02:00
|
|
|
concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
|
2019-10-22 01:21:58 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-07-17 13:36:12 +03:00
|
|
|
struct CmdProfileDiffClosures : virtual StoreCommand, MixDefaultProfile
|
2020-07-16 18:00:42 +03:00
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
2020-12-18 15:25:36 +02:00
|
|
|
return "show the closure difference between each version of a profile";
|
2020-07-16 18:00:42 +03:00
|
|
|
}
|
|
|
|
|
2020-12-18 15:25:36 +02:00
|
|
|
std::string doc() override
|
2020-07-16 18:00:42 +03:00
|
|
|
{
|
2020-12-18 15:25:36 +02:00
|
|
|
return
|
|
|
|
#include "profile-diff-closures.md"
|
|
|
|
;
|
2020-07-16 18:00:42 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
|
|
|
auto [gens, curGen] = findGenerations(*profile);
|
|
|
|
|
|
|
|
std::optional<Generation> prevGen;
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
for (auto & gen : gens) {
|
|
|
|
if (prevGen) {
|
|
|
|
if (!first) std::cout << "\n";
|
|
|
|
first = false;
|
2020-12-18 15:25:36 +02:00
|
|
|
std::cout << fmt("Version %d -> %d:\n", prevGen->number, gen.number);
|
2020-07-16 18:00:42 +03:00
|
|
|
printClosureDiff(store,
|
|
|
|
store->followLinksToStorePath(prevGen->path),
|
|
|
|
store->followLinksToStorePath(gen.path),
|
|
|
|
" ");
|
|
|
|
}
|
|
|
|
|
|
|
|
prevGen = gen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-17 18:44:52 +03:00
|
|
|
struct CmdProfile : NixMultiCommand
|
2019-10-22 01:21:58 +03:00
|
|
|
{
|
|
|
|
CmdProfile()
|
|
|
|
: MultiCommand({
|
|
|
|
{"install", []() { return make_ref<CmdProfileInstall>(); }},
|
2019-10-22 14:06:32 +03:00
|
|
|
{"remove", []() { return make_ref<CmdProfileRemove>(); }},
|
2019-10-22 15:44:51 +03:00
|
|
|
{"upgrade", []() { return make_ref<CmdProfileUpgrade>(); }},
|
2019-10-22 01:21:58 +03:00
|
|
|
{"info", []() { return make_ref<CmdProfileInfo>(); }},
|
2020-07-16 18:00:42 +03:00
|
|
|
{"diff-closures", []() { return make_ref<CmdProfileDiffClosures>(); }},
|
2019-10-22 01:21:58 +03:00
|
|
|
})
|
|
|
|
{ }
|
|
|
|
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "manage Nix profiles";
|
|
|
|
}
|
|
|
|
|
2020-12-18 15:25:36 +02:00
|
|
|
std::string doc() override
|
|
|
|
{
|
|
|
|
return
|
|
|
|
#include "profile.md"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2019-10-22 01:21:58 +03:00
|
|
|
void run() override
|
|
|
|
{
|
|
|
|
if (!command)
|
|
|
|
throw UsageError("'nix profile' requires a sub-command.");
|
2020-05-05 19:59:33 +03:00
|
|
|
command->second->prepare();
|
|
|
|
command->second->run();
|
2019-10-22 01:21:58 +03:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-06 14:36:55 +03:00
|
|
|
static auto rCmdProfile = registerCommand<CmdProfile>("profile");
|