summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorArthur LAURENT <[email protected]>2025-11-04 12:42:45 +0100
committerruki <[email protected]>2025-12-18 09:31:43 +0800
commit5dcd669f6e3017d6939af6fd391803c13395406a (patch)
treeb556d829a3e874be558ac16a29708bc321acfd6f
parent7a1be435eef977b88a2855df9bafc4ebeca1843e (diff)
fix(llvm) fix nf_runtime clang
-rw-r--r--tests/projects/c/llvm_compiler_rt/test.lua9
-rw-r--r--xmake/modules/core/tools/clang.lua169
-rw-r--r--xmake/modules/private/utils/toolchain.lua134
-rw-r--r--xmake/rules/utils/compiler_runtime/xmake.lua2
-rw-r--r--xmake/toolchains/llvm/check.lua6
-rw-r--r--xmake/toolchains/llvm/xmake.lua12
6 files changed, 207 insertions, 125 deletions
diff --git a/tests/projects/c/llvm_compiler_rt/test.lua b/tests/projects/c/llvm_compiler_rt/test.lua
index 3d9fd2cad..b2b39f1a8 100644
--- a/tests/projects/c/llvm_compiler_rt/test.lua
+++ b/tests/projects/c/llvm_compiler_rt/test.lua
@@ -1,15 +1,16 @@
import("lib.detect.find_tool")
+import("core.tool.toolchain")
import("utils.ci.is_running", {alias = "ci_is_running"})
function main(t)
local flags = ""
if ci_is_running() then
- flags = "-vD"
+ flags = "-vD"
end
- local cc = find_tool("clang", {version = true})
- if not cc then
- wprint("clang not found, skipping tests")
+ local llvm = toolchain.load("llvm")
+ if not llvm or not llvm:check() then
+ wprint("llvm not found, skipping tests")
return
end
os.exec("xmake clean -a")
diff --git a/xmake/modules/core/tools/clang.lua b/xmake/modules/core/tools/clang.lua
index 820f63c0c..75a165dbb 100644
--- a/xmake/modules/core/tools/clang.lua
+++ b/xmake/modules/core/tools/clang.lua
@@ -21,6 +21,7 @@
-- inherit gcc
inherit("gcc")
import("core.language.language")
+import("private.utils.toolchain", {alias = "toolchain_utils"})
-- init it
function init(self)
@@ -191,76 +192,6 @@ function _has_static_libstdcxx(self)
return has_static_libstdcxx
end
--- get llvm sdk root directory
-function _get_llvm_rootdir(self)
- local llvm_rootdir = _g._LLVM_ROOTDIR
- if llvm_rootdir == nil then
- local outdata = try { function() return os.iorunv(self:program(), {"-print-resource-dir"}, {envs = self:runenvs()}) end }
- if outdata then
- llvm_rootdir = path.normalize(path.join(outdata:trim(), "..", "..", ".."))
- if not os.isdir(llvm_rootdir) then
- llvm_rootdir = nil
- end
- end
- _g._LLVM_ROOTDIR = llvm_rootdir or false
- end
- return llvm_rootdir or nil
-end
-
--- get llvm target triple
-function _get_llvm_target_triple(self)
- local llvm_targettriple = _g._LLVM_TARGETTRIPLE
- if llvm_targettriple == nil then
- local outdata = try { function() return os.iorunv(self:program(), {"-print-target-triple"}, {envs = self:runenvs()}) end }
- if outdata then
- llvm_targettriple = outdata:trim()
- end
- _g._LLVM_TARGETTRIPLE = llvm_targettriple or false
- end
- return llvm_targettriple or nil
-end
-
--- find compiler-rt dir
-function _get_llvm_compiler_rtdir(self, target, llvm_rootdir)
- import("lib.detect.find_tool")
- import("core.base.semver")
-
- local libdir = path.absolute(path.join(llvm_rootdir, "lib", "clang"))
- local target_triple = _get_llvm_target_triple(self)
-
- local cc = target:tool("cc")
- local cc_tool = find_tool(cc, {version = true})
- if cc_tool and cc_tool.version then
- local version = semver.new(cc_tool.version):major()
- local basedir = path.join(libdir, version, "lib")
-
- local compiler_rtdir
- -- sometimes llvm is built with -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=ON, compiler_rt is located in a target-triple subfolder
- local tripletdir = path.join(basedir, target_triple)
- if os.isdir(tripletdir) then
- compiler_rtdir = tripletdir
- else
- -- sometimes llvm is built with -DLLVM_ENABLE_PER_TARGET_RUNTIME_DIR=OFF, compiler_rt is located in a platform name subfolder
- if target:is_plat("windows") then
- compiler_rtdir = os.isdir(path.join(basedir, "windows")) and path.join(basedir, "windows")
- elseif target:is_plat("linux") then
- compiler_rtdir = os.isdir(path.join(basedir, "linux")) and path.join(basedir, "linux")
- elseif target:is_plat("macosx") then
- compiler_rtdir = os.isdir(path.join(basedir, "darwin")) and path.join(basedir, "darwin")
- end
- end
-
- if compiler_rtdir and target_triple then
- local arch = target_triple:split("-")[1]
- local compiler_rtlink = "clang_rt.builtins-" .. arch
- if os.isfile(path.join(compiler_rtdir, compiler_rtlink .. ".lib")) then
- return compiler_rtdir, path.join(compiler_rtdir, compiler_rtlink .. ".lib")
- end
- end
- return compiler_rtdir
- end
-end
-
-- make the runtime flag
-- @see https://github.com/xmake-io/xmake/issues/3546
function nf_runtime(self, runtime, opt)
@@ -291,58 +222,72 @@ function nf_runtime(self, runtime, opt)
}
end
end
- if not self:is_plat("android") then -- we will set runtimes in android ndk toolchain
- maps = maps or {}
- local llvm_rootdir = self:toolchain():sdkdir()
- if not llvm_rootdir and self:is_plat("windows") then
- llvm_rootdir = _get_llvm_rootdir(self)
+ local target = opt.target or opt
+ -- llvm on windows still doesn't support autolinking of libc++ and compiler-rt builtins
+ -- @see https://discourse.llvm.org/t/improve-autolinking-of-compiler-rt-and-libc-on-windows-with-lld-link/71392/10
+ -- and need manual setting of libc++ headerdirectory
+ -- @see https://github.com/llvm/llvm-project/issues/79647
+ local llvm_dirs = toolchain_utils.get_llvm_dirs(self)
+
+ if self:is_plat("windows") and runtime == "c++_shared" then
+ if llvm_dirs.bin then
+ self:add("runenvs", "PATHS", llvm_dirs.bin)
end
- if kind == "cxx" then
+ if llvm_dirs.rt then
+ self:add("runenvs", "PATHS", llvm_dirs.rt)
+ end
+ end
+
+ -- we will set runtimes in android ndk toolchain
+ if not self:is_plat("android") then
+ maps = maps or {}
+ if kind == "cxx" or kind == "ld" or kind == "sh" then
maps["c++_static"] = "-stdlib=libc++"
maps["c++_shared"] = "-stdlib=libc++"
maps["stdc++_static"] = "-stdlib=libstdc++"
maps["stdc++_shared"] = "-stdlib=libstdc++"
- -- clang on windows fail to add libc++ includepath when using -stdlib=libc++ so we manually add it
- -- @see https://github.com/llvm/llvm-project/issues/79647
- if llvm_rootdir then
- maps["c++_static"] = table.join(maps["c++_static"], "-cxx-isystem" .. path.join(llvm_rootdir, "include", "c++", "v1"))
- maps["c++_shared"] = table.join(maps["c++_shared"], "-cxx-isystem" .. path.join(llvm_rootdir, "include", "c++", "v1"))
+ if kind == "cxx" then
+ -- force the toolchain libc++ headers to prevent clang picking the systems one
+ if llvm_dirs.cxxinclude then
+ maps["c++_static"] = table.join(maps["c++_static"], "-cxx-isystem" .. llvm_dirs.cxxinclude)
+ maps["c++_shared"] = table.join(maps["c++_shared"], "-cxx-isystem" .. llvm_dirs.cxxinclude)
+ end
end
- elseif kind == "ld" or kind == "sh" then
- local target = opt.target or opt
- local is_cxx_or_c = target and (target.sourcekinds and table.contains(table.wrap(target:sourcekinds()), "cxx", "cc"))
- if is_cxx_or_c then
- maps["c++_static"] = "-stdlib=libc++"
- maps["c++_shared"] = "-stdlib=libc++"
- maps["stdc++_static"] = "-stdlib=libstdc++"
- maps["stdc++_shared"] = "-stdlib=libstdc++"
- -- clang on windows fail to add libc++ librarypath when using -stdlib=libc++ so we manually add it
- -- @see https://github.com/llvm/llvm-project/issues/79647
- if llvm_rootdir then
- local libdir = path.absolute(path.join(llvm_rootdir, "lib"))
- maps["c++_static"] = table.join(maps["c++_static"], nf_linkdir(self, libdir))
- maps["c++_shared"] = table.join(maps["c++_shared"], nf_linkdir(self, libdir))
+ end
+
+ if self:is_plat("windows") and language.sourcekinds()[kind] then
+ -- on windows force link to compiler_rt builtins
+ if llvm_dirs.rt and llvm_dirs.rtlink then
+ for name, _ in pairs(maps) do
+ maps[name] = table.join({"-Xclang", "--dependent-lib=" .. llvm_dirs.rtlink}, maps[name])
+ end
+ end
+ end
+ if kind == "ld" or kind == "sh" then
+ if self:is_plat("windows") and llvm_dirs.rt then
+ -- on windows force add compiler_rt link directories
+ for name, _ in pairs(maps) do
+ maps[name] = table.join(nf_linkdir(self, llvm_dirs.rt), maps[name])
+ maps[name] = table.join("-resource-dir=" .. llvm_dirs.res, maps[name])
+ end
+ end
+
+ local is_cxx = target and (target.sourcekinds and table.contains(table.wrap(target:sourcekinds()), "cxx"))
+ if is_cxx then
+ if llvm_dirs.lib then
+ maps["c++_static"] = table.join(maps["c++_static"], nf_linkdir(self, llvm_dirs.lib))
+ maps["c++_shared"] = table.join(maps["c++_shared"], nf_linkdir(self, llvm_dirs.lib))
+ maps["c++_shared"] = table.join(maps["c++_shared"], nf_rpathdir(self, llvm_dirs.lib))
-- sometimes llvm c++ runtimes are located in c++ subfolder (e.g homebrew llvm)
- local cxx_libdir = path.join(libdir, "c++")
- if os.isdir(cxx_libdir) then
- maps["c++_static"] = table.join(maps["c++_static"], nf_linkdir(self, cxx_libdir))
- maps["c++_shared"] = table.join(maps["c++_shared"], "-L" .. nf_linkdir(self, cxx_libdir))
+ if llvm_dirs.cxxlib then
+ maps["c++_static"] = table.join(maps["c++_static"], nf_linkdir(self, llvm_dirs.cxxlib))
+ maps["c++_shared"] = table.join(maps["c++_shared"], nf_linkdir(self, llvm_dirs.cxxlib))
+ maps["c++_shared"] = table.join(maps["c++_shared"], nf_rpathdir(self, llvm_dirs.cxxlib))
end
- local compiler_rtdir, compiler_rtlink = _get_llvm_compiler_rtdir(self, target, llvm_rootdir)
- if compiler_rtdir then
- for name, _ in pairs(maps) do
- maps[name] = table.join(nf_linkdir(self, compiler_rtdir), maps[name])
- if compiler_rtlink then
- maps[name] = table.join(compiler_rtlink, maps[name])
- end
- end
+ if llvm_dirs.rt then
+ maps["c++_shared"] = table.join(maps["c++_shared"], nf_rpathdir(self, llvm_dirs.rt))
end
-- add rpath to avoid the user need to set LD_LIBRARY_PATH by hand
- maps["c++_shared"] = table.join(maps["c++_shared"], nf_rpathdir(self, libdir))
- if compiler_rtdir then
- maps["c++_shared"] = table.join(maps["c++_shared"], nf_rpathdir(self, compiler_rtdir))
- maps["MD"] = table.join(maps["MD"], nf_rpathdir(self, compiler_rtdir))
- end
if target.is_shared and target:is_shared() and target.filename and self:is_plat("macosx", "iphoneos", "watchos") then
maps["c++_shared"] = table.join(maps["c++_shared"], "-install_name")
maps["c++_shared"] = table.join(maps["c++_shared"], "@rpath/" .. target:filename())
diff --git a/xmake/modules/private/utils/toolchain.lua b/xmake/modules/private/utils/toolchain.lua
index 28cbc23c0..551209f2d 100644
--- a/xmake/modules/private/utils/toolchain.lua
+++ b/xmake/modules/private/utils/toolchain.lua
@@ -181,3 +181,137 @@ function map_linkflags_for_package(package, targetkind, sourcekinds, name, value
return flags
end
+-- get llvm sdk resource directory
+function _get_llvm_resourcedir(toolchain)
+ local llvm_resourcedir = _g._LLVM_resourceDIR
+ if llvm_resourcedir == nil then
+ local outdata = try { function() return os.iorunv(toolchain:get("cc"), {"-print-resource-dir"}, {envs = toolchain:runenvs()}) end }
+ if outdata then
+ llvm_resourcedir = path.normalize(outdata:trim())
+ if not os.isdir(llvm_resourcedir) then
+ llvm_resourcedir = nil
+ end
+ end
+ _g._LLVM_resourceDIR = llvm_resourcedir or false
+ end
+ return llvm_resourcedir or nil
+end
+
+-- get llvm sdk root directory
+function _get_llvm_rootdir(self)
+ local llvm_rootdir = _g._LLVM_ROOTDIR
+ if llvm_rootdir == nil then
+ local resourcedir = _get_llvm_resourcedir(self)
+ if resourcedir then
+ llvm_rootdir = path.normalize(path.join(resourcedir, "..", "..", ".."))
+ if not os.isdir(llvm_rootdir) then
+ llvm_rootdir = nil
+ end
+ end
+ _g._LLVM_ROOTDIR = llvm_rootdir or false
+ end
+ return llvm_rootdir or nil
+end
+
+-- find compiler-rt dir
+function _get_llvm_compiler_win_rtdir_and_link(self, target)
+ import("lib.detect.find_tool")
+
+ local cc = self:get("cc")
+ local cc_tool = find_tool(cc, {version = true})
+ if cc_tool and cc_tool.version then
+ local resdir = _get_llvm_resourcedir(self)
+ if resdir then
+ local res_libdir = path.join(resdir, "lib")
+ -- when -DLLVM_ENABLE_TARGET_RUNTIME_DIR=OFF rtdir is windows/ and rtlink is clang_rt.builtinsi_<arch>.lib
+ -- when ON rtdir is windows/<target-triple> and rtlink is clang_rt.builtins.lib
+ local target_triple = _get_llvm_target_triple(self)
+ local arch = target_triple and target_triple:split("-")[1]
+
+ local tripletdir = target_triple and path.join(res_libdir, "windows", target_triple)
+ tripletdir = os.isdir(tripletdir) or nil
+
+ local rtdir = tripletdir and path.join("windows", target_triple) or "windows"
+ if os.isdir(path.join(res_libdir, rtdir)) then
+ local rtlink = "clang_rt.builtins" .. (tripletdir and ".lib" or ("-" .. arch .. ".lib"))
+ if os.isfile(path.join(res_libdir, rtdir, rtlink)) then
+ return res_libdir, path.join(rtdir, rtlink)
+ end
+ end
+ return res_libdir
+ end
+ end
+end
+
+-- get llvm target triple
+function _get_llvm_target_triple(self)
+ local llvm_targettriple = _g._LLVM_TARGETTRIPLE
+ if llvm_targettriple == nil then
+ local outdata = try { function() return os.iorunv(self:program(), {"-print-target-triple"}, {envs = self:runenvs()}) end }
+ if outdata then
+ llvm_targettriple = outdata:trim()
+ end
+ _g._LLVM_TARGETTRIPLE = llvm_targettriple or false
+ end
+ return llvm_targettriple or nil
+end
+
+-- get llvm toolchain dirs
+function get_llvm_dirs(toolchain)
+ local llvm_dirs = _g.llvm_dirs
+ if llvm_dirs == nil then
+ local rootdir = toolchain:sdkdir()
+ if not rootdir and toolchain:is_plat("windows") then
+ rootdir = _get_llvm_rootdir(toolchain)
+ end
+
+ local bindir, libdir, cxxlibdir, includedir, cxxincludedir, resdir, rtdir, rtlink
+ if rootdir then
+ bindir = path.join(rootdir, "bin")
+ if bindir then
+ bindir = os.isdir(bindir) and bindir or nil
+ end
+
+ libdir = path.join(rootdir, "lib")
+ if libdir then
+ libdir = os.isdir(libdir) and libdir or nil
+ end
+
+ if libdir then
+ cxxlibdir = libdir and path.join(libdir, "c++")
+ if cxxlibdir then
+ cxxlibdir = os.isdir(cxxlibdir) and cxxlibdir or nil
+ end
+ end
+
+ includedir = path.join(rootdir, "include")
+ if includedir then
+ includedir = os.isdir(includedir) and includedir or nil
+ end
+
+ if includedir then
+ cxxincludedir = includedir and path.join(includedir, "c++", "v1") or nil
+ if cxxincludedir then
+ cxxincludedir = os.isdir(cxxincludedir) and cxxincludedir or nil
+ end
+ end
+
+ resdir = _get_llvm_resourcedir(toolchain)
+ if toolchain:is_plat("windows") then
+ rtdir, rtlink = _get_llvm_compiler_win_rtdir_and_link(toolchain)
+ end
+ end
+
+ llvm_dirs = {root = rootdir,
+ bin = bindir,
+ lib = libdir,
+ cxxlib = cxxlibdir,
+ include = includedir,
+ cxxinclude = cxxincludedir,
+ res = resdir,
+ rt = rtdir,
+ rtlink = rtlink }
+ _g.llvm_dirs = llvm_dirs
+ end
+ return llvm_dirs
+end
diff --git a/xmake/rules/utils/compiler_runtime/xmake.lua b/xmake/rules/utils/compiler_runtime/xmake.lua
index 6a59d7ace..756bd55f8 100644
--- a/xmake/rules/utils/compiler_runtime/xmake.lua
+++ b/xmake/rules/utils/compiler_runtime/xmake.lua
@@ -44,7 +44,7 @@ rule("utils.compiler.runtime")
-- enable vs runtime as MD by default
if target:is_plat("windows") and not target:get("runtimes") then
local vs_runtime_default = target:policy("build.c++.msvc.runtime")
- if vs_runtime_default and target:has_tool("cxx", "cl", "clang", "clang_cl") then
+ if vs_runtime_default and target:has_tool("cxx", "cl", "clang", "clangxx", "clang_cl") then
if is_mode("debug") then
vs_runtime_default = vs_runtime_default .. "d"
end
diff --git a/xmake/toolchains/llvm/check.lua b/xmake/toolchains/llvm/check.lua
index 54901824a..74cc96a7f 100644
--- a/xmake/toolchains/llvm/check.lua
+++ b/xmake/toolchains/llvm/check.lua
@@ -115,11 +115,13 @@ function main(toolchain)
toolchain:config_set("bindir", cross_toolchain.bindir)
toolchain:config_set("sdkdir", cross_toolchain.sdkdir)
else
- raise("llvm toolchain not found!")
+ wprint("llvm toolchain not found!")
+ return false
end
if toolchain:is_plat("cross") and (not toolchain:cross() or toolchain:cross():match("^%s*$")) then
- raise("Missing cross target. Use `--cross=name` to specify.")
+ wprint("Missing cross target. Use `--cross=name` to specify.")
+ return false
end
-- attempt to find xcode to pass `-isysroot` on macos
diff --git a/xmake/toolchains/llvm/xmake.lua b/xmake/toolchains/llvm/xmake.lua
index d4557071c..61288f1bd 100644
--- a/xmake/toolchains/llvm/xmake.lua
+++ b/xmake/toolchains/llvm/xmake.lua
@@ -50,12 +50,18 @@ toolchain("llvm")
on_check("check")
on_load(function (toolchain)
+ import("private.utils.toolchain", {alias = "toolchain_utils"})
-- add runtimes
if toolchain:is_plat("windows") then
toolchain:add("runtimes", "MT", "MTd", "MD", "MDd")
end
+ local dirs = toolchain_utils.get_llvm_dirs(toolchain)
+ if dirs and dirs.rt then
+ toolchain:add("runenvs", dirs.rt)
+ end
+
-- add target flags
local target
if toolchain:is_plat("windows") and not is_host("windows") then
@@ -151,11 +157,5 @@ toolchain("llvm")
toolchain:add("shflags", "--sysroot=" .. sysroot)
end
end
-
- -- add bin search library for loading some dependent .dll files windows
- local bindir = toolchain:bindir()
- if bindir and is_host("windows") then
- toolchain:add("runenvs", "PATH", bindir)
- end
end)