1use crate::common::CommonArch;
8use flowey::node::prelude::*;
9use flowey_lib_common::_util::group_by;
10use std::collections::BTreeMap;
11
12#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Copy)]
13pub enum OpenhclKernelPackageKind {
14 Main,
15 Cvm,
16 Dev,
17 CvmDev,
18}
19
20impl OpenhclKernelPackageKind {
21 pub fn is_dev(self) -> bool {
22 match self {
23 Self::Main | Self::Cvm => false,
24 Self::Dev | Self::CvmDev => true,
25 }
26 }
27}
28
29flowey_config! {
30 pub struct Config {
32 pub versions: BTreeMap<OpenhclKernelPackageKind, String>,
34 pub local_paths: BTreeMap<CommonArch, (ConfigVar<PathBuf>, ConfigVar<PathBuf>)>,
36 }
37}
38
39flowey_request! {
40 #[expect(clippy::enum_variant_names)]
41 pub enum Request {
42 GetKernel {
44 kind: OpenhclKernelPackageKind,
45 arch: CommonArch,
46 kernel: WriteVar<PathBuf>,
47 },
48 GetModules {
50 kind: OpenhclKernelPackageKind,
51 arch: CommonArch,
52 modules: WriteVar<PathBuf>,
53 },
54 GetPackageRoot {
56 kind: OpenhclKernelPackageKind,
57 arch: CommonArch,
58 pkg: WriteVar<PathBuf>,
59 },
60 GetMetadata {
62 kind: OpenhclKernelPackageKind,
63 arch: CommonArch,
64 metadata: WriteVar<PathBuf>,
65 },
66 }
67}
68
69new_flow_node_with_config!(struct Node);
70
71impl FlowNodeWithConfig for Node {
72 type Request = Request;
73 type Config = Config;
74
75 fn imports(ctx: &mut ImportCtx<'_>) {
76 ctx.import::<flowey_lib_common::install_dist_pkg::Node>();
77 ctx.import::<flowey_lib_common::download_gh_release::Node>();
78 }
79
80 fn emit(
81 config: Config,
82 requests: Vec<Self::Request>,
83 ctx: &mut NodeCtx<'_>,
84 ) -> anyhow::Result<()> {
85 let versions = config.versions;
86 let local_paths = config.local_paths;
87 let mut kernel_reqs: BTreeMap<
88 (OpenhclKernelPackageKind, CommonArch),
89 Vec<WriteVar<PathBuf>>,
90 > = BTreeMap::new();
91 let mut modules_reqs: BTreeMap<
92 (OpenhclKernelPackageKind, CommonArch),
93 Vec<WriteVar<PathBuf>>,
94 > = BTreeMap::new();
95 let mut pkg_reqs: BTreeMap<(OpenhclKernelPackageKind, CommonArch), Vec<WriteVar<PathBuf>>> =
96 BTreeMap::new();
97 let mut metadata_reqs: BTreeMap<
98 (OpenhclKernelPackageKind, CommonArch),
99 Vec<WriteVar<PathBuf>>,
100 > = BTreeMap::new();
101
102 for req in requests {
103 match req {
104 Request::GetKernel { kind, arch, kernel } => {
105 kernel_reqs.entry((kind, arch)).or_default().push(kernel);
106 }
107 Request::GetModules {
108 kind,
109 arch,
110 modules,
111 } => {
112 modules_reqs.entry((kind, arch)).or_default().push(modules);
113 }
114 Request::GetPackageRoot { kind, arch, pkg } => {
115 pkg_reqs.entry((kind, arch)).or_default().push(pkg);
116 }
117 Request::GetMetadata {
118 kind,
119 arch,
120 metadata,
121 } => {
122 metadata_reqs
123 .entry((kind, arch))
124 .or_default()
125 .push(metadata);
126 }
127 }
128 }
129
130 let all_reqs: std::collections::BTreeSet<(OpenhclKernelPackageKind, CommonArch)> =
132 kernel_reqs
133 .keys()
134 .chain(modules_reqs.keys())
135 .chain(pkg_reqs.keys())
136 .chain(metadata_reqs.keys())
137 .copied()
138 .collect();
139
140 for (kind, arch) in &all_reqs {
142 if !local_paths.contains_key(arch) && !versions.contains_key(kind) {
143 if kind.is_dev() {
144 anyhow::bail!(
145 "OpenHCL dev kernel support is disabled; provide local kernel paths for \
146 {:?} to enable {:?}",
147 arch,
148 kind,
149 );
150 }
151 anyhow::bail!(
152 "Must provide either SetLocal for {:?} or SetVersion for {:?}",
153 arch,
154 kind
155 );
156 }
157 }
158
159 if all_reqs.is_empty() {
160 return Ok(());
161 }
162
163 let (local_reqs, download_reqs): (Vec<_>, Vec<_>) = all_reqs
165 .into_iter()
166 .partition(|(_, arch)| local_paths.contains_key(arch));
167
168 let (kernel_reqs_local, mut kernel_reqs_download): (BTreeMap<_, _>, BTreeMap<_, _>) =
170 kernel_reqs
171 .into_iter()
172 .partition(|((_, arch), _)| local_paths.contains_key(arch));
173 let (modules_reqs_local, mut modules_reqs_download): (BTreeMap<_, _>, BTreeMap<_, _>) =
174 modules_reqs
175 .into_iter()
176 .partition(|((_, arch), _)| local_paths.contains_key(arch));
177 let (pkg_reqs_local, mut pkg_reqs_download): (BTreeMap<_, _>, BTreeMap<_, _>) = pkg_reqs
178 .into_iter()
179 .partition(|((_, arch), _)| local_paths.contains_key(arch));
180 let (metadata_reqs_local, mut metadata_reqs_download): (BTreeMap<_, _>, BTreeMap<_, _>) =
181 metadata_reqs
182 .into_iter()
183 .partition(|((_, arch), _)| local_paths.contains_key(arch));
184
185 if !local_reqs.is_empty() {
187 ctx.emit_rust_step("use local kernel package", |ctx| {
188 let mut kernel_reqs = kernel_reqs_local.claim(ctx);
189 let mut modules_reqs = modules_reqs_local.claim(ctx);
190 let mut pkg_reqs = pkg_reqs_local.claim(ctx);
191 let mut metadata_reqs = metadata_reqs_local.claim(ctx);
192 let local_paths: BTreeMap<_, _> = local_paths
193 .into_iter()
194 .map(|(arch, (k, m))| (arch, (k.claim(ctx), m.claim(ctx))))
195 .collect();
196 let local_reqs = group_by(local_reqs.into_iter().map(|(kind, arch)| (arch, kind)));
197
198 move |rt| {
199 for (arch, kinds) in local_reqs {
200 let (kernel_var, modules_var) = local_paths
201 .get(&arch)
202 .with_context(|| format!("missing local kernel paths for {arch:?}"))?;
203 let kernel_path = rt.read(kernel_var.clone());
204 let modules_path = rt.read(modules_var.clone());
205
206 log::info!(
207 "using local kernel at {:?} and modules at {:?}",
208 kernel_path,
209 modules_path
210 );
211
212 let package_root = kernel_path.parent().map(Path::to_path_buf);
213 let metadata_path = package_root
214 .as_ref()
215 .map(|root| root.join("kernel_build_metadata.json"));
216 for kind in kinds {
217 if let Some(vars) = kernel_reqs.remove(&(kind, arch)) {
218 rt.write_all(vars, &kernel_path);
219 }
220 if let Some(vars) = modules_reqs.remove(&(kind, arch)) {
221 rt.write_all(vars, &modules_path);
222 }
223 if let Some(vars) = pkg_reqs.remove(&(kind, arch)) {
224 rt.write_all(
225 vars,
226 package_root
227 .as_ref()
228 .context("local kernel path has no package directory")?,
229 );
230 }
231 if let Some(vars) = metadata_reqs.remove(&(kind, arch)) {
232 rt.write_all(
233 vars,
234 metadata_path
235 .as_ref()
236 .context("local kernel path has no metadata directory")?,
237 );
238 }
239 }
240 }
241 Ok(())
242 }
243 });
244 }
245
246 if download_reqs.is_empty() {
247 return Ok(());
248 }
249
250 let extract_zip_deps = flowey_lib_common::_util::extract::extract_zip_if_new_deps(ctx);
252
253 for (kind, arch) in download_reqs {
254 let version = versions
255 .get(&kind)
256 .with_context(|| format!("missing kernel package version for {kind:?}"))?;
257 let tag = format!(
258 "rolling-lts/hcl-{}/{}",
259 match kind {
260 OpenhclKernelPackageKind::Main | OpenhclKernelPackageKind::Cvm => "main",
261 OpenhclKernelPackageKind::Dev | OpenhclKernelPackageKind::CvmDev => "dev",
262 },
263 version
264 );
265
266 let file_name = format!(
267 "Microsoft.OHCL.Kernel{}.{}{}-{}.tar.gz",
268 match kind {
269 OpenhclKernelPackageKind::Main | OpenhclKernelPackageKind::Cvm => "",
270 OpenhclKernelPackageKind::Dev | OpenhclKernelPackageKind::CvmDev => ".Dev",
271 },
272 version,
273 match kind {
274 OpenhclKernelPackageKind::Main | OpenhclKernelPackageKind::Dev => "",
275 OpenhclKernelPackageKind::Cvm | OpenhclKernelPackageKind::CvmDev => "-cvm",
276 },
277 match arch {
278 CommonArch::X86_64 => "x64",
279 CommonArch::Aarch64 => "arm64",
280 },
281 );
282
283 let kernel_package_tar_gz =
284 ctx.reqv(|v| flowey_lib_common::download_gh_release::Request {
285 repo_owner: crate::common::OPENVMM_GITHUB_OWNER.into(),
286 repo_name: "OHCL-Linux-Kernel".into(),
287 needs_auth: false,
288 tag,
289 file_name: file_name.clone(),
290 path: v,
291 });
292
293 let kernel_file_name = match arch {
294 CommonArch::X86_64 => "vmlinux",
295 CommonArch::Aarch64 => "Image",
296 };
297
298 ctx.emit_rust_step("extract and resolve kernel package", |ctx| {
299 let extract_zip_deps = extract_zip_deps.clone().claim(ctx);
300 let kernel_vars = kernel_reqs_download.remove(&(kind, arch)).claim(ctx);
301 let modules_vars = modules_reqs_download.remove(&(kind, arch)).claim(ctx);
302 let pkg_vars = pkg_reqs_download.remove(&(kind, arch)).claim(ctx);
303 let metadata_vars = metadata_reqs_download.remove(&(kind, arch)).claim(ctx);
304 let kernel_package_tar_gz = kernel_package_tar_gz.claim(ctx);
305
306 move |rt| {
307 let kernel_package_tar_gz = rt.read(kernel_package_tar_gz);
308
309 let extract_dir = flowey_lib_common::_util::extract::extract_zip_if_new(
311 rt,
312 extract_zip_deps,
313 &kernel_package_tar_gz,
314 &file_name,
315 )?;
316
317 let kernel_path = extract_dir.join(kernel_file_name);
319 let modules_path = extract_dir.join("modules");
320 let metadata_path = extract_dir.join("kernel_build_metadata.json");
321
322 if let Some(vars) = kernel_vars {
323 rt.write_all(vars, &kernel_path);
324 }
325 if let Some(vars) = modules_vars {
326 rt.write_all(vars, &modules_path);
327 }
328 if let Some(vars) = pkg_vars {
329 rt.write_all(vars, &extract_dir);
330 }
331 if let Some(vars) = metadata_vars {
332 rt.write_all(vars, &metadata_path);
333 }
334
335 Ok(())
336 }
337 });
338 }
339
340 Ok(())
341 }
342}