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 /xmake/modules/utils/binary | |
| parent | 990cde63dbdf3002940698fd59dde5c236731f0f (diff) | |
| parent | 82600499728ebde05c988a10a3970123bf438e35 (diff) | |
Merge pull request #6651 from xmake-io/deplibs
improve depfiles
Diffstat (limited to 'xmake/modules/utils/binary')
| -rw-r--r-- | xmake/modules/utils/binary/deplibs.lua | 243 |
1 files changed, 210 insertions, 33 deletions
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 |
