From 77b4c2d971f40c8dcf54f673040a01edec4e21d9 Mon Sep 17 00:00:00 2001 From: magic-akari Date: Tue, 14 Jul 2026 10:14:28 +0800 Subject: [PATCH] Allow custom C flags in ruby-prism-sys Add RUBY_PRISM_CFLAGS for crate-scoped compiler arguments. Parse the value with shell quoting and apply the resulting arguments to both the vendored C build and bindgen. Pass existing vendored and WASI arguments to bindgen explicitly through the same argument list instead of mutating BINDGEN_EXTRA_CLANG_ARGS. This allows freestanding targets to provide sysroots, include paths, and Prism configuration macros without affecting unrelated Cargo dependencies. Closes #4165. --- rust/Cargo.lock | 1 + rust/ruby-prism-sys/Cargo.toml | 1 + rust/ruby-prism-sys/README.md | 11 +++++++++++ rust/ruby-prism-sys/build/main.rs | 18 +++++++++++++++--- rust/ruby-prism-sys/build/vendored.rs | 24 +++++++++--------------- 5 files changed, 37 insertions(+), 18 deletions(-) diff --git a/rust/Cargo.lock b/rust/Cargo.lock index f9a39b96dc..b8d1b2fe3b 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -222,6 +222,7 @@ version = "1.9.0" dependencies = [ "bindgen", "cc", + "shlex 1.3.0", ] [[package]] diff --git a/rust/ruby-prism-sys/Cargo.toml b/rust/ruby-prism-sys/Cargo.toml index 4578488af3..a907b087e3 100644 --- a/rust/ruby-prism-sys/Cargo.toml +++ b/rust/ruby-prism-sys/Cargo.toml @@ -24,6 +24,7 @@ include = ["src/", "build/", "Cargo.toml", "Cargo.lock", "README.md", "vendor"] [build-dependencies] bindgen = "0.72" cc = { version = "1.0", optional = true } +shlex = "1.3" [features] default = ["vendored"] diff --git a/rust/ruby-prism-sys/README.md b/rust/ruby-prism-sys/README.md index 56fb0cff81..da62011f8d 100644 --- a/rust/ruby-prism-sys/README.md +++ b/rust/ruby-prism-sys/README.md @@ -2,6 +2,17 @@ Rust bindings to [ruby/prism](https://github.com/ruby/prism)'s C API. +## Custom C flags + +Set `RUBY_PRISM_CFLAGS` to pass crate-specific arguments to both the C compiler +and bindgen. Arguments use shell quoting, so paths containing spaces can be +quoted. For example, a freestanding build can provide its sysroot and Prism +configuration without changing the flags of unrelated dependencies: + +```sh +RUBY_PRISM_CFLAGS='--sysroot=/path/to/sysroot -DPRISM_HAS_NO_FILESYSTEM=1' cargo build +``` + ## Examples Currently the best examples are found in the integration tests (in `tests/`). diff --git a/rust/ruby-prism-sys/build/main.rs b/rust/ruby-prism-sys/build/main.rs index 594c01ad9c..7ad19aadc4 100644 --- a/rust/ruby-prism-sys/build/main.rs +++ b/rust/ruby-prism-sys/build/main.rs @@ -4,8 +4,10 @@ mod vendored; use std::path::{Path, PathBuf}; fn main() { + let clang_args = ruby_prism_cflags(); + #[cfg(feature = "vendored")] - vendored::build().expect("failed to build Prism from source"); + let clang_args = vendored::build(clang_args).expect("failed to build Prism from source"); let ruby_build_path = prism_lib_path(); let ruby_include_path = prism_include_path(); @@ -18,13 +20,14 @@ fn main() { println!("cargo:rustc-link-search=native={}", ruby_build_path.to_str().unwrap()); // This is where the magic happens. - let bindings = generate_bindings(&ruby_include_path); + let bindings = generate_bindings(&ruby_include_path, &clang_args); // Write the bindings to file. write_bindings(&bindings); } fn emit_rerun_hints(ruby_include_path: &Path) { + println!("cargo:rerun-if-env-changed=RUBY_PRISM_CFLAGS"); println!("cargo:rerun-if-env-changed=PRISM_INCLUDE_DIR"); println!("cargo:rerun-if-env-changed=PRISM_LIB_DIR"); println!("cargo:rerun-if-changed={}", ruby_include_path.display()); @@ -37,6 +40,14 @@ fn emit_rerun_hints(ruby_include_path: &Path) { } } +fn ruby_prism_cflags() -> Vec { + let Ok(cflags) = std::env::var("RUBY_PRISM_CFLAGS") else { + return Vec::new(); + }; + + shlex::split(&cflags).expect("RUBY_PRISM_CFLAGS contains invalid shell quoting") +} + /// Gets the path to project files (`libprism*`) at `[root]/build/`. /// fn prism_lib_path() -> PathBuf { @@ -110,7 +121,7 @@ impl bindgen::callbacks::ParseCallbacks for Callbacks { /// /// This method only generates code in memory here--it doesn't write it to file. /// -fn generate_bindings(ruby_include_path: &Path) -> bindgen::Bindings { +fn generate_bindings(ruby_include_path: &Path, clang_args: &[String]) -> bindgen::Bindings { bindgen::Builder::default() .derive_default(true) .generate_block(true) @@ -118,6 +129,7 @@ fn generate_bindings(ruby_include_path: &Path) -> bindgen::Bindings { .header(ruby_include_path.join("prism.h").to_str().unwrap()) .clang_arg(format!("-I{}", ruby_include_path.to_str().unwrap())) .clang_arg("-fparse-all-comments") + .clang_args(clang_args) .impl_debug(true) .layout_tests(true) .merge_extern_blocks(true) diff --git a/rust/ruby-prism-sys/build/vendored.rs b/rust/ruby-prism-sys/build/vendored.rs index da01fa6a69..3800f398c2 100644 --- a/rust/ruby-prism-sys/build/vendored.rs +++ b/rust/ruby-prism-sys/build/vendored.rs @@ -4,7 +4,7 @@ use std::{ }; /// Builds libprism.a from source, and configures the build script to use it. -pub fn build() -> Result<(), Box> { +pub fn build(custom_cflags: Vec) -> Result, Box> { assert!( vendor_dir().exists(), "Prism source directory does not exist, expected: {}", @@ -45,19 +45,23 @@ pub fn build() -> Result<(), Box> { } } + flags.extend(custom_cflags); + + let mut bindgen_args = Vec::new(); + for (key, value) in defines { build.define(key, value); - push_bindgen_extra_clang_args(format!("-D{key}={value}")); + bindgen_args.push(format!("-D{key}={value}")); } for include in includes { build.include(&include); - push_bindgen_extra_clang_args(format!("-I{}", include.display())); + bindgen_args.push(format!("-I{}", include.display())); } for flag in flags { build.flag(&flag); - push_bindgen_extra_clang_args(flag); + bindgen_args.push(flag); } build.files(source_files(src_dir())); @@ -67,7 +71,7 @@ pub fn build() -> Result<(), Box> { std::env::set_var("PRISM_INCLUDE_DIR", include_dir()); std::env::set_var("PRISM_LIB_DIR", out_dir); - Ok(()) + Ok(bindgen_args) } fn version() -> &'static str { @@ -92,16 +96,6 @@ fn include_dir() -> PathBuf { vendor_dir().join("include") } -fn push_bindgen_extra_clang_args>(arg: T) { - let env_var_name = format!("BINDGEN_EXTRA_CLANG_ARGS_{}", std::env::var("TARGET").unwrap()); - - if let Ok(preexisting_arg) = std::env::var(&env_var_name) { - std::env::set_var(env_var_name, format!("{} {}", preexisting_arg, arg.as_ref())); - } else { - std::env::set_var(env_var_name, arg.as_ref()); - } -} - fn source_files>(root_dir: P) -> Vec { let mut files = Vec::new();