# How `cargo xtask ci` Reproduces GitHub Actions CI Jobs Locally in OpenLogi

> Learn how cargo xtask ci reproduces GitHub Actions CI jobs locally in OpenLogi. Map CI jobs to Rust enum variants and run identical shell steps on your machine.

- Repository: [Xuan Zhang/OpenLogi](https://github.com/AprilNEA/OpenLogi)
- Tags: how-to-guide
- Published: 2026-08-23

---

**`cargo xtask ci` is a development helper that mimics the CI pipeline defined in [`.github/workflows/ci.yml`](https://github.com/AprilNEA/OpenLogi/blob/main/.github/workflows/ci.yml) by mapping each GitHub Actions job to a Rust `Job` enum variant and executing an identical sequence of shell steps on your local machine.**

OpenLogi uses the `xtask` pattern to automate development workflows, and the `cargo xtask ci` command specifically allows developers to replicate complex GitHub Actions workflows locally without pushing code. This tool bridges the gap between local development and CI environments by parsing the repository's workflow definition and translating it into executable Rust code, ensuring that builds, tests, and linting steps behave identically on your workstation as they do in the cloud.

## Detecting the Host Platform at Runtime

Unlike traditional Rust compilation that relies on compile-time `cfg!` checks, the implementation in [`xtask/src/commands/ci.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/ci.rs) uses `Host::current` to detect the operating system at runtime. This design choice allows a single compiled binary to adapt its behavior across Linux, macOS, and Windows, skipping jobs that cannot run on the current platform rather than failing to compile.

## Modeling CI Jobs as Rust Enums

The core abstraction lives in [`xtask/src/commands/ci/jobs.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/ci/jobs.rs), where the `Job` enum defines variants such as `Rustfmt`, `Clippy`, and `TestsLinux`. Each variant implements a `spec()` method that returns a `Spec` struct containing:

- The display name matching the CI workflow (e.g., *"tests (macos)"*)
- Supported aliases and matrix prefixes
- Host constraints defining which operating systems can execute the job
- A participation flag for the default run set
- A human-readable caveat explaining any deviations from the true CI behavior

This declarative approach makes it trivial to add new jobs or modify existing ones while keeping the Rust source code in sync with [`.github/workflows/ci.yml`](https://github.com/AprilNEA/OpenLogi/blob/main/.github/workflows/ci.yml).

## Resolving Job Selection from CLI Arguments

The command-line interface supports three modes of operation controlled by the `Args` struct in [`ci.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ci.rs):

- **`--list`**: Prints a formatted table of every `Job` variant and its compatible hosts
- **Default run**: Invokes `Job::default_run()` to select all jobs marked with `in_default_run: true`
- **Explicit selection**: Parses job names or groups (like `tests`) through `Job::resolve`, which supports matrix prefixes for targeting specific configurations

The resolution logic handles fuzzy matching and group expansion, ensuring that `cargo xtask ci tests` correctly maps to all test-related variants regardless of the host operating system.

## Building the Execution Plan

For each selected job, the system constructs a `Plan` consisting of reusable `Step` structs defined in [`xtask/src/commands/ci/jobs/steps.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/ci/jobs/steps.rs). The `Job::plan` method first verifies host compatibility; if the current machine does not satisfy the job's constraints, the job is immediately marked as **SKIP** with a clear explanation such as *"CI runs it on linux and macos, this host is windows"*.

If the host is permitted, the plan generation proceeds:

1.  Each `Step` struct captures the executable program, command-line arguments, and required environment variables
2.  Steps are collected into a `Plan` object that maintains execution order
3.  Additional metadata like working directories and conditional logic are preserved from the original YAML workflow

## Executing Steps with CI-Equivalent Environment

The `Summary` object orchestrates actual execution in [`ci.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ci.rs). When `--dry-run` is passed, it prints the formatted command via `Step::fmt` and reports *PASS* without invoking the system. In live mode, each `Step` executes through `xshell::cmd!` with carefully curated environment variables:

```rust
// Environment injected before any step runs
CARGO_TERM_COLOR=always
CARGO_INCREMENTAL=0
RUSTFLAGS="-D warnings"

```

These three variables mirror the defaults set by the GitHub Actions runner, ensuring that terminal output formatting, incremental compilation behavior, and compiler warning levels match the cloud CI exactly.

## Reporting Results and Exit Codes

As the `Summary` iterates over plans, it categorizes each outcome as **PASS**, **FAIL**, or **SKIP**. Failed commands propagate their exit codes, and the final report displays counts for each category. This deterministic feedback loop allows developers to fix issues locally that would otherwise fail the remote CI pipeline.

## Practical Usage Examples

To inspect available jobs without running them:

```bash
devenv shell -- cargo xtask ci --list

```

To execute all jobs compatible with your current host:

```bash
devenv shell -- cargo xtask ci

```

To preview what the test suite would do without side effects:

```bash
devenv shell -- cargo xtask ci --dry-run tests

```

For programmatic integration in other Rust tools:

```rust
use xtask::commands::ci::{Args, run};

let args = Args {
    list: false,
    dry_run: true,
    jobs: vec!["rustfmt".into()],
};
run(&args).unwrap();   // prints the rustfmt command without executing it

```

## Summary

- **`cargo xtask ci`** replicates the [`.github/workflows/ci.yml`](https://github.com/AprilNEA/OpenLogi/blob/main/.github/workflows/ci.yml) pipeline by modeling jobs as a Rust `Job` enum in [`xtask/src/commands/ci/jobs.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/ci/jobs.rs)
- Runtime host detection via `Host::current` allows cross-platform usage without recompilation, automatically skipping incompatible jobs
- The CLI supports `--list` for discovery, `--dry-run` for safe previews, and explicit job selection through `Job::resolve`
- Each job generates a `Plan` of `Step` structs that capture exact shell commands and environment variables from the CI definition
- Execution via `xshell::cmd!` pre-injects GitHub Actions environment variables (`CARGO_TERM_COLOR`, `CARGO_INCREMENTAL`, `RUSTFLAGS`) to ensure behavior parity
- Entry point dispatch occurs in [`xtask/src/main.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/main.rs), with core logic residing in [`ci.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/ci.rs) and step definitions in [`jobs/steps.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/jobs/steps.rs)

## Frequently Asked Questions

### What is the xtask pattern and why does OpenLogi use it?

The xtask pattern is a Rust convention where development automation scripts are compiled as part of the workspace rather than relying on external build tools like Make or shell scripts. OpenLogi implements this in the `xtask` crate to provide type-safe, version-controlled automation that can leverage the full Rust ecosystem, ensuring that CI replication logic remains maintainable and testable alongside the main application code.

### How does `cargo xtask ci` handle jobs intended for a different operating system?

When `Job::plan` detects that the current host does not match the job's constraints, it immediately marks the job as **SKIP** and prints a descriptive message explaining the platform mismatch. This allows developers on Windows to run the tool locally while gracefully ignoring macOS- or Linux-specific test suites without throwing errors.

### What is the difference between `--dry-run` and normal execution?

The `--dry-run` flag causes the `Summary` object to print the exact command line for each `Step` via `Step::fmt` and report a simulated *PASS* status without ever calling `xshell::cmd!`. This mode is useful for verifying plan construction logic and reviewing which commands would execute given the current host and selection criteria.

### How are environment variables kept in sync with the real GitHub Actions runner?

Before executing any steps, the `run` function in [`xtask/src/commands/ci.rs`](https://github.com/AprilNEA/OpenLogi/blob/main/xtask/src/commands/ci.rs) injects three specific environment variables—`CARGO_TERM_COLOR`, `CARGO_INCREMENTAL`, and `RUSTFLAGS`—that mirror the defaults provided by GitHub Actions. This ensures that color output, incremental compilation settings, and warning escalation levels match the cloud CI environment exactly, preventing "works on my machine" discrepancies.