diff options
| author | ruki <[email protected]> | 2025-07-26 22:34:34 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2025-07-26 22:34:34 +0800 |
| commit | 16fb087ad29883bd2ea4bc082be2cda621b578c1 (patch) | |
| tree | 92e1dcd36afffca19a0283feb1820bafa5dd40a4 | |
| parent | 990cde63dbdf3002940698fd59dde5c236731f0f (diff) | |
| parent | 82600499728ebde05c988a10a3970123bf438e35 (diff) | |
Merge pull request #6651 from xmake-io/deplibs
improve depfiles
| -rw-r--r-- | xmake/modules/target/action/install/main.lua | 32 | ||||
| -rw-r--r-- | xmake/modules/target/action/uninstall/main.lua | 32 | ||||
| -rw-r--r-- | xmake/modules/utils/binary/deplibs.lua | 243 | ||||
| -rw-r--r-- | xmake/plugins/pack/batchcmds.lua | 32 |
4 files changed, 237 insertions, 102 deletions
diff --git a/xmake/modules/target/action/install/main.lua b/xmake/modules/target/action/install/main.lua index 753503eaa..d9faa80ad 100644 --- a/xmake/modules/target/action/install/main.lua +++ b/xmake/modules/target/action/install/main.lua @@ -49,26 +49,6 @@ function _get_target_includedir(target, opt) return path.join(opt.installdir, opt.includedir) end --- we need to get all deplibs, e.g. app -> libfoo.so -> libbar.so ... --- @see https://github.com/xmake-io/xmake/issues/5325#issuecomment-2242597732 -function _get_target_package_deplibs(binaryfile, depends, libfiles, opt) - local deplibs = get_depend_libraries(binaryfile, {plat = opt.plat, arch = opt.arch}) - local depends_new = hashset.new() - for _, deplib in ipairs(deplibs) do - local libname = path.filename(deplib) - if not depends:has(libname) then - depends:insert(libname) - depends_new:insert(libname) - end - end - for _, libfile in ipairs(libfiles) do - local libname = path.filename(libfile) - if depends_new:has(libname) then - _get_target_package_deplibs(libfile, depends, libfiles, opt) - end - end -end - function _get_target_package_libfiles(target, opt) if option.get("nopkgs") then return {} @@ -88,9 +68,15 @@ function _get_target_package_libfiles(target, opt) -- we can only reserve used libraries if project.policy("install.strip_packagelibs") then if target:is_binary() or target:is_shared() or opt.binaryfile then - local depends = hashset.new() - _get_target_package_deplibs(opt.binaryfile or target:targetfile(), depends, libfiles, {plat = target:plat(), arch = target:arch()}) - table.remove_if(libfiles, function (_, libfile) return not depends:has(path.filename(libfile)) end) + -- we need to get all deplibs, e.g. app -> libfoo.so -> libbar.so ... + -- @see https://github.com/xmake-io/xmake/issues/5325#issuecomment-2242597732 + local deplibs = get_depend_libraries(opt.binaryfile or target:targetfile(), { + plat = target:plat(), arch = target:arch(), + recursive = true, resolve_path = true, resolve_hint_paths = libfiles}) + if deplibs then + local depends = hashset.from(deplibs) + table.remove_if(libfiles, function (_, libfile) return not depends:has(libfile) end) + end end end return libfiles diff --git a/xmake/modules/target/action/uninstall/main.lua b/xmake/modules/target/action/uninstall/main.lua index e94a29897..2273ff76d 100644 --- a/xmake/modules/target/action/uninstall/main.lua +++ b/xmake/modules/target/action/uninstall/main.lua @@ -49,26 +49,6 @@ function _get_target_includedir(target, opt) return path.join(opt.installdir, opt.includedir) end --- we need to get all deplibs, e.g. app -> libfoo.so -> libbar.so ... --- @see https://github.com/xmake-io/xmake/issues/5325#issuecomment-2242597732 -function _get_target_package_deplibs(binaryfile, depends, libfiles, opt) - local deplibs = get_depend_libraries(binaryfile, {plat = opt.plat, arch = opt.arch}) - local depends_new = hashset.new() - for _, deplib in ipairs(deplibs) do - local libname = path.filename(deplib) - if not depends:has(libname) then - depends:insert(libname) - depends_new:insert(libname) - end - end - for _, libfile in ipairs(libfiles) do - local libname = path.filename(libfile) - if depends_new:has(libname) then - _get_target_package_deplibs(libfile, depends, libfiles, opt) - end - end -end - function _get_target_package_libfiles(target, opt) if option.get("nopkgs") then return {} @@ -88,9 +68,15 @@ function _get_target_package_libfiles(target, opt) -- we can only reserve used libraries if project.policy("install.strip_packagelibs") then if target:is_binary() or target:is_shared() or opt.binaryfile then - local depends = hashset.new() - _get_target_package_deplibs(opt.binaryfile or target:targetfile(), depends, libfiles, {plat = target:plat(), arch = target:arch()}) - table.remove_if(libfiles, function (_, libfile) return not depends:has(path.filename(libfile)) end) + -- we need to get all deplibs, e.g. app -> libfoo.so -> libbar.so ... + -- @see https://github.com/xmake-io/xmake/issues/5325#issuecomment-2242597732 + local deplibs = get_depend_libraries(opt.binaryfile or target:targetfile(), { + plat = target:plat(), arch = target:arch(), + recursive = true, resolve_path = true, resolve_hint_paths = libfiles}) + if deplibs then + local depends = hashset.from(deplibs) + table.remove_if(libfiles, function (_, libfile) return not depends:has(libfile) end) + end end end return libfiles diff --git a/xmake/modules/utils/binary/deplibs.lua b/xmake/modules/utils/binary/deplibs.lua index 03a85c9fe..54b3ed4a2 100644 --- a/xmake/modules/utils/binary/deplibs.lua +++ b/xmake/modules/utils/binary/deplibs.lua @@ -20,10 +20,13 @@ -- imports import("core.base.option") +import("core.base.graph") +import("core.base.hashset") import("core.tool.toolchain") import("lib.detect.find_tool") +import("utils.binary.rpath", {alias = "rpath_utils"}) -function _get_all_depends_by_dumpbin(binaryfile, opt) +function _get_depends_by_dumpbin(binaryfile, opt) local depends local plat = opt.plat or os.host() local arch = opt.arch or os.arch() @@ -37,7 +40,7 @@ function _get_all_depends_by_dumpbin(binaryfile, opt) if result then for _, line in ipairs(result:split("\n")) do line = line:trim() - if line:endswith(".dll") then + if not line:startswith("Dump of file") and line:endswith(".dll") then depends = depends or {} table.insert(depends, line) end @@ -48,7 +51,7 @@ function _get_all_depends_by_dumpbin(binaryfile, opt) return depends end -function _get_all_depends_by_objdump(binaryfile, opt) +function _get_depends_by_objdump(binaryfile, opt) local depends local plat = opt.plat or os.host() local arch = opt.arch or os.arch() @@ -64,27 +67,29 @@ function _get_all_depends_by_objdump(binaryfile, opt) if result then for _, line in ipairs(result:split("\n")) do line = line:trim() - if plat == "windows" or plat == "mingw" then - if line:startswith("DLL Name:") then - local filename = line:split(":")[2]:trim() - if filename:endswith(".dll") then - depends = depends or {} - table.insert(depends, filename) + if not line:endswith(":") then + if plat == "windows" or plat == "mingw" then + if line:startswith("DLL Name:") then + local filename = line:split(":")[2]:trim() + if filename:endswith(".dll") then + depends = depends or {} + table.insert(depends, filename) + end end - end - elseif plat == "macosx" or plat == "iphoneos" or plat == "appletvos" or plat == "watchos" then - local filename = line:match(".-%.dylib") or line:match(".-%.framework") - if filename then - depends = depends or {} - table.insert(depends, filename) - end - else - if line:startswith("NEEDED") then - local filename = line:split("%s+")[2] - if filename and filename:endswith(".so") or filename:find("%.so[%.%d+]+$") then + elseif plat == "macosx" or plat == "iphoneos" or plat == "appletvos" or plat == "watchos" then + local filename = line:match(".-%.dylib") or line:match(".-%.framework") + if filename then depends = depends or {} table.insert(depends, filename) end + else + if line:startswith("NEEDED") then + local filename = line:split("%s+")[2] + if filename and filename:endswith(".so") or filename:find("%.so[%.%d+]+$") then + depends = depends or {} + table.insert(depends, filename) + end + end end end end @@ -102,7 +107,7 @@ end -- libc.so.6 => /lib64/libc.so.6 (0x00007fe240ccd000) -- /lib64/ld-linux-x86-64.so.2 (0x00007fe24123a000) -- -function _get_all_depends_by_ldd(binaryfile, opt) +function _get_depends_by_ldd(binaryfile, opt) local plat = opt.plat or os.host() local arch = opt.arch or os.arch() if plat ~= "linux" and plat ~= "bsd" then @@ -143,7 +148,7 @@ end -- 0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1] -- 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] -- 0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN] -function _get_all_depends_by_readelf(binaryfile, opt) +function _get_depends_by_readelf(binaryfile, opt) local plat = opt.plat or os.host() local arch = opt.arch or os.arch() if plat ~= "linux" and plat ~= "bsd" and plat ~= "android" and plat ~= "cross" then @@ -178,7 +183,7 @@ end -- /usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 1600.151.0) -- /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1336.0.0) -- -function _get_all_depends_by_otool(binaryfile, opt) +function _get_depends_by_otool(binaryfile, opt) local plat = opt.plat or os.host() local arch = opt.arch or os.arch() if plat ~= "macosx" and plat ~= "iphoneos" and plat ~= "appletvos" and plat ~= "watchos" then @@ -192,10 +197,13 @@ function _get_all_depends_by_otool(binaryfile, opt) local result = try { function () return os.iorunv(otool.program, {"-L", binaryfile}) end } if result then for _, line in ipairs(result:split("\n")) do - local filename = line:match(".-%.dylib") or line:match(".-%.framework") - if filename then - depends = depends or {} - table.insert(depends, filename:trim()) + line = line:trim() + if not line:endswith(":") then + local filename = line:match(".-%.dylib") or line:match(".-%.framework") + if filename then + depends = depends or {} + table.insert(depends, filename:trim()) + end end end end @@ -203,18 +211,18 @@ function _get_all_depends_by_otool(binaryfile, opt) return depends end -function main(binaryfile, opt) +function _get_depends(binaryfile, opt) opt = opt or {} local ops = { - _get_all_depends_by_objdump, - _get_all_depends_by_readelf + _get_depends_by_objdump, + _get_depends_by_readelf } if is_host("windows") then - table.insert(ops, 1, _get_all_depends_by_dumpbin) + table.insert(ops, 1, _get_depends_by_dumpbin) elseif is_host("linux", "bsd") then - table.insert(ops, 1, _get_all_depends_by_ldd) + table.insert(ops, 1, _get_depends_by_ldd) elseif is_host("macosx") then - table.insert(ops, 1, _get_all_depends_by_otool) + table.insert(ops, 1, _get_depends_by_otool) end for _, op in ipairs(ops) do local depends = op(binaryfile, opt) @@ -224,3 +232,172 @@ function main(binaryfile, opt) end end +-- resolve file path with @rpath, @loader_path, and $ORIGIN +function _resolve_filepath(binaryfile, dependfile, opt) + local loaderfile = opt._loaderfile + local resolve_hint_paths = opt.resolve_hint_paths + local resolve_search_paths = opt.resolve_search_paths + local resolved = false + + -- resolve path from rpath + if not resolved and dependfile:startswith("@rpath/") then + local rpathlist = opt._rpathlist + if rpathlist == nil then + rpathlist = rpath_utils.list(loaderfile) + opt._rpathlist = rpathlist or false + end + if rpathlist then + for _, rpath in ipairs(rpathlist) do + local filepath = dependfile:replace("@rpath/", rpath .. "/", {plain = true}) + if os.isfile(filepath) then + dependfile = path.absolute(filepath) + resolved = true + break + elseif filepath:startswith("@loader_path/") then + filepath = filepath:replace("@loader_path/", path.directory(loaderfile) .. "/", {plain = true}) + if os.isfile(filepath) then + dependfile = path.absolute(filepath) + resolved = true + break + end + elseif filepath:startswith("$ORIGIN/") then + filepath = filepath:replace("$ORIGIN/", path.directory(loaderfile) .. "/", {plain = true}) + if os.isfile(filepath) then + dependfile = path.absolute(filepath) + resolved = true + break + end + end + end + end + end + + if not path.is_absolute(dependfile) then + + -- resolve absolute path + if not resolved and os.isfile(dependfile) then + dependfile = path.absolute(dependfile) + resolved = true + end + + -- resolve path from the hint paths + if not resolved and resolve_hint_paths then + local filename = path.filename(dependfile) + for _, filepath in ipairs(resolve_hint_paths) do + if filename == path.filename(filepath) then + dependfile = path.absolute(filepath) + resolved = true + break + end + end + end + + -- resolve path from the current loader directory on windows + if not resolved and is_host("windows") then + local loaderdir = path.directory(loaderfile) + local filepath = path.join(loaderdir, dependfile) + if os.isfile(filepath) then + dependfile = path.absolute(filepath) + resolved = true + end + end + + -- resolve path from the searth paths + if resolve_search_paths then + for _, searchdir in ipairs(resolve_search_paths) do + local filepath = path.join(searchdir, dependfile) + if os.isfile(filepath) then + dependfile = path.absolute(filepath) + resolved = true + end + end + end + + -- resolve path from LD_LIBRARY_PATH, ... + local library_paths = is_host("macosx") and os.getenv("DYLD_LIBRARY_PATH") or os.getenv("LD_LIBRARY_PATH") + if library_paths then + for _, searchdir in ipairs(path.splitenv(library_paths)) do + local filepath = path.join(searchdir, dependfile) + if os.isfile(filepath) then + dependfile = path.absolute(filepath) + resolved = true + end + end + end + end + + dependfile = path.normalize(dependfile) + if binaryfile ~= dependfile then + return dependfile + end +end + +function _get_plain_depends(binaryfile, opt) + opt = opt or {} + local depends = _get_depends(binaryfile, opt) + if depends and opt.resolve_path then + local result = {} + for _, dependfile in ipairs(depends) do + dependfile = _resolve_filepath(binaryfile, dependfile, opt) + if dependfile then + table.insert(result, dependfile) + end + end + depends = result + end + return depends +end + +function _get_recursive_depends(binaryfile, dag, depends, opt) + local dependfiles = _get_plain_depends(binaryfile, opt) + if dependfiles then + for _, dependfile in ipairs(dependfiles) do + dag:add_edge(binaryfile, dependfile) + if not depends:has(dependfile) then + depends:insert(dependfile) + if os.isfile(dependfile) then + _get_recursive_depends(dependfile, dag, depends, opt) + end + end + end + end +end + +-- get the library dependencies of the give binary files +-- +-- @param binaryfile the binary file +-- @param opt the option, e.g. {recursive = false, resolve_path = true, resolve_hint_paths = {}} +-- - plat, arch: the platform and architecture +-- - recursive: recursively get all sub-dependencies, sorted by topology +-- - resolve_path: try to resolve the file full path, e.g. @rpath, @loader_path, $ORIGIN, relative path .. +-- - resolve_hint_paths: we can resolve and match path from them +-- - resolve_search_paths: the search library paths, like: LD_LIBRARY_PATH, DYLD_LIBRARY_PATH, ... +-- +function main(binaryfile, opt) + opt = opt or {} + if opt.resolve_path then + opt._loaderfile = binaryfile + end + binaryfile = path.normalize(path.absolute(binaryfile)) + if opt.recursive then + local dag = graph.new(true) + _get_recursive_depends(binaryfile, dag, hashset.new(), opt) + local depends, has_cycle = dag:topo_sort() + if has_cycle then + local files = {} + local cycle = dag:find_cycle() + if cycle then + for _, file in ipairs(cycle) do + table.insert(files, file) + end + table.insert(files, binaryfile) + end + raise("deplibs(%s): circular library dependencies detected!\n%s", binaryfile, table.concat(files, "\n -> ")) + end + if depends and #depends > 1 then + return table.slice(depends, 2) + end + else + return _get_plain_depends(binaryfile, opt) + end +end diff --git a/xmake/plugins/pack/batchcmds.lua b/xmake/plugins/pack/batchcmds.lua index c768453d0..2418f39ac 100644 --- a/xmake/plugins/pack/batchcmds.lua +++ b/xmake/plugins/pack/batchcmds.lua @@ -62,26 +62,6 @@ function _get_target_installdir(package, target) return path.normalize(installdir) end --- we need to get all deplibs, e.g. app -> libfoo.so -> libbar.so ... --- @see https://github.com/xmake-io/xmake/issues/5325#issuecomment-2242597732 -function _get_target_package_deplibs(binaryfile, depends, libfiles, opt) - local deplibs = get_depend_libraries(binaryfile, {plat = opt.plat, arch = opt.arch}) - local depends_new = hashset.new() - for _, deplib in ipairs(deplibs) do - local libname = path.filename(deplib) - if not depends:has(libname) then - depends:insert(libname) - depends_new:insert(libname) - end - end - for _, libfile in ipairs(libfiles) do - local libname = path.filename(libfile) - if depends_new:has(libname) then - _get_target_package_deplibs(libfile, depends, libfiles, opt) - end - end -end - function _get_target_package_libfiles(target, opt) if option.get("nopkgs") then return {} @@ -101,9 +81,15 @@ function _get_target_package_libfiles(target, opt) -- we can only reserve used libraries if project.policy("install.strip_packagelibs") then if target:is_binary() or target:is_shared() or opt.binaryfile then - local depends = hashset.new() - _get_target_package_deplibs(opt.binaryfile or target:targetfile(), depends, libfiles, {plat = target:plat(), arch = target:arch()}) - table.remove_if(libfiles, function (_, libfile) return not depends:has(path.filename(libfile)) end) + -- we need to get all deplibs, e.g. app -> libfoo.so -> libbar.so ... + -- @see https://github.com/xmake-io/xmake/issues/5325#issuecomment-2242597732 + local deplibs = get_depend_libraries(opt.binaryfile or target:targetfile(), { + plat = target:plat(), arch = target:arch(), + recursive = true, resolve_path = true, resolve_hint_paths = libfiles}) + if deplibs then + local depends = hashset.from(deplibs) + table.remove_if(libfiles, function (_, libfile) return not depends:has(libfile) end) + end end end return libfiles |
