cargo_simics_build/
lib.rs1use artifact_dependency::ARTIFACT_NAMEPARTS;
7use cargo_subcommand::{Args, Subcommand};
8use clap::Parser;
9use command_ext::CommandExtCheck;
10use ispm_wrapper::ispm::{self, GlobalOptions};
11use itertools::Itertools;
12use simics_package::Package;
13use simics_sign::Sign;
14use std::{
15 env::var,
16 fs::{copy, read_dir},
17 io::BufRead,
18 path::PathBuf,
19 process::Command,
20 time::SystemTime,
21};
22
23#[derive(Debug, thiserror::Error)]
24pub enum Error {
26 #[error("env.SIMICS_BASE variable set in config.toml but could not be parsed from {output:?}")]
27 SimicsBaseParseError { output: Option<String> },
29 #[error("The SIMICS_BASE environment variable was not set or present in config.toml, and no packages were installed.")]
30 NoInstalledPackages,
32 #[error("The SIMICS_BASE environment variable was not set or present in config.toml, and no base package was installed.")]
33 NoBasePackage,
35 #[error("Base package found, but no paths registered for package number 1000")]
36 NoPathsForBasePackage,
38 #[error("Base package directory {path:?} does not exist")]
39 BasePackageDirectoryDoesNotExist { path: PathBuf },
41 #[error("No cdylib crate artifact found for package {package}. Ensure the build succeeded and there is a [lib] entry in Cargo.toml with crate-type 'cdylib'.")]
42 NoCdylibArtifact { package: String },
44 #[error("No parent directory found for {path:?}")]
45 NoParentDirectory { path: PathBuf },
47 #[error("No filename found for {path:?}")]
48 NoFilename { path: PathBuf },
50 #[error("Failed to copy library from {from:?} to {to:?}: {source:?}")]
51 CopyLibrary {
53 from: PathBuf,
55 to: PathBuf,
57 source: std::io::Error,
59 },
60 #[error("Failed to read directory {path:?}: {source}")]
61 ReadDirectory {
63 path: PathBuf,
65 source: std::io::Error,
67 },
68 #[error(transparent)]
69 IoError(#[from] std::io::Error),
71 #[error(transparent)]
72 Other(#[from] anyhow::Error),
74 #[error(transparent)]
75 VarError(#[from] std::env::VarError),
77 #[error(transparent)]
78 SubcommandError(#[from] cargo_subcommand::Error),
80 #[error(transparent)]
81 CommandExtError(#[from] command_ext::CommandExtError),
83 #[error(transparent)]
84 SignatureError(#[from] simics_sign::Error),
86 #[error(transparent)]
87 PackageError(#[from] simics_package::Error),
89 #[error(transparent)]
90 FromUtf8Error(#[from] std::string::FromUtf8Error),
92}
93
94#[derive(Parser, Debug, Clone)]
95pub struct Cmd {
96 #[clap(subcommand)]
97 pub simics_build: SimicsBuildCmd,
98}
99
100#[derive(clap::Subcommand, Debug, Clone)]
101pub enum SimicsBuildCmd {
102 SimicsBuild {
104 #[clap(flatten)]
105 args: Args,
106 #[clap(long)]
107 simics_base: Option<PathBuf>,
108 },
109}
110
111pub struct App;
112
113type Result<T> = std::result::Result<T, Error>;
114
115impl App {
116 pub fn run(cmd: Cmd) -> Result<PathBuf> {
117 let SimicsBuildCmd::SimicsBuild { args, simics_base } = cmd.simics_build;
118
119 let subcommand = Subcommand::new(args)?;
120 let cargo = var("CARGO")?;
121
122 let simics_base = if let Some(simics_base) = simics_base {
124 simics_base.clone()
125 } else if let Ok(output) = Command::new(&cargo)
126 .arg("-Zunstable-options")
127 .arg("config")
128 .arg("get")
129 .arg("env.SIMICS_BASE")
130 .check()
131 {
132 let line = output.stdout.lines().next().transpose()?;
133 line.clone()
134 .and_then(|l| l.split('=').last().map(|s| s.trim().replace('"', "")))
135 .map(PathBuf::from)
136 .ok_or_else(|| Error::SimicsBaseParseError { output: line })?
137 } else if let Ok(simics_base) = var("SIMICS_BASE") {
138 PathBuf::from(simics_base)
139 } else {
140 if !subcommand.quiet() {
141 println!("No SIMICS_BASE variable set, using the latest installed package with package number 1000")
142 }
143
144 let mut packages = ispm::packages::list(&GlobalOptions::default())?;
145 packages.sort();
146 let Some(installed) = packages.installed_packages.as_ref() else {
147 return Err(Error::NoInstalledPackages);
148 };
149 let Some(base) = installed.iter().find(|p| p.package_number == 1000) else {
150 return Err(Error::NoBasePackage);
151 };
152 base.paths
153 .first()
154 .ok_or_else(|| Error::NoPathsForBasePackage)?
155 .clone()
156 };
157
158 if !simics_base.exists() {
159 return Err(Error::BasePackageDirectoryDoesNotExist { path: simics_base });
160 }
161
162 if !subcommand.quiet() {
164 println!("Building package {}", subcommand.package());
165 }
166
167 let mut build_cmd = Command::new(&cargo);
169 build_cmd.arg("rustc");
170 build_cmd.env("SIMICS_BASE", simics_base);
171 build_cmd.env_remove("SIMICS_BINDINGS_NOCLEAN");
174 subcommand.args().apply(&mut build_cmd);
175 #[cfg(unix)]
176 build_cmd.args(["--", "-C", "link-args=-Wl,--gc-sections"]);
177 build_cmd.check()?;
178
179 let module_cdylib = subcommand
181 .artifacts()
182 .map(|a| {
183 subcommand.build_dir(subcommand.target()).join(format!(
184 "{}{}{}",
185 ARTIFACT_NAMEPARTS.0,
186 a.name.replace('-', "_"),
187 ARTIFACT_NAMEPARTS.1
188 ))
189 })
190 .find(|p| p.exists())
191 .ok_or_else(|| Error::NoCdylibArtifact {
192 package: subcommand.package().to_string(),
193 })?;
194
195 if !subcommand.quiet() {
197 println!("Signing module {module_cdylib:?}");
198 }
199
200 let mut signed = Sign::new(&module_cdylib)?;
201
202 let signed_module_cdylib = module_cdylib
203 .parent()
204 .ok_or_else(|| Error::NoParentDirectory {
205 path: module_cdylib.to_path_buf(),
206 })?
207 .join({
208 let file_name = module_cdylib
209 .file_name()
210 .ok_or_else(|| Error::NoFilename {
211 path: module_cdylib.to_path_buf(),
212 })?
213 .to_str()
214 .ok_or_else(|| Error::NoFilename {
215 path: module_cdylib.to_path_buf(),
216 })?;
217 let module_cdylib_dir =
218 module_cdylib
219 .parent()
220 .ok_or_else(|| Error::NoParentDirectory {
221 path: module_cdylib.to_path_buf(),
222 })?;
223
224 module_cdylib_dir
225 .join(file_name.replace('_', "-"))
226 .to_str()
227 .ok_or_else(|| Error::NoFilename {
228 path: module_cdylib.to_path_buf(),
229 })?
230 });
231
232 signed.write(&signed_module_cdylib)?;
233
234 let target_profile_build_dir = subcommand.build_dir(subcommand.target()).join("build");
235
236 let target_profile_build_subdirs = read_dir(&target_profile_build_dir)
238 .map_err(|e| Error::ReadDirectory {
239 path: target_profile_build_dir,
240 source: e,
241 })?
242 .filter_map(|rd| rd.ok())
243 .map(|de| de.path())
244 .filter(|p| {
245 p.is_dir()
246 && p.file_name().is_some_and(|n| {
247 n.to_str()
248 .is_some_and(|ns| ns.starts_with(subcommand.package()))
249 })
250 && !p
251 .join(format!("build-script-build{}", ARTIFACT_NAMEPARTS.4))
252 .exists()
253 && p.join("out").is_dir()
254 })
255 .collect::<Vec<_>>();
256
257 let cdylib_out_artifacts = target_profile_build_subdirs
259 .iter()
260 .map(|bd| bd.join("out"))
261 .map(|od| {
262 read_dir(&od)
263 .map_err(|e| Error::ReadDirectory {
264 path: od.clone(),
265 source: e,
266 })
267 .map(|rd| {
268 Ok(rd
269 .filter_map(|rd| rd.ok())
270 .map(|de| de.path())
271 .filter(|p| {
272 p.file_name().is_some_and(|n| {
273 n.to_str().is_some_and(|ns| {
274 ns.starts_with(ARTIFACT_NAMEPARTS.0)
275 && ns.ends_with(ARTIFACT_NAMEPARTS.1)
276 })
277 })
278 })
279 .collect::<Vec<_>>())
280 })?
281 })
282 .collect::<Result<Vec<_>>>()?
283 .into_iter()
284 .flatten()
285 .filter_map(|p| {
286 p.clone()
287 .file_name()
288 .and_then(|n| n.to_str().map(|n| (p, n.to_string())))
289 })
290 .sorted_by(|(_, a), (_, b)| a.cmp(b))
291 .chunk_by(|(_, n)| n.clone())
292 .into_iter()
294 .filter_map(|(_, g)| {
295 g.max_by_key(|(p, _)| {
296 p.metadata()
297 .map(|m| m.modified().unwrap_or(SystemTime::UNIX_EPOCH))
298 .unwrap_or_else(|_| SystemTime::UNIX_EPOCH)
299 })
300 })
301 .map(|(a, _)| {
302 a.file_name()
303 .and_then(|n| n.to_str())
304 .ok_or_else(|| Error::NoFilename { path: a.clone() })
305 .map(|t| (a.clone(), subcommand.build_dir(subcommand.target()).join(t)))
306 })
307 .collect::<Result<Vec<_>>>()?;
308
309 cdylib_out_artifacts.iter().try_for_each(|(a, t)| {
311 if !subcommand.quiet() {
312 println!("Copying interface library {a:?}");
313 }
314
315 copy(a, t).map_err(|e| Error::CopyLibrary {
316 from: a.clone(),
317 to: t.clone(),
318 source: e,
319 })?;
320
321 Ok::<(), anyhow::Error>(())
322 })?;
323
324 let package = Package::from_subcommand(&subcommand)?
325 .build(subcommand.build_dir(subcommand.target()))?;
326
327 if !subcommand.quiet() {
328 println!("Built ISPM package {:?}", package);
329 }
330
331 Ok(package)
332 }
333}