From 88cd2d41acb994684a3e4ead1b1676019f43b4b6 Mon Sep 17 00:00:00 2001 From: Shea Levy Date: Thu, 8 Feb 2018 11:26:18 -0500 Subject: [PATCH] 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. --- .gitignore | 1 + Makefile | 3 ++- doc/manual/command-ref/conf-file.xml | 27 +++++++++++++++++++ doc/manual/release-notes/rl-2.0.xml | 7 +++++ mk/libraries.mk | 7 +++++ src/build-remote/build-remote.cc | 2 ++ src/libmain/shared.hh | 1 + src/libstore/globals.cc | 15 +++++++++++ src/libstore/globals.hh | 7 +++++ src/libstore/local.mk | 3 +++ src/nix-build/nix-build.cc | 2 ++ src/nix-channel/nix-channel.cc | 3 +++ .../nix-collect-garbage.cc | 2 ++ src/nix-copy-closure/nix-copy-closure.cc | 2 ++ src/nix-daemon/nix-daemon.cc | 2 ++ src/nix-env/nix-env.cc | 2 ++ src/nix-instantiate/nix-instantiate.cc | 2 ++ src/nix-prefetch-url/nix-prefetch-url.cc | 2 ++ src/nix-store/nix-store.cc | 2 ++ src/nix/main.cc | 2 ++ tests/local.mk | 5 ++-- tests/plugins.sh | 7 +++++ tests/plugins/local.mk | 9 +++++++ tests/plugins/plugintest.cc | 10 +++++++ 24 files changed, 122 insertions(+), 3 deletions(-) create mode 100644 tests/plugins.sh create mode 100644 tests/plugins/local.mk create mode 100644 tests/plugins/plugintest.cc diff --git a/.gitignore b/.gitignore index ce22fa00..0a959937 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/Makefile b/Makefile index 5d8e990c..c867823f 100644 --- a/Makefile +++ b/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 diff --git a/doc/manual/command-ref/conf-file.xml b/doc/manual/command-ref/conf-file.xml index fff7994f..cede6db3 100644 --- a/doc/manual/command-ref/conf-file.xml +++ b/doc/manual/command-ref/conf-file.xml @@ -742,6 +742,33 @@ builtins.fetchurl { + + plugin-files + + + 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 + nix command. See the constructors for those + types for more details. + + + 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. + + + + + + diff --git a/doc/manual/release-notes/rl-2.0.xml b/doc/manual/release-notes/rl-2.0.xml index 32cdb1d0..effd2e39 100644 --- a/doc/manual/release-notes/rl-2.0.xml +++ b/doc/manual/release-notes/rl-2.0.xml @@ -389,6 +389,13 @@ configureFlags = "--prefix=${placeholder "out"} --includedir=${placeholder "dev" + + + Nix can now be extended with plugins. See the documentation of + the 'plugin-files' option for more details. + + + Some features were removed: diff --git a/mk/libraries.mk b/mk/libraries.mk index 3cd7a531..14c95fa9 100644 --- a/mk/libraries.mk +++ b/mk/libraries.mk @@ -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 diff --git a/src/build-remote/build-remote.cc b/src/build-remote/build-remote.cc index df579729..50eb6b29 100644 --- a/src/build-remote/build-remote.cc +++ b/src/build-remote/build-remote.cc @@ -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(); /* It would be more appropriate to use $XDG_RUNTIME_DIR, since diff --git a/src/libmain/shared.hh b/src/libmain/shared.hh index 1dcc4f0a..8e486123 100644 --- a/src/libmain/shared.hh +++ b/src/libmain/shared.hh @@ -22,6 +22,7 @@ public: int handleExceptions(const string & programName, std::function fun); +/* Don't forget to call initPlugins() after settings are initialized! */ void initNix(); void parseCmdLine(int argc, char * * argv, diff --git a/src/libstore/globals.cc b/src/libstore/globals.cc index d3c96ddd..21ab0e62 100644 --- a/src/libstore/globals.cc +++ b/src/libstore/globals.cc @@ -6,6 +6,7 @@ #include #include #include +#include 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()); + } +} + + } diff --git a/src/libstore/globals.hh b/src/libstore/globals.hh index 20ac8fe4..508084d0 100644 --- a/src/libstore/globals.hh +++ b/src/libstore/globals.hh @@ -367,12 +367,19 @@ public: Setting allowedUris{this, {}, "allowed-uris", "Prefixes of URIs that builtin functions such as fetchurl and fetchGit are allowed to fetch."}; + + Setting 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; diff --git a/src/libstore/local.mk b/src/libstore/local.mk index 50c46ce6..239356ae 100644 --- a/src/libstore/local.mk +++ b/src/libstore/local.mk @@ -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 diff --git a/src/nix-build/nix-build.cc b/src/nix-build/nix-build.cc index 1581c282..99f77345 100755 --- a/src/nix-build/nix-build.cc +++ b/src/nix-build/nix-build.cc @@ -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"); diff --git a/src/nix-channel/nix-channel.cc b/src/nix-channel/nix-channel.cc index 370f216a..ec9a7174 100755 --- a/src/nix-channel/nix-channel.cc +++ b/src/nix-channel/nix-channel.cc @@ -213,6 +213,9 @@ int main(int argc, char ** argv) } return true; }); + + initPlugins(); + switch (cmd) { case cNone: throw UsageError("no command specified"); diff --git a/src/nix-collect-garbage/nix-collect-garbage.cc b/src/nix-collect-garbage/nix-collect-garbage.cc index cc663a96..37fe22f4 100644 --- a/src/nix-collect-garbage/nix-collect-garbage.cc +++ b/src/nix-collect-garbage/nix-collect-garbage.cc @@ -77,6 +77,8 @@ int main(int argc, char * * argv) return true; }); + initPlugins(); + auto profilesDir = settings.nixStateDir + "/profiles"; if (removeOld) removeOldGenerations(profilesDir); diff --git a/src/nix-copy-closure/nix-copy-closure.cc b/src/nix-copy-closure/nix-copy-closure.cc index 861fc2e5..dfb1b8fc 100755 --- a/src/nix-copy-closure/nix-copy-closure.cc +++ b/src/nix-copy-closure/nix-copy-closure.cc @@ -44,6 +44,8 @@ int main(int argc, char ** argv) return true; }); + initPlugins(); + if (sshHost.empty()) throw UsageError("no host name specified"); diff --git a/src/nix-daemon/nix-daemon.cc b/src/nix-daemon/nix-daemon.cc index d3a8ebbd..890bffa1 100644 --- a/src/nix-daemon/nix-daemon.cc +++ b/src/nix-daemon/nix-daemon.cc @@ -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 */ diff --git a/src/nix-env/nix-env.cc b/src/nix-env/nix-env.cc index 016caf6d..97e66cbd 100644 --- a/src/nix-env/nix-env.cc +++ b/src/nix-env/nix-env.cc @@ -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(); diff --git a/src/nix-instantiate/nix-instantiate.cc b/src/nix-instantiate/nix-instantiate.cc index e05040a4..dd262bea 100644 --- a/src/nix-instantiate/nix-instantiate.cc +++ b/src/nix-instantiate/nix-instantiate.cc @@ -151,6 +151,8 @@ int main(int argc, char * * argv) myArgs.parseCmdline(argvToStrings(argc, argv)); + initPlugins(); + if (evalOnly && !wantsReadWrite) settings.readOnlyMode = true; diff --git a/src/nix-prefetch-url/nix-prefetch-url.cc b/src/nix-prefetch-url/nix-prefetch-url.cc index fef3eaa4..fa7ee254 100644 --- a/src/nix-prefetch-url/nix-prefetch-url.cc +++ b/src/nix-prefetch-url/nix-prefetch-url.cc @@ -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"); diff --git a/src/nix-store/nix-store.cc b/src/nix-store/nix-store.cc index 4fc3421c..efef7f15 100644 --- a/src/nix-store/nix-store.cc +++ b/src/nix-store/nix-store.cc @@ -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 */ diff --git a/src/nix/main.cc b/src/nix/main.cc index 8f6bbe8f..bb107ec7 100644 --- a/src/nix/main.cc +++ b/src/nix/main.cc @@ -92,6 +92,8 @@ void mainWrapped(int argc, char * * argv) args.parseCmdline(argvToStrings(argc, argv)); + initPlugins(); + if (!args.command) args.showHelpAndExit(); Finally f([]() { stopProgressBar(); }); diff --git a/tests/local.mk b/tests/local.mk index e90b9f7d..51bc09dd 100644 --- a/tests/local.mk +++ b/tests/local.mk @@ -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 diff --git a/tests/plugins.sh b/tests/plugins.sh new file mode 100644 index 00000000..6d18d1da --- /dev/null +++ b/tests/plugins.sh @@ -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" ] diff --git a/tests/plugins/local.mk b/tests/plugins/local.mk new file mode 100644 index 00000000..a5f19b08 --- /dev/null +++ b/tests/plugins/local.mk @@ -0,0 +1,9 @@ +libraries += plugintest + +plugintest_DIR := $(d) + +plugintest_SOURCES := $(d)/plugintest.cc + +plugintest_ALLOW_UNDEFINED := 1 + +plugintest_EXCLUDE_FROM_LIBRARY_LIST := 1 diff --git a/tests/plugins/plugintest.cc b/tests/plugins/plugintest.cc new file mode 100644 index 00000000..f788c481 --- /dev/null +++ b/tests/plugins/plugintest.cc @@ -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);