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"
|
2019-10-21 22:21:58 +00:00
|
|
|
|
|
|
|
#include <nlohmann/json.hpp>
|
2019-10-22 11:06:32 +00:00
|
|
|
#include <regex>
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
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
|
|
|
|
};
|
|
|
|
|
|
|
|
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.
|
|
|
|
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;
|
2019-12-11 13:53:30 +00:00
|
|
|
element.storePaths = singleton(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);
|
|
|
|
references.insert(path.clone());
|
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-06-03 14:15:22 +00:00
|
|
|
ValidPathInfo info(store->makeFixedOutputPath(FileIngestionMethod::Recursive, narHash, "profile", references));
|
2019-12-11 13:53:30 +00:00
|
|
|
info.references = std::move(references);
|
2019-12-14 22:09:57 +00:00
|
|
|
info.narHash = narHash;
|
2019-10-21 22:21:58 +00:00
|
|
|
info.narSize = sink.s->size();
|
2020-06-03 14:15:22 +00:00
|
|
|
info.ca = makeFixedOutputCA(FileIngestionMethod::Recursive, 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
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2019-10-22 13:16:57 +00:00
|
|
|
ProfileManifest manifest(*getEvalState(), *profile);
|
2019-10-21 22:21:58 +00:00
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
std::vector<StorePathWithOutputs> 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;
|
2019-12-11 13:53:30 +00:00
|
|
|
element.storePaths = singleton(drv.outPath.clone()); // FIXME
|
2019-10-21 22:21:58 +00:00
|
|
|
element.source = ProfileElementSource{
|
|
|
|
installable2->flakeRef,
|
|
|
|
resolvedRef,
|
|
|
|
attrPath,
|
|
|
|
};
|
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
pathsToBuild.emplace_back(drv.drvPath.clone(), StringSet{"out"}); // FIXME
|
2019-10-21 22:21:58 +00:00
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
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) {
|
|
|
|
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 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";
|
|
|
|
}
|
|
|
|
|
|
|
|
Examples examples() override
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
Example{
|
|
|
|
"To remove a package by attribute path:",
|
|
|
|
"nix profile remove packages.x86_64-linux.hello"
|
|
|
|
},
|
|
|
|
Example{
|
2019-10-22 12:44:51 +00:00
|
|
|
"To remove all packages:",
|
2019-10-22 11:06:32 +00:00
|
|
|
"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
|
|
|
|
{
|
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";
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
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
|
2019-12-11 13:53:30 +00:00
|
|
|
std::vector<StorePathWithOutputs> 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));
|
|
|
|
|
2020-05-09 15:35:33 +00:00
|
|
|
InstallableFlake installable(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);
|
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
element.storePaths = singleton(drv.outPath.clone()); // FIXME
|
2019-10-22 12:44:51 +00:00
|
|
|
element.source = ProfileElementSource{
|
|
|
|
installable.flakeRef,
|
|
|
|
resolvedRef,
|
|
|
|
attrPath,
|
|
|
|
};
|
|
|
|
|
2019-12-11 13:53:30 +00:00
|
|
|
pathsToBuild.emplace_back(drv.drvPath, StringSet{"out"}); // FIXME
|
2019-10-22 12:44:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
store->buildPaths(pathsToBuild);
|
|
|
|
|
|
|
|
updateProfile(manifest.build(store));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2019-10-22 13:16:57 +00:00
|
|
|
struct CmdProfileInfo : 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
|
|
|
}
|
|
|
|
|
|
|
|
Examples examples() override
|
|
|
|
{
|
|
|
|
return {
|
|
|
|
Example{
|
|
|
|
"To show what packages are installed in the default profile:",
|
|
|
|
"nix profile info"
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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-04-16 11:46:37 +00:00
|
|
|
logger->stdout("%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
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
struct CmdProfile : virtual MultiCommand, virtual Command
|
|
|
|
{
|
|
|
|
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>(); }},
|
2019-10-21 22:21:58 +00:00
|
|
|
{"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.");
|
2020-05-05 16:59:33 +00:00
|
|
|
command->second->prepare();
|
|
|
|
command->second->run();
|
2019-10-21 22:21:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void printHelp(const string & programName, std::ostream & out) override
|
|
|
|
{
|
|
|
|
MultiCommand::printHelp(programName, out);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
static auto r1 = registerCommand<CmdProfile>("profile");
|
|
|
|
|