diff --git a/doc/manual/src/release-notes/rl-next.md b/doc/manual/src/release-notes/rl-next.md index 52e3b6240..9684a70d4 100644 --- a/doc/manual/src/release-notes/rl-next.md +++ b/doc/manual/src/release-notes/rl-next.md @@ -2,3 +2,10 @@ * Nix can now be built with LTO by passing `--enable-lto` to `configure`. LTO is currently only supported when building with GCC. + +* `nix repl` now takes installables on the command line, unifying the usage + with other commands that use `--file` and `--expr`. Primary breaking change + is for the common usage of `nix repl ''` which can be recovered with + `nix repl --file ''` or `nix repl --expr 'import {}'` + + This is currently guarded by the 'repl-flake' experimental feature diff --git a/src/libcmd/command.hh b/src/libcmd/command.hh index 8982f21d0..3df6cd114 100644 --- a/src/libcmd/command.hh +++ b/src/libcmd/command.hh @@ -116,12 +116,13 @@ struct InstallablesCommand : virtual Args, SourceExprCommand InstallablesCommand(); void prepare() override; + Installables load(); virtual bool useDefaultInstallables() { return true; } std::optional getFlakeRefForCompletion() override; -private: +protected: std::vector _installables; }; diff --git a/src/libcmd/installables.cc b/src/libcmd/installables.cc index ffc25135e..03c8f82b3 100644 --- a/src/libcmd/installables.cc +++ b/src/libcmd/installables.cc @@ -1036,11 +1036,16 @@ InstallablesCommand::InstallablesCommand() void InstallablesCommand::prepare() { + installables = load(); +} + +Installables InstallablesCommand::load() { + Installables installables; if (_installables.empty() && useDefaultInstallables()) // FIXME: commands like "nix profile install" should not have a // default, probably. _installables.push_back("."); - installables = parseInstallables(getStore(), _installables); + return parseInstallables(getStore(), _installables); } std::optional InstallablesCommand::getFlakeRefForCompletion() diff --git a/src/libcmd/installables.hh b/src/libcmd/installables.hh index 5d715210e..948f78919 100644 --- a/src/libcmd/installables.hh +++ b/src/libcmd/installables.hh @@ -132,6 +132,8 @@ struct Installable const std::vector> & installables); }; +typedef std::vector> Installables; + struct InstallableValue : Installable { ref state; diff --git a/src/libcmd/repl.cc b/src/libcmd/repl.cc index 588115a48..23df40337 100644 --- a/src/libcmd/repl.cc +++ b/src/libcmd/repl.cc @@ -22,6 +22,7 @@ extern "C" { #include "ansicolor.hh" #include "shared.hh" #include "eval.hh" +#include "eval-cache.hh" #include "eval-inline.hh" #include "attr-path.hh" #include "store-api.hh" @@ -54,6 +55,8 @@ struct NixRepl size_t debugTraceIndex; Strings loadedFiles; + typedef std::vector> AnnotatedValues; + std::function getValues; const static int envSize = 32768; std::shared_ptr staticEnv; @@ -63,13 +66,15 @@ struct NixRepl const Path historyFile; - NixRepl(ref state); + NixRepl(const Strings & searchPath, nix::ref store,ref state, + std::function getValues); ~NixRepl(); - void mainLoop(const std::vector & files); + void mainLoop(); StringSet completePrefix(const std::string & prefix); bool getLine(std::string & input, const std::string & prompt); StorePath getDerivationPath(Value & v); bool processLine(std::string line); + void loadFile(const Path & path); void loadFlake(const std::string & flakeRef); void initEnv(); @@ -96,9 +101,11 @@ std::string removeWhitespace(std::string s) } -NixRepl::NixRepl(ref state) +NixRepl::NixRepl(const Strings & searchPath, nix::ref store, ref state, + std::function getValues) : state(state) , debugTraceIndex(0) + , getValues(getValues) , staticEnv(new StaticEnv(false, state->staticBaseEnv.get())) , historyFile(getDataDir() + "/nix/repl-history") { @@ -225,18 +232,12 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi return out; } -void NixRepl::mainLoop(const std::vector & files) +void NixRepl::mainLoop() { std::string error = ANSI_RED "error:" ANSI_NORMAL " "; notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n"); - if (!files.empty()) { - for (auto & i : files) - loadedFiles.push_back(i); - } - loadFiles(); - if (!loadedFiles.empty()) notice(""); // Allow nix-repl specific settings in .inputrc rl_readline_name = "nix-repl"; @@ -746,7 +747,6 @@ bool NixRepl::processLine(std::string line) return true; } - void NixRepl::loadFile(const Path & path) { loadedFiles.remove(path); @@ -806,13 +806,15 @@ void NixRepl::loadFiles() Strings old = loadedFiles; loadedFiles.clear(); - bool first = true; for (auto & i : old) { - if (!first) notice(""); - first = false; notice("Loading '%1%'...", i); loadFile(i); } + + for (auto & [i, what] : getValues()) { + notice("Loading installable '%1%'...", what); + addAttrsToScope(*i); + } } @@ -1012,7 +1014,17 @@ void runRepl( refevalState, const ValMap & extraEnv) { - auto repl = std::make_unique(evalState); + auto getValues = [&]()->NixRepl::AnnotatedValues{ + NixRepl::AnnotatedValues values; + return values; + }; + const Strings & searchPath = {}; + auto repl = std::make_unique( + searchPath, + openStore(), + evalState, + getValues + ); repl->initEnv(); @@ -1020,20 +1032,35 @@ void runRepl( for (auto & [name, value] : extraEnv) repl->addVarToScope(repl->state->symbols.create(name), *value); - repl->mainLoop({}); + repl->mainLoop(); } -struct CmdRepl : StoreCommand, MixEvalArgs +struct CmdRepl : InstallablesCommand { - std::vector files; - - CmdRepl() + CmdRepl(){ + evalSettings.pureEval = false; + } + void prepare() { - expectArgs({ - .label = "files", - .handler = {&files}, - .completer = completePath - }); + if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) { + warn("future versions of Nix will require using `--file` to load a file"); + if (this->_installables.size() > 1) + warn("more than one input file is not currently supported"); + auto filePath = this->_installables[0].data(); + file = std::optional(filePath); + _installables.front() = _installables.back(); + _installables.pop_back(); + } + installables = InstallablesCommand::load(); + } + std::vector files; + Strings getDefaultFlakeAttrPaths() override + { + return {""}; + } + virtual bool useDefaultInstallables() override + { + return file.has_value() or expr.has_value(); } bool forceImpureByDefault() override @@ -1055,12 +1082,37 @@ struct CmdRepl : StoreCommand, MixEvalArgs void run(ref store) override { - auto evalState = make_ref(searchPath, store); - - auto repl = std::make_unique(evalState); + auto state = getEvalState(); + auto getValues = [&]()->NixRepl::AnnotatedValues{ + auto installables = load(); + NixRepl::AnnotatedValues values; + for (auto & installable: installables){ + auto what = installable->what(); + if (file){ + auto [val, pos] = installable->toValue(*state); + auto what = installable->what(); + state->forceValue(*val, pos); + auto autoArgs = getAutoArgs(*state); + auto valPost = state->allocValue(); + state->autoCallFunction(*autoArgs, *val, *valPost); + state->forceValue(*valPost, pos); + values.push_back( {valPost, what }); + } else { + auto [val, pos] = installable->toValue(*state); + values.push_back( {val, what} ); + } + } + return values; + }; + auto repl = std::make_unique( + searchPath, + openStore(), + state, + getValues + ); repl->autoArgs = getAutoArgs(*repl->state); repl->initEnv(); - repl->mainLoop(files); + repl->mainLoop(); } }; diff --git a/src/libutil/experimental-features.cc b/src/libutil/experimental-features.cc index 315de64a4..fa79cca6b 100644 --- a/src/libutil/experimental-features.cc +++ b/src/libutil/experimental-features.cc @@ -13,6 +13,7 @@ std::map stringifiedXpFeatures = { { Xp::RecursiveNix, "recursive-nix" }, { Xp::NoUrlLiterals, "no-url-literals" }, { Xp::FetchClosure, "fetch-closure" }, + { Xp::ReplFlake, "repl-flake" }, }; const std::optional parseExperimentalFeature(const std::string_view & name) diff --git a/src/libutil/experimental-features.hh b/src/libutil/experimental-features.hh index 57512830c..d09ab025c 100644 --- a/src/libutil/experimental-features.hh +++ b/src/libutil/experimental-features.hh @@ -22,6 +22,7 @@ enum struct ExperimentalFeature RecursiveNix, NoUrlLiterals, FetchClosure, + ReplFlake, }; /** diff --git a/src/nix/repl.md b/src/nix/repl.md index 9b6f2bee3..23ef0f4e6 100644 --- a/src/nix/repl.md +++ b/src/nix/repl.md @@ -24,10 +24,34 @@ R""( * Interact with Nixpkgs in the REPL: ```console - # nix repl '' + # nix repl --file example.nix + Loading Installable ''... + Added 3 variables. - Loading ''... - Added 12428 variables. + # nix repl --expr '{a={b=3;c=4;};}' + Loading Installable ''... + Added 1 variables. + + # nix repl --expr '{a={b=3;c=4;};}' a + Loading Installable ''... + Added 1 variables. + + # nix repl --extra_experimental_features 'flakes repl-flake' nixpkgs + Loading Installable 'flake:nixpkgs#'... + Added 5 variables. + + nix-repl> legacyPackages.x86_64-linux.emacs.name + "emacs-27.1" + + nix-repl> legacyPackages.x86_64-linux.emacs.name + "emacs-27.1" + + nix-repl> :q + + # nix repl --expr 'import {}' + + Loading Installable ''... + Added 12439 variables. nix-repl> emacs.name "emacs-27.1" diff --git a/tests/repl.sh b/tests/repl.sh index 9e6a59f18..c555560cc 100644 --- a/tests/repl.sh +++ b/tests/repl.sh @@ -55,15 +55,17 @@ testRepl testRepl --store "$TEST_ROOT/store?real=$NIX_STORE_DIR" testReplResponse () { - local response="$(nix repl <<< "$1")" - echo "$response" | grep -qs "$2" \ + local commands="$1"; shift + local expectedResponse="$1"; shift + local response="$(nix repl "$@" <<< "$commands")" + echo "$response" | grep -qs "$expectedResponse" \ || fail "repl command set: -$1 +$commands does not respond with: -$2 +$expectedResponse but with: @@ -76,3 +78,48 @@ testReplResponse ' :a { a = "2"; } "result: ${a}" ' "result: 2" + +testReplResponse ' +drvPath +' '".*-simple.drv"' \ +$testDir/simple.nix + +testReplResponse ' +drvPath +' '".*-simple.drv"' \ +--file $testDir/simple.nix --experimental-features 'ca-derivations' + +testReplResponse ' +drvPath +' '".*-simple.drv"' \ +--file $testDir/simple.nix --extra-experimental-features 'repl-flake ca-derivations' + +mkdir -p flake && cat < flake/flake.nix +{ + outputs = { self }: { + foo = 1; + bar.baz = 2; + + changingThing = "beforeChange"; + }; +} +EOF +testReplResponse ' +foo + baz +' "3" \ + ./flake ./flake\#bar --experimental-features 'flakes repl-flake' + +# Test the `:reload` mechansim with flakes: +# - Eval `./flake#changingThing` +# - Modify the flake +# - Re-eval it +# - Check that the result has changed +replResult=$( ( +echo "changingThing" +sleep 1 # Leave the repl the time to eval 'foo' +sed -i 's/beforeChange/afterChange/' flake/flake.nix +echo ":reload" +echo "changingThing" +) | nix repl ./flake --experimental-features 'flakes repl-flake') +echo "$replResult" | grep -qs beforeChange +echo "$replResult" | grep -qs afterChange