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: 0 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ if_rust_version = "1.0"
konst = "0.3.0" # old one for for rust 1.75
toml = "0.9.5"
rand = "0.9.2"
target-lexicon = "0.13.2"

[patch]

Expand Down
1 change: 0 additions & 1 deletion prebindgen/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ if_rust_version = { workspace = true }
konst = { workspace = true, features = ["cmp"] }
itertools = { workspace = true }
toml = { workspace = true }
target-lexicon = { workspace = true }

[features]
debug = []
Expand Down
13 changes: 9 additions & 4 deletions prebindgen/src/api/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,15 @@ impl Source {
if let Some(target) = &self.target_triple {
let target_triple = TargetTriple::parse(target)
.unwrap_or_else(|e| panic!("Failed to parse target triple '{}': {}", target, e));
builder = builder
.enable_target_arch(target_triple.arch())
.enable_target_os(target_triple.os())
.enable_target_vendor(target_triple.vendor());
if let Some(arch) = target_triple.arch() {
builder = builder.enable_target_arch(arch);
}
if let Some(vendor) = target_triple.vendor() {
builder = builder.enable_target_vendor(vendor);
}
if let Some(os) = target_triple.os() {
builder = builder.enable_target_os(os);
}
if let Some(env) = target_triple.env() {
builder = builder.enable_target_env(env);
}
Expand Down
205 changes: 150 additions & 55 deletions prebindgen/src/api/utils/target_triple.rs
Original file line number Diff line number Diff line change
@@ -1,81 +1,117 @@
use proc_macro2::TokenStream;
use quote::quote;
use syn::LitStr;
use target_lexicon::{OperatingSystem, Triple};

/// TargetTriple is a small utility around `target_lexicon::Triple` with helpers
/// to access parts and to convert into Rust cfg tokens.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TargetTriple(Triple);
pub struct TargetTriple {
triple: String,
arch: Option<String>,
vendor: Option<String>,
os: Option<String>,
env: Option<String>,
}

fn extract_cfg_condition(s: &str, name: &str) -> Option<String> {
let prefix = format!("{}=\"", name);
if s.starts_with(&prefix) && s.ends_with('"') {
let s = &s[prefix.len()..s.len() - 1];
if s.is_empty() {
None
} else {
Some(s.to_string())
}
} else {
None
}
}

impl TargetTriple {
/// Parse from a string like "aarch64-apple-darwin".
pub fn parse(s: &str) -> Result<Self, String> {
s.parse::<Triple>()
.map(TargetTriple)
.map_err(|e| format!("Failed to parse target triple '{s}': {e}"))
}
pub fn parse(s: impl Into<String>) -> Result<Self, String> {
let triple = s.into();
// run `rustc --print cfg --target <target_triple>` and capture the output
let output = std::process::Command::new("rustc")
.args(["--print", "cfg", "--target", &triple])
.output()
.map_err(|e| format!("Failed to run rustc: {e}"))?;

if !output.status.success() {
return Err(format!("rustc failed with status: {}", output.status));
}

let stdout = String::from_utf8_lossy(&output.stdout);
let mut arch = None;
let mut vendor = None;
let mut os = None;
let mut env = None;

for line in stdout.lines() {
if let Some(a) = extract_cfg_condition(line, "target_arch") {
arch = Some(a);
} else if let Some(v) = extract_cfg_condition(line, "target_vendor") {
vendor = Some(v);
} else if let Some(o) = extract_cfg_condition(line, "target_os") {
os = Some(o);
} else if let Some(e) = extract_cfg_condition(line, "target_env") {
env = Some(e);
}
}

/// Create from an existing target_lexicon Triple.
pub fn from_triple(triple: Triple) -> Self {
Self(triple)
Ok(Self {
triple,
arch,
vendor,
os,
env,
})
}

/// Get the architecture as a canonical string used by Rust cfg target_arch.
pub fn arch(&self) -> String {
self.0.architecture.to_string()
pub fn arch(&self) -> Option<&str> {
self.arch.as_deref()
}

/// Get the vendor as string used by Rust cfg target_vendor.
pub fn vendor(&self) -> String {
self.0.vendor.to_string()
pub fn vendor(&self) -> Option<&str> {
self.vendor.as_deref()
}

/// Get the operating system as string used by Rust cfg target_os.
/// Maps Darwin to "macos" to match Rust cfg semantics.
pub fn os(&self) -> String {
match self.0.operating_system {
OperatingSystem::Darwin(_) => "macos".to_string(),
ref os => os.to_string(),
}
pub fn os(&self) -> Option<&str> {
self.os.as_deref()
}

/// Get the environment as string used by Rust cfg target_env (may be "unknown").
pub fn env(&self) -> Option<String> {
if self.0.environment == target_lexicon::Environment::Unknown {
None
} else {
Some(self.0.environment.to_string())
}
}

/// Access the inner Triple.
pub fn as_triple(&self) -> &Triple {
&self.0
}

/// Decompose into the inner Triple.
pub fn into_triple(self) -> Triple {
self.0
pub fn env(&self) -> Option<&str> {
self.env.as_deref()
}

/// Build a cfg expression TokenStream like:
/// all(target_arch = "aarch64", target_vendor = "apple", target_os = "macos", target_env = "gnu")
/// Omits target_env when unknown/empty.
pub fn to_cfg_tokens(&self) -> TokenStream {
let arch = LitStr::new(&self.arch(), proc_macro2::Span::call_site());
let vendor = LitStr::new(&self.vendor(), proc_macro2::Span::call_site());
let os = LitStr::new(&self.os(), proc_macro2::Span::call_site());
let env = self.env();
let mut parts: Vec<TokenStream> = Vec::with_capacity(4);
parts.push(quote! { target_arch = #arch });
parts.push(quote! { target_vendor = #vendor });
parts.push(quote! { target_os = #os });
if let Some(env) = env {
let env_lit = LitStr::new(&env, proc_macro2::Span::call_site());
parts.push(quote! { target_env = #env_lit });
if let Some(ref arch) = self.arch {
let arch = LitStr::new(arch, proc_macro2::Span::call_site());
parts.push(quote! { target_arch = #arch });
}
if parts.len() == 1 {
if let Some(ref vendor) = self.vendor {
let vendor = LitStr::new(vendor, proc_macro2::Span::call_site());
parts.push(quote! { target_vendor = #vendor });
}
if let Some(ref os) = self.os {
let os = LitStr::new(os, proc_macro2::Span::call_site());
parts.push(quote! { target_os = #os });
}
if let Some(ref env) = self.env {
let env = LitStr::new(env, proc_macro2::Span::call_site());
parts.push(quote! { target_env = #env });
}
if parts.is_empty() {
quote! { true }
} else if parts.len() == 1 {
parts.remove(0)
} else {
quote! { all( #(#parts),* ) }
Expand Down Expand Up @@ -103,18 +139,77 @@ mod tests {
use super::*;

#[test]
fn maps_darwin_to_macos() {
fn aarch64_apple_darwin() {
let tt = TargetTriple::parse("aarch64-apple-darwin").unwrap();
assert_eq!(tt.os(), "macos");
assert!(
tt.arch() == Some("aarch64"),
"Unexpected architecture found {:?}",
tt.arch()
);
assert!(
tt.vendor() == Some("apple"),
"Unexpected vendor found {:?}",
tt.vendor()
);
assert!(
tt.os() == Some("macos"),
"Unexpected OS found {:?}",
tt.os()
);
assert!(
tt.env().is_none(),
"Unexpected environment found {:?}",
tt.env()
);
}

#[test]
fn builds_cfg_without_unknown_env() {
fn x86_64_unknown_linux() {
let tt = TargetTriple::parse("x86_64-unknown-linux-gnu").unwrap();
let ts = tt.to_cfg_tokens().to_string();
assert!(ts.contains("target_arch = \"x86_64\""));
assert!(ts.contains("target_vendor = \"unknown\""));
assert!(ts.contains("target_os = \"linux\""));
assert!(ts.contains("target_env = \"gnu\""));
assert!(
tt.arch() == Some("x86_64"),
"Unexpected architecture found {:?}",
tt.arch()
);
assert!(
tt.vendor() == Some("unknown"),
"Unexpected vendor found {:?}",
tt.vendor()
);
assert!(
tt.os() == Some("linux"),
"Unexpected OS found {:?}",
tt.os()
);
assert!(
tt.env() == Some("gnu"),
"Unexpected environment found {:?}",
tt.env()
);
}

#[test]
fn armv7_unknown_linux_gnueabihf() {
let tt = TargetTriple::parse("armv7-unknown-linux-gnueabihf").unwrap();
assert!(
tt.arch() == Some("arm"),
"Unexpected architecture found {:?}",
tt.arch()
);
assert!(
tt.vendor() == Some("unknown"),
"Unexpected vendor found {:?}",
tt.vendor()
);
assert!(
tt.os() == Some("linux"),
"Unexpected OS found {:?}",
tt.os()
);
assert!(
tt.env() == Some("gnu"),
"Unexpected environment found {:?}",
tt.env()
);
}
}