xaction/
lib.rs

1use std::{
2    env,
3    path::PathBuf,
4    sync::atomic::AtomicBool,
5    sync::atomic::Ordering,
6    time::{Duration, Instant},
7};
8
9pub use xshell::*;
10
11pub type Error = Box<dyn std::error::Error>;
12pub type Result<T, E = Error> = std::result::Result<T, E>;
13
14pub fn section(name: &'static str) -> Section {
15    Section::new(name)
16}
17
18pub fn push_rustup_toolchain(name: &str) -> Pushenv {
19    pushenv("RUSTUP_TOOLCHAIN", name)
20}
21
22static DRY_RUN: AtomicBool = AtomicBool::new(false);
23pub fn set_dry_run(yes: bool) {
24    DRY_RUN.store(yes, Ordering::Relaxed)
25}
26fn dry_run() -> Option<&'static str> {
27    let dry_run = DRY_RUN.load(Ordering::Relaxed);
28    if dry_run {
29        Some("--dry-run")
30    } else {
31        None
32    }
33}
34
35pub fn cargo_toml() -> Result<CargoToml> {
36    let cwd = cwd()?;
37    let path = cwd.join("Cargo.toml");
38    let contents = read_file(&path)?;
39    Ok(CargoToml { path, contents })
40}
41
42pub struct CargoToml {
43    path: PathBuf,
44    contents: String,
45}
46
47impl CargoToml {
48    pub fn version(&self) -> Result<&str> {
49        self.get("version")
50    }
51
52    fn get(&self, field: &str) -> Result<&str> {
53        for line in self.contents.lines() {
54            let words = line.split_ascii_whitespace().collect::<Vec<_>>();
55            match words.as_slice() {
56                [n, "=", v, ..] if n.trim() == field => {
57                    assert!(v.starts_with('"') && v.ends_with('"'));
58                    return Ok(&v[1..v.len() - 1]);
59                }
60                _ => (),
61            }
62        }
63        Err(format!("can't find `{}` in {}", field, self.path.display()))?
64    }
65
66    pub fn publish(&self) -> Result<()> {
67        let token = env::var("CRATES_IO_TOKEN").unwrap_or("no token".to_string());
68        let dry_run = dry_run();
69        cmd!("cargo publish --token {token} {dry_run...}").run()?;
70        Ok(())
71    }
72    pub fn publish_all(&self, dirs: &[&str]) -> Result<()> {
73        let token = env::var("CRATES_IO_TOKEN").unwrap_or("no token".to_string());
74        if dry_run().is_none() {
75            for &dir in dirs {
76                for _ in 0..20 {
77                    std::thread::sleep(Duration::from_secs(10));
78                    if cmd!("cargo publish --manifest-path {dir}'/Cargo.toml' --token {token} --dry-run").run().is_ok()
79                    {
80                        break;
81                    }
82                }
83                cmd!("cargo publish --manifest-path {dir}'/Cargo.toml' --token {token}").run()?;
84            }
85        }
86        Ok(())
87    }
88}
89
90pub mod git {
91    use xshell::cmd;
92
93    use super::{dry_run, Result};
94
95    pub fn current_branch() -> Result<String> {
96        let res = cmd!("git branch --show-current").read()?;
97        Ok(res)
98    }
99
100    pub fn tag_list() -> Result<Vec<String>> {
101        let tags = cmd!("git tag --list").read()?;
102        let res = tags.lines().map(|it| it.trim().to_string()).collect();
103        Ok(res)
104    }
105
106    pub fn has_tag(tag: &str) -> Result<bool> {
107        let res = tag_list()?.iter().any(|it| it == tag);
108        Ok(res)
109    }
110
111    pub fn tag(tag: &str) -> Result<()> {
112        if dry_run().is_some() {
113            return Ok(());
114        }
115        cmd!("git tag {tag}").run()?;
116        Ok(())
117    }
118
119    pub fn push_tags() -> Result<()> {
120        // `git push --tags --dry-run` exists, but it will fail with permissions
121        // error for forks.
122        if dry_run().is_some() {
123            return Ok(());
124        }
125
126        cmd!("git push --tags").run()?;
127        Ok(())
128    }
129}
130
131pub struct Section {
132    name: &'static str,
133    start: Instant,
134}
135
136impl Section {
137    fn new(name: &'static str) -> Section {
138        println!("::group::{}", name);
139        let start = Instant::now();
140        Section { name, start }
141    }
142}
143
144impl Drop for Section {
145    fn drop(&mut self) {
146        eprintln!("{}: {:.2?}", self.name, self.start.elapsed());
147        println!("::endgroup::");
148    }
149}