machine_check_compile/
prepare.rs

1use cargo_metadata::{camino::Utf8PathBuf, Message};
2use log::info;
3use std::{collections::BTreeSet, io::Write};
4use std::{
5    fs::File,
6    io::BufWriter,
7    process::{Command, Stdio},
8};
9
10use serde::{Deserialize, Serialize};
11
12use crate::features::add_cargo_features;
13use crate::util::{log_process_error_log, log_process_output};
14use crate::Error;
15
16#[derive(Debug, Serialize, Deserialize)]
17pub(crate) struct Preparation {
18    pub target_build_args: Vec<String>,
19}
20
21pub fn default_preparation_dir() -> Result<Utf8PathBuf, Error> {
22    // directory 'machine-check-preparation' under the executable
23    let mut path = std::env::current_exe().map_err(Error::CurrentExe)?;
24    path.pop();
25    let path = Utf8PathBuf::try_from(path.clone()).map_err(|err| Error::PathToUtf8(path, err))?;
26    Ok(path.join("machine-check-preparation"))
27}
28
29pub struct Config {
30    pub preparation_path: Option<Utf8PathBuf>,
31    pub clean: bool,
32}
33
34pub fn prepare(config: Config) -> Result<(), Error> {
35    let preparation_dir = match config.preparation_path {
36        Some(preparation_path) => preparation_path,
37        None => {
38            // use the default directory
39            default_preparation_dir()?
40        }
41    };
42
43    if config.clean {
44        info!(
45            "Cleaning preparation by removing directory {:?}.",
46            preparation_dir
47        );
48        std::fs::remove_dir_all(preparation_dir.clone())
49            .map_err(|err| Error::RemoveDirAll(preparation_dir, err))?;
50        return Ok(());
51    }
52
53    info!(
54        "Preparing sub-artifacts for machine executable building into {:?}.",
55        preparation_dir
56    );
57
58    let src_dir_path = preparation_dir.join("src");
59    std::fs::create_dir_all(&src_dir_path)
60        .map_err(|err| Error::CreateDir(src_dir_path.clone(), err))?;
61    let lib_path = src_dir_path.join("lib.rs");
62
63    std::fs::write(lib_path.clone(), "").map_err(|err| Error::WriteFile(lib_path, err))?;
64
65    let cargo_toml = include_str!("../resources/Prepare_Cargo.toml");
66    let cargo_toml_path = preparation_dir.join("Cargo.toml");
67    std::fs::write(&cargo_toml_path, cargo_toml)
68        .map_err(|err| Error::WriteFile(cargo_toml_path.clone(), err))?;
69
70    let home_dir = preparation_dir.join("home");
71    std::fs::create_dir_all(&home_dir).map_err(|err| Error::CreateDir(home_dir.clone(), err))?;
72    let target_dir = preparation_dir.join("target");
73    std::fs::create_dir_all(&target_dir)
74        .map_err(|err| Error::CreateDir(target_dir.clone(), err))?;
75    let profile = String::from("release");
76
77    // cargo build machine_check_exec and copy the dependencies to a separate directory
78    let mut build_command = Command::new("cargo");
79    build_command
80        .arg("build")
81        .arg("--manifest-path")
82        .arg(cargo_toml_path)
83        .arg("--lib")
84        .arg("--profile")
85        .arg(&profile)
86        .arg("--message-format=json-render-diagnostics")
87        .arg("--target-dir")
88        .arg(&target_dir);
89
90    add_cargo_features(&mut build_command);
91
92    let build_output = build_command
93        .stdout(Stdio::piped())
94        .stderr(Stdio::inherit())
95        .env("CARGO_HOME", &home_dir)
96        .output()
97        .map_err(Error::BuildRun)?;
98
99    if !build_output.status.success() {
100        log_process_error_log("Preparation", &build_output.stderr);
101        return Err(Error::BuildStatus(build_output.status));
102    }
103
104    log_process_output("Preparation", &build_output);
105
106    let mut linked_paths = BTreeSet::new();
107
108    let mut target_build_args = vec![
109        String::from("--edition=2021"),
110        String::from("--error-format=json"),
111        String::from("--json=artifacts"),
112        String::from("--crate-type"),
113        String::from("bin"),
114        String::from("-C"),
115        String::from("opt-level=3"),
116        String::from("-C"),
117        String::from("embed-bitcode=no"),
118        String::from("-C"),
119        String::from("strip=symbols"),
120    ];
121
122    // add linked dependency which is in target
123    let deps_dir = target_dir.join(profile).join("deps");
124
125    target_build_args.push(String::from("-L"));
126    target_build_args.push(format!("dependency={}", deps_dir));
127
128    // get a list of paths to rlibs
129    let bytes: &[u8] = &build_output.stdout;
130    for message in cargo_metadata::Message::parse_stream(bytes) {
131        let message = message.map_err(Error::CargoParse)?;
132        match message {
133            Message::BuildScriptExecuted(build_script) => {
134                // add linked paths
135                linked_paths.extend(build_script.linked_paths);
136            }
137            Message::CompilerArtifact(artifact) => {
138                // replace target name hyphens with underscores
139                // this will also be needed for rustc
140                let target_name = artifact.target.name.replace('-', "_");
141                if matches!(
142                    target_name.as_str(),
143                    "mck" | "machine_check" | "machine_check_exec"
144                ) {
145                    for original_path in artifact.filenames {
146                        // TODO: base addition of extern on Target_Cargo.toml
147                        // add extern to args
148                        // replace hyphens with underscores for rustc
149                        let extern_target_name = artifact.target.name.replace('-', "_");
150                        target_build_args.push(String::from("--extern"));
151                        target_build_args.push(format!("{}={}", extern_target_name, original_path));
152                    }
153                }
154            }
155            Message::BuildFinished(finished) => {
156                // should never have successful exit status if build was unsuccessful
157                assert!(finished.success);
158            }
159            _ => (),
160        };
161    }
162
163    // add linked paths
164    for linked_path in linked_paths {
165        target_build_args.push(String::from("-L"));
166        target_build_args.push(linked_path.to_string());
167    }
168
169    let preparation = Preparation { target_build_args };
170
171    let preparation_path = preparation_dir.join("preparation.json");
172    let file = File::create(&preparation_path)
173        .map_err(|err| Error::CreateFile(preparation_path.clone(), err))?;
174    let mut writer = BufWriter::new(file);
175    serde_json::to_writer(&mut writer, &preparation)?;
176    writer
177        .flush()
178        .map_err(|err| Error::Flush(preparation_path, err))?;
179
180    info!("Preparation complete.");
181    Ok(())
182}