summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2026-08-16 22:00:38 +0800
committerruki <[email protected]>2026-08-16 22:00:38 +0800
commitdd7d4521ca5008a2c53401ec550f00c56c09435a (patch)
treed3db09204acba4aaba1b1d31d3931ff6fcef23e1
parentf53769382d8f65c6a90a838da55287864888cfbb (diff)
improve interpreter
-rw-r--r--xmake/actions/addon/main.lua2
-rw-r--r--xmake/core/base/interpreter.lua42
-rw-r--r--xmake/core/project/project.lua33
3 files changed, 34 insertions, 43 deletions
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