summaryrefslogtreecommitdiff
path: root/xmake/modules
diff options
context:
space:
mode:
authorruki <[email protected]>2024-11-29 23:47:36 +0800
committerGitHub <[email protected]>2024-11-29 23:47:36 +0800
commit2f1213a70b2ea1be2097c7f2bd7b4fc85a861b80 (patch)
tree8e80ee15aac85d1e6714a288553fb08e94384f58 /xmake/modules
parent7c6fd15fb59da11c1d45e6c4fa33a96dfd801889 (diff)
parentd9220fbb1cad4e025094c52c3ee5deaf6dbb9d8f (diff)
Merge pull request #5901 from xmake-io/deps
Improve to package deps
Diffstat (limited to 'xmake/modules')
-rw-r--r--xmake/modules/private/action/require/impl/package.lua215
1 files changed, 201 insertions, 14 deletions
diff --git a/xmake/modules/private/action/require/impl/package.lua b/xmake/modules/private/action/require/impl/package.lua
index 5c0d1bf3d..19b05f51d 100644
--- a/xmake/modules/private/action/require/impl/package.lua
+++ b/xmake/modules/private/action/require/impl/package.lua
@@ -139,7 +139,8 @@ function _parse_require(require_str)
end
-- load require info
-function _load_require(require_str, requires_extra, parentinfo)
+function _load_require(require_str, requires_extra, opt)
+ opt = opt or {}
-- parse require
local packagename, version, reponame = _parse_require(require_str)
@@ -215,8 +216,25 @@ function _load_require(require_str, requires_extra, parentinfo)
require_extra.system = false
end
+ -- resolve require info
+ local resolvedinfo = opt.resolvedinfo
+ local requirepath = opt.requirepath
+ if requirepath then
+ requirepath = requirepath .. "." .. packagename
+ else
+ requirepath = packagename
+ end
+ resolvedinfo = resolvedinfo and resolvedinfo[requirepath]
+ if resolvedinfo then
+ -- resolve the conflict package version
+ if resolvedinfo.version then
+ version = resolvedinfo.version
+ end
+ end
+
-- init required item
local required = {}
+ local parentinfo = opt.parentinfo
parentinfo = parentinfo or {}
required.packagename = packagename
required.requireinfo =
@@ -1011,6 +1029,7 @@ function _load_package(packagename, requireinfo, opt)
local version, source = _select_package_version(package, requireinfo, locked_requireinfo)
if version then
package:version_set(version, source)
+ package:data_set("__locked_requireinfo", locked_requireinfo)
end
-- get package key
@@ -1120,7 +1139,7 @@ function _load_packages(requires, opt)
-- load package
local requireinfo = requireitem.info
local requirepath = opt.requirepath and (opt.requirepath .. "." .. requireitem.name) or requireitem.name
- local package = _load_package(requireitem.name, requireinfo, table.join(opt, {requirepath = requirepath}))
+ local package = _load_package(requireitem.name, requireinfo, table.join(opt, {requirepath = requirepath}))
-- maybe package not found and optional
if package then
@@ -1134,6 +1153,7 @@ function _load_packages(requires, opt)
requires_extra = package:extraconf("deps") or {},
parentinfo = requireinfo,
nodeps = opt.nodeps,
+ resolvedinfo = opt.resolvedinfo,
system = false})
for _, dep in ipairs(plaindeps) do
dep:parents_add(package)
@@ -1176,7 +1196,148 @@ function _get_parents_str(package)
end
end
--- check dependencies conflicts
+-- get require paths
+function _get_requirepaths(package)
+ local requirepaths = {}
+ local parents = package:parents()
+ if parents and #parents > 0 then
+ for _, parent in ipairs(parents) do
+ for _, requirepath in ipairs(_get_requirepaths(parent)) do
+ table.insert(requirepaths, requirepath .. "." .. package:name())
+ end
+ end
+ else
+ table.insert(requirepaths, package:name())
+ end
+ return requirepaths
+end
+
+-- get configs key for compatibility
+function _get_package_compatkey(dep)
+ local key = dep:plat() .. "/" .. dep:arch() .. "/" .. (dep:kind() or "")
+ if dep:is_system() then
+ key = key .. "/system"
+ end
+ local configs = dep:requireinfo().configs
+ if configs then
+ local configs_order = {}
+ for k, v in pairs(configs) do
+ if type(v) == "table" then
+ v = string.serialize(v, {strip = true, indent = false, orderkeys = true})
+ end
+ table.insert(configs_order, k .. "=" .. tostring(v))
+ end
+ table.sort(configs_order)
+ key = key .. ":" .. string.serialize(configs_order, true)
+ end
+ return key
+end
+
+-- get package compatibility info
+function _get_package_compatinfo(package)
+ local compatinfo = {name = package:name()}
+ if package:data("__locked_requireinfo") then
+ compatinfo.locked = true
+ compatinfo.versions = hashset.from(table.wrap(package:version_str()))
+ return compatinfo
+ end
+
+ -- get compatible version range
+ local requireinfo = package:requireinfo()
+ local require_version = requireinfo.version
+ if require_version then
+ compatinfo.require_version = require_version
+ if semver.is_valid(require_version) or semver.is_valid_range(require_version) then
+ local versions = hashset.new()
+ for _, version in ipairs(package:versions()) do
+ if semver.satisfies(version, require_version) then
+ versions:insert(version)
+ end
+ end
+ compatinfo.versions = versions
+ elseif require_version == "latest" then
+ compatinfo.versions = hashset.from(table.wrap(package:versions()))
+ else
+ compatinfo.versions = hashset.from(table.wrap(require_version))
+ end
+ else
+ compatinfo.versions = hashset.from(table.wrap(package:versions()))
+ end
+ return compatinfo
+end
+
+-- check and resolve package conflicts
+function _check_and_resolve_package_depconflicts_impl(package, name, deps, resolvedinfo)
+
+ -- check version compatibility
+ local versions
+ for _, dep in ipairs(deps) do
+ local compatinfo = _get_package_compatinfo(dep)
+ assert(compatinfo.versions)
+
+ if versions then
+ for version in versions:items() do
+ if not compatinfo.versions:has(version) then
+ versions:remove(version)
+ end
+ end
+ else
+ versions = compatinfo.versions
+ end
+ end
+
+ if not versions or versions:empty() then
+ print("package(%s): add_deps(%s, ...)", package:name(), name)
+ for idx, dep in ipairs(deps) do
+ cprint(" ${color.warning}->${clear} %s %s ${dim}%s",
+ dep:displayname(), dep:version_str() or "", get_configs_str(dep))
+ end
+ print("we can use add_requireconfs(\"**.%s\", {override = true, configs = {version = \"x.x.x\"}}) to override version.", name)
+ raise("package(%s): conflict version dependencies!", name)
+ end
+
+ -- check configs compatibility
+ local prevkey
+ local configs_conflict = false
+ for _, dep in ipairs(deps) do
+ local key = _get_package_compatkey(dep)
+ if prevkey then
+ if prevkey ~= key then
+ configs_conflict = true
+ end
+ else
+ prevkey = key
+ end
+ end
+ if configs_conflict then
+ print("package(%s): add_deps(%s, ...)", package:name(), name)
+ for idx, dep in ipairs(deps) do
+ cprint(" ${color.warning}->${clear} %s %s ${dim}%s",
+ dep:displayname(), dep:version_str() or "", get_configs_str(dep))
+ end
+ print("we can use add_requireconfs(\"**.%s\", {override = true, configs = {}}) to override configs.", name)
+ raise("package(%s): conflict configs dependencies!", name)
+ end
+
+ -- resolve compatible version for all deps
+ if versions and not versions:empty() then
+ local version_best
+ for version in versions:items() do
+ if version_best == nil or semver.compare(version, version_best) > 0 then
+ version_best = version
+ end
+ end
+ if version_best then
+ for _, dep in ipairs(deps) do
+ for _, requirepath in ipairs(_get_requirepaths(dep)) do
+ resolvedinfo[requirepath] = {version = version_best}
+ end
+ end
+ end
+ end
+end
+
+-- check and resolve dependencies conflicts
--
-- It exists conflict for dependent packages for each root packages? resolve it first
-- e.g.
@@ -1190,15 +1351,19 @@ end
-- Of course, conflicts caused by `add_packages("foo", "ddd")`
-- cannot be detected at present and can only be resolved by the user
--
-function _check_package_depconflicts(package)
- local packagekeys = {}
+function _check_and_resolve_package_depconflicts(package, resolvedinfo)
+ local some_packagedeps = {}
for _, dep in ipairs(package:librarydeps()) do
- local key = _get_packagekey(dep:name(), dep:requireinfo())
- local prevkey = packagekeys[dep:name()]
- if prevkey then
- assert(key == prevkey, "package(%s): conflict dependencies with package(%s) in %s!", key, prevkey, package:name())
- else
- packagekeys[dep:name()] = key
+ local deps = some_packagedeps[dep:name()]
+ if deps == nil then
+ deps = {}
+ some_packagedeps[dep:name()] = deps
+ end
+ table.insert(deps, dep)
+ end
+ for name, deps in pairs(some_packagedeps) do
+ if #deps > 1 then
+ _check_and_resolve_package_depconflicts_impl(package, name, deps, resolvedinfo)
end
end
end
@@ -1434,7 +1599,7 @@ function load_requires(requires, requires_extra, opt)
opt = opt or {}
local requireitems = {}
for _, require_str in ipairs(requires) do
- local packagename, requireinfo = _load_require(require_str, requires_extra, opt.parentinfo)
+ local packagename, requireinfo = _load_require(require_str, requires_extra, opt)
table.insert(requireitems, {name = packagename, info = requireinfo})
end
return requireitems
@@ -1445,9 +1610,10 @@ function load_packages(requires, opt)
opt = opt or {}
local unique = {}
local packages = {}
- for _, package in ipairs((_load_packages(requires, opt))) do
+ local resolvedinfo = {}
+ for _, package in ipairs((_load_packages(requires, table.clone(opt)))) do
if package:is_toplevel() then
- _check_package_depconflicts(package)
+ _check_and_resolve_package_depconflicts(package, resolvedinfo)
end
local key = _get_packagekey(package:name(), package:requireinfo())
if not unique[key] then
@@ -1455,6 +1621,27 @@ function load_packages(requires, opt)
unique[key] = true
end
end
+
+ -- we need to reload packages with new resolved deps if there are some dep conflicts
+ if not table.empty(resolvedinfo) then
+ for _, package in ipairs(packages) do
+ local installdir = package:installdir({readonly = true})
+ if os.isdir(installdir) and os.emptydir(installdir) then
+ os.tryrm(installdir, {emptydirs = true})
+ end
+ end
+ unique = {}
+ packages = {}
+ _memcache():clear()
+ opt = table.join(opt, {resolvedinfo = resolvedinfo})
+ for _, package in ipairs((_load_packages(requires, opt))) do
+ local key = _get_packagekey(package:name(), package:requireinfo())
+ if not unique[key] then
+ table.insert(packages, package)
+ unique[key] = true
+ end
+ end
+ end
return packages
end