refactor some nix-env and profile code to libcmd
Notably, ProfileManifest and ProfileElement are useful generic profile management code, and nix profile is not the only place in the codebase where profiles are relevant. This commit is in preparation for fixing upgrade-nix's interaction with new-style profiles. Change-Id: Iefc8bbd34b4bc6012175cb3d6e6a8207973bc792
This commit is contained in:
parent
da677fce39
commit
2bd57d4d36
7 changed files with 356 additions and 292 deletions
277
src/libcmd/cmd-profiles.cc
Normal file
277
src/libcmd/cmd-profiles.cc
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
#include "cmd-profiles.hh"
|
||||||
|
#include "built-path.hh"
|
||||||
|
#include "builtins/buildenv.hh"
|
||||||
|
#include "names.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
|
||||||
|
namespace nix
|
||||||
|
{
|
||||||
|
|
||||||
|
DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
|
||||||
|
{
|
||||||
|
DrvInfos elems;
|
||||||
|
if (pathExists(userEnv + "/manifest.json"))
|
||||||
|
throw Error("profile '%s' is incompatible with 'nix-env'; please use 'nix profile' instead", userEnv);
|
||||||
|
auto manifestFile = userEnv + "/manifest.nix";
|
||||||
|
if (pathExists(manifestFile)) {
|
||||||
|
Value v;
|
||||||
|
state.evalFile(state.rootPath(CanonPath(manifestFile)), v);
|
||||||
|
Bindings & bindings(*state.allocBindings(0));
|
||||||
|
getDerivations(state, v, "", bindings, elems, false);
|
||||||
|
}
|
||||||
|
return elems;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string showVersions(const std::set<std::string> & versions)
|
||||||
|
{
|
||||||
|
if (versions.empty()) return "∅";
|
||||||
|
std::set<std::string> versions2;
|
||||||
|
for (auto & version : versions)
|
||||||
|
versions2.insert(version.empty() ? "ε" : version);
|
||||||
|
return concatStringsSep(", ", versions2);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProfileElementSource::operator<(const ProfileElementSource & other) const
|
||||||
|
{
|
||||||
|
return std::tuple(originalRef.to_string(), attrPath, outputs)
|
||||||
|
< std::tuple(other.originalRef.to_string(), other.attrPath, other.outputs);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ProfileElementSource::to_string() const
|
||||||
|
{
|
||||||
|
return fmt("%s#%s%s", originalRef, attrPath, outputs.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ProfileElement::identifier() const
|
||||||
|
{
|
||||||
|
if (source) {
|
||||||
|
return source->to_string();
|
||||||
|
}
|
||||||
|
StringSet names;
|
||||||
|
for (auto & path : storePaths) {
|
||||||
|
names.insert(DrvName(path.name()).name);
|
||||||
|
}
|
||||||
|
return concatStringsSep(", ", names);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::set<std::string> ProfileElement::toInstallables(Store & store)
|
||||||
|
{
|
||||||
|
if (source) {
|
||||||
|
return {source->to_string()};
|
||||||
|
}
|
||||||
|
StringSet rawPaths;
|
||||||
|
for (auto & path : storePaths) {
|
||||||
|
rawPaths.insert(store.printStorePath(path));
|
||||||
|
}
|
||||||
|
return rawPaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string ProfileElement::versions() const
|
||||||
|
{
|
||||||
|
StringSet versions;
|
||||||
|
for (auto & path : storePaths) {
|
||||||
|
versions.insert(DrvName(path.name()).version);
|
||||||
|
}
|
||||||
|
return showVersions(versions);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ProfileElement::operator<(const ProfileElement & other) const
|
||||||
|
{
|
||||||
|
return std::tuple(identifier(), storePaths) < std::tuple(other.identifier(), other.storePaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileElement::updateStorePaths(
|
||||||
|
ref<Store> evalStore, ref<Store> store, const BuiltPaths & builtPaths
|
||||||
|
)
|
||||||
|
{
|
||||||
|
storePaths.clear();
|
||||||
|
for (auto & buildable : builtPaths) {
|
||||||
|
std::visit(
|
||||||
|
overloaded{
|
||||||
|
[&](const BuiltPath::Opaque & bo) { storePaths.insert(bo.path); },
|
||||||
|
[&](const BuiltPath::Built & bfd) {
|
||||||
|
for (auto & output : bfd.outputs) {
|
||||||
|
storePaths.insert(output.second);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
buildable.raw()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
std::string sUrl;
|
||||||
|
std::string sOriginalUrl;
|
||||||
|
switch (version) {
|
||||||
|
case 1:
|
||||||
|
sUrl = "uri";
|
||||||
|
sOriginalUrl = "originalUri";
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
sUrl = "url";
|
||||||
|
sOriginalUrl = "originalUrl";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
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.contains("priority")) {
|
||||||
|
element.priority = e["priority"];
|
||||||
|
}
|
||||||
|
if (e.value(sUrl, "") != "") {
|
||||||
|
element.source = ProfileElementSource{
|
||||||
|
parseFlakeRef(e[sOriginalUrl]),
|
||||||
|
parseFlakeRef(e[sUrl]),
|
||||||
|
e["attrPath"],
|
||||||
|
e["outputs"].get<ExtendedOutputsSpec>()};
|
||||||
|
}
|
||||||
|
elements.emplace_back(std::move(element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else if (pathExists(profile + "/manifest.nix"))
|
||||||
|
{
|
||||||
|
// FIXME: needed because of pure mode; ugly.
|
||||||
|
state.allowPath(state.store->followLinksToStore(profile));
|
||||||
|
state.allowPath(state.store->followLinksToStore(profile + "/manifest.nix"));
|
||||||
|
|
||||||
|
auto drvInfos = queryInstalled(state, state.store->followLinksToStore(profile));
|
||||||
|
|
||||||
|
for (auto & drvInfo : drvInfos) {
|
||||||
|
ProfileElement element;
|
||||||
|
element.storePaths = {drvInfo.queryOutPath()};
|
||||||
|
elements.emplace_back(std::move(element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nlohmann::json ProfileManifest::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;
|
||||||
|
obj["priority"] = element.priority;
|
||||||
|
if (element.source) {
|
||||||
|
obj["originalUrl"] = element.source->originalRef.to_string();
|
||||||
|
obj["url"] = element.source->lockedRef.to_string();
|
||||||
|
obj["attrPath"] = element.source->attrPath;
|
||||||
|
obj["outputs"] = element.source->outputs;
|
||||||
|
}
|
||||||
|
array.push_back(obj);
|
||||||
|
}
|
||||||
|
nlohmann::json json;
|
||||||
|
json["version"] = 2;
|
||||||
|
json["elements"] = array;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
StorePath ProfileManifest::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, element.priority);
|
||||||
|
}
|
||||||
|
references.insert(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildProfile(tempDir, std::move(pkgs));
|
||||||
|
|
||||||
|
writeFile(tempDir + "/manifest.json", toJSON(*store).dump());
|
||||||
|
|
||||||
|
/* Add the symlink tree to the store. */
|
||||||
|
StringSink sink;
|
||||||
|
dumpPath(tempDir, sink);
|
||||||
|
|
||||||
|
auto narHash = hashString(htSHA256, sink.s);
|
||||||
|
|
||||||
|
ValidPathInfo info{
|
||||||
|
*store,
|
||||||
|
"profile",
|
||||||
|
FixedOutputInfo{
|
||||||
|
.method = FileIngestionMethod::Recursive,
|
||||||
|
.hash = narHash,
|
||||||
|
.references =
|
||||||
|
{
|
||||||
|
.others = std::move(references),
|
||||||
|
// profiles never refer to themselves
|
||||||
|
.self = false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
narHash,
|
||||||
|
};
|
||||||
|
info.narSize = sink.s.size();
|
||||||
|
|
||||||
|
StringSource source(sink.s);
|
||||||
|
store->addToStore(info, source);
|
||||||
|
|
||||||
|
return std::move(info.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileManifest::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->identifier() > j->identifier())) {
|
||||||
|
logger->cout("%s%s: ∅ -> %s", indent, j->identifier(), j->versions());
|
||||||
|
changes = true;
|
||||||
|
++j;
|
||||||
|
} else if (i != prevElems.end() && (j == curElems.end() || i->identifier() < j->identifier())) {
|
||||||
|
logger->cout("%s%s: %s -> ∅", indent, i->identifier(), i->versions());
|
||||||
|
changes = true;
|
||||||
|
++i;
|
||||||
|
} else {
|
||||||
|
auto v1 = i->versions();
|
||||||
|
auto v2 = j->versions();
|
||||||
|
if (v1 != v2) {
|
||||||
|
logger->cout("%s%s: %s -> %s", indent, i->identifier(), v1, v2);
|
||||||
|
changes = true;
|
||||||
|
}
|
||||||
|
++i;
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!changes) {
|
||||||
|
logger->cout("%sNo changes.", indent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
73
src/libcmd/cmd-profiles.hh
Normal file
73
src/libcmd/cmd-profiles.hh
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include "built-path.hh"
|
||||||
|
#include "eval.hh"
|
||||||
|
#include "flake/flakeref.hh"
|
||||||
|
#include "get-drvs.hh"
|
||||||
|
#include "types.hh"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace nix
|
||||||
|
{
|
||||||
|
|
||||||
|
struct ProfileElementSource
|
||||||
|
{
|
||||||
|
FlakeRef originalRef;
|
||||||
|
// FIXME: record original attrpath.
|
||||||
|
FlakeRef lockedRef;
|
||||||
|
std::string attrPath;
|
||||||
|
ExtendedOutputsSpec outputs;
|
||||||
|
|
||||||
|
bool operator<(const ProfileElementSource & other) const;
|
||||||
|
|
||||||
|
std::string to_string() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr int DEFAULT_PRIORITY = 5;
|
||||||
|
|
||||||
|
struct ProfileElement
|
||||||
|
{
|
||||||
|
StorePathSet storePaths;
|
||||||
|
std::optional<ProfileElementSource> source;
|
||||||
|
bool active = true;
|
||||||
|
int priority = DEFAULT_PRIORITY;
|
||||||
|
|
||||||
|
std::string identifier() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a string representing an installable corresponding to the current
|
||||||
|
* element, either a flakeref or a plain store path
|
||||||
|
*/
|
||||||
|
std::set<std::string> toInstallables(Store & store);
|
||||||
|
|
||||||
|
std::string versions() const;
|
||||||
|
|
||||||
|
bool operator<(const ProfileElement & other) const;
|
||||||
|
|
||||||
|
void updateStorePaths(ref<Store> evalStore, ref<Store> store, const BuiltPaths & builtPaths);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ProfileManifest
|
||||||
|
{
|
||||||
|
std::vector<ProfileElement> elements;
|
||||||
|
|
||||||
|
ProfileManifest() { }
|
||||||
|
|
||||||
|
ProfileManifest(EvalState & state, const Path & profile);
|
||||||
|
|
||||||
|
nlohmann::json toJSON(Store & store) const;
|
||||||
|
|
||||||
|
StorePath build(ref<Store> store);
|
||||||
|
|
||||||
|
static void printDiff(const ProfileManifest & prev, const ProfileManifest & cur, std::string_view indent);
|
||||||
|
};
|
||||||
|
|
||||||
|
DrvInfos queryInstalled(EvalState & state, const Path & userEnv);
|
||||||
|
std::string showVersions(const std::set<std::string> & versions);
|
||||||
|
|
||||||
|
}
|
|
@ -342,8 +342,6 @@ void completeFlakeRefWithFragment(
|
||||||
const Strings & defaultFlakeAttrPaths,
|
const Strings & defaultFlakeAttrPaths,
|
||||||
std::string_view prefix);
|
std::string_view prefix);
|
||||||
|
|
||||||
std::string showVersions(const std::set<std::string> & versions);
|
|
||||||
|
|
||||||
void printClosureDiff(
|
void printClosureDiff(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
const StorePath & beforePath,
|
const StorePath & beforePath,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
libcmd_sources = files(
|
libcmd_sources = files(
|
||||||
'built-path.cc',
|
'built-path.cc',
|
||||||
'command-installable-value.cc',
|
'command-installable-value.cc',
|
||||||
|
'cmd-profiles.cc',
|
||||||
'command.cc',
|
'command.cc',
|
||||||
'common-eval-args.cc',
|
'common-eval-args.cc',
|
||||||
'editor-for.cc',
|
'editor-for.cc',
|
||||||
|
@ -18,6 +19,7 @@ libcmd_sources = files(
|
||||||
libcmd_headers = files(
|
libcmd_headers = files(
|
||||||
'built-path.hh',
|
'built-path.hh',
|
||||||
'command-installable-value.hh',
|
'command-installable-value.hh',
|
||||||
|
'cmd-profiles.hh',
|
||||||
'command.hh',
|
'command.hh',
|
||||||
'common-eval-args.hh',
|
'common-eval-args.hh',
|
||||||
'editor-for.hh',
|
'editor-for.hh',
|
||||||
|
|
|
@ -16,22 +16,6 @@
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
|
|
||||||
DrvInfos queryInstalled(EvalState & state, const Path & userEnv)
|
|
||||||
{
|
|
||||||
DrvInfos elems;
|
|
||||||
if (pathExists(userEnv + "/manifest.json"))
|
|
||||||
throw Error("profile '%s' is incompatible with 'nix-env'; please use 'nix profile' instead", userEnv);
|
|
||||||
auto manifestFile = userEnv + "/manifest.nix";
|
|
||||||
if (pathExists(manifestFile)) {
|
|
||||||
Value v;
|
|
||||||
state.evalFile(state.rootPath(CanonPath(manifestFile)), v);
|
|
||||||
Bindings & bindings(*state.allocBindings(0));
|
|
||||||
getDerivations(state, v, "", bindings, elems, false);
|
|
||||||
}
|
|
||||||
return elems;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
bool createUserEnv(EvalState & state, DrvInfos & elems,
|
bool createUserEnv(EvalState & state, DrvInfos & elems,
|
||||||
const Path & profile, bool keepDerivations,
|
const Path & profile, bool keepDerivations,
|
||||||
const std::string & lockToken)
|
const std::string & lockToken)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "command.hh"
|
#include "command.hh"
|
||||||
|
#include "cmd-profiles.hh"
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
#include "common-args.hh"
|
#include "common-args.hh"
|
||||||
|
@ -43,15 +44,6 @@ GroupedPaths getClosureInfo(ref<Store> store, const StorePath & toplevel)
|
||||||
return groupedPaths;
|
return groupedPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string showVersions(const std::set<std::string> & versions)
|
|
||||||
{
|
|
||||||
if (versions.empty()) return "∅";
|
|
||||||
std::set<std::string> versions2;
|
|
||||||
for (auto & version : versions)
|
|
||||||
versions2.insert(version.empty() ? "ε" : version);
|
|
||||||
return concatStringsSep(", ", versions2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void printClosureDiff(
|
void printClosureDiff(
|
||||||
ref<Store> store,
|
ref<Store> store,
|
||||||
const StorePath & beforePath,
|
const StorePath & beforePath,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "command.hh"
|
#include "command.hh"
|
||||||
|
#include "cmd-profiles.hh"
|
||||||
#include "installable-flake.hh"
|
#include "installable-flake.hh"
|
||||||
#include "common-args.hh"
|
#include "common-args.hh"
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
|
@ -17,269 +18,6 @@
|
||||||
|
|
||||||
using namespace nix;
|
using namespace nix;
|
||||||
|
|
||||||
struct ProfileElementSource
|
|
||||||
{
|
|
||||||
FlakeRef originalRef;
|
|
||||||
// FIXME: record original attrpath.
|
|
||||||
FlakeRef lockedRef;
|
|
||||||
std::string attrPath;
|
|
||||||
ExtendedOutputsSpec outputs;
|
|
||||||
|
|
||||||
bool operator < (const ProfileElementSource & other) const
|
|
||||||
{
|
|
||||||
return
|
|
||||||
std::tuple(originalRef.to_string(), attrPath, outputs) <
|
|
||||||
std::tuple(other.originalRef.to_string(), other.attrPath, other.outputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string to_string() const
|
|
||||||
{
|
|
||||||
return fmt("%s#%s%s", originalRef, attrPath, outputs.to_string());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const int defaultPriority = 5;
|
|
||||||
|
|
||||||
struct ProfileElement
|
|
||||||
{
|
|
||||||
StorePathSet storePaths;
|
|
||||||
std::optional<ProfileElementSource> source;
|
|
||||||
bool active = true;
|
|
||||||
int priority = defaultPriority;
|
|
||||||
|
|
||||||
std::string identifier() const
|
|
||||||
{
|
|
||||||
if (source)
|
|
||||||
return source->to_string();
|
|
||||||
StringSet names;
|
|
||||||
for (auto & path : storePaths)
|
|
||||||
names.insert(DrvName(path.name()).name);
|
|
||||||
return concatStringsSep(", ", names);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a string representing an installable corresponding to the current
|
|
||||||
* element, either a flakeref or a plain store path
|
|
||||||
*/
|
|
||||||
std::set<std::string> toInstallables(Store & store)
|
|
||||||
{
|
|
||||||
if (source)
|
|
||||||
return {source->to_string()};
|
|
||||||
StringSet rawPaths;
|
|
||||||
for (auto & path : storePaths)
|
|
||||||
rawPaths.insert(store.printStorePath(path));
|
|
||||||
return rawPaths;
|
|
||||||
}
|
|
||||||
|
|
||||||
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(identifier(), storePaths) < std::tuple(other.identifier(), other.storePaths);
|
|
||||||
}
|
|
||||||
|
|
||||||
void updateStorePaths(
|
|
||||||
ref<Store> evalStore,
|
|
||||||
ref<Store> store,
|
|
||||||
const BuiltPaths & builtPaths)
|
|
||||||
{
|
|
||||||
storePaths.clear();
|
|
||||||
for (auto & buildable : builtPaths) {
|
|
||||||
std::visit(overloaded {
|
|
||||||
[&](const BuiltPath::Opaque & bo) {
|
|
||||||
storePaths.insert(bo.path);
|
|
||||||
},
|
|
||||||
[&](const BuiltPath::Built & bfd) {
|
|
||||||
for (auto & output : bfd.outputs)
|
|
||||||
storePaths.insert(output.second);
|
|
||||||
},
|
|
||||||
}, buildable.raw());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
std::string sUrl;
|
|
||||||
std::string sOriginalUrl;
|
|
||||||
switch (version) {
|
|
||||||
case 1:
|
|
||||||
sUrl = "uri";
|
|
||||||
sOriginalUrl = "originalUri";
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
sUrl = "url";
|
|
||||||
sOriginalUrl = "originalUrl";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
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.contains("priority")) {
|
|
||||||
element.priority = e["priority"];
|
|
||||||
}
|
|
||||||
if (e.value(sUrl, "") != "") {
|
|
||||||
element.source = ProfileElementSource {
|
|
||||||
parseFlakeRef(e[sOriginalUrl]),
|
|
||||||
parseFlakeRef(e[sUrl]),
|
|
||||||
e["attrPath"],
|
|
||||||
e["outputs"].get<ExtendedOutputsSpec>()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
elements.emplace_back(std::move(element));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else if (pathExists(profile + "/manifest.nix")) {
|
|
||||||
// FIXME: needed because of pure mode; ugly.
|
|
||||||
state.allowPath(state.store->followLinksToStore(profile));
|
|
||||||
state.allowPath(state.store->followLinksToStore(profile + "/manifest.nix"));
|
|
||||||
|
|
||||||
auto drvInfos = queryInstalled(state, state.store->followLinksToStore(profile));
|
|
||||||
|
|
||||||
for (auto & drvInfo : drvInfos) {
|
|
||||||
ProfileElement element;
|
|
||||||
element.storePaths = {drvInfo.queryOutPath()};
|
|
||||||
elements.emplace_back(std::move(element));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nlohmann::json 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;
|
|
||||||
obj["priority"] = element.priority;
|
|
||||||
if (element.source) {
|
|
||||||
obj["originalUrl"] = element.source->originalRef.to_string();
|
|
||||||
obj["url"] = element.source->lockedRef.to_string();
|
|
||||||
obj["attrPath"] = element.source->attrPath;
|
|
||||||
obj["outputs"] = element.source->outputs;
|
|
||||||
}
|
|
||||||
array.push_back(obj);
|
|
||||||
}
|
|
||||||
nlohmann::json json;
|
|
||||||
json["version"] = 2;
|
|
||||||
json["elements"] = array;
|
|
||||||
return json;
|
|
||||||
}
|
|
||||||
|
|
||||||
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, element.priority);
|
|
||||||
references.insert(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
buildProfile(tempDir, std::move(pkgs));
|
|
||||||
|
|
||||||
writeFile(tempDir + "/manifest.json", toJSON(*store).dump());
|
|
||||||
|
|
||||||
/* Add the symlink tree to the store. */
|
|
||||||
StringSink sink;
|
|
||||||
dumpPath(tempDir, sink);
|
|
||||||
|
|
||||||
auto narHash = hashString(htSHA256, sink.s);
|
|
||||||
|
|
||||||
ValidPathInfo info {
|
|
||||||
*store,
|
|
||||||
"profile",
|
|
||||||
FixedOutputInfo {
|
|
||||||
.method = FileIngestionMethod::Recursive,
|
|
||||||
.hash = narHash,
|
|
||||||
.references = {
|
|
||||||
.others = std::move(references),
|
|
||||||
// profiles never refer to themselves
|
|
||||||
.self = false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
narHash,
|
|
||||||
};
|
|
||||||
info.narSize = sink.s.size();
|
|
||||||
|
|
||||||
StringSource source(sink.s);
|
|
||||||
store->addToStore(info, source);
|
|
||||||
|
|
||||||
return std::move(info.path);
|
|
||||||
}
|
|
||||||
|
|
||||||
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->identifier() > j->identifier())) {
|
|
||||||
logger->cout("%s%s: ∅ -> %s", indent, j->identifier(), j->versions());
|
|
||||||
changes = true;
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
else if (i != prevElems.end() && (j == curElems.end() || i->identifier() < j->identifier())) {
|
|
||||||
logger->cout("%s%s: %s -> ∅", indent, i->identifier(), i->versions());
|
|
||||||
changes = true;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
auto v1 = i->versions();
|
|
||||||
auto v2 = j->versions();
|
|
||||||
if (v1 != v2) {
|
|
||||||
logger->cout("%s%s: %s -> %s", indent, i->identifier(), v1, v2);
|
|
||||||
changes = true;
|
|
||||||
}
|
|
||||||
++i;
|
|
||||||
++j;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!changes)
|
|
||||||
logger->cout("%sNo changes.", indent);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::map<Installable *, std::pair<BuiltPaths, ref<ExtraPathInfo>>>
|
static std::map<Installable *, std::pair<BuiltPaths, ref<ExtraPathInfo>>>
|
||||||
builtPathsPerInstallable(
|
builtPathsPerInstallable(
|
||||||
|
@ -361,8 +99,8 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile
|
||||||
: ({
|
: ({
|
||||||
auto * info2 = dynamic_cast<ExtraPathInfoValue *>(&*info);
|
auto * info2 = dynamic_cast<ExtraPathInfoValue *>(&*info);
|
||||||
info2
|
info2
|
||||||
? info2->value.priority.value_or(defaultPriority)
|
? info2->value.priority.value_or(DEFAULT_PRIORITY)
|
||||||
: defaultPriority;
|
: DEFAULT_PRIORITY;
|
||||||
});
|
});
|
||||||
|
|
||||||
element.updateStorePaths(getEvalStore(), store, res);
|
element.updateStorePaths(getEvalStore(), store, res);
|
||||||
|
|
Loading…
Reference in a new issue