aboutsummaryrefslogtreecommitdiff
path: root/src/libcmd
diff options
context:
space:
mode:
Diffstat (limited to 'src/libcmd')
-rw-r--r--src/libcmd/command.cc272
-rw-r--r--src/libcmd/command.hh313
-rw-r--r--src/libcmd/installables.cc893
-rw-r--r--src/libcmd/installables.hh126
-rw-r--r--src/libcmd/legacy.cc7
-rw-r--r--src/libcmd/legacy.hh23
-rw-r--r--src/libcmd/local.mk15
-rw-r--r--src/libcmd/markdown.cc50
-rw-r--r--src/libcmd/markdown.hh7
-rw-r--r--src/libcmd/nix-cmd.pc.in9
10 files changed, 1715 insertions, 0 deletions
diff --git a/src/libcmd/command.cc b/src/libcmd/command.cc
new file mode 100644
index 000000000..2daf43aa7
--- /dev/null
+++ b/src/libcmd/command.cc
@@ -0,0 +1,272 @@
+#include "command.hh"
+#include "store-api.hh"
+#include "local-fs-store.hh"
+#include "derivations.hh"
+#include "nixexpr.hh"
+#include "profiles.hh"
+
+#include <nlohmann/json.hpp>
+
+extern char * * environ __attribute__((weak));
+
+namespace nix {
+
+RegisterCommand::Commands * RegisterCommand::commands = nullptr;
+
+nix::Commands RegisterCommand::getCommandsFor(const std::vector<std::string> & prefix)
+{
+ nix::Commands res;
+ for (auto & [name, command] : *RegisterCommand::commands)
+ if (name.size() == prefix.size() + 1) {
+ bool equal = true;
+ for (size_t i = 0; i < prefix.size(); ++i)
+ if (name[i] != prefix[i]) equal = false;
+ if (equal)
+ res.insert_or_assign(name[prefix.size()], command);
+ }
+ return res;
+}
+
+nlohmann::json NixMultiCommand::toJSON()
+{
+ // FIXME: use Command::toJSON() as well.
+ return MultiCommand::toJSON();
+}
+
+StoreCommand::StoreCommand()
+{
+}
+
+ref<Store> StoreCommand::getStore()
+{
+ if (!_store)
+ _store = createStore();
+ return ref<Store>(_store);
+}
+
+ref<Store> StoreCommand::createStore()
+{
+ return openStore();
+}
+
+void StoreCommand::run()
+{
+ run(getStore());
+}
+
+EvalCommand::EvalCommand()
+{
+}
+
+EvalCommand::~EvalCommand()
+{
+ if (evalState)
+ evalState->printStats();
+}
+
+ref<Store> EvalCommand::getEvalStore()
+{
+ if (!evalStore)
+ evalStore = evalStoreUrl ? openStore(*evalStoreUrl) : getStore();
+ return ref<Store>(evalStore);
+}
+
+ref<EvalState> EvalCommand::getEvalState()
+{
+ if (!evalState)
+ evalState = std::make_shared<EvalState>(searchPath, getEvalStore(), getStore());
+ return ref<EvalState>(evalState);
+}
+
+BuiltPathsCommand::BuiltPathsCommand(bool recursive)
+ : recursive(recursive)
+{
+ if (recursive)
+ addFlag({
+ .longName = "no-recursive",
+ .description = "Apply operation to specified paths only.",
+ .category = installablesCategory,
+ .handler = {&this->recursive, false},
+ });
+ else
+ addFlag({
+ .longName = "recursive",
+ .shortName = 'r',
+ .description = "Apply operation to closure of the specified paths.",
+ .category = installablesCategory,
+ .handler = {&this->recursive, true},
+ });
+
+ addFlag({
+ .longName = "all",
+ .description = "Apply the operation to every store path.",
+ .category = installablesCategory,
+ .handler = {&all, true},
+ });
+}
+
+void BuiltPathsCommand::run(ref<Store> store)
+{
+ BuiltPaths paths;
+ if (all) {
+ if (installables.size())
+ throw UsageError("'--all' does not expect arguments");
+ // XXX: Only uses opaque paths, ignores all the realisations
+ for (auto & p : store->queryAllValidPaths())
+ paths.push_back(BuiltPath::Opaque{p});
+ } else {
+ paths = toBuiltPaths(getEvalStore(), store, realiseMode, operateOn, installables);
+ if (recursive) {
+ // XXX: This only computes the store path closure, ignoring
+ // intermediate realisations
+ StorePathSet pathsRoots, pathsClosure;
+ for (auto & root: paths) {
+ auto rootFromThis = root.outPaths();
+ pathsRoots.insert(rootFromThis.begin(), rootFromThis.end());
+ }
+ store->computeFSClosure(pathsRoots, pathsClosure);
+ for (auto & path : pathsClosure)
+ paths.push_back(BuiltPath::Opaque{path});
+ }
+ }
+
+ run(store, std::move(paths));
+}
+
+StorePathsCommand::StorePathsCommand(bool recursive)
+ : BuiltPathsCommand(recursive)
+{
+}
+
+void StorePathsCommand::run(ref<Store> store, BuiltPaths paths)
+{
+ StorePaths storePaths;
+ for (auto& builtPath : paths)
+ for (auto& p : builtPath.outPaths())
+ storePaths.push_back(p);
+
+ run(store, std::move(storePaths));
+}
+
+void StorePathCommand::run(ref<Store> store, std::vector<StorePath> storePaths)
+{
+ if (storePaths.size() != 1)
+ throw UsageError("this command requires exactly one store path");
+
+ run(store, *storePaths.begin());
+}
+
+Strings editorFor(const Pos & pos)
+{
+ auto editor = getEnv("EDITOR").value_or("cat");
+ auto args = tokenizeString<Strings>(editor);
+ if (pos.line > 0 && (
+ editor.find("emacs") != std::string::npos ||
+ editor.find("nano") != std::string::npos ||
+ editor.find("vim") != std::string::npos))
+ args.push_back(fmt("+%d", pos.line));
+ args.push_back(pos.file);
+ return args;
+}
+
+MixProfile::MixProfile()
+{
+ addFlag({
+ .longName = "profile",
+ .description = "The profile to update.",
+ .labels = {"path"},
+ .handler = {&profile},
+ .completer = completePath
+ });
+}
+
+void MixProfile::updateProfile(const StorePath & storePath)
+{
+ if (!profile) return;
+ auto store = getStore().dynamic_pointer_cast<LocalFSStore>();
+ if (!store) throw Error("'--profile' is not supported for this Nix store");
+ auto profile2 = absPath(*profile);
+ switchLink(profile2,
+ createGeneration(
+ ref<LocalFSStore>(store),
+ profile2, storePath));
+}
+
+void MixProfile::updateProfile(const BuiltPaths & buildables)
+{
+ if (!profile) return;
+
+ std::vector<StorePath> result;
+
+ for (auto & buildable : buildables) {
+ std::visit(overloaded {
+ [&](BuiltPath::Opaque bo) {
+ result.push_back(bo.path);
+ },
+ [&](BuiltPath::Built bfd) {
+ for (auto & output : bfd.outputs) {
+ result.push_back(output.second);
+ }
+ },
+ }, buildable.raw());
+ }
+
+ if (result.size() != 1)
+ throw UsageError("'--profile' requires that the arguments produce a single store path, but there are %d", result.size());
+
+ updateProfile(result[0]);
+}
+
+MixDefaultProfile::MixDefaultProfile()
+{
+ profile = getDefaultProfile();
+}
+
+MixEnvironment::MixEnvironment() : ignoreEnvironment(false)
+{
+ addFlag({
+ .longName = "ignore-environment",
+ .shortName = 'i',
+ .description = "Clear the entire environment (except those specified with `--keep`).",
+ .handler = {&ignoreEnvironment, true},
+ });
+
+ addFlag({
+ .longName = "keep",
+ .shortName = 'k',
+ .description = "Keep the environment variable *name*.",
+ .labels = {"name"},
+ .handler = {[&](std::string s) { keep.insert(s); }},
+ });
+
+ addFlag({
+ .longName = "unset",
+ .shortName = 'u',
+ .description = "Unset the environment variable *name*.",
+ .labels = {"name"},
+ .handler = {[&](std::string s) { unset.insert(s); }},
+ });
+}
+
+void MixEnvironment::setEnviron() {
+ if (ignoreEnvironment) {
+ if (!unset.empty())
+ throw UsageError("--unset does not make sense with --ignore-environment");
+
+ for (const auto & var : keep) {
+ auto val = getenv(var.c_str());
+ if (val) stringsEnv.emplace_back(fmt("%s=%s", var.c_str(), val));
+ }
+
+ vectorEnv = stringsToCharPtrs(stringsEnv);
+ environ = vectorEnv.data();
+ } else {
+ if (!keep.empty())
+ throw UsageError("--keep does not make sense without --ignore-environment");
+
+ for (const auto & var : unset)
+ unsetenv(var.c_str());
+ }
+}
+
+}
diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh
new file mode 100644
index 000000000..dac146d24
--- /dev/null
+++ b/src/libcmd/command.hh
@@ -0,0 +1,313 @@
+#pragma once
+
+#include "installables.hh"
+#include "args.hh"
+#include "common-eval-args.hh"
+#include "path.hh"
+#include "flake/lockfile.hh"
+#include "store-api.hh"
+
+#include <optional>
+
+namespace nix {
+
+extern std::string programPath;
+
+extern char * * savedArgv;
+
+class EvalState;
+struct Pos;
+class Store;
+
+static constexpr Command::Category catSecondary = 100;
+static constexpr Command::Category catUtility = 101;
+static constexpr Command::Category catNixInstallation = 102;
+
+static constexpr auto installablesCategory = "Options that change the interpretation of installables";
+
+struct NixMultiCommand : virtual MultiCommand, virtual Command
+{
+ nlohmann::json toJSON() override;
+};
+
+/* A command that requires a Nix store. */
+struct StoreCommand : virtual Command
+{
+ StoreCommand();
+ void run() override;
+ ref<Store> getStore();
+ virtual ref<Store> createStore();
+ virtual void run(ref<Store>) = 0;
+
+private:
+ std::shared_ptr<Store> _store;
+};
+
+struct EvalCommand : virtual StoreCommand, MixEvalArgs
+{
+ EvalCommand();
+
+ ~EvalCommand();
+
+ ref<Store> getEvalStore();
+
+ ref<EvalState> getEvalState();
+
+private:
+ std::shared_ptr<Store> evalStore;
+
+ std::shared_ptr<EvalState> evalState;
+};
+
+struct MixFlakeOptions : virtual Args, EvalCommand
+{
+ flake::LockFlags lockFlags;
+
+ MixFlakeOptions();
+
+ virtual std::optional<FlakeRef> getFlakeRefForCompletion()
+ { return {}; }
+};
+
+/* How to handle derivations in commands that operate on store paths. */
+enum class OperateOn {
+ /* Operate on the output path. */
+ Output,
+ /* Operate on the .drv path. */
+ Derivation
+};
+
+struct SourceExprCommand : virtual Args, MixFlakeOptions
+{
+ std::optional<Path> file;
+ std::optional<std::string> expr;
+
+ // FIXME: move this; not all commands (e.g. 'nix run') use it.
+ OperateOn operateOn = OperateOn::Output;
+
+ SourceExprCommand();
+
+ std::vector<std::shared_ptr<Installable>> parseInstallables(
+ ref<Store> store, std::vector<std::string> ss);
+
+ std::shared_ptr<Installable> parseInstallable(
+ ref<Store> store, const std::string & installable);
+
+ virtual Strings getDefaultFlakeAttrPaths();
+
+ virtual Strings getDefaultFlakeAttrPathPrefixes();
+
+ void completeInstallable(std::string_view prefix);
+};
+
+enum class Realise {
+ /* Build the derivation. Postcondition: the
+ derivation outputs exist. */
+ Outputs,
+ /* Don't build the derivation. Postcondition: the store derivation
+ exists. */
+ Derivation,
+ /* Evaluate in dry-run mode. Postcondition: nothing. */
+ // FIXME: currently unused, but could be revived if we can
+ // evaluate derivations in-memory.
+ Nothing
+};
+
+/* A command that operates on a list of "installables", which can be
+ store paths, attribute paths, Nix expressions, etc. */
+struct InstallablesCommand : virtual Args, SourceExprCommand
+{
+ std::vector<std::shared_ptr<Installable>> installables;
+
+ InstallablesCommand();
+
+ void prepare() override;
+
+ virtual bool useDefaultInstallables() { return true; }
+
+ std::optional<FlakeRef> getFlakeRefForCompletion() override;
+
+private:
+
+ std::vector<std::string> _installables;
+};
+
+/* A command that operates on exactly one "installable" */
+struct InstallableCommand : virtual Args, SourceExprCommand
+{
+ std::shared_ptr<Installable> installable;
+
+ InstallableCommand();
+
+ void prepare() override;
+
+ std::optional<FlakeRef> getFlakeRefForCompletion() override
+ {
+ return parseFlakeRef(_installable, absPath("."));
+ }
+
+private:
+
+ std::string _installable{"."};
+};
+
+/* A command that operates on zero or more store paths. */
+struct BuiltPathsCommand : public InstallablesCommand
+{
+private:
+
+ bool recursive = false;
+ bool all = false;
+
+protected:
+
+ Realise realiseMode = Realise::Derivation;
+
+public:
+
+ BuiltPathsCommand(bool recursive = false);
+
+ using StoreCommand::run;
+
+ virtual void run(ref<Store> store, BuiltPaths paths) = 0;
+
+ void run(ref<Store> store) override;
+
+ bool useDefaultInstallables() override { return !all; }
+};
+
+struct StorePathsCommand : public BuiltPathsCommand
+{
+ StorePathsCommand(bool recursive = false);
+
+ using BuiltPathsCommand::run;
+
+ virtual void run(ref<Store> store, std::vector<StorePath> storePaths) = 0;
+
+ void run(ref<Store> store, BuiltPaths paths) override;
+};
+
+/* A command that operates on exactly one store path. */
+struct StorePathCommand : public StorePathsCommand
+{
+ using StorePathsCommand::run;
+
+ virtual void run(ref<Store> store, const StorePath & storePath) = 0;
+
+ void run(ref<Store> store, std::vector<StorePath> storePaths) override;
+};
+
+/* A helper class for registering commands globally. */
+struct RegisterCommand
+{
+ typedef std::map<std::vector<std::string>, std::function<ref<Command>()>> Commands;
+ static Commands * commands;
+
+ RegisterCommand(std::vector<std::string> && name,
+ std::function<ref<Command>()> command)
+ {
+ if (!commands) commands = new Commands;
+ commands->emplace(name, command);
+ }
+
+ static nix::Commands getCommandsFor(const std::vector<std::string> & prefix);
+};
+
+template<class T>
+static RegisterCommand registerCommand(const std::string & name)
+{
+ return RegisterCommand({name}, [](){ return make_ref<T>(); });
+}
+
+template<class T>
+static RegisterCommand registerCommand2(std::vector<std::string> && name)
+{
+ return RegisterCommand(std::move(name), [](){ return make_ref<T>(); });
+}
+
+BuiltPaths build(
+ ref<Store> evalStore,
+ ref<Store> store, Realise mode,
+ const std::vector<std::shared_ptr<Installable>> & installables,
+ BuildMode bMode = bmNormal);
+
+std::set<StorePath> toStorePaths(
+ ref<Store> evalStore,
+ ref<Store> store,
+ Realise mode,
+ OperateOn operateOn,
+ const std::vector<std::shared_ptr<Installable>> & installables);
+
+StorePath toStorePath(
+ ref<Store> evalStore,
+ ref<Store> store,
+ Realise mode,
+ OperateOn operateOn,
+ std::shared_ptr<Installable> installable);
+
+std::set<StorePath> toDerivations(
+ ref<Store> store,
+ const std::vector<std::shared_ptr<Installable>> & installables,
+ bool useDeriver = false);
+
+BuiltPaths toBuiltPaths(
+ ref<Store> evalStore,
+ ref<Store> store,
+ Realise mode,
+ OperateOn operateOn,
+ const std::vector<std::shared_ptr<Installable>> & installables);
+
+/* Helper function to generate args that invoke $EDITOR on
+ filename:lineno. */
+Strings editorFor(const Pos & pos);
+
+struct MixProfile : virtual StoreCommand
+{
+ std::optional<Path> profile;
+
+ MixProfile();
+
+ /* If 'profile' is set, make it point at 'storePath'. */
+ void updateProfile(const StorePath & storePath);
+
+ /* If 'profile' is set, make it point at the store path produced
+ by 'buildables'. */
+ void updateProfile(const BuiltPaths & buildables);
+};
+
+struct MixDefaultProfile : MixProfile
+{
+ MixDefaultProfile();
+};
+
+struct MixEnvironment : virtual Args {
+
+ StringSet keep, unset;
+ Strings stringsEnv;
+ std::vector<char*> vectorEnv;
+ bool ignoreEnvironment;
+
+ MixEnvironment();
+
+ /* Modify global environ based on ignoreEnvironment, keep, and unset. It's expected that exec will be called before this class goes out of scope, otherwise environ will become invalid. */
+ void setEnviron();
+};
+
+void completeFlakeRef(ref<Store> store, std::string_view prefix);
+
+void completeFlakeRefWithFragment(
+ ref<EvalState> evalState,
+ flake::LockFlags lockFlags,
+ Strings attrPathPrefixes,
+ const Strings & defaultFlakeAttrPaths,
+ std::string_view prefix);
+
+std::string showVersions(const std::set<std::string> & versions);
+
+void printClosureDiff(
+ ref<Store> store,
+ const StorePath & beforePath,
+ const StorePath & afterPath,
+ std::string_view indent);
+
+}
diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc
new file mode 100644
index 000000000..8015cff4d
--- /dev/null
+++ b/src/libcmd/installables.cc
@@ -0,0 +1,893 @@
+#include "installables.hh"
+#include "command.hh"
+#include "attr-path.hh"
+#include "common-eval-args.hh"
+#include "derivations.hh"
+#include "eval-inline.hh"
+#include "eval.hh"
+#include "get-drvs.hh"
+#include "store-api.hh"
+#include "shared.hh"
+#include "flake/flake.hh"
+#include "eval-cache.hh"
+#include "url.hh"
+#include "registry.hh"
+
+#include <regex>
+#include <queue>
+
+#include <nlohmann/json.hpp>
+
+namespace nix {
+
+void completeFlakeInputPath(
+ ref<EvalState> evalState,
+ const FlakeRef & flakeRef,
+ std::string_view prefix)
+{
+ auto flake = flake::getFlake(*evalState, flakeRef, true);
+ for (auto & input : flake.inputs)
+ if (hasPrefix(input.first, prefix))
+ completions->add(input.first);
+}
+
+MixFlakeOptions::MixFlakeOptions()
+{
+ auto category = "Common flake-related options";
+
+ addFlag({
+ .longName = "recreate-lock-file",
+ .description = "Recreate the flake's lock file from scratch.",
+ .category = category,
+ .handler = {&lockFlags.recreateLockFile, true}
+ });
+
+ addFlag({
+ .longName = "no-update-lock-file",
+ .description = "Do not allow any updates to the flake's lock file.",
+ .category = category,
+ .handler = {&lockFlags.updateLockFile, false}
+ });
+
+ addFlag({
+ .longName = "no-write-lock-file",
+ .description = "Do not write the flake's newly generated lock file.",
+ .category = category,
+ .handler = {&lockFlags.writeLockFile, false}
+ });
+
+ addFlag({
+ .longName = "no-registries",
+ .description =
+ "Don't allow lookups in the flake registries. This option is deprecated; use `--no-use-registries`.",
+ .category = category,
+ .handler = {[&]() {
+ lockFlags.useRegistries = false;
+ warn("'--no-registries' is deprecated; use '--no-use-registries'");
+ }}
+ });
+
+ addFlag({
+ .longName = "commit-lock-file",
+ .description = "Commit changes to the flake's lock file.",
+ .category = category,
+ .handler = {&lockFlags.commitLockFile, true}
+ });
+
+ addFlag({
+ .longName = "update-input",
+ .description = "Update a specific flake input (ignoring its previous entry in the lock file).",
+ .category = category,
+ .labels = {"input-path"},
+ .handler = {[&](std::string s) {
+ lockFlags.inputUpdates.insert(flake::parseInputPath(s));
+ }},
+ .completer = {[&](size_t, std::string_view prefix) {
+ if (auto flakeRef = getFlakeRefForCompletion())
+ completeFlakeInputPath(getEvalState(), *flakeRef, prefix);
+ }}
+ });
+
+ addFlag({
+ .longName = "override-input",
+ .description = "Override a specific flake input (e.g. `dwarffs/nixpkgs`). This implies `--no-write-lock-file`.",
+ .category = category,
+ .labels = {"input-path", "flake-url"},
+ .handler = {[&](std::string inputPath, std::string flakeRef) {
+ lockFlags.writeLockFile = false;
+ lockFlags.inputOverrides.insert_or_assign(
+ flake::parseInputPath(inputPath),
+ parseFlakeRef(flakeRef, absPath(".")));
+ }}
+ });
+
+ addFlag({
+ .longName = "inputs-from",
+ .description = "Use the inputs of the specified flake as registry entries.",
+ .category = category,
+ .labels = {"flake-url"},
+ .handler = {[&](std::string flakeRef) {
+ auto evalState = getEvalState();
+ auto flake = flake::lockFlake(
+ *evalState,
+ parseFlakeRef(flakeRef, absPath(".")),
+ { .writeLockFile = false });
+ for (auto & [inputName, input] : flake.lockFile.root->inputs) {
+ auto input2 = flake.lockFile.findInput({inputName}); // resolve 'follows' nodes
+ if (auto input3 = std::dynamic_pointer_cast<const flake::LockedNode>(input2)) {
+ overrideRegistry(
+ fetchers::Input::fromAttrs({{"type","indirect"}, {"id", inputName}}),
+ input3->lockedRef.input,
+ {});
+ }
+ }
+ }},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeFlakeRef(getEvalState()->store, prefix);
+ }}
+ });
+}
+
+SourceExprCommand::SourceExprCommand()
+{
+ addFlag({
+ .longName = "file",
+ .shortName = 'f',
+ .description = "Interpret installables as attribute paths relative to the Nix expression stored in *file*.",
+ .category = installablesCategory,
+ .labels = {"file"},
+ .handler = {&file},
+ .completer = completePath
+ });
+
+ addFlag({
+ .longName = "expr",
+ .description = "Interpret installables as attribute paths relative to the Nix expression *expr*.",
+ .category = installablesCategory,
+ .labels = {"expr"},
+ .handler = {&expr}
+ });
+
+ addFlag({
+ .longName = "derivation",
+ .description = "Operate on the store derivation rather than its outputs.",
+ .category = installablesCategory,
+ .handler = {&operateOn, OperateOn::Derivation},
+ });
+}
+
+Strings SourceExprCommand::getDefaultFlakeAttrPaths()
+{
+ return {"defaultPackage." + settings.thisSystem.get()};
+}
+
+Strings SourceExprCommand::getDefaultFlakeAttrPathPrefixes()
+{
+ return {
+ // As a convenience, look for the attribute in
+ // 'outputs.packages'.
+ "packages." + settings.thisSystem.get() + ".",
+ // As a temporary hack until Nixpkgs is properly converted
+ // to provide a clean 'packages' set, look in 'legacyPackages'.
+ "legacyPackages." + settings.thisSystem.get() + "."
+ };
+}
+
+void SourceExprCommand::completeInstallable(std::string_view prefix)
+{
+ if (file) {
+ evalSettings.pureEval = false;
+ auto state = getEvalState();
+ Expr *e = state->parseExprFromFile(
+ resolveExprPath(state->checkSourcePath(lookupFileArg(*state, *file)))
+ );
+
+ Value root;
+ state->eval(e, root);
+
+ auto autoArgs = getAutoArgs(*state);
+
+ std::string prefix_ = std::string(prefix);
+ auto sep = prefix_.rfind('.');
+ std::string searchWord;
+ if (sep != std::string::npos) {
+ searchWord = prefix_.substr(sep, std::string::npos);
+ prefix_ = prefix_.substr(0, sep);
+ } else {
+ searchWord = prefix_;
+ prefix_ = "";
+ }
+
+ Value &v1(*findAlongAttrPath(*state, prefix_, *autoArgs, root).first);
+ state->forceValue(v1);
+ Value v2;
+ state->autoCallFunction(*autoArgs, v1, v2);
+
+ if (v2.type() == nAttrs) {
+ for (auto & i : *v2.attrs) {
+ std::string name = i.name;
+ if (name.find(searchWord) == 0) {
+ completions->add(i.name);
+ }
+ }
+ }
+ } else {
+ completeFlakeRefWithFragment(
+ getEvalState(),
+ lockFlags,
+ getDefaultFlakeAttrPathPrefixes(),
+ getDefaultFlakeAttrPaths(),
+ prefix);
+ }
+}
+
+void completeFlakeRefWithFragment(
+ ref<EvalState> evalState,
+ flake::LockFlags lockFlags,
+ Strings attrPathPrefixes,
+ const Strings & defaultFlakeAttrPaths,
+ std::string_view prefix)
+{
+ /* Look for flake output attributes that match the
+ prefix. */
+ try {
+ auto hash = prefix.find('#');
+ if (hash != std::string::npos) {
+ auto fragment = prefix.substr(hash + 1);
+ auto flakeRefS = std::string(prefix.substr(0, hash));
+ // FIXME: do tilde expansion.
+ auto flakeRef = parseFlakeRef(flakeRefS, absPath("."));
+
+ auto evalCache = openEvalCache(*evalState,
+ std::make_shared<flake::LockedFlake>(lockFlake(*evalState, flakeRef, lockFlags)));
+
+ auto root = evalCache->getRoot();
+
+ /* Complete 'fragment' relative to all the
+ attrpath prefixes as well as the root of the
+ flake. */
+ attrPathPrefixes.push_back("");
+
+ for (auto & attrPathPrefixS : attrPathPrefixes) {
+ auto attrPathPrefix = parseAttrPath(*evalState, attrPathPrefixS);
+ auto attrPathS = attrPathPrefixS + std::string(fragment);
+ auto attrPath = parseAttrPath(*evalState, attrPathS);
+
+ std::string lastAttr;
+ if (!attrPath.empty() && !hasSuffix(attrPathS, ".")) {
+ lastAttr = attrPath.back();
+ attrPath.pop_back();
+ }
+
+ auto attr = root->findAlongAttrPath(attrPath);
+ if (!attr) continue;
+
+ for (auto & attr2 : attr->getAttrs()) {
+ if (hasPrefix(attr2, lastAttr)) {
+ auto attrPath2 = attr->getAttrPath(attr2);
+ /* Strip the attrpath prefix. */
+ attrPath2.erase(attrPath2.begin(), attrPath2.begin() + attrPathPrefix.size());
+ completions->add(flakeRefS + "#" + concatStringsSep(".", attrPath2));
+ }
+ }
+ }
+
+ /* And add an empty completion for the default
+ attrpaths. */
+ if (fragment.empty()) {
+ for (auto & attrPath : defaultFlakeAttrPaths) {
+ auto attr = root->findAlongAttrPath(parseAttrPath(*evalState, attrPath));
+ if (!attr) continue;
+ completions->add(flakeRefS + "#");
+ }
+ }
+ }
+ } catch (Error & e) {
+ warn(e.msg());
+ }
+
+ completeFlakeRef(evalState->store, prefix);
+}
+
+void completeFlakeRef(ref<Store> store, std::string_view prefix)
+{
+ if (prefix == "")
+ completions->add(".");
+
+ completeDir(0, prefix);
+
+ /* Look for registry entries that match the prefix. */
+ for (auto & registry : fetchers::getRegistries(store)) {
+ for (auto & entry : registry->entries) {
+ auto from = entry.from.to_string();
+ if (!hasPrefix(prefix, "flake:") && hasPrefix(from, "flake:")) {
+ std::string from2(from, 6);
+ if (hasPrefix(from2, prefix))
+ completions->add(from2);
+ } else {
+ if (hasPrefix(from, prefix))
+ completions->add(from);
+ }
+ }
+ }
+}
+
+DerivedPath Installable::toDerivedPath()
+{
+ auto buildables = toDerivedPaths();
+ if (buildables.size() != 1)
+ throw Error("installable '%s' evaluates to %d derivations, where only one is expected", what(), buildables.size());
+ return std::move(buildables[0]);
+}
+
+std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+Installable::getCursors(EvalState & state)
+{
+ auto evalCache =
+ std::make_shared<nix::eval_cache::EvalCache>(std::nullopt, state,
+ [&]() { return toValue(state).first; });
+ return {{evalCache->getRoot(), ""}};
+}
+
+std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
+Installable::getCursor(EvalState & state)
+{
+ auto cursors = getCursors(state);
+ if (cursors.empty())
+ throw Error("cannot find flake attribute '%s'", what());
+ return cursors[0];
+}
+
+struct InstallableStorePath : Installable
+{
+ ref<Store> store;
+ StorePath storePath;
+
+ InstallableStorePath(ref<Store> store, StorePath && storePath)
+ : store(store), storePath(std::move(storePath)) { }
+
+ std::string what() override { return store->printStorePath(storePath); }
+
+ DerivedPaths toDerivedPaths() override
+ {
+ if (storePath.isDerivation()) {
+ auto drv = store->readDerivation(storePath);
+ return {
+ DerivedPath::Built {
+ .drvPath = storePath,
+ .outputs = drv.outputNames(),
+ }
+ };
+ } else {
+ return {
+ DerivedPath::Opaque {
+ .path = storePath,
+ }
+ };
+ }
+ }
+
+ std::optional<StorePath> getStorePath() override
+ {
+ return storePath;
+ }
+};
+
+DerivedPaths InstallableValue::toDerivedPaths()
+{
+ DerivedPaths res;
+
+ std::map<StorePath, std::set<std::string>> drvsToOutputs;
+ RealisedPath::Set drvsToCopy;
+
+ // Group by derivation, helps with .all in particular
+ for (auto & drv : toDerivations()) {
+ auto outputName = drv.outputName;
+ if (outputName == "")
+ throw Error("derivation '%s' lacks an 'outputName' attribute", state->store->printStorePath(drv.drvPath));
+ drvsToOutputs[drv.drvPath].insert(outputName);
+ drvsToCopy.insert(drv.drvPath);
+ }
+
+ for (auto & i : drvsToOutputs)
+ res.push_back(DerivedPath::Built { i.first, i.second });
+
+ return res;
+}
+
+struct InstallableAttrPath : InstallableValue
+{
+ SourceExprCommand & cmd;
+ RootValue v;
+ std::string attrPath;
+
+ InstallableAttrPath(ref<EvalState> state, SourceExprCommand & cmd, Value * v, const std::string & attrPath)
+ : InstallableValue(state), cmd(cmd), v(allocRootValue(v)), attrPath(attrPath)
+ { }
+
+ std::string what() override { return attrPath; }
+
+ std::pair<Value *, Pos> toValue(EvalState & state) override
+ {
+ auto [vRes, pos] = findAlongAttrPath(state, attrPath, *cmd.getAutoArgs(state), **v);
+ state.forceValue(*vRes);
+ return {vRes, pos};
+ }
+
+ virtual std::vector<InstallableValue::DerivationInfo> toDerivations() override;
+};
+
+std::vector<InstallableValue::DerivationInfo> InstallableAttrPath::toDerivations()
+{
+ auto v = toValue(*state).first;
+
+ Bindings & autoArgs = *cmd.getAutoArgs(*state);
+
+ DrvInfos drvInfos;
+ getDerivations(*state, *v, "", autoArgs, drvInfos, false);
+
+ std::vector<DerivationInfo> res;
+ for (auto & drvInfo : drvInfos) {
+ res.push_back({
+ state->store->parseStorePath(drvInfo.queryDrvPath()),
+ state->store->maybeParseStorePath(drvInfo.queryOutPath()),
+ drvInfo.queryOutputName()
+ });
+ }
+
+ return res;
+}
+
+std::vector<std::string> InstallableFlake::getActualAttrPaths()
+{
+ std::vector<std::string> res;
+
+ for (auto & prefix : prefixes)
+ res.push_back(prefix + *attrPaths.begin());
+
+ for (auto & s : attrPaths)
+ res.push_back(s);
+
+ return res;
+}
+
+Value * InstallableFlake::getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake)
+{
+ auto vFlake = state.allocValue();
+
+ callFlake(state, lockedFlake, *vFlake);
+
+ auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
+ assert(aOutputs);
+
+ state.forceValue(*aOutputs->value);
+
+ return aOutputs->value;
+}
+
+ref<eval_cache::EvalCache> openEvalCache(
+ EvalState & state,
+ std::shared_ptr<flake::LockedFlake> lockedFlake)
+{
+ auto fingerprint = lockedFlake->getFingerprint();
+ return make_ref<nix::eval_cache::EvalCache>(
+ evalSettings.useEvalCache && evalSettings.pureEval
+ ? std::optional { std::cref(fingerprint) }
+ : std::nullopt,
+ state,
+ [&state, lockedFlake]()
+ {
+ /* For testing whether the evaluation cache is
+ complete. */
+ if (getEnv("NIX_ALLOW_EVAL").value_or("1") == "0")
+ throw Error("not everything is cached, but evaluation is not allowed");
+
+ auto vFlake = state.allocValue();
+ flake::callFlake(state, *lockedFlake, *vFlake);
+
+ state.forceAttrs(*vFlake);
+
+ auto aOutputs = vFlake->attrs->get(state.symbols.create("outputs"));
+ assert(aOutputs);
+
+ return aOutputs->value;
+ });
+}
+
+static std::string showAttrPaths(const std::vector<std::string> & paths)
+{
+ std::string s;
+ for (const auto & [n, i] : enumerate(paths)) {
+ if (n > 0) s += n + 1 == paths.size() ? " or " : ", ";
+ s += '\''; s += i; s += '\'';
+ }
+ return s;
+}
+
+InstallableFlake::InstallableFlake(
+ SourceExprCommand * cmd,
+ ref<EvalState> state,
+ FlakeRef && flakeRef,
+ Strings && attrPaths,
+ Strings && prefixes,
+ const flake::LockFlags & lockFlags)
+ : InstallableValue(state),
+ flakeRef(flakeRef),
+ attrPaths(attrPaths),
+ prefixes(prefixes),
+ lockFlags(lockFlags)
+{
+ if (cmd && cmd->getAutoArgs(*state)->size())
+ throw UsageError("'--arg' and '--argstr' are incompatible with flakes");
+}
+
+std::tuple<std::string, FlakeRef, InstallableValue::DerivationInfo> InstallableFlake::toDerivation()
+{
+ auto lockedFlake = getLockedFlake();
+
+ auto cache = openEvalCache(*state, lockedFlake);
+ auto root = cache->getRoot();
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ auto attr = root->findAlongAttrPath(
+ parseAttrPath(*state, attrPath),
+ true
+ );
+
+ if (!attr) continue;
+
+ if (!attr->isDerivation())
+ throw Error("flake output attribute '%s' is not a derivation", attrPath);
+
+ auto drvPath = attr->forceDerivation();
+
+ auto drvInfo = DerivationInfo{
+ std::move(drvPath),
+ state->store->maybeParseStorePath(attr->getAttr(state->sOutPath)->getString()),
+ attr->getAttr(state->sOutputName)->getString()
+ };
+
+ return {attrPath, lockedFlake->flake.lockedRef, std::move(drvInfo)};
+ }
+
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, showAttrPaths(getActualAttrPaths()));
+}
+
+std::vector<InstallableValue::DerivationInfo> InstallableFlake::toDerivations()
+{
+ std::vector<DerivationInfo> res;
+ res.push_back(std::get<2>(toDerivation()));
+ return res;
+}
+
+std::pair<Value *, Pos> InstallableFlake::toValue(EvalState & state)
+{
+ auto lockedFlake = getLockedFlake();
+
+ auto vOutputs = getFlakeOutputs(state, *lockedFlake);
+
+ auto emptyArgs = state.allocBindings(0);
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ try {
+ auto [v, pos] = findAlongAttrPath(state, attrPath, *emptyArgs, *vOutputs);
+ state.forceValue(*v);
+ return {v, pos};
+ } catch (AttrPathNotFound & e) {
+ }
+ }
+
+ throw Error("flake '%s' does not provide attribute %s",
+ flakeRef, showAttrPaths(getActualAttrPaths()));
+}
+
+std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+InstallableFlake::getCursors(EvalState & state)
+{
+ auto evalCache = openEvalCache(state,
+ std::make_shared<flake::LockedFlake>(lockFlake(state, flakeRef, lockFlags)));
+
+ auto root = evalCache->getRoot();
+
+ std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>> res;
+
+ for (auto & attrPath : getActualAttrPaths()) {
+ auto attr = root->findAlongAttrPath(parseAttrPath(state, attrPath));
+ if (attr) res.push_back({attr, attrPath});
+ }
+
+ return res;
+}
+
+std::shared_ptr<flake::LockedFlake> InstallableFlake::getLockedFlake() const
+{
+ flake::LockFlags lockFlagsApplyConfig = lockFlags;
+ lockFlagsApplyConfig.applyNixConfig = true;
+ if (!_lockedFlake) {
+ _lockedFlake = std::make_shared<flake::LockedFlake>(lockFlake(*state, flakeRef, lockFlagsApplyConfig));
+ }
+ return _lockedFlake;
+}
+
+FlakeRef InstallableFlake::nixpkgsFlakeRef() const
+{
+ auto lockedFlake = getLockedFlake();
+
+ if (auto nixpkgsInput = lockedFlake->lockFile.findInput({"nixpkgs"})) {
+ if (auto lockedNode = std::dynamic_pointer_cast<const flake::LockedNode>(nixpkgsInput)) {
+ debug("using nixpkgs flake '%s'", lockedNode->lockedRef);
+ return std::move(lockedNode->lockedRef);
+ }
+ }
+
+ return Installable::nixpkgsFlakeRef();
+}
+
+std::vector<std::shared_ptr<Installable>> SourceExprCommand::parseInstallables(
+ ref<Store> store, std::vector<std::string> ss)
+{
+ std::vector<std::shared_ptr<Installable>> result;
+
+ if (file || expr) {
+ if (file && expr)
+ throw UsageError("'--file' and '--expr' are exclusive");
+
+ // FIXME: backward compatibility hack
+ if (file) evalSettings.pureEval = false;
+
+ auto state = getEvalState();
+ auto vFile = state->allocValue();
+
+ if (file)
+ state->evalFile(lookupFileArg(*state, *file), *vFile);
+ else {
+ auto e = state->parseExprFromString(*expr, absPath("."));
+ state->eval(e, *vFile);
+ }
+
+ for (auto & s : ss)
+ result.push_back(std::make_shared<InstallableAttrPath>(state, *this, vFile, s == "." ? "" : s));
+
+ } else {
+
+ for (auto & s : ss) {
+ std::exception_ptr ex;
+
+ if (s.find('/') != std::string::npos) {
+ try {
+ result.push_back(std::make_shared<InstallableStorePath>(store, store->followLinksToStorePath(s)));
+ continue;
+ } catch (BadStorePath &) {
+ } catch (...) {
+ if (!ex)
+ ex = std::current_exception();
+ }
+ }
+
+ try {
+ auto [flakeRef, fragment] = parseFlakeRefWithFragment(s, absPath("."));
+ result.push_back(std::make_shared<InstallableFlake>(
+ this,
+ getEvalState(),
+ std::move(flakeRef),
+ fragment == "" ? getDefaultFlakeAttrPaths() : Strings{fragment},
+ getDefaultFlakeAttrPathPrefixes(),
+ lockFlags));
+ continue;
+ } catch (...) {
+ ex = std::current_exception();
+ }
+
+ std::rethrow_exception(ex);
+ }
+ }
+
+ return result;
+}
+
+std::shared_ptr<Installable> SourceExprCommand::parseInstallable(
+ ref<Store> store, const std::string & installable)
+{
+ auto installables = parseInstallables(store, {installable});
+ assert(installables.size() == 1);
+ return installables.front();
+}
+
+BuiltPaths getBuiltPaths(ref<Store> evalStore, ref<Store> store, const DerivedPaths & hopefullyBuiltPaths)
+{
+ BuiltPaths res;
+ for (auto & b : hopefullyBuiltPaths)
+ std::visit(
+ overloaded{
+ [&](DerivedPath::Opaque bo) {
+ res.push_back(BuiltPath::Opaque{bo.path});
+ },
+ [&](DerivedPath::Built bfd) {
+ OutputPathMap outputs;
+ auto drv = evalStore->readDerivation(bfd.drvPath);
+ auto outputHashes = staticOutputHashes(*evalStore, drv); // FIXME: expensive
+ auto drvOutputs = drv.outputsAndOptPaths(*store);
+ for (auto & output : bfd.outputs) {
+ if (!outputHashes.count(output))
+ throw Error(
+ "the derivation '%s' doesn't have an output named '%s'",
+ store->printStorePath(bfd.drvPath), output);
+ if (settings.isExperimentalFeatureEnabled(
+ "ca-derivations")) {
+ auto outputId =
+ DrvOutput{outputHashes.at(output), output};
+ auto realisation =
+ store->queryRealisation(outputId);
+ if (!realisation)
+ throw Error(
+ "cannot operate on an output of unbuilt "
+ "content-addressed derivation '%s'",
+ outputId.to_string());
+ outputs.insert_or_assign(
+ output, realisation->outPath);
+ } else {
+ // If ca-derivations isn't enabled, assume that
+ // the output path is statically known.
+ assert(drvOutputs.count(output));
+ assert(drvOutputs.at(output).second);
+ outputs.insert_or_assign(
+ output, *drvOutputs.at(output).second);
+ }
+ }
+ res.push_back(BuiltPath::Built{bfd.drvPath, outputs});
+ },
+ },
+ b.raw());
+
+ return res;
+}
+
+BuiltPaths build(
+ ref<Store> evalStore,
+ ref<Store> store,
+ Realise mode,
+ const std::vector<std::shared_ptr<Installable>> & installables,
+ BuildMode bMode)
+{
+ if (mode == Realise::Nothing)
+ settings.readOnlyMode = true;
+
+ std::vector<DerivedPath> pathsToBuild;
+
+ for (auto & i : installables) {
+ auto b = i->toDerivedPaths();
+ pathsToBuild.insert(pathsToBuild.end(), b.begin(), b.end());
+ }
+
+ if (mode == Realise::Nothing || mode == Realise::Derivation)
+ printMissing(store, pathsToBuild, lvlError);
+ else if (mode == Realise::Outputs)
+ store->buildPaths(pathsToBuild, bMode, evalStore);
+
+ return getBuiltPaths(evalStore, store, pathsToBuild);
+}
+
+BuiltPaths toBuiltPaths(
+ ref<Store> evalStore,
+ ref<Store> store,
+ Realise mode,
+ OperateOn operateOn,
+ const std::vector<std::shared_ptr<Installable>> & installables)
+{
+ if (operateOn == OperateOn::Output)
+ return build(evalStore, store, mode, installables);
+ else {
+ if (mode == Realise::Nothing)
+ settings.readOnlyMode = true;
+
+ BuiltPaths res;
+ for (auto & drvPath : toDerivations(store, installables, true))
+ res.push_back(BuiltPath::Opaque{drvPath});
+ return res;
+ }
+}
+
+StorePathSet toStorePaths(
+ ref<Store> evalStore,
+ ref<Store> store,
+ Realise mode, OperateOn operateOn,
+ const std::vector<std::shared_ptr<Installable>> & installables)
+{
+ StorePathSet outPaths;
+ for (auto & path : toBuiltPaths(evalStore, store, mode, operateOn, installables)) {
+ auto thisOutPaths = path.outPaths();
+ outPaths.insert(thisOutPaths.begin(), thisOutPaths.end());
+ }
+ return outPaths;
+}
+
+StorePath toStorePath(
+ ref<Store> evalStore,
+ ref<Store> store,
+ Realise mode, OperateOn operateOn,
+ std::shared_ptr<Installable> installable)
+{
+ auto paths = toStorePaths(evalStore, store, mode, operateOn, {installable});
+
+ if (paths.size() != 1)
+ throw Error("argument '%s' should evaluate to one store path", installable->what());
+
+ return *paths.begin();
+}
+
+StorePathSet toDerivations(
+ ref<Store> store,
+ const std::vector<std::shared_ptr<Installable>> & installables,
+ bool useDeriver)
+{
+ StorePathSet drvPaths;
+
+ for (auto & i : installables)
+ for (auto & b : i->toDerivedPaths())
+ std::visit(overloaded {
+ [&](DerivedPath::Opaque bo) {
+ if (!useDeriver)
+ throw Error("argument '%s' did not evaluate to a derivation", i->what());
+ auto derivers = store->queryValidDerivers(bo.path);
+ if (derivers.empty())
+ throw Error("'%s' does not have a known deriver", i->what());
+ // FIXME: use all derivers?
+ drvPaths.insert(*derivers.begin());
+ },
+ [&](DerivedPath::Built bfd) {
+ drvPaths.insert(bfd.drvPath);
+ },
+ }, b.raw());
+
+ return drvPaths;
+}
+
+InstallablesCommand::InstallablesCommand()
+{
+ expectArgs({
+ .label = "installables",
+ .handler = {&_installables},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeInstallable(prefix);
+ }}
+ });
+}
+
+void InstallablesCommand::prepare()
+{
+ if (_installables.empty() && useDefaultInstallables())
+ // FIXME: commands like "nix install" should not have a
+ // default, probably.
+ _installables.push_back(".");
+ installables = parseInstallables(getStore(), _installables);
+}
+
+std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
+{
+ if (_installables.empty()) {
+ if (useDefaultInstallables())
+ return parseFlakeRef(".", absPath("."));
+ return {};
+ }
+ return parseFlakeRef(_installables.front(), absPath("."));
+}
+
+InstallableCommand::InstallableCommand()
+{
+ expectArgs({
+ .label = "installable",
+ .optional = true,
+ .handler = {&_installable},
+ .completer = {[&](size_t, std::string_view prefix) {
+ completeInstallable(prefix);
+ }}
+ });
+}
+
+void InstallableCommand::prepare()
+{
+ installable = parseInstallable(getStore(), _installable);
+}
+
+}
diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh
new file mode 100644
index 000000000..79931ad3e
--- /dev/null
+++ b/src/libcmd/installables.hh
@@ -0,0 +1,126 @@
+#pragma once
+
+#include "util.hh"
+#include "path.hh"
+#include "path-with-outputs.hh"
+#include "derived-path.hh"
+#include "eval.hh"
+#include "flake/flake.hh"
+
+#include <optional>
+
+namespace nix {
+
+struct DrvInfo;
+struct SourceExprCommand;
+
+namespace eval_cache { class EvalCache; class AttrCursor; }
+
+struct App
+{
+ std::vector<StorePathWithOutputs> context;
+ Path program;
+ // FIXME: add args, sandbox settings, metadata, ...
+};
+
+struct UnresolvedApp
+{
+ App unresolved;
+ App resolve(ref<Store> evalStore, ref<Store> store);
+};
+
+struct Installable
+{
+ virtual ~Installable() { }
+
+ virtual std::string what() = 0;
+
+ virtual DerivedPaths toDerivedPaths() = 0;
+
+ DerivedPath toDerivedPath();
+
+ UnresolvedApp toApp(EvalState & state);
+
+ virtual std::pair<Value *, Pos> toValue(EvalState & state)
+ {
+ throw Error("argument '%s' cannot be evaluated", what());
+ }
+
+ /* Return a value only if this installable is a store path or a
+ symlink to it. */
+ virtual std::optional<StorePath> getStorePath()
+ {
+ return {};
+ }
+
+ virtual std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+ getCursors(EvalState & state);
+
+ std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>
+ getCursor(EvalState & state);
+
+ virtual FlakeRef nixpkgsFlakeRef() const
+ {
+ return FlakeRef::fromAttrs({{"type","indirect"}, {"id", "nixpkgs"}});
+ }
+};
+
+struct InstallableValue : Installable
+{
+ ref<EvalState> state;
+
+ InstallableValue(ref<EvalState> state) : state(state) {}
+
+ struct DerivationInfo
+ {
+ StorePath drvPath;
+ std::optional<StorePath> outPath;
+ std::string outputName;
+ };
+
+ virtual std::vector<DerivationInfo> toDerivations() = 0;
+
+ DerivedPaths toDerivedPaths() override;
+};
+
+struct InstallableFlake : InstallableValue
+{
+ FlakeRef flakeRef;
+ Strings attrPaths;
+ Strings prefixes;
+ const flake::LockFlags & lockFlags;
+ mutable std::shared_ptr<flake::LockedFlake> _lockedFlake;
+
+ InstallableFlake(
+ SourceExprCommand * cmd,
+ ref<EvalState> state,
+ FlakeRef && flakeRef,
+ Strings && attrPaths,
+ Strings && prefixes,
+ const flake::LockFlags & lockFlags);
+
+ std::string what() override { return flakeRef.to_string() + "#" + *attrPaths.begin(); }
+
+ std::vector<std::string> getActualAttrPaths();
+
+ Value * getFlakeOutputs(EvalState & state, const flake::LockedFlake & lockedFlake);
+
+ std::tuple<std::string, FlakeRef, DerivationInfo> toDerivation();
+
+ std::vector<DerivationInfo> toDerivations() override;
+
+ std::pair<Value *, Pos> toValue(EvalState & state) override;
+
+ std::vector<std::pair<std::shared_ptr<eval_cache::AttrCursor>, std::string>>
+ getCursors(EvalState & state) override;
+
+ std::shared_ptr<flake::LockedFlake> getLockedFlake() const;
+
+ FlakeRef nixpkgsFlakeRef() const override;
+};
+
+ref<eval_cache::EvalCache> openEvalCache(
+ EvalState & state,
+ std::shared_ptr<flake::LockedFlake> lockedFlake);
+
+}
diff --git a/src/libcmd/legacy.cc b/src/libcmd/legacy.cc
new file mode 100644
index 000000000..6df09ee37
--- /dev/null
+++ b/src/libcmd/legacy.cc
@@ -0,0 +1,7 @@
+#include "legacy.hh"
+
+namespace nix {
+
+RegisterLegacyCommand::Commands * RegisterLegacyCommand::commands = 0;
+
+}
diff --git a/src/libcmd/legacy.hh b/src/libcmd/legacy.hh
new file mode 100644
index 000000000..f503b0da3
--- /dev/null
+++ b/src/libcmd/legacy.hh
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <functional>
+#include <map>
+#include <string>
+
+namespace nix {
+
+typedef std::function<void(int, char * *)> MainFunction;
+
+struct RegisterLegacyCommand
+{
+ typedef std::map<std::string, MainFunction> Commands;
+ static Commands * commands;
+
+ RegisterLegacyCommand(const std::string & name, MainFunction fun)
+ {
+ if (!commands) commands = new Commands;
+ (*commands)[name] = fun;
+ }
+};
+
+}
diff --git a/src/libcmd/local.mk b/src/libcmd/local.mk
new file mode 100644
index 000000000..8b0662753
--- /dev/null
+++ b/src/libcmd/local.mk
@@ -0,0 +1,15 @@
+libraries += libcmd
+
+libcmd_NAME = libnixcmd
+
+libcmd_DIR := $(d)
+
+libcmd_SOURCES := $(wildcard $(d)/*.cc)
+
+libcmd_CXXFLAGS += -I src/libutil -I src/libstore -I src/libexpr -I src/libmain -I src/libfetchers
+
+libcmd_LDFLAGS += -llowdown -pthread
+
+libcmd_LIBS = libstore libutil libexpr libmain libfetchers
+
+$(eval $(call install-file-in, $(d)/nix-cmd.pc, $(libdir)/pkgconfig, 0644))
diff --git a/src/libcmd/markdown.cc b/src/libcmd/markdown.cc
new file mode 100644
index 000000000..737356284
--- /dev/null
+++ b/src/libcmd/markdown.cc
@@ -0,0 +1,50 @@
+#include "markdown.hh"
+#include "util.hh"
+#include "finally.hh"
+
+#include <sys/queue.h>
+#include <lowdown.h>
+
+namespace nix {
+
+std::string renderMarkdownToTerminal(std::string_view markdown)
+{
+ struct lowdown_opts opts {
+ .type = LOWDOWN_TERM,
+ .maxdepth = 20,
+ .cols = std::max(getWindowSize().second, (unsigned short) 80),
+ .hmargin = 0,
+ .vmargin = 0,
+ .feat = LOWDOWN_COMMONMARK | LOWDOWN_FENCED | LOWDOWN_DEFLIST | LOWDOWN_TABLES,
+ .oflags = 0,
+ };
+
+ auto doc = lowdown_doc_new(&opts);
+ if (!doc)
+ throw Error("cannot allocate Markdown document");
+ Finally freeDoc([&]() { lowdown_doc_free(doc); });
+
+ size_t maxn = 0;
+ auto node = lowdown_doc_parse(doc, &maxn, markdown.data(), markdown.size());
+ if (!node)
+ throw Error("cannot parse Markdown document");
+ Finally freeNode([&]() { lowdown_node_free(node); });
+
+ auto renderer = lowdown_term_new(&opts);
+ if (!renderer)
+ throw Error("cannot allocate Markdown renderer");
+ Finally freeRenderer([&]() { lowdown_term_free(renderer); });
+
+ auto buf = lowdown_buf_new(16384);
+ if (!buf)
+ throw Error("cannot allocate Markdown output buffer");
+ Finally freeBuffer([&]() { lowdown_buf_free(buf); });
+
+ int rndr_res = lowdown_term_rndr(buf, nullptr, renderer, node);
+ if (!rndr_res)
+ throw Error("allocation error while rendering Markdown");
+
+ return filterANSIEscapes(std::string(buf->data, buf->size), !shouldANSI());
+}
+
+}
diff --git a/src/libcmd/markdown.hh b/src/libcmd/markdown.hh
new file mode 100644
index 000000000..78320fcf5
--- /dev/null
+++ b/src/libcmd/markdown.hh
@@ -0,0 +1,7 @@
+#include "types.hh"
+
+namespace nix {
+
+std::string renderMarkdownToTerminal(std::string_view markdown);
+
+}
diff --git a/src/libcmd/nix-cmd.pc.in b/src/libcmd/nix-cmd.pc.in
new file mode 100644
index 000000000..1761a9f41
--- /dev/null
+++ b/src/libcmd/nix-cmd.pc.in
@@ -0,0 +1,9 @@
+prefix=@prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: Nix
+Description: Nix Package Manager
+Version: @PACKAGE_VERSION@
+Libs: -L${libdir} -lnixcmd
+Cflags: -I${includedir}/nix -std=c++17