flowey_lib_hvlite/_jobs/
check_distro_build.rs1use crate::assemble_openvmm_vendor_release::{
17 CARGO_CONFIG_FILE, VendorReleaseOutput, read_vendor_identity, resolve_identity,
18};
19use flowey::node::prelude::*;
20use std::io::Write;
21
22fn append_vendor_config(config_path: &Path, vendor_config_path: &Path) -> anyhow::Result<()> {
23 let existing = fs_err::read_to_string(config_path)
24 .with_context(|| format!("failed to read {}", config_path.display()))?;
25 let existing = existing
26 .parse::<toml_edit::DocumentMut>()
27 .with_context(|| format!("failed to parse {}", config_path.display()))?;
28 if existing.get("source").is_some() {
29 anyhow::bail!(
30 "{} already defines [source]; refusing to overwrite existing source configuration",
31 config_path.display()
32 );
33 }
34
35 let vendor_config = fs_err::read(vendor_config_path)
36 .with_context(|| format!("failed to read {}", vendor_config_path.display()))?;
37 let mut config = fs_err::OpenOptions::new()
38 .append(true)
39 .open(config_path)
40 .with_context(|| format!("failed to open {}", config_path.display()))?;
41 config.write_all(b"\n")?;
42 config.write_all(&vendor_config)?;
43 Ok(())
44}
45
46flowey_request! {
47 pub struct Request {
48 pub release: ReadVar<VendorReleaseOutput>,
49 pub done: WriteVar<SideEffect>,
50 }
51}
52
53new_simple_flow_node!(struct Node);
54
55impl SimpleFlowNode for Node {
56 type Request = Request;
57
58 fn imports(ctx: &mut ImportCtx<'_>) {
59 ctx.import::<crate::git_checkout_openvmm_repo::Node>();
60 ctx.import::<flowey_lib_common::install_rust::Node>();
61 ctx.import::<flowey_lib_common::install_dist_pkg::Node>();
62 }
63
64 fn process_request(request: Self::Request, ctx: &mut NodeCtx<'_>) -> anyhow::Result<()> {
65 let Request { release, done } = request;
66
67 let target = target_lexicon::triple!("x86_64-unknown-linux-gnu");
68 let openvmm_repo_path = ctx.reqv(crate::git_checkout_openvmm_repo::req::GetRepoDir);
69 let rust_toolchain = ctx.reqv(flowey_lib_common::install_rust::Request::GetRustupToolchain);
70
71 let mut deps = vec![ctx.reqv(flowey_lib_common::install_rust::Request::EnsureInstalled)];
74
75 if matches!(
76 ctx.platform(),
77 FlowPlatform::Linux(FlowPlatformLinuxDistro::Ubuntu)
78 ) {
79 deps.push(
80 ctx.reqv(|v| flowey_lib_common::install_dist_pkg::Request::Install {
81 package_names: vec![
82 "build-essential".into(),
83 "linux-libc-dev".into(),
84 "libssl-dev".into(),
85 "pkg-config".into(),
86 "protobuf-compiler".into(),
87 "libprotobuf-dev".into(),
88 ],
89 done: v,
90 }),
91 );
92 }
93
94 ctx.req(flowey_lib_common::install_rust::Request::InstallTargetTriple(target.clone()));
95
96 ctx.emit_rust_step("build openvmm in a distribution configuration", |ctx| {
97 done.claim(ctx);
98 deps.claim(ctx);
99 let release = release.claim(ctx);
100 let openvmm_repo_path = openvmm_repo_path.claim(ctx);
101 let rust_toolchain = rust_toolchain.claim(ctx);
102 move |rt| {
103 let release = rt.read(release);
104 let openvmm_repo_path = rt.read(openvmm_repo_path);
105 let rust_toolchain = rt.read(rust_toolchain);
106 let identity = read_vendor_identity(&release.assets)?;
107
108 rt.sh.change_dir(&openvmm_repo_path);
109 let checkout_identity = resolve_identity(rt)?;
110 if checkout_identity != identity {
111 anyhow::bail!(
112 "vendor archive identity {:?} does not match checkout {:?}",
113 identity,
114 checkout_identity
115 );
116 }
117
118 let archive = release.assets.join(identity.archive_name());
119
120 let build_root = std::env::current_dir()?;
121
122 let source_dir = build_root.join("distro-build-source");
129 if source_dir.exists() {
130 fs_err::remove_dir_all(&source_dir)?;
131 }
132 fs_err::create_dir_all(&source_dir)?;
133
134 let source_tar = build_root.join("distro-build-source.tar");
135 rt.sh.change_dir(&openvmm_repo_path);
136 flowey::shell_cmd!(rt, "git archive --format=tar -o {source_tar} HEAD").run()?;
137 flowey::shell_cmd!(rt, "tar -xf {source_tar} -C {source_dir}").run()?;
138 fs_err::remove_file(&source_tar)?;
139
140 flowey::shell_cmd!(rt, "tar -xzf {archive} -C {source_dir}").run()?;
141
142 let vendor_dir = source_dir.join("vendor");
143 let cargo_config = source_dir.join(CARGO_CONFIG_FILE);
144
145 if !vendor_dir.is_dir() {
146 anyhow::bail!("vendor archive did not extract {}", vendor_dir.display());
147 }
148
149 if !cargo_config.is_file() {
150 anyhow::bail!("vendor archive did not extract {}", cargo_config.display());
151 }
152
153 let cargo_config_toml = source_dir.join(".cargo").join("config.toml");
154 append_vendor_config(&cargo_config_toml, &cargo_config)?;
155
156 let cargo_home = build_root.join("distro-cargo-home");
157 if cargo_home.exists() {
158 fs_err::remove_dir_all(&cargo_home)?;
159 }
160 fs_err::create_dir_all(&cargo_home)?;
161
162 let cargo_target_dir = build_root.join("distro-cargo-target");
163 if cargo_target_dir.exists() {
164 fs_err::remove_dir_all(&cargo_target_dir)?;
165 }
166 fs_err::create_dir_all(&cargo_target_dir)?;
167
168 let protoc = which::which("protoc")
171 .context("could not find the distribution-provided protoc")?;
172
173 let target = target.to_string();
174 let cargo = if let Some(rust_toolchain) = &rust_toolchain {
175 flowey::shell_cmd!(rt, "rustup run {rust_toolchain} cargo")
176 } else {
177 flowey::shell_cmd!(rt, "cargo")
178 };
179
180 rt.sh.change_dir(&source_dir);
181 cargo
182 .args([
183 "build",
184 "--release",
185 "--locked",
186 "--offline",
187 "-p",
188 "openvmm",
189 "--target",
190 &target,
191 ])
192 .env("PROTOC", protoc)
193 .env("OPENSSL_NO_VENDOR", "1")
194 .env("CARGO_HOME", cargo_home)
195 .env("CARGO_TARGET_DIR", cargo_target_dir)
196 .env("CARGO_PROFILE_RELEASE_DEBUG", "0")
199 .env("CARGO_INCREMENTAL", "0")
200 .run()?;
201
202 Ok(())
203 }
204 });
205
206 Ok(())
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn appends_vendor_config_without_overwriting_existing_settings() {
216 let dir = tempfile::tempdir().unwrap();
217 let config = dir.path().join("config.toml");
218 let vendor_config = dir.path().join("cargo_config");
219 let existing = "[build]\ntarget-dir = \"target\"\n";
220 let replacement = "[source.crates-io]\nreplace-with = \"vendored-sources\"\n";
221
222 fs_err::write(&config, existing).unwrap();
223 fs_err::write(&vendor_config, replacement).unwrap();
224 append_vendor_config(&config, &vendor_config).unwrap();
225
226 assert_eq!(
227 fs_err::read_to_string(config).unwrap(),
228 format!("{existing}\n{replacement}")
229 );
230 }
231
232 #[test]
233 fn rejects_existing_source_configuration() {
234 let dir = tempfile::tempdir().unwrap();
235 let config = dir.path().join("config.toml");
236 let vendor_config = dir.path().join("cargo_config");
237
238 fs_err::write(&config, "[source.crates-io]\nreplace-with = \"other\"\n").unwrap();
239 fs_err::write(
240 &vendor_config,
241 "[source.vendored-sources]\ndirectory = \"vendor\"\n",
242 )
243 .unwrap();
244
245 assert!(append_vendor_config(&config, &vendor_config).is_err());
246 }
247}