1use anyhow::Context;
5use std::collections::BTreeMap;
6use std::path::Path;
7use std::path::PathBuf;
8
9#[derive(clap::Args)]
11pub struct Regen {
12 #[clap(long)]
14 check: bool,
15
16 #[clap(long)]
18 quiet: bool,
19}
20
21impl 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 {
53 let quiet = self.quiet.then_some("-q");
54 let sh = xshell::Shell::new()?;
55 sh.change_dir(&working_dir);
56 xshell::cmd!(sh, "cargo build -p {bin_name} {quiet...}").run()?;
57 }
58
59 let bin = working_dir
61 .join(
62 std::env::var("CARGO_TARGET_DIR")
63 .as_deref()
64 .unwrap_or("target"),
65 )
66 .join(std::env::var("CARGO_BUILD_TARGET").as_deref().unwrap_or(""))
67 .join("debug")
68 .join(&exe_name);
69
70 if !bin.exists() {
71 panic!("should have found built {bin_name} at {}", bin.display());
72 }
73
74 bin2flowey.insert(bin_name.clone(), bin.clone());
76 bin
77 };
78
79 for (backend, defns) in pipelines {
80 for flowey_toml::PipelineDefn { file, cmd } in defns {
81 let check = if self.check {
82 vec!["--check".into(), file.display().to_string()]
83 } else {
84 vec![]
85 };
86
87 let sh = xshell::Shell::new()?;
88 sh.change_dir(&working_dir);
89 let res = xshell::cmd!(
90 sh,
91 "{bin} pipeline {backend} --out {file} {check...} {cmd...}"
92 )
93 .run();
94
95 if res.is_err() {
96 error = true;
97 }
98 }
99 }
100 }
101 }
102
103 if error {
104 anyhow::bail!("encountered one or more errors")
105 }
106
107 Ok(())
108 }
109}
110
111#[derive(Debug)]
112pub struct ResolvedFloweyToml {
113 pub working_dir: PathBuf,
114 pub pipelines: BTreeMap<String, BTreeMap<String, Vec<flowey_toml::PipelineDefn>>>,
116}
117
118fn resolve_flowey_toml(
119 flowey_toml: flowey_toml::FloweyToml,
120 working_dir: PathBuf,
121) -> anyhow::Result<Vec<ResolvedFloweyToml>> {
122 let mut v = Vec::new();
123 resolve_flowey_toml_inner(flowey_toml, working_dir, &mut v)?;
124 Ok(v)
125}
126
127fn resolve_flowey_toml_inner(
128 flowey_toml: flowey_toml::FloweyToml,
129 working_dir: PathBuf,
130 resolved: &mut Vec<ResolvedFloweyToml>,
131) -> anyhow::Result<()> {
132 let flowey_toml::FloweyToml { include, pipeline } = flowey_toml;
133
134 let mut resolved_pipelines: BTreeMap<String, BTreeMap<String, Vec<_>>> = BTreeMap::new();
135 for (bin_name, pipelines) in pipeline {
136 for (backend, defns) in pipelines {
137 resolved_pipelines
138 .entry(bin_name.clone())
139 .or_default()
140 .entry(backend)
141 .or_default()
142 .extend(defns);
143 }
144 }
145
146 for path in include.unwrap_or_default() {
147 let path = working_dir.join(path);
148 let flowey_toml = fs_err::read_to_string(&path)?;
149 let flowey_toml: flowey_toml::FloweyToml = toml_edit::de::from_str(&flowey_toml)
150 .with_context(|| anyhow::anyhow!("while parsing {}", path.display()))?;
151 let mut working_dir = path;
152 working_dir.pop();
153 resolve_flowey_toml_inner(flowey_toml, working_dir, resolved)?
154 }
155
156 resolved.push(ResolvedFloweyToml {
157 working_dir,
158 pipelines: resolved_pipelines,
159 });
160
161 Ok(())
162}
163
164mod flowey_toml {
165 use serde::Deserialize;
166 use serde::Serialize;
167 use std::collections::BTreeMap;
168 use std::path::PathBuf;
169
170 #[derive(Debug, Serialize, Deserialize)]
171 pub struct FloweyToml {
172 pub include: Option<Vec<PathBuf>>,
173 pub pipeline: BTreeMap<String, BTreeMap<String, Vec<PipelineDefn>>>,
175 }
176
177 #[derive(Debug, Serialize, Deserialize)]
178 pub struct PipelineDefn {
179 pub file: PathBuf,
180 pub cmd: Vec<String>,
181 }
182}
183
184fn install_flowey_merge_driver() -> anyhow::Result<()> {
185 const DRIVER_NAME: &str = "flowey-theirs merge driver";
186 const DRIVER_COMMAND: &str = "cp %B %A";
187
188 let sh = xshell::Shell::new()?;
189 xshell::cmd!(sh, "git config merge.flowey-theirs.name {DRIVER_NAME}")
190 .quiet()
191 .ignore_status()
192 .run()?;
193 xshell::cmd!(sh, "git config merge.flowey-theirs.driver {DRIVER_COMMAND}")
194 .quiet()
195 .ignore_status()
196 .run()?;
197
198 Ok(())
199}