2019-10-21 22:21:58 +00: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 12:29:29 +00:00
|
|
|
#include "../nix-env/user-env.hh"
|
2020-07-16 15:00:42 +00:00
|
|
|
#include "profiles.hh"
|
2021-01-12 22:51:07 +00:00
|
|
|
#include "names.hh"
|
2019-10-21 22:21:58 +00:00
|
|
|
|
|
|
|
#include <nlohmann/json.hpp>
|
2019-10-22 11:06:32 +00:00
|
|
|
#include <regex>
|
2021-09-14 18:47:33 +00:00
|
|
|
#include <iomanip>
|
2019-10-21 22:21:58 +00:00
|
|
|
|
|
|
|
using namespace nix;
|
|
|
|
|
|
|
|
struct ProfileElementSource
|
|
|
|
{
|
|
|
|
FlakeRef originalRef;
|
2019-10-21 22:28:16 +00:00
|
|
|
// FIXME: record original attrpath.
|
2019-10-21 22:21:58 +00:00
|
|
|
FlakeRef resolvedRef;
|
|
|
|
std::string attrPath;
|
|
|
|
// FIXME: output names
|
2021-01-12 22:51:07 +00:00
|
|
|
|
|
|
|
bool operator < (const ProfileElementSource & other) const
|
|
|
|
{
|
|
|
|
return
|
|
|
|
std::pair(originalRef.to_string(), attrPath) <
|
|
|
|
std::pair(other.originalRef.to_string(), other.attrPath);
|
|
|
|
}
|
2019-10-21 22:21:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct ProfileElement
|
|
|
|
{
|
2019-12-11 13:53:30 +00:00
|
|
|
StorePathSet storePaths;
|
2019-10-21 22:21:58 +00:00
|
|
|
std::optional<ProfileElementSource> source;
|
|
|
|
bool active = true;
|
|
|
|
// FIXME: priority
|
2021-01-12 22:51:07 +00:00
|
|
|
|
|
|
|
std::string describe() const
|
|
|
|
{
|
|
|
|
if (source)
|
|
|
|
return fmt("%s#%s", source->originalRef, source->attrPath);
|
|
|
|
StringSet names;
|
|
|
|
for (auto & path : storePaths)
|
|
|
|
names.insert(DrvName(path.name()).name);
|
|
|
|
return concatStringsSep(", ", names);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string versions() const
|
|
|
|
{
|
|
|
|
StringSet versions;
|
|
|
|
for (auto & path : storePaths)
|
|
|
|
versions.insert(DrvName(path.name()).version);
|
|
|
|
return showVersions(versions);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool operator < (const ProfileElement & other) const
|
|
|
|
{
|
|
|
|
return std::tuple(describe(), storePaths) < std::tuple(other.describe(), other.storePaths);
|
|
|
|
}
|
2019-10-21 22:21:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct ProfileManifest
|
|
|
|
{
|
|
|
|
std::vector<ProfileElement> elements;
|
|
|
|
|
2019-10-22 11:06:32 +00:00
|
|
|
ProfileManifest() { }
|
|
|
|
|
2019-10-22 13:16:57 +00:00
|
|
|
ProfileManifest(EvalState & state, const Path & profile)
|
2019-10-21 22:21:58 +00: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 13:53:30 +00:00
|
|
|
element.storePaths.insert(state.store->parseStorePath((std::string) p));
|
2019-10-21 22:21:58 +00:00
|
|
|
element.active = e["active"];
|
|
|
|
if (e.value("uri", "") != "") {
|
|
|
|
element.source = ProfileElementSource{
|
2020-01-21 15:27:53 +00:00
|
|
|
parseFlakeRef(e["originalUri"]),
|
|
|
|
parseFlakeRef(e["uri"]),
|
2019-10-21 22:21:58 +00:00
|
|
|
e["attrPath"]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
elements.emplace_back(std::move(element));
|
|
|
|
}
|
|
|
|
}
|
2019-10-22 13:16:57 +00:00
|
|
|
|
|
|
|
else if (pathExists(profile + "/manifest.nix")) {
|
|
|
|
// FIXME: needed because of pure mode; ugly.
|
2021-10-07 10:11:00 +00:00
|
|
|
state.allowPath(state.store->followLinksToStore(profile));
|
|
|
|
state.allowPath(state.store->followLinksToStore(profile + "/manifest.nix"));
|
2019-10-22 13:16:57 +00:00
|
|
|
|
|
|
|
auto drvInfos = queryInstalled(state, state.store->followLinksToStore(profile));
|
|
|
|
|
|
|
|
for (auto & drvInfo : drvInfos) {
|
|
|
|
ProfileElement element;
|
2020-06-17 08:26:52 +00:00
|
|
|
element.storePaths = {state.store->parseStorePath(drvInfo.queryOutPath())};
|
2019-10-22 13:16:57 +00:00
|
|
|
elements.emplace_back(std::move(element));
|
|
|
|
}
|
|
|
|
}
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
std::string toJSON(Store & store) const
|
2019-10-21 22:21:58 +00:00
|
|
|
{
|
|
|
|
auto array = nlohmann::json::array();
|
|
|
|
for (auto & element : elements) {
|
|
|
|
auto paths = nlohmann::json::array();
|
|
|
|
for (auto & path : element.storePaths)
|
2019-12-11 13:53:30 +00:00
|
|
|
paths.push_back(store.printStorePath(path));
|
2019-10-21 22:21:58 +00: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 13:53:30 +00:00
|
|
|
StorePath build(ref<Store> store)
|
2019-10-21 22:21:58 +00:00
|
|
|
{
|
|
|
|
auto tempDir = createTempDir();
|
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
StorePathSet references;
|
2019-10-21 22:21:58 +00:00
|
|
|
|
|
|
|
Packages pkgs;
|
|
|
|
for (auto & element : elements) {
|
|
|
|
for (auto & path : element.storePaths) {
|
|
|
|
if (element.active)
|
2019-12-11 13:53:30 +00:00
|
|
|
pkgs.emplace_back(store->printStorePath(path), true, 5);
|
2020-06-17 08:26:52 +00:00
|
|
|
references.insert(path);
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buildProfile(tempDir, std::move(pkgs));
|
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
writeFile(tempDir + "/manifest.json", toJSON(*store));
|
2019-10-21 22:21:58 +00:00
|
|
|
|
|
|
|
/* Add the symlink tree to the store. */
|
|
|
|
StringSink sink;
|
|
|
|
dumpPath(tempDir, sink);
|
|
|
|
|
2019-12-14 22:09:57 +00:00
|
|
|
auto narHash = hashString(htSHA256, *sink.s);
|
|
|
|
|
2020-08-06 18:31:48 +00:00
|
|
|
ValidPathInfo info {
|
|
|
|
store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, "profile", references),
|
|
|
|
narHash,
|
|
|
|
};
|
2019-12-11 13:53:30 +00:00
|
|
|
info.references = std::move(references);
|
2019-10-21 22:21:58 +00:00
|
|
|
info.narSize = sink.s->size();
|
2020-08-05 18:42:48 +00:00
|
|
|
info.ca = FixedOutputHash { .method = FileIngestionMethod::Recursive, .hash = info.narHash };
|
2019-10-21 22:21:58 +00:00
|
|
|
|
2020-06-03 14:15:22 +00:00
|
|
|
auto source = StringSource { *sink.s };
|
|
|
|
store->addToStore(info, source);
|
2019-10-21 22:21:58 +00:00
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
return std::move(info.path);
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
2021-01-12 22:51:07 +00:00
|
|
|
|
|
|
|
static void printDiff(const ProfileManifest & prev, const ProfileManifest & cur, std::string_view indent)
|
|
|
|
{
|
|
|
|
auto prevElems = prev.elements;
|
|
|
|
std::sort(prevElems.begin(), prevElems.end());
|
|
|
|
|
|
|
|
auto curElems = cur.elements;
|
|
|
|
std::sort(curElems.begin(), curElems.end());
|
|
|
|
|
|
|
|
auto i = prevElems.begin();
|
|
|
|
auto j = curElems.begin();
|
|
|
|
|
|
|
|
bool changes = false;
|
|
|
|
|
|
|
|
while (i != prevElems.end() || j != curElems.end()) {
|
|
|
|
if (j != curElems.end() && (i == prevElems.end() || i->describe() > j->describe())) {
|
|
|
|
std::cout << fmt("%s%s: ∅ -> %s\n", indent, j->describe(), j->versions());
|
|
|
|
changes = true;
|
|
|
|
++j;
|
|
|
|
}
|
|
|
|
else if (i != prevElems.end() && (j == curElems.end() || i->describe() < j->describe())) {
|
|
|
|
std::cout << fmt("%s%s: %s -> ∅\n", indent, i->describe(), i->versions());
|
|
|
|
changes = true;
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
auto v1 = i->versions();
|
|
|
|
auto v2 = j->versions();
|
|
|
|
if (v1 != v2) {
|
|
|
|
std::cout << fmt("%s%s: %s -> %s\n", indent, i->describe(), v1, v2);
|
|
|
|
changes = true;
|
|
|
|
}
|
|
|
|
++i;
|
|
|
|
++j;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!changes)
|
|
|
|
std::cout << fmt("%sNo changes.\n", indent);
|
|
|
|
}
|
2019-10-21 22:21:58 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "install a package into a profile";
|
|
|
|
}
|
|
|
|
|
2020-12-18 13:25:36 +00:00
|
|
|
std::string doc() override
|
2019-10-21 22:21:58 +00:00
|
|
|
{
|
2020-12-18 13:25:36 +00:00
|
|
|
return
|
|
|
|
#include "profile-install.md"
|
|
|
|
;
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-10-22 13:16:57 +00:00
|
|
|
ProfileManifest manifest(*getEvalState(), *profile);
|
2019-10-21 22:21:58 +00:00
|
|
|
|
2021-04-05 13:48:18 +00:00
|
|
|
std::vector<DerivedPath> pathsToBuild;
|
2019-10-21 22:21:58 +00: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 19:09:26 +00:00
|
|
|
if (!drv.outPath)
|
|
|
|
throw UnimplementedError("CA derivations are not yet supported by 'nix profile'");
|
|
|
|
element.storePaths = {*drv.outPath}; // FIXME
|
2019-10-21 22:21:58 +00:00
|
|
|
element.source = ProfileElementSource{
|
|
|
|
installable2->flakeRef,
|
|
|
|
resolvedRef,
|
|
|
|
attrPath,
|
|
|
|
};
|
|
|
|
|
2021-04-05 13:48:18 +00:00
|
|
|
pathsToBuild.push_back(DerivedPath::Built{drv.drvPath, StringSet{drv.outputName}});
|
2019-10-21 22:21:58 +00:00
|
|
|
|
|
|
|
manifest.elements.emplace_back(std::move(element));
|
2021-01-18 22:08:58 +00:00
|
|
|
} else {
|
2021-07-16 14:04:47 +00:00
|
|
|
auto buildables = build(getEvalStore(), store, Realise::Outputs, {installable}, bmNormal);
|
2021-01-18 22:08:58 +00:00
|
|
|
|
|
|
|
for (auto & buildable : buildables) {
|
|
|
|
ProfileElement element;
|
|
|
|
|
|
|
|
std::visit(overloaded {
|
2021-09-30 21:31:21 +00:00
|
|
|
[&](const BuiltPath::Opaque & bo) {
|
2021-03-02 03:50:41 +00:00
|
|
|
pathsToBuild.push_back(bo);
|
2021-01-18 22:08:58 +00:00
|
|
|
element.storePaths.insert(bo.path);
|
|
|
|
},
|
2021-09-30 21:31:21 +00:00
|
|
|
[&](const BuiltPath::Built & bfd) {
|
2021-03-02 03:50:41 +00:00
|
|
|
// TODO: Why are we querying if we know the output
|
|
|
|
// names already? Is it just to figure out what the
|
|
|
|
// default one is?
|
2021-01-18 22:08:58 +00:00
|
|
|
for (auto & output : store->queryDerivationOutputMap(bfd.drvPath)) {
|
2021-04-05 13:48:18 +00:00
|
|
|
pathsToBuild.push_back(DerivedPath::Built{bfd.drvPath, {output.first}});
|
2021-01-18 22:08:58 +00:00
|
|
|
element.storePaths.insert(output.second);
|
|
|
|
}
|
|
|
|
},
|
2021-04-05 14:05:21 +00:00
|
|
|
}, buildable.raw());
|
2021-01-18 22:08:58 +00:00
|
|
|
|
|
|
|
manifest.elements.emplace_back(std::move(element));
|
|
|
|
}
|
|
|
|
}
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
store->buildPaths(pathsToBuild);
|
|
|
|
|
|
|
|
updateProfile(manifest.build(store));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-10-22 11:06:32 +00: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) {
|
2021-01-08 11:22:21 +00:00
|
|
|
if (auto n = string2Int<size_t>(s))
|
|
|
|
res.push_back(*n);
|
2019-10-22 11:06:32 +00:00
|
|
|
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 13:53:30 +00:00
|
|
|
bool matches(const Store & store, const ProfileElement & element, size_t pos, const std::vector<Matcher> & matchers)
|
2019-10-22 11:06:32 +00: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 13:53:30 +00:00
|
|
|
if (element.storePaths.count(store.parseStorePath(*path))) return true;
|
2019-10-22 11:06:32 +00: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 13:16:57 +00:00
|
|
|
struct CmdProfileRemove : virtual EvalCommand, MixDefaultProfile, MixProfileElementMatchers
|
2019-10-22 11:06:32 +00:00
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "remove packages from a profile";
|
|
|
|
}
|
|
|
|
|
2020-12-18 13:25:36 +00:00
|
|
|
std::string doc() override
|
2019-10-22 11:06:32 +00:00
|
|
|
{
|
2020-12-18 13:25:36 +00:00
|
|
|
return
|
|
|
|
#include "profile-remove.md"
|
|
|
|
;
|
2019-10-22 11:06:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-10-22 13:16:57 +00:00
|
|
|
ProfileManifest oldManifest(*getEvalState(), *profile);
|
2019-10-22 11:06:32 +00: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 13:53:30 +00:00
|
|
|
if (!matches(*store, element, i, matchers))
|
|
|
|
newManifest.elements.push_back(std::move(element));
|
2019-10-22 11:06:32 +00: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 12:44:51 +00:00
|
|
|
struct CmdProfileUpgrade : virtual SourceExprCommand, MixDefaultProfile, MixProfileElementMatchers
|
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "upgrade packages using their most recent flake";
|
|
|
|
}
|
|
|
|
|
2020-12-18 13:25:36 +00:00
|
|
|
std::string doc() override
|
2019-10-22 12:44:51 +00:00
|
|
|
{
|
2020-12-18 13:25:36 +00:00
|
|
|
return
|
|
|
|
#include "profile-upgrade.md"
|
|
|
|
;
|
2019-10-22 12:44:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-10-22 13:16:57 +00:00
|
|
|
ProfileManifest manifest(*getEvalState(), *profile);
|
2019-10-22 12:44:51 +00:00
|
|
|
|
|
|
|
auto matchers = getMatchers(store);
|
|
|
|
|
|
|
|
// FIXME: code duplication
|
2021-04-05 13:48:18 +00:00
|
|
|
std::vector<DerivedPath> pathsToBuild;
|
2019-10-22 12:44:51 +00: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-29 22:44:11 +00:00
|
|
|
&& !element.source->originalRef.input.isImmutable()
|
2019-12-11 13:53:30 +00:00
|
|
|
&& matches(*store, element, i, matchers))
|
2019-10-22 12:44:51 +00:00
|
|
|
{
|
|
|
|
Activity act(*logger, lvlChatty, actUnknown,
|
|
|
|
fmt("checking '%s' for updates", element.source->attrPath));
|
|
|
|
|
2021-02-17 16:32:10 +00:00
|
|
|
InstallableFlake installable(
|
|
|
|
this,
|
|
|
|
getEvalState(),
|
|
|
|
FlakeRef(element.source->originalRef),
|
|
|
|
{element.source->attrPath},
|
|
|
|
{},
|
|
|
|
lockFlags);
|
2019-10-22 12:44:51 +00: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 19:09:26 +00:00
|
|
|
if (!drv.outPath)
|
|
|
|
throw UnimplementedError("CA derivations are not yet supported by 'nix profile'");
|
|
|
|
element.storePaths = {*drv.outPath}; // FIXME
|
2019-10-22 12:44:51 +00:00
|
|
|
element.source = ProfileElementSource{
|
|
|
|
installable.flakeRef,
|
|
|
|
resolvedRef,
|
|
|
|
attrPath,
|
|
|
|
};
|
|
|
|
|
2021-05-05 21:33:05 +00:00
|
|
|
pathsToBuild.push_back(DerivedPath::Built{drv.drvPath, {drv.outputName}});
|
2019-10-22 12:44:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
store->buildPaths(pathsToBuild);
|
|
|
|
|
|
|
|
updateProfile(manifest.build(store));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-01-12 18:57:05 +00:00
|
|
|
struct CmdProfileList : virtual EvalCommand, virtual StoreCommand, MixDefaultProfile
|
2019-10-21 22:21:58 +00:00
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
2019-10-22 11:06:32 +00:00
|
|
|
return "list installed packages";
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
|
|
|
|
2020-12-18 13:25:36 +00:00
|
|
|
std::string doc() override
|
2019-10-21 22:21:58 +00:00
|
|
|
{
|
2020-12-18 13:25:36 +00:00
|
|
|
return
|
2021-01-12 18:57:05 +00:00
|
|
|
#include "profile-list.md"
|
2020-12-18 13:25:36 +00:00
|
|
|
;
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
2019-10-22 13:16:57 +00:00
|
|
|
ProfileManifest manifest(*getEvalState(), *profile);
|
2019-10-21 22:21:58 +00:00
|
|
|
|
2019-10-21 22:28:16 +00:00
|
|
|
for (size_t i = 0; i < manifest.elements.size(); ++i) {
|
|
|
|
auto & element(manifest.elements[i]);
|
2020-09-25 15:30:04 +00:00
|
|
|
logger->cout("%d %s %s %s", i,
|
2019-10-21 22:21:58 +00:00
|
|
|
element.source ? element.source->originalRef.to_string() + "#" + element.source->attrPath : "-",
|
|
|
|
element.source ? element.source->resolvedRef.to_string() + "#" + element.source->attrPath : "-",
|
2019-12-11 13:53:30 +00:00
|
|
|
concatStringsSep(" ", store->printStorePathSet(element.storePaths)));
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-07-17 10:36:12 +00:00
|
|
|
struct CmdProfileDiffClosures : virtual StoreCommand, MixDefaultProfile
|
2020-07-16 15:00:42 +00:00
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
2020-12-18 13:25:36 +00:00
|
|
|
return "show the closure difference between each version of a profile";
|
2020-07-16 15:00:42 +00:00
|
|
|
}
|
|
|
|
|
2020-12-18 13:25:36 +00:00
|
|
|
std::string doc() override
|
2020-07-16 15:00:42 +00:00
|
|
|
{
|
2020-12-18 13:25:36 +00:00
|
|
|
return
|
|
|
|
#include "profile-diff-closures.md"
|
|
|
|
;
|
2020-07-16 15:00:42 +00: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 13:25:36 +00:00
|
|
|
std::cout << fmt("Version %d -> %d:\n", prevGen->number, gen.number);
|
2020-07-16 15:00:42 +00:00
|
|
|
printClosureDiff(store,
|
|
|
|
store->followLinksToStorePath(prevGen->path),
|
|
|
|
store->followLinksToStorePath(gen.path),
|
|
|
|
" ");
|
|
|
|
}
|
|
|
|
|
|
|
|
prevGen = gen;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-01-12 22:51:07 +00:00
|
|
|
struct CmdProfileHistory : virtual StoreCommand, EvalCommand, MixDefaultProfile
|
|
|
|
{
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "show all versions of a profile";
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string doc() override
|
|
|
|
{
|
|
|
|
return
|
|
|
|
#include "profile-history.md"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
|
|
|
auto [gens, curGen] = findGenerations(*profile);
|
|
|
|
|
|
|
|
std::optional<std::pair<Generation, ProfileManifest>> prevGen;
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
for (auto & gen : gens) {
|
|
|
|
ProfileManifest manifest(*getEvalState(), gen.path);
|
|
|
|
|
|
|
|
if (!first) std::cout << "\n";
|
|
|
|
first = false;
|
|
|
|
|
2021-09-14 18:47:33 +00:00
|
|
|
std::cout << fmt("Version %s%d" ANSI_NORMAL " (%s)%s:\n",
|
|
|
|
gen.number == curGen ? ANSI_GREEN : ANSI_BOLD,
|
|
|
|
gen.number,
|
|
|
|
std::put_time(std::gmtime(&gen.creationTime), "%Y-%m-%d"),
|
|
|
|
prevGen ? fmt(" <- %d", prevGen->first.number) : "");
|
2021-01-12 22:51:07 +00:00
|
|
|
|
|
|
|
ProfileManifest::printDiff(
|
|
|
|
prevGen ? prevGen->second : ProfileManifest(),
|
|
|
|
manifest,
|
|
|
|
" ");
|
|
|
|
|
|
|
|
prevGen = {gen, std::move(manifest)};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-09-14 17:05:28 +00:00
|
|
|
struct CmdProfileRollback : virtual StoreCommand, MixDefaultProfile, MixDryRun
|
|
|
|
{
|
|
|
|
std::optional<GenerationNumber> version;
|
|
|
|
|
|
|
|
CmdProfileRollback()
|
|
|
|
{
|
|
|
|
addFlag({
|
|
|
|
.longName = "to",
|
|
|
|
.description = "The profile version to roll back to.",
|
|
|
|
.labels = {"version"},
|
|
|
|
.handler = {&version},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string description() override
|
|
|
|
{
|
2021-09-14 17:57:45 +00:00
|
|
|
return "roll back to the previous version or a specified version of a profile";
|
2021-09-14 17:05:28 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
std::string doc() override
|
|
|
|
{
|
|
|
|
return
|
|
|
|
#include "profile-rollback.md"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
|
|
|
switchGeneration(*profile, version, dryRun);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-09-14 18:35:12 +00:00
|
|
|
struct CmdProfileWipeHistory : virtual StoreCommand, MixDefaultProfile, MixDryRun
|
|
|
|
{
|
|
|
|
std::optional<std::string> minAge;
|
|
|
|
|
|
|
|
CmdProfileWipeHistory()
|
|
|
|
{
|
|
|
|
addFlag({
|
|
|
|
.longName = "older-than",
|
|
|
|
.description =
|
|
|
|
"Delete versions older than the specified age. *age* "
|
|
|
|
"must be in the format *N*`d`, where *N* denotes a number "
|
|
|
|
"of days.",
|
|
|
|
.labels = {"age"},
|
|
|
|
.handler = {&minAge},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "delete non-current versions of a profile";
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string doc() override
|
|
|
|
{
|
|
|
|
return
|
|
|
|
#include "profile-wipe-history.md"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
|
|
|
void run(ref<Store> store) override
|
|
|
|
{
|
|
|
|
if (minAge)
|
|
|
|
deleteGenerationsOlderThan(*profile, *minAge, dryRun);
|
|
|
|
else
|
|
|
|
deleteOldGenerations(*profile, dryRun);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-08-17 15:44:52 +00:00
|
|
|
struct CmdProfile : NixMultiCommand
|
2019-10-21 22:21:58 +00:00
|
|
|
{
|
|
|
|
CmdProfile()
|
|
|
|
: MultiCommand({
|
|
|
|
{"install", []() { return make_ref<CmdProfileInstall>(); }},
|
2019-10-22 11:06:32 +00:00
|
|
|
{"remove", []() { return make_ref<CmdProfileRemove>(); }},
|
2019-10-22 12:44:51 +00:00
|
|
|
{"upgrade", []() { return make_ref<CmdProfileUpgrade>(); }},
|
2021-01-12 18:57:05 +00:00
|
|
|
{"list", []() { return make_ref<CmdProfileList>(); }},
|
2020-07-16 15:00:42 +00:00
|
|
|
{"diff-closures", []() { return make_ref<CmdProfileDiffClosures>(); }},
|
2021-01-12 22:51:07 +00:00
|
|
|
{"history", []() { return make_ref<CmdProfileHistory>(); }},
|
2021-09-14 17:05:28 +00:00
|
|
|
{"rollback", []() { return make_ref<CmdProfileRollback>(); }},
|
2021-09-14 18:35:12 +00:00
|
|
|
{"wipe-history", []() { return make_ref<CmdProfileWipeHistory>(); }},
|
2019-10-21 22:21:58 +00:00
|
|
|
})
|
|
|
|
{ }
|
|
|
|
|
|
|
|
std::string description() override
|
|
|
|
{
|
|
|
|
return "manage Nix profiles";
|
|
|
|
}
|
|
|
|
|
2020-12-18 13:25:36 +00:00
|
|
|
std::string doc() override
|
|
|
|
{
|
|
|
|
return
|
|
|
|
#include "profile.md"
|
|
|
|
;
|
|
|
|
}
|
|
|
|
|
2019-10-21 22:21:58 +00:00
|
|
|
void run() override
|
|
|
|
{
|
|
|
|
if (!command)
|
|
|
|
throw UsageError("'nix profile' requires a sub-command.");
|
2020-05-05 16:59:33 +00:00
|
|
|
command->second->prepare();
|
|
|
|
command->second->run();
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-10-06 11:36:55 +00:00
|
|
|
static auto rCmdProfile = registerCommand<CmdProfile>("profile");
|