diff options
| author | ruki <[email protected]> | 2024-11-29 23:47:36 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-11-29 23:47:36 +0800 |
| commit | 2f1213a70b2ea1be2097c7f2bd7b4fc85a861b80 (patch) | |
| tree | 8e80ee15aac85d1e6714a288553fb08e94384f58 | |
| parent | 7c6fd15fb59da11c1d45e6c4fa33a96dfd801889 (diff) | |
| parent | d9220fbb1cad4e025094c52c3ee5deaf6dbb9d8f (diff) | |
Merge pull request #5901 from xmake-io/deps
Improve to package deps
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 |
