Changeset

12254:5b0c8e499288

modulemanager: Add plugin load filter that reads module metadata from source Metadata in modules is added using lines formatted as: --% key: value Where key is a valid identifier string, and value is also a string (leading and trailing whitespace are trimmed during parsing). The initial supported keys are: --% requires_core_features: feature1, feature2, ... --% conflicts_core_features: feature1, feature2. ... These 'features' map to features reported by the new core.features module. A benefit of this load-time metadata approach compared to e.g. something like module:requires()/module:conflicts() is that we can continue to look in module search paths for a suitable module. Aborting an already-loaded module due to a version conflict would be too late.
author Matthew Wild <mwild1@gmail.com>
date Fri, 04 Feb 2022 14:20:00 +0000
parents 12253:57d35fcde488
children 12255:a3ad9cf740d6
files core/modulemanager.lua
diffstat 1 files changed, 32 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/core/modulemanager.lua	Fri Feb 04 14:11:46 2022 +0000
+++ b/core/modulemanager.lua	Fri Feb 04 14:20:00 2022 +0000
@@ -6,6 +6,7 @@
 -- COPYING file in the source package for more information.
 --
 
+local array = require "util.array";
 local logger = require "util.logger";
 local log = logger.init("modulemanager");
 local config = require "core.configmanager";
@@ -13,6 +14,8 @@
 local envload = require "util.envload";
 local set = require "util.set";
 
+local core_features = require "core.features".available;
+
 local new_multitable = require "util.multitable".new;
 local api = require "core.moduleapi"; -- Module API container
 
@@ -53,6 +56,35 @@
 -- luacheck: std none
 
 local loader = pluginloader.init({
+	load_filter_cb = function (path, content)
+		local metadata = {};
+		for line in content:gmatch("([^\r\n]+)\r?\n") do
+			local key, value = line:match("^%-%-%% *([%w_]+): *(.+)$");
+			if key then
+				value = value:gsub("%s+$", "");
+				metadata[key] = value;
+			end
+		end
+
+		if metadata.conflicts_core_features then
+			local conflicts_core_features = set.new(array.collect(metadata.conflicts_core_features:gmatch("[^, ]+")));
+			local conflicted_features = set.intersection(conflicts_core_features, core_features);
+			if not conflicted_features:empty() then
+				log("warn", "Not loading module, due to conflicting features '%s': %s", conflicted_features, path);
+				return; -- Don't load this module
+			end
+		end
+		if metadata.requires_core_features then
+			local required_features = set.new(array.collect(metadata.requires_core_features:gmatch("[^, ]+")));
+			local missing_features = required_features - core_features;
+			if not missing_features:empty() then
+				log("warn", "Not loading module, due to missing features '%s': %s", missing_features, path);
+				return; -- Don't load this module
+			end
+		end
+
+		return path, content, metadata;
+	end;
 });
 
 local load_modules_for_host, load, unload, reload, get_module, get_items;