From ec9edc294fd5e7e29977fc7b0e6fb953422bc0e2 Mon Sep 17 00:00:00 2001 From: 魏曹先生 <1992414357@qq.com> Date: Wed, 19 Aug 2026 05:54:00 +0800 Subject: chore: reorganize dev tools into dev/ directory and consolidate configs Move CI, dev tools, and configs from root-level scattered locations into a unified `dev/` directory structure. Update all references across build scripts, documentation, and editor configurations. Also consolidate editor config generation into build.rs for automated synchronization of rust-analyzer settings across VS Code and Zed. --- mingling_ci/src/cmd/cmd_git_lock.rs | 77 --------------- mingling_ci/src/cmd/cmd_git_unlock.rs | 116 ----------------------- mingling_ci/src/cmd/cmd_report_clean.rs | 54 ----------- mingling_ci/src/cmd/cmd_report_collect.rs | 150 ------------------------------ mingling_ci/src/cmd/cmd_show_features.rs | 26 ------ mingling_ci/src/cmd/cmd_show_manifests.rs | 71 -------------- 6 files changed, 494 deletions(-) delete mode 100644 mingling_ci/src/cmd/cmd_git_lock.rs delete mode 100644 mingling_ci/src/cmd/cmd_git_unlock.rs delete mode 100644 mingling_ci/src/cmd/cmd_report_clean.rs delete mode 100644 mingling_ci/src/cmd/cmd_report_collect.rs delete mode 100644 mingling_ci/src/cmd/cmd_show_features.rs delete mode 100644 mingling_ci/src/cmd/cmd_show_manifests.rs (limited to 'mingling_ci/src/cmd') diff --git a/mingling_ci/src/cmd/cmd_git_lock.rs b/mingling_ci/src/cmd/cmd_git_lock.rs deleted file mode 100644 index 0e9bf22..0000000 --- a/mingling_ci/src/cmd/cmd_git_lock.rs +++ /dev/null @@ -1,77 +0,0 @@ -use mingling::{ - Grouped, RenderResult, Routable, - macros::{buffer, command, r_println, renderer}, - res::ResExitCode, -}; - -use crate::Next; -use crate::git::{CI_TEMP_COMMIT_MESSAGE, LOCK_FILE, TEMP_COMMIT_MESSAGE, run_git, worktree_clean}; -use crate::res::{CargoError, MessagePrinter}; - -/// Temporarily commits the workspace so CI can run on a stable tree. -/// -/// First pins the current HEAD to the `mingling/bkup` backup branch (created -/// or force-reset). When the tree is dirty, all changes are packed into a -/// plain `TEMP` commit first so they can be restored later; the `CI TEMP` -/// commit then carries only the `MINGLING-CI-CHECKING` marker file, whose -/// content (`true`/`false`) tells `git-unlock` which restore path to take. -#[command(node = "git-lock")] -pub fn git_lock() -> Next { - if let Err(e) = run_git(["branch", "-f", "mingling/bkup", "HEAD"]) { - return ErrorGitLock(e).to_chain(); - } - - let dirty = !worktree_clean(); - if dirty { - if let Err(e) = run_git(["add", "."]) { - return ErrorGitLock(e).to_chain(); - } - if let Err(e) = run_git(["commit", "-m", TEMP_COMMIT_MESSAGE]) { - return ErrorGitLock(e).to_chain(); - } - } - - let marker = if dirty { "true" } else { "false" }; - if let Err(e) = std::fs::write(LOCK_FILE, marker) { - return ErrorGitLock(format!("failed to create {LOCK_FILE}: {e}")).to_chain(); - } - - if let Err(e) = run_git(["add", "."]) { - return ErrorGitLock(e).to_chain(); - } - if let Err(e) = run_git(["commit", "-m", CI_TEMP_COMMIT_MESSAGE]) { - return ErrorGitLock(e).to_chain(); - } - - ResultGitLock { dirty }.to_chain() -} - -/// Whether the tree was dirty (a base `TEMP` commit exists) when locking. -#[derive(Grouped)] -pub struct ResultGitLock { - dirty: bool, -} - -#[derive(Grouped, Default)] -pub struct ErrorGitLock(pub String); - -#[renderer(buffer)] -pub fn render_git_lock(r: ResultGitLock) { - if r.dirty { - r_println!("Locked: dirty workspace committed for CI"); - } else { - r_println!("Locked: clean workspace marked for CI"); - } -} - -#[renderer] -pub fn render_error_git_lock( - e: ErrorGitLock, - error: &CargoError, - exit_code: &mut ResExitCode, -) -> RenderResult { - let render_result = RenderResult::new(); - error.println(vec![format!("Git-Lock: {}", e.0)]); - exit_code.exit_code = 1; - render_result -} diff --git a/mingling_ci/src/cmd/cmd_git_unlock.rs b/mingling_ci/src/cmd/cmd_git_unlock.rs deleted file mode 100644 index 41efefc..0000000 --- a/mingling_ci/src/cmd/cmd_git_unlock.rs +++ /dev/null @@ -1,116 +0,0 @@ -use mingling::{ - Grouped, RenderResult, Routable, - macros::{arg, buffer, command, r_println, renderer}, - picker::{EntryPicker, value::Flag}, - res::ResExitCode, -}; - -use crate::git::{LOCK_FILE, TEMP_COMMIT_MARK, head_message, run_git, worktree_clean}; -use crate::res::{CargoError, MessagePrinter}; -use crate::{Entry, Next}; - -/// Undoes a CI temporary commit created by [`crate::cmd::cmd_git_lock`]. -/// -/// Only acts when the HEAD commit message contains `CI TEMP` (case-sensitive). -/// The restore path is picked by the marker file content: -/// -/// - `true`: a base `TEMP` commit with the dirty changes sits below; restore -/// by hard-resetting past the marker commit, then soft-resetting and -/// unstaging to put the user's changes back into the working tree. -/// - `false`: the tree was clean; a single hard reset back to the original -/// HEAD is enough. -/// -/// When the working tree is dirty (e.g. CI left tracked changes behind) the -/// restore still runs, but the command reports a non-zero exit code so the -/// caller knows the CI phase contaminated the repository. With `--show-diff` -/// the diff of those changes is printed before they are discarded. -#[command(node = "git-unlock")] -// `#[command]` rewrites an owned first param into the entry type, so the args -// must be passed by value even though the body only reads them. -#[allow(clippy::needless_pass_by_value)] -pub fn git_unlock(args: Entry) -> Next { - let head = head_message().unwrap_or_default(); - if !head.contains(TEMP_COMMIT_MARK) { - return ErrorGitUnlock(format!("HEAD is not a CI temporary commit: `{head}`")).to_chain(); - } - - // Record dirtiness before restoring: the restore discards those changes. - let dirty = !worktree_clean(); - - // The marker file lives in the HEAD (CI TEMP) commit, so it is readable - // from the working tree; a missing marker falls back to the clean path. - let based_on_dirty = - std::fs::read_to_string(LOCK_FILE).is_ok_and(|content| content.trim() == "true"); - - if dirty && *args.pick(&arg![show_diff: Flag]).unwrap() { - show_diff(); - } - - if let Err(e) = undo_ci_phase(based_on_dirty) { - return ErrorGitUnlock(e).to_chain(); - } - - ResultGitUnlock { dirty }.to_chain() -} - -/// Prints the tracked changes the CI run left behind, before the restore -/// discards them. Untracked files are not shown (they are removed by clean). -fn show_diff() { - let Ok(diff) = run_git(["diff", "HEAD"]) else { - return; - }; - if diff.is_empty() { - return; - } - println!("{diff}"); -} - -/// Restores the workspace, keeping the user's pre-lock changes. -/// -/// With a base `TEMP` commit (`true`) the marker commit is dropped by a hard -/// reset to `HEAD~1`, the `TEMP` commit is unwrapped into the staging area by -/// a soft reset, and a plain reset unstages it back into the working tree. -/// Without one (`false`) a single hard reset to `HEAD~1` removes the marker -/// commit and lands on the original HEAD. -fn undo_ci_phase(based_on_dirty: bool) -> Result<(), String> { - run_git(["reset", "--hard", "HEAD~1"])?; - if based_on_dirty { - // Unwrap the `TEMP` commit into the staging area, then unstage it - // back into the working tree. - run_git(["reset", "--soft", "HEAD~1"])?; - run_git(["reset"])?; - } - std::fs::remove_file(LOCK_FILE).ok(); - Ok(()) -} - -/// Whether the working tree was dirty when the unlock started. -#[derive(Grouped)] -pub struct ResultGitUnlock { - dirty: bool, -} - -#[derive(Grouped, Default)] -pub struct ErrorGitUnlock(pub String); - -#[renderer(buffer)] -pub fn render_git_unlock(r: ResultGitUnlock, exit_code: &mut ResExitCode) { - if r.dirty { - r_println!("Unlocked: workspace restored (working tree was dirty)"); - exit_code.exit_code = 1; - } else { - r_println!("Unlocked: workspace restored"); - } -} - -#[renderer] -pub fn render_error_git_unlock( - e: ErrorGitUnlock, - error: &CargoError, - exit_code: &mut ResExitCode, -) -> RenderResult { - let render_result = RenderResult::new(); - error.println(vec![format!("Git-Unlock: {}", e.0)]); - exit_code.exit_code = 1; - render_result -} diff --git a/mingling_ci/src/cmd/cmd_report_clean.rs b/mingling_ci/src/cmd/cmd_report_clean.rs deleted file mode 100644 index 976851e..0000000 --- a/mingling_ci/src/cmd/cmd_report_clean.rs +++ /dev/null @@ -1,54 +0,0 @@ -use std::path::PathBuf; - -use mingling::{ - Grouped, RenderResult, Routable, - macros::{buffer, command, r_println, renderer}, -}; - -use crate::Next; -use crate::reporter::{COLLECT_DIR, REPORT_PATH}; -use crate::res::{CargoError, MessagePrinter}; - -/// Removes collected logs and the generated report. -#[command(node = "report-clean")] -pub fn report_clean() -> Next { - let mut removed = Vec::new(); - for path in [PathBuf::from(COLLECT_DIR), PathBuf::from(REPORT_PATH)] { - match std::fs::remove_dir_all(&path).or_else(|_| std::fs::remove_file(&path)) { - Ok(()) => removed.push(path), - Err(e) if e.kind() == std::io::ErrorKind::NotFound => {} - Err(e) => { - return ErrorReportClean(format!("failed to remove {}: {e}", path.display())) - .to_chain(); - } - } - } - ResultReportClean { removed }.to_chain() -} - -/// Paths removed by `report-clean`. -#[derive(Grouped)] -pub struct ResultReportClean { - pub removed: Vec, -} - -#[derive(Grouped, Default)] -pub struct ErrorReportClean(pub String); - -#[renderer(buffer)] -pub fn render_report_clean(r: ResultReportClean) { - if r.removed.is_empty() { - r_println!("Report data already clean"); - } else { - for path in r.removed { - r_println!("Removed {}", path.display()); - } - } -} - -#[renderer] -pub fn render_error_report_clean(e: ErrorReportClean, error: &CargoError) -> RenderResult { - let render_result = RenderResult::new(); - error.println(vec![format!("Report: {}", e.0)]); - render_result -} diff --git a/mingling_ci/src/cmd/cmd_report_collect.rs b/mingling_ci/src/cmd/cmd_report_collect.rs deleted file mode 100644 index 2eff074..0000000 --- a/mingling_ci/src/cmd/cmd_report_collect.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::collections::{BTreeMap, HashMap}; -use std::path::PathBuf; - -use just_template::Template; -use mingling::{ - Grouped, RenderResult, Routable, - macros::{buffer, command, r_println, renderer}, -}; - -use crate::Next; -use crate::reporter::{COLLECT_DIR, REPORT_PATH}; -use crate::res::{CargoError, MessagePrinter, ResCollectLogs}; - -const REPORT_TEMPLATE: &str = include_str!("../../tmpls/report.md"); -const TASK_SECTION_TEMPLATE: &str = include_str!("../../tmpls/task_section.md"); - -/// Maps a package to its per-OS pass/fail status. -type OsStatuses = BTreeMap; - -/// A row in a task section: item name and its per-OS statuses. -type TaskRow<'a> = (&'a String, &'a OsStatuses); - -/// Rows grouped by task name. -type RowsByTask<'a> = BTreeMap<&'a String, Vec>>; - -#[command(node = "report-collect")] -pub fn report_collect(logs: &ResCollectLogs) -> Next { - if !PathBuf::from(COLLECT_DIR).is_dir() { - return ErrorNoCollectDir.to_chain(); - } - - // Group rows by task: task -> [(item, os_statuses)]. - let by_task: RowsByTask = - logs.statuses - .iter() - .fold(BTreeMap::new(), |mut acc, ((task, item), os_statuses)| { - acc.entry(task).or_default().push((item, os_statuses)); - acc - }); - - // Render one section per task (table rows + this task's failures). - let mut fail_count = 0; - let mut sections: Vec> = Vec::new(); - for (task, rows) in by_task { - let mut row_arms = Vec::new(); - let mut fail_arms = Vec::new(); - for (item, os_statuses) in rows { - let location = logs - .locations - .get(&(task.clone(), item.clone())) - .cloned() - .unwrap_or_default(); - row_arms.push(HashMap::from([ - ("item_name".to_string(), item.clone()), - ("location".to_string(), location), - ( - "pass_win".to_string(), - pass_cell(os_statuses.get("Windows")), - ), - ( - "pass_linux".to_string(), - pass_cell(os_statuses.get("Linux")), - ), - ("pass_mac".to_string(), pass_cell(os_statuses.get("MacOS"))), - ])); - - for (os, ok) in os_statuses { - if !ok { - let stdout = logs - .err_outputs - .get(&(task.clone(), os.clone(), item.clone())) - .cloned() - .unwrap_or_default(); - fail_arms.push(HashMap::from([ - ("item_name".to_string(), item.clone()), - ("stdout".to_string(), stdout), - ])); - fail_count += 1; - } - } - } - - let mut section = Template::from(TASK_SECTION_TEMPLATE); - section.insert_param("task_name".to_string(), task.clone()); - *section.add_impl("rows".to_string()) = row_arms; - *section.add_impl("fails".to_string()) = fail_arms; - sections.push(HashMap::from([( - "section".to_string(), - section.expand().unwrap_or_default(), - )])); - } - - let mut template = Template::from(REPORT_TEMPLATE); - - template.insert_param("date".to_string(), logs.git.date.clone()); - template.insert_param("commit_hash".to_string(), logs.git.commit_hash.clone()); - *template.add_impl("task_sections".to_string()) = sections; - - let expanded = template.expand().unwrap_or_default(); - let output = PathBuf::from(REPORT_PATH); - let parent = output.parent().expect("output path has a parent"); - - if let Err(e) = std::fs::create_dir_all(parent).and_then(|()| std::fs::write(&output, expanded)) - { - return ErrorReportWrite(format!("failed to write {}: {e}", output.display())).to_chain(); - } - - ResultCollectResults { output, fail_count }.to_chain() -} - -fn pass_cell(status: Option<&bool>) -> String { - match status { - Some(true) => "✅".to_string(), - Some(false) => "❌".to_string(), - None => "—".to_string(), - } -} - -/// The generated report. -#[derive(Grouped)] -pub struct ResultCollectResults { - pub output: PathBuf, - pub fail_count: usize, -} - -#[derive(Grouped, Default)] -pub struct ErrorNoCollectDir; - -#[derive(Grouped, Default)] -pub struct ErrorReportWrite(pub String); - -#[renderer(buffer)] -pub fn render_collect_results(r: ResultCollectResults) { - r_println!("Collected {} failing logs", r.fail_count); - r_println!("Report generated at {}", r.output.display()); -} - -#[renderer] -pub fn render_error_no_collect_dir(_: ErrorNoCollectDir, error: &CargoError) -> RenderResult { - let render_result = RenderResult::new(); - error.println(vec![format!("No collect directory: {COLLECT_DIR}")]); - render_result -} - -#[renderer] -pub fn render_error_report_write(e: ErrorReportWrite, error: &CargoError) -> RenderResult { - let render_result = RenderResult::new(); - error.println(vec![format!("Report: {}", e.0)]); - render_result -} diff --git a/mingling_ci/src/cmd/cmd_show_features.rs b/mingling_ci/src/cmd/cmd_show_features.rs deleted file mode 100644 index 5fff0c5..0000000 --- a/mingling_ci/src/cmd/cmd_show_features.rs +++ /dev/null @@ -1,26 +0,0 @@ -use mingling::{ - Grouped, - macros::{buffer, command, r_println, renderer}, -}; - -use crate::res::ResFeatureList; - -#[command(node = "show-features")] -pub fn show_features(features: &ResFeatureList) -> ResultShowFeatures { - ResultShowFeatures { - features: features.list.clone(), - } -} - -/// The docs.rs feature list of `mingling`. -#[derive(Grouped)] -pub struct ResultShowFeatures { - pub features: Vec, -} - -#[renderer(buffer)] -pub fn render_show_features(r: ResultShowFeatures) { - for feature in r.features { - r_println!("{feature}"); - } -} diff --git a/mingling_ci/src/cmd/cmd_show_manifests.rs b/mingling_ci/src/cmd/cmd_show_manifests.rs deleted file mode 100644 index 2be82d2..0000000 --- a/mingling_ci/src/cmd/cmd_show_manifests.rs +++ /dev/null @@ -1,71 +0,0 @@ -use std::path::PathBuf; - -use mingling::{ - Grouped, - macros::{buffer, command, r_println, renderer}, -}; - -use prettytable::{ - Cell, Row, Table, - format::{FormatBuilder, LinePosition, LineSeparator}, -}; - -use crate::res::Manifests; - -#[command(node = "show-manifests")] -pub fn show_manifests(manifests: &Manifests) -> ResultPrintManifests { - let mut entries: Vec = manifests - .package_dirs - .iter() - .map(|(name, path)| ManifestEntry { - name: name.clone(), - path: path.clone(), - }) - .collect(); - entries.sort_by(|a, b| a.path.cmp(&b.path)); - ResultPrintManifests { entries } -} - -/// All manifests the CI will check, sorted by path. -#[derive(Grouped)] -pub struct ResultPrintManifests { - pub entries: Vec, -} - -#[derive(Debug, Clone)] -pub struct ManifestEntry { - pub name: String, - pub path: PathBuf, -} - -#[renderer(buffer)] -pub fn render_print_manifests(r: ResultPrintManifests) { - let mut table = Table::new(); - - table.set_format( - FormatBuilder::new() - .column_separator('│') - .borders('│') - .separator(LinePosition::Top, LineSeparator::new('─', '┬', '┌', '┐')) - .separator(LinePosition::Title, LineSeparator::new('─', '┼', '├', '┤')) - .separator(LinePosition::Bottom, LineSeparator::new('─', '┴', '└', '┘')) - .padding(1, 1) - .build(), - ); - - table.set_titles(Row::new(vec![ - Cell::new("#"), - Cell::new("Package-Name"), - Cell::new("Package-Path"), - ])); - - for (index, entry) in r.entries.iter().enumerate() { - table.add_row(Row::new(vec![ - Cell::new(&(index + 1).to_string()), - Cell::new(&entry.name), - Cell::new(&entry.path.to_string_lossy()), - ])); - } - - r_println!("{table}"); -} -- cgit