diff options
| author | ruki <[email protected]> | 2026-08-08 18:31:55 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2026-08-09 18:25:42 +0800 |
| commit | 53075cd271268c1931a41648a92891c362f2483a (patch) | |
| tree | 663735ec08e713b76fc0f8994d4f1eed80e36152 | |
| parent | 73a790f3c4810755921350c61cf84d6504a30297 (diff) | |
add addon module
| -rw-r--r-- | xmake/core/base/task.lua | 12 | ||||
| -rw-r--r-- | xmake/core/package/addon.lua | 200 | ||||
| -rw-r--r-- | xmake/core/package/package.lua | 35 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/package/addon.lua | 41 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/package/package.lua | 16 | ||||
| -rw-r--r-- | xmake/modules/private/action/require/impl/actions/install.lua | 6 | ||||
| -rw-r--r-- | xmake/plugins/addon/main.lua | 43 |
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 |
