aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/manual/src/release-notes/rl-next.md7
-rw-r--r--src/libcmd/command.hh3
-rw-r--r--src/libcmd/installables.cc7
-rw-r--r--src/libcmd/installables.hh2
-rw-r--r--src/libcmd/repl.cc108
-rw-r--r--src/libutil/experimental-features.cc1
-rw-r--r--src/libutil/experimental-features.hh1
-rw-r--r--src/nix/repl.md30
-rw-r--r--tests/repl.sh55
9 files changed, 177 insertions, 37 deletions
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 '<nixpkgs>'` which can be recovered with
+ `nix repl --file '<nixpkgs>'` or `nix repl --expr 'import <nixpkgs>{}'`
+
+ 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<FlakeRef> getFlakeRefForCompletion() override;
-private:
+protected:
std::vector<std::string> _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<FlakeRef> 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<std::shared_ptr<Installable>> & installables);
};
+typedef std::vector<std::shared_ptr<Installable>> Installables;
+
struct InstallableValue : Installable
{
ref<EvalState> 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<std::pair<Value*,std::string>> AnnotatedValues;
+ std::function<AnnotatedValues()> getValues;
const static int envSize = 32768;
std::shared_ptr<StaticEnv> staticEnv;
@@ -63,13 +66,15 @@ struct NixRepl
const Path historyFile;
- NixRepl(ref<EvalState> state);
+ NixRepl(const Strings & searchPath, nix::ref<Store> store,ref<EvalState> state,
+ std::function<AnnotatedValues()> getValues);
~NixRepl();
- void mainLoop(const std::vector<std::string> & 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<EvalState> state)
+NixRepl::NixRepl(const Strings & searchPath, nix::ref<Store> store, ref<EvalState> state,
+ std::function<NixRepl::AnnotatedValues()> 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<std::string> & 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(
ref<EvalState>evalState,
const ValMap & extraEnv)
{
- auto repl = std::make_unique<NixRepl>(evalState);
+ auto getValues = [&]()->NixRepl::AnnotatedValues{
+ NixRepl::AnnotatedValues values;
+ return values;
+ };
+ const Strings & searchPath = {};
+ auto repl = std::make_unique<NixRepl>(
+ 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
{
+ CmdRepl(){
+ evalSettings.pureEval = false;
+ }
+ void prepare()
+ {
+ 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<std::string> files;
-
- CmdRepl()
+ Strings getDefaultFlakeAttrPaths() override
{
- expectArgs({
- .label = "files",
- .handler = {&files},
- .completer = completePath
- });
+ 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> store) override
{
- auto evalState = make_ref<EvalState>(searchPath, store);
-
- auto repl = std::make_unique<NixRepl>(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<NixRepl>(
+ 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<ExperimentalFeature, std::string> stringifiedXpFeatures = {
{ Xp::RecursiveNix, "recursive-nix" },
{ Xp::NoUrlLiterals, "no-url-literals" },
{ Xp::FetchClosure, "fetch-closure" },
+ { Xp::ReplFlake, "repl-flake" },
};
const std::optional<ExperimentalFeature> 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 '<nixpkgs>'
+ # nix repl --file example.nix
+ Loading Installable ''...
+ Added 3 variables.
- Loading '<nixpkgs>'...
- 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 <nixpkgs>{}'
+
+ 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 <<EOF > 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