prism-sys 0.1.1

Raw FFI bindings to the prism speech library
Documentation
//! Builds and links the vendored prism library.

use std::{env, path::PathBuf};

use cmake::Config;

fn main() {
	println!("cargo:rerun-if-env-changed=PRISM_LIB_DIR");
	let static_link = env::var("CARGO_FEATURE_STATIC").is_ok();
	if let Ok(dir) = env::var("PRISM_LIB_DIR") {
		println!("cargo:rustc-link-search=native={dir}");
	} else {
		build_vendored(static_link);
	}
	let kind = if static_link { "static" } else { "dylib" };
	println!("cargo:rustc-link-lib={kind}=prism");
	if static_link {
		link_cpp_runtime();
	}
}

fn build_vendored(static_link: bool) {
	let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").expect("cargo sets CARGO_MANIFEST_DIR"));
	let source = manifest_dir.join("prism");
	assert!(
		source.join("CMakeLists.txt").exists(),
		"vendored prism source not found at {}; run `git submodule update --init` \
		 or set PRISM_LIB_DIR to a directory containing a prebuilt prism library",
		source.display()
	);
	println!("cargo:rerun-if-changed=prism/CMakeLists.txt");
	println!("cargo:rerun-if-changed=prism/cmake");
	println!("cargo:rerun-if-changed=prism/source");
	println!("cargo:rerun-if-changed=prism/include");
	let mut cfg = Config::new(&source);
	let _ = cfg.define("BUILD_SHARED_LIBS", if static_link { "OFF" } else { "ON" });
	if env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("msvc") {
		// prism defaults to the static CRT, but the objects must use the same
		// CRT rustc links: the non-debug one, static or dynamic per the
		// crt-static target feature.
		let crt_static =
			env::var("CARGO_CFG_TARGET_FEATURE").is_ok_and(|features| features.split(',').any(|f| f == "crt-static"));
		let _ = cfg.define("CMAKE_MSVC_RUNTIME_LIBRARY", if crt_static { "MultiThreaded" } else { "MultiThreadedDLL" });
	}
	let dst = cfg.build();
	println!("cargo:rustc-link-search=native={}", dst.join("lib").display());
	println!("cargo:rustc-link-search=native={}", dst.join("bin").display());
}

fn link_cpp_runtime() {
	let os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
	let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();
	match (os.as_str(), target_env.as_str()) {
		// MSVC objects carry /DEFAULTLIB directives for their C++ runtime,
		// but not for the delay-load helper: prism_shutdown calls
		// FUnloadDelayLoadedDLL2, which lives in delayimp. Linking prism as a
		// DLL pulled that in on its own; linking it statically leaves it to
		// whoever is doing the linking, and without it the consumer fails
		// with "unresolved external symbol __FUnloadDelayLoadedDLL2".
		(_, "msvc") => println!("cargo:rustc-link-lib=delayimp"),
		("macos" | "ios" | "tvos" | "watchos" | "visionos", _) => println!("cargo:rustc-link-lib=c++"),
		("android", _) => println!("cargo:rustc-link-lib=c++_shared"),
		_ => println!("cargo:rustc-link-lib=stdc++"),
	}
}