use std::fs;
use std::path::{Path, PathBuf};
const FALLBACK_VERSION: &str = "0.17";
fn main() {
let manifest_dir = PathBuf::from(
std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set by cargo"),
);
let manifest_toml = manifest_dir.join("Cargo.toml");
println!("cargo:rerun-if-env-changed=DECIBRI_CPAL_VERSION");
println!("cargo:rerun-if-changed={}", manifest_toml.display());
println!("cargo:rerun-if-changed=build.rs");
if let Ok(v) = std::env::var("DECIBRI_CPAL_VERSION") {
if !v.is_empty() {
emit(&v);
return;
}
}
match fs::read_to_string(&manifest_toml) {
Ok(content) => match resolve_cpal_version(&content, &manifest_dir) {
Some(v) => {
emit(&v);
return;
}
None => {
println!(
"cargo:warning=decibri build.rs: cpal version not found in {}; \
falling back to hardcoded \"{}\"",
manifest_toml.display(),
FALLBACK_VERSION
);
}
},
Err(e) => {
println!(
"cargo:warning=decibri build.rs: failed to read {}: {}; \
falling back to hardcoded \"{}\"",
manifest_toml.display(),
e,
FALLBACK_VERSION
);
}
}
emit(FALLBACK_VERSION);
}
fn emit(version: &str) {
let major_minor = truncate_to_major_minor(version);
println!("cargo:rustc-env=DECIBRI_CPAL_VERSION={}", major_minor);
}
fn resolve_cpal_version(manifest_content: &str, manifest_dir: &Path) -> Option<String> {
if let Some(form) = find_cpal_in_dependencies(manifest_content) {
match form {
CpalEntry::Literal(v) => return Some(v),
CpalEntry::WorkspaceInherit => {
let workspace_toml = manifest_dir.parent()?.parent()?.join("Cargo.toml");
let ws_content = fs::read_to_string(&workspace_toml).ok()?;
if let Some(CpalEntry::Literal(v)) =
find_cpal_in_workspace_dependencies(&ws_content)
{
return Some(v);
}
return None;
}
}
}
None
}
#[derive(Debug)]
enum CpalEntry {
Literal(String),
WorkspaceInherit,
}
fn find_cpal_in_dependencies(content: &str) -> Option<CpalEntry> {
if let Some(v) = extract_version_after_section(content, "[dependencies.cpal]") {
return Some(CpalEntry::Literal(v));
}
extract_inline_cpal_in_section(content, "[dependencies]")
}
fn find_cpal_in_workspace_dependencies(content: &str) -> Option<CpalEntry> {
if let Some(v) = extract_version_after_section(content, "[workspace.dependencies.cpal]") {
return Some(CpalEntry::Literal(v));
}
extract_inline_cpal_in_section(content, "[workspace.dependencies]")
}
fn extract_version_after_section(content: &str, section_header: &str) -> Option<String> {
let mut in_section = false;
for line in content.lines() {
let trimmed = line.trim();
if trimmed == section_header {
in_section = true;
continue;
}
if in_section {
if trimmed.starts_with('[') && trimmed != section_header {
return None;
}
if let Some(rest) = trimmed.strip_prefix("version") {
let after_eq = rest.trim_start().strip_prefix('=')?.trim_start();
if let Some(stripped) = after_eq.strip_prefix('"') {
if let Some(v) = stripped.strip_suffix('"') {
return Some(v.to_string());
}
}
}
}
}
None
}
fn extract_inline_cpal_in_section(content: &str, section_header: &str) -> Option<CpalEntry> {
let mut in_section = false;
for line in content.lines() {
let trimmed = line.trim();
if trimmed == section_header {
in_section = true;
continue;
}
if in_section {
if trimmed.starts_with('[') && trimmed != section_header {
return None;
}
if let Some(value_part) = trimmed.strip_prefix("cpal") {
let after_eq = value_part.trim_start().strip_prefix('=')?.trim_start();
return parse_inline_value(after_eq);
}
}
}
None
}
fn parse_inline_value(value: &str) -> Option<CpalEntry> {
let trimmed = value.trim();
if let Some(stripped) = trimmed.strip_prefix('"') {
if let Some(v) = stripped.split('"').next() {
return Some(CpalEntry::Literal(v.to_string()));
}
}
if let Some(inner) = trimmed.strip_prefix('{').and_then(|s| s.strip_suffix('}')) {
for part in inner.split(',') {
let part = part.trim();
if part.starts_with("workspace") {
let after_eq = part.split('=').nth(1)?.trim();
if after_eq == "true" {
return Some(CpalEntry::WorkspaceInherit);
}
}
}
for part in inner.split(',') {
let part = part.trim();
if let Some(rest) = part.strip_prefix("version") {
let after_eq = rest.trim_start().strip_prefix('=')?.trim_start();
if let Some(stripped) = after_eq.strip_prefix('"') {
if let Some(v) = stripped.split('"').next() {
return Some(CpalEntry::Literal(v.to_string()));
}
}
}
}
}
None
}
fn truncate_to_major_minor(version: &str) -> String {
let parts: Vec<&str> = version.split('.').collect();
if parts.len() >= 2 {
format!("{}.{}", parts[0], parts[1])
} else {
version.to_string()
}
}