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 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}