target_cpu_fetch/
lib.rs

1//! Provides a method for querying the CPU name from the current target specification JSON passed
2//! to Rust.
3//!
4//! See also the crate [target-cpu-macro](https://crates.io/crates/target-cpu-macro) for
5//! conditional compilation support.
6
7use std::path::Path;
8
9/// Returns the name of the target CPU.
10///
11/// Looks at the name of the current target specification JSON passed to `rustc` and
12/// derives that target from it.
13///
14/// If there is an error during the lookup, `Err(message)` is returned. If the target
15/// specification could be found, but it was empty, then `Ok(None)` is returned.
16pub fn target_cpu() -> Result<Option<String>, String> {
17    // N.B. This environment variable can be used for testing. I didn't want to prefix
18    // with 'RUST_', this will work sufficiently well as a scare tactic against usage.
19    if let Ok(force_override_cpu) = std::env::var("_DYLAN_RUST_TARGET_CPU_FORCE_OVERRIDE").map(|s| s.trim().to_owned()) {
20        if !force_override_cpu.is_empty() {
21            eprintln!("warning: force overriding CPU detection in crate '{}' to CPU '{}'",
22                env!("CARGO_PKG_NAME"), force_override_cpu);
23
24            return Ok(Some(force_override_cpu));
25        }
26    }
27
28    let target = if let Ok(target) = std::env::var("TARGET") {
29        target
30    } else {
31        return Err("cannot retrieve CPU name, please, pass --target flag to Cargo, e. g. \"--target atmega88pa.json\"".to_owned());
32    };
33
34    let target_json_relative_path = Path::new(&format!("{}.json", target)).to_owned();
35
36    let cpu_name = match parse_target_cpu_from_target_json(&target_json_relative_path) {
37        Some(target_cpu) => target_cpu,
38        None => {
39            // TARGET environment variable should contain the value that was specified
40            // by --target X(C)argo option. Normally it's the name of .json file
41            // containing custom target specification, e. g. atmega88pa.json
42            // So in order to work, the name of *.json file should be the same
43            // as the name of your MCU
44            eprintln!("[warning]: assuming a target CPU name of '{}' from the file name of the target spec JSON file", target);
45
46            // Recognize the builtin target 'avr-unknown-gnu-atmega328'.
47            if let Some(cpu_name) = target.strip_prefix("avr-unknown-gnu-"){
48                cpu_name.to_owned()
49            } else {
50                target
51            }
52        },
53    };
54
55    Ok(Some(cpu_name))
56}
57
58
59fn parse_target_cpu_from_target_json(possible_json_path: &Path)
60    -> Option<String> {
61    let json_content = match std::fs::read(possible_json_path) {
62        Ok(content) => String::from_utf8(content).unwrap(),
63        Err(..) => {
64            eprintln!("[warning]: cannot find target JSON file '{}' - due to limitations it needs to be in the crate root - is it?", possible_json_path.display());
65            return None;
66        },
67    };
68
69    let parsed = json::parse(&json_content).expect(&format!("failed to parse target JSON at '{}'", possible_json_path.display()));
70
71    match parsed {
72        json::JsonValue::Object(dict) => {
73            match dict.get("cpu") {
74                Some(target_cpu) => Some(target_cpu.as_str().expect("target CPU in JSON is not a string").to_owned()),
75                None => panic!("target specification file '{}' does not define a target CPU", possible_json_path.display()),
76            }
77        },
78        _ => None,
79    }
80}