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
					
				
					 24 changed files with 122 additions and 3 deletions
				
			
		
							
								
								
									
										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…
	
	Add table
		Add a link
		
	
		Reference in a new issue