aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci-check-only.yml37
-rw-r--r--.github/workflows/ci.yml39
-rw-r--r--.idea/runConfigurations/Check__Codes.xml2
-rw-r--r--.idea/runConfigurations/Check__Documents.xml2
-rw-r--r--.run/src/bin/ci.rs201
-rw-r--r--.run/src/bin/cov-test.rs21
-rw-r--r--.run/src/bin/docsify-sidebar-gen.rs14
-rw-r--r--.run/src/bin/refresh-feature-mod.rs2
-rw-r--r--docs/dev/pages/abouts/ci.md51
9 files changed, 217 insertions, 152 deletions
diff --git a/.github/workflows/ci-check-only.yml b/.github/workflows/ci-check-only.yml
index 2cff3a6..fab7e99 100644
--- a/.github/workflows/ci-check-only.yml
+++ b/.github/workflows/ci-check-only.yml
@@ -6,48 +6,21 @@ on:
pull_request:
jobs:
- Check-Codes:
+ Check:
strategy:
+ fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
+ item: [build, clippy, test, arg-picker, markdown-code, examples, docs-refresh, api-docs]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
- - name: Restore .temp cache
- id: cache-temp
- uses: actions/cache@v4
- with:
- path: .temp
- key: ${{ runner.os }}-mingling-temp-codes-${{ hashFiles('.run/Cargo.toml', '.run/src/**/*.rs', 'Cargo.lock', 'verified-docs.toml', 'examples/test-examples.toml') }}
- restore-keys: |
- ${{ runner.os }}-mingling-temp-codes-
-
- - run: cargo ci --test-codes
-
- Check-Docs:
- strategy:
- matrix:
- os: [ubuntu-latest, windows-latest]
- runs-on: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v4
- - uses: actions-rust-lang/setup-rust-toolchain@v1
-
- - name: Restore .temp cache
- id: cache-temp
- uses: actions/cache@v4
- with:
- path: .temp
- key: ${{ runner.os }}-mingling-temp-docs-${{ hashFiles('.run/Cargo.toml', '.run/src/**/*.rs', 'Cargo.lock', 'verified-docs.toml', 'examples/test-examples.toml') }}
- restore-keys: |
- ${{ runner.os }}-mingling-temp-docs-
-
- - run: cargo ci --test-docs
+ - run: cargo ci --check-${{ matrix.item }}
- name: Upload docs test result on failure
- if: failure()
+ if: failure() && matrix.item == 'markdown-code'
uses: actions/upload-artifact@v4
with:
name: docs-test-result-${{ matrix.os }}.md
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index d8b133f..8210703 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -8,48 +8,21 @@ permissions:
contents: write
jobs:
- Check-Codes:
+ Check:
strategy:
+ fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
+ item: [build, clippy, test, arg-picker, markdown-code, examples, docs-refresh, api-docs]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
- - name: Restore .temp cache
- id: cache-temp
- uses: actions/cache@v4
- with:
- path: .temp
- key: ${{ runner.os }}-mingling-temp-codes-${{ hashFiles('.run/Cargo.toml', '.run/src/**/*.rs', 'Cargo.lock', 'verified-docs.toml', 'examples/test-examples.toml') }}
- restore-keys: |
- ${{ runner.os }}-mingling-temp-codes-
-
- - run: cargo ci --test-codes
-
- Check-Docs:
- strategy:
- matrix:
- os: [ubuntu-latest, windows-latest]
- runs-on: ${{ matrix.os }}
- steps:
- - uses: actions/checkout@v4
- - uses: actions-rust-lang/setup-rust-toolchain@v1
-
- - name: Restore .temp cache
- id: cache-temp
- uses: actions/cache@v4
- with:
- path: .temp
- key: ${{ runner.os }}-mingling-temp-docs-${{ hashFiles('.run/Cargo.toml', '.run/src/**/*.rs', 'Cargo.lock', 'verified-docs.toml', 'examples/test-examples.toml') }}
- restore-keys: |
- ${{ runner.os }}-mingling-temp-docs-
-
- - run: cargo ci --test-docs
+ - run: cargo ci --check-${{ matrix.item }}
- name: Upload docs test result on failure
- if: failure()
+ if: failure() && matrix.item == 'markdown-code'
uses: actions/upload-artifact@v4
with:
name: docs-test-result-${{ matrix.os }}.md
@@ -57,7 +30,7 @@ jobs:
Move-Unreleased-Tag:
if: github.ref == 'refs/heads/main'
- needs: [Check-Codes, Check-Docs]
+ needs: [Check]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
diff --git a/.idea/runConfigurations/Check__Codes.xml b/.idea/runConfigurations/Check__Codes.xml
index f117f8f..75f5e09 100644
--- a/.idea/runConfigurations/Check__Codes.xml
+++ b/.idea/runConfigurations/Check__Codes.xml
@@ -8,7 +8,7 @@
>
<option
name="command"
- value="run --manifest-path .run/Cargo.toml --bin ci --quiet -- --test-codes -y"
+ value="run --manifest-path .run/Cargo.toml --bin ci --quiet -- --check-build --check-clippy --check-test --check-arg-picker -y"
/>
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
diff --git a/.idea/runConfigurations/Check__Documents.xml b/.idea/runConfigurations/Check__Documents.xml
index b5a503b..3b40bbb 100644
--- a/.idea/runConfigurations/Check__Documents.xml
+++ b/.idea/runConfigurations/Check__Documents.xml
@@ -8,7 +8,7 @@
>
<option
name="command"
- value="run --manifest-path .run/Cargo.toml --bin ci --quiet -- --test-docs -y"
+ value="run --manifest-path .run/Cargo.toml --bin ci --quiet -- --check-markdown-code --check-examples --check-docs-refresh --check-api-docs -y"
/>
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
diff --git a/.run/src/bin/ci.rs b/.run/src/bin/ci.rs
index 39d55eb..c090eaa 100644
--- a/.run/src/bin/ci.rs
+++ b/.run/src/bin/ci.rs
@@ -11,17 +11,47 @@ fn get_ignore_dirs() -> Vec<String> {
vec![".temp".to_string()]
}
+/// A single CI step, each individually toggleable via `--check-*`.
+struct Checks {
+ build: bool,
+ clippy: bool,
+ test: bool,
+ arg_picker: bool,
+ markdown_code: bool,
+ examples: bool,
+ docs_refresh: bool,
+ api_docs: bool,
+}
+
+impl Checks {
+ fn any(&self) -> bool {
+ self.build
+ || self.clippy
+ || self.test
+ || self.arg_picker
+ || self.markdown_code
+ || self.examples
+ || self.docs_refresh
+ || self.api_docs
+ }
+}
+
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
+ -h, --help Print this help message
+ -y Auto-confirm temporary commits
+ --dirty Run CI on dirty workspace (skip temp commit & clean check)
+ --check-build Build all crates
+ --check-clippy Run clippy on all crates (-D warnings)
+ --check-test Run unit tests for all crates
+ --check-arg-picker Test the arg-picker crate
+ --check-markdown-code Verify all *.md code blocks compile
+ --check-examples Test all examples
+ --check-docs-refresh Refresh docs and fail if the tree is contaminated
+ --check-api-docs Build API docs with docs.rs features
If no specific options are given, all checks are run.
"
@@ -33,12 +63,29 @@ fn main() {
let _ = colored::control::set_virtual_terminal(true);
println!("{}", include_str!("../../../docs/res/ci_banner.txt"));
- let (auto_yes, dirty, test_docs, refresh_docs, test_codes, help) = Picker::from_args()
+ let (
+ auto_yes,
+ dirty,
+ check_build,
+ check_clippy,
+ check_test,
+ check_arg_picker,
+ check_markdown_code,
+ check_examples,
+ check_docs_refresh,
+ check_api_docs,
+ help,
+ ) = Picker::from_args()
.pick_or_default(&arg![yes: bool, 'y'])
.pick_or_default(&arg![dirty: bool])
- .pick_or_default(&arg![test_docs: bool])
- .pick_or_default(&arg![refresh_docs: bool])
- .pick_or_default(&arg![test_codes: bool])
+ .pick_or_default(&arg![check_build: bool])
+ .pick_or_default(&arg![check_clippy: bool])
+ .pick_or_default(&arg![check_test: bool])
+ .pick_or_default(&arg![check_arg_picker: bool])
+ .pick_or_default(&arg![check_markdown_code: bool])
+ .pick_or_default(&arg![check_examples: bool])
+ .pick_or_default(&arg![check_docs_refresh: bool])
+ .pick_or_default(&arg![check_api_docs: bool])
.pick_or_default(&arg![help: bool, 'h'])
.unwrap();
@@ -47,8 +94,17 @@ fn main() {
return;
}
- let any_specified = test_docs || refresh_docs || test_codes;
- let run_all = !any_specified;
+ let checks = Checks {
+ build: check_build,
+ clippy: check_clippy,
+ test: check_test,
+ arg_picker: check_arg_picker,
+ markdown_code: check_markdown_code,
+ examples: check_examples,
+ docs_refresh: check_docs_refresh,
+ api_docs: check_api_docs,
+ };
+ let run_all = !checks.any();
let needs_commit_temp = !dirty && !{ run_cmd!("git diff-index --quiet HEAD --").is_ok() };
@@ -72,7 +128,7 @@ fn main() {
}
}
- if let Err(exit_code) = ci(test_docs, test_codes, run_all) {
+ if let Err(exit_code) = ci(&checks, run_all) {
restore_workspace(needs_commit_temp).unwrap();
exit(exit_code)
}
@@ -109,47 +165,94 @@ fn restore_workspace(undo_commit: bool) -> Result<(), i32> {
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()?;
-
- println_cargo_style!("Phase: Test arg picker");
- test_arg_picker()?;
- }
-
- if run_all || test_docs {
- let mut exit_code = 0;
-
- println_cargo_style!("Phase: Verify all *.md document code blocks are compilable");
- if let Err(code) = test_docs_code_blocks() {
- exit_code = exit_code.max(code);
+/// Run one CI step.
+///
+/// When `continue_on_error` is set (used for the documentation steps in
+/// "run all" mode), a failing step is recorded and the remaining steps still
+/// execute, so every problem is reported in a single run.
+fn run_step(
+ exit_code: &mut i32,
+ phase: &str,
+ step: fn() -> Result<(), i32>,
+ continue_on_error: bool,
+) -> Result<(), i32> {
+ println_cargo_style!(phase);
+ match step() {
+ Ok(()) => Ok(()),
+ Err(code) if continue_on_error => {
+ *exit_code = (*exit_code).max(code);
+ Ok(())
}
+ Err(code) => Err(code),
+ }
+}
- println_cargo_style!("Phase: Test all examples");
- if let Err(code) = test_examples() {
- exit_code = exit_code.max(code);
- }
+fn ci(checks: &Checks, run_all: bool) -> Result<(), i32> {
+ let mut exit_code = 0;
- println_cargo_style!("Phase: Check all documentation is up to date");
- if let Err(code) = docs_refresh() {
- exit_code = exit_code.max(code);
- }
+ if run_all || checks.build {
+ run_step(
+ &mut exit_code,
+ "Phase: Scan and build all crates",
+ build_all,
+ false,
+ )?;
+ }
+ if run_all || checks.clippy {
+ run_step(
+ &mut exit_code,
+ "Phase: Run clippy for all crates",
+ clippy_all,
+ false,
+ )?;
+ }
+ if run_all || checks.test {
+ run_step(&mut exit_code, "Phase: Test all crates", test_all, false)?;
+ }
+ if run_all || checks.arg_picker {
+ run_step(
+ &mut exit_code,
+ "Phase: Test arg picker",
+ test_arg_picker,
+ false,
+ )?;
+ }
- println_cargo_style!("Phase: Try Build API docs");
- if let Err(code) = deploy_api_docs() {
- exit_code = exit_code.max(code);
- }
+ if run_all || checks.markdown_code {
+ run_step(
+ &mut exit_code,
+ "Phase: Verify all *.md document code blocks are compilable",
+ test_docs_code_blocks,
+ run_all,
+ )?;
+ }
+ if run_all || checks.examples {
+ run_step(
+ &mut exit_code,
+ "Phase: Test all examples",
+ test_examples,
+ run_all,
+ )?;
+ }
+ if run_all || checks.docs_refresh {
+ run_step(
+ &mut exit_code,
+ "Phase: Check all documentation is up to date",
+ docs_refresh,
+ run_all,
+ )?;
+ }
+ if run_all || checks.api_docs {
+ run_step(
+ &mut exit_code,
+ "Phase: Try Build API docs",
+ deploy_api_docs,
+ run_all,
+ )?;
+ }
- if exit_code != 0 {
- return Err(exit_code);
- }
+ if exit_code != 0 {
+ return Err(exit_code);
}
run_cmd!("git add --renormalize .")?;
diff --git a/.run/src/bin/cov-test.rs b/.run/src/bin/cov-test.rs
index 1599ff1..1b2342e 100644
--- a/.run/src/bin/cov-test.rs
+++ b/.run/src/bin/cov-test.rs
@@ -98,7 +98,11 @@ fn main() {
manifest.display()
))
.unwrap_or_else(|code| {
- eprintln_cargo_style!("test crate {} failed with exit code {}", manifest.display(), code);
+ eprintln_cargo_style!(
+ "test crate {} failed with exit code {}",
+ manifest.display(),
+ code
+ );
std::process::exit(code);
});
}
@@ -131,7 +135,11 @@ fn main() {
example
))
.unwrap_or_else(|code| {
- eprintln_cargo_style!("build of example {} failed with exit code {}", example, code);
+ eprintln_cargo_style!(
+ "build of example {} failed with exit code {}",
+ example,
+ code
+ );
std::process::exit(code);
});
}
@@ -232,11 +240,7 @@ fn main() {
// is generated, so the summary table is rewritten here.
let index_path = output_path.join("index.html");
if let Err(e) = recolor_report_index(&index_path) {
- eprintln_cargo_style!(
- "Warning: failed to recolor {}: {}",
- index_path.display(),
- e
- );
+ eprintln_cargo_style!("Warning: failed to recolor {}: {}", index_path.display(), e);
}
println_cargo_style!(
@@ -472,7 +476,8 @@ fn is_executable(path: &Path) -> bool {
}
#[cfg(not(unix))]
{
- path.extension().is_some_and(|e| e.eq_ignore_ascii_case("exe"))
+ path.extension()
+ .is_some_and(|e| e.eq_ignore_ascii_case("exe"))
}
}
diff --git a/.run/src/bin/docsify-sidebar-gen.rs b/.run/src/bin/docsify-sidebar-gen.rs
index 5beda7f..15ae184 100644
--- a/.run/src/bin/docsify-sidebar-gen.rs
+++ b/.run/src/bin/docsify-sidebar-gen.rs
@@ -67,10 +67,9 @@ fn find_content_dir(site_root: &Path) -> Option<PathBuf> {
entries.sort_by_key(|e| e.path());
for entry in entries {
let path = entry.path();
- if path.is_dir()
- && has_markdown_files(&path) {
- return Some(path);
- }
+ if path.is_dir() && has_markdown_files(&path) {
+ return Some(path);
+ }
}
}
@@ -255,8 +254,9 @@ fn natural_cmp(a: &str, b: &str) -> std::cmp::Ordering {
fn extract_leading_number(link: &str) -> usize {
if let Some(file_stem) = link.rsplit('/').next()
&& let Some(num_end) = file_stem.find('-')
- && let Ok(num) = file_stem[..num_end].parse::<usize>() {
- return num;
- }
+ && let Ok(num) = file_stem[..num_end].parse::<usize>()
+ {
+ return num;
+ }
usize::MAX
}
diff --git a/.run/src/bin/refresh-feature-mod.rs b/.run/src/bin/refresh-feature-mod.rs
index 2255dbc..4cd6532 100644
--- a/.run/src/bin/refresh-feature-mod.rs
+++ b/.run/src/bin/refresh-feature-mod.rs
@@ -2,7 +2,7 @@ use std::collections::BTreeSet;
use std::path::Path;
use just_fmt::snake_case;
-use just_template::{tmpl, Template};
+use just_template::{Template, tmpl};
use tools::println_cargo_style;
const CARGO_TOML_PATH: &str = "./mingling/Cargo.toml";
diff --git a/docs/dev/pages/abouts/ci.md b/docs/dev/pages/abouts/ci.md
index 8b59b72..a9be500 100644
--- a/docs/dev/pages/abouts/ci.md
+++ b/docs/dev/pages/abouts/ci.md
@@ -22,31 +22,40 @@ Simply execute:
cargo ci
```
-## CI Execution Flow
+## CI Steps
-`cargo ci` runs the following stages in order:
+Every CI step is an independent switch (`--check-*`). Running `cargo ci` with no options executes **all** steps in the order below; pass one or more `--check-*` flags to run only the selected steps.
-### 1. Code Checking Stage (run by default, or individually via `--test-codes`)
+| Step | Flag | What it does |
+| --------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Build | `--check-build` | Recursively finds all `Cargo.toml` files and runs `cargo build` for each crate in parallel (workspace members build with all documented features). |
+| Clippy | `--check-clippy` | Runs `cargo clippy ... -- -D warnings` for every crate in parallel; any warning fails the check. |
+| Test | `--check-test` | Runs `cargo test` for every crate in parallel (workspace tests run with all documented features; `arg-picker` is excluded). |
+| Arg picker | `--check-arg-picker` | Runs `cargo test -p arg-picker` with its default features. |
+| Markdown code | `--check-markdown-code` | Runs the `test-all-markdown-code` tool to verify code blocks in all `*.md` files compile. See [ABOUT_CODE_VERIFY](docs/_ABOUT_CODE_VERIFY.md). |
+| Examples | `--check-examples` | Runs the `test-examples` tool to verify all examples behave as expected. |
+| Docs up to date | `--check-docs-refresh` | Runs the documentation refresh tools and `cargo fmt`, then fails if the working tree is no longer clean (i.e. the docs were stale). |
+| API docs | `--check-api-docs` | Builds API docs with the `[package.metadata.docs.rs]` features and fails if `docs/api-docs/` is out of date. |
-- **Scan and build all crates**: Recursively finds all `Cargo.toml` files in the project and runs `cargo build` for each crate in parallel.
-- **Run Clippy on all crates**: Executes `cargo clippy ... -- -D warnings` in parallel; any warning will cause a failure.
-- **Run unit tests for all crates**: Executes `cargo test` in parallel.
+### Docs up to date in detail
-### 2. Documentation and Example Checking Stage (run by default, or individually via `--test-docs`)
+`--check-docs-refresh` runs the following documentation refresh tools in sequence:
-- **Test all examples**: Runs the `test-examples` tool.
-- **Verify Markdown code blocks compile**: Runs the `test-all-markdown-code` tool to check code blocks in all `*.md` files. See [ABOUT_CODE_VERIFY](docs/_ABOUT_CODE_VERIFY.md) for details.
-- **Check if documentation is up to date**: Runs the following documentation refresh tools in sequence:
- - `docs-code-box-fix`
- - `docsify-sidebar-gen`
- - `refresh-docs`
- - `refresh-feature-mod`
- - `sync-examples`
-- Finally, runs `cargo fmt` to unify code formatting.
+- `docs-code-box-fix`
+- `docsify-sidebar-gen`
+- `refresh-docs`
+- `refresh-feature-mod`
+- `sync-examples`
-### 3. File Normalization
+Finally, it runs `cargo fmt` to unify code formatting. Because the refresh tools regenerate derived files, running this check against stale documentation modifies the working tree — and `ci.rs` fails the run in that case. (Using `--dirty` skips the cleanliness check, which makes this flag behave like a plain "refresh docs" command.)
-Runs `git add --renormalize .` to ensure file attributes such as line endings conform to the repository configuration.
+### Combining steps
+
+When several `--check-*` flags are combined, the steps run in the order listed above. In "run all" mode (no flags given), the documentation steps all execute even if one of them fails, so every problem is reported in a single run.
+
+## File Normalization
+
+Regardless of which steps run, `cargo ci` finishes with `git add --renormalize .` to ensure file attributes such as line endings conform to the repository configuration.
## Workspace Cleanliness and Temporary Commits
@@ -67,10 +76,12 @@ To ensure reproducible CI results, `ci.rs` imposes strict requirements on the wo
`.github/workflows/ci.yml` defines the project's CI:
- Triggered on `push` to the `main` branch.
-- Runs `cargo ci` in parallel on `ubuntu-latest` and `windows-latest`.
+- A single `Check` job runs every `--check-*` step in a **step × platform** matrix (`ubuntu-latest` and `windows-latest`), i.e. `cargo ci --check-<item>` for each combination. The `.temp` build cache is no longer used; every matrix job starts from a clean workspace.
- After CI passes, the `unreleased` tag is automatically moved to the latest commit on `main`.
-### 4. API Documentation Deployment
+For non-`main` branches and pull requests, `.github/workflows/ci-check-only.yml` runs the same matrix without moving the tag or deploying.
+
+### API Documentation Deployment
After all checks pass, the `Deploy-Github-Pages` job: