# HG changeset patch # User Matthew Wild # Date 1643984400 0 # Node ID 5b0c8e499288a02a58d52b014cebe9da04c7f9e1 # Parent 57d35fcde4885962c0d08e45615bca1d26f972de 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. diff -r 57d35fcde488 -r 5b0c8e499288 core/modulemanager.lua --- 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;