summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorruki <[email protected]>2026-08-08 18:31:55 +0800
committerruki <[email protected]>2026-08-09 18:25:42 +0800
commit53075cd271268c1931a41648a92891c362f2483a (patch)
tree663735ec08e713b76fc0f8994d4f1eed80e36152
parent73a790f3c4810755921350c61cf84d6504a30297 (diff)
add addon module
-rw-r--r--xmake/core/base/task.lua12
-rw-r--r--xmake/core/package/addon.lua200
-rw-r--r--xmake/core/package/package.lua35
-rw-r--r--xmake/core/sandbox/modules/import/core/package/addon.lua41
-rw-r--r--xmake/core/sandbox/modules/import/core/package/package.lua16
-rw-r--r--xmake/modules/private/action/require/impl/actions/install.lua6
-rw-r--r--xmake/plugins/addon/main.lua43
7 files changed, 279 insertions, 74 deletions
diff --git a/xmake/core/base/task.lua b/xmake/core/base/task.lua
index 2488665f5..e8d2441b9 100644
--- a/xmake/core/base/task.lua
+++ b/xmake/core/base/task.lua
@@ -28,6 +28,7 @@ local string = require("base/string")
local global = require("base/global")
local hashset = require("base/hashset")
local interpreter = require("base/interpreter")
+local addon = require("package/addon")
local sandbox = require("sandbox/sandbox")
local config = require("project/config")
local sandbox_os = require("sandbox/modules/os")
@@ -89,12 +90,11 @@ function task._directories()
path.join(os.programdir(), "actions")}
-- add the plugins of the installed addons, e.g. ~/.xmake/addons/<name>/<version>/plugins
- local addonsdir = path.join(global.directory(), "addons")
- if os.isdir(addonsdir) then
- for _, plugindir in ipairs(os.dirs(path.join(addonsdir, "*", "*", "plugins"))) do
- table.insert(dirs, plugindir)
- end
- end
+ --
+ -- we get them from the addons registry file directly,
+ -- so we do not need to scan the whole addons directory on startup
+ --
+ table.join2(dirs, addon.payloads("plugins"))
local plugindirs = os.getenv("XMAKE_PLUGIN_DIRS")
if plugindirs then
diff --git a/xmake/core/package/addon.lua b/xmake/core/package/addon.lua
new file mode 100644
index 000000000..e40a83a47
--- /dev/null
+++ b/xmake/core/package/addon.lua
@@ -0,0 +1,200 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author ruki
+-- @file addon.lua
+--
+
+-- define module
+local addon = addon or {}
+
+-- load modules
+local os = require("base/os")
+local io = require("base/io")
+local path = require("base/path")
+local table = require("base/table")
+local utils = require("base/utils")
+local global = require("base/global")
+
+-- the payload directories of an addon
+--
+-- an addon can provide any subset of them, e.g. only `plugins`
+--
+-- @note only `plugins` is activated for now, the others are reserved
+--
+function addon.payloaddirs()
+ return {"plugins", "rules", "toolchains", "platforms", "modules", "templates", "themes", "includes"}
+end
+
+-- the install directory of addons, e.g. ~/.xmake/addons
+function addon.installdir()
+ return path.join(global.directory(), "addons")
+end
+
+-- get the directory name of the given addon name, e.g. "myns::foo" -> "myns_foo"
+function addon.dirname(name)
+ return (name:lower():gsub("::", "_"))
+end
+
+-- the registry file of the installed addons, e.g. ~/.xmake/addons/addons.conf
+--
+-- we save all installed addons to this file when installing/removing them,
+-- so we do not need to scan the whole addons directory on startup
+--
+function addon.registryfile()
+ return path.join(addon.installdir(), "addons.conf")
+end
+
+-- get all installed addons
+--
+-- @return the addons table, e.g. {["hello-world"] = {version = "latest", payloads = {"plugins"}}}
+--
+function addon.addons()
+ local addons = addon._ADDONS
+ if addons == nil then
+ addons = {}
+ local registryfile = addon.registryfile()
+ if os.isfile(registryfile) then
+ addons = io.load(registryfile) or {}
+ end
+ addon._ADDONS = addons
+ end
+ return addons
+end
+
+-- get the install directory of the given addon, e.g. ~/.xmake/addons/<name>/<version>
+function addon.addondir(name, version)
+ local dirname = addon.dirname(name)
+ if version == nil then
+ local addoninfo = addon.addons()[dirname]
+ if addoninfo == nil then
+ return nil
+ end
+ version = addoninfo.version
+ end
+ return path.join(addon.installdir(), dirname, version)
+end
+
+-- get the payload directories of the given kind from all installed addons
+--
+-- @param kind the payload kind, e.g. "plugins", "rules"
+-- @return the directories, e.g. {"~/.xmake/addons/hello-world/latest/plugins"}
+--
+-- @note we do not check if these directories exist, the callers will just ignore the invalid ones
+--
+function addon.payloads(kind)
+ local payloads = {}
+ for name, addoninfo in pairs(addon.addons()) do
+ if table.contains(addoninfo.payloads or {}, kind) then
+ table.insert(payloads, path.join(addon.installdir(), name, addoninfo.version, kind))
+ end
+ end
+ return payloads
+end
+
+-- get the payload directories of the given addon directory, e.g. {"plugins", "rules"}
+function addon.payloads_of(addondir)
+ local payloads = {}
+ for _, payloaddir in ipairs(addon.payloaddirs()) do
+ if os.isdir(path.join(addondir, payloaddir)) then
+ table.insert(payloads, payloaddir)
+ end
+ end
+ return payloads
+end
+
+-- get the default on_install script of addon packages
+--
+-- we only install the payload directories of this addon, e.g. plugins, rules, toolchains, ...
+--
+function addon.installscript()
+ return function (package)
+ local installed = false
+ for _, payloaddir in ipairs(addon.payloaddirs()) do
+ if os.isdir(payloaddir) then
+ os.cp(payloaddir, package:installdir())
+ installed = true
+ end
+ end
+ if not installed then
+ os.raise("addon(%s): no payload directory found, e.g. plugins!", package:name())
+ end
+ end
+end
+
+-- save the given addons to the registry file
+function addon._save(addons)
+ addon._ADDONS = addons
+ local registryfile = addon.registryfile()
+ -- we need not create an empty registry file if no addons are installed
+ if next(addons) == nil and not os.isfile(registryfile) then
+ return
+ end
+ local ok, errors = io.save(registryfile, addons)
+ if not ok then
+ utils.warning(errors)
+ end
+end
+
+-- register the given installed addon
+--
+-- @param name the addon name
+-- @param version the addon version, e.g. "1.0.1", "latest"
+--
+function addon.register(name, version)
+ local dirname = addon.dirname(name)
+ local addons = addon.addons()
+ addons[dirname] = {version = version, payloads = addon.payloads_of(path.join(addon.installdir(), dirname, version))}
+ addon._save(addons)
+end
+
+-- unregister the given addon
+function addon.unregister(name)
+ local dirname = addon.dirname(name)
+ local addons = addon.addons()
+ if addons[dirname] then
+ addons[dirname] = nil
+ addon._save(addons)
+ end
+end
+
+-- rescan the install directory and rebuild the registry
+--
+-- it's only used to repair the registry file, e.g. the user removed some addon directories manually
+--
+function addon.rescan()
+ local addons = {}
+ for _, versiondir in ipairs(os.dirs(path.join(addon.installdir(), "*", "*"))) do
+ local payloads = addon.payloads_of(versiondir)
+ if #payloads > 0 then
+ addons[path.filename(path.directory(versiondir))] = {version = path.filename(versiondir), payloads = payloads}
+ end
+ end
+ addon._save(addons)
+ return addons
+end
+
+-- clear all installed addons
+function addon.clear()
+ local installdir = addon.installdir()
+ if os.isdir(installdir) then
+ os.rmdir(installdir)
+ end
+ addon._ADDONS = {}
+end
+
+-- return module
+return addon
diff --git a/xmake/core/package/package.lua b/xmake/core/package/package.lua
index 8f035b6b5..2b56ee332 100644
--- a/xmake/core/package/package.lua
+++ b/xmake/core/package/package.lua
@@ -37,6 +37,7 @@ local interpreter = require("base/interpreter")
local select_script = require("base/private/select_script")
local is_cross = require("base/private/is_cross")
local memcache = require("cache/memcache")
+local addon = require("package/addon")
local toolchain = require("tool/toolchain")
local compiler = require("tool/compiler")
local linker = require("tool/linker")
@@ -940,7 +941,7 @@ function _instance:installdir(...)
if os.is_host("windows") then
version_str = version_str:gsub("[>=<|%*]", "")
end
- installdir = path.join(package.addon_installdir(), name, version_str)
+ installdir = addon.addondir(self:name(), version_str)
elseif self:is_plugin() then
-- deprecated, @see the `addon` kind
installdir = path.join(global.directory(), "plugins", name)
@@ -3053,21 +3054,6 @@ function package.installdir(opt)
return installdir
end
--- the install directory for addon packages, e.g. ~/.xmake/addons
-function package.addon_installdir()
- return path.join(global.directory(), "addons")
-end
-
--- the payload directories of addon packages
---
--- an addon can provide any subset of them, e.g. only `plugins`
---
--- @note only `plugins` is activated for now, the others are reserved
---
-function package.addon_payloaddirs()
- return {"plugins", "rules", "toolchains", "platforms", "modules", "templates", "themes", "includes"}
-end
-
-- the search directories
function package.searchdirs()
local searchdirs = global.get("pkg_searchdirs")
@@ -3264,22 +3250,9 @@ function package.load_from_repository(packagename, packagedir, opt)
return nil, string.format("%s: package(%s) not found!", scriptpath, packagename)
end
- -- we need set the default on_install script if it's addon package,
- -- we only install the payload directories of this addon, e.g. plugins, rules, toolchains, ...
+ -- we need set the default on_install script if it's addon package
if packageinfo:get("kind") == "addon" and not packageinfo:get("install") then
- local on_install = function (pkg)
- local installed = false
- for _, payloaddir in ipairs(package.addon_payloaddirs()) do
- if os.isdir(payloaddir) then
- os.cp(payloaddir, pkg:installdir())
- installed = true
- end
- end
- if not installed then
- os.raise("addon(%s): no payload directory found, e.g. plugins!", pkg:name())
- end
- end
- packageinfo:set("install", on_install)
+ packageinfo:set("install", addon.installscript())
end
-- we need set the default on_install script if it's plugin package
diff --git a/xmake/core/sandbox/modules/import/core/package/addon.lua b/xmake/core/sandbox/modules/import/core/package/addon.lua
new file mode 100644
index 000000000..c1267a5d4
--- /dev/null
+++ b/xmake/core/sandbox/modules/import/core/package/addon.lua
@@ -0,0 +1,41 @@
+--!A cross-platform build utility based on Lua
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- Copyright (C) 2015-present, Xmake Open Source Community.
+--
+-- @author ruki
+-- @file addon.lua
+--
+
+-- define module
+local sandbox_core_package_addon = sandbox_core_package_addon or {}
+
+-- load modules
+local addon = require("package/addon")
+
+-- inherit some builtin interfaces
+sandbox_core_package_addon.installdir = addon.installdir
+sandbox_core_package_addon.registryfile = addon.registryfile
+sandbox_core_package_addon.payloaddirs = addon.payloaddirs
+sandbox_core_package_addon.payloads = addon.payloads
+sandbox_core_package_addon.payloads_of = addon.payloads_of
+sandbox_core_package_addon.addons = addon.addons
+sandbox_core_package_addon.addondir = addon.addondir
+sandbox_core_package_addon.register = addon.register
+sandbox_core_package_addon.unregister = addon.unregister
+sandbox_core_package_addon.rescan = addon.rescan
+sandbox_core_package_addon.clear = addon.clear
+
+-- return module
+return sandbox_core_package_addon
diff --git a/xmake/core/sandbox/modules/import/core/package/package.lua b/xmake/core/sandbox/modules/import/core/package/package.lua
index 14729af65..e011bdff0 100644
--- a/xmake/core/sandbox/modules/import/core/package/package.lua
+++ b/xmake/core/sandbox/modules/import/core/package/package.lua
@@ -27,15 +27,13 @@ local package = require("package/package")
local raise = require("sandbox/modules/raise")
-- inherit some builtin interfaces
-sandbox_core_package_package.cachedir = package.cachedir
-sandbox_core_package_package.installdir = package.installdir
-sandbox_core_package_package.addon_installdir = package.addon_installdir
-sandbox_core_package_package.addon_payloaddirs = package.addon_payloaddirs
-sandbox_core_package_package.searchdirs = package.searchdirs
-sandbox_core_package_package.targetplat = package.targetplat
-sandbox_core_package_package.targetarch = package.targetarch
-sandbox_core_package_package.apis = package.apis
-sandbox_core_package_package.new = package.new
+sandbox_core_package_package.cachedir = package.cachedir
+sandbox_core_package_package.installdir = package.installdir
+sandbox_core_package_package.searchdirs = package.searchdirs
+sandbox_core_package_package.targetplat = package.targetplat
+sandbox_core_package_package.targetarch = package.targetarch
+sandbox_core_package_package.apis = package.apis
+sandbox_core_package_package.new = package.new
-- load the package from the project file
function sandbox_core_package_package.load_from_project(packagename)
diff --git a/xmake/modules/private/action/require/impl/actions/install.lua b/xmake/modules/private/action/require/impl/actions/install.lua
index 7fb54a951..7e60b16b6 100644
--- a/xmake/modules/private/action/require/impl/actions/install.lua
+++ b/xmake/modules/private/action/require/impl/actions/install.lua
@@ -22,6 +22,7 @@
import("core.base.option")
import("core.base.tty")
import("core.package.package", {alias = "core_package"})
+import("core.package.addon")
import("core.project.target")
import("core.project.project")
import("core.platform.platform")
@@ -499,6 +500,11 @@ function main(package)
-- save the package info to the manifest file
package:manifest_save()
+
+ -- register this addon, so that xmake can find its payloads, e.g. plugins
+ if package:is_addon() then
+ addon.register(package:name(), package:version_str() or "latest")
+ end
installed_now = true
end
end
diff --git a/xmake/plugins/addon/main.lua b/xmake/plugins/addon/main.lua
index 829aa0438..d072cec7f 100644
--- a/xmake/plugins/addon/main.lua
+++ b/xmake/plugins/addon/main.lua
@@ -20,8 +20,8 @@
-- imports
import("core.base.option")
+import("core.package.addon")
import("core.package.repository")
-import("core.package.package", {alias = "core_package"})
import("devel.git")
import("private.action.require.impl.environment")
@@ -36,9 +36,9 @@ end
-- get addon directory in ~/.xmake/addons
function _get_addondir(name, version)
- local addondir = core_package.addon_installdir()
+ local addondir = addon.installdir()
if name then
- addondir = path.join(addondir, _check_addon_name(name))
+ addondir = path.join(addondir, addon.dirname(_check_addon_name(name)))
if version then
addondir = path.join(addondir, version)
end
@@ -51,17 +51,6 @@ function _repositories()
return table.join(repository.repositories({global = false}), repository.repositories({global = true}))
end
--- get the payload directories of the given addon directory, e.g. {"plugins", "rules"}
-function _get_payloads(dir)
- local payloads = {}
- for _, payloaddir in ipairs(core_package.addon_payloaddirs()) do
- if os.isdir(path.join(dir, payloaddir)) then
- table.insert(payloads, payloaddir)
- end
- end
- return payloads
-end
-
-- install an addon from the given repository or the first repository containing it
function _install_from_repo(name, reponame)
@@ -91,11 +80,12 @@ end
-- install a single addon from a source directory (as the given name, default to the directory name)
function _install_from_local(dir, name)
assert(os.isdir(dir), "addon path(%s) not found!", dir)
- assert(#_get_payloads(dir) > 0, "addon path(%s): no payload directory found, e.g. ${bright}plugins${clear}!", dir)
+ assert(#addon.payloads_of(dir) > 0, "addon path(%s): no payload directory found, e.g. ${bright}plugins${clear}!", dir)
name = name or path.filename(path.absolute(dir))
local dstdir = _get_addondir(name, LOCALVERSION)
assert(not os.isdir(dstdir), "addon(%s) already exists!", name)
os.vcp(dir, dstdir)
+ addon.register(name, LOCALVERSION)
cprint("${color.success}install ${bright}%s${clear} ok!", name)
end
@@ -166,6 +156,7 @@ function _remove()
local dir = _get_addondir(name)
assert(os.isdir(dir), "addon(%s) not found!", name)
os.rmdir(dir)
+ addon.unregister(name)
cprint("${color.success}remove ${bright}%s${clear} ok!", name)
end
@@ -180,18 +171,17 @@ function _addon_description(dir)
end
end
--- collect the installed addons, e.g. ~/.xmake/addons/<name>/<version>
+-- collect the installed addons from the addons registry
+--
+-- we always rescan the install directory here to repair the registry,
+-- e.g. the user may remove some addon directories manually
+--
function _collect_installed_addons()
local entries = {}
- for _, versiondir in ipairs(os.dirs(path.join(_get_addondir(), "*", "*"))) do
- local payloads = _get_payloads(versiondir)
- if #payloads > 0 then
- table.insert(entries, {
- name = path.filename(path.directory(versiondir)),
- version = path.filename(versiondir),
- payloads = payloads})
- end
+ for name, addoninfo in pairs(addon.rescan()) do
+ table.insert(entries, {name = name, version = addoninfo.version, payloads = addoninfo.payloads})
end
+ table.sort(entries, function (a, b) return a.name < b.name end)
return entries
end
@@ -267,10 +257,7 @@ end
-- clear all installed addons
function _clear()
- local addonsdir = _get_addondir()
- if os.isdir(addonsdir) then
- os.rmdir(addonsdir)
- end
+ addon.clear()
cprint("${color.success}clear all installed addons ok!")
end