use std::env; use std::fs::OpenOptions; #[cfg(not(feature = "hosted"))] use std::io::Read; use std::io::Write; use std::path::PathBuf; fn out_dir() -> PathBuf { PathBuf::from(env::var_os("OUT_DIR").unwrap()) } /// Helper macro that returns a constant number of features enabled among specified list. macro_rules! count_enabled_features { ($($feature:literal),*) => { { let mut enabled_features = 0; $( enabled_features += cfg!(feature = $feature) as u32; )* enabled_features } } } /// Helper macro that returns a compile-time error if multiple or none of the /// features of some category are defined. /// /// # Example /// /// Given the following code: /// /// ``` /// allow_single_feature!("feature-category-name", "a", "b", "c"); /// ``` /// /// These runs fail compilation check: /// $ cargo check --features a,b # error msg: 'Multiple feature-category-name specified. Only one is allowed. /// $ cargo check # error msg: 'None of the feature-category-name specified. Pick one.' /// /// This compiles: /// $ cargo check --feature a macro_rules! allow_single_feature { ($name:literal, $($feature:literal),*) => { const _: () = { const MSG_MULTIPLE: &str = concat!("\nMultiple ", $name, " specified. Only one is allowed."); const MSG_NONE: &str = concat!("\nNone of the ", $name, " specified. Pick one."); match count_enabled_features!($($feature),*) { 0 => std::panic!("{}", MSG_NONE), 1 => {} 2.. => std::panic!("{}", MSG_MULTIPLE), } }; } } macro_rules! allow_single_target_feature { ($($args:tt)+) => { allow_single_feature!("targets", $($args)+); } } #[cfg(feature = "precursor")] // Gitrevs are only relevant for Precursor target macro_rules! allow_single_gitrev_feature { ($($args:tt)+) => { allow_single_feature!("gitrevs", $($args)+); } } fn main() { // ------ check that the feature flags are sane ----- // note on selecting "hosted" mode. An explicit "hosted" flag is provided to clarify // the build system's intent. However, in general, most packages prefer to use this idiom: // // #[cfg(not(target_os = "xous"))] // // This flag is synonymous with feature = "hosted", and it also makes "hosted" mode the // "default" package in the case that the code is being built in CI or in external // packages that don't know to configure the "hosted" feature flag. // // This idiom breaks if Xous ever gets to the point of compiling and running code on // its own platform; but generally, if the target binary is running on e.g. windows/linux/non-xous // target triples, the user's intent was "hosted" mode. // // This script retains the use of an explicit "hosted" flag because we want to catch // unintentional build system misconfigurations that meant to build for a target other // than "hosted", rather than just falling back silently to defaults. allow_single_target_feature!( "precursor", "hosted", "renode", "atsama5d27", "cramium-soc", "cramium-fpga" ); #[cfg(feature = "precursor")] allow_single_gitrev_feature!("precursor-perflib", "precursor-dvt", "precursor-pvt"); // ----- select an SVD file based on a specific revision ----- #[cfg(feature = "precursor-perflib")] let svd_filenames = vec!["precursor/soc-perf.svd"]; #[cfg(feature = "precursor-perflib")] let generated_filename = "src/generated/precursor_perf.rs"; #[cfg(feature = "renode")] let svd_filenames = vec!["renode/renode.svd"]; #[cfg(feature = "renode")] let generated_filename = "src/generated/renode.rs"; #[cfg(feature = "precursor-dvt")] let svd_filenames = vec!["precursor/soc-dvt.svd"]; #[cfg(feature = "precursor-dvt")] let generated_filename = "src/generated/precursor_dvt.rs"; #[cfg(feature = "precursor-pvt")] let svd_filenames = vec!["precursor/soc-pvt.svd"]; #[cfg(feature = "precursor")] let generated_filename = "src/generated/precursor_pvt.rs"; #[cfg(feature = "atsama5d27")] let svd_filenames = vec!["atsama5d/ATSAMA5D27.svd"]; #[cfg(feature = "atsama5d27")] let generated_filename = "src/generated/atsama5d27.rs"; #[cfg(feature = "cramium-soc")] let svd_filenames = vec!["cramium/core.svd", "cramium/daric.svd"]; #[cfg(feature = "cramium-soc")] let generated_filename = "src/generated/cramium_soc.rs"; #[cfg(feature = "cramium-fpga")] let svd_filenames = vec!["cramium/soc.svd", "cramium/core.svd", "cramium/daric.svd"]; #[cfg(feature = "cramium-fpga")] let generated_filename = "src/generated/cramium_fpga.rs"; // ----- control file generation and rebuild sequence ----- // check and see if the configuration has changed since the last build. This should be // passed by the build system (e.g. xtask) if the feature is used. // // Debug this using: // $env:CARGO_LOG="cargo::core::compiler::fingerprint=info" #[cfg(not(feature = "hosted"))] { let mut svd_files = Vec::new(); for svd in svd_filenames.iter() { let svd_file_path = std::path::Path::new(&svd); println!("cargo:rerun-if-changed={}", svd_file_path.canonicalize().unwrap().display()); svd_files.push(std::fs::File::open(svd_file_path).unwrap()); } // Regenerate the utra file in RAM. let mut dest_vec = vec![]; svd2utra::generate(svd_files, &mut dest_vec).unwrap(); // If the file exists, check to see if it is different from what we just generated. // If not, skip writing the new file. // If the file doesn't exist, or if it's different, write out a new utra file. let should_write = if let Ok(mut existing_file) = std::fs::File::open(generated_filename) { let mut existing_file_contents = vec![]; existing_file .read_to_end(&mut existing_file_contents) .expect("couldn't read existing utra generated file"); existing_file_contents != dest_vec } else { true }; if should_write { let mut dest_file = std::fs::File::create(generated_filename).expect("couldn't open dest file"); dest_file.write_all(&dest_vec).expect("couldn't write contents to utra file"); } // ----- feedback SVD path to build framework ----- // pass the computed SVD filename back to the build system, so that we can pass this // on to the image creation program. This is necessary so we can extract all the memory // regions and create the whitelist of memory pages allowed to the kernel; any page not // explicitly used by the hardware model is ineligible for mapping and allocation by any // process. This helps to prevent memory aliasing attacks by hardware blocks that partially // decode their addresses (this would be in anticipation of potential hardware bugs; ideally // this isn't ever a problem). let svd_path = out_dir().join("../../SVD_PATH"); let mut svd_file = OpenOptions::new().create(true).write(true).truncate(true).open(svd_path).unwrap(); for svd in svd_filenames.iter() { writeln!(svd_file, "utralib/{}", svd).unwrap(); } } #[cfg(feature = "hosted")] { let svd_path = out_dir().join("../../SVD_PATH"); let mut svd_file = OpenOptions::new().create(true).write(true).truncate(true).open(svd_path).unwrap(); write!(svd_file, "").unwrap(); // there is no SVD file for hosted mode } }