use std::env;
use std::path::PathBuf;
fn main() {
build_vendored();
generate_bindings();
}
fn build_vendored() {
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let re_dir = manifest.join("vendor/re");
let baresip_dir = manifest.join("vendor/baresip");
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
let profile = env::var("PROFILE").unwrap_or("debug".into());
let openssl_include = env::var("DEP_OPENSSL_INCLUDE").unwrap_or_default();
let openssl_root: Option<PathBuf> = if !openssl_include.is_empty() {
let root = PathBuf::from(&openssl_include)
.parent()
.map(|p| p.to_path_buf())
.unwrap_or_default();
if root.exists() { Some(root) } else { None }
} else {
None
};
let macos_arch: Option<&str> = if env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("macos") {
match env::var("CARGO_CFG_TARGET_ARCH").as_deref() {
Ok("aarch64") => Some("arm64"),
_ => Some("x86_64"),
}
} else {
None
};
let re_build = out.join("re-build");
let re_install = out.join("re-install");
let mut re_cmake_args = vec![
"-B".to_string(),
re_build.to_str().unwrap().into(),
"-S".to_string(),
re_dir.to_str().unwrap().into(),
format!("-DCMAKE_INSTALL_PREFIX={}", re_install.display()),
format!(
"-DCMAKE_BUILD_TYPE={}",
if profile == "release" {
"Release"
} else {
"Debug"
}
),
"-DBUILD_SHARED_LIBS=OFF".into(),
"-DRE_SHARED=OFF".into(),
"-DCMAKE_POSITION_INDEPENDENT_CODE=ON".into(),
];
if let Some(ref root) = openssl_root {
re_cmake_args.push(format!("-DOPENSSL_ROOT_DIR={}", root.display()));
re_cmake_args.push(format!("-DOPENSSL_INCLUDE_DIR={}", openssl_include));
}
if let Some(arch) = macos_arch {
re_cmake_args.push(format!("-DCMAKE_OSX_ARCHITECTURES={arch}"));
}
run(
"cmake",
&re_cmake_args.iter().map(|s| s.as_str()).collect::<Vec<_>>(),
);
run(
"cmake",
&[
"--build",
re_build.to_str().unwrap(),
"--parallel",
"--target",
"install",
],
);
let mut static_mods = vec![
"g711",
"g722",
"l16",
"opus",
"aubridge",
"ausine",
"aufile",
"auconv",
"auresamp",
"stun",
"turn",
"ice",
"srtp",
"dtls_srtp",
"mwi",
"netroam",
];
let feature_audio_mods = enabled_audio_modules();
static_mods.extend_from_slice(&feature_audio_mods);
let static_modules = static_mods.join(";");
let audio_csv = feature_audio_mods.join(",");
println!("cargo:rustc-env=RINGO_AUDIO_MODULES={audio_csv}");
let default_audio = default_audio_driver(&feature_audio_mods);
println!("cargo:rustc-env=RINGO_DEFAULT_AUDIO={default_audio}");
let baresip_build = out.join("baresip-build");
let baresip_install = out.join("baresip-install");
let mut baresip_cmake_args = vec![
"-B".to_string(),
baresip_build.to_str().unwrap().into(),
"-S".to_string(),
baresip_dir.to_str().unwrap().into(),
format!("-DCMAKE_INSTALL_PREFIX={}", baresip_install.display()),
format!(
"-DCMAKE_BUILD_TYPE={}",
if profile == "release" {
"Release"
} else {
"Debug"
}
),
format!("-DMODULES={}", static_modules),
"-DSTATIC=ON".into(),
"-DCMAKE_POSITION_INDEPENDENT_CODE=ON".into(),
format!("-Dre_DIR={}/lib/cmake/re", re_install.display()),
format!("-DCMAKE_PREFIX_PATH={}", re_install.display()),
];
if let Some(ref root) = openssl_root {
baresip_cmake_args.push(format!("-DOPENSSL_ROOT_DIR={}", root.display()));
baresip_cmake_args.push(format!("-DOPENSSL_INCLUDE_DIR={}", openssl_include));
}
if let Some(arch) = macos_arch {
baresip_cmake_args.push(format!("-DCMAKE_OSX_ARCHITECTURES={arch}"));
}
run(
"cmake",
&baresip_cmake_args
.iter()
.map(|s| s.as_str())
.collect::<Vec<_>>(),
);
run(
"cmake",
&[
"--build",
baresip_build.to_str().unwrap(),
"--parallel",
"--target",
"install",
],
);
link_audio_system_libs(&feature_audio_mods);
println!(
"cargo:rustc-link-search=native={}",
baresip_install.join("lib").display()
);
println!(
"cargo:rustc-link-search=native={}",
re_install.join("lib").display()
);
println!(
"cargo:baresip_lib={}",
baresip_install.join("lib/libbaresip.a").display()
);
println!("cargo:re_lib={}", re_install.join("lib/libre.a").display());
for lib in &["z", "resolv", "m", "dl", "pthread"] {
println!("cargo:rustc-link-lib=dylib={lib}");
}
if cfg!(target_os = "macos") {
if let Ok(out) = std::process::Command::new("brew").arg("--prefix").output() {
if let Ok(prefix) = String::from_utf8(out.stdout) {
println!("cargo:rustc-link-search=native={}/lib", prefix.trim());
}
}
}
println!("cargo:rustc-link-lib=dylib=spandsp");
println!("cargo:rustc-link-lib=dylib=opus");
if let Some(ref root) = openssl_root {
let lib_dir = root.join("lib");
println!("cargo:rustc-link-search=native={}", lib_dir.display());
println!("cargo:rustc-link-lib=static=ssl");
println!("cargo:rustc-link-lib=static=crypto");
}
let mut after: Vec<String> = Vec::new();
after.push("-lspandsp".into());
after.push("-lopus".into());
for m in &feature_audio_mods {
match *m {
"pulse" => {
after.push("-lpulse".into());
after.push("-lpulse-simple".into());
}
"alsa" => after.push("-lasound".into()),
"coreaudio" => {
after.push("-framework".into());
after.push("CoreAudio".into());
after.push("-framework".into());
after.push("AudioToolbox".into());
}
_ => {}
}
}
if let Some(ref root) = openssl_root {
after.push(format!("{}/lib/libssl.a", root.display()));
after.push(format!("{}/lib/libcrypto.a", root.display()));
}
if cfg!(target_os = "macos") {
after.push("-framework".into());
after.push("SystemConfiguration".into());
after.push("-lz".into());
after.push("-lresolv".into());
} else {
after.push("-lz".into());
after.push("-lresolv".into());
after.push("-ldl".into());
after.push("-lpthread".into());
after.push("-lc".into());
after.push("-lm".into());
}
println!("cargo:link_after_archives={}", after.join("\u{1f}"));
println!(
"cargo:rerun-if-changed={}",
manifest.join("vendor/re/src").display()
);
println!(
"cargo:rerun-if-changed={}",
manifest.join("vendor/baresip/src").display()
);
}
fn enabled_audio_modules() -> Vec<&'static str> {
let mut mods = vec![];
if cfg!(feature = "pulse") {
mods.push("pulse");
}
if cfg!(feature = "alsa") {
mods.push("alsa");
}
if cfg!(feature = "coreaudio") {
mods.push("coreaudio");
}
if mods.is_empty() && cfg!(feature = "default-audio") && env::var("RINGO_NO_AUDIO").is_err() {
#[cfg(target_os = "macos")]
{
mods.push("coreaudio");
}
#[cfg(target_os = "linux")]
{
if pkg_config::probe_library("libpulse").is_ok() {
mods.push("pulse");
}
}
}
mods
}
fn default_audio_driver(mods: &[&str]) -> String {
for pref in &["pulse", "alsa", "coreaudio"] {
if mods.contains(pref) {
return pref.to_string();
}
}
"aubridge".to_string()
}
fn link_audio_system_libs(mods: &[&str]) {
for m in mods {
match *m {
"pulse" => {
if let Ok(lib) = pkg_config::probe_library("libpulse") {
for path in &lib.include_paths {
println!("cargo:rustc-link-search=native={}", path.display());
}
println!("cargo:rustc-link-lib=pulse");
}
if let Ok(lib) = pkg_config::probe_library("libpulse-simple") {
for path in &lib.include_paths {
println!("cargo:rustc-link-search=native={}", path.display());
}
println!("cargo:rustc-link-lib=pulse-simple");
}
}
"alsa" => {
if let Ok(lib) = pkg_config::probe_library("alsa") {
for path in &lib.include_paths {
println!("cargo:rustc-link-search=native={}", path.display());
}
println!("cargo:rustc-link-lib=asound");
}
}
"coreaudio" => {
println!("cargo:rustc-link-lib=framework=CoreAudio");
println!("cargo:rustc-link-lib=framework=AudioToolbox");
}
_ => {}
}
}
}
fn run(cmd: &str, args: &[&str]) {
use std::process::Command;
let status = Command::new(cmd)
.args(args)
.status()
.unwrap_or_else(|e| panic!("failed to run {cmd}: {e}"));
if !status.success() {
panic!("{cmd} failed with status {status}");
}
}
fn generate_bindings() {
let manifest = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let wrapper = manifest.join("wrapper.h");
let mut include_paths: Vec<PathBuf> = Vec::new();
include_paths.push(manifest.join("vendor/re/include"));
include_paths.push(manifest.join("vendor/baresip/include"));
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
include_paths.push(out.join("re-install/include"));
include_paths.push(out.join("baresip-install/include"));
let mut builder = bindgen::Builder::default()
.header(wrapper.to_str().unwrap())
.rust_edition(bindgen::RustEdition::Edition2024)
.size_t_is_usize(true)
.allowlist_function("libre_init|libre_close|re_main|re_cancel|re_thread.*|re_thread_async_close|dbg_.*|mem_deref|mod_close|list_apply")
.allowlist_function("ua_.*|uag_find_msg|uag_list|uag_call_count|baresip_init|baresip_close|conf_.*|module_load|module_app_unload|module_unload")
.allowlist_function("account_.*|call_.*|audio_.*")
.allowlist_function("ausrc_register|baresip_ausrcl|auplay_register|baresip_auplayl|auframe_init|aufmt_sample_size|mem_zalloc|mem_alloc")
.allowlist_function("bevent_.*")
.allowlist_function("uag_sip|sip_set_trace_handler|sip_transp_name|sip_treplyf")
.allowlist_function("sa_af|sa_in|sa_in6|sa_port")
.allowlist_function("log_enable_.*|log_register_handler|log_unregister_handler|log_level_set|log_level_get")
.allowlist_function("play_.*|baresip_player|play_set_.*")
.allowlist_function("mbuf_.*")
.allowlist_function("g711_.*")
.allowlist_type("ua|call|bevent|audio|account|config|list|le|pl|sip_hdr|sip_msg|sip_taddr|list_apply_h")
.allowlist_type("vidmode|dtmfmode|bevent_ev|dbg_flags|dbg_print_h|log_level|log_h|log")
.allowlist_type("play|player|play_finish_h")
.allowlist_type("mbuf")
.allowlist_type("ausrc|ausrc_prm|ausrc_read_h|ausrc_error_h|ausrc_alloc_h|auframe|aufmt|mem_destroy_h")
.allowlist_type("auplay|auplay_prm|auplay_write_h|auplay_alloc_h")
.allowlist_type("sip|sa|sip_transp|sip_trace_h|sip_strans")
.allowlist_var("KEYCODE_REL")
.opaque_type("ua|call|bevent|audio|account|config")
.opaque_type("play|player")
.opaque_type("sip|sa|sip_strans")
.rustified_enum("vidmode|dtmfmode|bevent_ev|aufmt")
.derive_default(false)
.derive_copy(true)
.generate_comments(false)
.layout_tests(false)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()));
for path in &include_paths {
builder = builder.clang_arg(format!("-I{}", path.display()));
}
let bindings = builder
.generate()
.expect("failed to generate bindings from C headers");
let out = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out.join("bindings.rs"))
.expect("failed to write bindings.rs");
}