diff options
| author | ruki <[email protected]> | 2026-08-14 23:42:13 +0800 |
|---|---|---|
| committer | ruki <[email protected]> | 2026-08-14 23:42:13 +0800 |
| commit | 46141dbac176444bd1ed114ea419d4bb8e6d2e13 (patch) | |
| tree | 5bb62ecf325bd4d42bcedd2440a35dddada97c57 | |
| parent | a79cf74f920c088528d66a67a59b3d8eaa1d4f7a (diff) | |
update lock and version
| -rw-r--r-- | xmake/actions/addon/main.lua | 50 | ||||
| -rw-r--r-- | xmake/actions/addon/xmake.lua | 5 | ||||
| -rw-r--r-- | xmake/core/package/addon.lua | 178 | ||||
| -rw-r--r-- | xmake/core/project/addons.lua | 36 | ||||
| -rw-r--r-- | xmake/core/project/project.lua | 24 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/package/addon.lua | 5 | ||||
| -rw-r--r-- | xmake/core/sandbox/modules/import/core/project/addons.lua | 11 | ||||
| -rw-r--r-- | xmake/modules/private/action/require/impl/actions/install.lua | 4 | ||||
| -rw-r--r-- | xmake/modules/private/addons/main.lua | 56 |
9 files changed, 293 insertions, 76 deletions
diff --git a/xmake/actions/addon/main.lua b/xmake/actions/addon/main.lua index 7af106c05..6a4bd06ca 100644 --- a/xmake/actions/addon/main.lua +++ b/xmake/actions/addon/main.lua @@ -226,10 +226,36 @@ end -- remove the given installed addons function _remove() - local names = assert(option.get("addons"), "please specify the addon name to be removed!") + local names = option.get("addons") + + -- remove all the installed addons? e.g. xmake addon --remove --all + -- + -- @note we remove them one by one, so we can also remove the symlinks safely, + -- and the dependencies between them do not matter, they are all removed + -- + if option.get("all") then + names = table.keys(addon.addons()) + if #names == 0 then + cprint("${color.warning}no installed addons!") + return + end + for _, name in ipairs(names) do + addon.remove(name, {force = true}) + cprint("${color.success}remove ${bright}%s${clear} ok!", name) + end + return + end + + assert(names, "please specify the addon name to be removed!") _xrepo("remove", names) end +-- upgrade the addons which the current project declares in its `xmake-addons.lua` +function _upgrade() + import("private.addons", {alias = "install_addons"}) + install_addons(os.projectdir(), {upgrade = true}) +end + -- search the addons from the repositories function _search() local patterns = assert(option.get("addons"), "please specify the addon name pattern to be searched!") @@ -243,8 +269,12 @@ end -- function _collect_installed_addons() local entries = {} - for name, addoninfo in pairs(addon.rescan()) do + for name, addoninfo in pairs(addon.addons()) do + -- an addon can be installed with several versions, we show the active one, + -- the projects can lock the other ones, @see core/project/addons.lua + local versions = addon.versions(name) table.insert(entries, {name = name, version = addoninfo.version, + versions = #versions > 1 and versions or nil, description = addoninfo.description, payloads = addoninfo.payloads}) end table.sort(entries, function (a, b) return a.name < b.name end) @@ -285,7 +315,11 @@ function _list() if #installed > 0 then for _, entry in ipairs(installed) do exclude[entry.name] = true - _print_addon(entry, string.format(" ${dim}(%s)${clear}", table.concat(entry.payloads, ", "))) + local suffix = string.format(" ${dim}(%s)${clear}", table.concat(entry.payloads, ", ")) + if entry.versions then + suffix = suffix .. string.format(" ${dim}[installed: %s]${clear}", table.concat(entry.versions, ", ")) + end + _print_addon(entry, suffix) end else print(" (none)") @@ -308,22 +342,16 @@ function _list() end end --- clear all installed addons -function _clear() - addon.clear() - cprint("${color.success}clear all installed addons ok!") -end - function main() if option.get("install") then _install() elseif option.get("remove") then _remove() + elseif option.get("upgrade") then + _upgrade() elseif option.get("list") then _list() elseif option.get("search") then _search() - elseif option.get("clear") then - _clear() end end diff --git a/xmake/actions/addon/xmake.lua b/xmake/actions/addon/xmake.lua index 3c814cc6d..a0266aa4f 100644 --- a/xmake/actions/addon/xmake.lua +++ b/xmake/actions/addon/xmake.lua @@ -29,7 +29,8 @@ task("addon") {'r', "remove", "k", nil, "Remove the given installed addons."}, {'s', "search", "k", nil, "Search the addons from the repositories."}, {'l', "list", "k", nil, "List all installed addons."}, - {'c', "clear", "k", nil, "Clear all installed addons."}, + {'u', "upgrade", "k", nil, "Upgrade the addons which the current project declares."}, + {nil, "all", "k", nil, "Remove all installed addons, e.g. xmake addon --remove --all"}, {'f', "force", "k", nil, "Force to remove the addons, even if they are depended on by the others."}, {nil, "addons", "vs", nil, "The addon paths, urls or names.", "e.g.", @@ -40,6 +41,8 @@ task("addon") " $ xmake addon --install xmake-repo@serial-monitor", " $ xmake addon --install serial-monitor", " $ xmake addon --remove serial-monitor", + " $ xmake addon --remove --all", + " $ xmake addon --upgrade", " $ xmake addon --search serial", " $ xmake addon --list"} } diff --git a/xmake/core/package/addon.lua b/xmake/core/package/addon.lua index 6d56205fd..33eb22e03 100644 --- a/xmake/core/package/addon.lua +++ b/xmake/core/package/addon.lua @@ -70,15 +70,16 @@ function addon._registryfile() return path.join(addon.installdir(), "addons.conf") end --- save the given addons to the registry file -function addon._save(addons) - addon._ADDONS = addons +-- save the given registry to the registry file +function addon._save(registry) + addon._REGISTRY = registry + addon._ADDONS = nil 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 + if next(registry) == nil and not os.isfile(registryfile) then return end - local ok, errors = io.save(registryfile, addons) + local ok, errors = io.save(registryfile, registry) if not ok then utils.warning(errors) end @@ -182,10 +183,15 @@ end function addon._parents(name) local dirname = addon.dirname(name) local parents - for otherdirname, addoninfo in pairs(addon.addons()) do - if otherdirname ~= dirname and table.contains(addoninfo.deps or {}, dirname) then - parents = parents or {} - table.insert(parents, otherdirname) + for otherdirname, entry in pairs(addon._registry()) do + if otherdirname ~= dirname then + for _, addoninfo in pairs(entry.versions or {}) do + if table.contains(addoninfo.deps or {}, dirname) then + parents = parents or {} + table.insert(parents, otherdirname) + break + end + end end end if parents then @@ -194,14 +200,26 @@ function addon._parents(name) return parents end --- unregister the given addon -function addon._unregister(name) +-- unregister the given addon or only one of its versions +function addon._unregister(name, version) local dirname = addon.dirname(name) - local addons = addon.addons() - if addons[dirname] then - addons[dirname] = nil - addon._save(addons) + local registry = addon._registry() + local entry = registry[dirname] + if entry == nil then + return end + if version then + entry.versions[version] = nil + if entry.active == version then + entry.active = next(entry.versions) + end + if next(entry.versions) == nil then + registry[dirname] = nil + end + else + registry[dirname] = nil + end + addon._save(registry) end -- get the apis of the addon manifest @@ -400,7 +418,64 @@ function addon.resolve_reference(reference, sep, kind, opt) return {dir = payloaddir, name = name, addon = addonname} end --- get all installed addons +-- get the registry of the installed addons +-- +-- an addon can be installed with several versions at the same time, e.g. the projects +-- may lock the different versions of it, so we save all of them +-- +-- @return the registry, e.g. {["esp32"] = {active = "1.0.3", versions = {["1.0.3"] = {...}}}} +-- +function addon._registry(opt) + local registry = addon._REGISTRY + if opt and opt.force then + registry = nil + end + if registry == nil then + registry = {} + local registryfile = addon._registryfile() + if os.isfile(registryfile) then + registry = io.load(registryfile) or {} + end + -- migrate the old registry, it only saved one version for each addon + for dirname, addoninfo in pairs(registry) do + if addoninfo.versions == nil then + registry[dirname] = {active = addoninfo.version, + versions = {[addoninfo.version] = addoninfo}} + end + end + addon._REGISTRY = registry + addon._ADDONS = nil + end + return registry +end + +-- pin the active version of the given addon for this process +-- +-- @note a project locks the versions of its addons, so we need to activate them +-- when we load it, @see core/project/addons.lua +-- +function addon.pin(name, version) + local pinned = addon._PINNED + if pinned == nil then + pinned = {} + addon._PINNED = pinned + end + pinned[addon.dirname(name)] = version + addon._ADDONS = nil +end + +-- get all the installed versions of the given addon, e.g. {"1.0.2", "1.0.3"} +function addon.versions(name) + local versions = {} + local addoninfo = addon._registry()[addon.dirname(name)] + for version, _ in pairs(addoninfo and addoninfo.versions or {}) do + table.insert(versions, version) + end + table.sort(versions) + return versions +end + +-- get all installed addons, only the active version of each addon -- -- @param opt the options, e.g. {force = true}, we need it to reload the registry -- if the addons have been installed by another process @@ -408,15 +483,20 @@ end -- @return the addons table, e.g. {["hello-world"] = {version = "latest", payloads = {"plugins"}}} -- function addon.addons(opt) - local addons = addon._ADDONS if opt and opt.force then - addons = nil + addon._registry({force = true}) end + local addons = addon._ADDONS if addons == nil then addons = {} - local registryfile = addon._registryfile() - if os.isfile(registryfile) then - addons = io.load(registryfile) or {} + local pinned = addon._PINNED or {} + for dirname, addoninfo in pairs(addon._registry()) do + -- the project may lock another version of it, @see addon.pin + local version = pinned[dirname] or addoninfo.active + local versioninfo = addoninfo.versions and addoninfo.versions[version] + if versioninfo then + addons[dirname] = versioninfo + end end addon._ADDONS = addons end @@ -592,6 +672,8 @@ function addon.register(name, version, opt) name = name ~= dirname and name or nil, description = opt.description, deps = opt.deps, + -- where it comes from, e.g. {url = ..., commit = ..., branch = ...} + repo = opt.repo, -- the deps which the addon itself declares in its manifest, they are -- recorded whenever this addon has one, so that the repositories can -- check that the manifest and the package recipe are kept in sync @@ -608,9 +690,17 @@ function addon.register(name, version, opt) return false, errors end - local addons = addon.addons() - addons[dirname] = addoninfo - addon._save(addons) + -- we can install several versions of an addon at the same time, + -- and the version which we install now is always the active one + local registry = addon._registry() + local entry = registry[dirname] + if entry == nil or entry.versions == nil then + entry = {versions = {}} + registry[dirname] = entry + end + entry.versions[version] = addoninfo + entry.active = version + addon._save(registry) return true end @@ -662,6 +752,7 @@ end -- e.g. the addons which a project declares, @see core/project/project.lua -- function addon.reload() + addon._REGISTRY = nil addon._ADDONS = nil addon._MANIFESTS = nil addon._GLOBALMODULES = nil @@ -672,16 +763,17 @@ end -- it's only used to repair the registry file, e.g. the user removed some addon directories manually -- function addon.rescan() - local oldaddons = addon.addons() - local addons = {} + local oldregistry = addon._registry() + local registry = {} for _, versiondir in ipairs(os.dirs(path.join(addon.installdir(), "*", "*"))) do local payloads = addon.payloads_of(versiondir) if #payloads > 0 then local dirname = path.filename(path.directory(versiondir)) local version = path.filename(versiondir) - local oldaddoninfo = oldaddons[dirname] + local oldentry = oldregistry[dirname] + local oldaddoninfo = oldentry and oldentry.versions and oldentry.versions[version] local description, deps - if oldaddoninfo and oldaddoninfo.version == version then + if oldaddoninfo then -- we need to keep them, we cannot get them from the installed payloads description = oldaddoninfo.description deps = oldaddoninfo.deps @@ -696,24 +788,22 @@ function addon.rescan() description = manifest.description or description end end - addons[dirname] = {version = version, name = name or (oldaddoninfo and oldaddoninfo.name), - description = description, deps = deps, payloads = payloads, - plugins = addon._plugins_of(versiondir), templates = addon._templates_of(versiondir)} + local entry = registry[dirname] + if entry == nil then + entry = {versions = {}} + registry[dirname] = entry + end + entry.versions[version] = {version = version, name = name or (oldaddoninfo and oldaddoninfo.name), + description = description, deps = deps, payloads = payloads, + plugins = addon._plugins_of(versiondir), templates = addon._templates_of(versiondir)} + -- we keep the active version if it's still installed, otherwise we use the last one + if entry.active == nil or (oldentry and oldentry.active == version) then + entry.active = version + end 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 = {} - addon._MANIFESTS = nil - addon._GLOBALMODULES = nil + addon._save(registry) + return addon.addons() end -- return module diff --git a/xmake/core/project/addons.lua b/xmake/core/project/addons.lua index 994d52ec0..21fb9fbcf 100644 --- a/xmake/core/project/addons.lua +++ b/xmake/core/project/addons.lua @@ -26,7 +26,8 @@ local os = require("base/os") local io = require("base/io") local path = require("base/path") local table = require("base/table") -local addon = require("package/addon") +local semver = require("base/semver") +local addon = require("package/addon") -- the file which declares the addons of a project, e.g. <projectdir>/xmake-addons.lua -- @@ -36,6 +37,7 @@ local addon = require("package/addon") -- e.g. -- add_addons("esp32-devel 1.0.x") -- add_addons("serial-tools", {optional = true}) +-- add_repositories("myrepo [email protected]:me/myrepo.git") -- function addons.filename() return "xmake-addons.lua" @@ -63,6 +65,8 @@ function addons.apis() return { values = { "add_addons" + -- the repositories which provide them, e.g. add_repositories("myrepo [email protected]:me/myrepo.git") + , "add_repositories" } } end @@ -116,7 +120,8 @@ function addons.load(projectdir) end local addonsinfo = {addons = table.wrap(rootinfo:get("addons")), - addons_extra = rootinfo:extraconf("addons")} + addons_extra = rootinfo:extraconf("addons"), + repositories = table.wrap(rootinfo:get("repositories"))} -- check the declared addons local declared = {} @@ -147,6 +152,29 @@ function addons.load(projectdir) return addonsinfo end +-- split the given declaration, e.g. "esp32-devel 1.0.x" -> "esp32-devel", "1.0.x" +function addons.requirename(requirestr) + local splitinfo = requirestr:split("%s") + return splitinfo[1], splitinfo[2] +end + +-- is the locked version still valid for the given declaration? +-- +-- @note the declaration is authoritative, the lock only pins a version inside it, +-- so we need to resolve it again if the user has changed the declared version +-- +function addons.locked_valid(requirestr, lockinfo) + if not lockinfo or not lockinfo.version then + return false + end + local _, version = addons.requirename(requirestr) + if version then + -- @note we can only compare the semantic versions, e.g. the local addons are always `latest` + return semver.parse(lockinfo.version) ~= nil and semver.satisfies(lockinfo.version, version) + end + return true +end + -- get the locked addons, e.g. {["esp32-devel"] = {version = "1.0.3"}} function addons.locked(projectdir) local lockfile = addons.lockfile(projectdir) @@ -171,9 +199,9 @@ function addons.satisfied(addonsinfo, projectdir) end local installed = addon.addons() for _, requirestr in ipairs(addonsinfo.addons) do - local name = requirestr:split("%s")[1] + local name = addons.requirename(requirestr) local lockinfo = locked[name] - if not lockinfo then + if not addons.locked_valid(requirestr, lockinfo) then return false end local addoninfo = installed[addon.dirname(name)] diff --git a/xmake/core/project/project.lua b/xmake/core/project/project.lua index db269eda9..924ac8f9e 100644 --- a/xmake/core/project/project.lua +++ b/xmake/core/project/project.lua @@ -244,18 +244,37 @@ function project._install_addons() return project._ADDONS_OK, project._ADDONS_ERRORS end +-- activate the addon versions which this project locks +-- +-- @note an addon can be installed with several versions at the same time, the other +-- projects may lock the other versions of it, @see core/package/addon.lua +-- +function project._pin_addons() + for name, lockinfo in pairs(addons.locked() or {}) do + if lockinfo.version then + addon.pin(name, lockinfo.version) + end + end +end + -- do install the addons which this project declares function project._do_install_addons() if os.getenv("XMAKE_SKIP_ADDONS") then return true end - -- this project declares nothing? or they have been installed already + -- this project declares nothing? local addonsinfo, errors = addons.load() if errors then return false, errors end - if not addonsinfo or #addonsinfo.addons == 0 or addons.satisfied(addonsinfo) then + if not addonsinfo or #addonsinfo.addons == 0 then + return true + end + + -- they have been installed already? + project._pin_addons() + if addons.satisfied(addonsinfo) then return true end @@ -287,6 +306,7 @@ function project._do_install_addons() -- we have loaded the registry and its caches before installing them, so we need to reload it addon.reload() + project._pin_addons() rule.clear() task.clear() return true diff --git a/xmake/core/sandbox/modules/import/core/package/addon.lua b/xmake/core/sandbox/modules/import/core/package/addon.lua index b297347c6..89cd60866 100644 --- a/xmake/core/sandbox/modules/import/core/package/addon.lua +++ b/xmake/core/sandbox/modules/import/core/package/addon.lua @@ -35,9 +35,12 @@ sandbox_core_package_addon.payloadinfos = addon.payloadinfos sandbox_core_package_addon.payloads_of = addon.payloads_of sandbox_core_package_addon.payloadroot = addon.payloadroot sandbox_core_package_addon.addons = addon.addons +sandbox_core_package_addon.versions = addon.versions +sandbox_core_package_addon._registry = addon._registry +sandbox_core_package_addon._registryfile = addon._registryfile +sandbox_core_package_addon.pin = addon.pin sandbox_core_package_addon.addondir = addon.addondir sandbox_core_package_addon.rescan = addon.rescan -sandbox_core_package_addon.clear = addon.clear -- get the manifest of the given addon directory, e.g. <sourcedir>/addon.lua -- diff --git a/xmake/core/sandbox/modules/import/core/project/addons.lua b/xmake/core/sandbox/modules/import/core/project/addons.lua index 9e866bd06..0c3ad438c 100644 --- a/xmake/core/sandbox/modules/import/core/project/addons.lua +++ b/xmake/core/sandbox/modules/import/core/project/addons.lua @@ -26,10 +26,13 @@ local addons = require("project/addons") local raise = require("sandbox/modules/raise") -- inherit some builtin interfaces -sandbox_core_project_addons.file = addons.file -sandbox_core_project_addons.lockfile = addons.lockfile -sandbox_core_project_addons.locked = addons.locked -sandbox_core_project_addons.satisfied = addons.satisfied +sandbox_core_project_addons.file = addons.file +sandbox_core_project_addons.filename = addons.filename +sandbox_core_project_addons.lockfile = addons.lockfile +sandbox_core_project_addons.locked = addons.locked +sandbox_core_project_addons.locked_valid = addons.locked_valid +sandbox_core_project_addons.requirename = addons.requirename +sandbox_core_project_addons.satisfied = addons.satisfied -- load the declared addons of the given project directory -- diff --git a/xmake/modules/private/action/require/impl/actions/install.lua b/xmake/modules/private/action/require/impl/actions/install.lua index 65750c1bc..1a2e389b3 100644 --- a/xmake/modules/private/action/require/impl/actions/install.lua +++ b/xmake/modules/private/action/require/impl/actions/install.lua @@ -318,8 +318,12 @@ function _register_addon(package) end end + -- we also record where it comes from, so that the projects can lock it, + -- @see xmake/modules/private/addons/main.lua + local repo = package:repo() addon.register(package:name(), package:version_str() or "latest", {description = description, deps = deps, + repo = repo and {url = repo:url(), commit = repo:commit(), branch = repo:branch()} or nil, manifest_deps = manifest and manifest.deps or nil, globalmodules = manifest and #manifest.globalmodules > 0 and manifest.globalmodules or nil}) end diff --git a/xmake/modules/private/addons/main.lua b/xmake/modules/private/addons/main.lua index 43a623e20..1cdc2191d 100644 --- a/xmake/modules/private/addons/main.lua +++ b/xmake/modules/private/addons/main.lua @@ -28,15 +28,15 @@ local LOCKVERSION = "1.0" -- get the requires of the declared addons -- --- @note we always install the locked versions, the declared versions are only used --- to resolve them when there is no lock yet, @see _lock_addons +-- @note we install the locked versions, but the declaration is authoritative, so we +-- resolve them again if the user has changed it or upgrades them -- function _get_requires(addonsinfo, locked) local requires = {} for _, requirestr in ipairs(addonsinfo.addons) do - local name = requirestr:split("%s")[1] + local name = addons.requirename(requirestr) local lockinfo = locked and locked[name] - if lockinfo and lockinfo.version then + if addons.locked_valid(requirestr, lockinfo) then requirestr = name .. " " .. lockinfo.version end table.insert(requires, requirestr) @@ -51,13 +51,20 @@ end -- function _lock_addons(projectdir, addonsinfo) local lockinfo = {} + local locked = addons.locked(projectdir) or {} -- @note we need to reload the registry, they have been installed by another process local installed = addon.addons({force = true}) for _, requirestr in ipairs(addonsinfo.addons) do - local name = requirestr:split("%s")[1] + local name = addons.requirename(requirestr) local addoninfo = installed[addon.dirname(name)] if addoninfo then - lockinfo[name] = {version = addoninfo.version} + local oldversion = locked[name] and locked[name].version + if oldversion and oldversion ~= addoninfo.version then + cprint("${color.success}upgrade ${bright}%s${clear}: %s -> %s", name, oldversion, addoninfo.version) + end + -- we lock the repository too, so that the other users get it from the same source, + -- @see xmake/modules/private/action/require/impl/lock_packages.lua + lockinfo[name] = {version = addoninfo.version, repo = addoninfo.repo} end end lockinfo.__meta__ = {version = LOCKVERSION} @@ -69,23 +76,54 @@ end -- @note we are called from a sub-process, the project cannot be loaded until its -- addons are installed, @see core/project/project.lua -- -function main(projectdir) +function main(projectdir, opt) + opt = opt or {} projectdir = projectdir or os.projectdir() local addonsinfo = addons.load(projectdir) if not addonsinfo or #addonsinfo.addons == 0 then return end + -- upgrade them? we need to resolve the declared versions again + local locked = not opt.upgrade and addons.locked(projectdir) or nil + -- install them with xrepo, it installs the packages in its own working directory, -- so we need not a project here local argv = {"lua", "private.xrepo", "install", "--addon"} + + -- this project declares its own repositories? we pass them to xrepo, + -- they are only used by this installation, we do not register them globally + local rcfile + if #addonsinfo.repositories > 0 then + rcfile = os.tmpfile() .. ".lua" + local file = io.open(rcfile, "w") + for _, repo in ipairs(addonsinfo.repositories) do + file:print("add_repositories(%q)", repo) + end + file:close() + table.insert(argv, "--includes=" .. rcfile) + end + for _, name in ipairs({"yes", "verbose", "diagnosis"}) do if option.get(name) then table.insert(argv, "--" .. name) end end - table.join2(argv, _get_requires(addonsinfo, addons.locked(projectdir))) - os.execv(os.programfile(), argv) + table.join2(argv, _get_requires(addonsinfo, locked)) + try + { + function () + os.execv(os.programfile(), argv) + end, + finally + { + function () + if rcfile then + os.tryrm(rcfile) + end + end + } + } -- and lock them, so that the other users get the same versions _lock_addons(projectdir, addonsinfo) |
