summaryrefslogtreecommitdiff
path: root/xmake/core/package/addon.lua
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 /xmake/core/package/addon.lua
parent73a790f3c4810755921350c61cf84d6504a30297 (diff)
add addon module
Diffstat (limited to 'xmake/core/package/addon.lua')
-rw-r--r--xmake/core/package/addon.lua200
1 files changed, 200 insertions, 0 deletions
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