Add plugins to make Nix more extensible.

All plugins in plugin-files will be dlopened, allowing them to
statically construct instances of the various Register* types Nix
supports.
This commit is contained in:
Shea Levy 2018-02-08 11:26:18 -05:00
parent f201b7733e
commit 88cd2d41ac
No known key found for this signature in database
GPG Key ID: 5C0BD6957D86FE27
24 changed files with 122 additions and 3 deletions

1
.gitignore vendored
View File

@ -38,6 +38,7 @@ perl/Makefile.config
/scripts/nix-copy-closure
/scripts/nix-reduce-build
/scripts/nix-http-export.cgi
/scripts/nix-profile-daemon.sh
# /src/libexpr/
/src/libexpr/lexer-tab.cc

View File

@ -24,7 +24,8 @@ makefiles = \
misc/launchd/local.mk \
misc/upstart/local.mk \
doc/manual/local.mk \
tests/local.mk
tests/local.mk \
tests/plugins/local.mk
GLOBAL_CXXFLAGS += -std=c++14 -g -Wall -include config.h

View File

@ -742,6 +742,33 @@ builtins.fetchurl {
</varlistentry>
<varlistentry xml:id="conf-plugin-files">
<term><literal>plugin-files</literal></term>
<listitem>
<para>
A list of plugin files to be loaded by Nix. Each of these
files will be dlopened by Nix, allowing them to affect
execution through static initialization. In particular, these
plugins may construct static instances of RegisterPrimOp to
add new primops to the expression language,
RegisterStoreImplementation to add new store implementations,
and RegisterCommand to add new subcommands to the
<literal>nix</literal> command. See the constructors for those
types for more details.
</para>
<para>
Since these files are loaded into the same address space as
Nix itself, they must be DSOs compatible with the instance of
Nix running at the time (i.e. compiled against the same
headers, not linked to any incompatible libraries). They
should not be linked to any Nix libs directly, as those will
be available already at load time.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>

View File

@ -389,6 +389,13 @@ configureFlags = "--prefix=${placeholder "out"} --includedir=${placeholder "dev"
</para>
</listitem>
<listitem>
<para>
Nix can now be extended with plugins. See the documentation of
the 'plugin-files' option for more details.
</para>
</listitem>
</itemizedlist>
<para>Some features were removed:</para>

View File

@ -45,6 +45,11 @@ endif
# - $(1)_INSTALL_DIR: the directory where the library will be
# installed. Defaults to $(libdir).
#
# - $(1)_EXCLUDE_FROM_LIBRARY_LIST: if defined, the library will not
# be automatically marked as a dependency of the top-level all
# target andwill not be listed in the make help output. This is
# useful for libraries built solely for testing, for example.
#
# - BUILD_SHARED_LIBS: if equal to 1, a dynamic library will be
# built, otherwise a static library.
define build-library
@ -149,7 +154,9 @@ define build-library
$(1)_DEPS := $$(foreach fn, $$($(1)_OBJS), $$(call filename-to-dep, $$(fn)))
-include $$($(1)_DEPS)
ifndef $(1)_EXCLUDE_FROM_LIBRARY_LIST
libs-list += $$($(1)_PATH)
endif
clean-files += $$(_d)/*.a $$(_d)/*.$(SO_EXT) $$(_d)/*.o $$(_d)/.*.dep $$($(1)_DEPS) $$($(1)_OBJS)
dist-files += $$(_srcs)
endef

View File

@ -64,6 +64,8 @@ int main (int argc, char * * argv)
settings.maxBuildJobs.set("1"); // hack to make tests with local?root= work
initPlugins();
auto store = openStore().cast<LocalStore>();
/* It would be more appropriate to use $XDG_RUNTIME_DIR, since

View File

@ -22,6 +22,7 @@ public:
int handleExceptions(const string & programName, std::function<void()> fun);
/* Don't forget to call initPlugins() after settings are initialized! */
void initNix();
void parseCmdLine(int argc, char * * argv,

View File

@ -6,6 +6,7 @@
#include <algorithm>
#include <map>
#include <thread>
#include <dlfcn.h>
namespace nix {
@ -137,4 +138,18 @@ void MaxBuildJobsSetting::set(const std::string & str)
throw UsageError("configuration setting '%s' should be 'auto' or an integer", name);
}
void initPlugins()
{
for (const auto & pluginFile : settings.pluginFiles.get()) {
/* handle is purposefully leaked as there may be state in the
DSO needed by the action of the plugin. */
void *handle =
dlopen(pluginFile.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle)
throw Error(format("could not dynamically open plugin file '%1%': %2%") % pluginFile % dlerror());
}
}
}

View File

@ -367,12 +367,19 @@ public:
Setting<Strings> allowedUris{this, {}, "allowed-uris",
"Prefixes of URIs that builtin functions such as fetchurl and fetchGit are allowed to fetch."};
Setting<Paths> pluginFiles{this, {}, "plugin-files",
"Plugins to dynamically load at nix initialization time."};
};
// FIXME: don't use a global variable.
extern Settings settings;
/* This should be called after settings are initialized, but before
anything else */
void initPlugins();
extern const string nixVersion;

