1use std::path::{Path, PathBuf};
2use std::process::Command;
3
4pub enum BuildKind {
6 Bin(String),
8 Lib,
10}
11
12pub struct GuestBuild {
17 pub manifest_dir: PathBuf,
19 pub target_json_path: PathBuf,
21 pub target_dir_name: String,
24 pub build_kind: BuildKind,
26 pub extra_rustflags: Vec<String>,
28 pub env_overrides: Vec<(String, String)>,
30 pub rustc_bootstrap: bool,
32}
33
34impl GuestBuild {
35 pub fn build(&self) -> PathBuf {
43 let src_dir = self.manifest_dir.join("src");
45 println!("cargo:rerun-if-changed={}", src_dir.display());
46 println!(
47 "cargo:rerun-if-changed={}",
48 self.manifest_dir.join("Cargo.toml").display()
49 );
50 println!("cargo:rerun-if-env-changed=SKIP_GUEST_BUILD");
51
52 if std::env::var("SKIP_GUEST_BUILD").is_ok() {
54 let elf_path = self.output_elf_path();
55 if elf_path.exists() {
56 return elf_path;
57 }
58 }
60
61 let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
62 let target_dir = PathBuf::from(&out_dir)
63 .join("guest-build")
64 .join(&self.target_dir_name);
65
66 let manifest_path = self.manifest_dir.join("Cargo.toml");
67
68 let mut cmd = Command::new("cargo");
69 cmd.arg("build")
70 .arg("--release")
71 .arg("--manifest-path")
72 .arg(&manifest_path)
73 .arg("--target")
74 .arg(&self.target_json_path)
75 .arg("-Zbuild-std=core,alloc");
76
77 match &self.build_kind {
78 BuildKind::Bin(name) => {
79 cmd.arg("--bin").arg(name);
80 }
81 BuildKind::Lib => {
82 cmd.arg("--lib");
83 }
84 }
85
86 cmd.env("CARGO_TARGET_DIR", &target_dir);
88
89 if !self.extra_rustflags.is_empty() {
91 let encoded = self.extra_rustflags.join("\x1f");
92 cmd.env("CARGO_ENCODED_RUSTFLAGS", &encoded);
93 }
94
95 if self.rustc_bootstrap {
96 cmd.env("RUSTC_BOOTSTRAP", "1");
97 }
98
99 for (key, val) in &self.env_overrides {
100 cmd.env(key, val);
101 }
102
103 let output = cmd.output().expect("failed to spawn cargo for guest build");
104
105 if !output.status.success() {
106 let stderr = String::from_utf8_lossy(&output.stderr);
107 let stdout = String::from_utf8_lossy(&output.stdout);
108 panic!(
109 "Guest build failed for {}:\n--- stderr ---\n{}\n--- stdout ---\n{}",
110 self.manifest_dir.display(),
111 stderr,
112 stdout
113 );
114 }
115
116 let elf_path = self.output_elf_path();
117 assert!(
118 elf_path.exists(),
119 "Expected ELF artifact not found at: {}",
120 elf_path.display()
121 );
122 elf_path
123 }
124
125 fn output_elf_path(&self) -> PathBuf {
126 let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
127 let target_dir = PathBuf::from(&out_dir)
128 .join("guest-build")
129 .join(&self.target_dir_name);
130
131 let artifact_name = match &self.build_kind {
132 BuildKind::Bin(name) => name.clone(),
133 BuildKind::Lib => {
134 let manifest_path = self.manifest_dir.join("Cargo.toml");
136 let contents =
137 std::fs::read_to_string(&manifest_path).expect("failed to read Cargo.toml");
138 let lib_name = parse_lib_name(&contents, &self.manifest_dir);
139 lib_name
140 }
141 };
142
143 let release_dir = target_dir
144 .join(&self.target_dir_name)
145 .join("release");
146
147 let candidates = match &self.build_kind {
149 BuildKind::Bin(_) => vec![
150 release_dir.join(format!("{}.elf", artifact_name)),
151 release_dir.join(&artifact_name),
152 ],
153 BuildKind::Lib => vec![
154 release_dir.join(format!("{}.elf", artifact_name)),
155 release_dir.join(format!("lib{}.elf", artifact_name)),
156 ],
157 };
158
159 for candidate in &candidates {
160 if candidate.exists() {
161 return candidate.clone();
162 }
163 }
164
165 candidates.into_iter().next().unwrap()
167 }
168}
169
170fn parse_lib_name(contents: &str, manifest_dir: &Path) -> String {
173 let mut in_lib_section = false;
175 for line in contents.lines() {
176 let trimmed = line.trim();
177 if trimmed == "[lib]" {
178 in_lib_section = true;
179 continue;
180 }
181 if trimmed.starts_with('[') {
182 in_lib_section = false;
183 continue;
184 }
185 if in_lib_section && trimmed.starts_with("name") {
186 if let Some(name) = extract_toml_string_value(trimmed) {
187 return name;
188 }
189 }
190 }
191
192 for line in contents.lines() {
194 let trimmed = line.trim();
195 if trimmed.starts_with("name") {
196 if let Some(name) = extract_toml_string_value(trimmed) {
197 return name.replace('-', "_");
198 }
199 }
200 }
201
202 manifest_dir
204 .file_name()
205 .unwrap()
206 .to_str()
207 .unwrap()
208 .replace('-', "_")
209}
210
211fn extract_toml_string_value(line: &str) -> Option<String> {
212 let after_eq = line.split('=').nth(1)?.trim();
213 let unquoted = after_eq.trim_matches('"').trim_matches('\'');
214 Some(unquoted.to_string())
215}
216
217pub fn write_target_json(filename: &str, contents: &str) -> PathBuf {
219 let out_dir = std::env::var("OUT_DIR").expect("OUT_DIR not set");
220 let targets_dir = PathBuf::from(&out_dir).join("targets");
221 std::fs::create_dir_all(&targets_dir).expect("failed to create targets dir");
222 let path = targets_dir.join(filename);
223 std::fs::write(&path, contents).expect("failed to write target JSON");
224 path
225}
226
227pub fn resolve_manifest_dir(relative_path: &str) -> PathBuf {
229 let manifest_dir =
230 std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
231 let resolved = PathBuf::from(&manifest_dir).join(relative_path);
232 assert!(
233 resolved.exists(),
234 "Service crate not found at: {} (resolved from CARGO_MANIFEST_DIR={})",
235 resolved.display(),
236 manifest_dir
237 );
238 std::fs::canonicalize(&resolved).expect("failed to canonicalize path")
239}