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 | |
| parent | 45852e6ffcd3f043d961574e72f40d287a2898ab (diff) | |
| parent | 56ca5adad669d45f400198b0c99a1d20f546970c (diff) | |
Merge pull request #5335 from xmake-io/install
Improve installation
35 files changed, 1187 insertions, 912 deletions
diff --git a/tests/actions/install/.gitignore b/tests/actions/install/.gitignore new file mode 100644 index 000000000..152105761 --- /dev/null +++ b/tests/actions/install/.gitignore @@ -0,0 +1,8 @@ +# Xmake cache +.xmake/ +build/ + +# MacOS Cache +.DS_Store + + diff --git a/tests/actions/install/src/foo.cpp b/tests/actions/install/src/foo.cpp new file mode 100644 index 000000000..1a1fb3425 --- /dev/null +++ b/tests/actions/install/src/foo.cpp @@ -0,0 +1,5 @@ +#include "foo.h" + +int add(int a, int b) { + return a + b; +} diff --git a/tests/actions/install/src/foo.h b/tests/actions/install/src/foo.h new file mode 100644 index 000000000..20df0baa4 --- /dev/null +++ b/tests/actions/install/src/foo.h @@ -0,0 +1,17 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(_WIN32) +# define __export __declspec(dllexport) +#elif defined(__GNUC__) && ((__GNUC__ >= 4) || (__GNUC__ == 3 && __GNUC_MINOR__ >= 3)) +# define __export __attribute__((visibility("default"))) +#else +# define __export +#endif + +__export int add(int a, int b); + +#ifdef __cplusplus +} +#endif diff --git a/tests/actions/install/src/foo.txt b/tests/actions/install/src/foo.txt new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/tests/actions/install/src/foo.txt diff --git a/tests/actions/install/src/main.cpp b/tests/actions/install/src/main.cpp new file mode 100644 index 000000000..d55d4d342 --- /dev/null +++ b/tests/actions/install/src/main.cpp @@ -0,0 +1,7 @@ +#include "foo.h" +#include <iostream> + +int main(int argc, char** argv) { + std::cout << "add(1, 2) = " << add(1, 2) << std::endl; + return 0; +} diff --git a/tests/actions/install/test.lua b/tests/actions/install/test.lua new file mode 100644 index 000000000..4edc90275 --- /dev/null +++ b/tests/actions/install/test.lua @@ -0,0 +1,10 @@ +function main(t) + if is_host("windows", "linux", "macosx") and os.arch():startswith("x") then + os.vrun("xmake -y") + os.vrun("xmake run app") + os.vrun("xmake install -o build/usr") + if not is_host("linux") then -- TODO, change rpath has been not supported yet on linux. + os.vrun("./build/usr/app/bin/app" .. (is_host("windows") and ".exe" or "")) + end + end +end diff --git a/tests/actions/install/xmake.lua b/tests/actions/install/xmake.lua new file mode 100644 index 000000000..652857116 --- /dev/null +++ b/tests/actions/install/xmake.lua @@ -0,0 +1,27 @@ +add_rules("mode.debug", "mode.release") + +set_version("1.0.1", {soname = true}) + +add_requires("libzip", {system = false, configs = {shared = true}}) + +target("foo") + set_kind("shared") + add_files("src/foo.cpp") + add_packages("libzip", {public = true}) + add_headerfiles("src/foo.h", {public = true}) + add_installfiles("src/foo.txt", {prefixdir = "assets", public = true}) + set_prefixdir("/", {libdir = "foo_lib"}) + +target("app") + set_kind("binary") + add_deps("foo") + add_files("src/main.cpp") + set_prefixdir("app", {libdir = "app_lib"}) + add_rpathdirs("@loader_path/../app_lib", {installonly = true}) + +includes("@builtin/xpack") + +xpack("test") + add_targets("app") + set_formats("zip") + diff --git a/xmake/actions/clean/main.lua b/xmake/actions/clean/main.lua index 6470208d5..413a3121e 100644 --- a/xmake/actions/clean/main.lua +++ b/xmake/actions/clean/main.lua @@ -104,17 +104,19 @@ end -- clean target function _clean(targetname) - - -- clean the given target if targetname then local target = project.target(targetname) _clean_target(target) else _clean_targets(project.ordertargets()) end +end - -- remove the configure directory if remove all +-- clean configuration cache +function _clean_configs() if option.get("all") then + -- we need to close it first after removing file lock + project.filelock():close() remove_files(config.directory()) end end @@ -142,7 +144,6 @@ function _try_clean() end end --- main function main() -- try cleaning it using third-party buildsystem if xmake.lua not exists @@ -176,6 +177,9 @@ function main() -- unlock the whole project project.unlock() + -- we must call it after unlocking project because it will remove project lockfile + _clean_configs() + -- leave project directory os.cd(oldir) end diff --git a/xmake/actions/install/install.lua b/xmake/actions/install/install.lua index ae9857ed3..95f634fe6 100644 --- a/xmake/actions/install/install.lua +++ b/xmake/actions/install/install.lua @@ -110,13 +110,11 @@ function main(targetname, group_pattern) local targets = {} if targetname and not targetname:startswith("__") then local target = project.target(targetname) - table.join2(targets, target:orderdeps()) table.insert(targets, target) else for _, target in ipairs(project.ordertargets()) do local group = target:get("group") if (target:is_default() and not group_pattern) or targetname == "__all" or (group_pattern and group and group:match(group_pattern)) then - table.join2(targets, target:orderdeps()) table.insert(targets, target) end end diff --git a/xmake/actions/install/install_admin.lua b/xmake/actions/install/install_admin.lua index ba5101324..93fbf5beb 100644 --- a/xmake/actions/install/install_admin.lua +++ b/xmake/actions/install/install_admin.lua @@ -25,39 +25,30 @@ import("core.project.project") import("core.platform.platform") import("install") --- install function main(targetname, group_pattern, installdir, prefix) - local verbose = option.get("verbose") + if group_pattern and #group_pattern == 0 then + group_pattern = nil + end + if installdir and #installdir == 0 then + installdir = nil + end - -- enter project directory os.cd(project.directory()) - - -- load config config.load() - - -- load platform platform.load(config.plat()) -- save the current option and push a new option context option.save() - - -- preserve verbose option option.set("verbose", verbose) - - -- pass installdir to option if installdir then option.set("installdir", installdir) end - - -- pass prefix to option if prefix then option.set("prefix", prefix) end -- install target install(targetname, group_pattern) - - -- restore the previous option context option.restore() end diff --git a/xmake/actions/install/main.lua b/xmake/actions/install/main.lua index 1d9f8b1c1..24081f874 100644 --- a/xmake/actions/install/main.lua +++ b/xmake/actions/install/main.lua @@ -116,7 +116,10 @@ function main() if sudo.has() and option.get("admin") then -- install target with administrator permission - sudo.execl(path.join(os.scriptdir(), "install_admin.lua"), {targetname or (option.get("all") and "__all" or "__def"), group_pattern, option.get("installdir"), option.get("prefix")}) + sudo.execl(path.join(os.scriptdir(), "install_admin.lua"), { + targetname or (option.get("all") and "__all" or "__def"), + group_pattern or "", option.get("installdir") or "", + option.get("prefix")}) cprint("${color.success}install ok!") ok = true end diff --git a/xmake/actions/uninstall/main.lua b/xmake/actions/uninstall/main.lua index 5c403b85e..f2ae59eb9 100644 --- a/xmake/actions/uninstall/main.lua +++ b/xmake/actions/uninstall/main.lua @@ -68,7 +68,10 @@ function main() if sudo.has() and option.get("admin") then -- uninstall target with administrator permission - sudo.execl(path.join(os.scriptdir(), "uninstall_admin.lua"), {targetname or "__all", option.get("installdir"), option.get("prefix")}) + sudo.execl(path.join(os.scriptdir(), "uninstall_admin.lua"), { + targetname or "__all", + option.get("installdir") or "", + option.get("prefix")}) -- trace cprint("${color.success}uninstall ok!") diff --git a/xmake/actions/uninstall/uninstall.lua b/xmake/actions/uninstall/uninstall.lua index 5aade0313..76feafd56 100644 --- a/xmake/actions/uninstall/uninstall.lua +++ b/xmake/actions/uninstall/uninstall.lua @@ -110,13 +110,11 @@ function main(targetname) local targets = {} if targetname and not targetname:startswith("__") then local target = project.target(targetname) - table.join2(targets, target:orderdeps()) table.insert(targets, target) else for _, target in ipairs(project.ordertargets()) do local group = target:get("group") if (target:is_default() and not group_pattern) or targetname == "__all" or (group_pattern and group and group:match(group_pattern)) then - table.join2(targets, target:orderdeps()) table.insert(targets, target) end end diff --git a/xmake/actions/uninstall/uninstall_admin.lua b/xmake/actions/uninstall/uninstall_admin.lua index f597dea11..68b496d87 100644 --- a/xmake/actions/uninstall/uninstall_admin.lua +++ b/xmake/actions/uninstall/uninstall_admin.lua @@ -25,39 +25,27 @@ import("core.project.project") import("core.platform.platform") import("uninstall") --- uninstall function main(targetname, installdir, prefix) - local verbose = option.get("verbose") + if installdir and #installdir == 0 then + installdir = nil + end - -- enter project directory os.cd(project.directory()) - - -- load config config.load() - - -- load platform platform.load(config.plat()) -- save the current option and push a new option context option.save() - - -- preserve verbose option option.set("verbose", verbose) - - -- pass installdir to option if installdir then option.set("installdir", installdir) end - - -- pass prefix to option if prefix then option.set("prefix", prefix) end -- uninstall target uninstall(targetname ~= "__all" and targetname or nil) - - -- restore the previous option context option.restore() end diff --git a/xmake/core/base/os.lua b/xmake/core/base/os.lua index b0545afc8..9160aa534 100644 --- a/xmake/core/base/os.lua +++ b/xmake/core/base/os.lua @@ -503,7 +503,7 @@ function os.rm(filepath, opt) return false, errors end if opt.emptydirs then - ok, errors = os._rm_empty_parentdirs(filepath) + ok, errors = os._rm_empty_parentdirs(_filepath) if not ok then return false, errors end diff --git a/xmake/core/project/policy.lua b/xmake/core/project/policy.lua index 7299de719..2a2195cdb 100644 --- a/xmake/core/project/policy.lua +++ b/xmake/core/project/policy.lua @@ -68,6 +68,8 @@ function policy.policies() ["build.sanitizer.leak"] = {description = "Enable leak sanitizer for c/c++ building.", type = "boolean"}, -- Enable undefined sanitizer for c/c++ building. ["build.sanitizer.undefined"] = {description = "Enable undefined sanitizer for c/c++ building.", type = "boolean"}, + -- Enable build rpath + ["build.rpath"] = {description = "Enable build rpath.", default = true, type = "boolean"}, -- Enable C++ modules for C++ building, even if no .mpp is involved in the compilation ["build.c++.modules"] = {description = "Enable C++ modules for C++ building.", type = "boolean"}, -- Enable std module @@ -96,6 +98,8 @@ function policy.policies() ["windows.manifest.uac.ui"] = {description = "Enable windows manifest UAC.", type = "boolean"}, -- Automatically build before running ["run.autobuild"] = {description = "Automatically build before running.", type = "boolean"}, + -- Enable install rpath + ["install.rpath"] = {description = "Enable install rpath.", default = true, type = "boolean"}, -- Preprocessor configuration for ccache/distcc, we can disable linemarkers to speed up preprocess ["preprocessor.linemarkers"] = {description = "Enable linemarkers for preprocessor.", default = true, type = "boolean"}, -- Preprocessor configuration for ccache/distcc, we can disable it to avoid cache object file with __DATE__, __TIME__ diff --git a/xmake/core/project/target.lua b/xmake/core/project/target.lua index 0ed7465d4..e19ea07c5 100644 --- a/xmake/core/project/target.lua +++ b/xmake/core/project/target.lua @@ -1623,20 +1623,56 @@ function _instance:rundir() return baseoption.get("workdir") or self:get("rundir") or path.directory(self:targetfile()) end --- get install directory -function _instance:installdir() +-- get prefix directory +function _instance:prefixdir() + return self:get("prefixdir") +end + +-- get the installed binary directory +function _instance:bindir() + local bindir = self:extraconf("prefixdir", self:prefixdir(), "bindir") + if bindir == nil then + bindir = "bin" + end + return self:installdir(bindir) +end + +-- get the installed library directory +function _instance:libdir() + local libdir = self:extraconf("prefixdir", self:prefixdir(), "libdir") + if libdir == nil then + libdir = "lib" + end + return self:installdir(libdir) +end - -- get it from the cache +-- get the installed include directory +function _instance:includedir() + local includedir = self:extraconf("prefixdir", self:prefixdir(), "includedir") + if includedir == nil then + includedir = "include" + end + return self:installdir(includedir) +end + +-- get install directory +function _instance:installdir(...) + opt = opt or {} local installdir = baseoption.get("installdir") if not installdir then - -- DESTDIR: be compatible with https://www.gnu.org/prep/standards/html_node/DESTDIR.html installdir = self:get("installdir") or os.getenv("INSTALLDIR") or os.getenv("PREFIX") or os.getenv("DESTDIR") or platform.get("installdir") if installdir then installdir = installdir:trim() end end - return installdir + if installdir then + local prefixdir = self:prefixdir() + if prefixdir then + installdir = path.join(installdir, prefixdir) + end + return path.normalize(path.join(installdir, ...)) + end end -- get package directory @@ -2000,7 +2036,7 @@ end -- get the header files function _instance:headerfiles(outputdir, opt) opt = opt or {} - local headerfiles = self:get("headerfiles") + local headerfiles = self:get("headerfiles", opt) or {} -- add_headerfiles("src/*.h", {install = false}) -- @see https://github.com/xmake-io/xmake/issues/2577 if opt.installonly then @@ -2016,13 +2052,12 @@ function _instance:headerfiles(outputdir, opt) return end - local headerdir = outputdir - if not headerdir then - if self:installdir() then - headerdir = path.join(self:installdir(), "include") + if not outputdir then + if self:includedir() then + outputdir = self:includedir() end end - return match_copyfiles(self, "headerfiles", headerdir, {copyfiles = headerfiles}) + return match_copyfiles(self, "headerfiles", outputdir, {copyfiles = headerfiles}) end -- get the configuration files @@ -2036,8 +2071,9 @@ function _instance:configfiles(outputdir) end -- get the install files -function _instance:installfiles(outputdir) - return match_copyfiles(self, "installfiles", outputdir or self:installdir()) +function _instance:installfiles(outputdir, opt) + local installfiles = self:get("installfiles", opt) or {} + return match_copyfiles(self, "installfiles", outputdir or self:installdir(), {copyfiles = installfiles}) end -- get the extra files @@ -2737,6 +2773,7 @@ function target.apis() , "target.set_runargs" , "target.set_exceptions" , "target.set_encodings" + , "target.set_prefixdir" -- target.add_xxx , "target.add_deps" , "target.add_rules" diff --git a/xmake/core/sandbox/modules/os.lua b/xmake/core/sandbox/modules/os.lua index 9004f66ee..d626d0372 100644 --- a/xmake/core/sandbox/modules/os.lua +++ b/xmake/core/sandbox/modules/os.lua @@ -102,21 +102,21 @@ function sandbox_os.cp(srcpath, dstpath, opt) end -- move file or directory -function sandbox_os.mv(srcpath, dstpath) +function sandbox_os.mv(srcpath, dstpath, opt) assert(srcpath and dstpath) srcpath = tostring(srcpath) dstpath = tostring(dstpath) - local ok, errors = os.mv(vformat(srcpath), vformat(dstpath)) + local ok, errors = os.mv(vformat(srcpath), vformat(dstpath), opt) if not ok then os.raise(errors) end end -- remove files or directories -function sandbox_os.rm(filepath) +function sandbox_os.rm(filepath, opt) assert(filepath) filepath = tostring(filepath) - local ok, errors = os.rm(vformat(filepath)) + local ok, errors = os.rm(vformat(filepath), opt) if not ok then os.raise(errors) end @@ -161,12 +161,12 @@ function sandbox_os.vrm(filepath, opt) end -- link file or directory with the verbose info -function sandbox_os.vln(srcpath, dstpath) +function sandbox_os.vln(srcpath, dstpath, opt) assert(srcpath and dstpath) if option.get("verbose") then utils.cprint("${dim}> link %s to %s", srcpath, dstpath) end - return sandbox_os.ln(srcpath, dstpath) + return sandbox_os.ln(srcpath, dstpath, opt) end -- try to copy file or directory @@ -176,9 +176,9 @@ function sandbox_os.trycp(srcpath, dstpath, opt) end -- try to move file or directory -function sandbox_os.trymv(srcpath, dstpath) +function sandbox_os.trymv(srcpath, dstpath, opt) assert(srcpath and dstpath) - return os.mv(vformat(srcpath), vformat(dstpath)) + return os.mv(vformat(srcpath), vformat(dstpath), opt) end -- try to remove files or directories 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 diff --git a/xmake/plugins/pack/archive.lua b/xmake/plugins/pack/archive.lua index dd1861ca4..5d59c05fb 100644 --- a/xmake/plugins/pack/archive.lua +++ b/xmake/plugins/pack/archive.lua @@ -55,6 +55,7 @@ function _pack_archive(package) local oldir = os.cd(rootdir) local archivefiles = os.files("**") os.cd(oldir) + os.tryrm(package:outputfile()) archive.archive(path.absolute(package:outputfile()), archivefiles, {curdir = rootdir, compress = "best"}) end diff --git a/xmake/plugins/pack/batchcmds.lua b/xmake/plugins/pack/batchcmds.lua index 8091134b2..4017ed175 100644 --- a/xmake/plugins/pack/batchcmds.lua +++ b/xmake/plugins/pack/batchcmds.lua @@ -20,124 +20,274 @@ -- imports import("core.base.option") +import("core.base.hashset") import("utils.archive") +import("utils.binary.deplibs", {alias = "get_depend_libraries"}) import("private.utils.batchcmds") --- install headers -function _install_headers(target, batchcmds_, 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 - batchcmds_:cp(srcheader, dstheader) - end - i = i + 1 +function _get_target_bindir(package, target) + local bindir = package:bindir() + local prefixdir = target:prefixdir() + if prefixdir then + bindir = path.join(package:installdir(), prefixdir, target:extraconf("prefixdir", prefixdir, "bindir") or "bin") + end + return path.normalize(bindir) +end + +function _get_target_libdir(package, target) + local libdir = package:libdir() + local prefixdir = target:prefixdir() + if prefixdir then + libdir = path.join(package:installdir(), prefixdir, target:extraconf("prefixdir", prefixdir, "libdir") or "lib") + end + return path.normalize(libdir) +end + +function _get_target_includedir(package, target) + local includedir = package:includedir() + local prefixdir = target:prefixdir() + if prefixdir then + includedir = path.join(package:installdir(), prefixdir, target:extraconf("prefixdir", prefixdir, "includedir") or "include") + end + return path.normalize(includedir) +end + +function _get_target_installdir(package, target) + local installdir = package:installdir() + local prefixdir = target:prefixdir() + if prefixdir then + installdir = path.join(package:installdir(), prefixdir) + end + 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(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 --- install shared libraries for package -function _install_shared_for_package(target, pkg, batchcmds_, 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) - batchcmds_:cp(dllpath, path.join(outputdir, dllname)) - _g.installed_dllfiles[dllpath] = true +function _get_target_package_libfiles(target, opt) + 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(batchcmds_, 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(batchcmds_, srcfile_symlink, outputdir) + batchcmds_:cp(srcfile, path.join(outputdir, path.filename(srcfile)), {symlink = true, force = true}) + else + batchcmds_:cp(srcfile, path.join(outputdir, path.filename(srcfile))) + end end --- install shared libraries for packages -function _install_shared_for_packages(target, batchcmds_, outputdir) - _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, batchcmds_, outputdir) +-- update install rpath, we can only get and update rpathdirs with `{installonly = true}` +-- e.g. add_rpathdirs("@loader_path/../lib", {installonly = true}) +function _update_target_install_rpath(target, batchcmds_, opt) + if target:is_plat("windows", "mingw") then + return + end + local package = opt.package + local bindir = _get_target_bindir(package, target) + local targetfile = path.join(bindir, target:filename()) + if target:policy("install.rpath") then + batchcmds_:clean_rpath(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 + batchcmds_:insert_rpath(targetfile, rpathdir, {plat = target:plat(), arch = target:arch()}) + end + end + end end - _g.installed_packages[pkg:name()] = true end end end --- uninstall headers -function _uninstall_headers(target, batchcmds_, includedir) - local _, dstheaders = target:headerfiles(includedir, {installonly = true}) - for _, dstheader in ipairs(dstheaders) do - batchcmds_:rm(dstheader, {emptydirs = true}) +-- install target files +function _install_target_files(target, batchcmds_, opt) + local package = opt.package + local srcfiles, dstfiles = target:installfiles(_get_target_installdir(package, target)) + if srcfiles and dstfiles then + for idx, srcfile in ipairs(srcfiles) do + batchcmds_:cp(srcfile, dstfiles[idx]) + end + end + for _, dep in ipairs(target:orderdeps()) do + local srcfiles, dstfiles = dep:installfiles(_get_target_installdir(package, dep), {interface = true}) + if srcfiles and dstfiles then + for idx, srcfile in ipairs(srcfiles) do + batchcmds_:cp(srcfile, dstfiles[idx]) + end + end end end --- uninstall shared libraries for package -function _uninstall_shared_for_package(target, pkg, batchcmds_, outputdir) - for _, dllpath in ipairs(table.wrap(pkg:get("libfiles"))) do - if dllpath:endswith(".dll") then - local dllname = path.filename(dllpath) - batchcmds_:rm(path.join(outputdir, dllname), {emptydirs = true}) +-- install target headers +function _install_target_headers(target, batchcmds_, opt) + local package = opt.package + local srcheaders, dstheaders = target:headerfiles(_get_target_includedir(package, target), {installonly = true}) + if srcheaders and dstheaders then + for idx, srcheader in ipairs(srcheaders) do + batchcmds_:cp(srcheader, dstheaders[idx]) + end + end + for _, dep in ipairs(target:orderdeps()) do + local srcheaders, dstheaders = dep:headerfiles(_get_target_includedir(package, dep), {installonly = true, interface = true}) + if srcheaders and dstheaders then + for idx, srcheader in ipairs(srcheaders) do + batchcmds_:cp(srcheader, dstheaders[idx]) + end end end end --- uninstall shared libraries for packages -function _uninstall_shared_for_packages(target, batchcmds_, 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, batchcmds_, outputdir) +-- install target shared libraries +function _install_target_shared_libraries(target, batchcmds_, opt) + local package = opt.package + local bindir = target:is_plat("windows", "mingw") and _get_target_bindir(package, target) or _get_target_libdir(package, target) + + -- 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 - _g.uninstalled_packages[pkg:name()] = true 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) + _copy_file_with_symlinks(batchcmds_, libfile, bindir) end end --- on install binary target command -function _on_target_installcmd_binary(target, batchcmds_, opt) +-- uninstall target files +function _uninstall_target_files(target, batchcmds_, opt) local package = opt.package - local bindir = package:bindir() + local _, dstfiles = target:installfiles(_get_target_installdir(package, target)) + for _, dstfile in ipairs(dstfiles) do + batchcmds_:rm(dstfile, {emptydirs = true}) + end + for _, dep in ipairs(target:orderdeps()) do + local _, dstfiles = dep:installfiles(_get_target_installdir(package, dep), {interface = true}) + for _, dstfile in ipairs(dstfiles) do + batchcmds_:rm(dstfile, {emptydirs = true}) + end + end +end - -- install target file - batchcmds_:cp(target:targetfile(), path.join(bindir, target:filename())) - if os.isfile(target:symbolfile()) then - batchcmds_:cp(target:symbolfile(), path.join(bindir, path.filename(target:symbolfile()))) +-- uninstall target headers +function _uninstall_target_headers(target, batchcmds_, opt) + local package = opt.package + local _, dstheaders = target:headerfiles(_get_target_includedir(package, target), {installonly = true}) + for _, dstheader in ipairs(dstheaders) do + batchcmds_:rm(dstheader, {emptydirs = true}) end + for _, dep in ipairs(target:orderdeps()) do + local _, dstheaders = dep:headerfiles(_get_target_includedir(package, dep), {installonly = true, interface = true}) + for _, dstheader in ipairs(dstheaders) do + batchcmds_:rm(dstheader, {emptydirs = true}) + end + end +end - -- install the dependent shared/windows (*.dll) target - -- @see https://github.com/xmake-io/xmake/issues/961 - _g.installed_dllfiles = _g.installed_dllfiles or {} +-- uninstall target shared libraries +function _uninstall_target_shared_libraries(target, batchcmds_, opt) + local package = opt.package + local bindir = target:is_plat("windows", "mingw") and _get_target_bindir(package, target) or _get_target_libdir(package, target) + + -- 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 - if not _g.installed_dllfiles[depfile] then - batchcmds_:cp(depfile, path.join(bindir, path.filename(depfile))) - _g.installed_dllfiles[depfile] = true - end + table.insert(libfiles, depfile) end end - -- install all shared libraries in packages in all deps - _install_shared_for_packages(dep, batchcmds_, bindir) + table.join2(libfiles, _get_target_package_libfiles(dep, {interface = true})) end + table.join2(libfiles, _get_target_package_libfiles(target)) - -- install shared libraries for all packages - _install_shared_for_packages(target, batchcmds_, bindir) + -- 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) + batchcmds_:rm(libfile, path.join(bindir, filename), {emptydirs = true}) + end +end + +-- on install binary target command +function _on_target_installcmd_binary(target, batchcmds_, opt) + local package = opt.package + local bindir = _get_target_bindir(package, target) + batchcmds_:cp(target:targetfile(), path.join(bindir, target:filename())) + if os.isfile(target:symbolfile()) then + batchcmds_:cp(target:symbolfile(), path.join(bindir, path.filename(target:symbolfile()))) + end + _install_target_shared_libraries(target, batchcmds_, opt) + _update_target_install_rpath(target, batchcmds_, opt) end -- on install shared target command function _on_target_installcmd_shared(target, batchcmds_, opt) local package = opt.package - local bindir = package:bindir() - local libdir = package:libdir() - local includedir = package:includedir() + local bindir = target:is_plat("windows", "mingw") and _get_target_bindir(package, target) or _get_target_libdir(package, target) + local libdir = _get_target_libdir(package, target) - -- install target file - batchcmds_:cp(target:targetfile(), path.join(bindir, target:filename())) + _copy_file_with_symlinks(batchcmds_, target:targetfile(), bindir) if os.isfile(target:symbolfile()) then batchcmds_:cp(target:symbolfile(), path.join(bindir, path.filename(target:symbolfile()))) end @@ -151,36 +301,26 @@ function _on_target_installcmd_shared(target, batchcmds_, opt) batchcmds_:cp(targetfile_lib, path.join(libdir, path.filename(targetfile_lib))) end - -- install shared libraries for all packages - _install_shared_for_packages(target, batchcmds_, bindir) - - -- install headers - _install_headers(target, batchcmds_, includedir) + _install_target_headers(target, batchcmds_, opt) + _install_target_shared_libraries(target, batchcmds_, opt) end -- on install static target command function _on_target_installcmd_static(target, batchcmds_, opt) local package = opt.package - local libdir = package:libdir() - local includedir = package:includedir() + local libdir = _get_target_libdir(package, target) - -- install target file batchcmds_:cp(target:targetfile(), path.join(libdir, target:filename())) if os.isfile(target:symbolfile()) then batchcmds_:cp(target:symbolfile(), path.join(libdir, path.filename(target:symbolfile()))) end - -- install headers - _install_headers(target, batchcmds_, includedir) + _install_target_headers(target, batchcmds_, opt) end -- on install headeronly target command function _on_target_installcmd_headeronly(target, batchcmds_, opt) - local package = opt.package - local includedir = package:includedir() - - -- install headers - _install_headers(target, batchcmds_, includedir) + _install_target_headers(target, batchcmds_, opt) end -- on install source target command @@ -216,76 +356,60 @@ function _on_target_installcmd(target, batchcmds_, opt) end -- install target files - local srcfiles, dstfiles = target:installfiles(package:installdir()) - for idx, srcfile in ipairs(srcfiles) do - batchcmds_:cp(srcfile, dstfiles[idx]) - end + _install_target_files(target, batchcmds_, opt) end -- on uninstall binary target command function _on_target_uninstallcmd_binary(target, batchcmds_, opt) local package = opt.package - local bindir = package:bindir() + local bindir = _get_target_bindir(package, target) -- uninstall target file batchcmds_:rm(path.join(bindir, target:filename()), {emptydirs = true}) batchcmds_:rm(path.join(bindir, path.filename(target:symbolfile())), {emptydirs = true}) - -- 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:is_shared() then - batchcmds_:rm(path.join(bindir, path.filename(dep:targetfile())), {emptydirs = true}) - end - _uninstall_shared_for_packages(dep, batchcmds_, bindir) - end - - -- uninstall shared libraries for packages - _uninstall_shared_for_packages(target, batchcmds_, bindir) + -- uninstall target shared libraries + _uninstall_target_shared_libraries(target, batchcmds_, opt) end -- on uninstall shared target command function _on_target_uninstallcmd_shared(target, batchcmds_, opt) local package = opt.package - local bindir = package:bindir() - local libdir = package:libdir() - local includedir = package:includedir() + local bindir = target:is_plat("windows", "mingw") and _get_target_bindir(package, target) or _get_target_libdir(package, target) + local libdir = _get_target_libdir(package, target) -- uninstall target file batchcmds_:rm(path.join(bindir, target:filename()), {emptydirs = true}) batchcmds_:rm(path.join(bindir, path.filename(target:symbolfile())), {emptydirs = true}) - -- remove *.lib for shared/windows (*.dll) target + -- uninstall *.lib for shared/windows (*.dll) target -- @see https://github.com/xmake-io/xmake/issues/714 local targetfile = target:targetfile() batchcmds_:rm(path.join(libdir, path.basename(targetfile) .. (target:is_plat("mingw") and ".dll.a" or ".lib")), {emptydirs = true}) - -- remove headers from the include directory - _uninstall_headers(target, batchcmds_, includedir) + -- uninstall target headers + _uninstall_target_headers(target, batchcmds_, opt) - -- uninstall shared libraries for packages - _uninstall_shared_for_packages(target, batchcmds_, bindir) + -- uninstall target shared libraries + _uninstall_target_shared_libraries(target, batchcmds_, opt) end -- on uninstall static target command function _on_target_uninstallcmd_static(target, batchcmds_, opt) local package = opt.package - local libdir = package:libdir() - local includedir = package:includedir() + local libdir = _get_target_libdir(package, target) -- uninstall target file batchcmds_:rm(path.join(libdir, target:filename()), {emptydirs = true}) batchcmds_:rm(path.join(libdir, path.filename(target:symbolfile())), {emptydirs = true}) -- remove headers from the include directory - _uninstall_headers(target, batchcmds_, includedir) + _uninstall_target_headers(target, batchcmds_, opt) end -- on uninstall headeronly target command function _on_target_uninstallcmd_headeronly(target, batchcmds_, opt) - local package = opt.package - local includedir = package:includedir() - _uninstall_headers(target, batchcmds_, includedir) + _uninstall_target_headers(target, batchcmds_, opt) end -- on uninstall source target command @@ -314,10 +438,7 @@ function _on_target_uninstallcmd(target, batchcmds_, opt) end -- uninstall target files - local _, dstfiles = target:installfiles(package:installdir()) - for _, dstfile in ipairs(dstfiles) do - batchcmds_:rm(dstfile, {emptydirs = true}) - end + _uninstall_target_files(target, batchcmds_, opt) end -- get build commands from targets diff --git a/xmake/plugins/pack/xpack.lua b/xmake/plugins/pack/xpack.lua index 89ee5a54b..203fcbe6d 100644 --- a/xmake/plugins/pack/xpack.lua +++ b/xmake/plugins/pack/xpack.lua @@ -453,7 +453,7 @@ end -- get the binary directory function xpack:bindir() - local bindir = self:get("bindir") + local bindir = self:get("bindir") or self:extraconf("prefixdir", self:prefixdir(), "bindir") if bindir == nil then bindir = "bin" end @@ -462,7 +462,7 @@ end -- get the library directory function xpack:libdir() - local libdir = self:get("libdir") + local libdir = self:get("libdir") or self:extraconf("prefixdir", self:prefixdir(), "libdir") if libdir == nil then libdir = "lib" end @@ -471,7 +471,7 @@ end -- get the include directory function xpack:includedir() - local includedir = self:get("includedir") + local includedir = self:get("includedir") or self:extraconf("prefixdir", self:prefixdir(), "includedir") if includedir == nil then includedir = "include" end diff --git a/xmake/rules/c++/modules/modules_support/compiler_support.lua b/xmake/rules/c++/modules/modules_support/compiler_support.lua index 72e7b93fd..a005dc80d 100644 --- a/xmake/rules/c++/modules/modules_support/compiler_support.lua +++ b/xmake/rules/c++/modules/modules_support/compiler_support.lua @@ -293,7 +293,7 @@ function get_provided_module(module) return name, provide, cppfile end -function install_module_target(target) +function add_installfiles_for_modules(target) local sourcebatch = target:sourcebatches()["c++.build.modules.install"] if sourcebatch and sourcebatch.sourcefiles then for _, sourcefile in ipairs(sourcebatch.sourcefiles) do diff --git a/xmake/rules/c++/modules/xmake.lua b/xmake/rules/c++/modules/xmake.lua index 90ba66c81..d658815aa 100644 --- a/xmake/rules/c++/modules/xmake.lua +++ b/xmake/rules/c++/modules/xmake.lua @@ -227,6 +227,13 @@ rule("c++.build.modules.install") local modules = compiler_support.localcache():get2(target:name(), "c++.modules") builder.generate_metadata(target, modules) - compiler_support.install_module_target(target) + compiler_support.add_installfiles_for_modules(target) + end + end) + + before_uninstall(function (target) + import("modules_support.compiler_support") + if compiler_support.contains_modules(target) then + compiler_support.add_installfiles_for_modules(target) end end) diff --git a/xmake/rules/utils/inherit_links/inherit_links.lua b/xmake/rules/utils/inherit_links/inherit_links.lua index b235c8bff..a128e1cb7 100644 --- a/xmake/rules/utils/inherit_links/inherit_links.lua +++ b/xmake/rules/utils/inherit_links/inherit_links.lua @@ -104,7 +104,7 @@ function main(target) end -- export rpathdirs for all shared library - if target:is_binary() then + if target:is_binary() and target:policy("build.rpath") then local targetdir = target:targetdir() for _, dep in ipairs(target:orderdeps({inherit = true})) do if dep:kind() == "shared" then |
