use std::path::{Path, PathBuf};
use std::process::Command;
use semver::Version;
macro_rules! feature {
($feature:expr) => {
std::env::var(concat!("CARGO_FEATURE_", $feature))
};
}
#[derive(Debug)]
struct NcMetaHeader {
version: Version,
has_nc2: bool,
has_nc4: bool,
has_hdf4: bool,
has_hdf5: bool,
has_szip: bool,
has_szip_write: bool,
has_dap2: bool,
has_dap4: bool,
has_byterange: bool,
has_diskless: bool,
has_mmap: bool,
has_jna: bool,
has_pnetcdf: bool,
has_parallel4: bool,
has_parallel: bool,
has_cdf5: bool,
has_erange_fill: bool,
relax_coord_bound: bool,
dispatch_version: Option<u8>,
has_par_filters: bool,
has_nczarr: bool,
has_multifilters: bool,
has_logging: bool,
has_quantize: bool,
has_zstd: bool,
has_benchmarks: bool,
}
impl NcMetaHeader {
fn gather_from_includeheader(path: &std::path::Path) -> Self {
macro_rules! match_prefix {
($line: expr, $prefix: expr, $item: expr) => {
if let Some(item) = match_prefix_bool($line, $prefix) {
$item = item;
}
};
}
fn match_prefix<'a>(line: &'a str, prefix: &str) -> Option<&'a str> {
line.strip_prefix(&format!("#define {prefix} "))
.map(|item| item.trim())
}
fn match_prefix_bool(line: &str, prefix: &str) -> Option<bool> {
match_prefix(line, prefix).map(|item| item.starts_with('1'))
}
let meta = std::fs::read_to_string(path).expect("Could not read header file");
let mut info = Self {
version: Version::new(0, 0, 0),
has_nc2: false,
has_nc4: false,
has_benchmarks: false,
has_byterange: false,
has_cdf5: false,
has_dap2: false,
has_dap4: false,
has_diskless: false,
dispatch_version: None,
has_erange_fill: false,
has_hdf4: false,
has_hdf5: false,
has_jna: false,
has_logging: false,
has_mmap: false,
has_multifilters: false,
has_nczarr: false,
has_par_filters: false,
has_parallel: false,
has_parallel4: false,
has_pnetcdf: false,
has_quantize: false,
has_szip: false,
has_szip_write: false,
has_zstd: false,
relax_coord_bound: false,
};
for line in meta.lines() {
if let Some(ncversion) = match_prefix(line, "NC_VERSION") {
info.version = Version::parse(ncversion.trim_matches('"')).unwrap();
}
if let Some(dversion) = match_prefix(line, "NC_DISPATCH_VERSION") {
let (dversion, _) = dversion.split_once(' ').unwrap();
info.dispatch_version = Some(dversion.parse().unwrap());
}
match_prefix!(line, "NC_HAS_NC2", info.has_nc2);
match_prefix!(line, "NC_HAS_NC4", info.has_nc4);
match_prefix!(line, "NC_HAS_HDF4", info.has_hdf4);
match_prefix!(line, "NC_HAS_HDF5", info.has_hdf5);
match_prefix!(line, "NC_HAS_SZIP", info.has_szip);
match_prefix!(line, "NC_HAS_DAP2", info.has_dap2);
match_prefix!(line, "NC_HAS_DAP4", info.has_dap4);
match_prefix!(line, "NC_HAS_DISKLESS", info.has_diskless);
match_prefix!(line, "NC_HAS_MMAP", info.has_mmap);
match_prefix!(line, "NC_HAS_JNA", info.has_jna);
match_prefix!(line, "NC_HAS_PNETCDF", info.has_pnetcdf);
match_prefix!(line, "NC_HAS_PARALLEL", info.has_parallel);
match_prefix!(line, "NC_HAS_CDF5", info.has_cdf5);
match_prefix!(line, "NC_HAS_BYTERANGE", info.has_byterange);
match_prefix!(line, "NC_HAS_BENCHMARKS", info.has_benchmarks);
match_prefix!(line, "NC_HAS_ERANGE_FILL", info.has_erange_fill);
match_prefix!(line, "NC_HAS_ZSTD", info.has_zstd);
match_prefix!(line, "NC_HAS_QUANTIZE", info.has_quantize);
match_prefix!(line, "NC_HAS_LOGGING", info.has_logging);
match_prefix!(line, "NC_HAS_MULTIFILTERS", info.has_multifilters);
match_prefix!(line, "NC_HAS_NCZARR", info.has_nczarr);
match_prefix!(line, "NC_HAS_PAR_FILTERS", info.has_par_filters);
match_prefix!(line, "NC_RELAX_COORD_BOUND", info.relax_coord_bound);
match_prefix!(line, "NC_HAS_PARALLEL", info.has_parallel);
match_prefix!(line, "NC_HAS_PARALLEL4", info.has_parallel4);
match_prefix!(line, "NC_HAS_SZIP_WRITE", info.has_szip_write);
}
info
}
fn emit_feature_flags(&self) {
if self.has_dap2 || self.has_dap4 {
println!("cargo:rustc-cfg=feature=\"has-dap\"");
println!("cargo:has-dap=1");
} else {
assert!(
feature!("DAP").is_err(),
"DAP requested but not found in this installation of netCDF"
);
}
if self.has_mmap {
println!("cargo:rustc-cfg=feature=\"has-mmap\"");
println!("cargo:has-mmap=1");
} else {
assert!(
feature!("MEMIO").is_err(),
"MEMIO requested but not found in this installation of netCDF"
);
}
}
}
#[derive(Debug)]
struct NcInfo {
version: Option<Version>,
includedir: PathBuf,
libdir: PathBuf,
libname: String,
}
fn from_utf8_to_trimmed_string(bytes: &[u8]) -> String {
String::from_utf8_lossy(bytes).trim().to_owned()
}
impl NcInfo {
fn guess() -> Self {
todo!()
}
fn from_path(path: &Path) -> Self {
Self {
version: None,
includedir: path.join("include"),
libdir: path.join("lib"),
libname: "netcdf".to_owned(),
}
}
fn gather_from_ncconfig(search_path: Option<&Path>) -> Option<Self> {
let path = if let Some(search_path) = search_path {
let search_path = search_path.join("bin").join("nc-config");
search_path.as_os_str().to_owned()
} else {
std::ffi::OsString::from("nc-config")
};
let cmd = || Command::new(&path);
cmd().arg("--help").status().ok()?;
let extract = |arg: &str| -> Result<Option<String>, Box<dyn std::error::Error>> {
let output = &cmd().arg(arg).output()?;
if output.status.success() {
Ok(Some(from_utf8_to_trimmed_string(&output.stdout)))
} else {
Ok(None)
}
};
let version = if let Ok(Some(version)) = extract("--version") {
version.strip_prefix("netCDF ").unwrap().to_owned()
} else {
panic!("Could not get information from this installation of NetCDF");
};
let version = Version::parse(&version).unwrap();
let includedir = PathBuf::from(extract("--includedir").unwrap().unwrap());
let libdir = PathBuf::from(extract("--libdir").unwrap().unwrap());
let libs = extract("--libs").unwrap().unwrap();
assert!(libs.contains("-lnetcdf"));
let libname = "netcdf".to_owned();
let _inc = std::fs::read_to_string(std::path::Path::new(&includedir).join("netcdf.h"))
.expect("Could not find netcdf.h");
Some(Self {
version: Some(version),
includedir,
libdir,
libname,
})
}
}
fn _check_consistent_version_linked() {
todo!()
}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
let info;
if feature!("STATIC").is_ok() {
let netcdf_lib = std::env::var("DEP_NETCDFSRC_LIB").unwrap();
let netcdf_path = PathBuf::from(std::env::var_os("DEP_NETCDFSRC_SEARCH").unwrap());
info = NcInfo::gather_from_ncconfig(Some(&netcdf_path.join("..")))
.unwrap_or_else(|| NcInfo::from_path(&netcdf_path.join("..")));
println!("cargo:rustc-link-search=native={}", netcdf_path.display());
println!("cargo:rustc-link-lib=static={netcdf_lib}");
} else {
println!("cargo:rerun-if-env-changed=NETCDF_DIR");
let nc_dir = std::env::var_os("NETCDF_DIR")
.or_else(|| std::env::var_os("NetCDF_DIR"))
.map(PathBuf::from);
#[cfg(windows)]
let nc_dir = nc_dir.map(|d| d.join("Library"));
info = if let Some(nc_dir) = nc_dir.as_ref() {
NcInfo::gather_from_ncconfig(Some(nc_dir)).unwrap_or_else(|| NcInfo::from_path(nc_dir))
} else {
NcInfo::gather_from_ncconfig(None).unwrap_or_else(NcInfo::guess)
};
println!("cargo:rustc-link-search={}", info.libdir.display());
println!("cargo:rustc-link-lib={}", &info.libname);
}
let metaheader = NcMetaHeader::gather_from_includeheader(
&std::path::Path::new(&info.includedir).join("netcdf_meta.h"),
);
if let Some(version) = info.version {
assert_eq!(version, metaheader.version, "Version mismatch");
}
println!("cargo:includedir={}", info.includedir.display());
println!("cargo:nc_version={}", metaheader.version);
let versions = [
Version::new(4, 4, 0),
Version::new(4, 4, 1),
Version::new(4, 5, 0),
Version::new(4, 6, 0),
Version::new(4, 6, 1),
Version::new(4, 6, 2),
Version::new(4, 6, 3),
Version::new(4, 7, 0),
Version::new(4, 7, 1),
Version::new(4, 7, 2),
Version::new(4, 7, 3),
Version::new(4, 7, 4),
Version::new(4, 8, 0),
Version::new(4, 8, 1),
Version::new(4, 9, 0),
Version::new(4, 9, 1),
];
if !versions.contains(&metaheader.version) {
if versions
.iter()
.any(|x| (x.major == metaheader.version.major) && (x.minor == metaheader.version.minor))
{
println!("We don't know this release, but it is just a patch difference")
} else if versions.iter().any(|x| x.major == metaheader.version.major) {
eprintln!("This minor version of netCDF is not known, but the major version is known and the release is unlikely to contain breaking API changes");
} else {
eprintln!("This major version is not known, please file an issue if breaking API changes have been made to netCDF-c");
}
}
for version in versions {
if metaheader.version >= version {
println!(
"cargo:rustc-cfg=feature=\"{}.{}.{}\"",
version.major, version.minor, version.patch
);
}
}
metaheader.emit_feature_flags();
}