diff options
| author | ruki <[email protected]> | 2024-07-23 12:50:50 +0800 |
|---|---|---|
| committer | GitHub <[email protected]> | 2024-07-23 12:50:50 +0800 |
| commit | d4364efee8f1906288ac5c650ae7bd101066240a (patch) | |
| tree | 72baca766710b28ca154f4b0f0983d7e9d8abc00 /xmake/modules | |
| parent | 45852e6ffcd3f043d961574e72f40d287a2898ab (diff) | |
| parent | 56ca5adad669d45f400198b0c99a1d20f546970c (diff) | |
Merge pull request #5335 from xmake-io/install
Improve installation
Diffstat (limited to 'xmake/modules')
| -rw-r--r-- | xmake/modules/package/manager/xmake/find_package.lua | 7 | ||||
| -rw-r--r-- | xmake/modules/private/action/clean/remove_files.lua | 6 | ||||
| -rw-r--r-- | xmake/modules/private/utils/batchcmds.lua | 77 | ||||
| -rw-r--r-- | xmake/modules/target/action/install/main.lua | 233 | ||||
| -rw-r--r-- | xmake/modules/target/action/install/unix.lua | 161 | ||||
| -rw-r--r-- | xmake/modules/target/action/install/windows.lua | 163 | ||||
| -rw-r--r-- | xmake/modules/target/action/uninstall/main.lua | 191 | ||||
| -rw-r--r-- | xmake/modules/target/action/uninstall/unix.lua | 137 | ||||
| -rw-r--r-- | xmake/modules/target/action/uninstall/windows.lua | 115 | ||||
| -rw-r--r-- | xmake/modules/utils/binary/deplibs.lua (renamed from xmake/modules/utils/symbols/depend.lua) | 127 | ||||
| -rw-r--r-- | xmake/modules/utils/binary/rpath.lua | 277 |
11 files changed, 770 insertions, 724 deletions
diff --git a/xmake/modules/package/manager/xmake/find_package.lua b/xmake/modules/package/manager/xmake/find_package.lua index 0d5336fe4..e3aed1294 100644 --- a/xmake/modules/package/manager/xmake/find_package.lua +++ b/xmake/modules/package/manager/xmake/find_package.lua @@ -167,6 +167,13 @@ function _find_package_from_repo(name, opt) result.shared = true table.insert(libfiles, file) end + -- @see https://github.com/xmake-io/xmake/issues/5325#issuecomment-2242513463 + if not result.shared then + for _, file in ipairs(os.files(path.join(installdir, "**.dll"))) do + result.shared = true + table.insert(libfiles, file) + end + end end -- add root link directories diff --git a/xmake/modules/private/action/clean/remove_files.lua b/xmake/modules/private/action/clean/remove_files.lua index 3d62c6b78..8ec7dca77 100644 --- a/xmake/modules/private/action/clean/remove_files.lua +++ b/xmake/modules/private/action/clean/remove_files.lua @@ -25,6 +25,10 @@ import("core.base.option") function main(filedirs, opt) opt = opt or {} for _, filedir in ipairs(filedirs) do - os.tryrm(filedir, {emptydirs = option.get("all") or opt.emptydir}) + -- os.exists will return false if symlink -> not found, but we need still remove this symlink + if os.exists(filedir) or os.islink(filedir) then + -- we cannot use os.tryrm, because we need raise exception if remove failed with `uninstall --admin` + os.rm(filedir, {emptydirs = option.get("all") or opt.emptydir}) + end end end diff --git a/xmake/modules/private/utils/batchcmds.lua b/xmake/modules/private/utils/batchcmds.lua index 6c3742b9f..97f414c77 100644 --- a/xmake/modules/private/utils/batchcmds.lua +++ b/xmake/modules/private/utils/batchcmds.lua @@ -29,6 +29,7 @@ import("core.tool.linker") import("core.tool.compiler") import("core.language.language") import("utils.progress", {alias = "progress_utils"}) +import("utils.binary.rpath", {alias = "rpath_utils"}) -- define module local batchcmds = batchcmds or object { _init = {"_TARGET", "_CMDS", "_DEPINFO", "_tip"}} @@ -173,6 +174,34 @@ function _runcmd_ln(cmd, opt) end end +-- run command: clean rpath +function _runcmd_clean_rpath(cmd, opt) + if not opt.dryrun then + rpath_utils.clean(cmd.filepath, opt.opt) + end +end + +-- run command: insert rpath +function _runcmd_insert_rpath(cmd, opt) + if not opt.dryrun then + rpath_utils.insert(cmd.filepath, cmd.rpath, opt.opt) + end +end + +-- run command: remove rpath +function _runcmd_remove_rpath(cmd, opt) + if not opt.dryrun then + rpath_utils.remove(cmd.filepath, cmd.rpath, opt.opt) + end +end + +-- run command: change rpath +function _runcmd_change_rpath(cmd, opt) + if not opt.dryrun then + rpath_utils.change(cmd.filepath, cmd.rpath_old, cmd.rpath_new, opt.opt) + end +end + -- run command function _runcmd(cmd, opt) local kind = cmd.kind @@ -180,18 +209,22 @@ function _runcmd(cmd, opt) if not maps then maps = { - show = _runcmd_show, - runv = _runcmd_runv, - vrunv = _runcmd_vrunv, - execv = _runcmd_execv, - vexecv = _runcmd_vexecv, - mkdir = _runcmd_mkdir, - rmdir = _runcmd_rmdir, - cd = _runcmd_cd, - rm = _runcmd_rm, - cp = _runcmd_cp, - mv = _runcmd_mv, - ln = _runcmd_ln + show = _runcmd_show, + runv = _runcmd_runv, + vrunv = _runcmd_vrunv, + execv = _runcmd_execv, + vexecv = _runcmd_vexecv, + mkdir = _runcmd_mkdir, + rmdir = _runcmd_rmdir, + cd = _runcmd_cd, + rm = _runcmd_rm, + cp = _runcmd_cp, + mv = _runcmd_mv, + ln = _runcmd_ln, + clean_rpath = _runcmd_clean_rpath, + insert_rpath = _runcmd_insert_rpath, + remove_rpath = _runcmd_remove_rpath, + change_rpath = _runcmd_change_rpath } _g.maps = maps end @@ -389,6 +422,26 @@ function batchcmds:show(format, ...) table.insert(self:cmds(), {kind = "show", showtext = showtext}) end +-- add command: clean rpath +function batchcmds:clean_rpath(filepath, opt) + table.insert(self:cmds(), {kind = "clean_rpath", filepath = filepath, opt = opt}) +end + +-- add command: insert rpath +function batchcmds:insert_rpath(filepath, rpath, opt) + table.insert(self:cmds(), {kind = "insert_rpath", filepath = filepath, rpath = rpath, opt = opt}) +end + +-- add command: remove rpath +function batchcmds:remove_rpath(filepath, rpath, opt) + table.insert(self:cmds(), {kind = "remove_rpath", filepath = filepath, rpath = rpath, opt = opt}) +end + +-- add command: change rpath +function batchcmds:change_rpath(filepath, rpath_old, rpath_new, opt) + table.insert(self:cmds(), {kind = "change_rpath", filepath = filepath, rpath_old = rpath_old, rpath_new = rpath_new, opt = opt}) +end + -- add raw command for the specific generator or xpack format function batchcmds:rawcmd(kind, rawstr) table.insert(self:cmds(), {kind = kind, rawstr = rawstr}) diff --git a/xmake/modules/target/action/install/main.lua b/xmake/modules/target/action/install/main.lua index c115e836e..6eecfaba4 100644 --- a/xmake/modules/target/action/install/main.lua +++ b/xmake/modules/target/action/install/main.lua @@ -18,40 +18,239 @@ -- @file main.lua -- +-- imports +import("core.base.option") +import("core.base.hashset") +import("utils.binary.deplibs", {alias = "get_depend_libraries"}) +import("utils.binary.rpath", {alias = "rpath_utils"}) + +-- 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(target, depends, libfiles, binaryfile) + local deplibs = get_depend_libraries(binaryfile, {plat = target:plat(), arch = target: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(target, depends, libfiles, libfile) + end + end +end + +function _get_target_package_libfiles(target, opt) + if option.get("nopkgs") then + return {} + end + local libfiles = {} + for _, pkg in ipairs(target:orderpkgs(opt)) do + if pkg:enabled() and pkg:get("libfiles") then + for _, libfile in ipairs(table.wrap(pkg:get("libfiles"))) do + local filename = path.filename(libfile) + if filename:endswith(".dll") or filename:endswith(".so") or filename:find("%.so%.%d+$") or filename:endswith(".dylib") then + table.insert(libfiles, libfile) + end + end + end + end + -- we can only reserve used libraries + if target:is_binary() or target:is_shared() then + local depends = hashset.new() + _get_target_package_deplibs(target, depends, libfiles, target:targetfile()) + table.remove_if(libfiles, function (_, libfile) return not depends:has(path.filename(libfile)) end) + end + return libfiles +end + +-- copy file with symlinks +function _copy_file_with_symlinks(srcfile, outputdir) + if os.islink(srcfile) then + local srcfile_symlink = os.readlink(srcfile) + if not path.is_absolute(srcfile_symlink) then + srcfile_symlink = path.join(path.directory(srcfile), srcfile_symlink) + end + _copy_file_with_symlinks(srcfile_symlink, outputdir) + os.vcp(srcfile, path.join(outputdir, path.filename(srcfile)), {symlink = true, force = true}) + else + os.vcp(srcfile, path.join(outputdir, path.filename(srcfile))) + end +end + -- install files function _install_files(target) local srcfiles, dstfiles = target:installfiles() if srcfiles and dstfiles then - local i = 1 - for _, srcfile in ipairs(srcfiles) do - local dstfile = dstfiles[i] - if dstfile then - os.vcp(srcfile, dstfile) + for idx, srcfile in ipairs(srcfiles) do + os.vcp(srcfile, dstfiles[idx]) + end + end + for _, dep in ipairs(target:orderdeps()) do + local srcfiles, dstfiles = dep:installfiles(dep:installdir(), {interface = true}) + if srcfiles and dstfiles then + for idx, srcfile in ipairs(srcfiles) do + os.vcp(srcfile, dstfiles[idx]) end - i = i + 1 end end end --- the builtin install main entry -function main(target, opt) +-- install headers +function _install_headers(target, opt) + local srcheaders, dstheaders = target:headerfiles(target:includedir(), {installonly = true}) + if srcheaders and dstheaders then + for idx, srcheader in ipairs(srcheaders) do + os.vcp(srcheader, dstheaders[idx]) + end + end + for _, dep in ipairs(target:orderdeps()) do + local srcfiles, dstfiles = dep:headerfiles(dep:includedir(), {installonly = true, interface = true}) + if srcfiles and dstfiles then + for idx, srcfile in ipairs(srcfiles) do + os.vcp(srcfile, dstfiles[idx]) + end + end + end +end + +-- install shared libraries +function _install_shared_libraries(target, opt) + local bindir = target:is_plat("windows", "mingw") and target:bindir() or target:libdir() + + -- get all dependent shared libraries + local libfiles = {} + for _, dep in ipairs(target:orderdeps()) do + if dep:kind() == "shared" then + local depfile = dep:targetfile() + if os.isfile(depfile) then + table.insert(libfiles, depfile) + end + end + table.join2(libfiles, _get_target_package_libfiles(dep, {interface = true})) + end + table.join2(libfiles, _get_target_package_libfiles(target)) + + -- deduplicate libfiles, prevent packages using the same libfiles from overwriting each other + libfiles = table.unique(libfiles) + + -- do install + for _, libfile in ipairs(libfiles) do + local filename = path.filename(libfile) + local filepath = path.join(bindir, filename) + if os.isfile(filepath) and hash.sha256(filepath) ~= hash.sha256(libfile) then + wprint("'%s' already exists in install dir, we are copying '%s' to overwrite it.", filepath, libfile) + end + _copy_file_with_symlinks(libfile, bindir) + end +end + +-- update install rpath, we can only get and update rpathdirs with `{installonly = true}` +-- e.g. add_rpathdirs("@loader_path/../lib", {installonly = true}) +function _update_install_rpath(target, opt) + if target:is_plat("windows", "mingw") then + return + end + local bindir = target:bindir() + local targetfile = path.join(bindir, target:filename()) + if target:policy("install.rpath") then + rpath_utils.clean(targetfile, {plat = target:plat(), arch = target:arch()}) + local result, sources = target:get_from("rpathdirs", "*") + if result and sources then + for idx, rpathdirs in ipairs(result) do + local source = sources[idx] + local extraconf = target:extraconf_from("rpathdirs", source) + if extraconf then + for _, rpathdir in ipairs(rpathdirs) do + local extra = extraconf[rpathdir] + if extra and extra.installonly then + rpath_utils.insert(targetfile, rpathdir, {plat = target:plat(), arch = target:arch()}) + end + end + end + end + end + end +end + +-- install binary +function _install_binary(target, opt) + local bindir = target:bindir() + os.mkdir(bindir) + os.vcp(target:targetfile(), bindir) + os.trycp(target:symbolfile(), path.join(bindir, path.filename(target:symbolfile()))) + _install_shared_libraries(target, opt) + _update_install_rpath(target, opt) +end + +-- install shared library +function _install_shared(target, opt) + local bindir = target:is_plat("windows", "mingw") and target:bindir() or target:libdir() + os.mkdir(bindir) + local targetfile = target:targetfile() + + if target:is_plat("windows", "mingw") then + -- install *.lib for shared/windows (*.dll) target + -- @see https://github.com/xmake-io/xmake/issues/714 + os.vcp(target:targetfile(), bindir) + local libdir = target:libdir() + local targetfile_lib = path.join(path.directory(targetfile), path.basename(targetfile) .. (target:is_plat("mingw") and ".dll.a" or ".lib")) + if os.isfile(targetfile_lib) then + os.mkdir(libdir) + os.vcp(targetfile_lib, libdir) + end + else + -- install target with soname and symlink + _copy_file_with_symlinks(targetfile, bindir) + end + os.trycp(target:symbolfile(), path.join(bindir, path.filename(target:symbolfile()))) + + _install_headers(target, opt) + _install_shared_libraries(target, opt) +end - -- get install directory +-- install static library +function _install_static(target, opt) + local libdir = target:libdir() + os.mkdir(libdir) + os.vcp(target:targetfile(), libdir) + os.trycp(target:symbolfile(), path.join(libdir, path.filename(target:symbolfile()))) + _install_headers(target, opt) +end + +-- install headeronly library +function _install_headeronly(target, opt) + _install_headers(target, opt) +end + +-- install moduleonly library +function _install_moduleonly(target, opt) + _install_headers(target, opt) +end + +function main(target, opt) local installdir = target:installdir() if not installdir then + wprint("please use `xmake install -o installdir` or `set_installdir` to set install directory.") return end - - -- trace print("installing %s to %s ..", target:name(), installdir) - -- call script - local install_style = target:is_plat("windows", "mingw") and "windows" or "unix" - local script = import(install_style, {anonymous = true})["install_" .. target:kind()] - if script then - script(target, opt) + if target:is_binary() then + _install_binary(target, opt) + elseif target:is_shared() then + _install_shared(target, opt) + elseif target:is_static() then + _install_static(target, opt) + elseif target:is_headeronly() then + _install_headeronly(target, opt) + elseif target:is_moduleonly() then + _install_moduleonly(target, opt) end - -- install other files _install_files(target) end diff --git a/xmake/modules/target/action/install/unix.lua b/xmake/modules/target/action/install/unix.lua deleted file mode 100644 index 71a6df42b..000000000 --- a/xmake/modules/target/action/install/unix.lua +++ /dev/null @@ -1,161 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file unix.lua --- - --- imports -import("core.base.option") - --- install headers -function _install_headers(target, opt) - local includedir = path.join(target:installdir(), opt and opt.includedir or "include") - os.mkdir(includedir) - local srcheaders, dstheaders = target:headerfiles(includedir, {installonly = true}) - if srcheaders and dstheaders then - local i = 1 - for _, srcheader in ipairs(srcheaders) do - local dstheader = dstheaders[i] - if dstheader then - os.vcp(srcheader, dstheader) - end - i = i + 1 - end - end -end - --- install shared libraries for package -function _install_shared_for_package(target, pkg, outputdir) - _g.installed_libfiles = _g.installed_libfiles or {} - for _, sopath in ipairs(table.wrap(pkg:get("libfiles"))) do - if sopath:endswith(".so") or sopath:match(".+%.so%..+$") or sopath:endswith(".dylib") then - -- prevent packages using the same system libfiles from overwriting each other - if not _g.installed_libfiles[sopath] then - local soname = path.filename(sopath) - local targetname = path.join(outputdir, soname) - if os.isfile(targetname) then - wprint("'%s' already exists in install dir, overwriting it from package(%s).", soname, pkg:name()) - -- rm because symlink cannot overwrite existing file - os.rm(targetname) - end - -- we need to reserve symlink - -- @see https://github.com/xmake-io/xmake/issues/1582 - os.vcp(sopath, outputdir, {symlink = true, force = true}) - _g.installed_libfiles[sopath] = true - end - end - end -end - --- install shared libraries for packages -function _install_shared_for_packages(target, outputdir) - if option.get("nopkgs") then - return - end - _g.installed_packages = _g.installed_packages or {} - for _, pkg in ipairs(target:orderpkgs()) do - if not _g.installed_packages[pkg:name()] then - if pkg:enabled() and pkg:get("libfiles") then - _install_shared_for_package(target, pkg, outputdir) - end - _g.installed_packages[pkg:name()] = true - end - end -end - --- install binary -function install_binary(target, opt) - - -- install binary - local binarydir = path.join(target:installdir(), opt and opt.bindir or "bin") - os.mkdir(binarydir) - os.vcp(target:targetfile(), binarydir) - - -- install libraries - local librarydir = path.join(target:installdir(), opt and opt.libdir or "lib") - os.mkdir(librarydir) - - -- install the dependent shared (*.so) target - -- @see https://github.com/xmake-io/xmake/issues/961 - for _, dep in ipairs(target:orderdeps()) do - if dep:kind() == "shared" then - local depfile = dep:targetfile() - if os.isfile(depfile) then - os.vcp(depfile, librarydir) - end - end - -- install all shared libraries in packages in all deps - _install_shared_for_packages(dep, librarydir) - end - - -- install shared libraries for all packages - _install_shared_for_packages(target, librarydir) -end - --- install shared library -function install_shared(target, opt) - - -- install libraries - local librarydir = path.join(target:installdir(), opt and opt.libdir or "lib") - os.mkdir(librarydir) - local targetfile = target:targetfile() - if os.islink(targetfile) then - local targetfile_with_soname = os.readlink(targetfile) - if not path.is_absolute(targetfile_with_soname) then - targetfile_with_soname = path.join(target:targetdir(), targetfile_with_soname) - end - if os.islink(targetfile_with_soname) then - local targetfile_with_version = os.readlink(targetfile_with_soname) - if not path.is_absolute(targetfile_with_version) then - targetfile_with_version = path.join(target:targetdir(), targetfile_with_version) - end - os.vcp(targetfile_with_version, librarydir, {symlink = true, force = true}) - end - os.vcp(targetfile_with_soname, librarydir, {symlink = true, force = true}) - os.vcp(targetfile, librarydir, {symlink = true, force = true}) - else - os.vcp(targetfile, librarydir) - end - - -- install shared libraries for all packages - _install_shared_for_packages(target, librarydir) - - -- install headers - _install_headers(target, opt) -end - --- install static library -function install_static(target, opt) - - -- install libraries - local librarydir = path.join(target:installdir(), opt and opt.libdir or "lib") - os.mkdir(librarydir) - os.vcp(target:targetfile(), librarydir) - - -- install headers - _install_headers(target, opt) -end - --- install headeronly library -function install_headeronly(target, opt) - _install_headers(target, opt) -end - --- install moduleonly library -function install_moduleonly(target, opt) - _install_headers(target, opt) -end diff --git a/xmake/modules/target/action/install/windows.lua b/xmake/modules/target/action/install/windows.lua deleted file mode 100644 index d7b71387c..000000000 --- a/xmake/modules/target/action/install/windows.lua +++ /dev/null @@ -1,163 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file windows.lua --- - --- imports -import("core.base.option") -import("lib.detect.find_file") - --- get install directory -function _get_installdir(target) - local installdir = assert(target:installdir(), "please use `xmake install -o installdir` or `set_installdir` to set install directory on windows.") - return installdir -end - --- install headers -function _install_headers(target, opt) - local installdir = _get_installdir(target) - local includedir = path.join(installdir, opt and opt.includedir or "include") - os.mkdir(includedir) - local srcheaders, dstheaders = target:headerfiles(includedir, {installonly = true}) - if srcheaders and dstheaders then - local i = 1 - for _, srcheader in ipairs(srcheaders) do - local dstheader = dstheaders[i] - if dstheader then - os.vcp(srcheader, dstheader) - end - i = i + 1 - end - end -end - --- install shared libraries for package -function _install_shared_for_package(target, pkg, outputdir) - _g.installed_dllfiles = _g.installed_dllfiles or {} - for _, dllpath in ipairs(table.wrap(pkg:get("libfiles"))) do - if dllpath:endswith(".dll") then - -- prevent packages using the same libfiles from overwriting each other - if not _g.installed_dllfiles[dllpath] then - local dllname = path.filename(dllpath) - if os.isfile(path.join(outputdir, dllname)) then - wprint("'%s' already exists in install dir, overwriting it from package(%s).", dllname, pkg:name()) - end - os.vcp(dllpath, outputdir) - _g.installed_dllfiles[dllpath] = true - end - end - end -end - --- install shared libraries for packages -function _install_shared_for_packages(target, outputdir) - if option.get("nopkgs") then - return - end - _g.installed_packages = _g.installed_packages or {} - for _, pkg in ipairs(target:orderpkgs()) do - if not _g.installed_packages[pkg:name()] then - if pkg:enabled() and pkg:get("libfiles") then - _install_shared_for_package(target, pkg, outputdir) - end - _g.installed_packages[pkg:name()] = true - end - end -end - --- install binary -function install_binary(target, opt) - - -- install binary - local installdir = _get_installdir(target) - local binarydir = path.join(installdir, opt and opt.bindir or "bin") - os.mkdir(binarydir) - os.vcp(target:targetfile(), binarydir) - os.trycp(target:symbolfile(), binarydir) - - -- install the dependent shared/windows (*.dll) target - -- @see https://github.com/xmake-io/xmake/issues/961 - _g.installed_dllfiles = _g.installed_dllfiles or {} - for _, dep in ipairs(target:orderdeps()) do - if dep:kind() == "shared" then - local depfile = dep:targetfile() - if os.isfile(depfile) then - if not _g.installed_dllfiles[depfile] then - os.vcp(depfile, binarydir) - _g.installed_dllfiles[depfile] = true - end - end - end - -- install all shared libraries in packages in all deps - _install_shared_for_packages(dep, binarydir) - end - - -- install shared libraries for all packages - _install_shared_for_packages(target, binarydir) -end - --- install shared library -function install_shared(target, opt) - - -- install dll library to the binary directory - local installdir = _get_installdir(target) - local binarydir = path.join(installdir, opt and opt.bindir or "bin") - os.mkdir(binarydir) - os.vcp(target:targetfile(), binarydir) - os.trycp(target:symbolfile(), binarydir) - - -- install *.lib for shared/windows (*.dll) target - -- @see https://github.com/xmake-io/xmake/issues/714 - local targetfile = target:targetfile() - local librarydir = path.join(target:installdir(), opt and opt.libdir or "lib") - local targetfile_lib = path.join(path.directory(targetfile), path.basename(targetfile) .. (target:is_plat("mingw") and ".dll.a" or ".lib")) - if os.isfile(targetfile_lib) then - os.mkdir(librarydir) - os.vcp(targetfile_lib, librarydir) - end - - -- install shared libraries for all packages - _install_shared_for_packages(target, binarydir) - - -- install headers - _install_headers(target, opt) -end - --- install static library -function install_static(target, opt) - - -- install library - local installdir = _get_installdir(target) - local librarydir = path.join(installdir, opt and opt.libdir or "lib") - os.mkdir(librarydir) - os.vcp(target:targetfile(), librarydir) - os.trycp(target:symbolfile(), librarydir) - - -- install headers - _install_headers(target, opt) -end - --- install headeronly -function install_headeronly(target, opt) - _install_headers(target, opt) -end - --- install moduleonly -function install_moduleonly(target, opt) - _install_headers(target, opt) -end diff --git a/xmake/modules/target/action/uninstall/main.lua b/xmake/modules/target/action/uninstall/main.lua index daa23917c..ab2333207 100644 --- a/xmake/modules/target/action/uninstall/main.lua +++ b/xmake/modules/target/action/uninstall/main.lua @@ -18,45 +18,192 @@ -- @file main.lua -- +-- imports +import("core.base.option") +import("core.base.hashset") +import("utils.binary.deplibs", {alias = "get_depend_libraries"}) +import("private.action.clean.remove_files") + +-- 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(target, depends, libfiles, binaryfile) + local deplibs = get_depend_libraries(binaryfile, {plat = target:plat(), arch = target: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(target, depends, libfiles, libfile) + end + end +end + +function _get_target_package_libfiles(target, opt) + if option.get("nopkgs") then + return {} + end + local libfiles = {} + local bindir = target:is_plat("windows", "mingw") and target:bindir() or target:libdir() + for _, pkg in ipairs(target:orderpkgs(opt)) do + if pkg:enabled() and pkg:get("libfiles") then + for _, libfile in ipairs(table.wrap(pkg:get("libfiles"))) do + local filename = path.filename(libfile) + if filename:endswith(".dll") or filename:endswith(".so") or filename:find("%.so%.%d+$") or filename:endswith(".dylib") then + table.insert(libfiles, libfile) + end + end + end + end + -- we can only reserve used libraries + if target:is_binary() or target:is_shared() then + local depends = hashset.new() + _get_target_package_deplibs(target, depends, libfiles, target:targetfile()) + table.remove_if(libfiles, function (_, libfile) return not depends:has(path.filename(libfile)) end) + end + return libfiles +end + +-- remove file with symbols +function _remove_file_with_symbols(filepath) + if os.islink(filepath) then + local filepath_symlink = os.readlink(filepath) + if not path.is_absolute(filepath_symlink) then + filepath_symlink = path.join(path.directory(filepath), filepath_symlink) + end + _remove_file_with_symbols(filepath_symlink) + end + remove_files(filepath, {emptydir = true}) +end + -- uninstall files function _uninstall_files(target) local _, dstfiles = target:installfiles() for _, dstfile in ipairs(dstfiles) do - os.vrm(dstfile) + remove_files(dstfile, {emptydir = true}) + end + for _, dep in ipairs(target:orderdeps()) do + local _, dstfiles = dep:installfiles(dep:installdir(), {interface = true}) + for _, dstfile in ipairs(dstfiles) do + remove_files(dstfile, {emptydir = true}) + end end end --- uninstall modules -function _uninstall_modules(target, opt) - local moduledir = path.join(target:installdir(), opt and opt.moduledir or "modules") - os.vrm(moduledir) +-- uninstall headers +function _uninstall_headers(target, opt) + local _, dstheaders = target:headerfiles(target:includedir(), {installonly = true}) + for _, dstheader in ipairs(dstheaders) do + remove_files(dstheader, {emptydir = true}) + end + for _, dep in ipairs(target:orderdeps()) do + local _, dstfiles = dep:headerfiles(dep:includedir(), {installonly = true, interface = true}) + for _, dstfile in ipairs(dstfiles) do + remove_files(dstfile, {emptydir = true}) + end + end end --- the builtin uninstall main entry -function main(target, opt) +-- uninstall shared libraries +function _uninstall_shared_libraries(target, opt) + local bindir = target:is_plat("windows", "mingw") and target:bindir() or target:libdir() - -- get install directory + -- get all dependent shared libraries + local libfiles = {} + for _, dep in ipairs(target:orderdeps()) do + if dep:kind() == "shared" then + local depfile = dep:targetfile() + if os.isfile(depfile) then + table.insert(libfiles, depfile) + end + end + table.join2(libfiles, _get_target_package_libfiles(dep, {interface = true})) + end + table.join2(libfiles, _get_target_package_libfiles(target)) + + -- deduplicate libfiles, prevent packages using the same libfiles from overwriting each other + libfiles = table.unique(libfiles) + + -- do uninstall + for _, libfile in ipairs(libfiles) do + local filename = path.filename(libfile) + local filepath = path.join(bindir, filename) + _remove_file_with_symbols(filepath) + end +end + +-- uninstall binary +function _uninstall_binary(target, opt) + local bindir = target:bindir() + remove_files(path.join(bindir, path.filename(target:targetfile())), {emptydir = true}) + remove_files(path.join(bindir, path.filename(target:symbolfile())), {emptydir = true}) + _uninstall_shared_libraries(target, opt) +end + +-- uninstall shared library +function _uninstall_shared(target, opt) + local bindir = target:is_plat("windows", "mingw") and target:bindir() or target:libdir() + + if target:is_plat("windows", "mingw") then + -- uninstall *.lib for shared/windows (*.dll) target + -- @see https://github.com/xmake-io/xmake/issues/714 + local libdir = target:libdir() + local targetfile = target:targetfile() + remove_files(path.join(bindir, path.filename(targetfile)), {emptydir = true}) + remove_files(path.join(libdir, path.basename(targetfile) .. (target:is_plat("mingw") and ".dll.a" or ".lib")), {emptydir = true}) + else + local targetfile = path.join(bindir, path.filename(target:targetfile())) + _remove_file_with_symbols(targetfile) + end + remove_files(path.join(bindir, path.filename(target:symbolfile())), {emptydir = true}) + + _uninstall_headers(target, opt) + _uninstall_shared_libraries(target, opt) +end + +-- uninstall static library +function _uninstall_static(target, opt) + local libdir = target:libdir() + remove_files(path.join(libdir, path.filename(target:targetfile())), {emptydir = true}) + remove_files(path.join(libdir, path.filename(target:symbolfile())), {emptydir = true}) + _uninstall_headers(target, opt) +end + +-- uninstall headeronly library +function _uninstall_headeronly(target, opt) + _uninstall_headers(target, opt) +end + +-- uninstall moduleonly library +function _uninstall_moduleonly(target, opt) + _uninstall_headers(target, opt) +end + +function main(target, opt) local installdir = target:installdir() if not installdir then + wprint("please use `xmake install -o installdir` or `set_installdir` to set install directory.") return end + print("uninstalling %s to %s ..", target:name(), installdir) - -- trace - print("uninstalling %s from %s ..", target:name(), installdir) - - -- call script - if not target:is_phony() then - local install_style = target:is_plat("windows", "mingw") and "windows" or "unix" - local script = import(install_style, {anonymous = true})["uninstall_" .. target:kind()] - if script then - script(target, opt) - end + if target:is_binary() then + _uninstall_binary(target, opt) + elseif target:is_shared() then + _uninstall_shared(target, opt) + elseif target:is_static() then + _uninstall_static(target, opt) + elseif target:is_headeronly() then + _uninstall_headeronly(target, opt) + elseif target:is_moduleonly() then + _uninstall_moduleonly(target, opt) end - -- remove modules - _uninstall_modules(target, opt) - - -- uninstall the other files _uninstall_files(target) end diff --git a/xmake/modules/target/action/uninstall/unix.lua b/xmake/modules/target/action/uninstall/unix.lua deleted file mode 100644 index ce14c2c7b..000000000 --- a/xmake/modules/target/action/uninstall/unix.lua +++ /dev/null @@ -1,137 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file unix.lua --- - --- imports -import("private.action.clean.remove_files") - --- uninstall headers -function _uninstall_headers(target, opt) - local includedir = path.join(target:installdir(), opt and opt.includedir or "include") - local _, dstheaders = target:headerfiles(includedir, {installonly = true}) - for _, dstheader in ipairs(dstheaders) do - remove_files(dstheader, {emptydir = true}) - end -end - --- uninstall shared libraries for package -function _uninstall_shared_for_package(target, pkg, outputdir) - for _, sopath in ipairs(table.wrap(pkg:get("libfiles"))) do - if sopath:endswith(".so") or sopath:match(".+%.so%..+$") or sopath:endswith(".dylib") then - local soname = path.filename(sopath) - local filepath = path.join(outputdir, soname) - -- https://github.com/xmake-io/xmake/issues/2665#issuecomment-1209619081 - if os.islink(filepath) then - -- relative link? e.g. libxx.so -> libxx.4.so - local realitem = os.readlink(filepath) - if realitem and not path.is_absolute(realitem) then - local realpath = path.join(outputdir, realitem) - if os.isfile(realpath) then - os.vrm(realpath) - end - end - end - remove_files(filepath, {emptydir = true}) - end - end -end - --- uninstall shared libraries for packages -function _uninstall_shared_for_packages(target, outputdir) - _g.uninstalled_packages = _g.uninstalled_packages or {} - for _, pkg in ipairs(target:orderpkgs()) do - if not _g.uninstalled_packages[pkg:name()] then - if pkg:enabled() and pkg:get("libfiles") then - _uninstall_shared_for_package(target, pkg, outputdir) - end - _g.uninstalled_packages[pkg:name()] = true - end - end -end - --- uninstall binary -function uninstall_binary(target, opt) - - -- remove the target file - local binarydir = path.join(target:installdir(), opt and opt.bindir or "bin") - os.vrm(path.join(binarydir, path.filename(target:targetfile()))) - - -- remove the dependent shared (*.so) target - -- @see https://github.com/xmake-io/xmake/issues/961 - local librarydir = path.join(target:installdir(), opt and opt.libdir or "lib") - for _, dep in ipairs(target:orderdeps()) do - if dep:kind() == "shared" then - os.vrm(path.join(librarydir, path.filename(dep:targetfile()))) - end - _uninstall_shared_for_packages(dep, librarydir) - end - - -- uninstall shared libraries for packages - _uninstall_shared_for_packages(target, librarydir) -end - --- uninstall shared library -function uninstall_shared(target, opt) - - -- remove the target file - local librarydir = path.join(target:installdir(), opt and opt.libdir or "lib") - local targetfile = path.join(librarydir, path.filename(target:targetfile())) - if os.islink(targetfile) then - local targetfile_with_soname = os.readlink(targetfile) - if not path.is_absolute(targetfile_with_soname) then - targetfile_with_soname = path.join(librarydir, targetfile_with_soname) - end - if os.islink(targetfile_with_soname) then - local targetfile_with_version = os.readlink(targetfile_with_soname) - if not path.is_absolute(targetfile_with_version) then - targetfile_with_version = path.join(librarydir, targetfile_with_version) - end - os.vrm(targetfile_with_version) - end - os.vrm(targetfile_with_soname) - end - os.vrm(targetfile) - - -- remove headers from the include directory - _uninstall_headers(target, opt) - - -- uninstall shared libraries for packages - _uninstall_shared_for_packages(target, librarydir) -end - --- uninstall static library -function uninstall_static(target, opt) - - -- remove the target file - local librarydir = path.join(target:installdir(), opt and opt.libdir or "lib") - os.vrm(path.join(librarydir, path.filename(target:targetfile()))) - - -- remove headers from the include directory - _uninstall_headers(target, opt) -end - --- uninstall headeronly library -function uninstall_headeronly(target, opt) - _uninstall_headers(target, opt) -end - --- uninstall moduleonly library -function uninstall_moduleonly(target, opt) - _uninstall_headers(target, opt) -end diff --git a/xmake/modules/target/action/uninstall/windows.lua b/xmake/modules/target/action/uninstall/windows.lua deleted file mode 100644 index 338f8c707..000000000 --- a/xmake/modules/target/action/uninstall/windows.lua +++ /dev/null @@ -1,115 +0,0 @@ ---!A cross-platform build utility based on Lua --- --- Licensed under the Apache License, Version 2.0 (the "License"); --- you may not use this file except in compliance with the License. --- You may obtain a copy of the License at --- --- http://www.apache.org/licenses/LICENSE-2.0 --- --- Unless required by applicable law or agreed to in writing, software --- distributed under the License is distributed on an "AS IS" BASIS, --- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. --- See the License for the specific language governing permissions and --- limitations under the License. --- --- Copyright (C) 2015-present, TBOOX Open Source Group. --- --- @author ruki --- @file windows.lua --- - --- uninstall headers -function _uninstall_headers(target, opt) - local includedir = path.join(target:installdir(), opt and opt.includedir or "include") - local _, dstheaders = target:headerfiles(includedir, {installonly = true}) - for _, dstheader in ipairs(dstheaders) do - os.vrm(dstheader) - end -end - --- uninstall shared libraries for package -function _uninstall_shared_for_package(target, pkg, outputdir) - for _, dllpath in ipairs(table.wrap(pkg:get("libfiles"))) do - if dllpath:endswith(".dll") then - local dllname = path.filename(dllpath) - os.vrm(path.join(outputdir, dllname)) - end - end -end - --- uninstall shared libraries for packages -function _uninstall_shared_for_packages(target, outputdir) - _g.uninstalled_packages = _g.uninstalled_packages or {} - for _, pkg in ipairs(target:orderpkgs()) do - if not _g.uninstalled_packages[pkg:name()] then - if pkg:enabled() and pkg:get("libfiles") then - _uninstall_shared_for_package(target, pkg, outputdir) - end - _g.uninstalled_packages[pkg:name()] = true - end - end -end - --- uninstall binary -function uninstall_binary(target, opt) - - -- remove the target file - local binarydir = path.join(target:installdir(), opt and opt.bindir or "bin") - os.vrm(path.join(binarydir, path.filename(target:targetfile()))) - os.tryrm(path.join(binarydir, path.filename(target:symbolfile()))) - - -- remove the dependent shared/windows (*.dll) target - -- @see https://github.com/xmake-io/xmake/issues/961 - for _, dep in ipairs(target:orderdeps()) do - if dep:kind() == "shared" then - os.vrm(path.join(binarydir, path.filename(dep:targetfile()))) - end - _uninstall_shared_for_packages(dep, binarydir) - end - - -- uninstall shared libraries for packages - _uninstall_shared_for_packages(target, binarydir) -end - --- uninstall shared library -function uninstall_shared(target, opt) - - -- remove the target file - local binarydir = path.join(target:installdir(), opt and opt.bindir or "bin") - os.vrm(path.join(binarydir, path.filename(target:targetfile()))) - os.tryrm(path.join(binarydir, path.filename(target:symbolfile()))) - - -- remove *.lib for shared/windows (*.dll) target - -- @see https://github.com/xmake-io/xmake/issues/714 - local targetfile = target:targetfile() - local librarydir = path.join(target:installdir(), opt and opt.libdir or "lib") - os.vrm(path.join(librarydir, path.basename(targetfile) .. (target:is_plat("mingw") and ".dll.a" or ".lib"))) - - -- remove headers from the include directory - _uninstall_headers(target, opt) - - -- uninstall shared libraries for packages - _uninstall_shared_for_packages(target, binarydir) -end - --- uninstall static library -function uninstall_static(target, opt) - - -- remove the target file - local librarydir = path.join(target:installdir(), opt and opt.libdir or "lib") - os.vrm(path.join(librarydir, path.filename(target:targetfile()))) - os.tryrm(path.join(librarydir, path.filename(target:symbolfile()))) - - -- remove headers from the include directory - _uninstall_headers(target, opt) -end - --- uninstall headeronly library -function uninstall_headeronly(target, opt) - _uninstall_headers(target, opt) -end - --- uninstall moduleonly library -function uninstall_moduleonly(target, opt) - _uninstall_headers(target, opt) -end diff --git a/xmake/modules/utils/symbols/depend.lua b/xmake/modules/utils/binary/deplibs.lua index 94ca4c7e1..146e79504 100644 --- a/xmake/modules/utils/symbols/depend.lua +++ b/xmake/modules/utils/binary/deplibs.lua @@ -15,7 +15,7 @@ -- Copyright (C) 2015-present, TBOOX Open Source Group. -- -- @author ruki --- @file depend.lua +-- @file deplibs.lua -- -- imports @@ -27,7 +27,7 @@ function _get_all_depends_by_dumpbin(binaryfile, opt) local depends local plat = opt.plat or os.host() local arch = opt.arch or os.arch() - local cachekey = "utils.symbols.depend" + local cachekey = "utils.binary.deplibs" local msvc = toolchain.load("msvc", {plat = plat, arch = arch}) if msvc:check() then local dumpbin = find_tool("dumpbin", {cachekey = cachekey, envs = msvc:runenvs()}) @@ -38,16 +38,8 @@ function _get_all_depends_by_dumpbin(binaryfile, opt) for _, line in ipairs(result:split("\n")) do line = line:trim() if line:endswith(".dll") then - local dependfile - if os.isfile(line) then - dependfile = line - elseif os.isfile(path.join(binarydir, line)) then - dependfile = path.join(binarydir, line) - end - if dependfile then - depends = depends or {} - table.insert(depends, path.absolute(dependfile)) - end + depends = depends or {} + table.insert(depends, line) end end end @@ -60,7 +52,7 @@ function _get_all_depends_by_objdump(binaryfile, opt) local depends local plat = opt.plat or os.host() local arch = opt.arch or os.arch() - local cachekey = "utils.symbols.depend" + local cachekey = "utils.binary.deplibs" local objdump = find_tool("llvm-objdump", {cachekey = cachekey}) or find_tool("objdump", {cachekey = cachekey}) if objdump then local binarydir = path.directory(binaryfile) @@ -76,51 +68,22 @@ function _get_all_depends_by_objdump(binaryfile, opt) if line:startswith("DLL Name:") then local filename = line:split(":")[2]:trim() if filename:endswith(".dll") then - local dependfile - if os.isfile(filename) then - dependfile = filename - elseif os.isfile(path.join(binarydir, filename)) then - dependfile = path.join(binarydir, filename) - end - if dependfile then - depends = depends or {} - table.insert(depends, path.absolute(dependfile)) - end + depends = depends or {} + table.insert(depends, filename) 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 - local dependfile - if os.exists(filename) then - dependfile = filename - elseif os.exists(path.join(binarydir, filename)) then - dependfile = path.join(binarydir, filename) - elseif filename:startswith("@rpath/") then -- TODO - filename = filename:sub(8) - if os.exists(path.join(binarydir, filename)) then - dependfile = path.join(binarydir, filename) - end - end - if dependfile then - depends = depends or {} - table.insert(depends, path.absolute(dependfile)) - end + 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") then - local dependfile - if os.isfile(filename) then - dependfile = filename - elseif os.isfile(path.join(binarydir, filename)) then - dependfile = path.join(binarydir, filename) - end - if dependfile then - depends = depends or {} - table.insert(depends, path.absolute(dependfile)) - end + depends = depends or {} + table.insert(depends, filename) end end end @@ -146,28 +109,23 @@ function _get_all_depends_by_ldd(binaryfile, opt) return end local depends - local cachekey = "utils.symbols.depend" + local cachekey = "utils.binary.deplibs" local ldd = find_tool("ldd", {cachekey = cachekey}) if ldd then local binarydir = path.directory(binaryfile) local result = try { function () return os.iorunv(ldd.program, {binaryfile}) end } if result then for _, line in ipairs(result:split("\n")) do - line = line:split("=>")[2] or line + local splitinfo = line:split("=>") + line = splitinfo[2] + if not line or line:find("not found", 1, true) then + line = splitinfo[1] + end line = line:gsub("%(.+%)", ""):trim() local filename = line:match(".-%.so$") or line:match(".-%.so%.%d+") if filename then - filename = filename:trim() - local dependfile - if os.isfile(filename) then - dependfile = filename - elseif os.isfile(path.join(binarydir, filename)) then - dependfile = path.join(binarydir, filename) - end - if dependfile then - depends = depends or {} - table.insert(depends, path.absolute(dependfile)) - end + depends = depends or {} + table.insert(depends, filename:trim()) end end end @@ -192,7 +150,7 @@ function _get_all_depends_by_readelf(binaryfile, opt) return end local depends - local cachekey = "utils.symbols.depend" + local cachekey = "utils.binary.deplibs" local readelf = find_tool("readelf", {cachekey = cachekey}) if readelf then local binarydir = path.directory(binaryfile) @@ -202,17 +160,8 @@ function _get_all_depends_by_readelf(binaryfile, opt) if line:find("NEEDED", 1, true) then local filename = line:match("Shared library: %[(.-)%]") if filename then - filename = filename:trim() - local dependfile - if os.isfile(filename) then - dependfile = filename - elseif os.isfile(path.join(binarydir, filename)) then - dependfile = path.join(binarydir, filename) - end - if dependfile then - depends = depends or {} - table.insert(depends, path.absolute(dependfile)) - end + depends = depends or {} + table.insert(depends, filename:trim()) end end end @@ -236,7 +185,7 @@ function _get_all_depends_by_otool(binaryfile, opt) return end local depends - local cachekey = "utils.symbols.depend" + local cachekey = "utils.binary.deplibs" local otool = find_tool("otool", {cachekey = cachekey}) if otool then local binarydir = path.directory(binaryfile) @@ -245,22 +194,8 @@ function _get_all_depends_by_otool(binaryfile, opt) for _, line in ipairs(result:split("\n")) do local filename = line:match(".-%.dylib") or line:match(".-%.framework") if filename then - filename = filename:trim() - local dependfile - if os.exists(filename) then - dependfile = filename - elseif os.exists(path.join(binarydir, filename)) then - dependfile = path.join(binarydir, filename) - elseif filename:startswith("@rpath/") then -- TODO - filename = filename:sub(8) - if os.exists(path.join(binarydir, filename)) then - dependfile = path.join(binarydir, filename) - end - end - if dependfile then - depends = depends or {} - table.insert(depends, path.absolute(dependfile)) - end + depends = depends or {} + table.insert(depends, filename:trim()) end end end @@ -270,19 +205,19 @@ end function main(binaryfile, opt) opt = opt or {} - local dumpers = { + local ops = { _get_all_depends_by_objdump, _get_all_depends_by_readelf } if is_host("windows") then - table.insert(dumpers, 2, _get_all_depends_by_dumpbin) + table.insert(ops, 2, _get_all_depends_by_dumpbin) elseif is_host("linux", "bsd") then - table.insert(dumpers, 1, _get_all_depends_by_ldd) + table.insert(ops, 1, _get_all_depends_by_ldd) elseif is_host("macosx") then - table.insert(dumpers, 1, _get_all_depends_by_otool) + table.insert(ops, 1, _get_all_depends_by_otool) end - for _, dump in ipairs(dumpers) do - local depends = dump(binaryfile, opt) + for _, op in ipairs(ops) do + local depends = op(binaryfile, opt) if depends then return depends end diff --git a/xmake/modules/utils/binary/rpath.lua b/xmake/modules/utils/binary/rpath.lua new file mode 100644 index 000000000..674a9edf0 --- /dev/null +++ b/xmake/modules/utils/binary/rpath.lua @@ -0,0 +1,277 @@ +--!A cross-platform build utility based on Lua +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- +-- Copyright (C) 2015-present, TBOOX Open Source Group. +-- +-- @author ruki +-- @file rpath.lua +-- + +-- imports +import("core.base.option") +import("lib.detect.find_tool") + +function _replace_rpath_vars(rpath, opt) + local plat = opt.plat or os.host() + local arch = opt.arch or os.arch() + if plat == "macosx" or plat == "iphoneos" or plat == "appletvos" or plat == "watchos" then + rpath = rpath:gsub("%$ORIGIN", "@loader_path") + else + rpath = rpath:gsub("@loader_path", "$ORIGIN") + rpath = rpath:gsub("@executable_path", "$ORIGIN") + end + return rpath +end + +function _get_rpath_list_by_objdump(binaryfile, opt) + local list + local plat = opt.plat or os.host() + local arch = opt.arch or os.arch() + local cachekey = "utils.binary.rpath" + local objdump = find_tool("llvm-objdump", {cachekey = cachekey}) or find_tool("objdump", {cachekey = cachekey}) + if objdump then + local binarydir = path.directory(binaryfile) + local argv = {"-x", binaryfile} + if plat == "macosx" or plat == "iphoneos" or plat == "appletvos" or plat == "watchos" then + argv = {"--macho", "-x", binaryfile} + end + local result = try { function () return os.iorunv(objdump.program, argv) end } + if result then + local cmd = false + for _, line in ipairs(result:split("\n")) do + line = line:trim() + if plat == "macosx" or plat == "iphoneos" or plat == "appletvos" or plat == "watchos" then + if not cmd and line:find("cmd LC_RPATH", 1, true) then + cmd = true + elseif cmd and (line:find("cmd ", 1, true) or line:find("Load command", 1, true)) then + cmd = false + end + if cmd then + local p = line:match("path (.-) %(") + if p then + list = list or {} + table.insert(list, p:trim()) + end + end + else + if line:startswith("RUNPATH") or line:startswith("RPATH") then + local p = line:split("%s+")[2] + if p then + list = list or {} + table.insert(list, p:trim()) + end + end + end + end + end + end + return list +end + +-- $ readelf -d build/linux/x86_64/release/test +-- +-- Dynamic section at offset 0x2db8 contains 29 entries: +-- Tag Type Name/Value +-- 0x0000000000000001 (NEEDED) Shared library: [libfoo.so] +-- 0x0000000000000001 (NEEDED) Shared library: [libstdc++.so.6] +-- 0x0000000000000001 (NEEDED) Shared library: [libm.so.6] +-- 0x0000000000000001 (NEEDED) Shared library: [libgcc_s.so.1] +-- 0x0000000000000001 (NEEDED) Shared library: [libc.so.6] +-- 0x000000000000001d (RUNPATH) Library runpath: [$ORIGIN] +-- ... +-- 0x000000000000000f (RPATH) Library rpath: [$ORIGIN] +function _get_rpath_list_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 + return + end + local list + local cachekey = "utils.binary.rpath" + local readelf = find_tool("readelf", {cachekey = cachekey}) + if readelf then + local binarydir = path.directory(binaryfile) + local result = try { function () return os.iorunv(readelf.program, {"-d", binaryfile}) end } + if result then + for _, line in ipairs(result:split("\n")) do + if line:find("RUNPATH", 1, true) then + local p = line:match("Library runpath: %[(.-)%]") + if p then + list = list or {} + table.insert(list, p:trim()) + end + elseif line:find("RPATH", 1, true) then + local p = line:match("Library rpath: %[(.-)%]") + if p then + list = list or {} + table.insert(list, p:trim()) + end + end + end + end + end + return list +end + +-- $ otool -l build/iphoneos/arm64/release/test +-- build/iphoneos/arm64/release/test: +-- cmd LC_RPATH +-- cmdsize 32 +-- path @loader_path (offset 12) +-- +function _get_rpath_list_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 + return + end + local list + local cachekey = "utils.binary.rpath" + local otool = find_tool("otool", {cachekey = cachekey}) + if otool then + local binarydir = path.directory(binaryfile) + local result = try { function () return os.iorunv(otool.program, {"-l", binaryfile}) end } + if result then + local cmd = false + for _, line in ipairs(result:split("\n")) do + if not cmd and line:find("cmd LC_RPATH", 1, true) then + cmd = true + elseif cmd and (line:find("cmd ", 1, true) or line:find("Load command", 1, true)) then + cmd = false + end + if cmd then + local p = line:match("path (.-) %(") + if p then + list = list or {} + table.insert(list, p:trim()) + end + end + end + end + end + return list +end + +-- install_name_tool -add_rpath <rpath> binaryfile +function _insert_rpath_by_install_name_tool(binaryfile, rpath, 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 + return false + end + local ok = try { function () + os.vrunv("install_name_tool", {"-add_rpath", rpath, binaryfile}) + return true + end } + return ok +end + +-- install_name_tool -rpath <rpath_old> <rpath_new> binaryfile +function _change_rpath_by_install_name_tool(binaryfile, rpath_old, rpath_new, 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 + return false + end + local ok = try { function () + os.vrunv("install_name_tool", {"-rpath", rpath_old, rpath_new, binaryfile}) + return true + end } + return ok +end + +-- install_name_tool -delete_rpath <rpath> binaryfile +function _remove_rpath_by_install_name_tool(binaryfile, rpath, 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 + return false + end + local ok = try { function () + os.vrunv("install_name_tool", {"-delete_rpath", rpath, binaryfile}) + return true + end } + return ok +end + +-- get rpath list +function list(binaryfile, opt) + opt = opt or {} + local ops = { + _get_rpath_list_by_objdump, + _get_rpath_list_by_readelf + } + if is_host("macosx") then + table.insert(ops, 1, _get_rpath_list_by_otool) + end + for _, op in ipairs(ops) do + local result = op(binaryfile, opt) + if result then + return result + end + end +end + +-- insert rpath +function insert(binaryfile, rpath, opt) + opt = opt or {} + local ops = {} + if is_host("macosx") then + table.insert(ops, 1, _insert_rpath_by_install_name_tool) + end + rpath = _replace_rpath_vars(rpath, opt) + for _, op in ipairs(ops) do + if op(binaryfile, rpath, opt) then + break + end + end +end + +-- change rpath +function change(binaryfile, rpath_old, rpath_new, opt) + opt = opt or {} + local ops = {} + if is_host("macosx") then + table.insert(ops, 1, _change_rpath_by_install_name_tool) + end + rpath_old = _replace_rpath_vars(rpath_old, opt) + rpath_new = _replace_rpath_vars(rpath_new, opt) + for _, op in ipairs(ops) do + if op(binaryfile, rpath_old, rpath_new, opt) then + break + end + end +end + +-- remove rpath +function remove(binaryfile, rpath, opt) + opt = opt or {} + local ops = {} + if is_host("macosx") then + table.insert(ops, 1, _remove_rpath_by_install_name_tool) + end + rpath = _replace_rpath_vars(rpath, opt) + for _, op in ipairs(ops) do + if op(binaryfile, rpath, opt) then + break + end + end +end + +-- clean rpath +function clean(binaryfile, opt) + for _, rpath in ipairs(list(binaryfile, opt)) do + remove(binaryfile, rpath, opt) + end +end |
