# Contributing to rcpufetch
Thank you for your interest in contributing to **rcpufetch**! This document explains
what the project is, how it's built, and how to add to it.
## Table of contents
- [Project overview](#project-overview)
- [Relationship to cpufetch](#relationship-to-cpufetch)
- [Codebase structure](#codebase-structure)
- [Architecture](#architecture)
- [The `cpu` module: arch detection](#the-cpu-module-arch-detection)
- [How the uarch tables were generated](#how-the-uarch-tables-were-generated)
- [OS gatherers](#os-gatherers)
- [The `report`/`style` printer](#the-reportstyle-printer)
- [CLI parsing (`cla.rs`)](#cli-parsing-clars)
- [Build process](#build-process)
- [Testing](#testing)
- [CI](#ci)
- [How to contribute](#how-to-contribute)
- [Adding a new microarchitecture / vendor](#adding-a-new-microarchitecture--vendor)
- [Adding a new logo](#adding-a-new-logo)
- [Adding a new OS](#adding-a-new-os)
- [Adding a new CLI flag](#adding-a-new-cli-flag)
- [Coding style](#coding-style)
- [Reporting issues](#reporting-issues)
- [Pull request process](#pull-request-process)
---
## Project overview
**rcpufetch** is a fast, cross-platform CLI tool that displays detailed CPU information
in a visually appealing way, including vendor ASCII art. It supports Linux, macOS,
Windows, and FreeBSD/OpenBSD, across x86/x86_64, ARM/AArch64, PowerPC, and RISC-V.
The tool reports: CPU model, vendor, microarchitecture, process node, physical/logical
core count, cache sizes, frequency, and ISA feature flags — rendered next to a colorized
vendor logo, or as plain text with `--no-logo`.
## Relationship to cpufetch
rcpufetch is a **Rust rewrite of [cpufetch](https://github.com/Dr-Noob/cpufetch)**, the
C tool by Dr-Noob. It is not a from-scratch design: the CLI flags, the `--style`/
`--color` model, and the microarchitecture-name lookup tables are ported from upstream
so that command-line muscle memory and output familiarity transfer directly. When in
doubt about *what* a flag should do or *what* a field should be named, upstream
cpufetch's behavior is the tiebreaker — check its
[`src/common/args.c`](https://github.com/Dr-Noob/cpufetch/blob/master/src/common/args.c)
and [`src/common/printer.c`](https://github.com/Dr-Noob/cpufetch/blob/master/src/common/printer.c)
if unsure.
Where rcpufetch differs from upstream on purpose:
- **Zero dependencies.** Everything is `std`-only — no `libc`, no WMI/COM crate, no
CLI-parsing crate. OS APIs that upstream reaches via C library calls or syscalls are
instead reached the same way `macos.rs` always has: shelling out to a stable CLI
(`sysctl`, PowerShell's `Get-CimInstance`) via `std::process::Command`. This keeps
compile times fast and the dependency tree at exactly one crate.
- **A unified data model.** Upstream C has four almost-identical `print_cpufetch_{x86,arm,ppc,riscv}`
implementations. rcpufetch has one: every OS gatherer converts its raw data into a
single `report::CpuReport`, and one printer renders all of them (see
[Architecture](#architecture)).
- **FreeBSD and OpenBSD support**, which upstream doesn't have (upstream builds x86-only
on FreeBSD and has no OpenBSD support at all) — see [`src/bsd.rs`](src/bsd.rs).
- A few things are intentionally *not* ported yet — see the README's
[Implementation notes](README.md#implementation-notes). If you want to tackle one of
those, it's a great first contribution.
## Codebase structure
```
rcpufetch/
├── Cargo.toml # zero [dependencies] -- std only
├── src/
│ ├── main.rs # entry point: parse args, dispatch by OS, print
│ ├── cla.rs # CLI argument parsing (Args, Style, help/version/completions)
│ ├── style.rs # --color/--style resolution -> ANSI escape codes
│ ├── report.rs # CpuReport data model + the one shared printer
│ ├── cpu/ # architecture-specific detection (OS-independent logic)
│ │ ├── mod.rs # ArchInfo (the arch-detection output type)
│ │ ├── x86.rs # cpuid-based detection (works on any OS)
│ │ ├── arm.rs # MIDR decoding + SoC-name hints
│ │ ├── ppc.rs # PVR decoding
│ │ ├── riscv.rs # device-tree/isa string matching
│ │ └── tables/ # generated uarch lookup tables (see below)
│ ├── linux/linux.rs # Linux gatherer: /proc/cpuinfo, sysfs
│ ├── macos/macos.rs # macOS gatherer: sysctl
│ ├── windows/windows.rs # Windows gatherer: PowerShell/WMI
│ ├── bsd.rs # FreeBSD/OpenBSD gatherer: sysctl
│ └── art/logos.rs # vendor ASCII art + color-placeholder substitution
├── scripts/test-in-podman.sh # local container smoke test (see Testing)
└── .github/workflows/ci.yml # real multi-platform CI (see CI)
```
### Key files explained
- **`src/main.rs`** — parses CLI args, dispatches on `std::env::consts::OS`
(`linux`/`macos`/`windows`/`freebsd`/`openbsd`), calls the matching gatherer's
`new()` then `to_report()`, and hands the result to `report::print`.
- **`src/cla.rs`** — hand-rolled arg parser (no `clap`/`argh`/etc.), the `Style` enum,
help/version/license text, and shell completion generators.
- **`src/style.rs`** — resolves `--style`/`--color`/`NO_COLOR` into a `Colors` struct
(3 logo colors + 2 text colors, matching upstream's palette shape) and the effective
`Style`.
- **`src/report.rs`** — the `CpuReport` struct (the one data shape every OS/arch feeds
into) and the single `print()` function that lays it out next to the logo.
- **`src/cpu/`** — everything about *identifying* a CPU that doesn't depend on which OS
you're running on. `x86.rs` genuinely doesn't depend on the OS at all (`cpuid` is a
plain instruction); `arm.rs`/`ppc.rs`/`riscv.rs` take an already-extracted register
value or string and turn it into a name, leaving the *how do I get that value on this
OS* part to the OS gatherer.
- **`src/linux/linux.rs`, `src/macos/macos.rs`, `src/windows/windows.rs`, `src/bsd.rs`**
— one struct each, with a `new() -> Result<Self, String>` that gathers raw OS data and
a `to_report() -> CpuReport` that combines it with `crate::cpu` detection.
- **`src/art/logos.rs`** — ASCII art per vendor with `$C1`/`$C2`/`$C3`/`$CR` color
placeholders, substituted at print time by `report.rs` using the resolved `Colors`.
## Architecture
### The `cpu` module: arch detection
Each architecture module exposes a `detect*` function that turns a raw,
architecture-specific identifier into an `ArchInfo { vendor, uarch, soc, process_nm,
features }`:
| `cpu::x86` | nothing (reads registers itself) | `core::arch::x86_64::__cpuid_count` — vendor string (leaf 0), family/model/stepping (leaf 1), brand string (leaves 0x80000002-4), feature bits (leaves 1 and 7) |
| `cpu::arm` | a raw MIDR `u32` | bit-decodes implementer/part/variant/revision, looks it up in `tables::arm_uarch` |
| `cpu::ppc` | a raw PVR `u32`, or a kernel-decoded name string | masked-PVR lookup in `tables::ppc_uarch`, or substring match on the string |
| `cpu::riscv` | a device-tree `uarch`/`isa` string, or a `marchid` | lookup in `tables::riscv_uarch` |
`cpu::x86` is the only one that's fully self-contained, because `cpuid` is a plain
userspace instruction on every OS — it needs no OS-specific plumbing at all, which is
why `linux.rs`, `macos.rs`, `windows.rs`, and `bsd.rs` all call it identically. ARM/PPC/
RISC-V need the OS layer to hand them a register value or string first (see
[OS gatherers](#os-gatherers)).
### How the uarch tables were generated
`src/cpu/tables/{x86,arm,ppc}_uarch.rs` are **mechanically generated** from upstream
cpufetch's C source (`src/x86/uarch.c`, `src/arm/uarch.c`, `src/ppc/uarch.c`), not
hand-transcribed — those tables are hundreds of entries and hand-copying them would be
both slow and error-prone. If you need to re-sync with upstream after they add new CPUs:
1. Get the current upstream file, e.g. `curl -O https://raw.githubusercontent.com/Dr-Noob/cpufetch/master/src/x86/uarch.c`
2. Write (or adapt) a small regex-based script that extracts each `CHECK_UARCH(...)` /
`FILL_UARCH(...)` macro invocation's arguments and emits a Rust `static` array —
this is exactly what was done originally; there's no saved script in-tree since it's
a one-off per sync, but the table *shape* documents the mapping (see the comment atop
each generated file).
3. **Preserve wildcard semantics exactly.** Upstream's matching macros treat some fields
as exact-match-required and others as optional wildcards (`NA` in C, `None` in the
generated Rust `Option<u32>`). Getting this wrong silently mismatches CPUs. For x86:
`ext_family` and `family` are always required exact matches; `ext_model`, `model`,
and `stepping` are wildcards when absent. See the `lookup_uarch` test module in
`src/cpu/x86.rs` for the exact contract, and add a test case for whatever wildcard
pattern you're touching.
4. `riscv_uarch.rs` is small enough (single digits of entries) that it's hand-maintained
directly from upstream's `src/riscv/uarch.c`/`socs.h`.
`arm.rs`'s `SOC_HINTS` table (marketing names like "Snapdragon 8 Gen 3") is **not**
generated — it's a small hand-curated subset of upstream's ~415-entry SoC database. See
the README's [Implementation notes](README.md#implementation-notes) for why, and feel
free to extend it; it's just `(needle, marketing_name)` pairs matched by substring
against `/proc/cpuinfo`'s "Hardware" field or `sysctl`'s `hw.model`.
### OS gatherers
Each OS module follows the same two-method shape:
```rust
pub struct SomeOsCpuInfo {
model: String,
vendor: String,
// ...raw OS-specific fields...
arch_info: ArchInfo,
}
impl SomeOsCpuInfo {
pub fn new() -> Result<Self, String> {
// 1. Gather raw OS data (files/sysctl/WMI)
// 2. Feed whatever's needed (MIDR bits, PVR, uarch string, or nothing for x86)
// into crate::cpu:: to get an ArchInfo
}
pub fn to_report(&self) -> CpuReport {
// Merge raw OS data + arch_info into the one shared shape
}
}
```
Concretely:
- **Linux** (`linux.rs`): parses `/proc/cpuinfo` (model/vendor/flags/`CPU implementer`/
`CPU part`/`Hardware`/`cpu`/`uarch`/`isa` fields, whichever apply on the running
architecture) and sysfs (`/sys/devices/system/cpu/.../cache/`, `.../cpufreq/`) for
cache and frequency. `detect_arch_info` is `#[cfg(target_arch = ...)]`-gated to call
the right `crate::cpu` function per architecture.
- **macOS** (`macos.rs`): shells out to `sysctl -n <key>` (`machdep.cpu.*`,
`hw.cachesize`, `hw.perflevel{0,1}.*` for Apple Silicon P/E-core caches). x86 Macs get
real `cpuid`-based detection like every other x86 OS; Apple Silicon already gets a
descriptive name straight from `machdep.cpu.brand_string`.
- **Windows** (`windows.rs`): shells out to PowerShell's `Get-CimInstance Win32_Processor`
for core counts/cache/frequency (there's no `std`-only WMI API, so this mirrors the
macOS `sysctl`-shell-out pattern rather than adding a WMI/COM crate), plus
`std::thread::available_parallelism()` for a reliable logical-core count, plus
`cpu::x86::detect()` on x86.
- **BSD** (`bsd.rs`, shared by FreeBSD and OpenBSD): shells out to `sysctl -n` for
`hw.model`/`hw.ncpu`/frequency, same pattern as macOS. One module covers both OSes
since their relevant sysctl surface is nearly identical — don't split it into
`freebsd.rs`/`openbsd.rs` unless the two genuinely diverge for a feature you're adding.
### The `report`/`style` printer
`report::CpuReport` is the single shape every gatherer converts into. `report::print()`
is the *only* place that knows how to lay text out next to a logo, wrap it, and color
it — replacing what used to be four separate, nearly-identical `display_info_*`
implementations. If you're adding a new displayed field, it goes in `CpuReport` and in
`info_lines()` inside `report.rs` — not in each OS module.
`style::resolve()` turns `--style`/`--color`/`NO_COLOR` into a `Colors` (3 logo + 2 text
RGB values) and the effective `Style`, matching upstream's palette shape. `art::logos`'s
`get_logo_lines_styled()` takes those resolved colors and substitutes them into the
`$C1`/`$C2`/`$C3` placeholders in the ASCII art (Apple's logo is the one exception —
it keeps its fixed rainbow palette regardless of `--color`, matching how real-world
Apple branding is never recolored to a single accent).
### CLI parsing (`cla.rs`)
Manual parsing over `std::env::args()`, no argument-parsing crate. `Args` holds every
flag; `Args::parse()` is one `match` over `args[i].as_str()`. This mirrors upstream
cpufetch's flag set closely enough that `cpufetch --help` and `rcpufetch --help` should
look like siblings — see [Adding a new CLI flag](#adding-a-new-cli-flag) if you're
porting one upstream has that rcpufetch doesn't yet.
## Build process
```bash
cargo build --release # zero dependencies, so this is fast and needs no network access
# beyond fetching the Rust toolchain itself
./target/release/rcpufetch
```
There is intentionally nothing else in the build: no build script, no codegen step at
build time (the uarch tables are pre-generated source files checked into the repo, not
regenerated on every build), no vendored C code, no linked system libraries beyond what
`std` itself needs. If a change you're making seems to require adding a dependency,
that's worth raising in the PR/issue first — see
[Relationship to cpufetch](#relationship-to-cpufetch) for why that bar is high here.
## Testing
```bash
cargo test # unit tests: table matching, MIDR decoding, color parsing, formatting
cargo fmt --check
cargo clippy --all-targets -- -D warnings
```
Non-trivial logic (the FMS/MIDR/PVR wildcard-matching lookups, color parsing, cache-size
formatting) has unit tests colocated in the same file under `#[cfg(test)] mod tests`.
If you add a lookup table or matching rule, add a test alongside it in the same style —
particularly for wildcard semantics, which are easy to get subtly wrong (see
[How the uarch tables were generated](#how-the-uarch-tables-were-generated)).
Because this tool fundamentally reports on the machine it runs on, some things can only
really be verified by actually running the binary on real (or real-VM) hardware for that
OS/architecture — that's what CI and `scripts/test-in-podman.sh` are for:
- `scripts/test-in-podman.sh` builds and runs rcpufetch inside a Linux container,
useful for a quick sanity check on a different kernel than your host (e.g. testing
the Linux code path from macOS). It only covers your container engine's native
architecture reliably — cross-arch emulation via qemu-user binfmt was tried and found
unreliable on at least the `podman machine`/applehv provider; don't rely on it locally.
Windows and FreeBSD/OpenBSD **cannot** run in a container at all regardless of host —
containers share the host kernel, and those OSes need their own. That's what CI's real
VMs are for.
## CI
`.github/workflows/ci.yml` runs on every push/PR:
- **`lint`**: `cargo fmt --check`, `cargo clippy -D warnings`
- **`native`**: real build + `cargo test` + binary smoke tests on `ubuntu-latest`,
`macos-latest` (arm64), `macos-13` (x86_64), `windows-latest`
- **`cross-linux`**: aarch64/ppc64le/riscv64 built *and executed* under QEMU via
[`cross`](https://github.com/cross-rs/cross) (this is reliable in GitHub's runners,
unlike local podman-machine emulation — see [Testing](#testing))
- **`freebsd`/`openbsd`**: real VMs via `vmactions/*-vm`, installing Rust natively and
running the built binary — this is what actually exercises `src/bsd.rs`
- **`cross-check-only`**: compile-only checks for targets with no runnable CI host
(i686, aarch64-freebsd, x86_64-pc-windows-gnu)
If you're adding a new architecture or OS, add a matching CI job in the same style
rather than only relying on local testing — this project's whole premise is that CPU
detection code is easy to get subtly wrong on hardware/OS combos you don't personally
have, and CI catching it is cheaper than a bug report.
## How to contribute
### Prerequisites
- Rust via [rustup.rs](https://rustup.rs/) (stable toolchain)
- For cross-target checks: `rustup target add <target>`
### Workflow
1. Fork and clone: `git clone https://github.com/yourusername/rcpufetch.git`
2. Branch: `git checkout -b feature/my-new-feature`
3. Build/run: `cargo build && cargo run -- --no-logo`
4. Test: `cargo test && cargo fmt --check && cargo clippy --all-targets -- -D warnings`
5. Commit (see [commit message format](#commit-message-format)) and push
6. Open a PR with a clear description, what you tested it on, and screenshots if the
output changed visually
### Adding a new microarchitecture / vendor
Most of the time this means re-syncing a `tables/*_uarch.rs` file from upstream (see
[How the uarch tables were generated](#how-the-uarch-tables-were-generated)) rather than
writing new detection logic — the FMS/MIDR/PVR decoding logic itself rarely needs to
change, just the table it looks entries up in.
### Adding a new logo
1. Add a `const ASCII_<VENDOR>: &str` to `src/art/logos.rs` using `$C1`/`$C2`/`$C3`
placeholders for color and `$CR` for reset (keep it under ~50 columns wide so it fits
next to the text on a normal terminal).
2. Add a match arm in `raw_logo_template()` and in `logo_lines_for_vendor()`'s default
ANSI-color arrays.
3. If it should have a named `--color` scheme (like `intel`/`amd`/etc.), add a default
RGB triplet set in `style::Colors::named()`.
4. Add the user-facing name to the `--logo` mapping in `main.rs` and to the help text /
completions in `cla.rs`.
### Adding a new OS
Follow the shape in [OS gatherers](#os-gatherers): a `new() -> Result<Self, String>`
that gathers raw data plus calls into `crate::cpu`, and a `to_report() -> CpuReport`.
Wire it into the `match os { ... }` in `main.rs`, and add a CI job (a container if the
OS can run in one, a VM action like `vmactions` if not — see [CI](#ci)).
### Adding a new CLI flag
1. Add a field to `Args` in `cla.rs` and a match arm in `Args::parse()`.
2. Add it to `print_help()` and all three shell-completion functions
(`print_fish_completions`/`print_bash_completions`/`print_zsh_completions`) — yes,
all three; there's no single source of truth for the flag list yet, which is worth
fixing if you're adding several flags at once.
3. Wire the behavior into `main.rs`/`report.rs` as appropriate.
4. Update the flag table in `README.md`.
5. If the flag exists in upstream cpufetch, match its name/semantics exactly (see
[Relationship to cpufetch](#relationship-to-cpufetch)).
## Coding style
- `///` doc comments on public items; module-level `//!` docs explaining *why* a module
exists when that's non-obvious from its name.
- `Result<T, String>` for fallible operations that report to the user; avoid adding a
custom error-enum/`anyhow`-style dependency for this — string errors are fine here.
- Comments explain *why*, not *what* — see the comments in `bsd.rs`/`x86.rs`/`report.rs`
explaining scoped-down simplifications (e.g. `arm.rs`'s `SOC_HINTS` docstring) for the
house style: name the simplification and, where there's a known ceiling, what would
justify lifting it.
- No dependencies without discussion first (see
[Relationship to cpufetch](#relationship-to-cpufetch) and [Build process](#build-process)).
- Run `cargo fmt` and `cargo clippy --all-targets -- -D warnings` before opening a PR —
CI enforces both.
## Reporting issues
**Bug reports** should include: OS + version, CPU model/vendor, `rcpufetch --version`
output, the full error/incorrect output, what you expected instead, and — if it's a
detection bug — the relevant raw source (`/proc/cpuinfo` on Linux, `sysctl -a | grep
machdep.cpu` on macOS/BSD) so the table/parsing logic can be fixed against real data.
**Feature requests** should say which OS(es)/architecture(s) it applies to and, ideally,
where the data would come from (a sysfs path, a `sysctl` key, a WMI class) — that's
usually the actual hard part.
## Pull request process
- One feature/fix per PR, with a clear description of *why*, not just *what*.
- Update `README.md`/this file if you changed user-facing behavior or architecture.
- CI (lint + native + cross-linux + freebsd + openbsd) must pass.
- Note what you actually tested on (which OS/architecture, real hardware vs. VM vs.
`scripts/test-in-podman.sh`) — given this project's whole surface area is
"behaves correctly across many OS/CPU combos," this is more useful than usual.
### Commit message format
Conventional commits: `type(scope): brief description`, types `feat`/`fix`/`docs`/
`style`/`refactor`/`test`/`chore`. Examples:
```
feat(arm): add Snapdragon 8 Gen 4 to SOC_HINTS
fix(linux): correct physical core fallback on ARM (no physical id/core id keys)
docs(contributing): document uarch table generation process
```
---
Thank you for helping make **rcpufetch** better!