summaryrefslogtreecommitdiff
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
parent7c6fd15fb59da11c1d45e6c4fa33a96dfd801889 (diff)
parentd9220fbb1cad4e025094c52c3ee5deaf6dbb9d8f (diff)
Merge pull request #5901 from xmake-io/deps
Improve to package deps
-rwxr-xr-xtests/projects/package/compatibility/deps_with_version/src/main.c6
-rw-r--r--tests/projects/package/compatibility/deps_with_version/test.lua10
-rw-r--r--tests/projects/package/compatibility/deps_with_version/xmake.lua31
-rwxr-xr-xtests/projects/package/depconfigs/src/main.c3
-rw-r--r--xmake/modules/private/action/require/impl/package.lua215
5 files changed, 249 insertions, 16 deletions
diff --git a/tests/projects/package/compatibility/deps_with_version/src/main.c b/tests/projects/package/compatibility/deps_with_version/src/main.c
new file mode 100755
index 000000000..113930e1a
--- /dev/null
+++ b/tests/projects/package/compatibility/deps_with_version/src/main.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(int argc, char** argv) {
+ printf("hello world!\n");
+ return 0;
+}
diff --git a/tests/projects/package/compatibility/deps_with_version/test.lua b/tests/projects/package/compatibility/deps_with_version/test.lua
new file mode 100644
index 000000000..a6690754b
--- /dev/null
+++ b/tests/projects/package/compatibility/deps_with_version/test.lua
@@ -0,0 +1,10 @@
+function main(t)
+ -- freebsd ci is slower
+ if is_host("bsd") then
+ return
+ end
+ -- only for x86/x64, because it will take too long time on ci with arm/mips
+ if os.subarch():startswith("x") or os.subarch() == "i386" then
+ t:build()
+ end
+end
diff --git a/tests/projects/package/compatibility/deps_with_version/xmake.lua b/tests/projects/package/compatibility/deps_with_version/xmake.lua
new file mode 100644
index 000000000..28adf5e7e
--- /dev/null
+++ b/tests/projects/package/compatibility/deps_with_version/xmake.lua
@@ -0,0 +1,31 @@
+package("foo")
+ add_deps("zlib >=1.2.13")
+ set_policy("package.install_locally", true)
+ on_install(function () end)
+package_end()
+
+package("bar")
+ add_deps("zlib 1.2.x")
+ set_policy("package.install_locally", true)
+-- add_deps("libpng dev")
+ on_install(function () end)
+package_end()
+
+package("zoo")
+ add_deps("libpng master")
+ set_policy("package.install_locally", true)
+ on_install(function () end)
+package_end()
+
+package("test")
+ add_deps("foo", "bar", "zoo")
+ set_policy("package.install_locally", true)
+ on_install(function () end)
+package_end()
+
+add_requires("test")
+
+target("test")
+ set_kind("binary")
+ add_files("src/*.c")
+ add_packages("test")
diff --git a/tests/projects/package/depconfigs/src/main.c b/tests/projects/package/depconfigs/src/main.c
index 9ae580511..113930e1a 100755
--- a/tests/projects/package/depconfigs/src/main.c
+++ b/tests/projects/package/depconfigs/src/main.c
@@ -1,7 +1,6 @@
#include <stdio.h>
-int main(int argc, char** argv)
-{
+int main(int argc, char** argv) {
printf("hello world!\n");
return 0;
}
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