diff options
Diffstat (limited to 'xmake/core/project/rule.lua')
| -rw-r--r-- | xmake/core/project/rule.lua | 229 |
1 files changed, 213 insertions, 16 deletions
diff --git a/xmake/core/project/rule.lua b/xmake/core/project/rule.lua index 332844ca4..5b3b3aa78 100644 --- a/xmake/core/project/rule.lua +++ b/xmake/core/project/rule.lua @@ -31,6 +31,7 @@ local global = require("base/global") local interpreter = require("base/interpreter") local instance_deps = require("base/private/instance_deps") local select_script = require("base/private/select_script") +local addon = require("package/addon") local config = require("project/config") local sandbox = require("sandbox/sandbox") local sandbox_os = require("sandbox/modules/os") @@ -223,6 +224,11 @@ function rule._directories() } end +-- the rule directories of the installed addons, e.g. ~/.xmake/addons/<name>/<version>/rules +function rule._addon_directories() + return addon.payloadinfos("rules") +end + -- the interpreter function rule._interpreter() @@ -453,9 +459,205 @@ function rule.new(name, info, opt) return instance end +-- get the file which declares the given rule, e.g. c.build -> <rulesdir>/c++/xmake.lua +-- +-- most of the rule names match their directory name, so we only need it for the few +-- which do not, e.g. c.build, win.sdk.resource, and we get them by scanning the rule +-- declarations, which is much cheaper than interpreting all the rule files +-- +function rule._rulefile(name) + local rulefiles = rule._RULEFILES + if rulefiles == nil then + + -- @note we only limit the fast paths, a rule file which is nested deeper than this + -- is still found by the full load, @see rule.rules + local maxrecursion = 4 + rulefiles = {} + for _, dir in ipairs(rule._directories()) do + for _, filepath in ipairs(os.files(path.join(dir, "**/xmake.lua"), {recursion = maxrecursion})) do + local content = io.readfile(filepath) + if content then + for rulename in content:gmatch("rule%s*%(%s*\"(.-)\"%s*%)") do + rulefiles[rulename] = filepath + end + end + end + end + rule._RULEFILES = rulefiles + end + return rulefiles[name] +end + +-- load the rules which may provide the given rule name +-- +-- there are hundreds of builtin rules, but a project only uses a few of them, +-- so we do not load all of them, we only load the group which may provide it, +-- e.g. mode.debug -> <rulesdir>/mode/**/xmake.lua +-- +function rule._load_ondemand(name) + + -- it has been loaded with another group? e.g. c.build.pcheader comes with the c++ group + local loaded = rule._LOADED + if loaded == nil then + loaded = {} + rule._LOADED = loaded + end + local instance = loaded[name] + if instance then + return instance + end + + local groups = rule._GROUPS + if groups == nil then + groups = {} + rule._GROUPS = groups + end + + -- the rules of an addon are always referenced with its name, + -- e.g. add_rules("@addon/esp32/flash"), so we only load this addon + local maxrecursion = 4 + local groupkey, files, opt + if name:startswith("@addon/") then + local referenceinfo = addon.resolve_reference(name, "/", "rules") + if not referenceinfo then + return + end + groupkey = "@addon/" .. referenceinfo.addon + files = os.files(path.join(referenceinfo.dir, "**/xmake.lua"), {recursion = maxrecursion}) + opt = {prefix = groupkey .. "/"} + else + -- the group is the first part of the rule name, and it's usually the directory + -- name of its rules, e.g. mode.debug -> <rulesdir>/mode + groupkey = name:split(".", {plain = true})[1] + files = {} + for _, dir in ipairs(rule._directories()) do + local groupdir = path.join(dir, groupkey) + table.join2(files, os.files(path.join(groupdir, "xmake.lua"))) + table.join2(files, os.files(path.join(groupdir, "**/xmake.lua"), {recursion = maxrecursion})) + end + + -- the rule name does not match its directory name? we can only get its file + -- from the rule declarations, e.g. c.build -> <rulesdir>/c++/xmake.lua + if #files == 0 then + local rulefile = rule._rulefile(name) + if not rulefile then + return + end + groupkey = rulefile + files = {rulefile} + end + end + + if not groups[groupkey] then + local ruleinfos = {} + rule._load_rulefiles(ruleinfos, files, opt) + for rulename, ruleinfo in pairs(ruleinfos) do + loaded[rulename] = rule.new(rulename, ruleinfo) + end + groups[groupkey] = true + end + return loaded[name] +end + +-- report the missing addon of the given rule reference, e.g. add_rules("@addon/esp32/flash") +-- +-- it's either not installed at all, or it's installed but does not provide this rule +-- +function rule._raise_addon_notfound(name) + local referenceinfo, errors = addon.resolve_reference(name, "/", "rules") + if errors then + os.raise(errors) + end + os.raise("rule(%s) not found!\nplease install the addon which provides it first: xmake addon --install %s", + name, referenceinfo and referenceinfo.addon or "<addon>") +end + +-- clear the loaded rules, e.g. some addons may be installed just now +function rule.clear() + rule._RULES = nil + rule._GROUPS = nil + rule._LOADED = nil + rule._RULEFILES = nil +end + -- get the given global rule +-- +-- @param name the rule name, the rules of the installed addons need the +-- `@addon/<addon>/` prefix, e.g. "@addon/esp32/flash" +-- function rule.rule(name) - return rule.rules()[name] + local instance + if rule._RULES then + -- all the rules have been loaded, e.g. rule.rules() + instance = rule._RULES[name] + else + -- we only load the rules which may provide it + instance = rule._load_ondemand(name) + if instance == nil then + -- @note the rule name may not match its directory name, so we need + -- to load all the rules to be sure that it does not exist + instance = rule.rules()[name] + end + end + if instance == nil and name:startswith("@addon/") then + rule._raise_addon_notfound(name) + end + return instance +end + +-- load the rules from the given directory +function rule._load_rules(ruleinfos, dir, opt) + -- @note we may load a group directory directly, e.g. <rulesdir>/mode/xmake.lua + rule._load_rulefiles(ruleinfos, table.join(os.files(path.join(dir, "xmake.lua")), + os.files(path.join(dir, "**/xmake.lua"))), opt) +end + +-- load the rules from the given files +function rule._load_rulefiles(ruleinfos, files, opt) + opt = opt or {} + if files then + for _, filepath in ipairs(files) do + local results, errors = rule._load(filepath) + if results then + for rulename, ruleinfo in pairs(results) do + -- the addon rules are always referenced with the addon name, + -- e.g. add_rules("@addon/esp32/flash") + local fullname = (opt.prefix or "") .. rulename + if opt.prefix and ruleinfos[fullname] == nil then + -- the addon rules can depend on the other rules of the same addon, + -- e.g. add_deps("@self/base") -> add_deps("@addon/<addon>/base") + rule._replace_selfdeps(ruleinfo, opt.prefix) + end + ruleinfos[fullname] = ruleinfo + end + else + os.raise(errors) + end + end + end +end + +-- replace the `@self/` dependencies of the addon rules with the full names +function rule._replace_selfdeps(ruleinfo, prefix) + local deps = {} + local replace = function (depname) + if depname:startswith("@self/") then + return prefix .. depname:sub(#"@self/" + 1) + end + return depname + end + for _, depname in ipairs(table.wrap(ruleinfo:get("deps"))) do + table.insert(deps, replace(depname)) + end + if #deps > 0 then + ruleinfo:set("deps", table.unwrap(deps)) + end + for depname, extraconf in pairs(table.wrap(ruleinfo:extraconf("deps"))) do + local newname = replace(depname) + if newname ~= depname then + ruleinfo:extraconf_set("deps", newname, extraconf) + end + end end -- get global rules @@ -463,26 +665,21 @@ function rule.rules() local rules = rule._RULES if rules == nil then local ruleinfos = {} - local dirs = rule._directories() - for _, dir in ipairs(dirs) do - local files = os.files(path.join(dir, "**/xmake.lua")) - if files then - for _, filepath in ipairs(files) do - local results, errors = rule._load(filepath) - if results then - table.join2(ruleinfos, results) - else - os.raise(errors) - end - end - end + for _, dir in ipairs(rule._directories()) do + rule._load_rules(ruleinfos, dir) + end + for _, addoninfo in ipairs(rule._addon_directories()) do + rule._load_rules(ruleinfos, addoninfo.dir, {prefix = "@addon/" .. addoninfo.name .. "/"}) end -- make rule instances + -- + -- @note we reuse the rules which have been loaded on demand, + -- otherwise we would have two instances of the same rule + local loaded = rule._LOADED or {} rules = {} for rulename, ruleinfo in pairs(ruleinfos) do - local instance = rule.new(rulename, ruleinfo) - rules[rulename] = instance + rules[rulename] = loaded[rulename] or rule.new(rulename, ruleinfo) end rule._RULES = rules end |
