feat: cross-compile & upgrade LLVM to 21.1.8 (#390)
## Summary
This PR adds cross-compilation support for three new target platforms,
upgrades LLVM to 21.1.8, and overhauls the CI pipelines around
cross-builds and testing.
## Cross-compilation
New target triples accepted via `-DCLICE_TARGET_TRIPLE=...`:
| Target triple | Host | Output |
|---|---|---|
| `x86_64-apple-darwin` | macos-15 (arm64) | macOS x64 |
| `aarch64-linux-gnu` | ubuntu-24.04 (x64) | Linux arm64 |
| `aarch64-pc-windows-msvc` | windows-2025 (x64) | Windows arm64 |
- `cmake/toolchain.cmake` — maps `CLICE_TARGET_TRIPLE` to
`CMAKE_SYSTEM_NAME`/`CMAKE_SYSTEM_PROCESSOR`/compiler `--target`; picks
up conda aarch64 sysroot when cross-compiling Linux.
- `cmake/llvm.cmake` — forwards target platform/arch to `setup-llvm.py`
so the right prebuilt LLVM is downloaded for the target.
- `CMakeLists.txt` — uses a host-side `flatc` from `PATH` under
`CMAKE_CROSSCOMPILING` instead of the in-tree target build.
- `pixi.toml`:
- Adds `osx-64`, `linux-aarch64`, `win-arm64` platforms.
- New environments: `cross-macos-x64`, `cross-linux-aarch64` (adds
`gcc_linux-aarch64` + `sysroot_linux-aarch64`), `cross-windows-arm64`.
- New lightweight `test-run` env used on native ARM/x64 runners to
execute cross-built artifacts (pulls in upstream clang+lld on macOS so
tests don't fall back to Apple clang).
- `scripts/activate_cross_linux.sh` — exports `CONDA_PREFIX`-relative
paths for the aarch64 toolchain.
- `scripts/build-llvm.py` — `--target-triple` support and a
`build_native_tools()` helper that produces host `llvm-tblgen` /
`clang-tblgen` needed when cross-compiling LLVM itself.
## LLVM upgrade 21.1.4 → 21.1.8
- `cmake/package.cmake` bumps `setup_llvm("21.1.8")`.
- `config/llvm-manifest.json` regenerated with 6 new cross-compiled
entries and a new `arch` field on every entry so lookup is `(version,
platform, arch, lto, build_type)`.
- `scripts/setup-llvm.py` — honours the new `arch` field when resolving
artifacts.
- `scripts/update-llvm-version.py` (new) — single-call version bump
across `package.cmake` + manifest.
- `scripts/validate-llvm-components.py` (new) — scans the LLVM source
tree for library targets and diffs them against
`scripts/llvm-components.json` to catch stale/misspelled component names
before a build.
- `scripts/llvm-components.json` (new) — explicit allow-list of required
LLVM/Clang library targets used by `build-llvm.py`.
## CI changes
- `.github/workflows/build-llvm.yml`:
- Adds `workflow_dispatch` with `llvm_version`, `skip_upload`, `skip_pr`
inputs.
- Matrix extended with the 6 cross-compile entries (2 per new platform:
RelWithDebInfo ± LTO).
- `build clice` / test / prune steps gated on `!matrix.target_triple`
for cross-builds; cross-built LTO entries apply the native prune
manifest (arch-independent).
- Cross-compiled binary architecture is verified with `file(1)`.
- New `upload` job triggered by `workflow_dispatch` pushes artifacts to
`clice-io/clice-llvm` and hands the manifest off to the next job.
- `.github/workflows/test-cmake.yml`:
- Build matrix gains three `build_only: true` cross entries that upload
`bin/` + `lib/` artifacts.
- New `test-cross` job runs on native `macos-15-intel`,
`ubuntu-24.04-arm`, `windows-11-arm` runners, downloads the cross-built
artifacts, and runs unit / integration / smoke tests under the
`test-run` pixi env.
- Cache keys now include `target_triple` so native and cross builds
don't collide.
- `.github/workflows/publish-clice.yml`:
- Three additional release artifacts for the new targets
(`clice-x86_64-macos-darwin`, `clice-aarch64-linux-gnu`,
`clice-aarch64-windows-msvc`), each with a matching `-symbol` archive.
## Compatibility
- All existing native builds and tests are preserved; cross entries are
additive.
- `Debug` + ASAN remains disabled on Windows (`llvm_mode == Debug && os
== windows-*` no longer appends `-asan`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import subprocess
|
||||
import shutil
|
||||
import argparse
|
||||
import os
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -22,6 +23,66 @@ def normalize_mode(value: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def build_native_tools(project_root: Path, build_dir: Path) -> Path:
|
||||
"""Build native host tablegen tools for cross-compilation.
|
||||
|
||||
When cross-compiling LLVM, build tools like llvm-tblgen must run on the
|
||||
host but would otherwise be compiled for the target architecture. This
|
||||
function performs a minimal native build and returns the bin directory
|
||||
containing host-runnable executables.
|
||||
"""
|
||||
native_dir = build_dir.parent / f"{build_dir.name}-native-tools"
|
||||
native_dir.mkdir(exist_ok=True)
|
||||
source_dir = project_root / "llvm"
|
||||
|
||||
cmake_args = [
|
||||
"-G",
|
||||
"Ninja",
|
||||
"-DCMAKE_BUILD_TYPE=Release",
|
||||
"-DLLVM_ENABLE_PROJECTS=clang;clang-tools-extra",
|
||||
"-DLLVM_TARGETS_TO_BUILD=Native",
|
||||
"-DLLVM_DISABLE_ASSEMBLY_FILES=ON",
|
||||
"-DCMAKE_C_FLAGS=-w",
|
||||
"-DCMAKE_CXX_FLAGS=-w",
|
||||
]
|
||||
|
||||
if sys.platform == "win32":
|
||||
cmake_args += [
|
||||
"-DCMAKE_C_COMPILER=clang-cl",
|
||||
"-DCMAKE_CXX_COMPILER=clang-cl",
|
||||
]
|
||||
else:
|
||||
cmake_args += [
|
||||
"-DCMAKE_C_COMPILER=clang",
|
||||
"-DCMAKE_CXX_COMPILER=clang++",
|
||||
]
|
||||
|
||||
print(f"\nConfiguring native host tools in {native_dir}...")
|
||||
subprocess.check_call(
|
||||
["cmake", "-S", str(source_dir), "-B", str(native_dir)] + cmake_args
|
||||
)
|
||||
|
||||
required_tools = ["llvm-tblgen", "llvm-min-tblgen", "clang-tblgen"]
|
||||
optional_tools = ["clang-tidy-confusable-chars-gen"]
|
||||
|
||||
for tool in required_tools:
|
||||
print(f"Building native {tool}...")
|
||||
subprocess.check_call(["cmake", "--build", str(native_dir), "--target", tool])
|
||||
|
||||
for tool in optional_tools:
|
||||
try:
|
||||
print(f"Building native {tool} (optional)...")
|
||||
subprocess.check_call(
|
||||
["cmake", "--build", str(native_dir), "--target", tool]
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
print(f" {tool} not available, skipping.")
|
||||
|
||||
bin_dir = native_dir / "bin"
|
||||
print(f"Native host tools ready in {bin_dir}")
|
||||
return bin_dir
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Build LLVM with specific configurations."
|
||||
@@ -48,6 +109,10 @@ def main():
|
||||
"--build-dir",
|
||||
help="Custom build directory (relative to project root or absolute)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--target-triple",
|
||||
help="Cross-compilation target triple (e.g. x86_64-apple-darwin, aarch64-linux-gnu, aarch64-pc-windows-msvc)",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -85,118 +150,46 @@ def main():
|
||||
print("--- Configuration ---")
|
||||
print(f"Mode: {args.mode}")
|
||||
print(f"LTO: {args.lto}")
|
||||
print(f"Target Triple: {args.target_triple or '(native)'}")
|
||||
print(f"Root: {project_root}")
|
||||
print(f"Build Dir: {build_dir}")
|
||||
print(f"Install Prefix: {install_prefix}")
|
||||
print(f"Toolchain: {toolchain_file}")
|
||||
print("---------------------")
|
||||
|
||||
llvm_distribution_components = [
|
||||
"LLVMDemangle",
|
||||
"LLVMSupport",
|
||||
"LLVMCore",
|
||||
"LLVMOption",
|
||||
"LLVMBinaryFormat",
|
||||
"LLVMMC",
|
||||
"LLVMMCParser",
|
||||
"LLVMObject",
|
||||
"LLVMProfileData",
|
||||
"LLVMBitReader",
|
||||
"LLVMBitstreamReader",
|
||||
"LLVMRemarks",
|
||||
"LLVMObjectYAML",
|
||||
"LLVMAggressiveInstCombine",
|
||||
"LLVMInstCombine",
|
||||
"LLVMIRReader",
|
||||
"LLVMTextAPI",
|
||||
"LLVMSymbolize",
|
||||
"LLVMDebugInfoDWARF",
|
||||
"LLVMDebugInfoDWARFLowLevel",
|
||||
"LLVMDebugInfoCodeView",
|
||||
"LLVMDebugInfoGSYM",
|
||||
"LLVMDebugInfoPDB",
|
||||
"LLVMDebugInfoBTF",
|
||||
"LLVMDebugInfoMSF",
|
||||
"LLVMAsmParser",
|
||||
"LLVMTargetParser",
|
||||
"LLVMTransformUtils",
|
||||
"LLVMAnalysis",
|
||||
"LLVMScalarOpts",
|
||||
"LLVMFrontendHLSL",
|
||||
"LLVMFrontendOpenMP",
|
||||
"LLVMFrontendOffloading",
|
||||
"LLVMFrontendAtomic",
|
||||
"LLVMFrontendDirective",
|
||||
"LLVMWindowsDriver",
|
||||
"clangIndex",
|
||||
"clangAPINotes",
|
||||
"clangAST",
|
||||
"clangASTMatchers",
|
||||
"clangBasic",
|
||||
"clangDriver",
|
||||
"clangFormat",
|
||||
"clangFrontend",
|
||||
"clangLex",
|
||||
"clangParse",
|
||||
"clangSema",
|
||||
"clangSerialization",
|
||||
"clangRewrite",
|
||||
"clangAnalysis",
|
||||
"clangEdit",
|
||||
"clangSupport",
|
||||
"clangStaticAnalyzerCore",
|
||||
"clangStaticAnalyzerFrontend",
|
||||
"clangTidy",
|
||||
"clangTidyUtils",
|
||||
"clangTidyAndroidModule",
|
||||
"clangTidyAbseilModule",
|
||||
"clangTidyAlteraModule",
|
||||
"clangTidyBoostModule",
|
||||
"clangTidyBugproneModule",
|
||||
"clangTidyCERTModule",
|
||||
"clangTidyConcurrencyModule",
|
||||
"clangTidyCppCoreGuidelinesModule",
|
||||
"clangTidyDarwinModule",
|
||||
"clangTidyFuchsiaModule",
|
||||
"clangTidyGoogleModule",
|
||||
"clangTidyHICPPModule",
|
||||
"clangTidyLinuxKernelModule",
|
||||
"clangTidyLLVMModule",
|
||||
"clangTidyLLVMLibcModule",
|
||||
"clangTidyMiscModule",
|
||||
"clangTidyModernizeModule",
|
||||
"clangTidyObjCModule",
|
||||
"clangTidyOpenMPModule",
|
||||
"clangTidyPerformanceModule",
|
||||
"clangTidyPortabilityModule",
|
||||
"clangTidyReadabilityModule",
|
||||
"clangTidyZirconModule",
|
||||
"clangTooling",
|
||||
"clangToolingCore",
|
||||
"clangToolingInclusions",
|
||||
"clangToolingInclusionsStdlib",
|
||||
"clangToolingSyntax",
|
||||
"clangToolingRefactoring",
|
||||
"clangTransformer",
|
||||
"clangCrossTU",
|
||||
"clangAnalysisFlowSensitive",
|
||||
"clangAnalysisFlowSensitiveModels",
|
||||
"clangStaticAnalyzerCheckers",
|
||||
"clangIncludeCleaner",
|
||||
"llvm-headers",
|
||||
"clang-headers",
|
||||
"clang-tidy-headers",
|
||||
"clang-resource-headers",
|
||||
]
|
||||
components_path = Path(__file__).resolve().parent / "llvm-components.json"
|
||||
with components_path.open() as f:
|
||||
llvm_distribution_components = json.load(f)["components"]
|
||||
|
||||
components_joined = ";".join(llvm_distribution_components)
|
||||
cmake_args = [
|
||||
"-G",
|
||||
"Ninja",
|
||||
f"-DCMAKE_TOOLCHAIN_FILE={toolchain_file.as_posix()}",
|
||||
f"-DCMAKE_INSTALL_PREFIX={install_prefix}",
|
||||
"-DCMAKE_C_FLAGS=-w",
|
||||
"-DCMAKE_CXX_FLAGS=-w",
|
||||
]
|
||||
|
||||
if sys.platform == "win32":
|
||||
# Use clang-cl (MSVC driver) on Windows so that LLVM's CMake
|
||||
# generates correct MSVC-style linker flags for LTO, etc.
|
||||
c_flags = "-w"
|
||||
if args.target_triple:
|
||||
c_flags += f" --target={args.target_triple}"
|
||||
cmake_args += [
|
||||
"-DCMAKE_C_COMPILER=clang-cl",
|
||||
"-DCMAKE_CXX_COMPILER=clang-cl",
|
||||
f"-DCMAKE_C_FLAGS={c_flags}",
|
||||
f"-DCMAKE_CXX_FLAGS={c_flags}",
|
||||
"-DLLVM_USE_LINKER=lld-link",
|
||||
]
|
||||
else:
|
||||
cmake_args += [
|
||||
f"-DCMAKE_TOOLCHAIN_FILE={toolchain_file.as_posix()}",
|
||||
"-DCMAKE_C_FLAGS=-w",
|
||||
"-DCMAKE_CXX_FLAGS=-w",
|
||||
"-DLLVM_USE_LINKER=lld",
|
||||
]
|
||||
|
||||
cmake_args += [
|
||||
"-DLLVM_ENABLE_ZLIB=OFF",
|
||||
"-DLLVM_ENABLE_ZSTD=OFF",
|
||||
"-DLLVM_ENABLE_LIBXML2=OFF",
|
||||
@@ -231,7 +224,6 @@ def main():
|
||||
"-DCMAKE_JOB_POOL_LINK=console",
|
||||
"-DLLVM_ENABLE_PROJECTS=clang;clang-tools-extra",
|
||||
"-DLLVM_TARGETS_TO_BUILD=all",
|
||||
"-DLLVM_USE_LINKER=lld",
|
||||
"-DLLVM_DISABLE_ASSEMBLY_FILES=ON",
|
||||
# Distribution
|
||||
f"-DLLVM_DISTRIBUTION_COMPONENTS={components_joined}",
|
||||
@@ -256,8 +248,10 @@ def main():
|
||||
is_shared = "OFF"
|
||||
if args.mode == "Debug":
|
||||
cmake_args.append("-DCMAKE_BUILD_TYPE=Debug")
|
||||
cmake_args.append("-DLLVM_USE_SANITIZER=Address")
|
||||
is_shared = "ON"
|
||||
# ASAN is incompatible with -MDd on Windows (clang-cl), skip it there.
|
||||
if sys.platform != "win32":
|
||||
cmake_args.append("-DLLVM_USE_SANITIZER=Address")
|
||||
is_shared = "ON"
|
||||
elif args.mode == "Release":
|
||||
cmake_args.append("-DCMAKE_BUILD_TYPE=Release")
|
||||
elif args.mode == "RelWithDebInfo":
|
||||
@@ -272,6 +266,24 @@ def main():
|
||||
else:
|
||||
cmake_args.append("-DLLVM_ENABLE_LTO=OFF")
|
||||
|
||||
if args.target_triple:
|
||||
cmake_args.append(f"-DCLICE_TARGET_TRIPLE={args.target_triple}")
|
||||
cmake_args.append(f"-DLLVM_HOST_TRIPLE={args.target_triple}")
|
||||
|
||||
# When cross-compiling, clear conda's host-platform flags so they
|
||||
# don't leak into the target build (e.g. -L pointing to x86_64 libs).
|
||||
# This must happen before the native-tools build too so we don't
|
||||
# contaminate the native configure with target-arch link flags.
|
||||
for var in ["LIBRARY_PATH", "LDFLAGS", "CFLAGS", "CXXFLAGS", "CPPFLAGS"]:
|
||||
os.environ.pop(var, None)
|
||||
|
||||
# Cross-compilation needs native host tools (tablegen, etc.) that can
|
||||
# run on the build machine. macOS handles this transparently via
|
||||
# Rosetta 2, but Linux and Windows require a separate native build.
|
||||
if sys.platform != "darwin":
|
||||
native_bin_dir = build_native_tools(project_root, build_dir)
|
||||
cmake_args.append(f"-DLLVM_NATIVE_TOOL_DIR={native_bin_dir}")
|
||||
|
||||
build_dir.mkdir(exist_ok=True)
|
||||
|
||||
print(f"\nConfiguring in {build_dir}...")
|
||||
|
||||
Reference in New Issue
Block a user