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, 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 { 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) { println!("cargo::rustc-check-cfg=cfg(feature, values(\"has-mmap\",\"has-dap\"))"); if self.has_dap2 || self.has_dap4 { println!("cargo::rustc-cfg=feature=\"has-dap\""); println!("cargo::metadata=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::metadata=has-mmap=1"); } else { assert!( feature!("MEMIO").is_err(), "MEMIO requested but not found in this installation of netCDF" ); } if self.has_parallel { println!("cargo:rustc-cfg=feature=\"has-par\""); } else { assert!( feature!("MPI").is_err(), "MPI requested but not found in this installation of netCDF" ); } } } #[derive(Debug)] struct NcInfo { version: Option, 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 { 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, Box> { 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() { // use libloading 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"); } // panic!("{:?}", info); // Emit nc flags println!("cargo::metadata=includedir={}", info.includedir.display()); println!("cargo::metadata=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), Version::new(4, 9, 2), // Keep this list up to date with netcdf/build.rs ]; for version in &versions { println!("cargo::rustc-check-cfg=cfg(feature, values(\"{version}\"))"); } 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 ); println!( "cargo:version_\"{}.{}.{}\"=1", version.major, version.minor, version.patch ); } } metaheader.emit_feature_flags(); }