aboutsummaryrefslogtreecommitdiff
path: root/.run/src/bin/ci.rs
diff options
context:
space:
mode:
author魏曹先生 <1992414357@qq.com>2026-07-10 16:54:57 +0800
committer魏曹先生 <1992414357@qq.com>2026-07-10 16:54:57 +0800
commit5d258482fb84d58c966a7ccc1b467708278d28ad (patch)
tree1a0c9d2e245d43ff7bc11d9088cf88a5264d293f /.run/src/bin/ci.rs
parenta8c8d50b6c59b103da7fd4bbb5e7ca3ff7b2f49a (diff)
refactor(devtools): rename dev_tools to .run and replace run-tools
scripts Replace the old run-tools.ps1/run-tools.sh scripts with a new unified run.ps1/run.sh launcher that supports multiple languages (PowerShell, Rust, Python, Go, C#, Nim, Perl, Ruby, Zig, and arbitrary binaries) from the `.run/src/bin` directory. Move all development tools from `dev_tools/` to `.run/`, update Cargo workspace config and relevant documentation references.
Diffstat (limited to '.run/src/bin/ci.rs')
-rw-r--r--.run/src/bin/ci.rs225
1 files changed, 225 insertions, 0 deletions
diff --git a/.run/src/bin/ci.rs b/.run/src/bin/ci.rs
new file mode 100644
index 0000000..f8aed79
--- /dev/null
+++ b/.run/src/bin/ci.rs
@@ -0,0 +1,225 @@
+use std::io::Write as _;
+use std::process::exit;
+
+use tools::{
+ cargo_tomls, crate_name_from, eprintln_cargo_style, println_cargo_style, run_cmd, run_parallel,
+};
+
+fn get_ignore_dirs() -> Vec<String> {
+ vec![
+ ".temp".to_string(),
+ "mling/res".to_string(),
+ "mling\\res".to_string(),
+ ]
+}
+
+fn print_help() {
+ println!(
+ r"
+Usage: ci [options]
+Options:
+ -h, --help Print this help message
+ -y Auto-confirm temporary commits
+ --dirty Run CI on dirty workspace (skip temp commit & clean check)
+ --refresh-docs Refresh documentation files
+ --test-docs Run documentation tests (build, clippy, test)
+ --test-codes Test examples and documentation code blocks
+
+If no specific options are given, all checks are run.
+ "
+ );
+}
+
+fn main() {
+ #[cfg(windows)]
+ let _ = colored::control::set_virtual_terminal(true);
+ println!("{}", include_str!("../../../docs/res/ci_banner.txt"));
+
+ let args: Vec<String> = std::env::args().collect();
+
+ if args.iter().any(|a| a == "-h" || a == "--help") {
+ print_help();
+ return;
+ }
+
+ let auto_yes = args.iter().any(|a| a == "-y");
+ let dirty = args.iter().any(|a| a == "--dirty");
+
+ let test_docs = args.iter().any(|a| a == "--test-docs");
+ let refresh_docs = args.iter().any(|a| a == "--refresh-docs");
+ let test_codes = args.iter().any(|a| a == "--test-codes");
+ let any_specified = test_docs || refresh_docs || test_codes;
+ let run_all = !any_specified;
+
+ let needs_commit_temp = !dirty && !{ run_cmd!("git diff-index --quiet HEAD --").is_ok() };
+
+ if needs_commit_temp {
+ if auto_yes {
+ run_cmd!("git add .").unwrap();
+ run_cmd!("git commit -m \"[DO NOT PUSH] CI TEMP [DO NOT PUSH]\"").unwrap();
+ } else {
+ print!("Working tree is not clean, temporarily commit? [y/N]:");
+ std::io::stdout().flush().unwrap();
+ let mut input = String::new();
+ std::io::stdin().read_line(&mut input).unwrap();
+ let input = input.trim();
+ if input == "y" || input == "Y" || input == "yes" || input == "Yes" {
+ run_cmd!("git add .").unwrap();
+ run_cmd!("git commit -m \"[DO NOT PUSH] CI TEMP [DO NOT PUSH]\"").unwrap();
+ } else {
+ eprintln_cargo_style!("Aborting.");
+ exit(2)
+ }
+ }
+ }
+
+ if let Err(exit_code) = ci(test_docs, test_codes, run_all) {
+ restore_workspace(needs_commit_temp).unwrap();
+ exit(exit_code)
+ }
+
+ if !dirty {
+ let is_worktree_clean = run_cmd!("git diff-index --quiet HEAD --").is_ok();
+ if !is_worktree_clean {
+ eprintln_cargo_style!("The repository was contaminated during CI, failing!");
+
+ // Print git status
+ println!();
+ let _ = run_cmd!("git status");
+
+ if needs_commit_temp {
+ restore_workspace(true).unwrap();
+ }
+ exit(1)
+ }
+ }
+
+ println_cargo_style!("Done: All check passed!");
+
+ if needs_commit_temp {
+ restore_workspace(true).unwrap();
+ }
+}
+
+fn restore_workspace(undo_commit: bool) -> Result<(), i32> {
+ run_cmd!("git reset --hard --quiet")?;
+ if undo_commit {
+ run_cmd!("git reset --soft HEAD~1 --quiet")?;
+ run_cmd!("git reset --quiet")?;
+ }
+ Ok(())
+}
+
+fn ci(test_docs: bool, test_codes: bool, run_all: bool) -> Result<(), i32> {
+ if run_all || test_codes {
+ println_cargo_style!("Phase: Scan and build all crates");
+ build_all()?;
+
+ println_cargo_style!("Phase: Run clippy for all crates");
+ clippy_all()?;
+
+ println_cargo_style!("Phase: Test all crates");
+ test_all()?;
+ }
+
+ if run_all || test_docs {
+ println_cargo_style!("Phase: Test all examples");
+ test_examples()?;
+
+ println_cargo_style!("Phase: Verify all *.md document code blocks are compilable");
+ test_docs_code_blocks()?;
+
+ println_cargo_style!("Phase: Check all documentation is up to date");
+ docs_refresh()?;
+ }
+
+ run_cmd!("git add --renormalize .")?;
+
+ Ok(())
+}
+
+fn test_examples() -> Result<(), i32> {
+ run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --color always --bin test-examples")
+}
+
+fn test_docs_code_blocks() -> Result<(), i32> {
+ run_cmd!(
+ "cargo run --manifest-path dev_tools/Cargo.toml --color always --bin test-all-markdown-code"
+ )
+}
+
+fn build_all() -> Result<(), i32> {
+ let ignore_dirs = get_ignore_dirs();
+ let cargo_tomls = cargo_tomls();
+ let mut tasks = Vec::new();
+ for cargo_toml in cargo_tomls {
+ let path = cargo_toml.parent().unwrap_or(std::path::Path::new(""));
+ let path_str = path.to_string_lossy();
+ if ignore_dirs.iter().any(|d| path_str.contains(d.as_str())) {
+ continue;
+ }
+ let label = format!("Build: {}", cargo_toml.to_string_lossy());
+ let crate_name = crate_name_from(&cargo_toml);
+ let cmd = format!(
+ "cargo build --manifest-path {} --color always",
+ cargo_toml.to_string_lossy()
+ );
+ tasks.push((label, crate_name, cmd));
+ }
+ run_parallel("Building", tasks)
+}
+
+fn clippy_all() -> Result<(), i32> {
+ let ignore_dirs = get_ignore_dirs();
+ let cargo_tomls = cargo_tomls();
+ let mut tasks = Vec::new();
+ for cargo_toml in cargo_tomls {
+ let path = cargo_toml.parent().unwrap_or(std::path::Path::new(""));
+ let path_str = path.to_string_lossy();
+ if ignore_dirs.iter().any(|d| path_str.contains(d.as_str())) {
+ continue;
+ }
+ let label = format!("Clippy: {}", cargo_toml.to_string_lossy());
+ let crate_name = crate_name_from(&cargo_toml);
+ let cmd = format!(
+ "cargo clippy --manifest-path {} --color always -- -D warnings",
+ cargo_toml.to_string_lossy()
+ );
+ tasks.push((label, crate_name, cmd));
+ }
+ run_parallel("Clippy", tasks)
+}
+
+fn test_all() -> Result<(), i32> {
+ let ignore_dirs = get_ignore_dirs();
+ let cargo_tomls = cargo_tomls();
+ let mut tasks = Vec::new();
+ for cargo_toml in cargo_tomls {
+ let path = cargo_toml.parent().unwrap_or(std::path::Path::new(""));
+ let path_str = path.to_string_lossy();
+ if ignore_dirs.iter().any(|d| path_str.contains(d.as_str())) {
+ continue;
+ }
+ let label = format!("Testing: {}", cargo_toml.to_string_lossy());
+ let crate_name = crate_name_from(&cargo_toml);
+ let cmd = format!(
+ "cargo test --manifest-path {} --color always",
+ cargo_toml.to_string_lossy()
+ );
+ tasks.push((label, crate_name, cmd));
+ }
+ run_parallel("Testing", tasks)
+}
+
+fn docs_refresh() -> Result<(), i32> {
+ println_cargo_style!("Refresh: document at `./docs/`");
+
+ run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin docs-code-box-fix")?;
+ run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin docsify-sidebar-gen")?;
+ run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin refresh-docs")?;
+ run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin refresh-feature-mod")?;
+ run_cmd!("cargo run --manifest-path dev_tools/Cargo.toml --bin sync-examples")?;
+ run_cmd!("cargo fmt")?;
+
+ Ok(())
+}