Unnamed repository; edit this file 'description' to name the repository.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use std::{
    env, fs,
    process::{self, Command, ExitStatus, Stdio},
    time::Instant,
};

type Error = Box<dyn std::error::Error>;
type Result<T> = std::result::Result<T, Error>;

fn main() {
    if let Err(err) = try_main() {
        eprintln!("{}", err);
        process::exit(1);
    }
}

fn try_main() -> Result<()> {
    let cwd = env::current_dir()?;
    let cargo_toml = cwd.join("Cargo.toml");
    assert!(
        cargo_toml.exists(),
        "Cargo.toml not found, cwd: {}",
        cwd.display()
    );

    {
        let _s = Section::new("BUILD_NO_DEFAULT_FEATURES");
        shell("cargo test --all-features --workspace --no-run --no-default-features")?;
    }

    {
        let _s = Section::new("BUILD");
        shell("cargo test --all-features --workspace --no-run")?;
    }

    {
        let _s = Section::new("TEST");
        shell("cargo test --all-features --workspace")?;
        shell("cargo test --no-default-features --workspace")?;
    }

    {
        let _s = Section::new("TEST_BENCHES");
        shell("cargo test --benches --all-features")?;
    }

    let current_branch = shell_output("git branch --show-current")?;
    if &current_branch == "master" {
        let _s = Section::new("PUBLISH");
        let manifest = fs::read_to_string(&cargo_toml)?;
        let version = get_field(&manifest, "version")?;
        let tag = format!("v{}", version);
        let tags = shell_output("git tag --list")?;

        if !tags.contains(&tag) {
            let token = env::var("CRATES_IO_TOKEN").unwrap();
            shell(&format!("git tag v{}", version))?;
            shell(&format!("cargo publish --token {}", token))?;
            shell("git push --tags")?;
        }
    }
    Ok(())
}

fn get_field<'a>(text: &'a str, name: &str) -> Result<&'a str> {
    for line in text.lines() {
        let words = line.split_ascii_whitespace().collect::<Vec<_>>();
        match words.as_slice() {
            [n, "=", v, ..] if n.trim() == name => {
                assert!(v.starts_with('"') && v.ends_with('"'));
                return Ok(&v[1..v.len() - 1]);
            }
            _ => (),
        }
    }
    Err(format!("can't find `{}` in\n----\n{}\n----\n", name, text))?
}

fn shell(cmd: &str) -> Result<()> {
    let status = command(cmd).status()?;
    check_status(status)
}

fn shell_output(cmd: &str) -> Result<String> {
    let output = command(cmd).stderr(Stdio::inherit()).output()?;
    check_status(output.status)?;
    let res = String::from_utf8(output.stdout)?;
    let res = res.trim().to_string();
    println!("{}", res);
    Ok(res)
}

fn command(cmd: &str) -> Command {
    eprintln!("> {}", cmd);
    let words = cmd.split_ascii_whitespace().collect::<Vec<_>>();
    let (cmd, args) = words.split_first().unwrap();
    let mut res = Command::new(cmd);
    res.args(args);
    res
}

fn check_status(status: ExitStatus) -> Result<()> {
    if !status.success() {
        Err(format!("$status: {}", status))?;
    }
    Ok(())
}

struct Section {
    name: &'static str,
    start: Instant,
}

impl Section {
    fn new(name: &'static str) -> Section {
        println!("::group::{}", name);
        let start = Instant::now();
        Section { name, start }
    }
}

impl Drop for Section {
    fn drop(&mut self) {
        eprintln!("{}: {:.2?}", self.name, self.start.elapsed());
        println!("::endgroup::");
    }
}