From 1bd903ce8eb567ddb1847c119e21665255c879bb Mon Sep 17 00:00:00 2001 From: ruki Date: Tue, 4 Aug 2026 22:40:52 +0800 Subject: improve clang static libc++ runtimes --- xmake/languages/c++/xmake.lua | 12 +++++--- xmake/modules/core/tools/clang.lua | 58 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 7 deletions(-) diff --git a/xmake/languages/c++/xmake.lua b/xmake/languages/c++/xmake.lua index b94a073b8..42ddc2884 100644 --- a/xmake/languages/c++/xmake.lua +++ b/xmake/languages/c++/xmake.lua @@ -60,8 +60,7 @@ language("c++") , "toolchain.sysincludedirs" } , binary = { - "target.runtimes" - , "config.linkdirs" + "config.linkdirs" , "config.frameworkdirs" , "target.linkdirs" , "target.frameworkdirs" @@ -79,13 +78,16 @@ language("c++") , "config.frameworks" , "target.frameworks" , "toolchain.frameworks" + -- runtimes may link libc++.a/libc++abi.a explicitly, so it must come after the + -- object files and user links, but before the syslinks it depends on (e.g. pthread) + -- @see https://github.com/xmake-io/xmake/issues/7442 + , "target.runtimes" , "config.syslinks" , "target.syslinks" , "toolchain.syslinks" } , shared = { - "target.runtimes" - , "config.linkdirs" + "config.linkdirs" , "config.frameworkdirs" , "target.linkdirs" , "target.frameworkdirs" @@ -103,6 +105,8 @@ language("c++") , "config.frameworks" , "target.frameworks" , "toolchain.frameworks" + -- @see https://github.com/xmake-io/xmake/issues/7442 + , "target.runtimes" , "config.syslinks" , "target.syslinks" , "toolchain.syslinks" diff --git a/xmake/modules/core/tools/clang.lua b/xmake/modules/core/tools/clang.lua index d9d6512ee..a79e67142 100644 --- a/xmake/modules/core/tools/clang.lua +++ b/xmake/modules/core/tools/clang.lua @@ -192,6 +192,40 @@ function _has_static_libstdcxx(self) return has_static_libstdcxx end +-- has -nostdlib++? (disable the automatic c++ runtime link, so we can link libc++/libc++abi explicitly) +function _has_nostdlibxx(self) + local has_nostdlibxx = _g._HAS_NOSTDLIBXX + if has_nostdlibxx == nil then + if self:has_flags("-nostdlib++ -Werror", "ldflags", {flagskey = "clang_nostdlibxx"}) then + has_nostdlibxx = true + end + has_nostdlibxx = has_nostdlibxx or false + _g._HAS_NOSTDLIBXX = has_nostdlibxx + end + return has_nostdlibxx +end + +-- find the static libc++.a / libc++abi.a archives under the llvm library dirs +function _get_static_cxxlibs(llvm_dirs) + local libdirs = {} + if llvm_dirs.cxxlibdir then + table.insert(libdirs, llvm_dirs.cxxlibdir) + end + if llvm_dirs.libdir then + table.insert(libdirs, llvm_dirs.libdir) + end + local libcxx, libcxxabi + for _, dir in ipairs(libdirs) do + if not libcxx and os.isfile(path.join(dir, "libc++.a")) then + libcxx = path.join(dir, "libc++.a") + end + if not libcxxabi and os.isfile(path.join(dir, "libc++abi.a")) then + libcxxabi = path.join(dir, "libc++abi.a") + end + end + return libcxx, libcxxabi +end + -- make the runtime flag -- @see https://github.com/xmake-io/xmake/issues/3546 function nf_runtime(self, runtime, opt) @@ -284,9 +318,27 @@ function nf_runtime(self, runtime, opt) end end end - if runtime:endswith("_static") and _has_static_libstdcxx(self) then - maps["c++_static"] = table.join(maps["c++_static"], "-static-libstdc++") - maps["stdc++_static"] = table.join(maps["stdc++_static"], "-static-libstdc++") + if runtime:endswith("_static") then + -- -static-libstdc++ only pulls in libc++.a, not libc++abi.a, so libc++abi + -- symbols (typeinfo, __cxa_*) stay undefined and the link fails, especially + -- once c++ modules reference more of libc++. + -- + -- if we can locate both static archives, link them explicitly in the correct + -- order (libc++.a before libc++abi.a) and disable the driver's automatic c++ + -- runtime with -nostdlib++, otherwise fall back to -static-libstdc++. + -- + -- note: target.runtimes is ordered right before the syslinks in the c++ link + -- order, so these archives are placed after the object files / user links. + -- @see https://github.com/xmake-io/xmake/issues/7442 + local libcxx, libcxxabi = _get_static_cxxlibs(llvm_dirs) + if libcxx and libcxxabi and _has_nostdlibxx(self) then + maps["c++_static"] = table.join(maps["c++_static"], "-nostdlib++", libcxx, libcxxabi) + elseif _has_static_libstdcxx(self) then + maps["c++_static"] = table.join(maps["c++_static"], "-static-libstdc++") + end + if _has_static_libstdcxx(self) then + maps["stdc++_static"] = table.join(maps["stdc++_static"], "-static-libstdc++") + end end end end -- cgit v1.3.1 From e129ec374599dfddb45f1a157a64f7330f9a6124 Mon Sep 17 00:00:00 2001 From: ruki Date: Thu, 6 Aug 2026 23:35:52 +0800 Subject: improve libc++ links --- xmake/modules/core/tools/clang.lua | 46 ++++++++++----------------- xmake/modules/private/utils/toolchain.lua | 52 ++++++++++++++++++++++++++++++- 2 files changed, 68 insertions(+), 30 deletions(-) diff --git a/xmake/modules/core/tools/clang.lua b/xmake/modules/core/tools/clang.lua index a79e67142..c264f6656 100644 --- a/xmake/modules/core/tools/clang.lua +++ b/xmake/modules/core/tools/clang.lua @@ -205,27 +205,6 @@ function _has_nostdlibxx(self) return has_nostdlibxx end --- find the static libc++.a / libc++abi.a archives under the llvm library dirs -function _get_static_cxxlibs(llvm_dirs) - local libdirs = {} - if llvm_dirs.cxxlibdir then - table.insert(libdirs, llvm_dirs.cxxlibdir) - end - if llvm_dirs.libdir then - table.insert(libdirs, llvm_dirs.libdir) - end - local libcxx, libcxxabi - for _, dir in ipairs(libdirs) do - if not libcxx and os.isfile(path.join(dir, "libc++.a")) then - libcxx = path.join(dir, "libc++.a") - end - if not libcxxabi and os.isfile(path.join(dir, "libc++abi.a")) then - libcxxabi = path.join(dir, "libc++abi.a") - end - end - return libcxx, libcxxabi -end - -- make the runtime flag -- @see https://github.com/xmake-io/xmake/issues/3546 function nf_runtime(self, runtime, opt) @@ -319,20 +298,29 @@ function nf_runtime(self, runtime, opt) end end if runtime:endswith("_static") then - -- -static-libstdc++ only pulls in libc++.a, not libc++abi.a, so libc++abi + -- -static-libstdc++ only pulls in libc++.a, not libc++abi.a, so the libc++abi -- symbols (typeinfo, __cxa_*) stay undefined and the link fails, especially -- once c++ modules reference more of libc++. -- - -- if we can locate both static archives, link them explicitly in the correct - -- order (libc++.a before libc++abi.a) and disable the driver's automatic c++ - -- runtime with -nostdlib++, otherwise fall back to -static-libstdc++. + -- if we can locate both static archives, link them explicitly in a group + -- (they reference each other) and disable the driver's automatic c++ runtime + -- with -nostdlib++, otherwise (bundled abi) fall back to -static-libstdc++. -- -- note: target.runtimes is ordered right before the syslinks in the c++ link -- order, so these archives are placed after the object files / user links. - -- @see https://github.com/xmake-io/xmake/issues/7442 - local libcxx, libcxxabi = _get_static_cxxlibs(llvm_dirs) - if libcxx and libcxxabi and _has_nostdlibxx(self) then - maps["c++_static"] = table.join(maps["c++_static"], "-nostdlib++", libcxx, libcxxabi) + -- @see https://github.com/xmake-io/xmake/issues/7442, https://github.com/xmake-io/xmake/issues/7656 + if llvm_dirs.libcxx_static and llvm_dirs.libcxxabi_static and _has_nostdlibxx(self) then + local cxxlibs = {llvm_dirs.libcxx_static, llvm_dirs.libcxxabi_static} + -- apple ld64 does not support --start-group, it always rescans archives + if not self:is_plat("macosx", "iphoneos", "watchos", "appletvos", "applexros") then + cxxlibs = table.join("-Wl,--start-group", cxxlibs, "-Wl,--end-group") + end + -- -stdlib=libc++ is unused when linking with -nostdlib++, remove it to avoid + -- the `argument unused during compilation` warning + local ldflags = table.remove_if(table.wrap(maps["c++_static"]), function (_, flag) + return flag == "-stdlib=libc++" + end) + maps["c++_static"] = table.join(ldflags, "-nostdlib++", cxxlibs) elseif _has_static_libstdcxx(self) then maps["c++_static"] = table.join(maps["c++_static"], "-static-libstdc++") end diff --git a/xmake/modules/private/utils/toolchain.lua b/xmake/modules/private/utils/toolchain.lua index c2840a591..bd673fa0c 100644 --- a/xmake/modules/private/utils/toolchain.lua +++ b/xmake/modules/private/utils/toolchain.lua @@ -343,6 +343,25 @@ function map_linkflags_for_package(package, targetkind, sourcekinds, name, value return flags end +-- ask the clang driver for the absolute path of the given library +-- +-- clang knows its own library search paths, e.g. the per-target runtime directory +-- (`lib//libc++.a`), and it also works when the toolchain has no `--sdk=/xxx/llvm`. +-- +-- it just echoes back the given filename if the library does not exist, +-- so we only accept an existing absolute path. +function _get_llvm_libfile(toolchain, clang, libname, opt) + local argv = table.wrap(get_clang_target_flags(toolchain)) + table.insert(argv, "-print-file-name=" .. libname) + local libfile = try {function () return os.iorunv(clang, argv, {envs = opt.envs}) end} + if libfile then + libfile = path.normalize(libfile:trim()) + if path.is_absolute(libfile) and os.isfile(libfile) then + return libfile + end + end +end + -- Check and cache llvm/clang driver info (e.g. -print-resource-dir/-print-target-triple) in toolchain:config(). -- It must be collected during toolchain on_check so that toolchain on_load can use it without running clang again. -- @see https://github.com/xmake-io/xmake/issues/7337#issuecomment-3942499208 @@ -370,6 +389,17 @@ function check_llvm_info(toolchain, clang, opt) toolchain:config_set("llvm_target_triple", target_triple) end end + + -- the static c++ runtime archives, they may be in the per-target runtime directory, + -- e.g. `lib/x86_64-unknown-linux-gnu/libc++.a` + -- @see https://github.com/xmake-io/xmake/issues/7442 + for libname, configname in pairs({["libc++.a"] = "llvm_libcxx_static", ["libc++abi.a"] = "llvm_libcxxabi_static"}) do + local libfile = _get_llvm_libfile(toolchain, clang, libname, opt) + if libfile then + dprint("checking for llvm %s ... %s", libname, libfile) + toolchain:config_set(configname, libfile) + end + end end -- get llvm sdk resource directory @@ -457,6 +487,11 @@ function get_llvm_dirs(toolchain) end local bindir, libdir, cxxlibdir, includedir, cxxincludedir, resourcedir, rtdir, rtlink, rtlibdir + + -- the clang driver has already resolved the static c++ runtime archives on check + -- @see check_llvm_info + local libcxx_static = toolchain:config("llvm_libcxx_static") + local libcxxabi_static = toolchain:config("llvm_libcxxabi_static") if rootdir then bindir = path.join(rootdir, "bin") bindir = os.isdir(bindir) and bindir or nil @@ -476,6 +511,19 @@ function get_llvm_dirs(toolchain) end end + -- fallback to search the static c++ runtime archives in the library directories + if libdir and not (libcxx_static and libcxxabi_static) then + local staticdirs = cxxlibdir and {cxxlibdir, libdir} or {libdir} + for _, dir in ipairs(staticdirs) do + if not libcxx_static and os.isfile(path.join(dir, "libc++.a")) then + libcxx_static = path.join(dir, "libc++.a") + end + if not libcxxabi_static and os.isfile(path.join(dir, "libc++abi.a")) then + libcxxabi_static = path.join(dir, "libc++abi.a") + end + end + end + includedir = path.join(rootdir, "include") includedir = os.isdir(includedir) and includedir or nil @@ -502,7 +550,9 @@ function get_llvm_dirs(toolchain) resourcedir = resourcedir, rtdir = rtdir, rtlibdir = rtlibdir, - rtlink = rtlink } + rtlink = rtlink, + libcxx_static = libcxx_static, + libcxxabi_static = libcxxabi_static } memcache:set(cachekey, llvm_dirs) end return llvm_dirs -- cgit v1.3.1