aboutsummaryrefslogtreecommitdiff
path: root/.run/src
diff options
context:
space:
mode:
Diffstat (limited to '.run/src')
-rw-r--r--.run/src/bin/release.rs145
-rw-r--r--.run/src/dependency_order.rs2
-rw-r--r--.run/src/lib.rs32
3 files changed, 175 insertions, 4 deletions
diff --git a/.run/src/bin/release.rs b/.run/src/bin/release.rs
new file mode 100644
index 0000000..8cb4d1b
--- /dev/null
+++ b/.run/src/bin/release.rs
@@ -0,0 +1,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");
+}
diff --git a/.run/src/dependency_order.rs b/.run/src/dependency_order.rs
index 1fe5966..7235f12 100644
--- a/.run/src/dependency_order.rs
+++ b/.run/src/dependency_order.rs
@@ -121,7 +121,7 @@ fn topological_sort(
/// Find the workspace root by looking for a Cargo.toml with `[workspace]` members.
/// Starts from `start` and walks up the directory tree.
-fn find_workspace_root(start: &std::path::Path) -> Option<PathBuf> {
+pub fn find_workspace_root(start: &std::path::Path) -> Option<PathBuf> {
let mut current = Some(std::fs::canonicalize(start).unwrap_or_else(|_| start.to_path_buf()));
while let Some(dir) = current {
let members = get_workspace_members(&dir);
diff --git a/.run/src/lib.rs b/.run/src/lib.rs
index 1a956b1..d52c7fd 100644
--- a/.run/src/lib.rs
+++ b/.run/src/lib.rs
@@ -125,7 +125,7 @@ pub fn wprintln_cargo_style(str: impl Into<String>) {
);
}
-/// Run a shell command and return its exit status.
+/// Run a shell command in the current directory and return its exit status.
///
/// # Panics
///
@@ -135,6 +135,20 @@ pub fn wprintln_cargo_style(str: impl Into<String>) {
///
/// Returns `Err` with the exit code if the command finishes with a non-zero exit code.
pub fn run_cmd(cmd: impl Into<String>) -> Result<(), i32> {
+ let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
+ run_cmd_with_dir(cmd.into(), &cwd)
+}
+
+/// Run a shell command in the specified directory and return its exit status.
+///
+/// # Panics
+///
+/// Panics if the shell command cannot be spawned (e.g. the shell binary is not found).
+///
+/// # Errors
+///
+/// Returns `Err` with the exit code if the command finishes with a non-zero exit code.
+pub fn run_cmd_with_dir(cmd: impl Into<String>, dir: &std::path::Path) -> Result<(), i32> {
let shell = if cfg!(target_os = "windows") {
"powershell"
} else {
@@ -143,7 +157,7 @@ pub fn run_cmd(cmd: impl Into<String>) -> Result<(), i32> {
let status = std::process::Command::new(shell)
.arg("-c")
.arg(cmd.into())
- .current_dir(std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")))
+ .current_dir(dir)
.status()
.expect("failed to execute command");
@@ -160,6 +174,18 @@ pub fn run_cmd(cmd: impl Into<String>) -> Result<(), i32> {
/// On success returns `Ok(combined_output)`. On failure returns `Err((exit_code, stderr))`.
/// Stderr falls back to stdout if stderr is empty.
pub fn run_cmd_capture(cmd: impl Into<String>) -> Result<String, (i32, String)> {
+ let cwd = std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from("."));
+ run_cmd_capture_with_dir(cmd.into(), &cwd)
+}
+
+/// Run a shell command in the specified directory and capture its combined stdout+stderr output.
+///
+/// On success returns `Ok(combined_output)`. On failure returns `Err((exit_code, stderr))`.
+/// Stderr falls back to stdout if stderr is empty.
+pub fn run_cmd_capture_with_dir(
+ cmd: impl Into<String>,
+ dir: &std::path::Path,
+) -> Result<String, (i32, String)> {
let shell = if cfg!(target_os = "windows") {
"powershell"
} else {
@@ -168,7 +194,7 @@ pub fn run_cmd_capture(cmd: impl Into<String>) -> Result<String, (i32, String)>
let output = std::process::Command::new(shell)
.arg("-c")
.arg(cmd.into())
- .current_dir(std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")))
+ .current_dir(dir)
.output()
.expect("failed to execute command");