-
Notifications
You must be signed in to change notification settings - Fork 71
fix(build): reject system LLVM outside compiler sysroot #426
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -179,6 +179,55 @@ def parse_version_tuple(text: str) -> tuple[int, ...]: | |
| return tuple(digits) | ||
|
|
||
|
|
||
| # When building with pixi or NixOS, the compiler is sandboxed inside its own | ||
| # sysroot. System LLVM at /usr is outside that sysroot, so linking against it | ||
| # pulls in libraries built for the host's libc/libstdc++ which may be | ||
| # ABI-incompatible with the ones inside the sandbox. Detect this and refuse to | ||
| # use any LLVM install that falls outside the compiler's sysroot. | ||
| def compiler_sysroot() -> Path | None: | ||
| """Return the ``--sysroot`` from clang++'s per-target config, if any.""" | ||
| compiler = shutil.which("clang++") | ||
| if not compiler: | ||
| return None | ||
|
|
||
| try: | ||
| triple = subprocess.check_output([compiler, "-dumpmachine"], text=True).strip() | ||
| except (subprocess.CalledProcessError, OSError): | ||
| return None | ||
|
|
||
| if not triple: | ||
| return None | ||
|
|
||
| cfg = Path(compiler).with_name(f"{triple}.cfg") | ||
| if not cfg.is_file(): | ||
| return None | ||
|
|
||
| try: | ||
| for line in cfg.read_text(encoding="utf-8").splitlines(): | ||
| if not line.startswith("--sysroot="): | ||
| continue | ||
| sysroot = Path(line.split("=", 1)[1]).resolve() | ||
| if sysroot.exists(): | ||
| return sysroot | ||
| except OSError: | ||
| return None | ||
|
|
||
| return None | ||
|
Comment on lines
+182
to
+215
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: No, pixi (prefix.dev) does not generate a <triple>.cfg file next to clang++. Pixi, built on the conda/rattler ecosystem, installs clang packages from conda-forge/prefix.dev channels like clang_osx-64 or clang_linux-64. These packages provide the clang++ binary directly without generating Clang driver config files (.cfg) containing --sysroot next to it. Clang config files are a native Clang/LLVM feature for default flags (e.g., x86_64-unknown-linux-gnu.cfg), loaded automatically by the driver, but no evidence shows pixi/rattler-build or conda clang packages creating them dynamically. Sysroot handling in conda/pixi clang occurs via explicit --sysroot flags (e.g., in cross-compilation via CONDA_BUILD_SYSROOT or compiler activation), environment variables like SDKROOT (macOS), or compiler activation scripts—not .cfg files. nixpkgs stdenv configures clang's sysroot primarily through a wrapper script (cc-wrapper.sh), not config files. stdenv wraps clang++ (often clang-unwrapped) in a bash wrapper that sets environment variables (NIX_CFLAGS_COMPILE, defaultTarget), adds flags like -nostdlibinc (Linux, to block /usr/include), and passes search paths (-L, -B) via nix-support/cc-{cflags,ldflags}. For cross-compilation, it adjusts paths (e.g., /${targetPlatform.config}) and sets machineFlags/defaultTarget. No .cfg files are generated; sysroot is handled via wrapper flags/env vars, preserving --sysroot user overrides. Citations:
The detection mechanism doesn't work for either pixi or NixOS — the PR won't prevent the stated failure mode in either environment. The implementation assumes both pixi and NixOS place a
In both cases, 🧰 Tools🪛 Ruff (0.15.10)[error] 194-194: (S603) 🤖 Prompt for AI Agents
Comment on lines
+187
to
+215
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Detected compiler may not be the one CMake uses.
🧰 Tools🪛 Ruff (0.15.10)[error] 194-194: (S603) 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def llvm_install_matches_compiler(prefix: Path) -> bool: | ||
| """True when *prefix* is inside the compiler's sysroot (or no sysroot).""" | ||
| sysroot = compiler_sysroot() | ||
| if sysroot is None: | ||
| return True | ||
|
|
||
| try: | ||
| prefix.resolve().relative_to(sysroot) | ||
| return True | ||
| except ValueError: | ||
| return False | ||
|
Comment on lines
+224
to
+228
|
||
|
|
||
|
|
||
| def system_llvm_ok(required_version: str, build_type: str) -> Path | None: | ||
| if build_type.lower().startswith("debug"): | ||
| return None | ||
|
|
@@ -194,7 +243,12 @@ def system_llvm_ok(required_version: str, build_type: str) -> Path | None: | |
| found = parse_version_tuple(version) | ||
| if not found or found < required: | ||
| return None | ||
| return Path(prefix) | ||
|
|
||
| prefix_path = Path(prefix) | ||
| if not llvm_install_matches_compiler(prefix_path): | ||
| return None | ||
|
|
||
| return prefix_path | ||
|
|
||
|
|
||
| def github_api(url: str, token: str | None) -> dict: | ||
|
|
@@ -287,13 +341,21 @@ def main() -> None: | |
| if args.install_path: | ||
| candidate = Path(args.install_path) | ||
| if candidate.exists(): | ||
| log(f"Using provided LLVM install at {candidate}") | ||
| if llvm_install_matches_compiler(candidate): | ||
| log(f"Using provided LLVM install at {candidate}") | ||
| install_path = candidate | ||
| else: | ||
| log( | ||
| f"Provided LLVM install at {candidate} is outside the active compiler sysroot; " | ||
| "will install a bundled LLVM instead" | ||
| ) | ||
| needs_install = True | ||
| else: | ||
| log( | ||
| f"Provided LLVM install path does not exist; will install to {candidate}" | ||
| ) | ||
| needs_install = True | ||
| install_path = candidate | ||
| install_path = candidate | ||
|
Comment on lines
353
to
+358
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sysroot check is skipped when When the user passes Consider applying 🤖 Prompt for AI Agents |
||
| else: | ||
| detected = system_llvm_ok(args.version, build_type) | ||
| if detected: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
compiler_sysroot()only recognizes lines that start exactly with--sysroot=. Clang config files commonly contain blank lines/comments and may include leading whitespace or use the--sysroot <path>form; in those cases this would fail to detect the active sysroot and could reintroduce the ABI-mismatch scenario this PR is trying to prevent. It would be more robust to strip whitespace, ignore comments, and accept both--sysroot=and--sysrootforms when parsing the cfg.