summaryrefslogtreecommitdiff
path: root/xmake/modules/utils/binary/deplibs.lua
diff options
context:
space:
mode:
Diffstat (limited to 'xmake/modules/utils/binary/deplibs.lua')
-rw-r--r--xmake/modules/utils/binary/deplibs.lua243
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