View File

@ -9,6 +9,9 @@ libstore_SOURCES := $(wildcard $(d)/*.cc)
libstore_LIBS = libutil libformat
libstore_LDFLAGS = $(SQLITE3_LIBS) -lbz2 $(LIBCURL_LIBS) $(SODIUM_LIBS) -pthread
ifneq ($(OS), FreeBSD)
libstore_LDFLAGS += -ldl
endif
libstore_FILES = sandbox-defaults.sb sandbox-minimal.sb sandbox-network.sb

View File

@ -232,6 +232,8 @@ void mainWrapped(int argc, char * * argv)
myArgs.parseCmdline(args);
initPlugins();
if (packages && fromArgs)
throw UsageError("'-p' and '-E' are mutually exclusive");

View File

@ -213,6 +213,9 @@ int main(int argc, char ** argv)
}
return true;
});
initPlugins();
switch (cmd) {
case cNone:
throw UsageError("no command specified");

View File

@ -77,6 +77,8 @@ int main(int argc, char * * argv)
return true;
});
initPlugins();
auto profilesDir = settings.nixStateDir + "/profiles";
if (removeOld) removeOldGenerations(profilesDir);

View File

@ -44,6 +44,8 @@ int main(int argc, char ** argv)
return true;
});
initPlugins();
if (sshHost.empty())
throw UsageError("no host name specified");

View File

@ -1060,6 +1060,8 @@ int main(int argc, char * * argv)
return true;
});
initPlugins();
if (stdio) {
if (getStoreType() == tDaemon) {
/* Forward on this connection to the real daemon */

View File

@ -1393,6 +1393,8 @@ int main(int argc, char * * argv)
myArgs.parseCmdline(argvToStrings(argc, argv));
initPlugins();
if (!op) throw UsageError("no operation specified");
auto store = openStore();

View File

@ -151,6 +151,8 @@ int main(int argc, char * * argv)
myArgs.parseCmdline(argvToStrings(argc, argv));
initPlugins();
if (evalOnly && !wantsReadWrite)
settings.readOnlyMode = true;

View File

@ -89,6 +89,8 @@ int main(int argc, char * * argv)
myArgs.parseCmdline(argvToStrings(argc, argv));
initPlugins();
if (args.size() > 2)
throw UsageError("too many arguments");

View File

@ -1052,6 +1052,8 @@ int main(int argc, char * * argv)
return true;
});
initPlugins();
if (!op) throw UsageError("no operation specified");
if (op != opDump && op != opRestore) /* !!! hack */

View File

@ -92,6 +92,8 @@ void mainWrapped(int argc, char * * argv)
args.parseCmdline(argvToStrings(argc, argv));
initPlugins();
if (!args.command) args.showHelpAndExit();
Finally f([]() { stopProgressBar(); });

View File

@ -22,7 +22,8 @@ nix_tests = \
run.sh \
brotli.sh \
pure-eval.sh \
check.sh
check.sh \
plugins.sh
# parallel.sh
install-tests += $(foreach x, $(nix_tests), tests/$(x))
@ -31,4 +32,4 @@ tests-environment = NIX_REMOTE= $(bash) -e
clean-files += $(d)/common.sh
installcheck: $(d)/common.sh
installcheck: $(d)/common.sh $(d)/plugins/plugintest.so

7
tests/plugins.sh Normal file
View File

@ -0,0 +1,7 @@
source common.sh
set -o pipefail
res=$(nix eval '(builtins.constNull true)' --option plugin-files $PWD/plugins/plugintest.so)
[ "$res"x = "nullx" ]

9
tests/plugins/local.mk Normal file
View File

@ -0,0 +1,9 @@
libraries += plugintest
plugintest_DIR := $(d)
plugintest_SOURCES := $(d)/plugintest.cc
plugintest_ALLOW_UNDEFINED := 1
plugintest_EXCLUDE_FROM_LIBRARY_LIST := 1

View File

@ -0,0 +1,10 @@
#include "primops.hh"
using namespace nix;
static void prim_constNull (EvalState & state, const Pos & pos, Value ** args, Value & v)
{
mkNull(v);
}
static RegisterPrimOp r("constNull", 1, prim_constNull);