From 1d00657e9b629f9f27695a51ddda9e221ce44b1c Mon Sep 17 00:00:00 2001 From: Qyriad Date: Sat, 27 Apr 2024 13:24:39 -0600 Subject: [PATCH 1/2] refactor some nix-env and nix profile code into libcmd Change-Id: Iefc8bbd34b4bc6012175cb3d6e6a8207973bc792 --- src/libcmd/cmd-profiles.cc | 100 +++++++++++++++++++++++++++++++++++++ src/libcmd/cmd-profiles.hh | 56 +++++++++++++++++++++ src/libcmd/command.hh | 2 - src/libcmd/meson.build | 2 + src/nix-env/user-env.cc | 16 ------ src/nix/diff-closures.cc | 10 +--- src/nix/profile.cc | 91 ++------------------------------- 7 files changed, 162 insertions(+), 115 deletions(-) create mode 100644 src/libcmd/cmd-profiles.cc create mode 100644 src/libcmd/cmd-profiles.hh diff --git a/src/libcmd/cmd-profiles.cc b/src/libcmd/cmd-profiles.cc new file mode 100644 index 000000000..dcc63e8c8 --- /dev/null +++ b/src/libcmd/cmd-profiles.cc @@ -0,0 +1,100 @@ +#include "cmd-profiles.hh" +#include "built-path.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 & 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() + ); + } +} +} diff --git a/src/libcmd/cmd-profiles.hh b/src/libcmd/cmd-profiles.hh new file mode 100644 index 000000000..27486482d --- /dev/null +++ b/src/libcmd/cmd-profiles.hh @@ -0,0 +1,56 @@ +#pragma once +///@file + +#include "built-path.hh" +#include "eval.hh" +#include "flake/flakeref.hh" +#include "get-drvs.hh" +#include "types.hh" + +#include +#include + +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 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 toInstallables(Store & store); + + std::string versions() const; + + bool operator<(const ProfileElement & other) const; + + void updateStorePaths(ref evalStore, ref store, const BuiltPaths & builtPaths); +}; + +DrvInfos queryInstalled(EvalState & state, const Path & userEnv); +std::string showVersions(const std::set & versions); + +} diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 120c832ac..6c6c81718 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -342,8 +342,6 @@ void completeFlakeRefWithFragment( const Strings & defaultFlakeAttrPaths, std::string_view prefix); -std::string showVersions(const std::set & versions); - void printClosureDiff( ref store, const StorePath & beforePath, diff --git a/src/libcmd/meson.build b/src/libcmd/meson.build index 6b1b44d84..5a0e61503 100644 --- a/src/libcmd/meson.build +++ b/src/libcmd/meson.build @@ -1,6 +1,7 @@ libcmd_sources = files( 'built-path.cc', 'command-installable-value.cc', + 'cmd-profiles.cc', 'command.cc', 'common-eval-args.cc', 'editor-for.cc', @@ -18,6 +19,7 @@ libcmd_sources = files( libcmd_headers = files( 'built-path.hh', 'command-installable-value.hh', + 'cmd-profiles.hh', 'command.hh', 'common-eval-args.hh', 'editor-for.hh', diff --git a/src/nix-env/user-env.cc b/src/nix-env/user-env.cc index 757938914..f0131a458 100644 --- a/src/nix-env/user-env.cc +++ b/src/nix-env/user-env.cc @@ -16,22 +16,6 @@ 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, const Path & profile, bool keepDerivations, const std::string & lockToken) diff --git a/src/nix/diff-closures.cc b/src/nix/diff-closures.cc index c7c37b66f..736cbf55d 100644 --- a/src/nix/diff-closures.cc +++ b/src/nix/diff-closures.cc @@ -1,4 +1,5 @@ #include "command.hh" +#include "cmd-profiles.hh" #include "shared.hh" #include "store-api.hh" #include "common-args.hh" @@ -43,15 +44,6 @@ GroupedPaths getClosureInfo(ref store, const StorePath & toplevel) return groupedPaths; } -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); -} - void printClosureDiff( ref store, const StorePath & beforePath, diff --git a/src/nix/profile.cc b/src/nix/profile.cc index 476ddcd60..5cc7281ab 100644 --- a/src/nix/profile.cc +++ b/src/nix/profile.cc @@ -1,4 +1,5 @@ #include "command.hh" +#include "cmd-profiles.hh" #include "installable-flake.hh" #include "common-args.hh" #include "shared.hh" @@ -17,92 +18,6 @@ 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 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 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 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()); - } - } -}; struct ProfileManifest { @@ -361,8 +276,8 @@ struct CmdProfileInstall : InstallablesCommand, MixDefaultProfile : ({ auto * info2 = dynamic_cast(&*info); info2 - ? info2->value.priority.value_or(defaultPriority) - : defaultPriority; + ? info2->value.priority.value_or(DEFAULT_PRIORITY) + : DEFAULT_PRIORITY; }); element.updateStorePaths(getEvalStore(), store, res); From a7c0aefb642e146b1d775b9549c5b458260a9e4a Mon Sep 17 00:00:00 2001 From: Qyriad Date: Sat, 27 Apr 2024 17:28:56 -0600 Subject: [PATCH 2/2] mvp Change-Id: I4c49b1beba93bb50e8f8a107edc451affe08c3f7 --- src/nix/upgrade-nix.cc | 128 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 116 insertions(+), 12 deletions(-) diff --git a/src/nix/upgrade-nix.cc b/src/nix/upgrade-nix.cc index af219c1b9..0878e0445 100644 --- a/src/nix/upgrade-nix.cc +++ b/src/nix/upgrade-nix.cc @@ -1,5 +1,6 @@ #include "command.hh" #include "common-args.hh" +#include "logging.hh" #include "store-api.hh" #include "filetransfer.hh" #include "eval.hh" @@ -15,6 +16,8 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand Path profileDir; std::string storePathsUrl = "https://github.com/NixOS/nixpkgs/raw/master/nixos/modules/installer/tools/nix-fallback-paths.nix"; + std::optional overrideStorePath; + CmdUpgradeNix() { addFlag({ @@ -25,6 +28,13 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand .handler = {&profileDir} }); + addFlag({ + .longName = "store-path", + .description = "A specific store path to upgrade Nix to", + .labels = {"store-path"}, + .handler = {&overrideStorePath}, + }); + addFlag({ .longName = "nix-store-paths-url", .description = "The URL of the file that contains the store paths of the latest Nix release.", @@ -59,12 +69,15 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand { evalSettings.pureEval = true; - if (profileDir == "") + if (profileDir == "") { profileDir = getProfileDir(store); + } + + auto canonProfileDir = canonPath(profileDir, true); printInfo("upgrading Nix in profile '%s'", profileDir); - auto storePath = getLatestNix(store); + StorePath storePath = getLatestNix(store); auto version = DrvName(storePath.name()).version; @@ -89,13 +102,41 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand stopProgressBar(); - { - Activity act(*logger, lvlInfo, actUnknown, - fmt("installing '%s' into profile '%s'...", store->printStorePath(storePath), profileDir)); - runProgram(settings.nixBinDir + "/nix-env", false, - {"--profile", profileDir, "-i", store->printStorePath(storePath), "--no-sandbox"}); + auto const fullStorePath = store->printStorePath(storePath); + + // Will be either nix-env or nix (for nix profile). + std::string upgradeCmd; + Strings upgradeArgs; + + if (canonProfileDir.ends_with("user-environment")) { + upgradeCmd = settings.nixBinDir + "/nix-env"; + upgradeArgs = { + "--profile", + this->profileDir, + "--install", + fullStorePath, + "--no-sandbox", + }; + } else if (canonProfileDir.ends_with("profile")) { + upgradeCmd = settings.nixBinDir + "/nix"; + upgradeArgs = { + "profile", + "install", + "--profile", + this->profileDir, + fullStorePath, + "--no-sandbox", + }; + } else { + // No I will not use std::unreachable. + // That is undefined behavior if you're wrong. + // This will have a half-decent error message and coredump. + assert("unreachable" == nullptr); } + printTalkative("running %s %s", upgradeCmd, concatStringsSep(" ", upgradeArgs)); + runProgram(upgradeCmd, false, upgradeArgs); + printInfo(ANSI_GREEN "upgrade to version %s done" ANSI_NORMAL, version); } @@ -121,26 +162,89 @@ struct CmdUpgradeNix : MixDryRun, StoreCommand Path profileDir = dirOf(where); // Resolve profile to /nix/var/nix/profiles/ link. - while (canonPath(profileDir).find("/profiles/") == std::string::npos && isLink(profileDir)) + while (canonPath(profileDir).find("/profiles/") == std::string::npos && isLink(profileDir)) { profileDir = readLink(profileDir); + } printInfo("found profile '%s'", profileDir); Path userEnv = canonPath(profileDir, true); - if (baseNameOf(where) != "bin" || - !userEnv.ends_with("user-environment")) - throw Error("directory '%s' does not appear to be part of a Nix profile", where); + if (baseNameOf(where) != "bin") { + if (!userEnv.ends_with("user-environment") && !userEnv.ends_with("profile")) { + throw Error("directory '%s' does not appear to be part of a Nix profile", where); + } + } - if (!store->isValidPath(store->parseStorePath(userEnv))) + if (!store->isValidPath(store->parseStorePath(userEnv))) { throw Error("directory '%s' is not in the Nix store", userEnv); + } return profileDir; } + void upgradeWithNixEnv(Store & store, StorePath const & newNix) + { + auto const fullStorePath = store.printStorePath(newNix); + + // Starts the activity on construction and stops it on destruction. + Activity act( + *logger, + lvlInfo, + actUnknown, + fmt("installing '%s' into profile '%s'...", fullStorePath) + ); + + auto const nixEnv = settings.nixBinDir + "/nix-env"; + Strings nixEnvArgs = { + "--profile", + this->profileDir, + "--install", + fullStorePath, + "--no-sandbox", + }; + + printTalkative("running %s %s", nixEnv, concatStringsSep(" ", nixEnvArgs)); + runProgram(nixEnv, false, nixEnvArgs); + } + + void upgradeWithNixProfile(Store & store, StorePath const & newNix) + { + auto const fullStorePath = store.printStorePath(newNix); + + // Starts the activity on construction and stops it on destruction. + Activity act( + *logger, + lvlInfo, + actUnknown, + fmt("installing '%s' into profile '%s'...", fullStorePath, this->profileDir, this->profileDir) + ); + + auto const nixCmd = settings.nixBinDir + "/nix"; + Strings nixCmdArgs = { + "profile", + "install", + "--profile", + this->profileDir, + fullStorePath, + "--no-sandbox", + }; + + printTalkative("running %s %s", nixCmd, concatStringsSep(" ", nixCmdArgs)); + runProgram(nixCmd, false, nixCmdArgs); + } + /* Return the store path of the latest stable Nix. */ StorePath getLatestNix(ref store) { + if (this->overrideStorePath) { + printTalkative( + "skipping Nix version query and using '%s' as latest Nix", + *this->overrideStorePath + ); + return store->parseStorePath(*this->overrideStorePath); + } + Activity act(*logger, lvlInfo, actUnknown, "querying latest Nix version"); // FIXME: use nixos.org?