xtask_lib/tasks/ci/
matrix.rs

1//! `cargo xtask ci matrix` — emit GitHub Actions dynamic matrix JSON.
2//!
3//! Scans the workspace `Cargo.toml` files for feature flags and writes two
4//! outputs to `$GITHUB_OUTPUT`:
5//!
6//! - `matrix` — feature × OS entries (for the `clippy` job)
7//! - `test-matrix` — feature entries for ubuntu test jobs
8//!
9//! ```text
10//! matrix={"include":[{"label":"default","features":"","os":"ubuntu-latest"},…]}
11//! test-matrix={"include":[{"label":"default","features":"","os":"ubuntu-latest"},…]}
12//! ```
13//!
14//! ## Usage in a workflow
15//!
16//! ```yaml
17//! generate-matrix:
18//!   outputs:
19//!     matrix: ${{ steps.matrix.outputs.matrix }}
20//!     test-matrix: ${{ steps.matrix.outputs.test-matrix }}
21//!   steps:
22//!     - id: matrix
23//!       run: cargo xtask ci matrix
24//!
25//! clippy:
26//!   needs: generate-matrix
27//!   strategy:
28//!     matrix:
29//!       include: ${{ fromJson(needs.generate-matrix.outputs.matrix).include }}
30//!
31//! test:
32//!   needs: generate-matrix
33//!   strategy:
34//!     matrix:
35//!       include: ${{ fromJson(needs.generate-matrix.outputs.test-matrix).include }}
36//! ```
37
38use std::io::Write;
39
40use anyhow::{Context, Result};
41
42use crate::tasks::util::{matrix, workspace};
43
44/// Generates the feature matrix and writes both outputs to `$GITHUB_OUTPUT`.
45///
46/// # Errors
47///
48/// Returns an error if the workspace cannot be scanned, JSON serialisation
49/// fails, or `$GITHUB_OUTPUT` cannot be written.
50pub fn run() -> Result<()> {
51    let root = workspace::root()?;
52    let entries = matrix::scan(&root, matrix::CI_CLIPPY_OS)?;
53
54    let clippy_json = matrix::to_github_matrix_json(&entries)?;
55    write_github_output("matrix", &clippy_json)?;
56
57    let test_json = matrix::to_github_test_matrix_json(&entries)?;
58    write_github_output("test-matrix", &test_json)
59}
60
61fn write_github_output(key: &str, value: &str) -> Result<()> {
62    match std::env::var("GITHUB_OUTPUT") {
63        Ok(path) => {
64            let mut file = std::fs::OpenOptions::new()
65                .append(true)
66                .open(&path)
67                .with_context(|| format!("failed to open GITHUB_OUTPUT at {path}"))?;
68            writeln!(file, "{key}={value}")
69                .with_context(|| format!("failed to write to GITHUB_OUTPUT at {path}"))
70        }
71        Err(_) => {
72            println!("{value}");
73            Ok(())
74        }
75    }
76}