summaryrefslogtreecommitdiff
path: root/xmake/core/project/rule.lua
diff options
context:
space:
mode:
Diffstat (limited to 'xmake/core/project/rule.lua')
-rw-r--r--xmake/core/project/rule.lua229
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