Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add repl-overlays setting #10203

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions doc/manual/rl-next/repl-overlays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
---
synopsis: Add `repl-overlays` option
prs: 10203
---

A `repl-overlays` option has been added, which specifies files that can overlay
and modify the top-level bindings in `nix repl`. For example, with the
following contents in `~/.config/nix/repl.nix`:

```nix
info: final: prev: let
optionalAttrs = predicate: attrs:
if predicate
then attrs
else {};
in
optionalAttrs (prev ? legacyPackages && prev.legacyPackages ? ${info.currentSystem})
{
pkgs = prev.legacyPackages.${info.currentSystem};
}
```

We can run `nix repl` and use `pkgs` to refer to `legacyPackages.${currentSystem}`:

```ShellSession
$ nix repl --repl-overlays ~/.config/nix/repl.nix nixpkgs
Nix 2.21.0pre20240309_4111bb6
Type :? for help.
Loading installable 'flake:nixpkgs#'...
Added 5 variables.
Loading 'repl-overlays'...
Added 6 variables.
nix-repl> pkgs.bash
«derivation /nix/store/g08b5vkwwh0j8ic9rkmd8mpj878rk62z-bash-5.2p26.drv»
```
2 changes: 2 additions & 0 deletions src/libcmd/local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ libcmd_LDFLAGS = $(EDITLINE_LIBS) $(LOWDOWN_LIBS) $(THREAD_LDFLAGS)
libcmd_LIBS = libstore libutil libexpr libmain libfetchers

$(eval $(call install-file-in, $(buildprefix)$(d)/nix-cmd.pc, $(libdir)/pkgconfig, 0644))

$(d)/repl.cc: $(d)/repl-overlays.nix.gen.hh
8 changes: 8 additions & 0 deletions src/libcmd/repl-overlays.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
info:
initial:
functions:
let final = builtins.foldl'
(prev: function: prev // (function info final prev))
initial
functions;
in final
129 changes: 126 additions & 3 deletions src/libcmd/repl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ extern "C" {
#include "finally.hh"
#include "markdown.hh"
#include "local-fs-store.hh"
#include "print.hh"
#include "gc-small-vector.hh"
#include "fs-input-accessor.hh"
#include "memory-input-accessor.hh"

#if HAVE_BOEHMGC
#define GC_INCLUDE_NEW
Expand Down Expand Up @@ -114,6 +116,37 @@ struct NixRepl
void evalString(std::string s, Value & v);
void loadDebugTraceEnv(DebugTrace & dt);

/**
* Load the `repl-overlays` and add the resulting AttrSet to the top-level
* bindings.
*/
void loadReplOverlays();

/**
* Get a list of each of the `repl-overlays` (parsed and evaluated).
*/
Value * replOverlays();

/**
* Get the Nix function that composes the `repl-overlays` together.
*/
Value * replOverlaysEvalFunction();

/**
* Get the `info` AttrSet that's passed as the first argument to each
* of the `repl-overlays`.
*/
Value * replInitInfo();

/**
* Get the current top-level bindings as an AttrSet.
*/
Value * bindingsToAttrs();
/**
* Parse a file, evaluate its result, and force the resulting value.
*/
Value * evalFile(SourcePath & path);

void printValue(std::ostream & str,
Value & v,
unsigned int maxDepth = std::numeric_limits<unsigned int>::max())
Expand Down Expand Up @@ -879,14 +912,83 @@ void NixRepl::loadFiles()
loadedFiles.clear();

for (auto & i : old) {
notice("Loading '%1%'...", i);
notice("Loading '%1%'...", Magenta(i));
loadFile(i);
}

for (auto & [i, what] : getValues()) {
notice("Loading installable '%1%'...", what);
notice("Loading installable '%1%'...", Magenta(what));
addAttrsToScope(*i);
}

loadReplOverlays();
}

void NixRepl::loadReplOverlays()
{
if (!evalSettings.replOverlays) {
return;
}

notice("Loading '%1%'...", Magenta("repl-overlays"));
auto replInitFilesFunction = replOverlaysEvalFunction();

Value &newAttrs(*state->allocValue());
SmallValueVector<3> args = {replInitInfo(), bindingsToAttrs(), replOverlays()};
state->callFunction(
*replInitFilesFunction,
args.size(),
args.data(),
newAttrs,
replInitFilesFunction->determinePos(noPos)
);

addAttrsToScope(newAttrs);
}

Value * NixRepl::replOverlaysEvalFunction()
{
auto evalReplInitFilesPath = CanonPath("repl-overlays.nix");
if (!state->corepkgsFS->pathExists(evalReplInitFilesPath)) {
state->corepkgsFS->addFile(
evalReplInitFilesPath,
#include "repl-overlays.nix.gen.hh"
);
}

SourcePath evalReplInitFilesSourcePath(state->corepkgsFS, evalReplInitFilesPath);
return evalFile(evalReplInitFilesSourcePath);
}

Value * NixRepl::replOverlays()
{
Value * replInits(state->allocValue());
state->mkList(*replInits, evalSettings.replOverlays.get().size());
Value ** replInitElems = replInits->listElems();

size_t i = 0;
for (auto path : evalSettings.replOverlays.get()) {
debug("Loading '%1%'...", path);
SourcePath sourcePath(makeFSInputAccessor(), CanonPath(path));
replInitElems[i] = evalFile(sourcePath);
i++;
}


return replInits;
}

Value * NixRepl::replInitInfo()
{
auto builder = state->buildBindings(1);

Value * currentSystem(state->allocValue());
currentSystem->mkString(evalSettings.getCurrentSystem());
builder.insert(state->symbols.create("currentSystem"), currentSystem);

Value * info(state->allocValue());
info->mkAttrs(builder.finish());
return info;
}


Expand Down Expand Up @@ -919,6 +1021,18 @@ void NixRepl::addVarToScope(const Symbol name, Value & v)
varNames.emplace(state->symbols[name]);
}

