microsoft/openvmm
Publicmirrored fromhttps://github.com/microsoft/openvmmAvailable
flowey/flowey_cli/src/cli/regen.rs
212lines · modecode
| 1 | // Copyright (c) Microsoft Corporation. |
| 2 | // Licensed under the MIT License. |
| 3 | |
| 4 | use anyhow::Context; |
| 5 | use std::collections::BTreeMap; |
| 6 | use std::path::Path; |
| 7 | use std::path::PathBuf; |
| 8 | |
| 9 | /// Regenerate all pipelines defined in the repo's root `.flowey.toml` |
| 10 | #[derive(clap::Args)] |
| 11 | pub struct Regen { |
| 12 | /// Check that pipelines are up to date, without regenerating them. |
| 13 | #[clap(long)] |
| 14 | check: bool, |
| 15 | |
| 16 | /// Pass `--quiet` to any subprocess invocations of `cargo run`. |
| 17 | #[clap(long)] |
| 18 | quiet: bool, |
| 19 | } |
| 20 | |
| 21 | impl Regen { |
| 22 | pub fn run(self, repo_root: &Path) -> anyhow::Result<()> { |
| 23 | install_flowey_merge_driver()?; |
| 24 | |
| 25 | if !repo_root.join(".flowey.toml").exists() { |
| 26 | log::warn!("no .flowey.toml exists in the repo root"); |
| 27 | return Ok(()); |
| 28 | } |
| 29 | |
| 30 | let flowey_toml = fs_err::read_to_string(repo_root.join(".flowey.toml"))?; |
| 31 | let flowey_toml: flowey_toml::FloweyToml = |
| 32 | toml_edit::de::from_str(&flowey_toml).context("while parsing .flowey.toml")?; |
| 33 | |
| 34 | let data = resolve_flowey_toml(flowey_toml, repo_root.to_owned()) |
| 35 | .context("while resolving .flowey.toml")?; |
| 36 | |
| 37 | let mut bin2flowey = BTreeMap::<String, PathBuf>::new(); |
| 38 | |
| 39 | let mut error = false; |
| 40 | for ResolvedFloweyToml { |
| 41 | working_dir, |
| 42 | pipelines, |
| 43 | } in data |
| 44 | { |
| 45 | for (bin_name, pipelines) in pipelines { |
| 46 | let exe_name = format!("{bin_name}{}", std::env::consts::EXE_SUFFIX); |
| 47 | |
| 48 | let bin = if let Some(bin) = bin2flowey.get(&bin_name) { |
| 49 | bin.clone() |
| 50 | } else { |
| 51 | // build the requested flowey |
| 52 | { |
| 53 | let quiet = self.quiet.then_some("-q"); |
| 54 | #[expect( |
| 55 | clippy::disallowed_methods, |
| 56 | reason = "not in a flowey runtime context" |
| 57 | )] |
| 58 | let sh = xshell::Shell::new()?; |
| 59 | sh.change_dir(&working_dir); |
| 60 | #[expect(clippy::disallowed_macros)] |
| 61 | xshell::cmd!(sh, "cargo build -p {bin_name} {quiet...}").run()?; |
| 62 | } |
| 63 | |
| 64 | // find the built flowey |
| 65 | let bin = working_dir |
| 66 | .join( |
| 67 | std::env::var("CARGO_TARGET_DIR") |
| 68 | .as_deref() |
| 69 | .unwrap_or("target"), |
| 70 | ) |
| 71 | .join(std::env::var("CARGO_BUILD_TARGET").as_deref().unwrap_or("")) |
| 72 | .join("debug") |
| 73 | .join(&exe_name); |
| 74 | |
| 75 | if !bin.exists() { |
| 76 | panic!("should have found built {bin_name} at {}", bin.display()); |
| 77 | } |
| 78 | |
| 79 | // stash result for future consumers |
| 80 | bin2flowey.insert(bin_name.clone(), bin.clone()); |
| 81 | bin |
| 82 | }; |
| 83 | |
| 84 | for (backend, defns) in pipelines { |
| 85 | for flowey_toml::PipelineDefn { file, cmd } in defns { |
| 86 | let check = if self.check { |
| 87 | vec!["--check".into(), file.display().to_string()] |
| 88 | } else { |
| 89 | vec![] |
| 90 | }; |
| 91 | |
| 92 | #[expect( |
| 93 | clippy::disallowed_methods, |
| 94 | reason = "not in a flowey runtime context" |
| 95 | )] |
| 96 | let sh = xshell::Shell::new()?; |
| 97 | sh.change_dir(&working_dir); |
| 98 | #[expect(clippy::disallowed_macros)] |
| 99 | let res = xshell::cmd!( |
| 100 | sh, |
| 101 | "{bin} pipeline {backend} --out {file} {check...} {cmd...}" |
| 102 | ) |
| 103 | .run(); |
| 104 | |
| 105 | if res.is_err() { |
| 106 | error = true; |
| 107 | } |
| 108 | } |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | if error { |
| 114 | anyhow::bail!("encountered one or more errors") |
| 115 | } |
| 116 | |
| 117 | Ok(()) |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | #[derive(Debug)] |
| 122 | pub struct ResolvedFloweyToml { |
| 123 | pub working_dir: PathBuf, |
| 124 | // (bin, (backend, metadata)) |
| 125 | pub pipelines: BTreeMap<String, BTreeMap<String, Vec<flowey_toml::PipelineDefn>>>, |
| 126 | } |
| 127 | |
| 128 | fn resolve_flowey_toml( |
| 129 | flowey_toml: flowey_toml::FloweyToml, |
| 130 | working_dir: PathBuf, |
| 131 | ) -> anyhow::Result<Vec<ResolvedFloweyToml>> { |
| 132 | let mut v = Vec::new(); |
| 133 | resolve_flowey_toml_inner(flowey_toml, working_dir, &mut v)?; |
| 134 | Ok(v) |
| 135 | } |
| 136 | |
| 137 | fn resolve_flowey_toml_inner( |
| 138 | flowey_toml: flowey_toml::FloweyToml, |
| 139 | working_dir: PathBuf, |
| 140 | resolved: &mut Vec<ResolvedFloweyToml>, |
| 141 | ) -> anyhow::Result<()> { |
| 142 | let flowey_toml::FloweyToml { include, pipeline } = flowey_toml; |
| 143 | |
| 144 | let mut resolved_pipelines: BTreeMap<String, BTreeMap<String, Vec<_>>> = BTreeMap::new(); |
| 145 | for (bin_name, pipelines) in pipeline { |
| 146 | for (backend, defns) in pipelines { |
| 147 | resolved_pipelines |
| 148 | .entry(bin_name.clone()) |
| 149 | .or_default() |
| 150 | .entry(backend) |
| 151 | .or_default() |
| 152 | .extend(defns); |
| 153 | } |
| 154 | } |
| 155 | |
| 156 | for path in include.unwrap_or_default() { |
| 157 | let path = working_dir.join(path); |
| 158 | let flowey_toml = fs_err::read_to_string(&path)?; |
| 159 | let flowey_toml: flowey_toml::FloweyToml = toml_edit::de::from_str(&flowey_toml) |
| 160 | .with_context(|| anyhow::anyhow!("while parsing {}", path.display()))?; |
| 161 | let mut working_dir = path; |
| 162 | working_dir.pop(); |
| 163 | resolve_flowey_toml_inner(flowey_toml, working_dir, resolved)? |
| 164 | } |
| 165 | |
| 166 | resolved.push(ResolvedFloweyToml { |
| 167 | working_dir, |
| 168 | pipelines: resolved_pipelines, |
| 169 | }); |
| 170 | |
| 171 | Ok(()) |
| 172 | } |
| 173 | |
| 174 | mod flowey_toml { |
| 175 | use serde::Deserialize; |
| 176 | use serde::Serialize; |
| 177 | use std::collections::BTreeMap; |
| 178 | use std::path::PathBuf; |
| 179 | |
| 180 | #[derive(Debug, Serialize, Deserialize)] |
| 181 | pub struct FloweyToml { |
| 182 | pub include: Option<Vec<PathBuf>>, |
| 183 | // (bin, (backend, metadata)) |
| 184 | pub pipeline: BTreeMap<String, BTreeMap<String, Vec<PipelineDefn>>>, |
| 185 | } |
| 186 | |
| 187 | #[derive(Debug, Serialize, Deserialize)] |
| 188 | pub struct PipelineDefn { |
| 189 | pub file: PathBuf, |
| 190 | pub cmd: Vec<String>, |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | fn install_flowey_merge_driver() -> anyhow::Result<()> { |
| 195 | const DRIVER_NAME: &str = "flowey-theirs merge driver"; |
| 196 | const DRIVER_COMMAND: &str = "cp %B %A"; |
| 197 | |
| 198 | #[expect(clippy::disallowed_methods, reason = "not in a flowey runtime context")] |
| 199 | let sh = xshell::Shell::new()?; |
| 200 | #[expect(clippy::disallowed_macros, reason = "not in a flowey runtime context")] |
| 201 | xshell::cmd!(sh, "git config merge.flowey-theirs.name {DRIVER_NAME}") |
| 202 | .quiet() |
| 203 | .ignore_status() |
| 204 | .run()?; |
| 205 | #[expect(clippy::disallowed_macros, reason = "not in a flowey runtime context")] |
| 206 | xshell::cmd!(sh, "git config merge.flowey-theirs.driver {DRIVER_COMMAND}") |
| 207 | .quiet() |
| 208 | .ignore_status() |
| 209 | .run()?; |
| 210 | |
| 211 | Ok(()) |
| 212 | } |
| 213 | |