use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let vendor_dir = manifest_dir.join("vendor");
let vendor_src = vendor_dir.join("src");
let fax = env::var("CARGO_FEATURE_FAX").is_ok();
let v32bis = env::var("CARGO_FEATURE_V32BIS").is_ok();
let v34 = env::var("CARGO_FEATURE_V34").is_ok();
let ssl_fax = env::var("CARGO_FEATURE_SSL_FAX").is_ok();
// Phase A: Generate headers
generate_config_h(&out_dir, fax, v32bis, v34);
generate_spandsp_h(&out_dir, &vendor_src, fax, v32bis, v34);
generate_version_h(&out_dir);
// Create spandsp subdirectory in OUT_DIR for version.h
let spandsp_dir = out_dir.join("spandsp");
fs::create_dir_all(&spandsp_dir).unwrap();
// Copy version.h into spandsp/ subdirectory so #include <spandsp/version.h> works
fs::copy(out_dir.join("version.h"), spandsp_dir.join("version.h")).unwrap();
// Phase B: Build and run code generators
run_generators(&out_dir, &vendor_src, fax, v34);
// Phase C: Compile C sources
compile_c_sources(&out_dir, &vendor_src, fax, v32bis, v34, ssl_fax);
// Phase D: Link system libraries
link_system_libraries(fax, ssl_fax);
// Phase E: Run bindgen
run_bindgen(&out_dir, &vendor_src, &manifest_dir, fax);
}
fn generate_config_h(out_dir: &Path, fax: bool, v32bis: bool, v34: bool) {
let mut config = String::new();
config.push_str("/* Generated by build.rs */\n");
config.push_str("#if !defined(_CONFIG_H_)\n");
config.push_str("#define _CONFIG_H_\n\n");
// Math functions
for func in &[
"SINF", "COSF", "TANF", "ASINF", "ACOSF", "ATANF", "ATAN2F", "CEILF", "FLOORF", "POWF",
"EXPF", "LOGF", "LOG10F",
] {
config.push_str(&format!("#define HAVE_{func}\n"));
}
config.push('\n');
// Headers
config.push_str("#define HAVE_MATH_H 1\n");
config.push_str("#define HAVE_TGMATH_H 1\n");
config.push_str("#define HAVE_STDBOOL_H 1\n");
config.push_str("#define HAVE_INTTYPES_H 1\n");
config.push_str("#define HAVE_STDINT_H 1\n");
config.push_str("#define HAVE_FCNTL_H 1\n");
config.push_str("#define HAVE_STDLIB_H 1\n");
config.push_str("#define HAVE_STRING_H 1\n");
if !cfg!(target_os = "windows") {
config.push_str("#define HAVE_UNISTD_H 1\n");
config.push_str("#define HAVE_SYS_TIME_H 1\n");
config.push_str("#define HAVE_SYS_SOCKET_H 1\n");
config.push_str("#define HAVE_SYS_SELECT_H 1\n");
}
config.push('\n');
// Types
config.push_str("#define HAVE_LONG_DOUBLE 1\n");
config.push('\n');
// Functions
config.push_str("#define HAVE_MEMMOVE 1\n");
config.push_str("#define HAVE_MALLOC 1\n");
config.push_str("#define HAVE_REALLOC 1\n");
config.push_str("#define HAVE_FREE 1\n");
config.push_str("#define HAVE_STRCASECMP 1\n");
config.push_str("#define HAVE_STRNCASECMP 1\n");
if !cfg!(target_os = "windows") {
config.push_str("#define HAVE_GETTIMEOFDAY 1\n");
config.push_str("#define HAVE_OPEN_MEMSTREAM 1\n");
config.push_str("#define HAVE_DRAND48 1\n");
}
config.push('\n');
// Tiff support
if fax {
config.push_str("#define HAVE_LIBTIFF 4\n");
config.push_str("#define HAVE_LIBJPEG 1\n");
}
config.push('\n');
// Package info
config.push_str("#define PACKAGE \"spandsp\"\n");
config.push_str("#define VERSION \"3.0.0\"\n");
config.push('\n');
// Feature flags
if v32bis {
config.push_str("#define SPANDSP_SUPPORT_V32BIS 1\n");
}
if v34 {
config.push_str("#define SPANDSP_SUPPORT_V34 1\n");
}
config.push_str("\n#endif\n");
fs::write(out_dir.join("config.h"), config).unwrap();
}
fn generate_spandsp_h(out_dir: &Path, vendor_src: &Path, fax: bool, v32bis: bool, v34: bool) {
let template = fs::read_to_string(vendor_src.join("spandsp.h.in")).unwrap();
let output = template
// Fixed point - not using
.replace(
"@SPANDSP_USE_FIXED_POINT@",
"/* #undef SPANDSP_USE_FIXED_POINT */",
)
// Misaligned access - not relevant
.replace(
"@SPANDSP_MISALIGNED_ACCESS_FAILS@",
"/* #undef SPANDSP_MISALIGNED_ACCESS_FAILS */",
)
// Export capability - not needed for static linking
.replace(
"@SPANDSP_USE_EXPORT_CAPABILITY@",
"/* #undef SPANDSP_USE_EXPORT_CAPABILITY */",
)
// T.43 support - only with fax
.replace(
"@SPANDSP_SUPPORT_T43@",
if fax {
"#define SPANDSP_SUPPORT_T43 1"
} else {
"/* #undef SPANDSP_SUPPORT_T43 */"
},
)
// V.32bis support
.replace(
"@SPANDSP_SUPPORT_V32BIS@",
if v32bis {
"#define SPANDSP_SUPPORT_V32BIS 1"
} else {
"/* #undef SPANDSP_SUPPORT_V32BIS */"
},
)
// V.34 support
.replace(
"@SPANDSP_SUPPORT_V34@",
if v34 {
"#define SPANDSP_SUPPORT_V34 1"
} else {
"/* #undef SPANDSP_SUPPORT_V34 */"
},
)
// TIFF FX - not supported
.replace(
"@SPANDSP_SUPPORT_TIFF_FX@",
"/* #undef SPANDSP_SUPPORT_TIFF_FX */",
)
// Standard headers
.replace("@INSERT_INTTYPES_HEADER@", "#include <inttypes.h>")
.replace("@INSERT_MATH_HEADER@", "#include <math.h>")
.replace("@INSERT_STDBOOL_HEADER@", "#include <stdbool.h>");
// Handle tiffio.h include - only when fax feature is enabled
let output = if fax {
output
} else {
output.replace(
"#include <tiffio.h>",
"/* #include <tiffio.h> -- fax feature disabled */",
)
};
fs::write(out_dir.join("spandsp.h"), output).unwrap();
}
fn generate_version_h(out_dir: &Path) {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap();
// Simple date/time generation
let secs = now.as_secs();
// Approximate UTC date components
let days = secs / 86400;
let time_of_day = secs % 86400;
let hours = time_of_day / 3600;
let minutes = (time_of_day % 3600) / 60;
let seconds = time_of_day % 60;
// Approximate year/month/day from days since epoch
let (year, month, day) = days_to_ymd(days);
let date_str = format!("{year:04}{month:02}{day:02}");
let time_str = format!("{hours:02}{minutes:02}{seconds:02}");
let version_h = format!(
r#"/*
* SpanDSP - version.h - Generated by build.rs
*/
#if !defined(_SPANDSP_VERSION_H_)
#define _SPANDSP_VERSION_H_
#define SPANDSP_RELEASE_DATE {date_str}
#define SPANDSP_RELEASE_TIME {time_str}
#define SPANDSP_RELEASE_DATETIME_STRING "{date_str} {time_str}"
#endif
"#
);
fs::write(out_dir.join("version.h"), version_h).unwrap();
}
fn days_to_ymd(days_since_epoch: u64) -> (u64, u64, u64) {
// Simple algorithm to convert days since 1970-01-01 to (year, month, day)
let mut y = 1970;
let mut remaining = days_since_epoch;
loop {
let days_in_year = if is_leap(y) { 366 } else { 365 };
if remaining < days_in_year {
break;
}
remaining -= days_in_year;
y += 1;
}
let days_in_months: [u64; 12] = if is_leap(y) {
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
} else {
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
};
let mut m = 0;
for (i, &dim) in days_in_months.iter().enumerate() {
if remaining < dim {
m = i;
break;
}
remaining -= dim;
}
(y, (m + 1) as u64, remaining + 1)
}
fn is_leap(y: u64) -> bool {
(y.is_multiple_of(4) && !y.is_multiple_of(100)) || y.is_multiple_of(400)
}
fn run_generators(out_dir: &Path, vendor_src: &Path, fax: bool, v34: bool) {
let cc = env::var("CC").unwrap_or_else(|_| "cc".to_string());
// Helper to compile and run a generator
let compile_and_run =
|sources: &[&str], extra_args: &[&str], output_file: &str, run_args: &[&str]| {
let bin_path = out_dir.join(format!(
"gen_{}",
Path::new(output_file)
.file_stem()
.unwrap()
.to_str()
.unwrap()
));
let mut cmd = Command::new(&cc);
cmd.arg("-o")
.arg(&bin_path)
.arg("-DHAVE_CONFIG_H")
.arg(format!("-I{}", out_dir.display()))
.arg(format!("-I{}", vendor_src.display()));
for src in sources {
cmd.arg(vendor_src.join(src));
}
for arg in extra_args {
cmd.arg(arg);
}
// Link math library on unix
if cfg!(unix) {
cmd.arg("-lm");
}
let status = cmd
.status()
.unwrap_or_else(|e| panic!("Failed to compile generator for {output_file}: {e}"));
assert!(
status.success(),
"Generator compilation failed for {output_file}"
);
let mut run_cmd = Command::new(&bin_path);
for arg in run_args {
run_cmd.arg(arg);
}
let output = run_cmd
.output()
.unwrap_or_else(|e| panic!("Failed to run generator for {output_file}: {e}"));
assert!(
output.status.success(),
"Generator execution failed for {output_file}: {}",
String::from_utf8_lossy(&output.stderr)
);
fs::write(out_dir.join(output_file), &output.stdout).unwrap();
};
// 1. at_interpreter_dictionary.h
compile_and_run(
&["make_at_dictionary.c"],
&[],
"at_interpreter_dictionary.h",
&[],
);
// 2. math_fixed_tables.h
compile_and_run(
&["make_math_fixed_tables.c"],
&[],
"math_fixed_tables.h",
&[],
);
// 3. cielab_luts.h (fax only)
if fax {
compile_and_run(&["make_cielab_luts.c"], &[], "cielab_luts.h", &[]);
}
// 4. t43_gray_code_tables.h (fax only)
if fax {
compile_and_run(
&["make_t43_gray_code_tables.c"],
&[],
"t43_gray_code_tables.h",
&[],
);
}
// 5. Modem filter headers
// Build the modem filter generator once, then run it multiple times
let filter_bin = out_dir.join("gen_modem_filter");
{
let status = Command::new(&cc)
.arg("-o")
.arg(&filter_bin)
.arg("-DHAVE_CONFIG_H")
.arg(format!("-I{}", out_dir.display()))
.arg(format!("-I{}", vendor_src.display()))
.arg(vendor_src.join("make_modem_filter.c"))
.arg(vendor_src.join("filter_tools.c"))
.args(if cfg!(unix) { vec!["-lm"] } else { vec![] })
.status()
.expect("Failed to compile modem filter generator");
assert!(
status.success(),
"Modem filter generator compilation failed"
);
}
let filter_configs: &[(&[&str], &str)] = &[
(&["-m", "V.17", "-r"], "v17_v32bis_rx_rrc.h"),
(&["-m", "V.17", "-t"], "v17_v32bis_tx_rrc.h"),
(&["-m", "V.22bis1200", "-r"], "v22bis_rx_1200_rrc.h"),
(&["-m", "V.22bis2400", "-r"], "v22bis_rx_2400_rrc.h"),
(&["-m", "V.22bis", "-t"], "v22bis_tx_rrc.h"),
(&["-m", "V.27ter2400", "-r"], "v27ter_rx_2400_rrc.h"),
(&["-m", "V.27ter4800", "-r"], "v27ter_rx_4800_rrc.h"),
(&["-m", "V.27ter2400", "-t"], "v27ter_tx_2400_rrc.h"),
(&["-m", "V.27ter4800", "-t"], "v27ter_tx_4800_rrc.h"),
(&["-m", "V.29", "-r"], "v29rx_rrc.h"),
(&["-m", "V.29", "-t"], "v29tx_rrc.h"),
];
for (args, output_file) in filter_configs {
let output = Command::new(&filter_bin)
.args(*args)
.output()
.unwrap_or_else(|e| panic!("Failed to run modem filter for {output_file}: {e}"));
assert!(
output.status.success(),
"Modem filter failed for {output_file}: {}",
String::from_utf8_lossy(&output.stderr)
);
fs::write(out_dir.join(output_file), &output.stdout).unwrap();
}
// 6. Godard descriptor headers
let godard_bin = out_dir.join("gen_modem_godard");
{
let status = Command::new(&cc)
.arg("-o")
.arg(&godard_bin)
.arg("-DHAVE_CONFIG_H")
.arg(format!("-I{}", out_dir.display()))
.arg(format!("-I{}", vendor_src.display()))
.arg(vendor_src.join("make_modem_godard_descriptor.c"))
.arg(vendor_src.join("filter_tools.c"))
.args(if cfg!(unix) { vec!["-lm"] } else { vec![] })
.status()
.expect("Failed to compile godard generator");
assert!(status.success(), "Godard generator compilation failed");
}
// v17_v32bis_rx_godard.h
let output = Command::new(&godard_bin)
.args(["1800.0", "2400.0", "0.99", "1000.0", "100.0", "15", "1"])
.output()
.expect("Failed to run godard for v17");
assert!(
output.status.success(),
"Godard failed for v17: {}",
String::from_utf8_lossy(&output.stderr)
);
fs::write(out_dir.join("v17_v32bis_rx_godard.h"), &output.stdout).unwrap();
// v29rx_godard.h
let output = Command::new(&godard_bin)
.args(["1700.0", "2400.0", "0.99", "1000.0", "30.0", "5", "1"])
.output()
.expect("Failed to run godard for v29");
assert!(
output.status.success(),
"Godard failed for v29: {}",
String::from_utf8_lossy(&output.stderr)
);
fs::write(out_dir.join("v29rx_godard.h"), &output.stdout).unwrap();
// Note: constellation map headers (v17_v32bis_rx_constellation_maps.h,
// v17_v32bis_tx_constellation_maps.h, v29tx_constellation_maps.h) are static
// files already present in vendor/spandsp/src/. No generation needed.
// V.34 generators (only if v34 feature enabled)
if v34 {
compile_and_run(
&["make_v34_convolutional_coders.c"],
&[],
"v34_convolutional_coders.h",
&[],
);
compile_and_run(
&["make_v34_probe_signals.c", "g711.c", "alloc.c"],
&[],
"v34_probe_signals.h",
&[],
);
compile_and_run(&["make_v34_shell_map.c"], &[], "v34_shell_map.h", &[]);
// V.34 TX pre-emphasis filters - this one writes to files in CWD, not stdout
let pre_emph_bin = out_dir.join("gen_v34_pre_emphasis");
let tools_dir = vendor_src.join("../tools");
let mut cmd = Command::new(&cc);
cmd.arg("-o")
.arg(&pre_emph_bin)
.arg("-DHAVE_CONFIG_H")
.arg(format!("-I{}", out_dir.display()))
.arg(format!("-I{}", vendor_src.display()));
// Only add tools dir if meteor-engine.c exists
let meteor_path = tools_dir.join("meteor-engine.c");
if meteor_path.exists() {
cmd.arg(format!("-I{}", tools_dir.display()))
.arg(vendor_src.join("make_v34_tx_pre_emphasis_filters.c"))
.arg(&meteor_path);
} else {
cmd.arg(vendor_src.join("make_v34_tx_pre_emphasis_filters.c"));
}
if cfg!(unix) {
cmd.arg("-lm");
}
let status = cmd
.status()
.expect("Failed to compile v34 pre-emphasis generator");
if status.success() {
// Run in out_dir so it writes files there
let _ = Command::new(&pre_emph_bin).current_dir(out_dir).status();
}
// V.34 modem filter headers
let v34_filter_configs: &[(&[&str], &str)] = &[
(&["-m", "V.34_2400", "-r"], "v34_rx_2400_low_carrier_rrc.h"),
(
&["-m", "V.34_2400_high", "-r"],
"v34_rx_2400_high_carrier_rrc.h",
),
(&["-m", "V.34_2743", "-r"], "v34_rx_2743_low_carrier_rrc.h"),
(
&["-m", "V.34_2743_high", "-r"],
"v34_rx_2743_high_carrier_rrc.h",
),
(&["-m", "V.34_2800", "-r"], "v34_rx_2800_low_carrier_rrc.h"),
(
&["-m", "V.34_2800_high", "-r"],
"v34_rx_2800_high_carrier_rrc.h",
),
(&["-m", "V.34_3000", "-r"], "v34_rx_3000_low_carrier_rrc.h"),
(
&["-m", "V.34_3000_high", "-r"],
"v34_rx_3000_high_carrier_rrc.h",
),
(&["-m", "V.34_3200", "-r"], "v34_rx_3200_low_carrier_rrc.h"),
(
&["-m", "V.34_3200_high", "-r"],
"v34_rx_3200_high_carrier_rrc.h",
),
(&["-m", "V.34_3429", "-r"], "v34_rx_3429_rrc.h"),
(&["-m", "V.34_2400", "-t"], "v34_tx_2400_rrc.h"),
(&["-m", "V.34_2743", "-t"], "v34_tx_2743_rrc.h"),
(&["-m", "V.34_2800", "-t"], "v34_tx_2800_rrc.h"),
(&["-m", "V.34_3000", "-t"], "v34_tx_3000_rrc.h"),
(&["-m", "V.34_3200", "-t"], "v34_tx_3200_rrc.h"),
(&["-m", "V.34_3429", "-t"], "v34_tx_3429_rrc.h"),
];
for (args, output_file) in v34_filter_configs {
let output = Command::new(&filter_bin)
.args(*args)
.output()
.unwrap_or_else(|e| panic!("Failed to run v34 filter for {output_file}: {e}"));
assert!(
output.status.success(),
"V34 filter failed for {output_file}: {}",
String::from_utf8_lossy(&output.stderr)
);
fs::write(out_dir.join(output_file), &output.stdout).unwrap();
}
}
}
fn compile_c_sources(
out_dir: &Path,
vendor_src: &Path,
fax: bool,
v32bis: bool,
v34: bool,
ssl_fax: bool,
) {
let mut build = cc::Build::new();
build
.warnings(false)
.std("c99")
.define("HAVE_CONFIG_H", None)
.include(out_dir)
.include(vendor_src);
// When fax is enabled, we need libtiff and libjpeg include paths for C compilation
if fax {
if let Ok(lib) = pkg_config::probe_library("libtiff-4") {
for path in &lib.include_paths {
build.include(path);
}
} else if let Ok(lib) = pkg_config::probe_library("libtiff") {
for path in &lib.include_paths {
build.include(path);
}
}
if let Ok(lib) = pkg_config::probe_library("libjpeg") {
for path in &lib.include_paths {
build.include(path);
}
}
}
// Always-compiled sources
let always_sources = [
"ademco_contactid.c",
"adsi.c",
"agc_float.c",
"alloc.c",
"async.c",
"at_interpreter.c",
"awgn.c",
"bell_r2_mf.c",
"bert.c",
"bit_operations.c",
"bitstream.c",
"complex_filters.c",
"complex_vector_float.c",
"complex_vector_int.c",
"crc.c",
"dds_float.c",
"dds_int.c",
"dtmf.c",
"echo.c",
"fsk.c",
"g711.c",
"g722.c",
"g726.c",
"godard.c",
"gsm0610_decode.c",
"gsm0610_encode.c",
"gsm0610_long_term.c",
"gsm0610_lpc.c",
"gsm0610_preprocess.c",
"gsm0610_rpe.c",
"gsm0610_short_term.c",
"hdlc.c",
"ima_adpcm.c",
"logging.c",
"lpc10_analyse.c",
"lpc10_decode.c",
"lpc10_encode.c",
"lpc10_placev.c",
"lpc10_voicing.c",
"math_fixed.c",
"modem_echo.c",
"modem_connect_tones.c",
"noise.c",
"oki_adpcm.c",
"playout.c",
"plc.c",
"power_meter.c",
"queue.c",
"schedule.c",
"sig_tone.c",
"silence_gen.c",
"sprt.c",
"super_tone_rx.c",
"super_tone_tx.c",
"swept_tone.c",
"testcpuid.c",
"time_scale.c",
"timezone.c",
"tone_detect.c",
"tone_generate.c",
"v150_1.c",
"v150_1_sse.c",
"v17rx.c",
"v17tx.c",
"v18.c",
"v22bis_rx.c",
"v22bis_tx.c",
"v27ter_rx.c",
"v27ter_tx.c",
"v29rx.c",
"v29tx.c",
"v42.c",
"v42bis.c",
"v8.c",
"v80.c",
"vector_float.c",
"vector_int.c",
];
for src in &always_sources {
build.file(vendor_src.join(src));
}
// FAX feature sources
if fax {
let fax_sources = [
"data_modems.c",
"fax.c",
"fax_modems.c",
"image_translate.c",
"t30.c",
"t30_api.c",
"t30_logging.c",
"t31.c",
"t35.c",
"t38_core.c",
"t38_gateway.c",
"t38_non_ecm_buffer.c",
"t38_terminal.c",
"t4_t6_decode.c",
"t4_t6_encode.c",
"t4_rx.c",
"t4_tx.c",
"t42.c",
"t43.c",
"t81_t82_arith_coding.c",
"t85_decode.c",
"t85_encode.c",
];
for src in &fax_sources {
build.file(vendor_src.join(src));
}
if ssl_fax {
build.file(vendor_src.join("ssl_fax.c"));
}
}
// V.32bis
if v32bis {
build.file(vendor_src.join("v32bis.c"));
build.define("SPANDSP_SUPPORT_V32BIS", None);
}
// V.34
if v34 {
build.file(vendor_src.join("v34rx.c"));
build.file(vendor_src.join("v34tx.c"));
build.file(vendor_src.join("v34_logging.c"));
build.define("SPANDSP_SUPPORT_V34", None);
}
build.compile("spandsp");
}
fn link_system_libraries(fax: bool, ssl_fax: bool) {
if cfg!(unix) {
println!("cargo:rustc-link-lib=m");
}
if fax {
// Use pkg-config to find libtiff and libjpeg
if pkg_config::probe_library("libtiff-4").is_err() {
// Fallback: try without version
if pkg_config::probe_library("libtiff").is_err() {
println!("cargo:rustc-link-lib=tiff");
}
}
if pkg_config::probe_library("libjpeg").is_err() {
println!("cargo:rustc-link-lib=jpeg");
}
}
if ssl_fax {
println!("cargo:rustc-link-lib=ssl");
println!("cargo:rustc-link-lib=crypto");
}
}
fn run_bindgen(out_dir: &Path, vendor_src: &Path, manifest_dir: &Path, fax: bool) {
let wrapper_h = manifest_dir.join("wrapper.h");
let mut builder = bindgen::Builder::default()
.header(wrapper_h.to_str().unwrap())
.clang_arg(format!("-I{}", out_dir.display()))
.clang_arg(format!("-I{}", vendor_src.display()))
.clang_arg("-DHAVE_CONFIG_H")
// Make SPAN_DECLARE transparent to bindgen
.clang_arg("-DSPAN_DECLARE(type)=type")
.clang_arg("-DSPAN_DECLARE_DATA=");
// Add include paths for libtiff/libjpeg when fax is enabled
if fax {
if let Ok(lib) = pkg_config::probe_library("libtiff-4") {
for path in &lib.include_paths {
builder = builder.clang_arg(format!("-I{}", path.display()));
}
} else if let Ok(lib) = pkg_config::probe_library("libtiff") {
for path in &lib.include_paths {
builder = builder.clang_arg(format!("-I{}", path.display()));
}
}
if let Ok(lib) = pkg_config::probe_library("libjpeg") {
for path in &lib.include_paths {
builder = builder.clang_arg(format!("-I{}", path.display()));
}
}
}
let builder = builder
.layout_tests(false)
.generate_comments(true)
.derive_default(true)
// Allowlist spandsp public API — functions
.allowlist_function("(ademco_contactid|adsi|agc_float|alloc|async_|at_interpreter|awgn|bell_r2_mf|bert|bit_operations|bitstream|complex_filters|complex_vector|crc|dds|dtmf|echo_can|fsk|g711|g722|g726|godard|goertzel|gsm0610|hdlc|ima_adpcm|image_translate|logging|span_log|lpc10|math_fixed|modem_echo|modem_connect|noise|oki_adpcm|playout|plc|power_meter|power_surge|queue|schedule|sig_tone|silence_gen|sprt|super_tone|swept_tone|testcpuid|time_scale|timezone|tone_detect|tone_gen|v150_1|v17_|v18_|v22bis|v27ter|v29_|v32bis|v34_|v42_|v42bis|v8_|v80_|fax_|fax_modems|t30_|t31_|t35_|t38_|t4_|t42_|t43_|t81_|t85_|ssl_fax|data_modems|span_set_message_handler|linear_to_ulaw|ulaw_to_linear|linear_to_alaw|alaw_to_linear|alaw_to_ulaw|ulaw_to_alaw|periodogram|make_goertzel_descriptor).*")
// Allowlist spandsp public API — types
.allowlist_type("(ademco_contactid|adsi|agc_float|async_|at_interpreter|awgn|bell_r2_mf|bert|bitstream|complex_filters|complexf_t|crc|dds|dtmf|digits_|echo_can|fsk|g711|g722|g726|godard|goertzel|gsm0610|hdlc|ima_adpcm|image_translate|logging|message_handler|span_|lpc10|math_fixed|modem_echo|modem_connect|noise|oki_adpcm|playout|plc|power_meter|power_surge|queue|schedule|sig_tone|silence_gen|sprt|super_tone|swept_tone|time_scale|timezone|tone_|v150_1|v17_|v18_|v22bis|v27ter|v29_|v32bis|v34_|v42_|v42bis|v8_|v80_|fax_|fax_modems|t30_|t31_|t35_|t38_|t4_|t42_|t43_|t81_|t85_|ssl_fax|data_modems|SAMPLE_RATE).*")
// Allowlist constants from anonymous enums and #defines
.allowlist_var("(G711_|G722_|G726_|SPAN_LOG_|ECHO_CAN_|T30_|T38_|HDLC_|MAX_DTMF|SAMPLE_RATE).*")
// Turn named C enums into proper Rust enums
.rustified_enum("t30_err_e")
.rustified_enum("t30_indicator_types_e")
.rustified_enum("t38_data_types_e")
.rustified_enum("t38_field_types_e")
.rustified_enum("t38_field_classes_e")
.rustified_enum("t38_message_types_e");
let bindings = builder.generate().expect("Unable to generate bindings");
bindings
.write_to_file(out_dir.join("bindings.rs"))
.expect("Couldn't write bindings!");
}