From f53769382d8f65c6a90a838da55287864888cfbb Mon Sep 17 00:00:00 2001 From: ruki Date: Sun, 16 Aug 2026 20:56:42 +0800 Subject: move add_addons to xmake.lua --- xmake/core/project/project.lua | 91 +++++++++++++++++++++++++++--------------- 1 file changed, 59 insertions(+), 32 deletions(-) (limited to 'xmake/core/project/project.lua') diff --git a/xmake/core/project/project.lua b/xmake/core/project/project.lua index 0cda89db1..a9b843ef7 100644 --- a/xmake/core/project/project.lua +++ b/xmake/core/project/project.lua @@ -234,14 +234,14 @@ end -- @note we cannot install them here, we are loading the project, so we do it in a -- sub-process, @see xmake/modules/private/action/addon/impl/install_addons.lua -- -function project._install_addons() +function project._install_addons(rootinfo) -- @note we need to cache the result, the project may be loaded many times, -- otherwise the failure would be ignored by the next load if not project._ADDONS_CHECKED then project._ADDONS_CHECKED = true - project._ADDONS_OK, project._ADDONS_ERRORS = project._do_install_addons() + project._ADDONS_OK, project._ADDONS_ERRORS, project._ADDONS_INSTALLED = project._do_install_addons(rootinfo) end - return project._ADDONS_OK, project._ADDONS_ERRORS + return project._ADDONS_OK, project._ADDONS_ERRORS, project._ADDONS_INSTALLED end -- activate the addon versions which this project locks @@ -263,33 +263,45 @@ function project._pin_addons() end -- do install the addons which this project declares -function project._do_install_addons() +-- install the addons which this project declares, e.g. add_addons("esp32-devel 1.0.x") +-- +-- @return true, errors and installed +-- +function project._do_install_addons(rootinfo) -- this project declares nothing? - local addonsinfo, errors = addons.load() - if errors then - return false, errors - end - if not addonsinfo or #addonsinfo.addons == 0 then + local requires = table.wrap(rootinfo:get("addons")) + if #requires == 0 then return true end + local ok, errors = addons.validate(requires) + if not ok then + return false, errors + end -- they have been installed already? - project._pin_addons() - if addons.satisfied(addonsinfo) then + if addons.satisfied(requires) then return true end -- tell the user why we are installing something, it may need to confirm and download, -- e.g. `xmake --help` in a project directory which declares some addons - utils.cprint("${color.warning}note: ${clear}%s: this project needs the addons(${bright}%s${clear}), installing them ..", - addons.filename(), table.concat(addonsinfo.addons, ", ")) + utils.cprint("${color.warning}note: ${clear}this project needs the addons(${bright}%s${clear}), installing them ..", + table.concat(requires, ", ")) if baseoption.get("help") then -- the help menu also shows the options which the addons provide, but the user -- did not ask for an installation, so we tell them how to skip it utils.cprint("${dim}we can run it outside of the project directory to skip the installation${clear}") end + -- we pass the declarations to the installer, it must not load this project again, + -- @see xmake/modules/private/action/addon/impl/install_addons.lua + local datafile = os.tmpfile() + local ok, errors = io.save(datafile, {addons = requires, repositories = table.wrap(rootinfo:get("repositories"))}) + if not ok then + return false, errors + end + -- @note we run it in a working directory which has no project, @see addon.workdir(), -- otherwise it would load this project again -- @@ -310,8 +322,10 @@ function project._do_install_addons() end table.insert(argv, "private.action.addon.impl.install_addons") table.insert(argv, os.projectdir()) - local ok, errors = os.execv(os.programfile(), argv, {curdir = addon.workdir()}) - if ok ~= 0 then + table.insert(argv, datafile) + local exitcode, errors = os.execv(os.programfile(), argv, {curdir = addon.workdir()}) + os.rm(datafile) + if exitcode ~= 0 then return false, errors or "install the addons of this project failed!" end @@ -320,7 +334,7 @@ function project._do_install_addons() project._pin_addons() rule.clear() task.clear() - return true + return true, nil, true end -- load the project file @@ -329,26 +343,13 @@ end -- - force: load the project file again even if it has been loaded -- - disable_filter: disable the interpreter filter, e.g. `$(plat)` -- - skip_addons: do not install the addons which this project declares +-- - addons_installed: the addons have been installed, we are loading it again -- function project._load(opt) opt = opt or {} - -- install the addons which this project declares in `xmake-addons.lua` first, - -- it may use their rules, toolchains and includes files, - -- e.g. includes("@addon/esp32-devel/board") - -- - -- @note we need to check it before the cache, the project file may have been loaded - -- already without them, e.g. by the option menu - -- - if opt.skip_addons then - -- we do not install them here, but we still need to use the locked versions - project._pin_addons() - else - local ok, errors = project._install_addons() - if not ok then - return false, errors - end - end + -- use the locked versions of the addons which this project declares + project._pin_addons() -- has already been loaded? if project._memcache():get("rootinfo") and not opt.force then @@ -364,6 +365,12 @@ function project._load(opt) -- get interpreter local interp = project.interpreter() + -- this project declares the addons which it needs, e.g. add_addons("esp32-devel"), + -- but we can only know them after loading it, so this pass must survive the references + -- of the addons which are not installed yet, and we load it again after installing them, + -- e.g. includes("@addon/esp32-devel/board") + interp:addons_deferred_set(not opt.addons_installed) + -- load script local ok, errors = interp:load(project.rootfile(), {on_load_data = function (data) for _, xmakerc_file in ipairs(project.rcfiles()) do @@ -386,6 +393,23 @@ function project._load(opt) return false, errors end + -- install the addons which this project declares, and then load it again with them + -- + -- @note we do not install them for the option menu, it merges the project tasks in a + -- best-effort way and every command builds it, @see project._load_tasks() + -- + if not opt.skip_addons and not opt.addons_installed then + local ok, errors, installed = project._install_addons(rootinfo) + if not ok then + os.cd(oldir) + return false, errors + end + if installed then + os.cd(oldir) + return project._load({force = true, disable_filter = opt.disable_filter, addons_installed = true}) + end + end + -- load the root info of the target local rootinfo_target, errors = project._load_scope("root.target", true, not opt.disable_filter) if not rootinfo_target then @@ -780,6 +804,9 @@ function project.apis() , "add_requires" , "add_requireconfs" , "add_repositories" + -- the addons which this project needs, they are installed automatically, + -- e.g. add_addons("esp32-devel 1.0.x"), @see core/project/addons.lua + , "add_addons" } , paths = { -- cgit v1.3.1 From dd7d4521ca5008a2c53401ec550f00c56c09435a Mon Sep 17 00:00:00 2001 From: ruki Date: Sun, 16 Aug 2026 22:00:38 +0800 Subject: improve interpreter --- xmake/actions/addon/main.lua | 2 +- xmake/core/base/interpreter.lua | 42 ++++++++++++++++------------------------- xmake/core/project/project.lua | 33 ++++++++++++++++---------------- 3 files changed, 34 insertions(+), 43 deletions(-) (limited to 'xmake/core/project/project.lua') diff --git a/xmake/actions/addon/main.lua b/xmake/actions/addon/main.lua index 28c73a754..99f92d125 100644 --- a/xmake/actions/addon/main.lua +++ b/xmake/actions/addon/main.lua @@ -21,6 +21,7 @@ -- imports import("core.base.option") import("core.package.addon") +import("core.project.project") import("devel.git") import("private.action.addon.impl.install_addons") import("private.action.addon.impl.xrepo", {alias = "xrepo_addon"}) @@ -230,7 +231,6 @@ end -- upgrade the addons which the current project declares, e.g. add_addons("esp32-devel 1.0.x") function _upgrade() - import("core.project.project") local declarations = {addons = table.wrap(project.get("addons")), repositories = table.wrap(project.get("repositories"))} assert(#declarations.addons > 0, "no addons are declared in this project, e.g. add_addons(\"esp32-devel\")!") diff --git a/xmake/core/base/interpreter.lua b/xmake/core/base/interpreter.lua index 215ff6673..ad62168fb 100644 --- a/xmake/core/base/interpreter.lua +++ b/xmake/core/base/interpreter.lua @@ -880,31 +880,24 @@ function interpreter:scriptdir() return path.directory(self._PRIVATE._CURFILE) end --- set root scope kind --- --- the root api will affect these scopes --- --- do we defer the unresolvable addon references? e.g. includes("@addon/esp32/board") --- --- @note the project file declares the addons which it needs, but we can only know them --- after loading it, so the first load must survive the references of the addons which --- are not installed yet, @see project._load() --- -function interpreter:addons_deferred() - return self._PRIVATE._ADDONS_DEFERRED -end - --- defer the unresolvable addon references instead of raising errors -function interpreter:addons_deferred_set(enabled) - self._PRIVATE._ADDONS_DEFERRED = enabled - self._PRIVATE._ADDONS_MISSING = nil +-- do we ignore the unresolvable references of includes()? e.g. includes("@addon/esp32/board") +function interpreter:includes_unresolved() + return self._PRIVATE._INCLUDES_UNRESOLVED end --- get the addon references which have not been resolved, @see interpreter:addons_deferred_set -function interpreter:addons_missing() - return self._PRIVATE._ADDONS_MISSING +-- ignore the unresolvable references of includes() instead of raising errors +-- +-- @note the project file may reference the resources which have not been installed yet, +-- so the caller can load it, install them and load it again, @see project._load() +-- +function interpreter:includes_unresolved_set(enabled) + self._PRIVATE._INCLUDES_UNRESOLVED = enabled end +-- set root scope kind +-- +-- the root api will affect these scopes +-- function interpreter:rootscope_set(scope_kind) assert(self and self._PRIVATE) self._PRIVATE._ROOTSCOPE = scope_kind @@ -1821,11 +1814,8 @@ end function interpreter:_find_addon_includes(subpath) local referenceinfo, errors = addon.resolve_reference(subpath, "/", "includes", {scriptdir = self:scriptdir()}) if not referenceinfo then - -- this addon is not installed yet? we will install it and load this file again - if self:addons_deferred() then - local missing = self._PRIVATE._ADDONS_MISSING or {} - table.insert(missing, subpath) - self._PRIVATE._ADDONS_MISSING = missing + -- it has not been installed yet? the caller may install it and load this file again + if self:includes_unresolved() then return {} end os.raise(errors) diff --git a/xmake/core/project/project.lua b/xmake/core/project/project.lua index a9b843ef7..f67ac9a67 100644 --- a/xmake/core/project/project.lua +++ b/xmake/core/project/project.lua @@ -237,11 +237,12 @@ end function project._install_addons(rootinfo) -- @note we need to cache the result, the project may be loaded many times, -- otherwise the failure would be ignored by the next load - if not project._ADDONS_CHECKED then - project._ADDONS_CHECKED = true - project._ADDONS_OK, project._ADDONS_ERRORS, project._ADDONS_INSTALLED = project._do_install_addons(rootinfo) + local result = project._ADDONS_RESULT + if result == nil then + result = project._do_install_addons(rootinfo) + project._ADDONS_RESULT = result end - return project._ADDONS_OK, project._ADDONS_ERRORS, project._ADDONS_INSTALLED + return result end -- activate the addon versions which this project locks @@ -265,23 +266,23 @@ end -- do install the addons which this project declares -- install the addons which this project declares, e.g. add_addons("esp32-devel 1.0.x") -- --- @return true, errors and installed +-- @return the result, e.g. {ok = true, installed = true}, {ok = false, errors = ".."} -- function project._do_install_addons(rootinfo) -- this project declares nothing? local requires = table.wrap(rootinfo:get("addons")) if #requires == 0 then - return true + return {ok = true} end local ok, errors = addons.validate(requires) if not ok then - return false, errors + return {ok = false, errors = errors} end -- they have been installed already? if addons.satisfied(requires) then - return true + return {ok = true} end -- tell the user why we are installing something, it may need to confirm and download, @@ -299,7 +300,7 @@ function project._do_install_addons(rootinfo) local datafile = os.tmpfile() local ok, errors = io.save(datafile, {addons = requires, repositories = table.wrap(rootinfo:get("repositories"))}) if not ok then - return false, errors + return {ok = false, errors = errors} end -- @note we run it in a working directory which has no project, @see addon.workdir(), @@ -326,7 +327,7 @@ function project._do_install_addons(rootinfo) local exitcode, errors = os.execv(os.programfile(), argv, {curdir = addon.workdir()}) os.rm(datafile) if exitcode ~= 0 then - return false, errors or "install the addons of this project failed!" + return {ok = false, errors = errors or "install the addons of this project failed!"} end -- we have loaded the registry and its caches before installing them, so we need to reload it @@ -334,7 +335,7 @@ function project._do_install_addons(rootinfo) project._pin_addons() rule.clear() task.clear() - return true, nil, true + return {ok = true, installed = true} end -- load the project file @@ -369,7 +370,7 @@ function project._load(opt) -- but we can only know them after loading it, so this pass must survive the references -- of the addons which are not installed yet, and we load it again after installing them, -- e.g. includes("@addon/esp32-devel/board") - interp:addons_deferred_set(not opt.addons_installed) + interp:includes_unresolved_set(not opt.addons_installed) -- load script local ok, errors = interp:load(project.rootfile(), {on_load_data = function (data) @@ -399,12 +400,12 @@ function project._load(opt) -- best-effort way and every command builds it, @see project._load_tasks() -- if not opt.skip_addons and not opt.addons_installed then - local ok, errors, installed = project._install_addons(rootinfo) - if not ok then + local result = project._install_addons(rootinfo) + if not result.ok then os.cd(oldir) - return false, errors + return false, result.errors end - if installed then + if result.installed then os.cd(oldir) return project._load({force = true, disable_filter = opt.disable_filter, addons_installed = true}) end -- cgit v1.3.1 From d7fc1e30a747c0457b4f45194946fcff747c76db Mon Sep 17 00:00:00 2001 From: ruki Date: Sun, 16 Aug 2026 22:28:05 +0800 Subject: improve find addon includes --- xmake/core/base/interpreter.lua | 61 +++++++++++++++++++++-------------------- xmake/core/package/addon.lua | 30 ++++++++++++++++++++ xmake/core/project/project.lua | 4 +++ 3 files changed, 65 insertions(+), 30 deletions(-) (limited to 'xmake/core/project/project.lua') diff --git a/xmake/core/base/interpreter.lua b/xmake/core/base/interpreter.lua index ad62168fb..8dc0e4880 100644 --- a/xmake/core/base/interpreter.lua +++ b/xmake/core/base/interpreter.lua @@ -30,7 +30,6 @@ local string = require("base/string") local hashset = require("base/hashset") local scopeinfo = require("base/scopeinfo") local deprecated = require("base/deprecated") -local addon = require("package/addon") local sandbox = require("sandbox/sandbox") -- the rules to reword the raw lua error messages into friendly ones, {pattern, replacement} @@ -880,6 +879,19 @@ function interpreter:scriptdir() return path.directory(self._PRIVATE._CURFILE) end +-- add a resolver for the references of includes(), e.g. includes("@addon/esp32/check") +-- +-- @param resolver function (interp, reference), it returns the files, or nil and errors +-- +-- @note the interpreter knows nothing about the references, the callers register the +-- resolvers which they support, e.g. @see project._interpreter() +-- +function interpreter:includes_resolver_add(resolver) + local resolvers = self._PRIVATE._INCLUDES_RESOLVERS or {} + table.insert(resolvers, resolver) + self._PRIVATE._INCLUDES_RESOLVERS = resolvers +end + -- do we ignore the unresolvable references of includes()? e.g. includes("@addon/esp32/board") function interpreter:includes_unresolved() return self._PRIVATE._INCLUDES_UNRESOLVED @@ -1810,30 +1822,6 @@ function interpreter:_find_builtin_includes(subpath) return os.files(path.join(os.programdir(), "includes", builtin_path, "xmake.lua")) end --- find the include files of the addons, e.g. includes("@addon/esp32/check"), includes("@self/check") -function interpreter:_find_addon_includes(subpath) - local referenceinfo, errors = addon.resolve_reference(subpath, "/", "includes", {scriptdir = self:scriptdir()}) - if not referenceinfo then - -- it has not been installed yet? the caller may install it and load this file again - if self:includes_unresolved() then - return {} - end - os.raise(errors) - end - local addon_path = referenceinfo.name - local files - if addon_path:endswith(".lua") then - files = os.files(path.join(referenceinfo.dir, addon_path)) - else - files = os.files(path.join(referenceinfo.dir, addon_path, "xmake.lua")) - end - -- the addon is installed, but it does not provide this file, we cannot ignore it - if not files or #files == 0 then - os.raise("includes(%s) not found!", subpath) - end - return files -end - function interpreter:api_builtin_includes(...) assert(self and self._PRIVATE and self._PRIVATE._ROOTDIR and self._PRIVATE._MTIMES) local curfile = self._PRIVATE._CURFILE @@ -1853,11 +1841,24 @@ function interpreter:api_builtin_includes(...) found = true end end - -- attempt to find files from the includes of the addons - -- e.g. includes("@addon/esp32/check"), includes("@self/check") - if not found and addon.is_reference(subpath, "/") then - table.join2(subpaths_matched, self:_find_addon_includes(subpath)) - found = true + -- attempt to find files from the registered resolvers of the references + -- e.g. includes("@addon/esp32/check"), @see interpreter:includes_resolver_add() + if not found and subpath:startswith("@") then + for _, resolver in ipairs(self._PRIVATE._INCLUDES_RESOLVERS or {}) do + local files, errors = resolver(self, subpath) + if files then + table.join2(subpaths_matched, files) + found = true + break + elseif errors then + -- it has not been resolved yet? the caller may load this file again + if self:includes_unresolved() then + found = true + break + end + os.raise(errors) + end + end end -- find the given files from the project directory if not found then diff --git a/xmake/core/package/addon.lua b/xmake/core/package/addon.lua index 6e7d62eb4..e35a58317 100644 --- a/xmake/core/package/addon.lua +++ b/xmake/core/package/addon.lua @@ -566,6 +566,36 @@ function addon.globalmodules() return globalmodules end +-- find the include files of the given addon reference, e.g. includes("@addon/esp32/board") +-- +-- @param interp the interpreter which is loading the file, @see interpreter:includes_resolver_add +-- @param reference the reference, e.g. "@addon/esp32/board", "@self/board" +-- +-- @return the files, or nil and errors +-- +function addon.find_includes(interp, reference) + if not addon.is_reference(reference, "/") then + return + end + local referenceinfo, errors = addon.resolve_reference(reference, "/", "includes", {scriptdir = interp:scriptdir()}) + if not referenceinfo then + return nil, errors + end + local name = referenceinfo.name + local files + if name:endswith(".lua") then + files = os.files(path.join(referenceinfo.dir, name)) + else + files = os.files(path.join(referenceinfo.dir, name, "xmake.lua")) + end + + -- the addon is installed, but it does not provide this file, we cannot ignore it + if not files or #files == 0 then + os.raise("includes(%s) not found!", reference) + end + return files +end + -- get the payload directories of the given kind from all installed addons -- -- @param kind the payload kind, e.g. "plugins", "rules" diff --git a/xmake/core/project/project.lua b/xmake/core/project/project.lua index f67ac9a67..b92c5cfab 100644 --- a/xmake/core/project/project.lua +++ b/xmake/core/project/project.lua @@ -860,6 +860,10 @@ function project.interpreter() -- set root scope interp:rootscope_set("target") + -- the project file can reference the includes files of the addons, + -- e.g. includes("@addon/esp32-devel/board") + interp:includes_resolver_add(addon.find_includes) + -- define apis for rule interp:api_define(rule.apis()) -- cgit v1.3.1