opus-codec 0.1.2

Safe Rust bindings for the Opus audio codec
Documentation
use std::env;

fn main() {
    emit_rerun_directives();
    let opts = BuildOptions::from_env();

    if opts.use_system_lib {
        handle_system_lib(&opts);
    } else {
        build_bundled_and_link(&opts);
    }

    generate_bindings();
}

struct BuildOptions {
    use_system_lib: bool,
    dred_enabled: bool,
    presume_avx: bool,
    target_arch: String,
    avx_allowed: bool,
}

impl BuildOptions {
    fn from_env() -> Self {
        let use_system_lib = env::var("CARGO_FEATURE_SYSTEM_LIB").is_ok();
        let dred_enabled = env::var("CARGO_FEATURE_DRED").is_ok();
        let presume_avx = env::var("CARGO_FEATURE_PRESUME_AVX2").is_ok();
        let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap_or_default();
        let avx_allowed = presume_avx && matches!(target_arch.as_str(), "x86" | "x86_64");

        Self {
            use_system_lib,
            dred_enabled,
            presume_avx,
            target_arch,
            avx_allowed,
        }
    }
}

fn emit_rerun_directives() {
    println!("cargo:rerun-if-changed=opus/include/opus.h");
    println!("cargo:rerun-if-changed=opus/include/opus_defines.h");
    println!("cargo:rerun-if-changed=opus/include/opus_types.h");
    println!("cargo:rerun-if-changed=opus/include/opus_multistream.h");
    println!("cargo:rerun-if-changed=opus/include/opus_projection.h");
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rerun-if-changed=opus/dnn/download_model.sh");
    println!("cargo:rerun-if-env-changed=CARGO_FEATURE_SYSTEM_LIB");
    println!("cargo:rerun-if-env-changed=CARGO_FEATURE_PRESUME_AVX2");
}

fn handle_system_lib(opts: &BuildOptions) {
    if opts.dred_enabled {
        println!(
            "cargo:warning=system-lib feature enabled; ensure the system libopus includes DRED support"
        );
    }
    if opts.presume_avx {
        println!(
            "cargo:warning=presume-avx2 feature enabled; ensure the system libopus was built with OPUS_X86_PRESUME_AVX2"
        );
    }
    link_system_lib();
}

fn build_bundled_and_link(opts: &BuildOptions) {
    if opts.dred_enabled {
        ensure_dred_assets();
    }
    if opts.presume_avx && !opts.avx_allowed {
        println!(
            "cargo:warning=presume-avx2 feature only applies to x86/x86_64 targets; ignoring for {}",
            opts.target_arch
        );
    }

    let dst = build_bundled(opts.dred_enabled, opts.avx_allowed);
    println!("cargo:rustc-link-search=native={}/lib", dst.display());
    println!("cargo:rustc-link-lib=static=opus");
}

fn build_bundled(dred_enabled: bool, presume_avx: bool) -> std::path::PathBuf {
    let mut config = cmake::Config::new("opus");

    config.profile("Release");

    if should_use_msvc_crt_flag() {
        let profile = env::var("PROFILE").unwrap_or_default();
        let crt_flag = if profile.eq_ignore_ascii_case("debug") {
            "/MDd"
        } else {
            "/MD"
        };
        config.cflag(crt_flag);
    }

    config
        .define("OPUS_BUILD_SHARED_LIBRARY", "OFF")
        .define("OPUS_BUILD_TESTING", "OFF")
        .define("OPUS_BUILD_PROGRAMS", "OFF")
        .define("OPUS_DRED", if dred_enabled { "ON" } else { "OFF" })
        .define("BUILD_SHARED_LIBS", "OFF")
        .define("OPUS_DISABLE_INTRINSICS", "OFF")
        .define("CMAKE_POSITION_INDEPENDENT_CODE", "ON");

    if presume_avx {
        config
            .define("OPUS_X86_PRESUME_AVX2", "ON")
            .define("OPUS_X86_MAY_HAVE_AVX2", "ON");
    }

    config.build()
}

fn link_system_lib() {
    pkg_config::Config::new()
        .atleast_version("1.5.2")
        .probe("opus")
        .expect("system-lib feature requested but pkg-config couldn't find libopus");
}

fn generate_bindings() {
    let bindings_path = std::path::Path::new("src/bindings.rs");

    if bindings_path.exists() {
        println!(
            "cargo:warning=Using existing src/bindings.rs. Delete this file to force regeneration."
        );
        return;
    }

    let bindings = bindgen::Builder::default()
        .header("opus/include/opus.h")
        .header("opus/include/opus_defines.h")
        .header("opus/include/opus_types.h")
        .header("opus/include/opus_multistream.h")
        .header("opus/include/opus_projection.h")
        .parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
        .generate()
        .expect("Unable to generate bindings");

    bindings
        .write_to_file(bindings_path)
        .expect("Couldn't write bindings!");
}

fn should_use_msvc_crt_flag() -> bool {
    matches!(
        env::var("CARGO_CFG_TARGET_FAMILY").as_deref(),
        Ok("windows")
    ) && matches!(env::var("CARGO_CFG_TARGET_ENV").as_deref(), Ok("msvc"))
}

fn ensure_dred_assets() {
    use std::path::Path;
    use std::process::Command;

    const REQUIRED_FILE: &str = "opus/dnn/fargan_data.h";
    if Path::new(REQUIRED_FILE).exists() {
        return;
    }

    let script = Path::new("opus/dnn/download_model.sh");
    if !script.exists() {
        panic!("DRED feature requires {script:?}, but it was not found");
    }

    let status = Command::new("sh")
        .arg("dnn/download_model.sh")
        .arg("735117b")
        .current_dir("opus")
        .status()
        .expect("failed to spawn DRED model download script");

    if !status.success() {
        panic!("downloading DRED model assets failed (exit status: {status})");
    }

    if !Path::new(REQUIRED_FILE).exists() {
        panic!("DRED model download completed but {REQUIRED_FILE} is still missing");
    }
}