commit
4c8210095e
9 changed files with 178 additions and 38 deletions
|
@ -2,3 +2,10 @@
|
||||||
|
|
||||||
* Nix can now be built with LTO by passing `--enable-lto` to `configure`.
|
* Nix can now be built with LTO by passing `--enable-lto` to `configure`.
|
||||||
LTO is currently only supported when building with GCC.
|
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
|
||||||
|
|
|
@ -116,12 +116,13 @@ struct InstallablesCommand : virtual Args, SourceExprCommand
|
||||||
InstallablesCommand();
|
InstallablesCommand();
|
||||||
|
|
||||||
void prepare() override;
|
void prepare() override;
|
||||||
|
Installables load();
|
||||||
|
|
||||||
virtual bool useDefaultInstallables() { return true; }
|
virtual bool useDefaultInstallables() { return true; }
|
||||||
|
|
||||||
std::optional<FlakeRef> getFlakeRefForCompletion() override;
|
std::optional<FlakeRef> getFlakeRefForCompletion() override;
|
||||||
|
|
||||||
private:
|
protected:
|
||||||
|
|
||||||
std::vector<std::string> _installables;
|
std::vector<std::string> _installables;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1036,11 +1036,16 @@ InstallablesCommand::InstallablesCommand()
|
||||||
|
|
||||||
void InstallablesCommand::prepare()
|
void InstallablesCommand::prepare()
|
||||||
{
|
{
|
||||||
|
installables = load();
|
||||||
|
}
|
||||||
|
|
||||||
|
Installables InstallablesCommand::load() {
|
||||||
|
Installables installables;
|
||||||
if (_installables.empty() && useDefaultInstallables())
|
if (_installables.empty() && useDefaultInstallables())
|
||||||
// FIXME: commands like "nix profile install" should not have a
|
// FIXME: commands like "nix profile install" should not have a
|
||||||
// default, probably.
|
// default, probably.
|
||||||
_installables.push_back(".");
|
_installables.push_back(".");
|
||||||
installables = parseInstallables(getStore(), _installables);
|
return parseInstallables(getStore(), _installables);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
|
std::optional<FlakeRef> InstallablesCommand::getFlakeRefForCompletion()
|
||||||
|
|
|
@ -132,6 +132,8 @@ struct Installable
|
||||||
const std::vector<std::shared_ptr<Installable>> & installables);
|
const std::vector<std::shared_ptr<Installable>> & installables);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef std::vector<std::shared_ptr<Installable>> Installables;
|
||||||
|
|
||||||
struct InstallableValue : Installable
|
struct InstallableValue : Installable
|
||||||
{
|
{
|
||||||
ref<EvalState> state;
|
ref<EvalState> state;
|
||||||
|
|
|
@ -22,6 +22,7 @@ extern "C" {
|
||||||
#include "ansicolor.hh"
|
#include "ansicolor.hh"
|
||||||
#include "shared.hh"
|
#include "shared.hh"
|
||||||
#include "eval.hh"
|
#include "eval.hh"
|
||||||
|
#include "eval-cache.hh"
|
||||||
#include "eval-inline.hh"
|
#include "eval-inline.hh"
|
||||||
#include "attr-path.hh"
|
#include "attr-path.hh"
|
||||||
#include "store-api.hh"
|
#include "store-api.hh"
|
||||||
|
@ -54,6 +55,8 @@ struct NixRepl
|
||||||
size_t debugTraceIndex;
|
size_t debugTraceIndex;
|
||||||
|
|
||||||
Strings loadedFiles;
|
Strings loadedFiles;
|
||||||
|
typedef std::vector<std::pair<Value*,std::string>> AnnotatedValues;
|
||||||
|
std::function<AnnotatedValues()> getValues;
|
||||||
|
|
||||||
const static int envSize = 32768;
|
const static int envSize = 32768;
|
||||||
std::shared_ptr<StaticEnv> staticEnv;
|
std::shared_ptr<StaticEnv> staticEnv;
|
||||||
|
@ -63,13 +66,15 @@ struct NixRepl
|
||||||
|
|
||||||
const Path historyFile;
|
const Path historyFile;
|
||||||
|
|
||||||
NixRepl(ref<EvalState> state);
|
NixRepl(const Strings & searchPath, nix::ref<Store> store,ref<EvalState> state,
|
||||||
|
std::function<AnnotatedValues()> getValues);
|
||||||
~NixRepl();
|
~NixRepl();
|
||||||
void mainLoop(const std::vector<std::string> & files);
|
void mainLoop();
|
||||||
StringSet completePrefix(const std::string & prefix);
|
StringSet completePrefix(const std::string & prefix);
|
||||||
bool getLine(std::string & input, const std::string & prompt);
|
bool getLine(std::string & input, const std::string & prompt);
|
||||||
StorePath getDerivationPath(Value & v);
|
StorePath getDerivationPath(Value & v);
|
||||||
bool processLine(std::string line);
|
bool processLine(std::string line);
|
||||||
|
|
||||||
void loadFile(const Path & path);
|
void loadFile(const Path & path);
|
||||||
void loadFlake(const std::string & flakeRef);
|
void loadFlake(const std::string & flakeRef);
|
||||||
void initEnv();
|
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)
|
: state(state)
|
||||||
, debugTraceIndex(0)
|
, debugTraceIndex(0)
|
||||||
|
, getValues(getValues)
|
||||||
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
|
, staticEnv(new StaticEnv(false, state->staticBaseEnv.get()))
|
||||||
, historyFile(getDataDir() + "/nix/repl-history")
|
, historyFile(getDataDir() + "/nix/repl-history")
|
||||||
{
|
{
|
||||||
|
@ -225,18 +232,12 @@ static std::ostream & showDebugTrace(std::ostream & out, const PosTable & positi
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NixRepl::mainLoop(const std::vector<std::string> & files)
|
void NixRepl::mainLoop()
|
||||||
{
|
{
|
||||||
std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
std::string error = ANSI_RED "error:" ANSI_NORMAL " ";
|
||||||
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
|
notice("Welcome to Nix " + nixVersion + ". Type :? for help.\n");
|
||||||
|
|
||||||
if (!files.empty()) {
|
|
||||||
for (auto & i : files)
|
|
||||||
loadedFiles.push_back(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
loadFiles();
|
loadFiles();
|
||||||
if (!loadedFiles.empty()) notice("");
|
|
||||||
|
|
||||||
// Allow nix-repl specific settings in .inputrc
|
// Allow nix-repl specific settings in .inputrc
|
||||||
rl_readline_name = "nix-repl";
|
rl_readline_name = "nix-repl";
|
||||||
|
@ -746,7 +747,6 @@ bool NixRepl::processLine(std::string line)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void NixRepl::loadFile(const Path & path)
|
void NixRepl::loadFile(const Path & path)
|
||||||
{
|
{
|
||||||
loadedFiles.remove(path);
|
loadedFiles.remove(path);
|
||||||
|
@ -806,13 +806,15 @@ void NixRepl::loadFiles()
|
||||||
Strings old = loadedFiles;
|
Strings old = loadedFiles;
|
||||||
loadedFiles.clear();
|
loadedFiles.clear();
|
||||||
|
|
||||||
bool first = true;
|
|
||||||
for (auto & i : old) {
|
for (auto & i : old) {
|
||||||
if (!first) notice("");
|
|
||||||
first = false;
|
|
||||||
notice("Loading '%1%'...", i);
|
notice("Loading '%1%'...", i);
|
||||||
loadFile(i);
|
loadFile(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto & [i, what] : getValues()) {
|
||||||
|
notice("Loading installable '%1%'...", what);
|
||||||
|
addAttrsToScope(*i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1012,7 +1014,17 @@ void runRepl(
|
||||||
ref<EvalState>evalState,
|
ref<EvalState>evalState,
|
||||||
const ValMap & extraEnv)
|
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();
|
repl->initEnv();
|
||||||
|
|
||||||
|
@ -1020,20 +1032,35 @@ void runRepl(
|
||||||
for (auto & [name, value] : extraEnv)
|
for (auto & [name, value] : extraEnv)
|
||||||
repl->addVarToScope(repl->state->symbols.create(name), *value);
|
repl->addVarToScope(repl->state->symbols.create(name), *value);
|
||||||
|
|
||||||
repl->mainLoop({});
|
repl->mainLoop();
|
||||||
}
|
}
|
||||||
|
|
||||||
struct CmdRepl : StoreCommand, MixEvalArgs
|
struct CmdRepl : InstallablesCommand
|
||||||
{
|
{
|
||||||
std::vector<std::string> files;
|
CmdRepl(){
|
||||||
|
evalSettings.pureEval = false;
|
||||||
CmdRepl()
|
}
|
||||||
|
void prepare()
|
||||||
{
|
{
|
||||||
expectArgs({
|
if (!settings.isExperimentalFeatureEnabled(Xp::ReplFlake) && !(file) && this->_installables.size() >= 1) {
|
||||||
.label = "files",
|
warn("future versions of Nix will require using `--file` to load a file");
|
||||||
.handler = {&files},
|
if (this->_installables.size() > 1)
|
||||||
.completer = completePath
|
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;
|
||||||
|
Strings getDefaultFlakeAttrPaths() override
|
||||||
|
{
|
||||||
|
return {""};
|
||||||
|
}
|
||||||
|
virtual bool useDefaultInstallables() override
|
||||||
|
{
|
||||||
|
return file.has_value() or expr.has_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool forceImpureByDefault() override
|
bool forceImpureByDefault() override
|
||||||
|
@ -1055,12 +1082,37 @@ struct CmdRepl : StoreCommand, MixEvalArgs
|
||||||
|
|
||||||
void run(ref<Store> store) override
|
void run(ref<Store> store) override
|
||||||
{
|
{
|
||||||
auto evalState = make_ref<EvalState>(searchPath, store);
|
auto state = getEvalState();
|
||||||
|
auto getValues = [&]()->NixRepl::AnnotatedValues{
|
||||||
auto repl = std::make_unique<NixRepl>(evalState);
|
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->autoArgs = getAutoArgs(*repl->state);
|
||||||
repl->initEnv();
|
repl->initEnv();
|
||||||
repl->mainLoop(files);
|
repl->mainLoop();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ std::map<ExperimentalFeature, std::string> stringifiedXpFeatures = {
|
||||||
{ Xp::RecursiveNix, "recursive-nix" },
|
{ Xp::RecursiveNix, "recursive-nix" },
|
||||||
{ Xp::NoUrlLiterals, "no-url-literals" },
|
{ Xp::NoUrlLiterals, "no-url-literals" },
|
||||||
{ Xp::FetchClosure, "fetch-closure" },
|
{ Xp::FetchClosure, "fetch-closure" },
|
||||||
|
{ Xp::ReplFlake, "repl-flake" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
|
const std::optional<ExperimentalFeature> parseExperimentalFeature(const std::string_view & name)
|
||||||
|
|
|
@ -22,6 +22,7 @@ enum struct ExperimentalFeature
|
||||||
RecursiveNix,
|
RecursiveNix,
|
||||||
NoUrlLiterals,
|
NoUrlLiterals,
|
||||||
FetchClosure,
|
FetchClosure,
|
||||||
|
ReplFlake,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -24,10 +24,34 @@ R""(
|
||||||
* Interact with Nixpkgs in the REPL:
|
* Interact with Nixpkgs in the REPL:
|
||||||
|
|
||||||
```console
|
```console
|
||||||
# nix repl '<nixpkgs>'
|
# nix repl --file example.nix
|
||||||
|
Loading Installable ''...
|
||||||
|
Added 3 variables.
|
||||||
|
|
||||||
Loading '<nixpkgs>'...
|
# nix repl --expr '{a={b=3;c=4;};}'
|
||||||
Added 12428 variables.
|
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
|
nix-repl> emacs.name
|
||||||
"emacs-27.1"
|
"emacs-27.1"
|
||||||
|
|
|
@ -55,15 +55,17 @@ testRepl
|
||||||
testRepl --store "$TEST_ROOT/store?real=$NIX_STORE_DIR"
|
testRepl --store "$TEST_ROOT/store?real=$NIX_STORE_DIR"
|
||||||
|
|
||||||
testReplResponse () {
|
testReplResponse () {
|
||||||
local response="$(nix repl <<< "$1")"
|
local commands="$1"; shift
|
||||||
echo "$response" | grep -qs "$2" \
|
local expectedResponse="$1"; shift
|
||||||
|
local response="$(nix repl "$@" <<< "$commands")"
|
||||||
|
echo "$response" | grep -qs "$expectedResponse" \
|
||||||
|| fail "repl command set:
|
|| fail "repl command set:
|
||||||
|
|
||||||
$1
|
$commands
|
||||||
|
|
||||||
does not respond with:
|
does not respond with:
|
||||||
|
|
||||||
$2
|
$expectedResponse
|
||||||
|
|
||||||
but with:
|
but with:
|
||||||
|
|
||||||
|
@ -76,3 +78,48 @@ testReplResponse '
|
||||||
:a { a = "2"; }
|
:a { a = "2"; }
|
||||||
"result: ${a}"
|
"result: ${a}"
|
||||||
' "result: 2"
|
' "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
|
||||||
|
|
Loading…
Reference in a new issue