use core::str;
use std::collections::BTreeMap;
use std::fs::File;
use std::io::{BufRead, Write};
use std::path::{Path, PathBuf};
use std::{env, io};
use once_cell::sync::Lazy;
use regex::Regex;
const INDIGO_GIT_REPOSITORY: &str = "https://github.com/indigo-astronomy/indigo";
const INDIGO_GIT_SUBMODULE: &str = "sys/externals/indigo";
static RE: Lazy<Regex> =
Lazy::new(|| Regex::new(r#"^#define\s+(?<name>\w+)_NAME\s+"(?<value>.+)"\s*$"#).unwrap());
fn main() -> std::io::Result<()> {
let submodule = Path::new(INDIGO_GIT_SUBMODULE);
if submodule.exists() {
if let Err(e) = extract_indigo_constants(&submodule) {
eprintln!("Warning: Could not extract INDIGO constants: {}", e);
eprintln!("Continuing with existing constants.rs");
}
} else {
eprintln!("INDIGO submodule not found at {}", INDIGO_GIT_SUBMODULE);
eprintln!("Run: git submodule update --init --recursive");
eprintln!("Continuing with existing constants.rs");
}
let has_ffi = env::var("CARGO_FEATURE_FFI_STRATEGY").is_ok()
|| env::var("CARGO_FEATURE_SYS").is_ok()
|| env::var("CARGO_FEATURE_AUTO").is_ok();
if !has_ffi {
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
let names_path = out_dir.join("name.rs");
let interface_path = out_dir.join("interface.rs");
std::fs::write(names_path, "// No INDIGO names for pure Rust build\n")?;
std::fs::write(
interface_path,
"// No INDIGO interface for pure Rust build\n",
)?;
return Ok(());
}
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
if !submodule.exists() {
init_indigo_submodule()?;
}
generate_ffi_bindings(&submodule, &out_dir)?;
Ok(())
}
fn extract_indigo_constants(submodule: &Path) -> std::io::Result<()> {
let mut names = BTreeMap::new();
let include = submodule
.join("indigo_libs/indigo/indigo_names.h")
.canonicalize()?;
for line in read_lines(include)? {
if let Some(cap) = RE.captures(&line?) {
let name = cap["name"].to_string();
let value = cap["value"].to_string();
names.insert(name, value);
}
}
let constants_path = Path::new("src/constants.rs");
let mut constants_file = File::create(constants_path)?;
writeln!(
constants_file,
"// This file is automatically generated by build.rs"
)?;
writeln!(constants_file, "// DO NOT EDIT MANUALLY")?;
writeln!(constants_file, "//")?;
writeln!(constants_file, "// To regenerate this file:")?;
writeln!(
constants_file,
"// 1. Update the INDIGO submodule: git submodule update --remote sys/externals/indigo"
)?;
writeln!(constants_file, "// 2. Run: cargo clean && cargo build")?;
writeln!(constants_file, "//")?;
writeln!(
constants_file,
"// Source: sys/externals/indigo/indigo_libs/indigo/indigo_names.h"
)?;
writeln!(constants_file)?;
for (name, value) in names {
writeln!(constants_file, r#"pub const {}: &str = "{}";"#, name, value)?;
}
println!("cargo:rerun-if-changed=sys/externals/indigo/indigo_libs/indigo/indigo_names.h");
Ok(())
}
fn generate_ffi_bindings(submodule: &Path, out_dir: &Path) -> std::io::Result<()> {
let include = submodule.join("indigo_libs").canonicalize()?;
let bindings = bindgen::Builder::default()
.clang_arg(format!("-I{}", include.to_str().expect("path not found")))
.header(join_paths(&include, "indigo/indigo_bus.h"))
.derive_debug(true)
.allowlist_item("indigo_device_interface")
.bitfield_enum("indigo_device_interface")
.prepend_enum_name(false)
.translate_enum_integer_types(true)
.generate_cstr(true)
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Unable to generate bindings");
bindings
.write_to_file(out_dir.join("interface.rs"))
.expect("Couldn't write bindings!");
Ok(())
}
fn join_paths(base: &Path, path: &str) -> String {
let p = base.join(path);
let s = p.to_str().expect("path not found");
String::from(s)
}
fn read_lines<P>(filename: P) -> io::Result<io::Lines<io::BufReader<File>>>
where
P: AsRef<Path>,
{
let file = File::open(filename)?;
Ok(io::BufReader::new(file).lines())
}
fn init_indigo_submodule() -> std::io::Result<PathBuf> {
let outcome = if PathBuf::from(".git").exists() {
std::process::Command::new("git")
.arg("submodule")
.arg("update")
.arg("--init")
.arg("--recursive")
.status()
.expect("could not spawn `git`")
} else {
std::process::Command::new("git")
.arg("clone")
.arg(INDIGO_GIT_REPOSITORY)
.arg("externals/indigo")
.status()
.expect("could not spawn `git`")
};
if !outcome.success() {
panic!("could not clone or checkout git submodule externals/indigo");
}
Path::new(INDIGO_GIT_SUBMODULE).canonicalize()
}