diff --git a/crates/compositor/build.rs b/crates/compositor/build.rs index 33db6e6c..f8c9e5f5 100644 --- a/crates/compositor/build.rs +++ b/crates/compositor/build.rs @@ -124,6 +124,15 @@ fn main() { .derive_default(true) .layout_tests(false); + // Linux uniquement : cf. `freestanding_header_args()`. Sur macOS le sysroot arrive + // par `xcrun` juste en dessous, sur Windows par MSVC — dans les deux cas clang a + // déjà ses en-têtes builtin et injecter ceux de gcc n'aurait aucun sens. + if target_os == "linux" { + for arg in freestanding_header_args() { + builder = builder.clang_arg(arg); + } + } + // Sur macOS le bindgen doit viser aarch64-apple-darwin pour que les layouts // générés (long=8, etc.) matchent la cible. Sans ce flag, bindgen utilise // le défaut du host (probablement x86_64), et les structs ffmpeg sont mal @@ -162,6 +171,61 @@ fn main() { } } +/// Flags `-I` supplémentaires pour que clang trouve les en-têtes « freestanding » qu'il +/// embarque normalement lui-même (`stddef.h`, `limits.h`, `stdint.h`). +/// +/// Ubuntu scinde libclang : `libclang.so.1` vient du paquet runtime, le répertoire +/// d'en-têtes builtin de `libclang-N-dev`. Avec seulement le premier — le cas courant — +/// parser n'importe quel header réel échoue sur +/// `/usr/include/stdio.h: 'stddef.h' file not found`, parce que la copie de la glibc fait +/// un `#include_next` de celle du compilateur et qu'il n'y en a aucune. Celles de gcc +/// sont interchangeables pour cet usage : on y pointe clang plutôt que d'imposer une +/// seconde toolchain à chaque contributeur. +/// +/// Jumeau de `freestanding_header_args()` dans +/// electron/native/pipewire-capture/build.rs, et comme lui ça vit DANS build.rs plutôt +/// que dans scripts/build-linux-compositor-addon.mjs : tant que seul le script posait +/// `BINDGEN_EXTRA_CLANG_ARGS`, un `cargo check -p openscreen-compositor` nu échouait sur +/// une Ubuntu de série — y compris en x86_64. +fn freestanding_header_args() -> Vec { + if let Ok(extra) = env::var("BINDGEN_EXTRA_CLANG_ARGS") { + // Déjà configuré par l'appelant ; bindgen le lit de lui-même. + if !extra.trim().is_empty() { + return Vec::new(); + } + } + // Le triplet vendeur n'est PAS codé en dur : c'est `x86_64-linux-gnu` sur Debian/ + // Ubuntu amd64, `aarch64-linux-gnu` sur arm64 et `x86_64-pc-linux-gnu` sur Arch. + // Matcher sur le préfixe d'architecture de la CIBLE couvre les trois, et sur une + // machine où un cross-gcc est installé refuse quand même les en-têtes de l'AUTRE + // architecture, dont les largeurs de types seraient fausses. + let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_else(|_| env::consts::ARCH.to_string()); + let prefix = format!("{arch}-"); + let Ok(vendors) = std::fs::read_dir("/usr/lib/gcc") else { + return Vec::new(); + }; + // Les DEUX en-têtes sont exigés : l'échec observé porte tantôt sur `stddef.h` + // (compositor) tantôt sur `limits.h` (pipewire-capture), et un répertoire qui n'a + // que l'un des deux ne réglerait qu'une moitié du problème tout en ayant l'air + // d'un candidat valable. + let mut dirs: Vec = vendors + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.file_name().to_string_lossy().starts_with(&prefix)) + .filter_map(|vendor| std::fs::read_dir(vendor.path()).ok()) + .flatten() + .filter_map(|entry| entry.ok()) + .map(|entry| entry.path().join("include")) + .filter(|dir| dir.join("limits.h").is_file() && dir.join("stddef.h").is_file()) + .collect(); + // gcc le plus récent d'abord, pour une machine qui en a plusieurs. + dirs.sort(); + dirs.reverse(); + dirs.into_iter() + .take(1) + .map(|dir| format!("-I{}", dir.display())) + .collect() +} + /// Ajoute `#[link_name = ""]` devant chaque `pub fn` ffmpeg de `ffi.rs`. /// /// Seul le SYMBOLE importé change ; l'identifiant Rust reste `avformat_open_input`, donc diff --git a/electron/native/pipewire-capture/build.rs b/electron/native/pipewire-capture/build.rs index 0190e818..43ff9d6d 100644 --- a/electron/native/pipewire-capture/build.rs +++ b/electron/native/pipewire-capture/build.rs @@ -63,9 +63,12 @@ fn build_pipewire_shim(root: &Path) { /// `/usr/include/limits.h: 'limits.h' file not found`, because glibc's copy /// `#include_next`s the compiler's and there is none. gcc's copies are /// interchangeable for this purpose, so point clang at those instead of making -/// every contributor install a second toolchain. Mirrors `bindgenClangArgs()` in -/// scripts/build-linux-compositor-addon.mjs, but lives here so that a bare -/// `cargo build` in this directory works too. +/// every contributor install a second toolchain. +/// +/// Twin of `freestanding_header_args()` in crates/compositor/build.rs. Both live in +/// build.rs rather than in the npm build scripts so that a bare `cargo build` works +/// too — scripts/build-linux-compositor-addon.mjs used to carry a copy of this, and +/// `cargo check -p openscreen-compositor` stayed broken for as long as it did. fn freestanding_header_args() -> Vec { if let Ok(extra) = std::env::var("BINDGEN_EXTRA_CLANG_ARGS") { // Already configured by the caller; bindgen picks that up on its own. diff --git a/scripts/build-linux-compositor-addon.mjs b/scripts/build-linux-compositor-addon.mjs index 557ce868..9cf1b735 100644 --- a/scripts/build-linux-compositor-addon.mjs +++ b/scripts/build-linux-compositor-addon.mjs @@ -118,38 +118,15 @@ function resolveLibclangDir() { return found; } -/** - * On distributions that ship only `libclang.so.1` (no -dev package) clang also - * cannot find its own `stddef.h`. Point it at gcc's copy in that case rather - * than making the caller discover it. - * - * Kept behaviourally identical to `freestanding_header_args()` in - * electron/native/pipewire-capture/build.rs, which is the authority — that one - * runs for a bare `cargo build` too. Two properties matter and both were once - * wrong here: `limits.h` must be required alongside `stddef.h` (the failure - * being fixed is `'limits.h' file not found` — glibc's copy `#include_next`s - * the compiler's), and the newest gcc must be picked deterministically rather - * than whatever `readdirSync` happened to return first. - */ -function bindgenClangArgs() { - if (process.env.BINDGEN_EXTRA_CLANG_ARGS) { - return process.env.BINDGEN_EXTRA_CLANG_ARGS; - } - const gccIncludeRoot = `/usr/lib/gcc/${MULTIARCH}`; - if (!fs.existsSync(gccIncludeRoot)) { - return ""; - } - const usable = fs - .readdirSync(gccIncludeRoot) - .map((version) => path.join(gccIncludeRoot, version, "include")) - .filter( - (dir) => - fs.existsSync(path.join(dir, "limits.h")) && fs.existsSync(path.join(dir, "stddef.h")), - ) - .sort() - .reverse(); - return usable.length > 0 ? `-I${usable[0]}` : ""; -} +// No BINDGEN_EXTRA_CLANG_ARGS is set below, on purpose. On distributions shipping only +// `libclang.so.1` (no -dev package) clang cannot find its own `stddef.h`, and this +// script used to paper over that by pointing bindgen at gcc's copies. That only ever +// covered the build that goes through here: `cargo check -p openscreen-compositor` on a +// stock Ubuntu still died on `'stddef.h' file not found`, x86_64 included. The fallback +// now lives in crates/compositor/build.rs (`freestanding_header_args()`), which covers +// both entry points — and, being the only claimant, cannot lose to a worse guess made +// here. Setting the variable again would suppress it, since build.rs defers to a +// caller-supplied value. /** * Prefix applied to every ffmpeg dynamic symbol. Anything unique works; this one is @@ -302,7 +279,6 @@ await run("cargo", ["build", "-p", "compositor-view-napi", "--release"], { FFMPEG_DIR: stagedFfmpegDir, OPENSCREEN_FFMPEG_SYMBOL_PREFIX: SYMBOL_PREFIX, LIBCLANG_PATH: resolveLibclangDir(), - BINDGEN_EXTRA_CLANG_ARGS: bindgenClangArgs(), // `$ORIGIN` is resolved by the dynamic linker against the directory the // .node itself lives in, so the ffmpeg copies below are found wherever the // app is installed. Single-quoted on purpose: the shell must not expand it.