#include #include "cmd-profiles.hh" #include "built-path.hh" #include "builtins/buildenv.hh" #include "logging.hh" #include "names.hh" #include "store-api.hh" #include "url-name.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 & versions) { if (versions.empty()) return "∅"; std::set 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 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 evalStore, ref 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: [[fallthrough]]; case 3: sUrl = "url"; sOriginalUrl = "originalUrl"; break; default: throw Error("profile manifest '%s' has unsupported version %d", manifestPath, version); } auto elems = json["elements"]; for (auto & elem : elems.items()) { auto & e = elem.value(); 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()}; } // TODO(Qyriad): holy crap this chain of ternaries needs cleanup. std::string name = elems.is_object() ? elem.key() : element.source ? getNameFromURL(parseURL(element.source->to_string())).value_or(element.identifier()) : element.identifier(); addElement(name, 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()}; addElement(std::move(element)); } } } void ProfileManifest::addElement(std::string_view nameCandidate, ProfileElement element) { std::string finalName(nameCandidate); for (unsigned i = 1; elements.contains(finalName); ++i) { finalName = nameCandidate + "-" + std::to_string(i); } elements.insert_or_assign(finalName, std::move(element)); } void ProfileManifest::addElement(ProfileElement element) { auto name = element.source ? getNameFromURL(parseURL(element.source->to_string())) : std::nullopt; auto finalName = name.value_or(element.identifier()); addElement(finalName, std::move(element)); } nlohmann::json ProfileManifest::toJSON(Store & store) const { auto es = nlohmann::json::object(); for (auto & [name, 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; } es[name] = obj; } nlohmann::json json; json["version"] = 3; json["elements"] = es; return json; } StorePath ProfileManifest::build(ref store) { auto tempDir = createTempDir(); StorePathSet references; Packages pkgs; for (auto & [name, 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; sink << dumpPath(tempDir); 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 prevElemIt = prev.elements.begin(); auto curElemIt = cur.elements.begin(); bool changes = false; while (prevElemIt != prev.elements.end() || curElemIt != cur.elements.end()) { if (curElemIt != cur.elements.end() && (prevElemIt == prev.elements.end() || prevElemIt->first > curElemIt->first)) { logger->cout("%s%s: ∅ -> %s", indent, curElemIt->second.identifier(), curElemIt->second.versions()); changes = true; ++curElemIt; } else if (prevElemIt != prev.elements.end() && (curElemIt == cur.elements.end() || prevElemIt->first < curElemIt->first)) { logger->cout("%s%s: %s -> ∅", indent, prevElemIt->second.identifier(), prevElemIt->second.versions()); changes = true; ++prevElemIt; } else { auto v1 = prevElemIt->second.versions(); auto v2 = curElemIt->second.versions(); if (v1 != v2) { logger->cout("%s%s: %s -> %s", indent, prevElemIt->second.identifier(), v1, v2); changes = true; } ++prevElemIt; ++curElemIt; } } if (!changes) { logger->cout("%sNo changes.", indent); } } }