Value * NixRepl::bindingsToAttrs()
{
auto builder = state->buildBindings(staticEnv->vars.size());
for (auto & [symbol, displacement] : staticEnv->vars) {
builder.insert(symbol, env->values[displacement]);
}

Value * attrs(state->allocValue());
attrs->mkAttrs(builder.finish());
return attrs;
}


Expr * NixRepl::parseString(std::string s)
{
Expand All @@ -933,6 +1047,15 @@ void NixRepl::evalString(std::string s, Value & v)
state->forceValue(v, v.determinePos(noPos));
}

Value * NixRepl::evalFile(SourcePath & path)
{
auto expr = state->parseExprFromFile(path, staticEnv);
Value * result(state->allocValue());
expr->eval(*state, *env, *result);
state->forceValue(*result, result->determinePos(noPos));
return result;
}


std::unique_ptr<AbstractNixRepl> AbstractNixRepl::create(
const SearchPath & searchPath, nix::ref<Store> store, ref<EvalState> state,
Expand Down
36 changes: 36 additions & 0 deletions src/libexpr/eval-settings.hh
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,42 @@ struct EvalSettings : Config

This is useful for debugging warnings in third-party Nix code.
)"};

PathsSetting replOverlays{this, Paths(), "repl-overlays",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the interest of convention over configuration, it would be good if this had a default value like ~/.nix/config/repl.nix.

R"(
A list of files containing Nix expressions that can be used to add
default bindings to [`nix
repl`](@docroot@/command-ref/new-cli/nix3-repl.md) sessions.

Each file is called with three arguments:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The help text documents what the arguments are, but not what the function is supposed to return.

1. An [attribute set](@docroot@/language/values.html#attribute-set)
containing a
[`currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem)
attribute (this is identical to
[`builtins.currentSystem`](@docroot@/language/builtin-constants.md#builtins-currentSystem),
except that it's available in
[`pure-eval`](@docroot@/command-ref/conf-file.html#conf-pure-eval)
mode).
2. The top-level bindings produced by the previous `repl-overlays`
value (or the default top-level bindings).
3. The final top-level bindings produced by calling all
`repl-overlays`.

For example, the following file would alias `pkgs` to
`legacyPackages.${info.currentSystem}` (if that attribute is defined):

```nix
info: prev: final:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having three positional arguments, of which one is an attrset with fields like currentSystem, seems unnecessarily complicated. I would prefer having a single attrset argument, e.g.

{ prev, final, currentSystem, ... }: ...

This would make it easier to add more fields in the future.

if prev ? legacyPackages
&& prev.legacyPackages ? ${info.currentSystem}
then
{
pkgs = prev.legacyPackages.${info.currentSystem};
}
else
{ }
```
)"};
};

extern EvalSettings evalSettings;
Expand Down
3 changes: 2 additions & 1 deletion src/libexpr/gc-small-vector.hh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#if HAVE_BOEHMGC

#define GC_INCLUDE_NEW
#include <gc/gc.h>
#include <gc/gc_cpp.h>
#include <gc/gc_allocator.h>
Expand Down Expand Up @@ -39,4 +40,4 @@ using SmallValueVector = SmallVector<Value *, nItems>;
template <size_t nItems>
using SmallTemporaryValueVector = SmallVector<Value, nItems>;

}
}
33 changes: 33 additions & 0 deletions src/libutil/config.cc
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,39 @@ void OptionalPathSetting::operator =(const std::optional<Path> & v)
this->assign(v);
}

PathsSetting::PathsSetting(Config * options,
const Paths & def,
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases)
: BaseSetting<Paths>(def, true, name, description, aliases)
{
options->addSetting(this);
}


Paths PathsSetting::parse(const std::string & str) const
{
auto strings = tokenizeString<Strings>(str);
Paths parsed;

for (auto str : strings) {
parsed.push_back(canonPath(str));
}

return parsed;
}

void PathsSetting::operator =(const Paths & v)
{
this->assign(v);
}

PathsSetting::operator bool() const noexcept
{
return !get().empty();
}

bool GlobalConfig::set(const std::string & name, const std::string & value)
{
for (auto & config : *configRegistrations)
Expand Down
20 changes: 20 additions & 0 deletions src/libutil/config.hh
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,26 @@ public:
void operator =(const std::optional<Path> & v);
};

/**
* Like `OptionalPathSetting`, but allows multiple paths.
*/
class PathsSetting : public BaseSetting<Paths>
{
public:

PathsSetting(Config * options,
const Paths & def,
const std::string & name,
const std::string & description,
const std::set<std::string> & aliases = {});

Paths parse(const std::string & str) const override;

void operator =(const Paths & v);

operator bool() const noexcept;
};

struct GlobalConfig : public AbstractConfig
{
typedef std::vector<Config*> ConfigRegistrations;
Expand Down
Loading