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:
parent
f201b7733e
commit
88cd2d41ac
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -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
|
||||
|
|
3
Makefile
3
Makefile
|
@ -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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -213,6 +213,9 @@ int main(int argc, char ** argv)
|
|||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
initPlugins();
|
||||
|
||||
switch (cmd) {
|
||||
case cNone:
|
||||
throw UsageError("no command specified");
|
||||
|
|
|
@ -77,6 +77,8 @@ int main(int argc, char * * argv)
|
|||
return true;
|
||||
});
|
||||
|
||||
initPlugins();
|
||||
|
||||
auto profilesDir = settings.nixStateDir + "/profiles";
|
||||
if (removeOld) removeOldGenerations(profilesDir);
|
||||
|
||||
|
|
|
@ -44,6 +44,8 @@ int main(int argc, char ** argv)
|
|||
return true;
|
||||
});
|
||||
|
||||
initPlugins();
|
||||
|
||||
if (sshHost.empty())
|
||||
throw UsageError("no host name specified");
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -151,6 +151,8 @@ int main(int argc, char * * argv)
|
|||
|
||||
myArgs.parseCmdline(argvToStrings(argc, argv));
|
||||
|
||||
initPlugins();
|
||||
|
||||
if (evalOnly && !wantsReadWrite)
|
||||
settings.readOnlyMode = true;
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -92,6 +92,8 @@ void mainWrapped(int argc, char * * argv)
|
|||
|
||||
args.parseCmdline(argvToStrings(argc, argv));
|
||||
|
||||
initPlugins();
|
||||
|
||||
if (!args.command) args.showHelpAndExit();
|
||||
|
||||
Finally f([]() { stopProgressBar(); });
|
||||
|
|
|
@ -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
7
tests/plugins.sh
Normal 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
9
tests/plugins/local.mk
Normal 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
|
10
tests/plugins/plugintest.cc
Normal file
10
tests/plugins/plugintest.cc
Normal 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);
|
Loading…
Reference in a new issue