lintd_taskops/
ops.rs

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    /// Like make recipe, drop all outputs.
13    ///
14    /// # Errors
15    /// Return error if recipe failed.
16    fn go(&self) -> Result<()>;
17
18    /// Recipe helper, attach cmd debug repr at failing status.
19    ///
20    /// # Errors
21    /// Return error if expression failed.
22    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
39/// # Errors
40/// print short hint to stderr and bail.
41pub 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
83/// # Errors
84/// print short hint to stderr and bail.
85pub 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
182/// # Errors
183/// print short hint to stderr and bail.
184pub 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}