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 ],
88 done: v,
89 }),
90 );
91 }
92
93 ctx.req(flowey_lib_common::install_rust::Request::InstallTargetTriple(target.clone()));
94
95 ctx.emit_rust_step("build openvmm in a distribution configuration", |ctx| {
96 done.claim(ctx);
97 deps.claim(ctx);
98 let release = release.claim(ctx);
99 let openvmm_repo_path = openvmm_repo_path.claim(ctx);
100 let rust_toolchain = rust_toolchain.claim(ctx);
101 move |rt| {
102 let release = rt.read(release);
103 let openvmm_repo_path = rt.read(openvmm_repo_path);
104 let rust_toolchain = rt.read(rust_toolchain);
105 let identity = read_vendor_identity(&release.assets)?;
106
107 rt.sh.change_dir(&openvmm_repo_path);
108 let checkout_identity = resolve_identity(rt)?;
109 if checkout_identity != identity {
110 anyhow::bail!(
111 "vendor archive identity {:?} does not match checkout {:?}",
112 identity,
113 checkout_identity
114 );
115 }
116
117 let archive = release.assets.join(identity.archive_name());
118
119 let build_root = std::env::current_dir()?;
120
121 let source_dir = build_root.join("distro-build-source");
128 if source_dir.exists() {
129 fs_err::remove_dir_all(&source_dir)?;
130 }
131 fs_err::create_dir_all(&source_dir)?;
132
133 let source_tar = build_root.join("distro-build-source.tar");
134 rt.sh.change_dir(&openvmm_repo_path);
135 flowey::shell_cmd!(rt, "git archive --format=tar -o {source_tar} HEAD").run()?;
136 flowey::shell_cmd!(rt, "tar -xf {source_tar} -C {source_dir}").run()?;
137 fs_err::remove_file(&source_tar)?;
138
139 flowey::shell_cmd!(rt, "tar -xzf {archive} -C {source_dir}").run()?;
140
141 let vendor_dir = source_dir.join("vendor");
142 let cargo_config = source_dir.join(CARGO_CONFIG_FILE);
143
144 if !vendor_dir.is_dir() {
145 anyhow::bail!("vendor archive did not extract {}", vendor_dir.display());
146 }
147
148 if !cargo_config.is_file() {
149 anyhow::bail!("vendor archive did not extract {}", cargo_config.display());
150 }
151
152 let cargo_config_toml = source_dir.join(".cargo").join("config.toml");
153 append_vendor_config(&cargo_config_toml, &cargo_config)?;
154
155 let cargo_home = build_root.join("distro-cargo-home");
156 if cargo_home.exists() {
157 fs_err::remove_dir_all(&cargo_home)?;
158 }
159 fs_err::create_dir_all(&cargo_home)?;
160
161 let cargo_target_dir = build_root.join("distro-cargo-target");
162 if cargo_target_dir.exists() {
163 fs_err::remove_dir_all(&cargo_target_dir)?;
164 }
165 fs_err::create_dir_all(&cargo_target_dir)?;
166
167 let protoc = which::which("protoc")
170 .context("could not find the distribution-provided protoc")?;
171
172 let target = target.to_string();
173 let cargo = if let Some(rust_toolchain) = &rust_toolchain {
174 flowey::shell_cmd!(rt, "rustup run {rust_toolchain} cargo")
175 } else {
176 flowey::shell_cmd!(rt, "cargo")
177 };
178
179 rt.sh.change_dir(&source_dir);
180 cargo
181 .args([
182 "build",
183 "--release",
184 "--locked",
185 "--offline",
186 "-p",
187 "openvmm",
188 "--target",
189 &target,
190 ])
191 .env("PROTOC", protoc)
192 .env("OPENSSL_NO_VENDOR", "1")
193 .env("CARGO_HOME", cargo_home)
194 .env("CARGO_TARGET_DIR", cargo_target_dir)
195 .env("CARGO_PROFILE_RELEASE_DEBUG", "0")
198 .env("CARGO_INCREMENTAL", "0")
199 .run()?;
200
201 Ok(())
202 }
203 });
204
205 Ok(())
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use super::*;
212
213 #[test]
214 fn appends_vendor_config_without_overwriting_existing_settings() {
215 let dir = tempfile::tempdir().unwrap();
216 let config = dir.path().join("config.toml");
217 let vendor_config = dir.path().join("cargo_config");
218 let existing = "[build]\ntarget-dir = \"target\"\n";
219 let replacement = "[source.crates-io]\nreplace-with = \"vendored-sources\"\n";
220
221 fs_err::write(&config, existing).unwrap();
222 fs_err::write(&vendor_config, replacement).unwrap();
223 append_vendor_config(&config, &vendor_config).unwrap();
224
225 assert_eq!(
226 fs_err::read_to_string(config).unwrap(),
227 format!("{existing}\n{replacement}")
228 );
229 }
230
231 #[test]
232 fn rejects_existing_source_configuration() {
233 let dir = tempfile::tempdir().unwrap();
234 let config = dir.path().join("config.toml");
235 let vendor_config = dir.path().join("cargo_config");
236
237 fs_err::write(&config, "[source.crates-io]\nreplace-with = \"other\"\n").unwrap();
238 fs_err::write(
239 &vendor_config,
240 "[source.vendored-sources]\ndirectory = \"vendor\"\n",
241 )
242 .unwrap();
243
244 assert!(append_vendor_config(&config, &vendor_config).is_err());
245 }
246}