Skip to content

Commit

Permalink
Add plugins to make Nix more extensible.
Browse files Browse the repository at this point in the history
All plugins in plugin-files will be dlopened, allowing them to
statically construct instances of the various Register* types Nix
supports.
  • Loading branch information
shlevy committed Feb 8, 2018
1 parent f201b77 commit 88cd2d4
Show file tree
Hide file tree
Showing 24 changed files with 122 additions and 3 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 27 additions & 0 deletions doc/manual/command-ref/conf-file.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
7 changes: 7 additions & 0 deletions doc/manual/release-notes/rl-2.0.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down
7 changes: 7 additions & 0 deletions mk/libraries.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions src/build-remote/build-remote.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/libmain/shared.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
15 changes: 15 additions & 0 deletions src/libstore/globals.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <algorithm>
#include <map>
#include <thread>
#include <dlfcn.h>


namespace nix {
Expand Down Expand Up @@ -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());
}
}


}
7 changes: 7 additions & 0 deletions src/libstore/globals.hh
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions src/libstore/local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions src/nix-build/nix-build.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
3 changes: 3 additions & 0 deletions src/nix-channel/nix-channel.cc
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,9 @@ int main(int argc, char ** argv)
}
return true;
});

initPlugins();

switch (cmd) {
case cNone:
throw UsageError("no command specified");
Expand Down
2 changes: 2 additions & 0 deletions src/nix-collect-garbage/nix-collect-garbage.cc
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ int main(int argc, char * * argv)
return true;
});

initPlugins();

auto profilesDir = settings.nixStateDir + "/profiles";
if (removeOld) removeOldGenerations(profilesDir);

Expand Down
2 changes: 2 additions & 0 deletions src/nix-copy-closure/nix-copy-closure.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ int main(int argc, char ** argv)
return true;
});

initPlugins();

if (sshHost.empty())
throw UsageError("no host name specified");

Expand Down
2 changes: 2 additions & 0 deletions src/nix-daemon/nix-daemon.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
2 changes: 2 additions & 0 deletions src/nix-env/nix-env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 2 additions & 0 deletions src/nix-instantiate/nix-instantiate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ int main(int argc, char * * argv)

myArgs.parseCmdline(argvToStrings(argc, argv));

initPlugins();

if (evalOnly && !wantsReadWrite)
settings.readOnlyMode = true;

Expand Down
2 changes: 2 additions & 0 deletions src/nix-prefetch-url/nix-prefetch-url.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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");

Expand Down
2 changes: 2 additions & 0 deletions src/nix-store/nix-store.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
2 changes: 2 additions & 0 deletions src/nix/main.cc
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ void mainWrapped(int argc, char * * argv)

args.parseCmdline(argvToStrings(argc, argv));

initPlugins();

if (!args.command) args.showHelpAndExit();

Finally f([]() { stopProgressBar(); });
Expand Down
5 changes: 3 additions & 2 deletions tests/local.mk
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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 changes: 7 additions & 0 deletions tests/plugins.sh
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions tests/plugins/local.mk
Original file line number Diff line number Diff line change
@@ -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 changes: 10 additions & 0 deletions tests/plugins/plugintest.cc
Original file line number Diff line number Diff line change
@@ -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);

0 comments on commit 88cd2d4

Please sign in to comment.