Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/ruby-prism-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down
11 changes: 11 additions & 0 deletions rust/ruby-prism-sys/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/`).
Expand Down
18 changes: 15 additions & 3 deletions rust/ruby-prism-sys/build/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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());
Expand All @@ -37,6 +40,14 @@ fn emit_rerun_hints(ruby_include_path: &Path) {
}
}

fn ruby_prism_cflags() -> Vec<String> {
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 {
Expand Down Expand Up @@ -110,14 +121,15 @@ 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)
.generate_comments(true)
.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)
Expand Down
24 changes: 9 additions & 15 deletions rust/ruby-prism-sys/build/vendored.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
};

/// Builds libprism.a from source, and configures the build script to use it.
pub fn build() -> Result<(), Box<dyn std::error::Error>> {
pub fn build(custom_cflags: Vec<String>) -> Result<Vec<String>, Box<dyn std::error::Error>> {
assert!(
vendor_dir().exists(),
"Prism source directory does not exist, expected: {}",
Expand Down Expand Up @@ -45,19 +45,23 @@ pub fn build() -> Result<(), Box<dyn std::error::Error>> {
}
}

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()));
Expand All @@ -67,7 +71,7 @@ pub fn build() -> Result<(), Box<dyn std::error::Error>> {
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 {
Expand All @@ -92,16 +96,6 @@ fn include_dir() -> PathBuf {
vendor_dir().join("include")
}

fn push_bindgen_extra_clang_args<T: AsRef<str>>(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<P: AsRef<Path>>(root_dir: P) -> Vec<String> {
let mut files = Vec::new();

Expand Down
Loading