forked from lix-project/lix
refactor profile code from nix-env and nix profile to libcmd
Change-Id: I94e948ff6b92af7d0ab53b17c1570b7549c3b4e0
This commit is contained in:
parent
78ce710722
commit
abb6ff3111
284
src/libcmd/cmd-profiles.cc
Normal file
284
src/libcmd/cmd-profiles.cc
Normal file
|
@ -0,0 +1,284 @@
|
||||||
|
#include "cmd-profiles.hh"
|
||||||
|
#include "builtins/buildenv.hh"
|
||||||
|
#include "command.hh"
|
||||||
|
#include "names.hh"
|
||||||
|
#include "store-api.hh"
|
||||||
|
#include "../nix-env/user-env.hh"
|
||||||
|
|
||||||
|
#include <tuple>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
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 ProfileElementSource::operator<(ProfileElementSource const & rhs) const
|
||||||
|
{
|
||||||
|
auto const lhsTuple = std::tuple(originalRef.to_string(), attrPath, outputs);
|
||||||
|
auto const rhsTuple = std::tuple(rhs.originalRef.to_string(), rhs.attrPath, rhs.outputs);
|
||||||
|
|
||||||
|
return lhsTuple < rhsTuple;
|
||||||
|
}
|
||||||
|
|
||||||
|
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<(ProfileElement const & rhs) const
|
||||||
|
{
|
||||||
|
auto const lhsTuple = std::tuple(identifier(), storePaths);
|
||||||
|
auto const rhsTuple = std::tuple(rhs.identifier(), rhs.storePaths);
|
||||||
|
return lhsTuple < rhsTuple;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProfileElement::updateStorePaths(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
BuiltPaths const & builtPaths
|
||||||
|
)
|
||||||
|
{
|
||||||
|
storePaths.clear();
|
||||||
|
|
||||||
|
for (auto & buildable : builtPaths) {
|
||||||
|
std::visit(overloaded {
|
||||||
|
[&](BuiltPath::Opaque const & bo) {
|
||||||
|
storePaths.insert(bo.path);
|
||||||
|
},
|
||||||
|
[&](BuiltPath::Built const & bfd) {
|
||||||
|
for (auto & output : bfd.outputs) {
|
||||||
|
storePaths.insert(output.second);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}, buildable.raw());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileManifest::ProfileManifest(EvalState & state, Path const & profile)
|
||||||
|
{
|
||||||
|
auto manifestPath = profile + "/manifest.nix";
|
||||||
|
|
||||||
|
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")) {
|
||||||
|
printTalkative("found nix-env profile at %s", profile);
|
||||||
|
// 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(
|
||||||
|
ProfileManifest const & prev,
|
||||||
|
ProfileManifest const & 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
78
src/libcmd/cmd-profiles.hh
Normal file
78
src/libcmd/cmd-profiles.hh
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
#pragma once
|
||||||
|
///@file
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include "built-path.hh"
|
||||||
|
#include "eval.hh"
|
||||||
|
#include "flake/flakeref.hh"
|
||||||
|
#include "get-drvs.hh"
|
||||||
|
#include "path.hh"
|
||||||
|
#include "types.hh"
|
||||||
|
|
||||||
|
namespace nix
|
||||||
|
{
|
||||||
|
|
||||||
|
constexpr int DEFAULT_PRIORITY = 5;
|
||||||
|
|
||||||
|
DrvInfos queryInstalled(EvalState & state, const Path & userEnv);
|
||||||
|
|
||||||
|
struct ProfileElementSource
|
||||||
|
{
|
||||||
|
FlakeRef originalRef;
|
||||||
|
// FIXME: record original attrpath.
|
||||||
|
FlakeRef lockedRef;
|
||||||
|
std::string attrPath;
|
||||||
|
ExtendedOutputsSpec outputs;
|
||||||
|
|
||||||
|
bool operator<(ProfileElementSource const & rhs) const;
|
||||||
|
|
||||||
|
std::string to_string() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
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<(ProfileElement const & rhs) const;
|
||||||
|
|
||||||
|
void updateStorePaths(
|
||||||
|
ref<Store> evalStore,
|
||||||
|
ref<Store> store,
|
||||||
|
BuiltPaths const & builtPaths
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ProfileManifest
|
||||||
|
{
|
||||||
|
std::vector<ProfileElement> elements;
|
||||||
|
|
||||||
|
ProfileManifest()
|
||||||
|
: elements({})
|
||||||
|
{ }
|
||||||
|
|
||||||
|
ProfileManifest(EvalState & state, Path const & profile);
|
||||||
|
|
||||||
|
nlohmann::json toJSON(Store & store) const;
|
||||||
|
|
||||||
|
StorePath build(ref<Store> store);
|
||||||
|
|
||||||
|
static void printDiff(ProfileManifest const & prev, ProfileManifest const & cur, std::string_view indent);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
|
@ -319,4 +319,13 @@ void MixEnvironment::setEnviron() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
libcmd_sources = files(
|
libcmd_sources = files(
|
||||||
'built-path.cc',
|
'built-path.cc',
|
||||||
|
'cmd-profiles.cc',
|
||||||
'command-installable-value.cc',
|
'command-installable-value.cc',
|
||||||
'command.cc',
|
'command.cc',
|
||||||
'common-eval-args.cc',
|
'common-eval-args.cc',
|
||||||
|
@ -17,6 +18,7 @@ libcmd_sources = files(
|
||||||
|
|
||||||
libcmd_headers = files(
|
libcmd_headers = files(
|
||||||
'built-path.hh',
|
'built-path.hh',
|
||||||
|
'cmd-profiles.hh',
|
||||||
'command-installable-value.hh',
|
'command-installable-value.hh',
|
||||||
'command.hh',
|
'command.hh',
|
||||||
'common-eval-args.hh',
|
'common-eval-args.hh',
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "attr-path.hh"
|
#include "attr-path.hh"
|
||||||
|
#include "cmd-profiles.hh"
|
||||||
#include "common-eval-args.hh"
|
#include "common-eval-args.hh"
|
||||||
#include "derivations.hh"
|
#include "derivations.hh"
|
||||||
#include "eval.hh"
|
#include "eval.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)
|
||||||
|
|
|
@ -5,8 +5,6 @@
|
||||||
|
|
||||||
namespace nix {
|
namespace nix {
|
||||||
|
|
||||||
DrvInfos queryInstalled(EvalState & state, const Path & userEnv);
|
|
||||||
|
|
||||||
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);
|
||||||
|
|
|
@ -43,15 +43,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,
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include "flake/flakeref.hh"
|
#include "flake/flakeref.hh"
|
||||||
#include "../nix-env/user-env.hh"
|
#include "../nix-env/user-env.hh"
|
||||||
#include "profiles.hh"
|
#include "profiles.hh"
|
||||||
|
#include "cmd-profiles.hh"
|
||||||
#include "names.hh"
|
#include "names.hh"
|
||||||
|
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
@ -17,270 +18,8 @@
|
||||||
|
|
||||||
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;
|
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(
|
||||||
const std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> & builtPaths)
|
const std::vector<std::pair<ref<Installable>, BuiltPathWithResult>> & builtPaths)
|
||||||
|
|
Loading…
Reference in a new issue