aboutsummaryrefslogtreecommitdiff
path: root/.run/src/bin/RELEASE-WORKSPACE.rs
blob: 8cb4d1bab01aa59c03e33309081b80ed43f5e145 (plain) (blame)
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
use std::path::{Path, PathBuf};
use std::process;
use tools::dependency_order::{display_dependency_order, find_workspace_root};
use tools::{
    eprintln_cargo_style, println_cargo_style, run_cmd_capture_with_dir, run_cmd_with_dir,
};

/// Read the version from `[workspace.package].version` in the root Cargo.toml.
fn read_workspace_version(workspace_root: &Path) -> Option<String> {
    let content = std::fs::read_to_string(workspace_root.join("Cargo.toml")).ok()?;
    let value: toml::Value = content.parse().ok()?;
    value
        .get("workspace")?
        .get("package")?
        .get("version")?
        .as_str()
        .map(String::from)
}

/// Replace `path = "crate_name"` with `version = "VERSION"` in the file content.
fn update_path_to_version(content: &str, crate_name: &str, version: &str) -> String {
    let old = format!("path = \"{}\"", crate_name);
    let new = format!("version = \"{}\"", version);
    content.replace(&old, &new)
}

/// Restore the backup and abort.
fn abort(toml_path: &Path) -> ! {
    let backup_path = toml_path.with_extension("toml.bkup");
    if backup_path.exists() {
        if let Err(e) = std::fs::copy(&backup_path, toml_path) {
            eprintln_cargo_style!("failed to restore Cargo.toml from backup: {}", e);
        } else {
            eprintln_cargo_style!("restored Cargo.toml from backup");
        }
    }
    process::exit(1);
}

fn main() {
    // 1. Compute dependency order
    let order = display_dependency_order();
    if order.is_empty() {
        eprintln_cargo_style!("could not find workspace root or mingling crates");
        process::exit(1);
    }

    // 2. Locate workspace root
    let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
    let workspace_root = match find_workspace_root(&cwd) {
        Some(r) => r,
        None => {
            eprintln_cargo_style!("could not find workspace root");
            process::exit(1);
        }
    };

    // 3. Read current version
    let version = match read_workspace_version(&workspace_root) {
        Some(v) => v,
        None => {
            eprintln_cargo_style!("could not read workspace.package.version from Cargo.toml");
            process::exit(1);
        }
    };

    println_cargo_style!("Version: detected version {}", version);

    let toml_path = workspace_root.join("Cargo.toml");
    let backup_path = workspace_root.join("Cargo.toml.bkup");

    // 4. Backup Cargo.toml
    if let Err(e) = std::fs::copy(&toml_path, &backup_path) {
        eprintln_cargo_style!("failed to backup Cargo.toml: {}", e);
        process::exit(1);
    }
    println_cargo_style!("Backup: created Cargo.toml.bkup");

    // 5. Iterate crates in dependency order
    for crate_name in &order {
        let name = match crate_name.to_str() {
            Some(n) => n,
            None => {
                eprintln_cargo_style!("invalid crate path: {}", crate_name.display());
                abort(&toml_path);
            }
        };

        let crate_dir = workspace_root.join(crate_name);

        // 5a. Publish (capture output to check for warnings)
        println_cargo_style!("Publish: {}", name);
        let output = run_cmd_capture_with_dir("cargo publish", &crate_dir);
        let output_str = match output {
            Ok(o) => o,
            Err((code, _)) => {
                eprintln_cargo_style!("{} publish failed (exit code {})", name, code);
                abort(&toml_path);
            }
        };

        // Reject any warnings
        let warnings: Vec<&str> = output_str
            .lines()
            .filter(|l| l.to_lowercase().contains("warning:"))
            .collect();
        if !warnings.is_empty() {
            for w in &warnings {
                eprintln_cargo_style!("warning detected: {}", w);
            }
            eprintln_cargo_style!(
                "{} publish rejected due to {} warning(s)",
                name,
                warnings.len()
            );
            abort(&toml_path);
        }

        // 5b. Replace path with version in Cargo.toml
        let content = match std::fs::read_to_string(&toml_path) {
            Ok(c) => c,
            Err(e) => {
                eprintln_cargo_style!("failed to read Cargo.toml: {}", e);
                abort(&toml_path);
            }
        };
        let new_content = update_path_to_version(&content, name, &version);
        if let Err(e) = std::fs::write(&toml_path, &new_content) {
            eprintln_cargo_style!("failed to write Cargo.toml: {}", e);
            abort(&toml_path);
        }
        println_cargo_style!("Dependency: {} path -> version \"{}\"", name, version);

        // 5c. Verify workspace compiles
        println_cargo_style!("Check: workspace");
        if let Err(code) = run_cmd_with_dir("cargo check --workspace", &workspace_root) {
            eprintln_cargo_style!("cargo check --workspace failed (exit code {})", code);
            abort(&toml_path);
        }

        println_cargo_style!("Done: `{}` published", name);
    }

    println_cargo_style!("Done: all crates published successfully");
}