1use anyhow::{anyhow, bail, Context, Result};
2use duct::cmd;
3use serde_derive::Deserialize;
4use std::{
5 fs::File,
6 io::{BufReader, Read},
7};
8use toml::Table;
9use xtaskops::ops::clean_files;
10
11pub trait Recipe {
12 fn go(&self) -> Result<()>;
17
18 fn eval(&self) -> Result<String>;
23}
24
25impl Recipe for duct::Expression {
26 fn go(&self) -> Result<()> {
27 let txt = format!("{self:?}");
28 println!("-- {txt}");
29 self.run()?;
30 Ok(())
31 }
32
33 fn eval(&self) -> Result<String> {
34 let txt = format!("{self:?}");
35 self.read().with_context(|| format!("eval: {txt}"))
36 }
37}
38
39pub fn neo_coverage() -> Result<()> {
42 do_neo_coverage().map_err(|e| {
43 eprintln!("{:#}", &e);
44 e
45 })
46}
47
48fn do_neo_coverage() -> Result<()> {
49 cmd!("cargo", "test")
50 .env("CARGO_INCREMENTAL", "0")
51 .env("RUSTFLAGS", "-Cinstrument-coverage")
52 .env("LLVM_PROFILE_FILE", "cargo-test-%p-%m.profraw")
53 .stderr_to_stdout()
54 .stdout_capture()
55 .go()?;
56 cmd!(
57 "grcov",
58 ".",
59 "--binary-path",
60 "./target/debug/deps",
61 "-s",
62 ".",
63 "-t",
64 "coveralls",
65 "--branch",
66 "--ignore-not-existing",
67 "--ignore",
68 "../*",
69 "--ignore",
70 "/*",
71 "--ignore",
72 "xtask/*",
73 "--ignore",
74 "*/src/tests/*",
75 "--token",
76 "NO_TOKEN",
77 )
78 .go()?;
79 clean_files("**/*.profraw")?;
80 Ok(())
81}
82
83pub fn bump_version(bump: &str) -> Result<()> {
86 check_wd_clean()?;
87 println!("=== bump version ===");
88 cmd!("cargo", "set-version", "--exclude", "xtask", "--bump", bump).go()?;
89 let pkg = default_members()?
90 .pop()
91 .ok_or_else(|| anyhow!("bad default-members"))?;
92 println!("pkg: {}", &pkg);
93 let ver = package_version(&pkg)?;
94 if cmd!("nix", "eval", ".#packages")
95 .stderr_to_stdout()
96 .read()
97 .is_ok()
98 {
99 cmd!("nix-update", "default", "--flake", "--version", &ver).go()?;
100 }
101 cmd!(
102 "git",
103 "commit",
104 "--all",
105 "--message",
106 format!(":bookmark: bump to v{ver}")
107 )
108 .go()?;
109 Ok(())
110}
111
112fn read_toml(path: &str) -> Result<Table> {
113 let f = File::open(path).context("open file")?;
114 let mut buf_rd = BufReader::new(f);
115 let mut buf = String::new();
116 buf_rd.read_to_string(&mut buf).context("read_to_string")?;
117 buf.parse().context("try into Table")
118}
119
120#[derive(Deserialize)]
121struct WorkspaceMeta {
122 workspace: Workspace,
123}
124
125#[derive(Deserialize)]
126struct Workspace {
127 members: Vec<String>,
128}
129
130#[derive(Deserialize)]
131struct PackageMeta {
132 package: Package,
133}
134#[derive(Deserialize)]
135struct Package {
136 name: String,
137 version: String,
138}
139
140fn package_version(package: &str) -> Result<String> {
141 let meta: PackageMeta = read_toml(&format!("./{package}/Cargo.toml"))?
142 .try_into()
143 .context("try into PackageMeta")?;
144 assert_eq!(meta.package.name, package);
145 Ok(meta.package.version)
146}
147
148fn default_members() -> Result<Vec<String>> {
149 let ws: WorkspaceMeta = read_toml("./Cargo.toml")?
150 .try_into()
151 .context("try into WorkspaceMeta")?;
152 Ok(ws
153 .workspace
154 .members
155 .into_iter()
156 .filter(|x| x != "xtask")
157 .collect())
158}
159
160fn check_wd_clean() -> Result<()> {
161 let status = cmd!("git", "status", "-s").eval()?;
162 if !status.is_empty() {
163 bail!("{status}\nWorking directory dirty !!");
164 }
165 Ok(())
166}
167
168fn check_main_branch() -> Result<()> {
169 let branch = cmd!("git", "branch", "--show-current").eval()?;
170 if matches!(branch.as_ref(), "master" | "main") {
171 Ok(())
172 } else {
173 bail!("branch `{branch}` is not main branch")
174 }
175}
176
177fn push_verbose() -> Result<()> {
178 cmd!("git", "push", "--verbose").go()?;
179 Ok(())
180}
181
182pub fn publish() -> Result<()> {
185 check_wd_clean()?;
186 check_main_branch()?;
187 push_verbose()?;
188 let pkgs = &default_members()?;
189 println!("=== dry run check all ===");
190 for pkg in pkgs {
191 println!("=== checking {pkg} ===");
192 cmd!(
193 "cargo",
194 "publish",
195 "--registry",
196 "crates-io",
197 "-p",
198 pkg,
199 "--dry-run"
200 )
201 .go()?;
202 }
203 println!("=== do publish ===");
204 for pkg in pkgs {
205 println!("=== publishing {pkg} ===");
206 cmd!("cargo", "publish", "--registry", "crates-io", "-p", pkg).go()?;
207 }
208 println!("=== github release ===");
209 let ver = package_version(&pkgs[0])?;
210 cmd!(
211 "gh",
212 "release",
213 "create",
214 format!("v{ver}"),
215 "--generate-notes"
216 )
217 .go()?;
218 cmd!("git", "fetch", "--tags").go()?;
219 println!("=== bump patch version ===");
220 bump_version("patch")?;
221 push_verbose()?;
222 Ok(())
223}